第二节:必备中间件集成(缓存策略、Redis服务)、JWT校验整合、 YpfCore.Utils层详解
一. 必备中间件集成
1. 配置缓存策略
关于Redis缓存,这里统一使用CSRedisCore程序集,删掉旧版本的StackExchange.Redis,通过Nuget安装【CSRedisCore】【Caching.CSRedis】
(1). 前提:
A. AddMemoryCache 对应的使用的是 IMemoryCache , 它永远代表内存缓存,这里直接默认注入,不需要做其它判断了。
B. AddDistributedMemoryCache 对应的是 IDistributedCache,他可以基于内存缓存来使用,也可以基于Redis缓存来使用。
(2).说明:
A. IMemoryCache 默认直接注入进去,如果想一直使用内存缓存,不考虑切换redis缓存,可以使用它。
B. IDistributedCache 根据配置选择注入,既可以基于内存缓存,也可以基于redis缓存,可以无缝切换
注:启用redis缓存,必须开启redis服务器,否则程序无法运行,try-catch 无效。
配置代码:
/*
说明:
(1). AddMemoryCache 对应的使用的是 IMemoryCache , 它永远代表内存缓存,这里直接默认注入,不需要做其它判断了。
(2). AddDistributedMemoryCache 对应的是 IDistributedCache,他可以基于内存缓存来使用,也可以基于Redis缓存来使用,二者可以无缝切换,详见下面参数配置
*/
builder.Services.AddMemoryCache(); //默认注入了
switch (builder.Configuration["CacheType"])
{
case "DistributedCache":
{
builder.Services.AddDistributedMemoryCache();
}; break;
case "RedisCache":
{
var csredis = new CSRedisClient(builder.Configuration["RedisStr"]);
builder.Services.AddSingleton<IDistributedCache>(new CSRedisCache(csredis));
}; break;
default: throw new Exception("缓存类型无效");
}
配置参数:
//缓存类型 (DistributedCache 表示分布式内存缓存、RedisCache 表示基于Redis的缓存,二者可以无缝切换)
//注:(1).启用Redis缓存的时候,必须启动Redis服务器,否则程序无法运行
// (2).这里的 DistributedCache 表示注入 IDistributedCache内存缓存, 而IMemoryCache是默认直接注入的,不需要配置
"CacheType": "DistributedCache",
//Redis连接字符串 【119.45.xx.xx:6379,password=123456,defaultDatabase=1】
"RedisStr": "localhost:6379,defaultDatabase=1"
测试:
/// <summary>
/// 7-1 单纯的内存缓存
/// </summary>
/// <returns></returns>
[HttpPost]
public string Test7_1()
{
string myTime = memoryCache.GetOrCreate("mySpecialTime1", (cacheEnty) =>
{
//将数据库中查询的结果写入缓存
return DateTime.Now.ToString();
});
return myTime;
}
/// <summary>
/// 7-2 内存缓存和redis缓存无缝切换
/// </summary>
/// <returns></returns>
[HttpPost]
public string Test7_2()
{
var myTime = distributedCache.GetString("mySpecialTime2");
if (string.IsNullOrEmpty(myTime))
{
//从数据库中查询 写入缓存
distributedCache.SetString("mySpecialTime2", DateTime.Now.ToString());
}
return myTime;
}
(3) 整合改造的 MemoryCachePro 和 DistributedCachePro
分别针对IMemoryCache 和 IDistributedCache 封装扩展了GetOrCreate方法。了解思路即可,当前版本是两个并列的过期时间,并没有达到在手动设置过期时间的基础上增加随机时间戳的效果。
代码分享
IMemoryCachePro
/// <summary> /// 扩展的内存缓存接口 /// </summary> public interface IMemoryCachePro { /// <summary> /// 01-读取或设置缓存(同步) /// </summary> TResult GetOrCreate<TResult>(object key, Func<ICacheEntry, TResult> Func, int defaultExpireSecondes = 60); /// <summary> /// 02-读取or设置缓存(异步) /// </summary> Task<TResult> GetOrCreateAsync<TResult>(object key, Func<ICacheEntry, Task<TResult>> Func, int defaultExpireSecondes = 60); }
MemoryCachePro
/// <summary> /// 基于Asp.Net Core中的IMemoryCache类型扩展 /// 1. 目标: /// 主要是对内存缓存添加一个随机过期时间,防止缓存出现雪崩现象。 /// 至于缓存穿透,通常采用cache null策略,表现在调用的时候,对目标值不判空,直接存入缓存即可 /// 2. 依赖程序集 /// 【Microsoft.Extensions.Caching.Memory】 /// 3. 思路: /// (1).先反编译分析一下默认GetOrCreate的实现,在这个基础上继续添加业务(详见截图) /// 注:这里直接不直接使用内置的GetOrCreate,而是直接用它的实现代码来改造,可以省了一次的委托的调用。 /// 经分析源码可知: CreateEntry方法,设置一个缓存key;然后通过 cacheEntry.Value,给该key赋值,这相当于一个基础方法了,不能在再拆解。 /// (2).增加限制:校验缓存内容的类型 /// IQueryable、IEnumerable等类型可能存在着延迟加载的问题,如果把这两种类型的变量指向的对象保存到缓存中, /// 在我们把它们取出来再去执行的时候,如果它们延迟加载时候需要的对象已经被释放的话,就会执行失败。因此缓存禁止这两种类型。 /// 注:如果是是IEnumerable<String>这样的泛型类型,则把String这样的具体类型信息去掉,变成IEnumerable<>再比较 【待测试】 /// (3).增加随机过期时间 /// A. 默认值为60s,即在60s-120s之间取一个值。 /// B. 如果使用的时候想设置缓存是永久有效的,此时这个值将导致无法设置缓存永久有效,需要将该值改为0(或负数) /// C. 设置为0 或者 负数,不生效 /// (4).全局注册:builder.Services.AddScoped<IMemoryCachePro, MemoryCachePro>(); /// /// 4. 测试 /// (1).默认测试【ok】 /// 手动不设置过期时间,经测试:默认会加上一个60-120s之间的随机过期时间 /// (2).叠加测试 【ok】 /// 手动设置的时绝对过期时间,比如到某个时间点A点,那么和默认的“绝对随机时间”是怎么生效的呢? /// 测试结果:首先到了A点一定过期,其次到了默认随机绝对时间也一定过期,如果此时还没有到达A点,那么会再次进入一个默认的随机过期时间周期。 /// (3).叠加测试 【ok】 /// 手动设置的时滑动过期时间,那么和默认的“绝对随机时间”是怎么生效的呢? /// 测试结果:在滑动过期时间内,每访问一次,自动重置一下时间;如果超过滑动过期时间没有访问,一定过期。 如果到了设置的默认随机过期时间,也一定过期。 /// (4).Cache Null测试【ok】 /// 将null存入缓存,经测试,可以正常存入null,读取出来的时候也是null (注:不是"null") /// /// 5.结论 /// 了解思路即可,当前版本是两个并列的过期时间,并没有达到在手动设置过期时间的基础上增加随机时间戳的效果。 /// /// /// </summary> public class MemoryCachePro: IMemoryCachePro { private readonly IMemoryCache memoryCache; public MemoryCachePro(IMemoryCache memoryCache) { this.memoryCache = memoryCache; } #region 01-读取或设置缓存(同步) /// <summary> /// 01-读取或设置缓存(同步) /// </summary> /// <typeparam name="TResult">函数的</typeparam> /// <param name="key">缓存key</param> /// <param name="Func">委托,需要传入一个函数 /// 函数的参数为:ICacheEntry /// 函数的返回值为:TResult /// </param> /// <param name="defaultExpireSecondes">默认添加的随机过期时间,随机值为 [defaultExprieSeconds,defaultExprieSeconds * 2]之间 /// (1).默认值为60s,即在60s-120s之间取一个值。 /// (2).如果使用的时候想设置缓存是永久有效的,此时这个值将导致无法设置缓存永久有效,需要将该值改为0(或负数) /// (3).设置为0 或者 负数,不生效 /// </param> /// <returns></returns> public TResult GetOrCreate<TResult>(object key, Func<ICacheEntry, TResult> Func, int defaultExpireSecondes = 60) { //一. 校验缓存类型 ValidateCacheValueType<TResult>(); //二. 利用TryGetValue和CreateEntry方法进行封装 if (!memoryCache.TryGetValue(key, out var result)) { //表示缓存不存在 //2.1 创建或覆盖一个缓存key using ICacheEntry cacheEntry = memoryCache.CreateEntry(key); //三. 添加一个随机过期时间 if (defaultExpireSecondes > 0) { //只有该值 > 0 才生效 SetCacheRandomTime(cacheEntry, defaultExpireSecondes); } //2.2 返回值赋值 (这个值来源于委托的调用,获取的返回值) result = Func(cacheEntry); //2.3 给该缓存赋值 cacheEntry.Value = result; // 上述2.2 2.3可以简化为 //result = (cacheEntry.Value = factory(cacheEntry)); } return (TResult)result; } #endregion #region 02-读取or设置缓存(异步) /// <summary> /// 02-读取or设置缓存(异步) /// </summary> /// <typeparam name="TResult"></typeparam> /// <param name="key"></param> /// <param name="Func"></param> /// <param name="defaultExpireSecondes"></param> /// <returns></returns> public async Task<TResult> GetOrCreateAsync<TResult>(object key, Func<ICacheEntry, Task<TResult>> Func, int defaultExpireSecondes = 60) { //一. 校验缓存类型 ValidateCacheValueType<TResult>(); //二. 利用TryGetValue和CreateEntry方法进行封装 if (!memoryCache.TryGetValue(key, out TResult result)) { //表示缓存不存在 //2.1 创建或覆盖一个缓存key using ICacheEntry cacheEntry = memoryCache.CreateEntry(key); //三. 添加一个随机过期时间 if (defaultExpireSecondes > 0) { //只有该值 > 0 才生效 SetCacheRandomTime(cacheEntry, defaultExpireSecondes); } //2.2 返回值赋值 (这个值来源于委托的调用,获取的返回值) result = await Func(cacheEntry); //2.3 给该缓存赋值 cacheEntry.Value = result; } return result; } #endregion /********************************************* 下面是抽离的方法 **************************************************************/ #region 检验缓存value的类型 /// <summary> /// 检验缓存value的类型 /// 注:这里直接以<T>的形式传递,不写在参数里了 /// </summary> /// <typeparam name="T">缓存value的类型</typeparam> private static void ValidateCacheValueType<T>() { Type typeResult = typeof(T); if (typeResult.IsGenericType) { //如果是是IEnumerable<String>这样的泛型类型,则把String这样的具体类型信息去掉,再比较 typeResult = typeResult.GetGenericTypeDefinition(); } //类型比较,使用==进行比较,不要使用IsAssignableTo var typeList = new List<Type>() { typeof(IEnumerable), typeof(IEnumerable<>), typeof(IAsyncEnumerable<T>),typeof(IQueryable),typeof(IQueryable<T>) }; if (typeList.Contains(typeResult)) { throw new InvalidOperationException($"T of {typeResult} is not allowed, please use List<T> or T[] instead."); } } #endregion #region 设置缓存随机过期时间 /// <summary> /// 设置缓存随机过期时间 /// </summary> /// <param name="entry">缓存实体</param> /// <param name="expireSecondes">过期时间</param> private static void SetCacheRandomTime(ICacheEntry entry, int expireSecondes) { double result = Random.Shared.NextDouble(expireSecondes, expireSecondes * 2); entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(result); } #endregion }
IDistributedCachePro
/// <summary> /// 扩展的分布式缓存接口 /// </summary> public interface IDistributedCachePro { /// <summary> /// 01-读取或设置缓存(同步) /// </summary> TResult GetOrCreate<TResult>(string key, Func<DistributedCacheEntryOptions, TResult> Func, int defaultExpireSecondes = 60); /// <summary> /// 02-读取或设置缓存(异步) /// </summary> Task<TResult> GetOrCreateAsync<TResult>(string key, Func<DistributedCacheEntryOptions, Task<TResult>> Func, int defaultExpireSecondes = 60); }
DistributedCachePro
/// <summary> /// 分布式缓存 /// 1. 目标: /// 主要是对分布式缓存添加一个随机过期时间,防止缓存出现雪崩现象。 /// 至于缓存穿透,通常采用cache null策略,表现在调用的时候,对目标值不判空,直接存入缓存即可 /// 2. 依赖程序集 /// 【Microsoft.Extensions.Caching.Abstractions】,PS:【Microsoft.Extensions.Caching.Memory】里包含前面的程序集。 /// 3. 思路: /// (1).利用SetString和GetString方法两个基础方法来进行封装 /// (2).判断缓存key中是否有值 /// A. 无值 /// 先通过defaultExpireSecondes值内容,来决定是否调用封装方法SetCacheRandomTime来设置缓存随机过期时间。 /// 然后,调用Func委托传递过来的方法,获取需要存入缓存的内容。 /// 最后,将内容序列化一下,存入缓存。 /// B. 有值 /// 首先,刷新一下缓存,可以达到重置滑动过期时间的目的。 /// 然后,反序列化成对象进行返回。 /// (3).全局注册:builder.Services.AddScoped<IDistributedCachePro, DistributedCachePro>(); /// 4. 测试 /// (1).默认测试【ok】 /// 手动不设置过期时间,经测试:默认会加上一个60-120s之间的随机过期时间 /// (2).叠加测试 【ok】 /// 手动设置的时绝对过期时间,比如到某个时间点A点,那么和默认的“绝对随机时间”是怎么生效的呢? /// 测试结果:首先到了A点一定过期,其次到了默认随机绝对时间也一定过期,如果此时还没有到达A点,那么会再次进入一个默认的随机过期时间周期。 /// (3).叠加测试 【ok】 /// 手动设置的时滑动过期时间,那么和默认的“绝对随机时间”是怎么生效的呢? /// 测试结果:在滑动过期时间内,每访问一次,自动重置一下时间;如果超过滑动过期时间没有访问,一定过期。 如果到了设置的默认随机过期时间,也一定过期。 /// (4).Cache Null测试【ok】 /// 将null存入缓存,经测试,可以正常存入null,读取出来反序列化的时候也是null (注:不是"null") /// 5. 结论 /// 了解封装思路 和 委托的使用即可,该封装可以弥补IDistributedCache中没有GetOrCreate的遗憾,但是当前版本是两个并列的过期时间, /// 并没有达到在手动设置过期时间的基础上增加随机时间戳的效果。 /// /// </summary> public class DistributedCachePro: IDistributedCachePro { private readonly IDistributedCache distributedCache; public DistributedCachePro(IDistributedCache distributedCache) { this.distributedCache = distributedCache; } #region 01-读取或设置缓存(同步) /// <summary> /// 01-读取或设置缓存(同步) /// </summary> /// <typeparam name="TResult">委托返回类型</typeparam> /// <param name="key">缓存key</param> /// <param name="Func">委托,需要传入一个函数 /// 函数的参数为:DistributedCacheEntryOptions /// 函数的返回值为:TResult /// </param> /// <param name="defaultExpireSecondes">默认添加的随机过期时间,随机值为 [defaultExprieSeconds,defaultExprieSeconds * 2]之间 /// (1).默认值为60s,即在60s-120s之间取一个值。 /// (2).如果使用的时候想设置缓存是永久有效的,此时这个值将导致无法设置缓存永久有效,需要将该值改为0(或负数) /// (3).设置为0 或者 负数,不生效 /// </param> /// <returns></returns> public TResult GetOrCreate<TResult>(string key, Func<DistributedCacheEntryOptions, TResult> Func, int defaultExpireSecondes = 60) { //判断缓存中是否有值 string result = distributedCache.GetString(key); if (string.IsNullOrEmpty(result)) { //配置随机过期时间 DistributedCacheEntryOptions options = new(); if (defaultExpireSecondes > 0) { SetCacheRandomTime(options, defaultExpireSecondes); } //调用方法 TResult value = Func(options); // 写入缓存 string valueString = JsonHelp.ToJsonString(value); //null会被json序列化为字符串"null",所以可以防范“缓存穿透” distributedCache.SetString(key, valueString, options); return value; } else { //读取缓存 distributedCache.Refresh(key); //重置一下过期时间,便于滑动过期时间延期 return JsonHelp.ToObject<TResult>(result); //"null"会被反序列化为null; TResult如果是引用类型,就有为null的可能性; } } #endregion #region 02-读取或设置缓存(异步) /// <summary> /// 02-读取或设置缓存(异步) /// </summary> /// <typeparam name="TResult">委托返回类型</typeparam> /// <param name="key">缓存key</param> /// <param name="Func">委托,需要传入一个函数 /// 函数的参数为:DistributedCacheEntryOptions /// 函数的返回值为:TResult /// </param> /// <param name="defaultExpireSecondes">默认添加的随机过期时间,随机值为 [defaultExprieSeconds,defaultExprieSeconds * 2]之间 /// (1).默认值为60s,即在60s-120s之间取一个值。 /// (2).如果使用的时候想设置缓存是永久有效的,此时这个值将导致无法设置缓存永久有效,需要将该值改为0(或负数) /// (3).设置为0 或者 负数,不生效 /// </param> /// <returns></returns> public async Task<TResult> GetOrCreateAsync<TResult>(string key, Func<DistributedCacheEntryOptions, Task<TResult>> Func, int defaultExpireSecondes = 60) { //判断缓存中是否有值 string result = await distributedCache.GetStringAsync(key); if (string.IsNullOrEmpty(result)) { //配置随机过期时间 DistributedCacheEntryOptions options = new(); if (defaultExpireSecondes > 0) { SetCacheRandomTime(options, defaultExpireSecondes); } //调用方法 TResult value = await Func(options); // 写入缓存 string valueString = JsonHelp.ToJsonString(value); //null会被json序列化为字符串"null",所以可以防范“缓存穿透” await distributedCache.SetStringAsync(key, valueString, options); return value; } else { //读取缓存 await distributedCache.RefreshAsync(key); //重置一下过期时间,便于滑动过期时间延期 return JsonHelp.ToObject<TResult>(result); //"null"会被反序列化为null; TResult如果是引用类型,就有为null的可能性; } } #endregion /********************************************* 下面是抽离的方法 **************************************************************/ #region 设置缓存随机过期时间 /// <summary> /// 设置缓存随机过期时间 /// </summary> /// <param name="expireSecondes">过期时间</param> private static DistributedCacheEntryOptions SetCacheRandomTime(DistributedCacheEntryOptions options, int expireSecondes) { double result = Random.Shared.NextDouble(expireSecondes, expireSecondes * 2); options.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(result); return options; } #endregion }
测试
/// <summary> /// 11_1 加强版内存缓存 /// </summary> /// <returns></returns> [HttpPost] public IActionResult Test11_1([FromServices] IMemoryCachePro cachePro) { var nowTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); //模拟从数据库中获取 //1. 加强版的缓存 //1.1 手动不设置过期时间 string result1 = cachePro.GetOrCreate<string>("myNowTime1", cacheEntry => nowTime); //1.2 手动设置绝对过期时间(某个时间点) string result2 = cachePro.GetOrCreate<string>("myNowTime2", cacheEntry => { cacheEntry.AbsoluteExpiration = new DateTimeOffset(DateTime.Parse("2022-11-02 08:10:00")); return nowTime; }, 10); //1.3 手动设置滑动过期时间 string result3 = cachePro.GetOrCreate<string>("myNowTime3", cacheEntry => { cacheEntry.SlidingExpiration = TimeSpan.FromSeconds(10); return nowTime; }, 30); //1.4 null策略 DateTime? content = null; var result4 = cachePro.GetOrCreate<DateTime?>("myNowTime4", cacheEntry => content); return Json(new { result1, result2, result3, result4 }); } /// <summary> /// 11_2 加强版分布式缓存 /// </summary> /// <returns></returns> [HttpPost] public IActionResult Test11_2([FromServices] IDistributedCachePro cachePro) { //1. 加强版的缓存 //1.1 手动不设置过期时间 var result1 = cachePro.GetOrCreate<PagingClass>("page1", cacheEntry => { PagingClass page = new() { pageNum = 1, pageSize = 10 }; //模拟从数据库中获取 return page; }); //1.2 手动设置绝对过期时间(某个时间点) var result2 = cachePro.GetOrCreate<PagingClass>("page2", cacheEntry => { cacheEntry.AbsoluteExpiration = new DateTimeOffset(DateTime.Parse("2022-11-02 08:10:00")); PagingClass page = new() { pageNum = 1, pageSize = 10 }; //模拟从数据库中获取 return page; }); //1.3 手动设置滑动过期时间 var result3 = cachePro.GetOrCreate<PagingClass>("page3", cacheEntry => { cacheEntry.SlidingExpiration = TimeSpan.FromSeconds(10); PagingClass page = new() { pageNum = 1, pageSize = 10 }; //模拟从数据库中获取 return page; }, 30); //1.4 Cache Null var result4 = cachePro.GetOrCreate<PagingClass>("page4", cacheEntry => { PagingClass page = null; //模拟从数据库中获取 return page; }); return Json(new { result1, result2, result3, result4 }); }
2. 单独的Redis服务
关于Redis缓存,这里统一使用CSRedisCore程序集,删掉旧版本的StackExchange.Redis,通过Nuget安装【CSRedisCore】【Caching.CSRedis】
说明:
这里是单独注册redis帮助类,单独使用,与默认缓存策略没有任何关系
注意:
必须开启redis服务器,否则程序无法运行,这里新增了一个配置 IsRegisterRedis,用来表示是否注册redis服务
代码
//7.注册Redis实例 (两种使用方式都注册)
if (builder.Configuration["IsRegisterRedis"] == "yes")
{
var myRedis = new CSRedisClient(builder.Configuration["RedisStr"]);
builder.Services.AddSingleton(myRedis);
RedisHelper.Initialization(myRedis);
}
配置:
//表示是否注册Redis单独服务--与缓存无关 (yes表示注册,no表示不注册)
"IsRegisterRedis": "no",
测试:
/// <summary>
/// 08-测试Redis的使用
/// (两种写法)
/// </summary>
/// <returns></returns>
[HttpPost]
public string Test8([FromServices] CSRedisClient _csredis)
{
//1. 用法1,需要注入
_csredis.Set("name1", "ypf11");
var result1 = _csredis.Get("name1");
Console.WriteLine($"name1={result1}");
//2. 用法2,直接使用RedisHelp类即可
RedisHelper.Set("name2", "ypf22");
var result2 = RedisHelper.Get("name2");
Console.WriteLine($"name2={result2}");
return "ok";
}
二. JWT校验整合
1. 校验JWT的准确性
迁移之前版本的CheckJwt过滤器,用来校验jwt准确性、过期性等,并注册为全局过滤器。
代码分享:
namespace YpfCore.AdminWeb.Filter; /// <summary> /// JWT校验过滤器 /// 控制器中获取jwt内容的代码: /// var jwtData = JsonHelp.ToObject<JwtData>(ControllerContext.RouteData.Values["auth"].ToString()); /// </summary> public class CheckJWT : ActionFilterAttribute { private IConfiguration _configuration; public CheckJWT(IConfiguration configuration) { _configuration = configuration; } public override void OnActionExecuting(ActionExecutingContext context) { //判断action是否有skip特性 var isHasAttr = context.ActionDescriptor.EndpointMetadata.Any(x => x.GetType() == typeof(SkipAllAttribute) || x.GetType() == typeof(SkipJwtAttribute)); if (isHasAttr == false) //表示需要校验,反之不需要校验,正常走业务 { var actionContext = context.HttpContext; if (IsAjaxRequest(actionContext.Request)) { //表示是ajax请求,则auth从Header中传过来 var token = actionContext.Request.Headers["auth"].ToString(); if (token == "null" || string.IsNullOrEmpty(token)) { //context.Result = new JsonResult(new { status = "error", msg = "非法请求,参数为空" }); context.Result = new ContentResult() { StatusCode = 401, Content = "非法请求,参数为空" }; return; } //校验auth的正确性 var result = JWTHelp.JWTJieM(token, _configuration["JWTSecret"]); if (result == "expired") { //context.Result = new JsonResult(new { status = "expired", msg = "非法请求,参数已经过期" }); context.Result = new ContentResult() { StatusCode = 401, Content = "非法请求,参数已经过期" }; return; } else if (result == "invalid") { //context.Result = new JsonResult(new { status = "invalid", msg = "非法请求,未通过校验1" }); context.Result = new ContentResult() { StatusCode = 401, Content = "非法请求,未通过校验" }; return; } else if (result == "error") { //context.Result = new JsonResult(new { status = "error", msg = "非法请求,未通过校验2" }); context.Result = new ContentResult() { StatusCode = 401, Content = "非法请求,未通过校验" }; return; } else { //表示校验通过,用于向控制器中传值 context.RouteData.Values.Add("auth", result); } } else { //表示是非ajax请求,则auth拼接在参数中传过来 var token = actionContext.Request.Query["auth"].ToString(); if (string.IsNullOrEmpty(token)) { context.Result = new ContentResult() { StatusCode = 401, Content = "非法请求,参数为空" }; return; } //校验auth的正确性 var result = JWTHelp.JWTJieM(token, _configuration["JWTSecret"]); if (result == "expired") { context.Result = new ContentResult() { StatusCode = 401, Content = "非法请求,参数已经过期" }; return; } else if (result == "invalid") { context.Result = new ContentResult() { StatusCode = 401, Content = "非法请求,未通过校验" }; return; } else if (result == "error") { context.Result = new ContentResult() { StatusCode = 401, Content = "非法请求,未通过校验" }; return; } else { //表示校验通过,用于向控制器中传值 context.RouteData.Values.Add("auth", result); } } } } /// <summary> /// 判断该请求是否是ajax请求 /// </summary> /// <param name="request"></param> /// <returns></returns> private static bool IsAjaxRequest(HttpRequest request) { string header = request.Headers["X-Requested-With"]; return "XMLHttpRequest".Equals(header); } }
全局注册:
//8.过滤器注册
builder.Services.Configure<MvcOptions>(option =>
{
option.Filters.Add<CheckJWT>();
});
三. YpfCore.Utils层详解
保留:SkipAllAttribute 和 SkipJwtAttribute,删掉SkipLoginAttribute,并且给已有的特性新增如下约束:[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
含义:可以作用在方法或类上、同一个方法/类针对该特性只能写一个、允许被继承。
/// <summary>
/// 跨过系统所有校验
///(可以作用在方法或类上、同一个方法/类针对该特性只能写一个、允许被继承)
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class SkipAllAttribute : Attribute
{
}
/// <summary>
/// 跨过JWT校验
///(可以作用在方法或类上、同一个方法/类针对该特性只能写一个、允许被继承)
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class SkipJwtAttribute : Attribute
{
}
2. Common通用类文件夹
需要安装的程序集有:
(1). ConfigHelp
直接调用,读取JSON 或 Xml中的配置文件,支持手动传入文件名,默认的json文件为appsettings.json。
/// <summary> /// 读取配置文件 /// 依赖程序集:【 Microsoft.Extensions.Configuration】、【Microsoft.Extensions.Configuration.FileExtensions】 /// 【Microsoft.Extensions.Configuration.Json】、【 Microsoft.Extensions.Configuration.xml】 /// </summary> public static class ConfigHelp { /// <summary> /// 读取Json类型的配置文件 /// </summary> /// <param name="key">键名</param> /// <param name="FilePath">文件路径,默认为:appsettings.json</param> /// <returns></returns> public static string GetString(string key, string FilePath = "appsettings.json") { var configurationBuilder = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile(FilePath, optional: true, reloadOnChange: true); var configuration = configurationBuilder.Build(); return configuration[key]; } /// <summary> /// 读取Xml类型的配置文件 /// </summary> /// <param name="key">键名</param> /// <param name="FilePath">文件路径,默认为:myXml.json</param> /// <returns></returns> public static string GetXmlString(string key, string FilePath = "myXml.json") { var configurationBuilder = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddXmlFile(FilePath, optional: true, reloadOnChange: true); var configuration = configurationBuilder.Build(); return configuration[key]; } }
(2). MailHelp
用于发送邮件。
/// <summary> /// 邮件帮助类 /// </summary> public class MailHelp { #region 01-发送邮件 /// <summary> /// 发送邮件 /// </summary> /// <param name="subject">主题</param> /// <param name="content">内容</param> /// <param name="receiveAddress">收件人邮箱</param> /// <param name="attachFileList">附件路径列表</param> /// <param name="senderName">发件人姓名</param> /// <returns></returns> public static string SendEmail(string subject, string content, string receiveAddress, List<string> attachFileList = null, string senderName = "") { string fromto = "brucelee@xxx.com"; //发件人邮箱地址 string name = "brucelee@xxx.com"; //发件人用户名 string upass = "lvyiyi626A"; //发件人密码 string smtp = "smtp.xxx.com"; //发件SMTP服务器(如果用qq邮箱发,则是:smtp.qq.com) SmtpClient _smtpClient = new SmtpClient(); _smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network;//指定电子邮件发送方式 _smtpClient.Host = smtp; //指定SMTP服务器 _smtpClient.Credentials = new System.Net.NetworkCredential(name, upass);//用户名和密码 MailMessage _mailMessage = new MailMessage(); //表示可以使用SmtpClient发送电子邮件 //发件人,发件人名 _mailMessage.From = new MailAddress(fromto, senderName); //收件人 _mailMessage.To.Add(receiveAddress); _mailMessage.SubjectEncoding = Encoding.UTF8; //Encoding.GetEncoding("gb2312"); //主题内容编码 _mailMessage.Subject = subject;//主题 _mailMessage.Body = content;//内容 //设置附件 if (attachFileList != null && attachFileList.Count > 0) { foreach (var item in attachFileList) { _mailMessage.Attachments.Add(new Attachment(item)); } } _mailMessage.BodyEncoding = Encoding.UTF8; //Encoding.GetEncoding("gb2312");//正文编码 _mailMessage.IsBodyHtml = true;//设置为HTML格式 _mailMessage.Priority = MailPriority.Normal;//MailPriority.High;//优先级 try { _smtpClient.Send(_mailMessage); //将指定的邮件发送到 SMTP 服务器以便传递。 return "ok"; } catch (Exception) { return "error"; } } #endregion #region 复杂发送(暂时注释) //private readonly static string SmtpServer = "smtp.wedn.net"; //private readonly static int SmtpServerPort = 25; //private readonly static bool SmtpEnableSsl = false; //private readonly static string SmtpUsername = "server@wedn.net"; //private readonly static string SmtpDisplayName = "Wedn.Net"; //private readonly static string SmtpPassword = "2014@itcast"; ///// <summary> ///// 发送邮件到指定收件人 ///// </summary> ///// <param name="to">收件人地址</param> ///// <param name="subject">主题</param> ///// <param name="mailBody">正文内容(支持HTML)</param> ///// <param name="copyTos">抄送地址列表</param> ///// <returns>是否发送成功</returns> //public static bool Send(string to, string subject, string mailBody, params string[] copyTos) //{ // return Send(new[] { to }, subject, mailBody, copyTos, new string[] { }, MailPriority.Normal); //} ///// <summary> ///// 发送邮件到指定收件人 ///// </summary> ///// <remarks> ///// 2013-11-18 18:55 Created By iceStone ///// </remarks> ///// <param name="tos">收件人地址列表</param> ///// <param name="subject">主题</param> ///// <param name="mailBody">正文内容(支持HTML)</param> ///// <param name="ccs">抄送地址列表</param> ///// <param name="bccs">密件抄送地址列表</param> ///// <param name="priority">此邮件的优先级</param> ///// <param name="attachments">附件列表</param> ///// <returns>是否发送成功</returns> ///// <exception cref="System.ArgumentNullException">attachments</exception> //public static bool Send(string[] tos, string subject, string mailBody, string[] ccs, string[] bccs, MailPriority priority, params Attachment[] attachments) //{ // if (attachments == null) throw new ArgumentNullException("attachments"); // if (tos.Length == 0) return false; // //创建Email实体 // var message = new MailMessage(); // message.From = new MailAddress(SmtpUsername, SmtpDisplayName); // message.Subject = subject; // message.Body = mailBody; // message.BodyEncoding = Encoding.UTF8; // message.IsBodyHtml = true; // message.Priority = priority; // //插入附件 // foreach (var attachment in attachments) // { // message.Attachments.Add(attachment); // } // //插入收件人地址,抄送地址和密件抄送地址 // foreach (var to in tos.Where(c => !string.IsNullOrEmpty(c))) // { // message.To.Add(new MailAddress(to)); // } // foreach (var cc in ccs.Where(c => !string.IsNullOrEmpty(c))) // { // message.CC.Add(new MailAddress(cc)); // } // foreach (var bcc in bccs.Where(c => !string.IsNullOrEmpty(c))) // { // message.CC.Add(new MailAddress(bcc)); // } // //创建SMTP客户端 // var client = new SmtpClient // { // Host = SmtpServer, // Credentials = new System.Net.NetworkCredential(SmtpUsername, SmtpPassword), // DeliveryMethod = SmtpDeliveryMethod.Network, // EnableSsl = SmtpEnableSsl, // Port = SmtpServerPort // }; // //client.SendCompleted += Client_SendCompleted; // //try // //{ // //发送邮件 // client.Send(message); // //client.SendAsync(message,DateTime.Now.ToString()); // //client.Dispose(); // //message.Dispose(); // return true; // //} // //catch (Exception) // //{ // // throw; // //} //} #endregion }
(3). RequestHelp
可以直接调用,发送Get、Post请求。
/// <summary> /// 基于HttpClientFactory的请求封装 /// 依赖【Microsoft.Extensions.DependencyInjection】和 【Microsoft.Extensions.Http】 /// 【System.Text.Json】 /// 其它层可以直接调用,不再需要注入AddHttpClient了,因为下面封装里已Add进去了 /// </summary> public class RequestHelp { /// <summary> /// Get请求 /// </summary> /// <param name="url">请求地址</param> /// <param name="headers">headers内容,可以不填</param> /// <returns></returns> public static string Get(string url, Dictionary<string, string> headers = null) { var serviceProvider = new ServiceCollection().AddHttpClient().BuildServiceProvider(); IHttpClientFactory clientFactory = serviceProvider.GetService<IHttpClientFactory>(); var request = new HttpRequestMessage(HttpMethod.Get, url); //添加Headers内容 foreach (var item in headers) { request.Headers.Add(item.Key, item.Value); } var client = clientFactory.CreateClient(); var response = client.SendAsync(request).Result; var myResult = response.Content.ReadAsStringAsync().Result; return myResult; } /// <summary> /// Post请求-表单形式 /// </summary> /// <param name="url">请求地址</param> /// <param name="content">请求内容,形如:"userName=admin&pwd=123456"</param> /// <param name="headers">headers内容,可以不填</param> /// <returns></returns> public static string Post(string url, string content, Dictionary<string, string> headers = null) { var serviceProvider = new ServiceCollection().AddHttpClient().BuildServiceProvider(); IHttpClientFactory clientFactory = serviceProvider.GetService<IHttpClientFactory>(); var request = new HttpRequestMessage(HttpMethod.Post, url) { //内容的处理 Content = new StringContent(content, Encoding.UTF8, "application/x-www-form-urlencoded") }; //添加Headers内容 foreach (var item in headers) { request.Headers.Add(item.Key, item.Value); } var client = clientFactory.CreateClient(); var response = client.SendAsync(request).Result; var myResult = response.Content.ReadAsStringAsync().Result; return myResult; } /// <summary> /// Post请求-Json形式 /// </summary> /// <param name="url">请求地址</param> /// <param name="content">请求内容, 形如: /// new {userName = "admin", pwd = "123456"} /// </param> /// <param name="headers">headers内容,可以不填</param> /// <returns></returns> public static string PostJson(string url, object content, Dictionary<string, string> headers = null) { var serviceProvider = new ServiceCollection().AddHttpClient().BuildServiceProvider(); IHttpClientFactory clientFactory = serviceProvider.GetService<IHttpClientFactory>(); var request = new HttpRequestMessage(HttpMethod.Post, url) { //内容的处理 Content = new StringContent(JsonSerializer.Serialize(content), Encoding.UTF8, "application/json") }; //添加Headers内容 foreach (var item in headers) { request.Headers.Add(item.Key, item.Value); } var client = clientFactory.CreateClient(); var response = client.SendAsync(request).Result; var myResult = response.Content.ReadAsStringAsync().Result; return myResult; } }
(4). SftpHelp
用于Ftp文件的上传、下载等。
/// <summary> /// SFTP相关的帮助类 /// 依赖程序集:【SSH.NET】 /// </summary> public class SftpHelp { #region 字段或属性 private SftpClient sftp; /// <summary> /// SFTP连接状态 /// </summary> public bool Connected { get { return sftp.IsConnected; } } #endregion #region 构造 /// <summary> /// 构造 /// </summary> /// <param name="host">host</param> /// <param name="user">用户名</param> /// <param name="pwd">密码</param> public SftpHelp(string host, string user, string pwd) { sftp = new SftpClient(host, user, pwd); } #endregion #region 连接SFTP /// <summary> /// 连接SFTP /// </summary> /// <returns>true成功</returns> public bool Connect() { if (!Connected) { sftp.Connect(); } return true; } #endregion #region 断开SFTP /// <summary> /// 断开SFTP /// </summary> public void Disconnect() { try { if (sftp != null && Connected) { sftp.Disconnect(); } } catch (Exception ex) { LogUtils.Error(ex); //throw new Exception(string.Format("断开SFTP失败,原因:{0}", ex.Message)); } } #endregion #region SFTP上传文件 /// <summary> /// SFTP上传文件 /// </summary> /// <param name="localPath">本地路径</param> /// <param name="remotePath">远程路径</param> public void Put(string localPath, string remotePath) { try { using (var file = File.OpenRead(localPath)) { Connect(); sftp.UploadFile(file, remotePath); Disconnect(); } } catch (Exception ex) { LogUtils.Error(ex); //throw new Exception(string.Format("SFTP文件上传失败,原因:{0}", ex.Message)); } } #endregion #region SFTP获取文件 /// <summary> /// SFTP获取文件 /// </summary> /// <param name="remotePath">远程路径</param> /// <param name="localPath">本地路径</param> /// <returns></returns> public bool Get(string remotePath, string localPath) { try { Connect(); var byt = sftp.ReadAllBytes(remotePath); Disconnect(); File.WriteAllBytes(localPath, byt); return true; } catch (Exception ex) { LogUtils.Error(ex); //throw new Exception(string.Format("SFTP文件获取失败,原因:{0}", ex.Message)); return false; } } #endregion #region 删除SFTP文件 /// <summary> /// 删除SFTP文件 /// </summary> /// <param name="remoteFile">远程路径</param> public bool Delete(string remoteFile) { var isDelete = false; try { Connect(); sftp.Delete(remoteFile); isDelete = true; Disconnect(); } catch (Exception ex) { LogUtils.Error(ex); //throw new Exception(string.Format("SFTP文件删除失败,原因:{0}", ex.Message)); } return isDelete; } #endregion #region 获取SFTP文件列表 /// <summary> /// 获取SFTP文件列表 /// </summary> /// <param name="remotePath">远程目录</param> /// <param name="fileSuffix">文件后缀</param> /// <returns></returns> public ArrayList GetFileList(string remotePath, string fileSuffix) { var objList = new ArrayList(); try { Connect(); var files = sftp.ListDirectory(remotePath); Disconnect(); foreach (var file in files) { string name = file.Name; if (name.Length > (fileSuffix.Length + 1) && fileSuffix == name.Substring(name.Length - fileSuffix.Length)) { objList.Add(name); } } } catch (Exception ex) { LogUtils.Error(ex); //throw new Exception(string.Format("SFTP文件列表获取失败,原因:{0}", ex.Message)); } return objList; } #endregion #region 移动SFTP文件 /// <summary> /// 移动SFTP文件 /// </summary> /// <param name="oldRemotePath">旧远程路径</param> /// <param name="newRemotePath">新远程路径</param> public void Move(string oldRemotePath, string newRemotePath) { try { Connect(); sftp.RenameFile(oldRemotePath, newRemotePath); Disconnect(); } catch (Exception ex) { LogUtils.Error(ex); //throw new Exception(string.Format("SFTP文件移动失败,原因:{0}", ex.Message)); } } #endregion }
3. Extensions扩展类文件夹
需要安装的程序集有:【】
PS:删掉了cache、coremvc、redis、session的封装,后续再新添加。
(1). SortExtension
Linq排序的扩展类,支持单字段、多字段,直接根据名称进行升序 or 降序。
/// <summary> /// 排序的扩展 /// </summary> public static class SortExtension { #region 01-根据string名称排序扩展(单字段) /// <summary> /// 根据string名称排序扩展(单字段) /// </summary> /// <typeparam name="T"></typeparam> /// <param name="source">排序数据源</param> /// <param name="sortName">排序名称</param> /// <param name="sortDirection">排序方式 asc或desc</param> /// <returns></returns> public static IQueryable<T> DataSorting<T>(this IQueryable<T> source, string sortName, string sortDirection) { string sortingDir = string.Empty; if (sortDirection.ToUpper().Trim() == "ASC") { sortingDir = "OrderBy"; } else if (sortDirection.ToUpper().Trim() == "DESC") { sortingDir = "OrderByDescending"; } ParameterExpression param = Expression.Parameter(typeof(T), sortName); PropertyInfo pi = typeof(T).GetProperty(sortName); Type[] types = new Type[2]; types[0] = typeof(T); types[1] = pi.PropertyType; Expression expr = Expression.Call(typeof(Queryable), sortingDir, types, source.Expression, Expression.Lambda(Expression.Property(param, sortName), param)); IQueryable<T> query = source.AsQueryable().Provider.CreateQuery<T>(expr); return query; } #endregion #region 02-根据多个string名称排序扩展(多字段) /// <summary> /// 根据多个string名称排序扩展(多字段) /// </summary> /// <typeparam name="T"></typeparam> /// <param name="data">数据源</param> /// <param name="orderParams">排序类</param> /// <returns></returns> public static IQueryable<T> DataManySorting<T>(this IQueryable<T> data, params FiledOrderParam[] orderParams) where T : class { var parameter = Expression.Parameter(typeof(T), "p"); if (orderParams != null && orderParams.Length > 0) { for (int i = 0; i < orderParams.Length; i++) { var property = typeof(T).GetProperty(orderParams[i].PropertyName); if (property != null) { var propertyAccess = Expression.MakeMemberAccess(parameter, property); var orderByExpr = Expression.Lambda(propertyAccess, parameter); string methodName = i > 0 ? orderParams[i].IsDesc ? "ThenByDescending" : "ThenBy" : orderParams[i].IsDesc ? "OrderByDescending" : "OrderBy"; var resultExp = Expression.Call(typeof(Queryable), methodName, new Type[] { typeof(T), property.PropertyType }, data.Expression, Expression.Quote(orderByExpr)); data = data.Provider.CreateQuery<T>(resultExp); } } } return data; } #endregion } /// <summary> /// 排序类 /// </summary> public class FiledOrderParam { //是否降序(默认是升序的) public bool IsDesc { get; set; } = false; //排序名称 public string PropertyName { get; set; } }
测试:
/// <summary>
/// 09-测试根据字段名称排序(单字段 和 多字段)
/// </summary>
/// <returns></returns>
[HttpPost]
public string Test9()
{
//1.单字段
var data1 = baseService.Entities<T_SysUser>().DataSorting("userAccount", "DESC").ToList();
// 生成的SQL
//SELECT[t].[id], [t].[addTime], [t].[delFlag], [t].[userAccount], [t].[userPhone], [t].[userPicture], [t].[userPwd], [t].[userRealName], [t].[userRemark], [t].[userSex]
//FROM[T_SysUser] AS[t]
//ORDER BY[t].[userAccount] DESC
//2.多字段
List<FiledOrderParam> param = new()
{
new FiledOrderParam() { IsDesc = true, PropertyName = "userSex" },
new FiledOrderParam() { IsDesc = false, PropertyName = "addTime" },
};
var data2 = baseService.Entities<T_SysUser>().DataManySorting(param.ToArray()).ToList();
// 生成的SQL
//SELECT[t].[id], [t].[addTime], [t].[delFlag], [t].[userAccount], [t].[userPhone], [t].[userPicture], [t].[userPwd], [t].[userRealName], [t].[userRemark], [t].[userSex]
//FROM[T_SysUser] AS[t]
//ORDER BY[t].[userSex] DESC, [t].[addTime]
return "ok";
}
(2). RandomExtensions
对.Net6中Random.Shared.NextDouble() 默认的随机数进行扩展,默认的只能生成0-1之间,扩展后的支持自定义最大值和最小值。
代码分享
/// <summary> /// 对 .Net6 新增的高效随机数进行扩展 /// </summary> public static class RandomExtensions { /// <summary> /// 随机数的生成 /// 默认的NextDouble 只能生成0-1之间 /// </summary> /// <param name="random">扩展类</param> /// <param name="minValue">最小值</param> /// <param name="maxValue">最大值 (maxValue 必须 > minValue)</param> /// <returns></returns> public static double NextDouble(this Random random, double minValue, double maxValue) { if (minValue >= maxValue) { throw new ArgumentOutOfRangeException(nameof(minValue), "maxValue必须大于minValue"); } //参考://https://stackoverflow.com/questions/65900931/c-sharp-random-number-between-double-minvalue-and-double-maxvalue double value = random.NextDouble(); //.Net6自带的,只能生成0-1之间 return value * maxValue + (1 - value) * minValue; } }
测试
public dynamic Test10()
{
//1. 系统自带的
var d1 = Random.Shared.Next(1, 10000);
var d2 = Random.Shared.NextDouble(); //默认的只能生成0-1之间
//2.自己扩展的
var d3 = Random.Shared.NextDouble(1, 10);
return new
{
d1,
d2,
d3
};
}
4. Log日志文件夹
需要安装的程序集有:【Serilog】【Serilog.Sinks.Async】【Serilog.Sinks.File】
(1). SeriLog/LogUtils
Serilog日志,前面已经详细介绍,此处不再赘述。
/// <summary> /// SeriLog帮助类 /// </summary> public class LogUtils { static readonly string log1Name = "ApiLog"; static readonly string log2Name = "ErrorApiLog"; /// <summary> /// 初始化日志 /// </summary> public static void InitLog() { //static string LogFilePath(string FileName) => $@"{AppContext.BaseDirectory}Log\{FileName}\log.log"; //bin目录下 static string LogFilePath(string FileName) => $@"MyLogs\{FileName}\log_.log"; string SerilogOutputTemplate = "{NewLine}Date:{Timestamp:yyyy-MM-dd HH:mm:ss.fff}{NewLine}LogLevel:{Level}{NewLine}Message:{Message}{NewLine}{Exception}" + new string('-', 100); Serilog.Log.Logger = new LoggerConfiguration() .Enrich.FromLogContext() .MinimumLevel.Debug() // 所有Sink的最小记录级别 .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(Matching.WithProperty<string>("position", p => p == log1Name)).WriteTo.Async(a => a.File(LogFilePath(log1Name), rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate))) .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(Matching.WithProperty<string>("position", p => p == log2Name)).WriteTo.Async(a => a.File(LogFilePath(log2Name), rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate))) .CreateLogger(); } /*****************************下面是不同日志级别*********************************************/ // FATAL(致命错误) > ERROR(一般错误) > Warning(警告) > Information(一般信息) > DEBUG(调试信息)>Verbose(详细模式,即全部) /// <summary> /// 普通日志 /// </summary> /// <param name="msg">日志内容</param> /// <param name="fileName">文件夹名称</param> public static void Info(string msg, string fileName = "") { if (fileName == "" || fileName == log1Name) { Serilog.Log.Information($"{{position}}:{msg}", log1Name); } } /// <summary> /// 异常日志 /// </summary> /// <param name="ex">Exception</param> /// <param name="fileName">文件夹名称</param> public static void Error(Exception ex, string fileName = "") { if (fileName == "" || fileName == log2Name) { Serilog.Log.Error(ex, "{position}:" + ex.Message, log2Name); } } }
5. Safe安全相关文件夹
需要安装的程序集有:【JWT】【NETCore.Encrypt】
(1). JwtHelp
现在保留的是 7.x的写法,暂时没有更新9.x的写法。
/// <summary> /// JWT的加密和解密 /// 注:加密和加密用的是用一个密钥 /// 依赖程序集:【JWT】 /// 文档参考:https://github.com/jwt-dotnet/jwt /// </summary> public class JWTHelp { /// <summary> /// JWT加密算法 (7.x版本写法) /// </summary> /// <param name="payload">负荷部分,存储使用的信息</param> /// <param name="secret">密钥</param> /// <param name="extraHeaders">存放表头额外的信息,不需要的话可以不传</param> /// <returns></returns> public static string JWTJiaM(IDictionary<string, object> payload, string secret, IDictionary<string, object> extraHeaders = null) { IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); IJsonSerializer serializer = new JsonNetSerializer(); IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder(); IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder); var token = encoder.Encode(payload, secret); return token; } /// <summary> /// JWT解密算法(7.x版本写法) /// </summary> /// <param name="token">需要解密的token串</param> /// <param name="secret">密钥</param> /// <returns></returns> public static string JWTJieM(string token, string secret) { try { IJsonSerializer serializer = new JsonNetSerializer(); IDateTimeProvider provider = new UtcDateTimeProvider(); IJwtValidator validator = new JwtValidator(serializer, provider); IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder(); IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); // symmetric IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm); var json = decoder.Decode(token, secret, true); //校验通过,返回解密后的字符串 return json; } catch (TokenExpiredException) { //表示过期 return "expired"; } catch (SignatureVerificationException) { //表示验证不通过 return "invalid"; } catch (Exception) { return "error"; } } }
(2). SecurityHelp
各种加密、解密算法。比如:Base64、MD5、SHA系列、HMAC系列、AES、DES、RSA 等等。
/// <summary> /// 各种加密解密算法 /// 依赖程序集【NETCore.Encrypt】 /// </summary> public class SecurityHelp { /// <summary> /// Base64编码 /// </summary> /// <param name="srcString">需要编码的字符串</param> /// <returns></returns> public static string Base64Encrypt(string srcString) { return EncryptProvider.Base64Encrypt(srcString); //default encoding is UTF-8 } /// <summary> /// Base64编码 /// </summary> /// <param name="encString">需要解码的字符串</param> /// <returns></returns> public static string Base64Decrypt(string encString) { return EncryptProvider.Base64Decrypt(encString); //default encoding is UTF-8 } /// <summary> /// MD5加密 /// </summary> /// <param name="srcString">需要加密的字符串</param> /// <param name="num">位数,默认为32位,也可以是16位</param> /// <returns></returns> public static string Md5(string srcString, int num = 32) { if (num == 32) { return EncryptProvider.Md5(srcString); //默认32位 } else { return EncryptProvider.Md5(srcString, MD5Length.L16); //16位 } } /// <summary> /// SHA系列算法 /// </summary> /// <param name="srcString">需要加密的字符串</param> /// <param name="kind">类型</param> /// <returns></returns> public static string SHA(string srcString, string kind = "Sha512") { if (kind.Equals("Sha1")) { return EncryptProvider.Sha1(srcString); } else if (kind.Equals("Sha256")) { return EncryptProvider.Sha256(srcString); } else if (kind.Equals("Sha384")) { return EncryptProvider.Sha384(srcString); } else { return EncryptProvider.Sha512(srcString); } } /// <summary> /// HMAC系列算法 /// </summary> /// <param name="srcString">需要加密的字符串</param> /// <param name="secretKey">密钥</param> /// <param name="kind">类型</param> /// <returns></returns> public static string HMAC(string srcString, string secretKey, string kind = "HMACSHA512") { if (kind.Equals("HMACMD5")) { return EncryptProvider.HMACMD5(srcString, secretKey); } else if (kind.Equals("HMACSHA1")) { return EncryptProvider.HMACSHA1(srcString, secretKey); } else if (kind.Equals("HMACSHA256")) { return EncryptProvider.HMACSHA256(srcString, secretKey); } else if (kind.Equals("HMACSHA384")) { return EncryptProvider.HMACSHA384(srcString, secretKey); } else { return EncryptProvider.HMACSHA512(srcString, secretKey); } } /// <summary> /// 生成AES算法所需的Key和iv /// (当然这里可以自己指定并保存好) /// </summary> /// <param name="key">加密所需的key</param> /// <param name="iv">加密所需的矢量</param> public static void CreateAesKey(ref string key, ref string iv) { //利用算法生成key和iv(32位和16位),当然这里可以自己指定并保存好 var aesKey = EncryptProvider.CreateAesKey(); key = aesKey.Key; iv = aesKey.IV; } /// <summary> /// AES加密算法 /// </summary> /// <param name="srcString">需要加密的字符串</param> /// <param name="secretKey">密钥</param> /// <param name="iv">矢量(可以不填)</param> /// <returns></returns> public static string AESEncrypt(string srcString, string secretKey, string iv = "") { if (string.IsNullOrEmpty(iv)) { return EncryptProvider.AESEncrypt(srcString, secretKey); //表示不需要iv矢量的AES加密算法 } else { return EncryptProvider.AESEncrypt(srcString, secretKey, iv); //表示需要iv矢量的AES加密算法 } } /// <summary> /// AES解密算法 /// </summary> /// <param name="encString">需要解密的字符串</param> /// <param name="secretKey">密钥</param> /// <param name="iv">矢量(可以不填)</param> /// <returns></returns> public static string AESDecrypt(string encString, string secretKey, string iv = "") { if (string.IsNullOrEmpty(iv)) { return EncryptProvider.AESDecrypt(encString, secretKey); //表示不需要iv矢量的AES解密算法 } else { return EncryptProvider.AESDecrypt(encString, secretKey, iv); //表示需要iv矢量的AES解密算法 } } /// <summary> /// DES加密算法 /// </summary> /// <param name="srcString">需要加密的字符串</param> /// <param name="secretKey">密钥(这里的密钥需要是24位)</param> /// <returns></returns> public static string DESEncrypt(string srcString, string secretKey) { return EncryptProvider.DESEncrypt(srcString, secretKey); } /// <summary> /// DES解密算法 /// </summary> /// <param name="encString">需要解密的字符串</param> /// <param name="secretKey">密钥</param> /// <returns></returns> public static string DESDecrypt(string encString, string secretKey) { return EncryptProvider.DESDecrypt(encString, secretKey); } /// <summary> /// 生成RSA算法所需的Key和iv /// (当然这里可以自己指定并保存好) /// </summary> /// <param name="PublicKey">公钥</param> /// <param name="PrivateKey">私钥</param> public static void CreateRsaKey(ref string PublicKey, ref string PrivateKey) { //利用算法生成公钥和私钥,然后保存;当然这里可以自己指定并保存好 var rsaKey = EncryptProvider.CreateRsaKey(); //default is 2048 // var rsaKey = EncryptProvider.CreateRsaKey(RsaSize.R3072); PublicKey = rsaKey.PublicKey; PrivateKey = rsaKey.PrivateKey; } /// <summary> /// RSA加密算法 /// </summary> /// <param name="srcString">需要加密的字符串</param> /// <param name="publicKey">公钥 加密</param> /// <returns></returns> public static string RSAEncrypt(string srcString, string publicKey) { return EncryptProvider.RSAEncrypt(publicKey, srcString); //公钥加密 } /// <summary> /// RSA解密算法 /// </summary> /// <param name="encString">需要解密的字符串</param> /// <param name="privateKey">私钥 解密</param> /// <returns></returns> public static string RSADecrypt(string encString, string privateKey) { return EncryptProvider.RSADecrypt(privateKey, encString); //私钥解密 } } public enum RsaSize { R2048 = 2048, R3072 = 3072, R4096 = 4096 }
6. Transform安全相关文件夹
需要安装的程序集有:【System.Text.Json】 或 【Newtonsoft.Json】
(1). JsonHelp
序列化和反序列化,基于:【System.Text.Json】 或 【Newtonsoft.Json】,可以自定义选择。
PS:经测试,【System.Text.Json】在反序列化的时候还是存在问题(可能需要配置JsonSerializerOptions参数),比如jwt的字符串无法反序列化成功,所以还是需要重新引进NewtonSoft.Json来使用。
/// <summary> /// Json的序列化和反序列化 /// 基于:【System.Text.Json】 或 【Newtonsoft.Json】 /// </summary> public class JsonHelp { #region 01-将JSON对象转换成JSON字符串 /// <summary> /// 01-将JSON转换成JSON字符串 /// </summary> /// <typeparam name="T">泛型类</typeparam> /// <param name="obj">JSON对象</param> /// <param name="isUseNewtonsoft">是否使用Newtonsoft解析,默认使用</param> /// <returns></returns> public static string ToJsonString<T>(T obj, bool isUseNewtonsoft = true) { if (isUseNewtonsoft) { //【Newtonsoft.Json】 return JsonConvert.SerializeObject(obj); } else { //【System.Text.Json】 return System.Text.Json.JsonSerializer.Serialize(obj); } } #endregion #region 02-将字符串转换成JSON对象 /// <summary> /// 02-将字符串转换成JSON对象 /// </summary> /// <typeparam name="T">泛型类</typeparam> /// <param name="content">字符串</param> /// <param name="isUseNewtonsoft">是否使用Newtonsoft解析,默认使用</param> /// <returns></returns> public static T ToObject<T>(string content, bool isUseNewtonsoft = true) { if (isUseNewtonsoft) { //【Newtonsoft.Json】 return JsonConvert.DeserializeObject<T>(content); } else { //【System.Text.Json】 return System.Text.Json.JsonSerializer.Deserialize<T>(content); } } #endregion }
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。