初始化项目
This commit is contained in:
parent
bead790d60
commit
7da90e0b51
|
|
@ -0,0 +1,6 @@
|
|||
<Solution>
|
||||
<Project Path="YuanXuan.IM.Api/YuanXuan.IM.Api.csproj" />
|
||||
<Project Path="YuanXuan.IM.Common/YuanXuan.IM.Common.csproj" />
|
||||
<Project Path="YuanXuan.IM.Core/YuanXuan.IM.Core.csproj" />
|
||||
<Project Path="YuanXuan.IM.Infrastructure/YuanXuan.IM.Infrastructure.csproj" />
|
||||
</Solution>
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
using Asp.Versioning;
|
||||
|
||||
namespace YuanXuan.IM.Api.CollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Api版本控制服务扩展类
|
||||
/// </summary>
|
||||
public static class ApiVersioningServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加Api版本控制服务
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddApiVersion(this IServiceCollection services)
|
||||
{
|
||||
services.AddApiVersioning(config =>
|
||||
{
|
||||
// 默认的Api版号
|
||||
config.DefaultApiVersion = new ApiVersion(1, 0);
|
||||
// 未指定Api版号的时候,使用默认版号
|
||||
config.AssumeDefaultVersionWhenUnspecified = true;
|
||||
// 是否在返回响应头中返回Api版本信息
|
||||
config.ReportApiVersions = true;
|
||||
|
||||
}).AddApiExplorer(options =>
|
||||
{
|
||||
// Api 版本分组名称
|
||||
options.GroupNameFormat = "'v'VVV";
|
||||
// 未指定Api版号的时候,使用默认版号
|
||||
options.SubstituteApiVersionInUrl = true;
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
using Masuit.Tools;
|
||||
using Microsoft.Extensions.DependencyModel;
|
||||
using Serilog;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using YuanXuan.IM.Common.Attributes;
|
||||
|
||||
namespace YuanXuan.IM.Api.CollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 批量注册服务
|
||||
/// </summary>
|
||||
public static class BatchRegisterServiceCollectionExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 批量注册带属性的服务和后台服务
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
public static void BatchRegisterServices(this IServiceCollection services)
|
||||
{
|
||||
var allAssembly = GetAllAssembly();
|
||||
|
||||
services.RegisterServiceByAttribute(ServiceLifetime.Singleton, allAssembly);
|
||||
services.RegisterServiceByAttribute(ServiceLifetime.Scoped, allAssembly);
|
||||
services.RegisterServiceByAttribute(ServiceLifetime.Transient, allAssembly);
|
||||
|
||||
services.RegisterBackgroundService(allAssembly);
|
||||
}
|
||||
/// <summary>
|
||||
/// 通过 InjectAttribute 批量注册服务
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <param name="serviceLifetime"></param>
|
||||
private static void RegisterServiceByAttribute(this IServiceCollection services, ServiceLifetime serviceLifetime, List<Assembly> allAssembly)
|
||||
{
|
||||
var types = allAssembly.SelectMany(t => t.GetTypes())
|
||||
.Where(t => t.GetCustomAttributes(typeof(InjectAttribute), false).Length > 0 &&
|
||||
t.GetCustomAttribute<InjectAttribute>()?.Lifetime == serviceLifetime &&
|
||||
t.IsClass && !t.IsAbstract).ToList();
|
||||
|
||||
foreach (var type in types)
|
||||
{
|
||||
var baseInterfaces = type.BaseType?.GetInterfaces();
|
||||
Type? typeInterface = null;
|
||||
|
||||
// 获取所有接口,然后过滤掉系统接口和常见的不适合DI的接口
|
||||
var allInterfaces = type.GetInterfaces();
|
||||
var filteredInterfaces = allInterfaces.Where(IsCustomInterface).ToList();
|
||||
|
||||
// 如果没有自定义接口,则使用直接注入
|
||||
if (filteredInterfaces.Count == 0)
|
||||
{
|
||||
//服务非继承自接口的直接注入
|
||||
switch (serviceLifetime)
|
||||
{
|
||||
case ServiceLifetime.Singleton: services.AddSingleton(type); break;
|
||||
case ServiceLifetime.Scoped: services.AddScoped(type); break;
|
||||
case ServiceLifetime.Transient: services.AddTransient(type); break;
|
||||
}
|
||||
Log.Information("直接注入服务: {TypeName} (生命周期: {Lifetime})", type.Name, serviceLifetime);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果有多个自定义接口,选择最合适的接口
|
||||
typeInterface = ChooseBestInterface(filteredInterfaces, type);
|
||||
|
||||
//服务继承自接口的和接口一起注入
|
||||
switch (serviceLifetime)
|
||||
{
|
||||
case ServiceLifetime.Singleton: services.AddSingleton(typeInterface, type); break;
|
||||
case ServiceLifetime.Scoped: services.AddScoped(typeInterface, type); break;
|
||||
case ServiceLifetime.Transient: services.AddTransient(typeInterface, type); break;
|
||||
}
|
||||
Log.Information("接口注入服务: {TypeName} -> {InterfaceName} (生命周期: {Lifetime})",
|
||||
type.Name, typeInterface.Name, serviceLifetime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 注册后台服务
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <param name="serviceLifetime"></param>
|
||||
private static void RegisterBackgroundService(this IServiceCollection services, List<Assembly> allAssembly)
|
||||
{
|
||||
List<Type> types = allAssembly.SelectMany(t => t.GetTypes()).Where(t => typeof(BackgroundService).IsAssignableFrom(t) && t.IsClass && !t.IsAbstract).ToList();
|
||||
|
||||
foreach (var type in types)
|
||||
{
|
||||
services.AddSingleton(typeof(IHostedService), type);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 判断是否为自定义接口(过滤掉系统接口和常见的不适合DI的接口)
|
||||
/// </summary>
|
||||
/// <param name="interfaceType">接口类型</param>
|
||||
/// <returns></returns>
|
||||
private static bool IsCustomInterface(Type interfaceType)
|
||||
{
|
||||
// 系统命名空间的接口(不适合DI)
|
||||
var systemNamespaces = new[]
|
||||
{
|
||||
"System",
|
||||
"Microsoft",
|
||||
"Windows",
|
||||
"IDisposable",
|
||||
"IComparable",
|
||||
"IEquatable",
|
||||
"IEnumerable",
|
||||
"ICollection",
|
||||
"IList",
|
||||
"IAsyncEnumerable",
|
||||
"IAsyncDisposable"
|
||||
};
|
||||
|
||||
// 如果接口名称包含系统命名空间或常见系统接口名称,则过滤掉
|
||||
if (systemNamespaces.Any(ns => interfaceType.Namespace?.StartsWith(ns) == true ||
|
||||
interfaceType.Name.StartsWith(ns)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查接口是否有自定义项目的命名空间
|
||||
var customNamespaces = new[]
|
||||
{
|
||||
"LearningOfficer.OA",
|
||||
"LearningOfficer"
|
||||
};
|
||||
|
||||
// 如果接口有自定义项目的命名空间,则认为是自定义接口
|
||||
return customNamespaces.Any(ns => interfaceType.Namespace?.StartsWith(ns) == true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从多个自定义接口中选择最合适的接口
|
||||
/// </summary>
|
||||
/// <param name="interfaces">接口列表</param>
|
||||
/// <param name="implementationType">实现类型</param>
|
||||
/// <returns></returns>
|
||||
private static Type ChooseBestInterface(List<Type> interfaces, Type implementationType)
|
||||
{
|
||||
// 如果只有一个接口,直接返回
|
||||
if (interfaces.Count == 1)
|
||||
return interfaces[0];
|
||||
|
||||
// 优先选择名称匹配的接口(例如:MyService -> IMyService)
|
||||
var serviceName = implementationType.Name;
|
||||
var preferredInterface = interfaces.FirstOrDefault(i =>
|
||||
i.Name == "I" + serviceName ||
|
||||
i.Name == serviceName.Replace("Service", "").Replace("Impl", "") + "Service");
|
||||
|
||||
if (preferredInterface != null)
|
||||
return preferredInterface;
|
||||
|
||||
// 按接口名称排序,选择最合适的
|
||||
return interfaces
|
||||
.OrderBy(i => i.Name.StartsWith("I") ? 0 : 1) // 优先选择 I 开头的接口
|
||||
.ThenBy(i => i.Name.Length) // 选择名称较短的接口
|
||||
.First();
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取全部 Assembly
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private static List<Assembly> GetAllAssembly()
|
||||
{
|
||||
var allAssemblies = new List<Assembly>();
|
||||
var loadedAssemblies = new HashSet<string>();
|
||||
|
||||
var dependencyContext = DependencyContext.Default;
|
||||
var libraries = dependencyContext.RuntimeLibraries
|
||||
.Where(lib => !lib.Serviceable && lib.Type != "package")
|
||||
.ToList();
|
||||
|
||||
foreach (var library in libraries)
|
||||
{
|
||||
try
|
||||
{
|
||||
var assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(library.Name));
|
||||
if (loadedAssemblies.Add(assembly.FullName))
|
||||
{
|
||||
allAssemblies.Add(assembly);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Log.Fatal("加载程序集失败:{0}", library.Name);
|
||||
// Log or handle the exception if necessary
|
||||
}
|
||||
}
|
||||
|
||||
return allAssemblies;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
using YuanXuan.IM.Common.Configs;
|
||||
using YuanXuan.IM.Common.Dtos.ALiYun;
|
||||
|
||||
namespace YuanXuan.IM.Api.CollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 强类型配置服务
|
||||
/// </summary>
|
||||
public static class ConfigureOptionServiceCollectionExtension
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 添加强类型配置服务
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
public static void AddConfigureOptions(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.Configure<NacosConfig>(configuration.GetSection("nacos"));
|
||||
services.AddOptions();
|
||||
services.Configure<OSSConfigResult>(configuration.GetSection("OSSConfig"));
|
||||
services.Configure<ImConfig>(configuration.GetSection("ImConfig"));
|
||||
services.Configure<HangFireSettings>(configuration.GetSection("HangFireSettings"));
|
||||
services.Configure<UpAppVersionConfig>(configuration.GetSection("UpAppVersion"));
|
||||
|
||||
services.Configure<RabbitMQConfig>(configuration.GetSection("RabbitMQ"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.Text;
|
||||
using YuanXuan.IM.Common.Configs;
|
||||
|
||||
namespace YuanXuan.IM.Api.CollectionExtensions
|
||||
{
|
||||
public static class JWTAuthServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Jwt认证服务
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <param name="configuration"></param>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddJwtAuth(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
//将配置文件中的相关内容反序列化
|
||||
var jwtOption = configuration.GetSection("Jwt").Get<JwtSettings>();
|
||||
|
||||
services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
}).AddJwtBearer(options =>
|
||||
{
|
||||
options.Events = new JwtBearerEvents
|
||||
{
|
||||
//验证失败时的处理
|
||||
OnAuthenticationFailed = context =>
|
||||
{
|
||||
//若失败类型为过期,则返回特定Header,便于客户端判断
|
||||
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
|
||||
context.Response.Headers.Add("tokenErr", "expired");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
//// 配置 SignalR 使用 JWT
|
||||
//,OnMessageReceived = context =>
|
||||
//{
|
||||
// var accessToken = context.Request.Query["access_token"];
|
||||
// var path = context.HttpContext.Request.Path;
|
||||
// if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/signalr"))
|
||||
// {
|
||||
// context.Token = accessToken;
|
||||
// }
|
||||
// return Task.CompletedTask;
|
||||
//}
|
||||
};
|
||||
|
||||
options.RequireHttpsMetadata = false;
|
||||
options.SaveToken = true;
|
||||
|
||||
options.TokenValidationParameters = new TokenValidationParameters()
|
||||
{
|
||||
ValidateIssuer = true, //是否验证Issuer
|
||||
ValidIssuer = jwtOption.Issuer, //发行人Issuer
|
||||
ValidateAudience = true, //是否验证Audience
|
||||
ValidAudience = jwtOption.Audience, //订阅人Audience
|
||||
ValidateIssuerSigningKey = true, //是否验证SecurityKey
|
||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtOption.AccessSecret)), //SecurityKey
|
||||
ValidateLifetime = true, //是否验证失效时间
|
||||
ClockSkew = TimeSpan.FromSeconds(jwtOption.ClockSkew), //过期时间容错值,解决服务器端时间不同步问题(秒)
|
||||
RequireExpirationTime = true,
|
||||
};
|
||||
});
|
||||
|
||||
services.AddAuthorization();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
using Nacos.V2.DependencyInjection;
|
||||
|
||||
namespace YuanXuan.IM.Api.CollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 强类型配置服务
|
||||
/// </summary>
|
||||
public static class NacosServiceCollectionExtension
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 添加强类型配置服务
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
public static void AddNacos(this IServiceCollection services, IConfiguration configuration, IHostBuilder host)
|
||||
{
|
||||
services.AddNacosV2Config(configuration);
|
||||
services.AddNacosV2Naming(configuration);
|
||||
host.UseNacosConfig("nacos");
|
||||
//services.AddNacosAspNet(configuration, section: "nacos");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace YuanXuan.IM.Api.CollectionExtensions
|
||||
{
|
||||
public static class PollyServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加Polly策略
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddPollyPolicies(this IServiceCollection services)
|
||||
{
|
||||
// 添加HTTP客户端工厂
|
||||
services.AddHttpClient("YarpClient")
|
||||
.ConfigurePrimaryHttpMessageHandler(() => new System.Net.Http.HttpClientHandler());
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
using YuanXuan.IM.Common.Configs;
|
||||
using YuanXuan.IM.Infrastructure.Redis;
|
||||
|
||||
namespace YuanXuan.IM.Api.CollectionExtensions
|
||||
{
|
||||
public static class RedisServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加Redis服务
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <param name="configuration"></param>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddRedis(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
var connectionStrings = configuration.GetSection("ConnectionStrings").Get<ConnectionStringsSettings>();
|
||||
|
||||
RedisHelper.Initialization(connectionStrings.Redis);
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
using Serilog;
|
||||
using Serilog.Sinks.Grafana.Loki;
|
||||
using System;
|
||||
using YuanXuan.IM.Api.CollectionExtensions;
|
||||
|
||||
namespace YuanXuan.IM.Api.CollectionExtensions
|
||||
{
|
||||
public static class SerilogServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加Serilog
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <param name="configuration"></param>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddSerilog(this IServiceCollection services, IConfiguration configuration, IHostBuilder hostBuilder, IHostEnvironment environment)
|
||||
{
|
||||
var labels = new List<LokiLabel>
|
||||
{
|
||||
new LokiLabel
|
||||
{
|
||||
Key="app",
|
||||
Value=environment.ApplicationName
|
||||
},
|
||||
new LokiLabel
|
||||
{
|
||||
Key="env",
|
||||
Value=environment.EnvironmentName
|
||||
},
|
||||
};
|
||||
|
||||
// 配置 Serilog
|
||||
var logger = new LoggerConfiguration()
|
||||
.WriteTo.Console()
|
||||
.Enrich.FromLogContext();
|
||||
|
||||
//if (environment.IsDevelopment())
|
||||
//{
|
||||
logger.MinimumLevel.Information()
|
||||
.WriteTo.GrafanaLoki(configuration["GrafanaLoki:LokiUri"], labels, new List<string>()
|
||||
{
|
||||
//"RequestId","Path"
|
||||
}, tenant: configuration["GrafanaLoki:TenantId"]);
|
||||
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// logger.MinimumLevel.Information()
|
||||
// .WriteTo.GrafanaLoki(configuration["GrafanaLoki:LokiUri"], labels);
|
||||
//}
|
||||
|
||||
Log.Logger = logger.CreateLogger();
|
||||
services.AddSerilog(Log.Logger);
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
using Serilog;
|
||||
using SqlSugar;
|
||||
using System.Text.RegularExpressions;
|
||||
using Yitter.IdGenerator;
|
||||
using YuanXuan.IM.Common.Configs;
|
||||
using YuanXuan.IM.Infrastructure.DBContext;
|
||||
|
||||
namespace YuanXuan.IM.Api.CollectionExtensions
|
||||
{
|
||||
public static class SqlSugarServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加SqlSugar
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <param name="configuration"></param>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddSqlSugar(this IServiceCollection services, IConfiguration configuration, IWebHostEnvironment env)
|
||||
{
|
||||
var connectionStrings = configuration.GetSection("ConnectionStrings").Get<ConnectionStringsSettings>();
|
||||
|
||||
//YitIdHelper.SetIdGenerator(new IdGeneratorOptions(configuration.GetValue<ushort>("SnowFlakeWorkId")));
|
||||
|
||||
// 自定义雪花ID算法 程序启动时执行一次就行
|
||||
StaticConfig.CustomSnowFlakeFunc = () =>
|
||||
{
|
||||
return YitIdHelper.NextId();
|
||||
};
|
||||
|
||||
services.AddScoped<ISqlSugarClient>(provider =>
|
||||
{
|
||||
var serviceProvider = provider; // 获取 IServiceProvider
|
||||
List<ConnectionConfig> configs = new()
|
||||
{
|
||||
new ConnectionConfig
|
||||
{
|
||||
DbType = DbType.MySql,
|
||||
ConfigId = nameof(connectionStrings.Db),
|
||||
ConnectionString = connectionStrings?.Db ?? throw new InvalidOperationException("Connection string cannot be null."),
|
||||
IsAutoCloseConnection = true,
|
||||
AopEvents = SetAopEvents(connectionStrings.Db, env.EnvironmentName, serviceProvider),
|
||||
},
|
||||
|
||||
new ConnectionConfig
|
||||
{
|
||||
DbType = DbType.MySql,
|
||||
ConfigId = nameof(connectionStrings.UserCenterDb),
|
||||
ConnectionString = connectionStrings?.UserCenterDb ?? throw new InvalidOperationException("Connection string cannot be null."),
|
||||
IsAutoCloseConnection = true,
|
||||
AopEvents = SetAopEvents(connectionStrings.UserCenterDb, env.EnvironmentName, serviceProvider),
|
||||
}
|
||||
};
|
||||
|
||||
return new SqlSugarClient(configs);
|
||||
});
|
||||
|
||||
services.AddScoped(typeof(SugarRepository<>));
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
private static AopEvents SetAopEvents(string connectionString, string environmentName, IServiceProvider serviceProvider)
|
||||
{
|
||||
var aopEvents = new AopEvents();
|
||||
|
||||
if (environmentName == Environments.Development)
|
||||
{
|
||||
// 正则表达式匹配Ip
|
||||
var ipMatch = Regex.Match(connectionString, @"((25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d)))\.){3}(25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d)))");
|
||||
|
||||
var connections = connectionString.Split(';');
|
||||
string dbNamne = string.Empty;
|
||||
foreach (var item in connections)
|
||||
{
|
||||
if (item.Contains("Database"))
|
||||
{
|
||||
dbNamne = item.Split('=')[1];
|
||||
}
|
||||
}
|
||||
|
||||
aopEvents.OnLogExecuting = (sql, pars) =>
|
||||
{
|
||||
// 打印 Sql 语句
|
||||
Console.WriteLine($"执行Sql:{Environment.NewLine}{UtilMethods.GetNativeSql(sql, pars)}");
|
||||
Log.Logger.Debug($"执行Sql:{Environment.NewLine}{UtilMethods.GetNativeSql(sql, pars)}");
|
||||
};
|
||||
}
|
||||
|
||||
//aopEvents.DataExecuting += (oldValue, entityInfo) =>
|
||||
//{
|
||||
// // 获取 ICurrentUserService
|
||||
// var currentUserService = serviceProvider.GetService<ICurrentUserService>();
|
||||
|
||||
|
||||
// //if (entityInfo.EntityColumnInfo.IsPrimarykey && entityInfo.EntityValue is BaseEntity baseEntity)
|
||||
// //{
|
||||
// // if (entityInfo.OperationType == DataFilterType.InsertByObject)
|
||||
// // {
|
||||
// // // 插入时填充创建信息
|
||||
// // baseEntity.CreatedUserId = currentUserService.GetUserId();
|
||||
// // baseEntity.CreatedUserName = currentUserService.GetUserName();
|
||||
// // baseEntity.CreatedUserRealname = currentUserService.GetUserName();
|
||||
// // }
|
||||
// // else if (entityInfo.OperationType == DataFilterType.UpdateByObject)
|
||||
// // {
|
||||
// // // 更新时填充修改信息
|
||||
// // baseEntity.ModifiedUserId = currentUserService.GetUserId();
|
||||
// // baseEntity.ModifiedUserName = currentUserService.GetUserName();
|
||||
// // baseEntity.ModifiedUserRealname = currentUserService.GetUserName();
|
||||
// // }
|
||||
// //}
|
||||
//};
|
||||
|
||||
return aopEvents;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
using LearningOfficer.OA.Mobile.Api.Filters;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace YuanXuan.IM.Api.CollectionExtensions
|
||||
{
|
||||
public static class SwaggerServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Swagger注入
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
public static void AddSwagger(this IServiceCollection services)
|
||||
{
|
||||
services.AddSwaggerGen(c =>
|
||||
{
|
||||
c.SwaggerDoc("v1", new OpenApiInfo
|
||||
{
|
||||
Title = "OA移动端Api",
|
||||
Version = "v1"
|
||||
|
||||
});
|
||||
// v2 文档
|
||||
c.SwaggerDoc("v2", new OpenApiInfo
|
||||
{
|
||||
Title = "OA移动端Api",
|
||||
Version = "v2"
|
||||
});
|
||||
// v3 文档
|
||||
c.SwaggerDoc("v3", new OpenApiInfo
|
||||
{
|
||||
Title = "OA移动端Api",
|
||||
Version = "v3"
|
||||
});
|
||||
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
|
||||
{
|
||||
Description = "在下框中输入请求头中需要添加Jwt授权Token:Bearer {Token},注意中间有空格",
|
||||
Name = "Authorization",
|
||||
In = ParameterLocation.Header,
|
||||
Type = SecuritySchemeType.ApiKey,
|
||||
BearerFormat = "JWT",
|
||||
Scheme = "Bearer"
|
||||
});
|
||||
c.AddSecurityRequirement(new OpenApiSecurityRequirement
|
||||
{
|
||||
{
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference {
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = "Bearer"
|
||||
}
|
||||
},
|
||||
new string[] { }
|
||||
}
|
||||
});
|
||||
c.SupportNonNullableReferenceTypes();
|
||||
c.ParameterFilter<NullableParameterFilter>();
|
||||
|
||||
// 获取主项目生成的 XML 文件路径
|
||||
var mainXmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
|
||||
var mainXmlPath = Path.Combine(AppContext.BaseDirectory, mainXmlFile);
|
||||
c.IncludeXmlComments(mainXmlPath, includeControllerXmlComments: true);
|
||||
|
||||
// 获取所有引用的类库 XML 文件路径
|
||||
var referencedAssemblies = Assembly.GetExecutingAssembly().GetReferencedAssemblies();
|
||||
foreach (var assemblyName in referencedAssemblies)
|
||||
{
|
||||
var libraryXmlPath = Path.Combine(AppContext.BaseDirectory, $"{assemblyName.Name}.xml");
|
||||
if (File.Exists(libraryXmlPath))
|
||||
{
|
||||
c.IncludeXmlComments(libraryXmlPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Nacos.V2;
|
||||
using Nacos.V2.Naming;
|
||||
using Nacos.V2.Naming.Dtos;
|
||||
using Newtonsoft.Json;
|
||||
using System.Threading;
|
||||
using Yarp.ReverseProxy.Configuration;
|
||||
using YuanXuan.IM.Api.Proxy;
|
||||
|
||||
namespace YuanXuan.IM.Api.CollectionExtensions
|
||||
{
|
||||
public static class YarpServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加YARP反向代理服务
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <param name="configuration"></param>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddYarpWithNacos(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
// 添加YARP服务
|
||||
services.AddReverseProxy();
|
||||
|
||||
// 注册Nacos服务发现提供者
|
||||
services.AddSingleton<NacosProxyConfigProvider>();
|
||||
services.AddSingleton<IProxyConfigProvider>(provider => provider.GetRequiredService<NacosProxyConfigProvider>());
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using YuanXuan.IM.Common.Dtos.LoginMobile;
|
||||
using YuanXuan.IM.Common.Response;
|
||||
|
||||
namespace YuanXuan.IM.Api.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// 自定义基础Api控制器
|
||||
/// </summary>
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
public class BaseApiController : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Api版本前缀
|
||||
/// </summary>
|
||||
protected const string RoutePrefix = "api/v{version:apiVersion}";
|
||||
public MobileTokenInfo ZereUser => GetTokenInfo();
|
||||
private MobileTokenInfo GetTokenInfo()
|
||||
{
|
||||
return new MobileTokenInfo
|
||||
{
|
||||
UserName = HttpContext?.User?.FindFirst("UserName")?.Value,
|
||||
UserId = HttpContext?.User?.FindFirst("UserId")?.Value,
|
||||
Jwt_id = HttpContext?.User?.FindFirst("Jwt_id")?.Value,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回成功结果
|
||||
/// </summary>
|
||||
/// <param name="data">数据</param>
|
||||
/// <returns></returns>
|
||||
protected IActionResult Success(object data = null)
|
||||
{
|
||||
return Ok(new BaseResponse<object>(200, "success", data));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回失败结果
|
||||
/// </summary>
|
||||
/// <param name="message">错误信息</param>
|
||||
/// <returns></returns>
|
||||
protected IActionResult Fail(string message)
|
||||
{
|
||||
return BadRequest(new BaseResponse<object>(400, message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace YuanXuan.IM.Api.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// 健康检查控制器
|
||||
/// </summary>
|
||||
[Route("[controller]")]
|
||||
[ApiController]
|
||||
public class HealthController : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 健康检查
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public IActionResult Check()
|
||||
{
|
||||
return Ok(new { Status = "Healthy", Timestamp = DateTime.UtcNow });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using YuanXuan.IM.Common.Dtos.LoginMobile;
|
||||
using YuanXuan.IM.Common.Helpers;
|
||||
using YuanXuan.IM.Infrastructure.Redis;
|
||||
|
||||
namespace YuanXuan.IM.Api.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// 登录授权控制器
|
||||
/// </summary>
|
||||
[Route($@"{RoutePrefix}/[controller]/[action]")]
|
||||
[ApiVersion(1.0)]
|
||||
public class LoginAuthorController : BaseApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// 登录
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Login([FromBody] LoginRequest request)
|
||||
{
|
||||
// 这里应该添加实际的登录验证逻辑
|
||||
// 暂时模拟登录成功
|
||||
var userId = "123456";
|
||||
var userName = "testuser";
|
||||
|
||||
// 生成JWT token
|
||||
var token = JwtHelper.GenerateToken(userId, userName);
|
||||
var refreshToken = JwtHelper.GenerateRefreshToken();
|
||||
|
||||
// 存储token到Redis,用于后续的验证和登出
|
||||
await RedisHelper.SetAsync($"user:token:{userId}", token, TimeSpan.FromHours(24));
|
||||
await RedisHelper.SetAsync($"user:refreshToken:{userId}", refreshToken, TimeSpan.FromDays(7));
|
||||
|
||||
return Success(new { Token = token, RefreshToken = refreshToken, UserId = userId, UserName = userName });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新Token
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> RefreshToken([FromBody] RefreshTokenRequest request)
|
||||
{
|
||||
// 验证refreshToken
|
||||
var principal = JwtHelper.GetPrincipalFromExpiredToken(request.Token);
|
||||
var userId = principal?.Claims.FirstOrDefault(c => c.Type == "sub")?.Value;
|
||||
|
||||
if (userId == null)
|
||||
{
|
||||
return Fail("无效的token");
|
||||
}
|
||||
|
||||
var storedRefreshToken = await RedisHelper.GetStringAsync($"user:refreshToken:{userId}");
|
||||
if (storedRefreshToken != request.RefreshToken)
|
||||
{
|
||||
return Fail("无效的refreshToken");
|
||||
}
|
||||
|
||||
var userName = principal?.Claims.FirstOrDefault(c => c.Type == "name")?.Value;
|
||||
var newToken = JwtHelper.GenerateToken(userId, userName);
|
||||
var newRefreshToken = JwtHelper.GenerateRefreshToken();
|
||||
|
||||
await RedisHelper.SetAsync($"user:token:{userId}", newToken, TimeSpan.FromHours(24));
|
||||
await RedisHelper.SetAsync($"user:refreshToken:{userId}", newRefreshToken, TimeSpan.FromDays(7));
|
||||
|
||||
return Success(new { Token = newToken, RefreshToken = newRefreshToken });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 登出
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Authorize]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Logout()
|
||||
{
|
||||
var userId = User.FindFirst("sub")?.Value;
|
||||
if (userId == null)
|
||||
{
|
||||
return Fail("用户未登录");
|
||||
}
|
||||
|
||||
// 从Redis中删除token
|
||||
await RedisHelper.DeleteAsync($"user:token:{userId}");
|
||||
await RedisHelper.DeleteAsync($"user:refreshToken:{userId}");
|
||||
|
||||
return Success("登出成功");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 全局登出(所有设备)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Authorize]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> GlobalLogout()
|
||||
{
|
||||
var userId = User.FindFirst("sub")?.Value;
|
||||
if (userId == null)
|
||||
{
|
||||
return Fail("用户未登录");
|
||||
}
|
||||
|
||||
// 删除所有相关的token
|
||||
await RedisHelper.DeleteAsync($"user:token:{userId}");
|
||||
await RedisHelper.DeleteAsync($"user:refreshToken:{userId}");
|
||||
// 这里可以添加更多的清理逻辑,比如删除所有设备的登录记录
|
||||
|
||||
return Success("全局登出成功");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
using Newtonsoft.Json;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Text;
|
||||
|
||||
namespace LearningOfficer.OA.Mobile.Api.Filters
|
||||
{
|
||||
public class CustomContractResolver : DefaultContractResolver
|
||||
{
|
||||
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
|
||||
{
|
||||
var property = base.CreateProperty(member, memberSerialization);
|
||||
|
||||
if (!string.IsNullOrEmpty(member.Name))
|
||||
{
|
||||
if (member.Name.Length >= 1 && char.IsUpper(member.Name[0]))
|
||||
{
|
||||
property.PropertyName = char.ToLower(member.Name[0]) + member.Name.Substring(1);
|
||||
}
|
||||
property.PropertyName = ChangeName(property.PropertyName);
|
||||
}
|
||||
return property;
|
||||
}
|
||||
/// <summary>
|
||||
/// 将下划线命名转换为小驼峰命名
|
||||
/// </summary>
|
||||
/// <param name="name">变量名</param>
|
||||
/// <returns></returns>
|
||||
private string ChangeName(string name)
|
||||
{
|
||||
Match mt = Regex.Match(name, @"_(\w*)*");
|
||||
if (mt.Success) {
|
||||
var sb = new StringBuilder();
|
||||
bool toUpper = false;
|
||||
for (int i = 0; i < name.Length; i++)
|
||||
{
|
||||
char c = name[i];
|
||||
if (c == '_')
|
||||
{
|
||||
toUpper = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(toUpper ? char.ToUpperInvariant(c) : c);
|
||||
toUpper = false;
|
||||
}
|
||||
}
|
||||
if (sb.Length > 0)
|
||||
sb[0] = char.ToLowerInvariant(sb[0]);
|
||||
return sb.ToString();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
using System.Runtime.Intrinsics.X86;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace LearningOfficer.OA.Mobile.Api.Filters
|
||||
{
|
||||
public class CustomJsonNamingPolicy : JsonNamingPolicy
|
||||
{
|
||||
public override string ConvertName(string name)
|
||||
{
|
||||
//第一个字母小写
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
return name;
|
||||
}
|
||||
if (name.Length > 1 && char.IsUpper(name[0]))
|
||||
{
|
||||
name = char.ToLower(name[0]) + name.Substring(1);
|
||||
}
|
||||
|
||||
return ChangeName(name);
|
||||
}
|
||||
private string ChangeName(string name)
|
||||
{
|
||||
Match mt = Regex.Match(name, @"_(\w*)*");
|
||||
if (mt.Success)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
bool toUpper = false;
|
||||
for (int i = 0; i < name.Length; i++)
|
||||
{
|
||||
char c = name[i];
|
||||
if (c == '_')
|
||||
{
|
||||
toUpper = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(toUpper ? char.ToUpperInvariant(c) : c);
|
||||
toUpper = false;
|
||||
}
|
||||
}
|
||||
if (sb.Length > 0)
|
||||
{
|
||||
|
||||
sb[0] = char.ToLowerInvariant(sb[0]);
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
using System.Text.RegularExpressions;
|
||||
using System.Text;
|
||||
using Mapster;
|
||||
using Masuit.Tools;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using YuanXuan.IM.Common.Response;
|
||||
using YuanXuan.IM.Common.Exceptions;
|
||||
|
||||
namespace LearningOfficer.OA.Mobile.Api.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// 全局异常捕获过滤器
|
||||
/// </summary>
|
||||
public class GlobalExceptionCatchFilter : IAsyncExceptionFilter
|
||||
{
|
||||
private readonly ILogger<GlobalExceptionCatchFilter> _logger;
|
||||
|
||||
public GlobalExceptionCatchFilter(ILogger<GlobalExceptionCatchFilter> logger)
|
||||
{
|
||||
_logger = logger; //在构造函数中注入日志处理实例
|
||||
}
|
||||
|
||||
public async Task OnExceptionAsync(ExceptionContext context)
|
||||
{
|
||||
// 如果异常没有被处理则进行处理
|
||||
if (context.ExceptionHandled == false)
|
||||
{
|
||||
// 定义返回类型
|
||||
BaseResponse<object> result;
|
||||
|
||||
// 如果为业务逻辑抛出的内部异常
|
||||
if (context.Exception is BusinessException ex)
|
||||
{
|
||||
if (ex.BussinessExceptionData != null)
|
||||
{
|
||||
result = new BaseResponse<object>((int)ex.ErrorCode, context.Exception.Message, ex.BussinessExceptionData);
|
||||
//将result的data转换为Dictionary<string, object>
|
||||
var data = result.data.ToDictionary();
|
||||
|
||||
ConvertKeysToCamelCase(data);
|
||||
result.data = data;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = new BaseResponse<object>((int)ex.ErrorCode, context.Exception.Message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 程序异常,不对外暴露程序异常细节
|
||||
result = new BaseResponse<object>((int)BusinessExceptionCode.AppError, "服务器好像出错了!"
|
||||
// ,new
|
||||
//{
|
||||
// RequestId = context.HttpContext.TraceIdentifier
|
||||
//}
|
||||
);
|
||||
|
||||
//使用日志对象 _logger 的 LogError() 方法将异常信息写入日志文件
|
||||
_logger.LogError(context.Exception, context.Exception.Message);
|
||||
}
|
||||
|
||||
context.Result = new ContentResult
|
||||
{
|
||||
// 返回状态码设置为200,表示成功
|
||||
StatusCode = StatusCodes.Status200OK,
|
||||
// 设置返回格式
|
||||
ContentType = "application/json;charset=utf-8",
|
||||
Content = result.ToJsonString()
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
// 设置为true,表示异常已经被处理了
|
||||
context.ExceptionHandled = true;
|
||||
}
|
||||
|
||||
public static void ConvertKeysToCamelCase(Dictionary<string, object> dictionary)
|
||||
{
|
||||
var keys = dictionary.Keys.ToList();
|
||||
foreach (var key in keys)
|
||||
{
|
||||
string ItemKey = key;
|
||||
if (ItemKey.Length >= 1 && char.IsUpper(ItemKey[0]))
|
||||
{
|
||||
ItemKey = char.ToLower(ItemKey[0]) + ItemKey.Substring(1);
|
||||
}
|
||||
var camelCaseKey = ChangeName(ItemKey);
|
||||
if (dictionary.ContainsKey(camelCaseKey))
|
||||
{
|
||||
dictionary[camelCaseKey] = dictionary[key];
|
||||
}
|
||||
else
|
||||
{
|
||||
dictionary.Add(camelCaseKey, dictionary[key]);
|
||||
}
|
||||
dictionary.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将下划线命名转换为小驼峰命名
|
||||
/// </summary>
|
||||
/// <param name="name">变量名</param>
|
||||
/// <returns></returns>
|
||||
private static string ChangeName(string name)
|
||||
{
|
||||
Match mt = Regex.Match(name, @"_(\w*)*");
|
||||
if (mt.Success)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
bool toUpper = false;
|
||||
for (int i = 0; i < name.Length; i++)
|
||||
{
|
||||
char c = name[i];
|
||||
if (c == '_')
|
||||
{
|
||||
toUpper = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(toUpper ? char.ToUpperInvariant(c) : c);
|
||||
toUpper = false;
|
||||
}
|
||||
}
|
||||
if (sb.Length > 0)
|
||||
sb[0] = char.ToLowerInvariant(sb[0]);
|
||||
return sb.ToString();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text;
|
||||
|
||||
namespace LearningOfficer.OA.Mobile.Api.Filters
|
||||
{
|
||||
public class GlobalOperationLogFilter : IAsyncActionFilter
|
||||
{
|
||||
private readonly ILogger<GlobalOperationLogFilter> _logger;
|
||||
|
||||
public GlobalOperationLogFilter(ILogger<GlobalOperationLogFilter> logger )
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
|
||||
{
|
||||
//// 获取请求信息
|
||||
var paramss = context.ActionDescriptor.Parameters;
|
||||
var ActionName = ((ControllerActionDescriptor)context.ActionDescriptor)?.ActionName;
|
||||
var Method = context.HttpContext.Request.Method;
|
||||
var queryString = context.HttpContext.Request.QueryString;
|
||||
var RequestUrl = context.HttpContext.Request.Host + context.HttpContext.Request.Path;
|
||||
string token = context.HttpContext.Request.Headers["Authorization"].FirstOrDefault();
|
||||
string body = "";
|
||||
var request = context.HttpContext.Request;
|
||||
var stream = request.Body;
|
||||
if (request.ContentLength != null && request.ContentLength > 0)
|
||||
{
|
||||
request.EnableBuffering();
|
||||
request.Body.Position = 0;//将读取指针迻到开始位置
|
||||
using (var reader = new StreamReader(stream, Encoding.UTF8, true, 1024, true))
|
||||
{
|
||||
body = await reader.ReadToEndAsync();
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation(context.HttpContext.TraceIdentifier + ": {RequestParams}", body);
|
||||
await next();
|
||||
}
|
||||
|
||||
private async Task<string> GetRequestBody(HttpRequest request)
|
||||
{
|
||||
request.EnableBuffering();
|
||||
var body = request.Body;
|
||||
var buffer = new byte[Convert.ToInt32(request.ContentLength)];
|
||||
await request.Body.ReadAsync(buffer, 0, buffer.Length);
|
||||
request.Body.Position = 0; // 重置流位置
|
||||
return Encoding.UTF8.GetString(buffer);
|
||||
}
|
||||
|
||||
private async Task<string> GetResponseBody(HttpResponse response)
|
||||
{
|
||||
response.Body.Seek(0, SeekOrigin.Begin);
|
||||
var body = await new StreamReader(response.Body).ReadToEndAsync();
|
||||
response.Body.Seek(0, SeekOrigin.Begin);
|
||||
return body;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
using System.Reflection;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
namespace LearningOfficer.OA.Mobile.Api.Filters
|
||||
{
|
||||
public class NullableParameterFilter : IParameterFilter
|
||||
{
|
||||
public void Apply(OpenApiParameter parameter, ParameterFilterContext context)
|
||||
{
|
||||
if (context.ApiParameterDescription.Type is null)
|
||||
return;
|
||||
|
||||
if (Nullable.GetUnderlyingType(context.ApiParameterDescription.Type) != null)
|
||||
{
|
||||
parameter.Schema.Nullable = true;
|
||||
}
|
||||
else if (context.ApiParameterDescription.Type.IsClass || context.ApiParameterDescription.Type.IsInterface)
|
||||
{
|
||||
|
||||
var nullableAttribute = context.ParameterInfo?.GetCustomAttribute<System.Runtime.CompilerServices.NullableAttribute>();
|
||||
if (nullableAttribute != null || context.ApiParameterDescription.Type.FullName?.Contains("System.Nullable") == true)
|
||||
{
|
||||
parameter.Schema.Nullable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,199 @@
|
|||
using Dm.util;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NetTaste;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using YuanXuan.IM.Common.Configs;
|
||||
using YuanXuan.IM.Common.Dtos.LoginMobile;
|
||||
using YuanXuan.IM.Common.Response;
|
||||
using YuanXuan.IM.Infrastructure.Redis;
|
||||
|
||||
namespace LearningOfficer.OA.Mobile.Api.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// 统一返回结果过滤器
|
||||
/// </summary>
|
||||
public class UniformResultActionFilter : ActionFilterAttribute
|
||||
{
|
||||
private readonly NacosConfig _nacosConfig;
|
||||
private readonly ILogger<UniformResultActionFilter> _logger;
|
||||
public UniformResultActionFilter(ILogger<UniformResultActionFilter> logger,IOptions<NacosConfig> nacosConfig)
|
||||
{
|
||||
_logger = logger;
|
||||
_nacosConfig = nacosConfig.Value;
|
||||
}
|
||||
/// <summary>
|
||||
/// 在Controller的Action执行后执行
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
public override void OnActionExecuted(ActionExecutedContext context)
|
||||
{
|
||||
//特殊处理:对有ApiResultIgnoreAttribute标签的,不进行返回结果包装,原样输出
|
||||
//var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
|
||||
//if (controllerActionDescriptor != null)
|
||||
//{
|
||||
// var isDefined = controllerActionDescriptor.EndpointMetadata.Any(a => a.GetType().Equals(typeof(ApiResultIgnoreAttribute)));
|
||||
// if (isDefined)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
//}
|
||||
// 返回结果为JsonResult的请求进行Result包装
|
||||
if (context.Result != null)
|
||||
{
|
||||
switch (context.Result)
|
||||
{
|
||||
case ObjectResult:
|
||||
{
|
||||
var result = context.Result as ObjectResult;
|
||||
context.Result = new JsonResult(new BaseResponse<object>(200, "请求成功", result.Value));
|
||||
break;
|
||||
}
|
||||
case EmptyResult:
|
||||
context.Result = new JsonResult(new BaseResponse<object>(200, "请求成功"));
|
||||
break;
|
||||
case ContentResult:
|
||||
{
|
||||
var result = context.Result as ContentResult;
|
||||
context.Result = new JsonResult(new BaseResponse<object>(200, "请求成功", result.Content));
|
||||
break;
|
||||
}
|
||||
case OkResult:
|
||||
{
|
||||
context.Result = new JsonResult(new BaseResponse<object>(200, "请求成功"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//_logger.LogInformation("");
|
||||
|
||||
base.OnActionExecuted(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在Controller的Action执行前执行
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
public override async void OnActionExecuting(ActionExecutingContext context)
|
||||
{
|
||||
var actionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
|
||||
if (actionDescriptor != null)
|
||||
{
|
||||
var methodInfo = actionDescriptor.MethodInfo;
|
||||
var hasAllowAnonymous = methodInfo.GetCustomAttributes(true)
|
||||
.OfType<AllowAnonymousAttribute>()
|
||||
.Any();
|
||||
//获取action名称
|
||||
var actionName = actionDescriptor.ActionName;
|
||||
//获取控制器名称
|
||||
var controllerName = actionDescriptor.ControllerName;
|
||||
//判断是否是刷新Token的请求
|
||||
if (controllerName == "Login" && actionName == "RefreshToken")
|
||||
{
|
||||
//获取body参数对象中token
|
||||
var requestBody = context.ActionArguments.Values.FirstOrDefault();
|
||||
if (requestBody != null)
|
||||
{
|
||||
var token = context.HttpContext.Request.Headers["Authorization"].FirstOrDefault();
|
||||
if (!string.IsNullOrEmpty(token))
|
||||
{
|
||||
token = token.Replace("Bearer ", "");
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Result = new JsonResult(new BaseResponse<object>(1000, "刷新token需要请求头同时带上原token"));
|
||||
return;
|
||||
}
|
||||
var parms = requestBody as RefreshTokenRequest;
|
||||
if (parms.Token != token)
|
||||
{
|
||||
context.Result = new JsonResult(new BaseResponse<object>(1000, "参数token与头部token不一致"));
|
||||
return;
|
||||
}
|
||||
if ((RefreshTokenExpToken(context, token)))
|
||||
{
|
||||
context.HttpContext.Response.StatusCode = 402;
|
||||
context.Result = new JsonResult(new BaseResponse<object>(402, "账号被其他人登录,请重新登录"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (!hasAllowAnonymous)
|
||||
{
|
||||
var request = context.HttpContext.Request;
|
||||
var token = request.Headers["Authorization"].FirstOrDefault();
|
||||
if (!string.IsNullOrEmpty(token))
|
||||
{
|
||||
token = token.Replace("Bearer ", "");
|
||||
}
|
||||
if (IsExpRedisToken(context, token))
|
||||
{
|
||||
context.HttpContext.Response.StatusCode = 402;
|
||||
context.Result = new JsonResult(new BaseResponse<object>(402, "账号被其他人登录,请重新登录"));
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
base.OnActionExecuting(context);
|
||||
}
|
||||
/// <summary>
|
||||
/// 是否返回过期
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
private bool IsExpRedisToken(ActionExecutingContext context, string token)
|
||||
{
|
||||
if (_nacosConfig.Namespace == "dev")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var userId = context.HttpContext.User.Claims.FirstOrDefault(c => c.Type == "UserId")?.Value;
|
||||
if (string.IsNullOrEmpty(token) || string.IsNullOrEmpty(userId))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// 验证Token是否存在于Redis中
|
||||
var redisToken = RedisHelper.Instance.HGet("Login_Token", userId);
|
||||
if (string.IsNullOrEmpty(redisToken) || token != redisToken)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/// <summary>
|
||||
/// 是否返回过期
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
private bool RefreshTokenExpToken(ActionExecutingContext context, string token)
|
||||
{
|
||||
if (_nacosConfig.Namespace == "dev")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var userId = context.HttpContext.User.Claims.FirstOrDefault(c => c.Type == "UserId")?.Value;
|
||||
if (string.IsNullOrEmpty(token) || string.IsNullOrEmpty(userId))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// 验证Token是否存在于Redis中
|
||||
var redisToken = RedisHelper.Instance.HGet("Login_Token", userId);
|
||||
//
|
||||
if (string.IsNullOrEmpty(redisToken) || token != redisToken)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Yitter.IdGenerator;
|
||||
using YuanXuan.IM.Common.Attributes;
|
||||
|
||||
namespace YuanXuan.IM.Api.Hangfire
|
||||
{
|
||||
[Inject]
|
||||
public class HangfireJobs : IAdminHangfireJobs
|
||||
{
|
||||
private readonly ILogger<HangfireJobs> _logger;
|
||||
public HangfireJobs(ILogger<HangfireJobs> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
namespace YuanXuan.IM.Api.Hangfire
|
||||
{
|
||||
public interface IAdminHangfireJobs
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
using Hangfire.Redis.StackExchange;
|
||||
|
||||
namespace YuanXuan.IM.Api.Hangfire
|
||||
{
|
||||
public class InitialHangfireService : IHostedService
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
public InitialHangfireService(IConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
}
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public void Dispose() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
using Hangfire.Dashboard;
|
||||
|
||||
namespace YuanXuan.IM.Api.Hangfire
|
||||
{
|
||||
public class MyHangfireFilter : IDashboardAuthorizationFilter
|
||||
{
|
||||
public bool Authorize(DashboardContext context)
|
||||
{
|
||||
var httpContext = context.GetHttpContext();
|
||||
|
||||
return true; // 允许远程无限制访问
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
using Hangfire;
|
||||
using Hangfire.Redis.StackExchange;
|
||||
using YuanXuan.IM.Common.Configs;
|
||||
|
||||
namespace YuanXuan.IM.Api.Hangfire
|
||||
{
|
||||
public static class WskService
|
||||
{
|
||||
public static void AddWskService(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
if (services==null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
services.AddHostedService<InitialHangfireService>();
|
||||
|
||||
var HangfireConfig = configuration.GetSection("Hangfire").Get<HangFireSettings>();
|
||||
HangFireSettings.hangfireStorage = new RedisStorage(HangfireConfig.ConnectionString, new RedisStorageOptions
|
||||
{
|
||||
|
||||
Db = HangfireConfig.Db,
|
||||
FetchTimeout = TimeSpan.FromSeconds(30),
|
||||
|
||||
});
|
||||
services.AddHangfire(config => config.UseStorage(HangFireSettings.hangfireStorage));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
using System;
|
||||
using System.Buffers.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using YuanXuan.IM.Common.Exceptions;
|
||||
using YuanXuan.IM.Common.Helpers;
|
||||
using YuanXuan.IM.Infrastructure.Redis;
|
||||
|
||||
namespace YuanXuan.IM.Api.Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// 手机跳H5用户签名帮助类
|
||||
/// </summary>
|
||||
public static class MobileToH5UserSign
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取用户跳转H5的签名,并缓存sign 1小时
|
||||
/// </summary>
|
||||
/// <param name="UserId"></param>
|
||||
/// <param name="DataId"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetUserSign(string UserId, long? DataId)
|
||||
{
|
||||
//生成sign,规则为:Id + "&" + userId
|
||||
var sign = $"{UserId}";
|
||||
if (DataId != null)
|
||||
{
|
||||
sign = $"{DataId}&" + sign;
|
||||
}
|
||||
//aes加密sign
|
||||
sign = AesEncryptHelper.EncryptToShortString(sign);
|
||||
//sign进行base64
|
||||
// 将字符串转换为字节数组
|
||||
byte[] byteArray = Encoding.UTF8.GetBytes(sign);
|
||||
// 将字节数组转换为Base64字符串
|
||||
string base64String = Convert.ToBase64String(byteArray);
|
||||
//存储到redis中,有效期1小时
|
||||
RedisHelper.Instance.Set(base64String, $"{DataId}&{UserId}", TimeSpan.FromHours(1));
|
||||
return base64String;
|
||||
}
|
||||
/// <summary>
|
||||
/// 解密用户跳转H5的签名,获取Id(有可能为null)和userId
|
||||
/// </summary>
|
||||
/// <param name="sign"></param>
|
||||
/// <param name="parCount">正确参数个数</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static (long? DataId, long UserId) DecryptUserSign(string sign, int parCount)
|
||||
{
|
||||
//判断redis中是否存在sign
|
||||
var redisSign = RedisHelper.Instance.Get(sign);
|
||||
if (string.IsNullOrEmpty(redisSign))
|
||||
{
|
||||
throw new BusinessException("链接已过期,请重新获取");
|
||||
}
|
||||
//sign的base64解码
|
||||
byte[] decodedBytes = Convert.FromBase64String(sign);
|
||||
// 转换为UTF8字符串
|
||||
string decodedString = Encoding.UTF8.GetString(decodedBytes);
|
||||
//解密sign
|
||||
sign = AesEncryptHelper.DecryptFromShortString(decodedString);
|
||||
var arr = sign.Split('&');
|
||||
if (parCount!=arr.Length)
|
||||
{
|
||||
throw new BusinessException("参数错误");
|
||||
}
|
||||
if (arr.Length == 1)
|
||||
{
|
||||
var userId = Convert.ToInt64(arr[0]);
|
||||
|
||||
return (null, userId);
|
||||
}
|
||||
else if (true)
|
||||
{
|
||||
var id = Convert.ToInt64(arr[0]);
|
||||
var userId = Convert.ToInt64(arr[1]);
|
||||
return (id, userId);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new BusinessException("参数错误");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
using Yitter.IdGenerator;
|
||||
using YuanXuan.IM.Infrastructure.Redis;
|
||||
|
||||
namespace YuanXuan.IM.Api.Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// IdWorker自动注册帮助类 需要先注册Redis
|
||||
/// </summary>
|
||||
public class WorkerIdAutoRegisterHelper
|
||||
{
|
||||
|
||||
private static System.Timers.Timer _timer;
|
||||
|
||||
public static IdGeneratorOptions GetIdGeneratorOptions()
|
||||
{
|
||||
byte workerIdBitLength = 6;
|
||||
var maxWorkId = Math.Pow(2, workerIdBitLength) - 1;
|
||||
var workIdKey = "idgen:workid";
|
||||
|
||||
var workId = GetNextWorkId();
|
||||
|
||||
while (!RedisHelper.Instance.SetNx($"{workIdKey}:{workId}", 1))
|
||||
{
|
||||
// workId 已被占用,获取下一个workId
|
||||
workId = GetNextWorkId();
|
||||
};
|
||||
|
||||
// 设置5分钟过期
|
||||
RedisHelper.Instance.Expire($"{workIdKey}:{workId}", 60 * 5);
|
||||
|
||||
// 设置定时器,每4分钟更新一次过期时间
|
||||
SetTimer(4, (s, e) =>
|
||||
{
|
||||
RedisHelper.Instance.Expire($"{workIdKey}:{workId}", 60 * 5);
|
||||
});
|
||||
|
||||
// WorkerIdBitLength + SeqBitLength 不超过 22
|
||||
return new IdGeneratorOptions
|
||||
{
|
||||
WorkerIdBitLength = workerIdBitLength,
|
||||
SeqBitLength = 6, // 数值越高,性能越好,但是Id也越长
|
||||
WorkerId = (ushort)workId
|
||||
};
|
||||
|
||||
long GetNextWorkId()
|
||||
{
|
||||
var workId = RedisHelper.Instance.IncrBy(workIdKey,1);
|
||||
if (workId > maxWorkId)
|
||||
{
|
||||
// 大于了最大可用WorkId,重置workId,并获取
|
||||
RedisHelper.Instance.Set(workIdKey, 0);
|
||||
workId = RedisHelper.Instance.IncrBy(workIdKey, 1);
|
||||
}
|
||||
Console.WriteLine($"================================================分配到的workId:{workId}===============================================================================");
|
||||
return workId;
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetTimer(int mins, ElapsedEventHandler eh)
|
||||
{
|
||||
// 创建一个 Timer 实例,并设置其相关属性
|
||||
_timer = new System.Timers.Timer(TimeSpan.FromMinutes(mins).TotalMilliseconds); // 4 分钟
|
||||
_timer.Elapsed += eh;
|
||||
_timer.AutoReset = true; // 设置 Timer 实例能否多次触发
|
||||
_timer.Enabled = true; // 启动 Timer 实例
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
|
||||
using Hangfire;
|
||||
using Hangfire.Dashboard;
|
||||
using LearningOfficer.OA.Mobile.Api.Filters;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using System.Text;
|
||||
using Yitter.IdGenerator;
|
||||
using YuanXuan.IM.Api.CollectionExtensions;
|
||||
using YuanXuan.IM.Api.Hangfire;
|
||||
using YuanXuan.IM.Api.Helper;
|
||||
using YuanXuan.IM.Api.Proxy;
|
||||
|
||||
namespace YuanXuan.IM.Api
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
|
||||
var Configuration = builder.Configuration;
|
||||
var services = builder.Services;
|
||||
//Services=============================
|
||||
services.AddControllers();
|
||||
services.AddEndpointsApiExplorer();
|
||||
services.AddSwaggerGen();
|
||||
services.AddNacos(Configuration, builder.Host);
|
||||
services.AddYarpWithNacos(Configuration);
|
||||
services.AddPollyPolicies();
|
||||
services.AddControllers().AddJsonOptions(option =>
|
||||
{
|
||||
option.JsonSerializerOptions.PropertyNamingPolicy = new CustomJsonNamingPolicy();
|
||||
});
|
||||
services.AddControllers(opt =>
|
||||
{
|
||||
opt.Filters.Add<GlobalExceptionCatchFilter>();
|
||||
opt.Filters.Add<UniformResultActionFilter>();
|
||||
opt.Filters.Add<GlobalOperationLogFilter>();
|
||||
//// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>֤<EFBFBD><D6A4><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ƽ<EFBFBD><C6BD>ɵ<EFBFBD><C9B5>豸<EFBFBD><E8B1B8>¼
|
||||
//opt.Filters.Add<AuthonizationFilter>();
|
||||
}).AddNewtonsoftJson(opt =>
|
||||
{
|
||||
//<2F><><EFBFBD><EFBFBD>ѭ<EFBFBD><D1AD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
opt.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
|
||||
//<2F><><EFBFBD>ı<EFBFBD><C4B1>ֶδ<D6B6>С
|
||||
opt.SerializerSettings.ContractResolver = new DefaultContractResolver();
|
||||
|
||||
opt.SerializerSettings.ContractResolver = new CustomContractResolver();
|
||||
//<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĭ<EFBFBD>ϸ<EFBFBD>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
opt.SerializerSettings.Converters.Add(new IsoDateTimeConverter() { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" });
|
||||
});
|
||||
|
||||
// <20><><EFBFBD>ӿ<EFBFBD><D3BF><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
services.AddCors(options =>
|
||||
{
|
||||
options.AddDefaultPolicy(builder =>
|
||||
{
|
||||
builder.AllowAnyOrigin()
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
});
|
||||
|
||||
services.BatchRegisterServices();
|
||||
services.AddApiVersion();
|
||||
services.AddJwtAuth(Configuration);
|
||||
services.AddSwagger();
|
||||
services.AddHttpClient();
|
||||
services.AddSerilog(Configuration, builder.Host, builder.Environment);
|
||||
services.AddRedis(Configuration);
|
||||
services.AddSqlSugar(Configuration, builder.Environment);
|
||||
services.AddConfigureOptions(Configuration);
|
||||
services.AddHttpContextAccessor();
|
||||
services.AddWskService(Configuration);
|
||||
services.AddHangfireServer();
|
||||
YitIdHelper.SetIdGenerator(WorkerIdAutoRegisterHelper.GetIdGeneratorOptions());
|
||||
|
||||
var app = builder.Build();
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI(c =>
|
||||
{
|
||||
c.SwaggerEndpoint("/swagger/v1/swagger.json", "YuanXuan-IM v1");
|
||||
});
|
||||
app.UseCors();
|
||||
}
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
if (context.Request.Method.Equals("POST", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
context.Request.EnableBuffering();
|
||||
using (var reader = new StreamReader(context.Request.Body, encoding: Encoding.UTF8
|
||||
, detectEncodingFromByteOrderMarks: false, leaveOpen: true))
|
||||
{
|
||||
var body = await reader.ReadToEndAsync();
|
||||
context.Items.Add("body", body);
|
||||
context.Request.Body.Position = 0;
|
||||
}
|
||||
}
|
||||
if (context.Request.Method.Equals("Put", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
context.Request.EnableBuffering();
|
||||
|
||||
using (var reader = new StreamReader(context.Request.Body, encoding: Encoding.UTF8
|
||||
, detectEncodingFromByteOrderMarks: false, leaveOpen: true))
|
||||
{
|
||||
var body = await reader.ReadToEndAsync();
|
||||
context.Items.Add("body", body);
|
||||
context.Request.Body.Position = 0;
|
||||
}
|
||||
}
|
||||
await next.Invoke();
|
||||
});
|
||||
app.UseHangfireDashboard("/hang", new DashboardOptions
|
||||
{
|
||||
IgnoreAntiforgeryToken = true,
|
||||
DashboardTitle = "Hangfire<72><65><EFBFBD>",
|
||||
Authorization = new[] { new MyHangfireFilter() },
|
||||
IsReadOnlyFunc = (DashboardContext context) => false
|
||||
});
|
||||
var LocalTimeZone = new RecurringJobOptions
|
||||
{
|
||||
TimeZone = TimeZoneInfo.Local
|
||||
};
|
||||
//RecurringJob.AddOrUpdate<IAdminHangfireJobs>("OverdueTasks", x => x.OverdueTasks(), "0 5 0 * * ?", LocalTimeZone);
|
||||
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
// 映射YARP反向代理
|
||||
app.MapReverseProxy();
|
||||
|
||||
|
||||
|
||||
app.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:34020",
|
||||
"sslPort": 44342
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "http://localhost:5070",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "https://localhost:7045;http://localhost:5070",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,269 @@
|
|||
using Nacos.V2;
|
||||
using Newtonsoft.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Yarp.ReverseProxy.Configuration;
|
||||
|
||||
namespace YuanXuan.IM.Api.Proxy
|
||||
{
|
||||
/// <summary>
|
||||
/// Nacos代理配置提供者
|
||||
/// </summary>
|
||||
public class NacosProxyConfigProvider : IProxyConfigProvider
|
||||
{
|
||||
private readonly INacosNamingService _nacosNamingService;
|
||||
private readonly IConfiguration _configuration;
|
||||
private volatile IProxyConfig _config;
|
||||
private CancellationTokenSource _cts;
|
||||
|
||||
public NacosProxyConfigProvider(INacosNamingService nacosNamingService, IConfiguration configuration)
|
||||
{
|
||||
_nacosNamingService = nacosNamingService;
|
||||
_configuration = configuration;
|
||||
|
||||
// 立即从Nacos获取服务实例
|
||||
_config = CreateConfig();
|
||||
// 通知YARP配置已变更
|
||||
ProxyConfig.SignalChange();
|
||||
_cts = new CancellationTokenSource();
|
||||
|
||||
// 启动后台任务,定期从Nacos更新服务实例
|
||||
Task.Run(async () => await UpdateConfigPeriodically(), _cts.Token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建配置
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private IProxyConfig CreateConfig()
|
||||
{
|
||||
var routes = new List<RouteConfig>
|
||||
{
|
||||
new RouteConfig
|
||||
{
|
||||
RouteId = "api_route",
|
||||
ClusterId = "api_cluster",
|
||||
Match = new RouteMatch
|
||||
{
|
||||
Path = "/api/{**catch-all}"
|
||||
}
|
||||
},
|
||||
new RouteConfig
|
||||
{
|
||||
RouteId = "user_route",
|
||||
ClusterId = "user_cluster",
|
||||
Match = new RouteMatch
|
||||
{
|
||||
Path = "/user/{**catch-all}"
|
||||
}
|
||||
},
|
||||
new RouteConfig
|
||||
{
|
||||
RouteId = "testdemo1",
|
||||
ClusterId = "test_cluster",
|
||||
Match = new RouteMatch
|
||||
{
|
||||
Path = "/test/{**catch-all}"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var clusters = new List<ClusterConfig>();
|
||||
|
||||
try
|
||||
{
|
||||
// 从Nacos获取YuanXuan.Api服务实例,指定分组为qx-im
|
||||
var apiServiceInstances = _nacosNamingService.GetAllInstances("YuanXuan.Api", "qx-im").Result;
|
||||
|
||||
if (apiServiceInstances.Any())
|
||||
{
|
||||
// 创建api_cluster集群配置
|
||||
var apiDestinations = new Dictionary<string, DestinationConfig>();
|
||||
|
||||
foreach (var instance in apiServiceInstances)
|
||||
{
|
||||
if (instance.Healthy)
|
||||
{
|
||||
var destinationId = $"{instance.Ip}:{instance.Port}";
|
||||
apiDestinations.Add(destinationId, new DestinationConfig
|
||||
{
|
||||
Address = $"http://{instance.Ip}:{instance.Port}"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
clusters.Add(new ClusterConfig
|
||||
{
|
||||
ClusterId = "api_cluster",
|
||||
Destinations = apiDestinations
|
||||
});
|
||||
}
|
||||
|
||||
// 从Nacos获取User.Service服务实例,指定分组为qx-im
|
||||
try
|
||||
{
|
||||
var userServiceInstances = _nacosNamingService.GetAllInstances("User.Service", "qx-im").Result;
|
||||
|
||||
if (userServiceInstances.Any())
|
||||
{
|
||||
// 创建user_cluster集群配置
|
||||
var userDestinations = new Dictionary<string, DestinationConfig>();
|
||||
|
||||
foreach (var instance in userServiceInstances)
|
||||
{
|
||||
if (instance.Healthy)
|
||||
{
|
||||
var destinationId = $"{instance.Ip}:{instance.Port}";
|
||||
userDestinations.Add(destinationId, new DestinationConfig
|
||||
{
|
||||
Address = $"http://{instance.Ip}:{instance.Port}"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
clusters.Add(new ClusterConfig
|
||||
{
|
||||
ClusterId = "user_cluster",
|
||||
Destinations = userDestinations
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"从Nacos获取User.Service服务实例失败: {ex.Message}");
|
||||
}
|
||||
|
||||
// 从Nacos获取Test.Service服务实例,指定分组为qx-im
|
||||
try
|
||||
{
|
||||
var testServiceInstances = _nacosNamingService.GetAllInstances("Test.Service", "qx-im").Result;
|
||||
|
||||
if (testServiceInstances.Any())
|
||||
{
|
||||
// 创建test_cluster集群配置
|
||||
var testDestinations = new Dictionary<string, DestinationConfig>();
|
||||
|
||||
foreach (var instance in testServiceInstances)
|
||||
{
|
||||
if (instance.Healthy)
|
||||
{
|
||||
var destinationId = $"{instance.Ip}:{instance.Port}";
|
||||
testDestinations.Add(destinationId, new DestinationConfig
|
||||
{
|
||||
Address = $"http://{instance.Ip}:{instance.Port}"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
clusters.Add(new ClusterConfig
|
||||
{
|
||||
ClusterId = "test_cluster",
|
||||
Destinations = testDestinations
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"从Nacos获取Test.Service服务实例失败: {ex.Message}");
|
||||
}
|
||||
|
||||
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}:" + JsonConvert.SerializeObject(clusters));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"从Nacos获取服务实例失败: {ex.Message}");
|
||||
}
|
||||
|
||||
// 如果没有从Nacos获取到api_cluster集群,使用默认配置
|
||||
if (!clusters.Any(c => c.ClusterId == "api_cluster"))
|
||||
{
|
||||
clusters.Add(new ClusterConfig
|
||||
{
|
||||
ClusterId = "api_cluster",
|
||||
Destinations = new Dictionary<string, DestinationConfig>
|
||||
{
|
||||
{
|
||||
"default", new DestinationConfig { Address = "http://localhost:5001" }
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 如果没有从Nacos获取到user_cluster集群,使用默认配置
|
||||
if (!clusters.Any(c => c.ClusterId == "user_cluster"))
|
||||
{
|
||||
clusters.Add(new ClusterConfig
|
||||
{
|
||||
ClusterId = "user_cluster",
|
||||
Destinations = new Dictionary<string, DestinationConfig>
|
||||
{
|
||||
{
|
||||
"default", new DestinationConfig { Address = "http://localhost:5181" }
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 如果没有从Nacos获取到test_cluster集群,使用默认配置
|
||||
if (!clusters.Any(c => c.ClusterId == "test_cluster"))
|
||||
{
|
||||
clusters.Add(new ClusterConfig
|
||||
{
|
||||
ClusterId = "test_cluster",
|
||||
Destinations = new Dictionary<string, DestinationConfig>
|
||||
{
|
||||
{
|
||||
"default", new DestinationConfig { Address = "http://localhost:5252" }
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return new ProxyConfig(routes, clusters, DateTime.UtcNow);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取代理配置
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IProxyConfig GetConfig() => _config;
|
||||
|
||||
/// <summary>
|
||||
/// 定期更新配置
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private async Task UpdateConfigPeriodically()
|
||||
{
|
||||
while (!_cts.Token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(30), _cts.Token); // 每30秒更新一次
|
||||
await UpdateConfig();
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
// 任务被取消,退出循环
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"更新Nacos代理配置失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新配置
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private async Task UpdateConfig()
|
||||
{
|
||||
// 重新从Nacos获取服务实例并更新配置
|
||||
_config = CreateConfig();
|
||||
// 通知YARP配置已变更
|
||||
ProxyConfig.SignalChange();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
using Microsoft.Extensions.Primitives;
|
||||
using System.Threading;
|
||||
using Yarp.ReverseProxy.Configuration;
|
||||
|
||||
namespace YuanXuan.IM.Api.Proxy
|
||||
{
|
||||
/// <summary>
|
||||
/// 代理配置
|
||||
/// </summary>
|
||||
public class ProxyConfig : IProxyConfig
|
||||
{
|
||||
private static CancellationTokenSource _cts = new CancellationTokenSource();
|
||||
|
||||
public ProxyConfig(IReadOnlyList<RouteConfig> routes, IReadOnlyList<ClusterConfig> clusters, DateTime timestamp)
|
||||
{
|
||||
Routes = routes;
|
||||
Clusters = clusters;
|
||||
Timestamp = timestamp;
|
||||
}
|
||||
|
||||
public IReadOnlyList<RouteConfig> Routes { get; }
|
||||
public IReadOnlyList<ClusterConfig> Clusters { get; }
|
||||
public DateTime Timestamp { get; }
|
||||
public IChangeToken ChangeToken => new CancellationChangeToken(_cts.Token);
|
||||
|
||||
/// <summary>
|
||||
/// 通知配置变更
|
||||
/// </summary>
|
||||
public static void SignalChange()
|
||||
{
|
||||
var oldCts = Interlocked.Exchange(ref _cts, new CancellationTokenSource());
|
||||
oldCts.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="1\**" />
|
||||
<Content Remove="1\**" />
|
||||
<EmbeddedResource Remove="1\**" />
|
||||
<None Remove="1\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Polly" Version="8.6.5" />
|
||||
<PackageReference Include="Polly.Extensions.Http" Version="3.0.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||
<PackageReference Include="Yarp.ReverseProxy" Version="2.3.0" />
|
||||
<PackageReference Include="nacos-sdk-csharp.AspNetCore" Version="1.3.10" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\YuanXuan.IM.Common\YuanXuan.IM.Common.csproj" />
|
||||
<ProjectReference Include="..\YuanXuan.IM.Core\YuanXuan.IM.Core.csproj" />
|
||||
<ProjectReference Include="..\YuanXuan.IM.Infrastructure\YuanXuan.IM.Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.22" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.22" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
@YuanXuan.IM.Api_HostAddress = http://localhost:5070
|
||||
|
||||
GET {{YuanXuan.IM.Api_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
|
||||
"nacos": {
|
||||
"Namespace": "public", //对应的是命名空间的Id
|
||||
"ServerAddresses": [ "http://192.168.2.9:8848" ], //是Nacos的服务器地址,可以添加多个
|
||||
"UserName": "nacos", // 你的Nacos用户名
|
||||
"Password": "qwe123!@#", // 你的Nacos密码
|
||||
"Group": "qx-im", // 默认配置分组,可按需修改
|
||||
"Listeners": [ //对应配置文件
|
||||
{
|
||||
"Optional": false,
|
||||
"DataId": "qx-im-api-dev", //配置名称
|
||||
"Group": "qx-im" //组名
|
||||
}
|
||||
],
|
||||
"ServiceName": "YuanXuan.Api", //服务名称
|
||||
// Ncaos 注册中心的配置
|
||||
"DefaultTimeOut": 15000,
|
||||
"ListenInterval": 1000,
|
||||
"GroupName": "qx-im",
|
||||
"RegisterEnabled": true,
|
||||
"InstanceEnabled": true,
|
||||
"Ephemeral": true,
|
||||
"ConfigUseRpc": true,
|
||||
"NamingUseRpc": true,
|
||||
"LBStrategy": "WeightRandom" //WeightRandom WeightRoundRobin
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
|
||||
"nacos": {
|
||||
"Listeners": [ //对应配置文件
|
||||
{
|
||||
"Optional": false,
|
||||
"DataId": "qx-im-api-dev", //配置名称
|
||||
"Group": "qx-im" //组名
|
||||
}
|
||||
],
|
||||
"DefaultTimeOut": 15,
|
||||
"ListenInterval": 1000,
|
||||
"ServiceName": "qx-im-api-dev", //服务名称
|
||||
"Namespace": "public", //对应的是命名空间的Id
|
||||
"ServerAddresses": [ "http://192.168.2.9:8848" ], //是Nacos的服务器地址,可以添加多个
|
||||
"UserName": "nacos",
|
||||
"Password": "qwe123!@#",
|
||||
"ConfigUseRpc": false, //false-http协议,true-grpc
|
||||
"NamingUseRpc": false //false-http协议,true-grpc
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace YuanXuan.IM.Common.Attributes
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class InjectAttribute : Attribute
|
||||
{
|
||||
public ServiceLifetime Lifetime { get; set; } = ServiceLifetime.Scoped;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace YuanXuan.IM.Common.Configs
|
||||
{
|
||||
public class ConnectionStringsSettings
|
||||
{
|
||||
public string Redis { get; set; }
|
||||
public string Db { get; set; }
|
||||
public string UserCenterDb { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Hangfire;
|
||||
|
||||
namespace YuanXuan.IM.Common.Configs
|
||||
{
|
||||
public class HangFireSettings
|
||||
{
|
||||
public string ConnectionString { get; set; }
|
||||
public int Db { get; set; } = 0;
|
||||
public string cron { get; set; }
|
||||
|
||||
public static JobStorage hangfireStorage { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace YuanXuan.IM.Common.Configs
|
||||
{
|
||||
public class ImConfig
|
||||
{
|
||||
public int SDKAppID { get; set; }
|
||||
public string Key { get; set; }
|
||||
public int ExpiredTime { get; set; }
|
||||
/// <summary>
|
||||
/// 头像默认地址
|
||||
/// </summary>
|
||||
public string DefaultAvater { get; set; }
|
||||
/// <summary>
|
||||
/// 管理员用户
|
||||
/// </summary>
|
||||
public string AdminUserId { get; set; }
|
||||
/// <summary>
|
||||
/// api头部地址
|
||||
/// </summary>
|
||||
public string ApiHeadUrl { get; set; }
|
||||
/// <summary>
|
||||
/// 视频通话状态
|
||||
/// </summary>
|
||||
public bool VideoCall { get; set; }
|
||||
/// <summary>
|
||||
/// 语音通话状态
|
||||
/// </summary>
|
||||
public bool VoiceCall { get; set; }
|
||||
}
|
||||
public class ImConfigResult
|
||||
{
|
||||
|
||||
public int SDKAppID { get; set; }
|
||||
/// <summary>
|
||||
/// 视频通话状态
|
||||
/// </summary>
|
||||
public bool VideoCall { get; set; }
|
||||
/// <summary>
|
||||
/// 语音通话状态
|
||||
/// </summary>
|
||||
public bool VoiceCall { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace YuanXuan.IM.Common.Configs
|
||||
{
|
||||
public class JwtSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// AccessToken密钥
|
||||
/// </summary>
|
||||
public string AccessSecret { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// 签发人
|
||||
/// </summary>
|
||||
public string Issuer { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// 受众
|
||||
/// </summary>
|
||||
public string Audience { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// AccessToken有效时长
|
||||
/// </summary>
|
||||
public int AccessExpiration { get; set; }
|
||||
/// <summary>
|
||||
/// RefreshExpiration有效时长
|
||||
/// </summary>
|
||||
public int RefreshExpiration { get; set; }
|
||||
/// <summary>
|
||||
/// 允许的时差
|
||||
/// </summary>
|
||||
public int ClockSkew { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace YuanXuan.IM.Common.Configs
|
||||
{
|
||||
public class NacosConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Nacos命名空间
|
||||
/// </summary>
|
||||
public string Namespace { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
using RabbitMQ.Client;
|
||||
|
||||
namespace YuanXuan.IM.Common.Configs
|
||||
{
|
||||
/// <summary>
|
||||
/// RabbitMQ 配置
|
||||
/// </summary>
|
||||
public class RabbitMQConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// RabbitMQ 服务器地址
|
||||
/// </summary>
|
||||
public string HostName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 端口号
|
||||
/// </summary>
|
||||
public int Port { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户名
|
||||
/// </summary>
|
||||
public string UserName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 密码
|
||||
/// </summary>
|
||||
public string Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 虚拟主机
|
||||
/// </summary>
|
||||
public string VirtualHost { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// im推送mq配置
|
||||
/// </summary>
|
||||
public BaseMqBusinessConfig imMq { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// 对应业务的mq配置
|
||||
/// </summary>
|
||||
public class BaseMqBusinessConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// 队列名称
|
||||
/// </summary>
|
||||
public string QueueName { get; set; }
|
||||
/// <summary>
|
||||
/// 交换机名称
|
||||
/// </summary>
|
||||
public string ExchangeName { get; set; }
|
||||
/// <summary>
|
||||
/// 路由键
|
||||
/// </summary>
|
||||
public string RoutingKey { get; set; }
|
||||
/// <summary>
|
||||
/// 是否启用消息持久化
|
||||
/// </summary>
|
||||
public bool Durable { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 是否自动删除队列
|
||||
/// </summary>
|
||||
public bool AutoDelete { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 预取计数
|
||||
/// </summary>
|
||||
public ushort PrefetchCount { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace YuanXuan.IM.Common.Configs
|
||||
{
|
||||
public class UpAppVersionConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// IOS链接
|
||||
/// </summary>
|
||||
public string IOSUrl { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace YuanXuan.IM.Common.Dtos.ALiYun
|
||||
{
|
||||
public class CodeMsg
|
||||
{
|
||||
public int Code { get; set; }
|
||||
public string Data { get; set; }
|
||||
public string Reson { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace YuanXuan.IM.Common.Dtos.ALiYun
|
||||
{
|
||||
public class OSSConfigResult
|
||||
{
|
||||
public string AccessKeyId { get; set; }
|
||||
public string AccessKeySecret { get; set; }
|
||||
public string Endpoint { get; set; }
|
||||
public string BucketName { get; set; }
|
||||
public long Size { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace YuanXuan.IM.Common.Dtos.Consul
|
||||
{
|
||||
public class ConsulServiceInstance
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 服务ID
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
/// <summary>
|
||||
/// 服务名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary>
|
||||
/// 服务地址
|
||||
/// </summary>
|
||||
public string Address { get; set; }
|
||||
/// <summary>
|
||||
/// 服务端口
|
||||
/// </summary>
|
||||
public int Port { get; set; }
|
||||
/// <summary>
|
||||
/// 是否健康
|
||||
/// </summary>
|
||||
public bool IsHealthy { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace YuanXuan.IM.Common.Dtos.LoginMobile
|
||||
{
|
||||
public class LoginRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户名
|
||||
/// </summary>
|
||||
public string username { get; set; }
|
||||
/// <summary>
|
||||
/// 密码
|
||||
/// </summary>
|
||||
public string Pwd { get; set; }
|
||||
/// <summary>
|
||||
/// 登录类型 1:Android;2Ios;3扫码;4:H5
|
||||
/// </summary>
|
||||
public int loginType { get; set; } = 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace YuanXuan.IM.Common.Dtos.LoginMobile
|
||||
{
|
||||
public class MobileTokenInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户Id
|
||||
/// </summary>
|
||||
public string UserId { get; set; }
|
||||
/// <summary>
|
||||
/// 用户名称
|
||||
/// </summary>
|
||||
public string UserName { get; set; }
|
||||
/// <summary>
|
||||
/// Jwt_id
|
||||
/// </summary>
|
||||
public string Jwt_id { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace YuanXuan.IM.Common.Dtos.LoginMobile
|
||||
{
|
||||
public class RefreshTokenRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 过期的token
|
||||
/// </summary>
|
||||
public string Token { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 刷新token
|
||||
/// </summary>
|
||||
public string RefreshToken { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
using SqlSugar;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace YuanXuan.IM.Common.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// 基础数据库实体类
|
||||
/// </summary>
|
||||
public class BaseEntity
|
||||
{
|
||||
[SugarColumn(IsPrimaryKey = true)]
|
||||
public long Id { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace YuanXuan.IM.Common.Enums
|
||||
{
|
||||
/// <summary>
|
||||
/// 二维码用途类型
|
||||
/// </summary>
|
||||
public enum QRCodeTypeEnum
|
||||
{
|
||||
/// <summary>
|
||||
/// 登录到PC
|
||||
/// </summary>
|
||||
LoginPC = 1,
|
||||
/// <summary>
|
||||
/// 登录到统计
|
||||
/// </summary>
|
||||
LoginStatistic =2
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace YuanXuan.IM.Common.Enums
|
||||
{
|
||||
/// <summary>
|
||||
/// 系统角色枚举
|
||||
/// </summary>
|
||||
public enum SysRoleEnum
|
||||
{
|
||||
/// <summary>
|
||||
/// 超级管理员
|
||||
/// </summary>
|
||||
[Description("超级管理员")]
|
||||
SuperAdmin = 1,
|
||||
/// <summary>
|
||||
/// 云校管理员
|
||||
/// </summary>
|
||||
[Description("云校管理员")]
|
||||
CloudSchoolAdmin = 2,
|
||||
/// <summary>
|
||||
/// 运营管理员
|
||||
/// </summary>
|
||||
OperationAdmin = 3,
|
||||
/// <summary>
|
||||
/// 总部长
|
||||
/// </summary>
|
||||
[Description("总部长")]
|
||||
GeneralMinisterAdmin = 1000,
|
||||
/// <summary>
|
||||
/// 部长
|
||||
/// </summary>
|
||||
[Description("部长")]
|
||||
MinisterAdmin = 1001,
|
||||
/// <summary>
|
||||
/// 组长
|
||||
/// </summary>
|
||||
[Description("组长")]
|
||||
TeamLeader = 1002,
|
||||
/// <summary>
|
||||
/// 学习官
|
||||
/// </summary>
|
||||
[Description("学习官")]
|
||||
LearningOfficer = 1003,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace YuanXuan.IM.Common.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 业务异常
|
||||
/// </summary>
|
||||
public class BusinessException : Exception
|
||||
{
|
||||
|
||||
public BusinessExceptionCode ErrorCode { get; private set; }
|
||||
public object BussinessExceptionData { get; private set; }
|
||||
|
||||
public BusinessException(string message, object friendlyData = null, BusinessExceptionCode errorCode = BusinessExceptionCode.BussinessError) : base(message)
|
||||
{
|
||||
ErrorCode = errorCode;
|
||||
BussinessExceptionData = friendlyData;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 业务异常码
|
||||
/// </summary>
|
||||
public enum BusinessExceptionCode
|
||||
{
|
||||
/// <summary>
|
||||
/// 业务异常码
|
||||
/// </summary>
|
||||
BussinessError = 1000,
|
||||
|
||||
/// <summary>
|
||||
/// 程序异常码
|
||||
/// </summary>
|
||||
AppError = 500,
|
||||
|
||||
/// <summary>
|
||||
/// 访问令牌异常码
|
||||
/// </summary>
|
||||
AccessTokenError = 401,
|
||||
|
||||
/// <summary>
|
||||
/// 访问令牌被顶号异常码
|
||||
/// </summary>
|
||||
AccessTokenTopNumberError = 402,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace YuanXuan.IM.Common.Helpers
|
||||
{
|
||||
public static class AesEncryptHelper
|
||||
{
|
||||
static string key = "ThisIsAVerySecre";
|
||||
static string iv = "16ByteIV12345678";
|
||||
|
||||
// 使用CBC模式加密并返回Base64字符串
|
||||
public static string EncryptToShortString(string plainText)
|
||||
{
|
||||
using (Aes aesAlg = Aes.Create())
|
||||
{
|
||||
aesAlg.Key = Encoding.UTF8.GetBytes(key);
|
||||
aesAlg.IV = Encoding.UTF8.GetBytes(iv);
|
||||
aesAlg.Mode = CipherMode.CBC;
|
||||
aesAlg.Padding = PaddingMode.PKCS7;
|
||||
|
||||
ICryptoTransform encryptor = aesAlg.CreateEncryptor();
|
||||
byte[] encryptedBytes = encryptor.TransformFinalBlock(
|
||||
Encoding.UTF8.GetBytes(plainText), 0, plainText.Length);
|
||||
|
||||
return Convert.ToBase64String(encryptedBytes);
|
||||
}
|
||||
}
|
||||
|
||||
// 解密Base64加密字符串
|
||||
public static string DecryptFromShortString(string cipherText)
|
||||
{
|
||||
using (Aes aesAlg = Aes.Create())
|
||||
{
|
||||
aesAlg.Key = Encoding.UTF8.GetBytes(key);
|
||||
aesAlg.IV = Encoding.UTF8.GetBytes(iv);
|
||||
aesAlg.Mode = CipherMode.CBC;
|
||||
aesAlg.Padding = PaddingMode.PKCS7;
|
||||
|
||||
ICryptoTransform decryptor = aesAlg.CreateDecryptor();
|
||||
byte[] cipherBytes = Convert.FromBase64String(cipherText);
|
||||
byte[] decryptedBytes = decryptor.TransformFinalBlock(
|
||||
cipherBytes, 0, cipherBytes.Length);
|
||||
|
||||
return Encoding.UTF8.GetString(decryptedBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
using Aliyun.OSS;
|
||||
using Aliyun.OSS.Common;
|
||||
using Castle.Core.Logging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.AccessControl;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using YuanXuan.IM.Common.Attributes;
|
||||
using YuanXuan.IM.Common.Dtos.ALiYun;
|
||||
|
||||
namespace YuanXuan.IM.Common.Helpers
|
||||
{
|
||||
[Inject(Lifetime = Microsoft.Extensions.DependencyInjection.ServiceLifetime.Transient)]
|
||||
public class AliyunOssHelper
|
||||
{
|
||||
private readonly IOptionsMonitor<OSSConfigResult> ossConfig;
|
||||
private readonly ILogger<AliyunOssHelper> logger;
|
||||
|
||||
public AliyunOssHelper(IOptionsMonitor<OSSConfigResult> ossConfig,
|
||||
ILogger<AliyunOssHelper> logger)
|
||||
{
|
||||
this.ossConfig = ossConfig;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public string UploadByStream(Stream stream, string fileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var client = new OssClient(ossConfig.CurrentValue.Endpoint, ossConfig.CurrentValue.AccessKeyId, ossConfig.CurrentValue.AccessKeySecret, new ClientConfiguration()
|
||||
{
|
||||
SignatureVersion = SignatureVersion.V4
|
||||
});
|
||||
client.SetRegion("cn-chengdu");
|
||||
client.PutObject(ossConfig.CurrentValue.BucketName, fileName, stream);
|
||||
|
||||
return $"https://{ossConfig.CurrentValue.BucketName}.{ossConfig.CurrentValue.Endpoint}/{fileName.TrimStart('/')}";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError("AliyunOssHelper UploadByStream Error:{0}", ex.Message);
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace YuanXuan.IM.Common.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 异常通知
|
||||
/// </summary>
|
||||
public class ExceptionNotice
|
||||
{
|
||||
private static HttpClient httpClient = new HttpClient()
|
||||
{
|
||||
BaseAddress = new Uri("https://oapi.dingtalk.com/robot/send?access_token=339d1f43d3b2a084abaa77871ddd187b613206149962d844adf37a46a14359a1"),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 发送异常信息
|
||||
/// </summary>
|
||||
/// <param name="exp">异常</param>
|
||||
/// <param name="expSrc">异常来源(用于显示)</param>
|
||||
/// <returns></returns>
|
||||
public static async Task<bool> SendAsync(Exception exp, string expSrc)
|
||||
{
|
||||
var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Development";
|
||||
if (env == "Development")
|
||||
{
|
||||
Console.WriteLine("*************** Excpetion ***************");
|
||||
Console.WriteLine(exp.Message, exp);
|
||||
Console.WriteLine("*************** Excpetion ***************");
|
||||
return true;
|
||||
}
|
||||
|
||||
var reponse = await httpClient.PostAsync(string.Empty, JsonContent.Create(new
|
||||
{
|
||||
msgtype = "markdown",
|
||||
markdown = new
|
||||
{
|
||||
title = "Mobile.API抛出异常",
|
||||
text = $"Mobile.API异常.描述:{exp.Message}\n详情:{exp}"
|
||||
},
|
||||
}));
|
||||
return reponse.IsSuccessStatusCode;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
using Microsoft.IdentityModel.Tokens;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using YuanXuan.IM.Common.Configs;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System.Reflection;
|
||||
using System.IO;
|
||||
|
||||
namespace YuanXuan.IM.Common.Helpers
|
||||
{
|
||||
public class JwtHelper
|
||||
{
|
||||
private static JwtSettings _jwtSettings;
|
||||
|
||||
static JwtHelper()
|
||||
{
|
||||
// 初始化JWT配置
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.SetBasePath(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location))
|
||||
.AddJsonFile("appsettings.json", optional: true)
|
||||
.AddJsonFile("appsettings.Development.json", optional: true)
|
||||
.Build();
|
||||
|
||||
_jwtSettings = configuration.GetSection("Jwt").Get<JwtSettings>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成token
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="secretKey"></param>
|
||||
/// <param name="issuer"></param>
|
||||
/// <param name="audience"></param>
|
||||
/// <param name="expires"></param>
|
||||
/// <param name="claims"></param>
|
||||
/// <returns></returns>
|
||||
public static string CreateToken(string uid, string secretKey, string issuer, string audience, double expires, List<Claim> claims = null)
|
||||
{
|
||||
if (claims.IsNullOrEmpty())
|
||||
claims = new();
|
||||
claims.AddRange(new List<Claim>
|
||||
{
|
||||
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
|
||||
new Claim(ClaimTypes.NameIdentifier, uid),
|
||||
});
|
||||
|
||||
// 2. 从 appsettings.json 中读取SecretKey
|
||||
var secret = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey));
|
||||
|
||||
// 3. 选择加密算法
|
||||
var algorithm = SecurityAlgorithms.HmacSha256;
|
||||
|
||||
// 4. 生成Credentials
|
||||
var signingCredentials = new SigningCredentials(secret, algorithm);
|
||||
|
||||
// 5. 根据以上,生成token
|
||||
var jwtSecurityToken = new JwtSecurityToken(
|
||||
issuer, //Issuer
|
||||
audience, //Audience
|
||||
claims, //Claims,
|
||||
DateTime.Now, //notBefore
|
||||
DateTime.Now.AddSeconds(expires), //expires
|
||||
signingCredentials //Credentials
|
||||
);
|
||||
|
||||
// 6. 将token变为string
|
||||
var token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成JWT token
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="userName"></param>
|
||||
/// <returns></returns>
|
||||
public static string GenerateToken(string userId, string userName)
|
||||
{
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new Claim(JwtRegisteredClaimNames.Sub, userId),
|
||||
new Claim(JwtRegisteredClaimNames.Name, userName),
|
||||
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
|
||||
};
|
||||
|
||||
return CreateToken(userId, _jwtSettings.AccessSecret, _jwtSettings.Issuer, _jwtSettings.Audience, _jwtSettings.AccessExpiration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成刷新token
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GenerateRefreshToken()
|
||||
{
|
||||
return Guid.NewGuid().ToString("N");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从过期的token中获取principal
|
||||
/// </summary>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
public static ClaimsPrincipal GetPrincipalFromExpiredToken(string token)
|
||||
{
|
||||
var tokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuer = true,
|
||||
ValidIssuer = _jwtSettings.Issuer,
|
||||
ValidateAudience = true,
|
||||
ValidAudience = _jwtSettings.Audience,
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.AccessSecret)),
|
||||
ValidateLifetime = false // 不验证过期时间
|
||||
};
|
||||
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out var securityToken);
|
||||
|
||||
if (!(securityToken is JwtSecurityToken jwtSecurityToken) || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
throw new SecurityTokenException("无效的token");
|
||||
}
|
||||
|
||||
return principal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace YuanXuan.IM.Common.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// MD5帮助类
|
||||
/// </summary>
|
||||
public static class MD5EncryptionHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// MD5字符串加密
|
||||
/// </summary>
|
||||
/// <param name="txt"></param>
|
||||
/// <returns>加密后字符串</returns>
|
||||
public static string MD5Encryption(string txt)
|
||||
{
|
||||
using (MD5 mi = MD5.Create())
|
||||
{
|
||||
byte[] buffer = Encoding.Default.GetBytes(txt);
|
||||
//开始加密
|
||||
byte[] newBuffer = mi.ComputeHash(buffer);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < newBuffer.Length; i++)
|
||||
{
|
||||
sb.Append(newBuffer[i].ToString("x2"));
|
||||
}
|
||||
return sb.ToString().ToUpper(); ;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
using QRCoder;
|
||||
|
||||
namespace YuanXuan.IM.Common.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 二维码生成器
|
||||
/// </summary>
|
||||
public class QRCoderHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 生成二维码的 URL
|
||||
/// </summary>
|
||||
/// <param name="filePath">网络路径</param>
|
||||
/// <returns>二维码图片的base64字符串</returns>
|
||||
public static string GenerateQRCodeUrl(string filePath)
|
||||
{
|
||||
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
{
|
||||
throw new ArgumentException("文件路径不能为空", nameof(filePath));
|
||||
}
|
||||
|
||||
using (var qrGenerator = new QRCodeGenerator())
|
||||
{
|
||||
// 生成二维码数据
|
||||
var qrCodeData = qrGenerator.CreateQrCode(filePath, QRCodeGenerator.ECCLevel.Q);
|
||||
|
||||
// 使用 PngByteQRCode 生成二维码字节数组
|
||||
var pngByteQRCode = new PngByteQRCode(qrCodeData);
|
||||
var qrCodeBytes = pngByteQRCode.GetGraphic(20);
|
||||
|
||||
// 将字节数组转换为 Base64 字符串
|
||||
var base64String = Convert.ToBase64String(qrCodeBytes);
|
||||
return $"data:image/png;base64,{base64String}";
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using RestSharp;
|
||||
|
||||
namespace YuanXuan.IM.Common.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// RestSharpHelper
|
||||
/// </summary>
|
||||
public class RestSharpHelper
|
||||
{
|
||||
|
||||
private readonly RestClient _client;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数,初始化 RestClient
|
||||
/// </summary>
|
||||
public RestSharpHelper()
|
||||
{
|
||||
_client = new RestClient();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送 GET 请求
|
||||
/// </summary>
|
||||
/// <param name="url">完整的请求 URL</param>
|
||||
/// <param name="headers">请求头</param>
|
||||
/// <returns>响应数据</returns>
|
||||
public async Task<string> GetAsync(string url, Dictionary<string, string>? headers = null)
|
||||
{
|
||||
var request = new RestRequest(url, Method.Get);
|
||||
AddHeaders(request, headers);
|
||||
|
||||
var response = await _client.ExecuteAsync(request);
|
||||
return HandleResponse(response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送 POST 请求
|
||||
/// </summary>
|
||||
/// <param name="url">完整的请求 URL</param>
|
||||
/// <param name="body">请求体</param>
|
||||
/// <param name="headers">请求头</param>
|
||||
/// <returns>响应数据</returns>
|
||||
public async Task<string> PostAsync(string url, object body, Dictionary<string, string>? headers = null)
|
||||
{
|
||||
var request = new RestRequest(url, Method.Post);
|
||||
AddHeaders(request, headers);
|
||||
request.AddBody(body);
|
||||
|
||||
var response = await _client.ExecuteAsync(request);
|
||||
return HandleResponse(response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送 POST 请求
|
||||
/// </summary>
|
||||
/// <param name="url">完整的请求 URL</param>
|
||||
/// <param name="body">请求体</param>
|
||||
/// <param name="headers">请求头</param>
|
||||
/// <returns>响应数据</returns>
|
||||
public async Task<string> PostByJsonBodyAsync(string url, object body, Dictionary<string, string>? headers = null)
|
||||
{
|
||||
var request = new RestRequest(url, Method.Post);
|
||||
AddHeaders(request, headers);
|
||||
request.AddJsonBody(body);
|
||||
|
||||
var response = await _client.ExecuteAsync(request);
|
||||
return HandleResponse(response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送 PUT 请求
|
||||
/// </summary>
|
||||
/// <param name="url">完整的请求 URL</param>
|
||||
/// <param name="body">请求体</param>
|
||||
/// <param name="headers">请求头</param>
|
||||
/// <returns>响应数据</returns>
|
||||
public async Task<string> PutAsync(string url, object body, Dictionary<string, string>? headers = null)
|
||||
{
|
||||
var request = new RestRequest(url, Method.Put);
|
||||
AddHeaders(request, headers);
|
||||
request.AddJsonBody(body);
|
||||
|
||||
var response = await _client.ExecuteAsync(request);
|
||||
return HandleResponse(response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加请求头
|
||||
/// </summary>
|
||||
/// <param name="request">RestSharp 请求对象</param>
|
||||
/// <param name="headers">请求头</param>
|
||||
private void AddHeaders(RestRequest request, Dictionary<string, string>? headers)
|
||||
{
|
||||
if (headers != null)
|
||||
{
|
||||
foreach (var header in headers)
|
||||
{
|
||||
request.AddHeader(header.Key, header.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理响应
|
||||
/// </summary>
|
||||
/// <param name="response">RestSharp 响应对象</param>
|
||||
/// <returns>响应数据</returns>
|
||||
/// <exception cref="Exception">当响应失败时抛出异常</exception>
|
||||
private string HandleResponse(RestResponse response)
|
||||
{
|
||||
if (response.IsSuccessful && response.Content != null)
|
||||
{
|
||||
return response.Content;
|
||||
}
|
||||
|
||||
throw new Exception($"请求失败: {response.StatusCode}, 错误信息: {response.ErrorMessage ?? response.Content}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,251 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Security.Cryptography;
|
||||
using ComponentAce.Compression.Libs.zlib;
|
||||
|
||||
|
||||
namespace YuanXuan.IM.Common.Helpers
|
||||
{
|
||||
public class TLSSigAPIv2
|
||||
{
|
||||
|
||||
private readonly int sdkappid;
|
||||
private readonly string key;
|
||||
|
||||
public TLSSigAPIv2(int sdkappid, string key)
|
||||
{
|
||||
this.sdkappid = sdkappid;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
/**
|
||||
*【功能说明】用于签发 TRTC 和 IM 服务中必须要使用的 UserSig 鉴权票据
|
||||
*
|
||||
*【参数说明】
|
||||
* userid - 用户id,限制长度为32字节,只允许包含大小写英文字母(a-zA-Z)、数字(0-9)及下划线和连词符。
|
||||
* expire - UserSig 票据的过期时间,单位是秒,比如 86400 代表生成的 UserSig 票据在一天后就无法再使用了。
|
||||
*/
|
||||
public string genUserSig(string userid, int expire = 180 * 86400)
|
||||
{
|
||||
return genUserSig(userid, expire, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
*【功能说明】
|
||||
* 用于签发 TRTC 进房参数中可选的 PrivateMapKey 权限票据。
|
||||
* PrivateMapKey 需要跟 UserSig 一起使用,但 PrivateMapKey 比 UserSig 有更强的权限控制能力:
|
||||
* - UserSig 只能控制某个 UserID 有无使用 TRTC 服务的权限,只要 UserSig 正确,其对应的 UserID 可以进出任意房间。
|
||||
* - PrivateMapKey 则是将 UserID 的权限控制的更加严格,包括能不能进入某个房间,能不能在该房间里上行音视频等等。
|
||||
* 如果要开启 PrivateMapKey 严格权限位校验,需要在【实时音视频控制台】=>【应用管理】=>【应用信息】中打开“启动权限密钥”开关。
|
||||
*
|
||||
*【参数说明】
|
||||
* userid - 用户id,限制长度为32字节,只允许包含大小写英文字母(a-zA-Z)、数字(0-9)及下划线和连词符。
|
||||
* roomid - 房间号,用于指定该 userid 可以进入的房间号
|
||||
* expire - PrivateMapKey 票据的过期时间,单位是秒,比如 86400 生成的 PrivateMapKey 票据在一天后就无法再使用了。
|
||||
* privilegeMap - 权限位,使用了一个字节中的 8 个比特位,分别代表八个具体的功能权限开关:
|
||||
* - 第 1 位:0000 0001 = 1,创建房间的权限
|
||||
* - 第 2 位:0000 0010 = 2,加入房间的权限
|
||||
* - 第 3 位:0000 0100 = 4,发送语音的权限
|
||||
* - 第 4 位:0000 1000 = 8,接收语音的权限
|
||||
* - 第 5 位:0001 0000 = 16,发送视频的权限
|
||||
* - 第 6 位:0010 0000 = 32,接收视频的权限
|
||||
* - 第 7 位:0100 0000 = 64,发送辅路(也就是屏幕分享)视频的权限
|
||||
* - 第 8 位:1000 0000 = 200,接收辅路(也就是屏幕分享)视频的权限
|
||||
* - privilegeMap == 1111 1111 == 255 代表该 userid 在该 roomid 房间内的所有功能权限。
|
||||
* - privilegeMap == 0010 1010 == 42 代表该 userid 拥有加入房间和接收音视频数据的权限,但不具备其他权限。
|
||||
*/
|
||||
|
||||
public string genPrivateMapKey(string userid, int expire, uint roomid, uint privilegeMap)
|
||||
{
|
||||
byte[] userbuf = genUserBuf(userid, roomid, expire, privilegeMap, 0, "");
|
||||
System.Console.WriteLine(userbuf);
|
||||
return genUserSig(userid, expire, userbuf, true);
|
||||
}
|
||||
/**
|
||||
*【功能说明】
|
||||
* 用于签发 TRTC 进房参数中可选的 PrivateMapKey 权限票据。
|
||||
* PrivateMapKey 需要跟 UserSig 一起使用,但 PrivateMapKey 比 UserSig 有更强的权限控制能力:
|
||||
* - UserSig 只能控制某个 UserID 有无使用 TRTC 服务的权限,只要 UserSig 正确,其对应的 UserID 可以进出任意房间。
|
||||
* - PrivateMapKey 则是将 UserID 的权限控制的更加严格,包括能不能进入某个房间,能不能在该房间里上行音视频等等。
|
||||
* 如果要开启 PrivateMapKey 严格权限位校验,需要在【实时音视频控制台】=>【应用管理】=>【应用信息】中打开“启动权限密钥”开关。
|
||||
*
|
||||
*【参数说明】
|
||||
* userid - 用户id,限制长度为32字节,只允许包含大小写英文字母(a-zA-Z)、数字(0-9)及下划线和连词符。
|
||||
* roomstr - 房间号,用于指定该 userid 可以进入的房间号
|
||||
* expire - PrivateMapKey 票据的过期时间,单位是秒,比如 86400 生成的 PrivateMapKey 票据在一天后就无法再使用了。
|
||||
* privilegeMap - 权限位,使用了一个字节中的 8 个比特位,分别代表八个具体的功能权限开关:
|
||||
* - 第 1 位:0000 0001 = 1,创建房间的权限
|
||||
* - 第 2 位:0000 0010 = 2,加入房间的权限
|
||||
* - 第 3 位:0000 0100 = 4,发送语音的权限
|
||||
* - 第 4 位:0000 1000 = 8,接收语音的权限
|
||||
* - 第 5 位:0001 0000 = 16,发送视频的权限
|
||||
* - 第 6 位:0010 0000 = 32,接收视频的权限
|
||||
* - 第 7 位:0100 0000 = 64,发送辅路(也就是屏幕分享)视频的权限
|
||||
* - 第 8 位:1000 0000 = 200,接收辅路(也就是屏幕分享)视频的权限
|
||||
* - privilegeMap == 1111 1111 == 255 代表该 userid 在该 roomid 房间内的所有功能权限。
|
||||
* - privilegeMap == 0010 1010 == 42 代表该 userid 拥有加入房间和接收音视频数据的权限,但不具备其他权限。
|
||||
*/
|
||||
|
||||
public string genPrivateMapKeyWithStringRoomID(string userid, int expire, string roomstr, uint privilegeMap)
|
||||
{
|
||||
byte[] userbuf = genUserBuf(userid, 0, expire, privilegeMap, 0, roomstr);
|
||||
System.Console.WriteLine(userbuf);
|
||||
return genUserSig(userid, expire, userbuf, true);
|
||||
}
|
||||
private string genUserSig(string userid, int expire, byte[] userbuf, bool userBufEnabled)
|
||||
{
|
||||
DateTime epoch = new DateTime(1970, 1, 1); // unix 时间戳
|
||||
Int64 currTime = (Int64)(DateTime.UtcNow - epoch).TotalMilliseconds / 1000;
|
||||
|
||||
string base64UserBuf;
|
||||
string jsonData;
|
||||
if (true == userBufEnabled)
|
||||
{
|
||||
base64UserBuf = Convert.ToBase64String(userbuf);
|
||||
string base64sig = HMACSHA256(userid, currTime, expire, base64UserBuf, userBufEnabled);
|
||||
// 没有引入 json 库,所以这里手动进行组装
|
||||
jsonData = String.Format("{{"
|
||||
+ "\"TLS.ver\":" + "\"2.0\","
|
||||
+ "\"TLS.identifier\":" + "\"{0}\","
|
||||
+ "\"TLS.sdkappid\":" + "{1},"
|
||||
+ "\"TLS.expire\":" + "{2},"
|
||||
+ "\"TLS.time\":" + "{3},"
|
||||
+ "\"TLS.sig\":" + "\"{4}\","
|
||||
+ "\"TLS.userbuf\":" + "\"{5}\""
|
||||
+ "}}", userid, sdkappid, expire, currTime, base64sig, base64UserBuf);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 没有引入 json 库,所以这里手动进行组装
|
||||
string base64sig = HMACSHA256(userid, currTime, expire, "", false);
|
||||
jsonData = String.Format("{{"
|
||||
+ "\"TLS.ver\":" + "\"2.0\","
|
||||
+ "\"TLS.identifier\":" + "\"{0}\","
|
||||
+ "\"TLS.sdkappid\":" + "{1},"
|
||||
+ "\"TLS.expire\":" + "{2},"
|
||||
+ "\"TLS.time\":" + "{3},"
|
||||
+ "\"TLS.sig\":" + "\"{4}\""
|
||||
+ "}}", userid, sdkappid, expire, currTime, base64sig);
|
||||
}
|
||||
|
||||
byte[] buffer = Encoding.UTF8.GetBytes(jsonData);
|
||||
return Convert.ToBase64String(CompressBytes(buffer))
|
||||
.Replace('+', '*').Replace('/', '-').Replace('=', '_');
|
||||
}
|
||||
public byte[] genUserBuf(string account, uint dwAuthID, int dwExpTime, uint dwPrivilegeMap, uint dwAccountType, string roomStr)
|
||||
{
|
||||
int length = 1 + 2 + account.Length + 20;
|
||||
int offset = 0;
|
||||
if (roomStr.Length > 0)
|
||||
length = length + 2 + roomStr.Length;
|
||||
byte[] userBuf = new byte[length];
|
||||
|
||||
if (roomStr.Length > 0)
|
||||
userBuf[offset++] = 1;
|
||||
else
|
||||
userBuf[offset++] = 0;
|
||||
|
||||
userBuf[offset++] = (byte)((account.Length & 0xFF00) >> 8);
|
||||
userBuf[offset++] = (byte)(account.Length & 0x00FF);
|
||||
|
||||
byte[] accountByte = System.Text.Encoding.UTF8.GetBytes(account);
|
||||
accountByte.CopyTo(userBuf, offset);
|
||||
offset += account.Length;
|
||||
|
||||
//dwSdkAppid
|
||||
userBuf[offset++] = (byte)((sdkappid & 0xFF000000) >> 24);
|
||||
userBuf[offset++] = (byte)((sdkappid & 0x00FF0000) >> 16);
|
||||
userBuf[offset++] = (byte)((sdkappid & 0x0000FF00) >> 8);
|
||||
userBuf[offset++] = (byte)(sdkappid & 0x000000FF);
|
||||
|
||||
//dwAuthId
|
||||
userBuf[offset++] = (byte)((dwAuthID & 0xFF000000) >> 24);
|
||||
userBuf[offset++] = (byte)((dwAuthID & 0x00FF0000) >> 16);
|
||||
userBuf[offset++] = (byte)((dwAuthID & 0x0000FF00) >> 8);
|
||||
userBuf[offset++] = (byte)(dwAuthID & 0x000000FF);
|
||||
|
||||
//time_t now = time(0);
|
||||
//uint32_t expire = now + dwExpTime;
|
||||
long expire = dwExpTime + (long)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
|
||||
userBuf[offset++] = (byte)((expire & 0xFF000000) >> 24);
|
||||
userBuf[offset++] = (byte)((expire & 0x00FF0000) >> 16);
|
||||
userBuf[offset++] = (byte)((expire & 0x0000FF00) >> 8);
|
||||
userBuf[offset++] = (byte)(expire & 0x000000FF);
|
||||
|
||||
//dwPrivilegeMap
|
||||
userBuf[offset++] = (byte)((dwPrivilegeMap & 0xFF000000) >> 24);
|
||||
userBuf[offset++] = (byte)((dwPrivilegeMap & 0x00FF0000) >> 16);
|
||||
userBuf[offset++] = (byte)((dwPrivilegeMap & 0x0000FF00) >> 8);
|
||||
userBuf[offset++] = (byte)(dwPrivilegeMap & 0x000000FF);
|
||||
|
||||
//dwAccountType
|
||||
userBuf[offset++] = (byte)((dwAccountType & 0xFF000000) >> 24);
|
||||
userBuf[offset++] = (byte)((dwAccountType & 0x00FF0000) >> 16);
|
||||
userBuf[offset++] = (byte)((dwAccountType & 0x0000FF00) >> 8);
|
||||
userBuf[offset++] = (byte)(dwAccountType & 0x000000FF);
|
||||
|
||||
if (roomStr.Length > 0)
|
||||
{
|
||||
userBuf[offset++] = (byte)((roomStr.Length & 0xFF00) >> 8);
|
||||
userBuf[offset++] = (byte)(roomStr.Length & 0x00FF);
|
||||
|
||||
byte[] roomStrByte = System.Text.Encoding.UTF8.GetBytes(roomStr);
|
||||
roomStrByte.CopyTo(userBuf, offset);
|
||||
offset += roomStr.Length;
|
||||
}
|
||||
return userBuf;
|
||||
}
|
||||
private static byte[] CompressBytes(byte[] sourceByte)
|
||||
{
|
||||
MemoryStream inputStream = new MemoryStream(sourceByte);
|
||||
Stream outStream = CompressStream(inputStream);
|
||||
byte[] outPutByteArray = new byte[outStream.Length];
|
||||
outStream.Position = 0;
|
||||
outStream.Read(outPutByteArray, 0, outPutByteArray.Length);
|
||||
return outPutByteArray;
|
||||
}
|
||||
|
||||
private static Stream CompressStream(Stream sourceStream)
|
||||
{
|
||||
MemoryStream streamOut = new MemoryStream();
|
||||
ZOutputStream streamZOut = new ZOutputStream(streamOut, zlibConst.Z_DEFAULT_COMPRESSION);
|
||||
CopyStream(sourceStream, streamZOut);
|
||||
streamZOut.finish();
|
||||
return streamOut;
|
||||
}
|
||||
|
||||
public static void CopyStream(System.IO.Stream input, System.IO.Stream output)
|
||||
{
|
||||
byte[] buffer = new byte[2000];
|
||||
int len;
|
||||
while ((len = input.Read(buffer, 0, 2000)) > 0)
|
||||
{
|
||||
output.Write(buffer, 0, len);
|
||||
}
|
||||
output.Flush();
|
||||
}
|
||||
|
||||
private string HMACSHA256(string identifier, long currTime, int expire, string base64UserBuf, bool userBufEnabled)
|
||||
{
|
||||
string rawContentToBeSigned = "TLS.identifier:" + identifier + "\n"
|
||||
+ "TLS.sdkappid:" + sdkappid + "\n"
|
||||
+ "TLS.time:" + currTime + "\n"
|
||||
+ "TLS.expire:" + expire + "\n";
|
||||
if (true == userBufEnabled)
|
||||
{
|
||||
rawContentToBeSigned += "TLS.userbuf:" + base64UserBuf + "\n";
|
||||
}
|
||||
using (HMACSHA256 hmac = new HMACSHA256())
|
||||
{
|
||||
UTF8Encoding encoding = new UTF8Encoding();
|
||||
Byte[] textBytes = encoding.GetBytes(rawContentToBeSigned);
|
||||
Byte[] keyBytes = encoding.GetBytes(key);
|
||||
Byte[] hashBytes;
|
||||
using (HMACSHA256 hash = new HMACSHA256(keyBytes))
|
||||
hashBytes = hash.ComputeHash(textBytes);
|
||||
return Convert.ToBase64String(hashBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace YuanXuan.IM.Common.Request
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 分页请求实体类
|
||||
/// </summary>
|
||||
public class PageRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前页
|
||||
/// </summary>
|
||||
public int PageIndex { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 一页条数
|
||||
/// </summary>
|
||||
public int PageSize { get; set; } = 10;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace YuanXuan.IM.Common.Response
|
||||
{
|
||||
public class BaseResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// 业务结果代码
|
||||
/// </summary>
|
||||
public int code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回消息
|
||||
/// </summary>
|
||||
public string msg { get; set; } = "";
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 基础返回实体类
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class BaseResponse<T> : BaseResponse
|
||||
{
|
||||
public BaseResponse(int code, string msg = "", T data = default)
|
||||
{
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回实体
|
||||
/// </summary>
|
||||
public T data { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace YuanXuan.IM.Common.Request
|
||||
{
|
||||
/// <summary>
|
||||
/// 分页响应实体类
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class PageResponse<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// 总记录条数
|
||||
/// </summary>
|
||||
public int Total { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 响应数据
|
||||
/// </summary>
|
||||
public List<T> Items { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="aliyun-net-sdk-core" Version="1.6.2" />
|
||||
<PackageReference Include="Aliyun.OSS.SDK.NetCore" Version="2.14.1" />
|
||||
<PackageReference Include="ComponentAce.Compression.Libs.zlib" Version="1.0.4" />
|
||||
<PackageReference Include="Hangfire" Version="1.8.22" />
|
||||
<PackageReference Include="Hangfire.Core" Version="1.8.22" />
|
||||
<PackageReference Include="HangFire.Redis" Version="2.0.1" />
|
||||
<PackageReference Include="Hangfire.Redis.StackExchange" Version="1.12.0" />
|
||||
<PackageReference Include="Mapster" Version="7.4.0" />
|
||||
<PackageReference Include="Masuit.Tools.Core" Version="2025.5.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="8.0.2" />
|
||||
<PackageReference Include="MiniExcel" Version="1.42.0" />
|
||||
<PackageReference Include="nacos-sdk-csharp.AspNetCore" Version="1.3.10" />
|
||||
<PackageReference Include="nacos-sdk-csharp.Extensions.Configuration" Version="1.3.10" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
<PackageReference Include="QRCoder" Version="1.4.1" />
|
||||
<PackageReference Include="RabbitMQ.Client" Version="7.2.0" />
|
||||
<PackageReference Include="RestSharp" Version="112.1.0" />
|
||||
<PackageReference Include="Serilog" Version="4.3.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
|
||||
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.4" />
|
||||
<PackageReference Include="Serilog.Sinks.Async" Version="2.1.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Grafana.Loki" Version="8.3.1" />
|
||||
<PackageReference Include="Serilog.Sinks.PeriodicBatching" Version="5.0.0" />
|
||||
<PackageReference Include="SqlSugarCore" Version="5.1.4.193" />
|
||||
<PackageReference Include="UserCenter.Model" Version="1.5.0" />
|
||||
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
using SqlSugar;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using YuanXuan.IM.Common.Entities;
|
||||
using YuanXuan.IM.Common.Request;
|
||||
|
||||
namespace YuanXuan.IM.Core.Interfaces
|
||||
{
|
||||
public interface IBaseService<TEntity> where TEntity : BaseEntity, new()
|
||||
{
|
||||
Task<bool> CreateAsync(TEntity entity);
|
||||
Task<bool> CreateAsync(List<TEntity> entity);
|
||||
Task<long> InsertReturnBigIdentityAsync(TEntity entity);
|
||||
Task<long> CreateReturnSnowflakeIdAsync(TEntity entity);
|
||||
Task<List<long>> CreateReturnSnowflakeIdAsync(List<TEntity> entity);
|
||||
Task<bool> DeleteByIdsAsync(params dynamic[] ids);
|
||||
Task<bool> DeleteByIdsAsync(params long[] ids);
|
||||
Task<TEntity> GetByIdAsync(long id);
|
||||
Task<TEntity> GetFirstAsync(System.Linq.Expressions.Expression<Func<TEntity, bool>> whereExpression);
|
||||
Task<List<TEntity>> GetListAsync(System.Linq.Expressions.Expression<Func<TEntity, bool>> whereExpression = null);
|
||||
Task<PageResponse<TEntity>> GetPageListAsync(PageRequest pagination, Expression<Func<TEntity, bool>> whereExpression = null);
|
||||
Task<PageResponse<TEntity>> GetPageListAsync<T>(PageRequest pagination, Expression<Func<TEntity, bool>> whereExpression, Expression<Func<TEntity, object>> orderByExpression = null, OrderByType orderByType = OrderByType.Asc);
|
||||
Task<PageResponse<T>> GetPageListAsync<T>(PageRequest pagination, Expression<Func<TEntity, bool>> whereExpression = null);
|
||||
Task<TEntity> GetSingleAsync(System.Linq.Expressions.Expression<Func<TEntity, bool>> whereExpression);
|
||||
Task<bool> UpdateAsync(TEntity entity);
|
||||
Task<bool> UpdateAsync(List<TEntity> entity);
|
||||
Task<bool> UpdateColumsAsync(Expression<Func<TEntity, TEntity>> columns, Expression<Func<TEntity, bool>> whereExpression);
|
||||
Task<bool> AnyAsync(Expression<Func<TEntity, bool>> whereExpression);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,254 @@
|
|||
using Mapster;
|
||||
using SqlSugar;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using YuanXuan.IM.Common.Entities;
|
||||
using YuanXuan.IM.Common.Request;
|
||||
using YuanXuan.IM.Core.Interfaces;
|
||||
using YuanXuan.IM.Infrastructure.DBContext;
|
||||
|
||||
namespace YuanXuan.IM.Core.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 服务基类
|
||||
/// </summary>
|
||||
//[Inject(Lifetime = Microsoft.Extensions.DependencyInjection.ServiceLifetime.Scoped)]
|
||||
public class BaseService<TEntity> : IBaseService<TEntity> where TEntity : BaseEntity, new()
|
||||
{
|
||||
//protected readonly SugarRepository<TEntity> _db;
|
||||
private readonly SugarRepository<TEntity> _db;
|
||||
|
||||
|
||||
public BaseService(SugarRepository<TEntity> repository)
|
||||
{
|
||||
_db = repository;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建(自增ID)
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
public virtual async Task<bool> CreateAsync(TEntity entity)
|
||||
{
|
||||
return await _db.InsertAsync(entity);
|
||||
}
|
||||
/// <summary>
|
||||
/// 创建
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
public virtual async Task<bool> CreateAsync(List<TEntity> entity)
|
||||
{
|
||||
return await _db.InsertRangeAsync(entity);
|
||||
}
|
||||
/// <summary>
|
||||
/// 创建(自增ID)
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
public virtual async Task<long> InsertReturnBigIdentityAsync(TEntity entity)
|
||||
{
|
||||
return await _db.InsertReturnBigIdentityAsync(entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建并返回雪花ID
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
public virtual async Task<long> CreateReturnSnowflakeIdAsync(TEntity entity)
|
||||
{
|
||||
return await _db.InsertReturnSnowflakeIdAsync(entity);
|
||||
}
|
||||
/// <summary>
|
||||
/// 创建并返回雪花ID
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
public virtual async Task<List<long>> CreateReturnSnowflakeIdAsync(List<TEntity> entity)
|
||||
{
|
||||
return await _db.InsertReturnSnowflakeIdAsync(entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 物理删除
|
||||
/// </summary>
|
||||
/// <param name="ids"></param>
|
||||
/// <returns></returns>
|
||||
public virtual async Task<bool> DeleteByIdsAsync(params dynamic[] ids)
|
||||
{
|
||||
return await _db.DeleteByIdsAsync(ids);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 物理删除
|
||||
/// </summary>
|
||||
/// <param name="ids"></param>
|
||||
/// <returns></returns>
|
||||
public virtual async Task<bool> DeleteByIdsAsync(params long[] ids)
|
||||
{
|
||||
return await _db.Context.Deleteable<TEntity>().Where(x => ids.Contains(x.Id)).ExecuteCommandHasChangeAsync();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 更新
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
public virtual async Task<bool> UpdateAsync(TEntity entity)
|
||||
{
|
||||
return await _db.UpdateAsync(entity);
|
||||
}
|
||||
/// <summary>
|
||||
/// 批量更新
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
public virtual async Task<bool> UpdateAsync(List<TEntity> entity)
|
||||
{
|
||||
return await _db.UpdateRangeAsync(entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新指定字段
|
||||
/// </summary>
|
||||
/// <param name="columns"></param>
|
||||
/// <param name="whereExpression"></param>
|
||||
/// <returns></returns>
|
||||
public virtual async Task<bool> UpdateColumsAsync(Expression<Func<TEntity, TEntity>> columns, Expression<Func<TEntity, bool>> whereExpression)
|
||||
{
|
||||
return await _db.UpdateAsync(columns, whereExpression);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据ID获取
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
public virtual async Task<TEntity> GetByIdAsync(long id)
|
||||
{
|
||||
return await _db.GetByIdAsync(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据ID获取
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
public virtual async Task<List<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> whereExpression = null)
|
||||
{
|
||||
if (whereExpression == null)
|
||||
{
|
||||
return await _db.GetListAsync();
|
||||
}
|
||||
return await _db.GetListAsync(whereExpression);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据条件获取单个
|
||||
/// </summary>
|
||||
/// <param name="whereExpression"></param>
|
||||
/// <returns></returns>
|
||||
public virtual async Task<TEntity> GetSingleAsync(Expression<Func<TEntity, bool>> whereExpression)
|
||||
{
|
||||
return await _db.GetSingleAsync(whereExpression);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据条件获取第一个
|
||||
/// </summary>
|
||||
/// <param name="whereExpression"></param>
|
||||
/// <returns></returns>
|
||||
public virtual async Task<TEntity> GetFirstAsync(Expression<Func<TEntity, bool>> whereExpression)
|
||||
{
|
||||
return await _db.GetFirstAsync(whereExpression);
|
||||
}
|
||||
public virtual async Task<PageResponse<TEntity>> GetPageListAsync<T>(PageRequest pagination, Expression<Func<TEntity, bool>> whereExpression, Expression<Func<TEntity, object>> orderByExpression = null, OrderByType orderByType = OrderByType.Asc)
|
||||
{
|
||||
var pageModel = pagination.Adapt<PageModel>();
|
||||
List<TEntity> result = null;
|
||||
if (whereExpression != null)
|
||||
result = await _db.GetPageListAsync(whereExpression, pageModel, orderByExpression, orderByType);
|
||||
else
|
||||
result = await _db.GetPageListAsync(x => 1 == 1, pageModel, orderByExpression, orderByType);
|
||||
return new PageResponse<TEntity>
|
||||
{
|
||||
Items = result,
|
||||
Total = pageModel.TotalCount,
|
||||
};
|
||||
}
|
||||
/// <summary>
|
||||
/// 分页查询
|
||||
/// </summary>
|
||||
/// <param name="whereExpression"></param>
|
||||
/// <param name="pagination"></param>
|
||||
/// <returns></returns>
|
||||
public virtual async Task<PageResponse<TEntity>> GetPageListAsync(PageRequest pagination, Expression<Func<TEntity, bool>> whereExpression = null)
|
||||
{
|
||||
var pageModel = pagination.Adapt<PageModel>();
|
||||
|
||||
List<TEntity> result = null;
|
||||
if (whereExpression != null)
|
||||
result = await _db.GetPageListAsync(whereExpression, pageModel, orderByExpression: x => x.Id, orderByType: OrderByType.Desc);
|
||||
else
|
||||
result = await _db.GetPageListAsync(x => 1 == 1, pageModel, orderByExpression: x => x.Id, orderByType: OrderByType.Desc);
|
||||
|
||||
return new PageResponse<TEntity>
|
||||
{
|
||||
Items = result,
|
||||
Total = pageModel.TotalCount,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询
|
||||
/// </summary>
|
||||
/// <param name="whereExpression"></param>
|
||||
/// <param name="pagination"></param>
|
||||
/// <returns></returns>
|
||||
public virtual async Task<PageResponse<T>> GetPageListAsync<T>(PageRequest pagination, Expression<Func<TEntity, bool>> whereExpression)
|
||||
{
|
||||
var pageModel = pagination.Adapt<PageModel>();
|
||||
|
||||
List<TEntity> result = null;
|
||||
if (whereExpression != null)
|
||||
result = await _db.GetPageListAsync(whereExpression, pageModel, orderByExpression: x => x.Id, orderByType: OrderByType.Desc);
|
||||
else
|
||||
result = await _db.GetPageListAsync(x => 1 == 1, pageModel, orderByExpression: x => x.Id, orderByType: OrderByType.Desc);
|
||||
|
||||
return new PageResponse<T>
|
||||
{
|
||||
Items = result.Adapt<List<T>>(),
|
||||
Total = pageModel.TotalCount,
|
||||
};
|
||||
}
|
||||
public virtual async Task<bool> AnyAsync(Expression<Func<TEntity, bool>> whereExpression)
|
||||
{
|
||||
return await _db.IsAnyAsync(whereExpression);
|
||||
}
|
||||
//protected async Task<SqlSugarTransaction> BeginTransaction(IsolationLevel isolationLevel = IsolationLevel.ReadCommitted)
|
||||
//{
|
||||
// return await _db.Context.AsTenant().UseTranAsync(isolationLevel);
|
||||
//}
|
||||
|
||||
//// 针对不同业务场景使用不同隔离级别
|
||||
//protected async SqlSugarTransaction BeginSerializableTransaction()
|
||||
//{
|
||||
// return BeginTransaction(IsolationLevel.Serializable);
|
||||
//}
|
||||
|
||||
//protected async SqlSugarTransaction BeginRepeatableReadTransaction()
|
||||
//{
|
||||
// return BeginTransaction(IsolationLevel.RepeatableRead);
|
||||
//}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\YuanXuan.IM.Common\YuanXuan.IM.Common.csproj" />
|
||||
<ProjectReference Include="..\YuanXuan.IM.Infrastructure\YuanXuan.IM.Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Interfaces\Login\" />
|
||||
<Folder Include="Services\Login\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
using SqlSugar;
|
||||
using YuanXuan.IM.Common.Entities;
|
||||
|
||||
namespace YuanXuan.IM.Infrastructure.DBContext
|
||||
{
|
||||
/// <summary>
|
||||
/// SqlSugar通用仓储类
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
public class SugarRepository<TEntity> : SimpleClient<TEntity> where TEntity : BaseEntity, new()
|
||||
{
|
||||
public SugarRepository(ISqlSugarClient sugarClient) : base(sugarClient)
|
||||
{
|
||||
var db = sugarClient.AsTenant().GetConnectionWithAttr<TEntity>();
|
||||
base.Context = db;
|
||||
}
|
||||
|
||||
public IEnumerable<object> Queryable<T1, T2>(Func<object, object, JoinQueryInfos> value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
using FreeRedis;
|
||||
using Masuit.Tools;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace YuanXuan.IM.Infrastructure.Redis
|
||||
{
|
||||
public abstract class RedisHelper
|
||||
{
|
||||
|
||||
private static RedisClient _instance;
|
||||
|
||||
/// <summary>
|
||||
/// redis实例
|
||||
/// </summary>
|
||||
public static RedisClient Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
throw new Exception("使用前初始化redis静态访问类 RedisHelper.Initialization(new FreeRedis.RedisClient(\"127.0.0.1:6379,password=123,defaultDatabase=13,maxpoolsize=50,prefix=key前辍\"));");
|
||||
}
|
||||
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
public static void SetLoginToken(long userId, string token)
|
||||
{
|
||||
var key = "Login_Token";
|
||||
Instance.HSet(key, userId.ToString(), token);
|
||||
}
|
||||
public static void DeleteLoginToken(long userId)
|
||||
{
|
||||
var key = "Login_Token";
|
||||
Instance.HDel(key, userId.ToString());
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 初始化redis静态访问类 RedisHelper.Initialization(new FreeRedis.RedisClient(\"127.0.0.1:6379,password=123,defaultDatabase=13,maxpoolsize=50,prefix=key前辍\"))
|
||||
/// </summary>
|
||||
/// <param name="redisClient"></param>
|
||||
public static void Initialization(string connectionString)
|
||||
{
|
||||
_instance = new FreeRedis.RedisClient(connectionString)
|
||||
{
|
||||
Serialize = (x) => x.ToJsonString(),
|
||||
Deserialize = (x, t) => JsonConvert.DeserializeObject(x, t),
|
||||
};
|
||||
}
|
||||
|
||||
internal static ThreadLocal<Random> rnd = new ThreadLocal<Random>(() => new Random());
|
||||
/// <summary>
|
||||
/// 随机秒(防止所有key同一时间过期,雪崩)
|
||||
/// </summary>
|
||||
/// <param name="minTimeoutSeconds">最小秒数</param>
|
||||
/// <param name="maxTimeoutSeconds">最大秒数</param>
|
||||
/// <returns></returns>
|
||||
public static int RandomExpired(int minTimeoutSeconds, int maxTimeoutSeconds) => rnd.Value.Next(minTimeoutSeconds, maxTimeoutSeconds);
|
||||
/// <summary>
|
||||
/// 获取Redis分布式锁
|
||||
/// </summary>
|
||||
/// <param name="lockKey">锁键</param>
|
||||
/// <param name="lockValue">锁值(建议使用GUID)</param>
|
||||
/// <param name="expireSeconds">过期时间(秒)</param>
|
||||
/// <returns>是否获取成功</returns>
|
||||
public static bool TryLock(string lockKey, string lockValue, int expireSeconds = 30)
|
||||
{
|
||||
return Instance.SetNx(lockKey, lockValue, expireSeconds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放Redis分布式锁
|
||||
/// </summary>
|
||||
/// <param name="lockKey">锁键</param>
|
||||
/// <param name="lockValue">锁值(必须与加锁时一致)</param>
|
||||
/// <returns>是否释放成功</returns>
|
||||
public static bool ReleaseLock(string lockKey, string lockValue)
|
||||
{
|
||||
// 使用Lua脚本确保原子性:只有当锁的值匹配时才删除
|
||||
const string luaScript = @"
|
||||
if redis.call('GET', KEYS[1]) == ARGV[1] then
|
||||
return redis.call('DEL', KEYS[1])
|
||||
else
|
||||
return 0
|
||||
end";
|
||||
|
||||
var result = Instance.Eval(luaScript, new[] { lockKey }, new[] { lockValue });
|
||||
return (long)result == 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 延长Redis锁的过期时间
|
||||
/// </summary>
|
||||
/// <param name="lockKey">锁键</param>
|
||||
/// <param name="lockValue">锁值</param>
|
||||
/// <param name="expireSeconds">新的过期时间(秒)</param>
|
||||
/// <returns>是否续期成功</returns>
|
||||
public static bool RenewLock(string lockKey, string lockValue, int expireSeconds)
|
||||
{
|
||||
// 使用Lua脚本确保原子性:只有当锁的值匹配时才更新过期时间
|
||||
const string luaScript = @"
|
||||
if redis.call('GET', KEYS[1]) == ARGV[1] then
|
||||
return redis.call('EXPIRE', KEYS[1], ARGV[2])
|
||||
else
|
||||
return 0
|
||||
end";
|
||||
|
||||
var result = Instance.Eval(luaScript, new[] { lockKey }, new[] { lockValue, expireSeconds.ToString() });
|
||||
return (long)result == 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步设置键值对
|
||||
/// </summary>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <param name="expiry">过期时间</param>
|
||||
/// <returns></returns>
|
||||
public static async Task SetAsync(string key, object value, TimeSpan expiry)
|
||||
{
|
||||
await Instance.SetAsync(key, value, expiry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取字符串值
|
||||
/// </summary>
|
||||
/// <param name="key">键</param>
|
||||
/// <returns></returns>
|
||||
public static async Task<string> GetStringAsync(string key)
|
||||
{
|
||||
return await Instance.GetAsync<string>(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步删除键
|
||||
/// </summary>
|
||||
/// <param name="key">键</param>
|
||||
/// <returns></returns>
|
||||
public static async Task DeleteAsync(string key)
|
||||
{
|
||||
await Instance.DelAsync(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取值
|
||||
/// </summary>
|
||||
/// <typeparam name="T">类型</typeparam>
|
||||
/// <param name="key">键</param>
|
||||
/// <returns></returns>
|
||||
public static async Task<T> GetAsync<T>(string key)
|
||||
{
|
||||
return await Instance.GetAsync<T>(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步设置键值对(默认过期时间)
|
||||
/// </summary>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <returns></returns>
|
||||
public static async Task SetAsync(string key, object value)
|
||||
{
|
||||
await Instance.SetAsync(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
|
||||
<PackageReference Include="FreeRedis" Version="1.5.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\YuanXuan.IM.Common\YuanXuan.IM.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
namespace YuanXuan.IM.Services
|
||||
{
|
||||
public class Class1
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
Loading…
Reference in New Issue