第二节:必备中间件集成(缓存策略、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);
}
View Code

 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



}
View Code

 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);
}
View Code

 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


}
View Code

测试

 /// <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
        });
    }
View Code

 

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);
    }
}
View Code

全局注册:

//8.过滤器注册
builder.Services.Configure<MvcOptions>(option =>
{
    option.Filters.Add<CheckJWT>();
});

 

 

 

 

 

 

 

 

 

三. YpfCore.Utils层详解

1. Attributes特性文件夹 

   保留: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];
    }

}
View Code

(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

}
View Code

(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;
    }

}
View Code

(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
}
View Code

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; }
}
View Code

测试:

    /// <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;
    }

}
View Code

测试

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);
        }
    }
}
View Code

 

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";
        }
    }



}
View Code

(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
}
View Code

 

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
}
View Code

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 
posted @ 2022-10-19 08:02  Yaopengfei  阅读(295)  评论(3编辑  收藏  举报