• ASP.NET Core搭建多层网站架构【2-公共基础库】


    2020/01/28, ASP.NET Core 3.1, VS2019,Newtonsoft.Json 12.0.3, Microsoft.AspNetCore.Cryptography.KeyDerivation 3.1.1

    摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构【2-公共基础库】
    Snowflake雪花算法ID、Enum枚举方法扩展、Lambda方法扩展、Json方法封装

    文章目录

    此分支项目代码

    本章节介绍了MS.Common类库中一些常用公共方法,可以自行添加自己积累的一些库

    添加包引用

    MS.Common类库中添加包引用:

    <ItemGroup>
      <PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="3.1.1" />
      <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
    </ItemGroup>
    

    其中Microsoft.AspNetCore.Cryptography.KeyDerivation是为了支持PBKDF2加密方式,这个后文会用到

    枚举扩展方法

    MS.Common类库中新建Extensions文件夹,在其中添加EnumExtension.cs类:

    using System;
    using System.ComponentModel;
    using System.Reflection;
    
    namespace MS.Common.Extensions
    {
        public static class EnumExtension
        {
            /// <summary>
            /// 根据名称拿到枚举
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="itemName"></param>
            /// <returns></returns>
            public static T GetEnum<T>(this string itemName)
            {
                return (T)Enum.Parse(typeof(T), itemName);
            }
            /// <summary>
            /// 根据枚举值拿到枚举
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="itemValue"></param>
            /// <returns></returns>
            public static T GetEnum<T>(this int itemValue)
            {
                return (T)Enum.Parse(typeof(T), Enum.GetName(typeof(T), itemValue));
            }
            /// <summary>
            /// 根据枚举值拿到枚举名称
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="itemValue"></param>
            /// <returns></returns>
            public static string GetEnumName<T>(this int itemValue)
            {
                return Enum.GetName(typeof(T), itemValue);
            }
            /// <summary>
            /// 根据名称拿到枚举值
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="itemName"></param>
            /// <returns></returns>
            public static int GetEnumValue<T>(this string itemName)
            {
                return itemName.GetEnum<T>().GetHashCode();
            }
            /// <summary>
            /// 枚举获取描述
            /// </summary>
            /// <param name="item"></param>
            /// <returns></returns>
            public static string GetDescription(this Enum item)
            {
                Type type = item.GetType();
                MemberInfo[] memInfo = type.GetMember(item.ToString());
                if (memInfo != null && memInfo.Length > 0)
                {
                    object[] attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
                    if (attrs != null && attrs.Length > 0)
                        return ((DescriptionAttribute)attrs[0]).Description;
                }
                return item.ToString();//如果不存在描述,则返回枚举名称
            } 
        }
    }
    
    

    Lambda表达式扩展方法

    在Extensions中继续添加LambdaExtension.cs类:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    
    namespace MS.Common.Extensions
    {
        //add by yzh 2019/04/26 -用于lambda表达式拼接
        public class ParameterRebinder : ExpressionVisitor
        {
            private readonly Dictionary<ParameterExpression, ParameterExpression> map;
            public ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
            {
                this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
            }
            public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
            {
                return new ParameterRebinder(map).Visit(exp);
            }
            protected override Expression VisitParameter(ParameterExpression p)
            {
                ParameterExpression replacement;
                if (map.TryGetValue(p, out replacement))
                {
                    p = replacement;
                }
                return base.VisitParameter(p);
            }
        }
        public static class LambdaExtension
        {
            public static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
            {
                var map = first.Parameters.Select((f, i) => new { f, s = second.Parameters[i] }).ToDictionary(p => p.s, p => p.f);
                var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);
                return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
            }
            public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
            {
                return first.Compose(second, Expression.And);
            }
            public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
            {
                return first.Compose(second, Expression.Or);
            }
        }
    }
    
    

    原生的Lambda表达式不支持动态拼接表达式条件,有了这个扩展方法,就弥补了这个缺点。

    Json扩展方法

    在Extensions中添加JsonExtension.cs类:

    using Newtonsoft.Json;
    using Newtonsoft.Json.Converters;
    
    namespace MS.Common.Extensions
    {
        public static class JsonExtension
        {
            public static JsonSerializerSettings jsonSetting = new JsonSerializerSettings
            {
                ReferenceLoopHandling = ReferenceLoopHandling.Ignore
            };
            /// <summary>
            /// 序列化对象,默认禁止循环引用
            /// </summary>
            /// <param name="data"></param>
            /// <returns></returns>
            public static string ToJsonString(this object data)
            {
                return JsonConvert.SerializeObject(data, jsonSetting);
            }
            /// <summary>
            /// 序列化对象
            /// </summary>
            /// <param name="data"></param>
            /// <param name="timeConverter"></param>
            /// <returns></returns>
            public static string ToJsonString(this object data, IsoDateTimeConverter timeConverter)
            {
                return JsonConvert.SerializeObject(data, timeConverter);
            }
    
            /// <summary>
            /// 反序列化字符串
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="data"></param>
            /// <returns></returns>
            public static T GetDeserializeObject<T>(this string data)
            {
                if (string.IsNullOrWhiteSpace(data)) return default;
                return JsonConvert.DeserializeObject<T>(data, jsonSetting);
            }
    
            /// <summary>
            /// 使用序列化和反序列化获得一次深拷贝
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="data"></param>
            /// <returns></returns>
            public static T GetMemberwiseCopy<T>(this T data)
            {
                return data.ToJsonString().GetDeserializeObject<T>();
            }
        }
    }
    
    

    每个方法都写好了注释,默认禁止循环引用

    PBKDF2加密方法

    在类库中添加Security文件夹,向其中添加Crypto.cs类:

    using Microsoft.AspNetCore.Cryptography.KeyDerivation;
    using System;
    using System.Runtime.CompilerServices;
    using System.Security.Cryptography;
    
    namespace MS.Common.Security
    {
        /// <summary>
        /// Provides helper methods for hashing/salting and verifying passwords.
        /// </summary>
        public static class Crypto
        {
            /* =======================
             * HASHED PASSWORD FORMATS
             * =======================
             *
             * Version 3:
             * PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 10000 iterations.
             * Format: { 0x01, prf (UInt32), iter count (UInt32), salt length (UInt32), salt, subkey }
             * (All UInt32s are stored big-endian.)
             */
    
            private const int PBKDF2IterCount = 10000;
            private const int PBKDF2SubkeyLength = 256 / 8; // 256 bits
            private const int SaltSize = 128 / 8; // 128 bits
    
    
            /// <summary>
            /// Returns a hashed representation of the specified <paramref name="password"/>.
            /// </summary>
            /// <param name="password">The password to generate a hash value for.</param>
            /// <returns>The hash value for <paramref name="password" /> as a base-64-encoded string.</returns>
            /// <exception cref="System.ArgumentNullException"><paramref name="password" /> is null.</exception>
            public static string HashPassword(string password)
            {
                if (password == null)
                {
                    throw new ArgumentNullException(nameof(password));
                }
    
                return HashPasswordInternal(password);
            }
    
            /// <summary>
            /// Determines whether the specified RFC 2898 hash and password are a cryptographic match.
            /// </summary>
            /// <param name="hashedPassword">The previously-computed RFC 2898 hash value as a base-64-encoded string.</param>
            /// <param name="password">The plaintext password to cryptographically compare with hashedPassword.</param>
            /// <returns>true if the hash value is a cryptographic match for the password; otherwise, false.</returns>
            /// <remarks>
            /// <paramref name="hashedPassword" /> must be of the format of HashPassword (salt + Hash(salt+input).
            /// </remarks>
            /// <exception cref="System.ArgumentNullException">
            /// <paramref name="hashedPassword" /> or <paramref name="password" /> is null.
            /// </exception>
            public static bool VerifyHashedPassword(string hashedPassword, string password)
            {
                if (hashedPassword == null)
                {
                    throw new ArgumentNullException(nameof(hashedPassword));
                }
                if (password == null)
                {
                    throw new ArgumentNullException(nameof(password));
                }
    
                return VerifyHashedPasswordInternal(hashedPassword, password);
            }
    
            private static readonly RandomNumberGenerator _rng = RandomNumberGenerator.Create();
    
            private static string HashPasswordInternal(string password)
            {
                var bytes = HashPasswordInternal(password, KeyDerivationPrf.HMACSHA256, PBKDF2IterCount, SaltSize, PBKDF2SubkeyLength);
                return Convert.ToBase64String(bytes);
            }
    
            private static byte[] HashPasswordInternal(
                string password,
                KeyDerivationPrf prf,
                int iterCount,
                int saltSize,
                int numBytesRequested)
            {
                // Produce a version 3 (see comment above) text hash.
                var salt = new byte[saltSize];
                _rng.GetBytes(salt);
                var subkey = KeyDerivation.Pbkdf2(password, salt, prf, iterCount, numBytesRequested);
    
                var outputBytes = new byte[13 + salt.Length + subkey.Length];
    
                // Write format marker.
                outputBytes[0] = 0x01;
    
                // Write hashing algorithm version.
                WriteNetworkByteOrder(outputBytes, 1, (uint)prf);
    
                // Write iteration count of the algorithm.
                WriteNetworkByteOrder(outputBytes, 5, (uint)iterCount);
    
                // Write size of the salt.
                WriteNetworkByteOrder(outputBytes, 9, (uint)saltSize);
    
                // Write the salt.
                Buffer.BlockCopy(salt, 0, outputBytes, 13, salt.Length);
    
                // Write the subkey.
                Buffer.BlockCopy(subkey, 0, outputBytes, 13 + saltSize, subkey.Length);
                return outputBytes;
            }
    
            private static bool VerifyHashedPasswordInternal(string hashedPassword, string password)
            {
                var decodedHashedPassword = Convert.FromBase64String(hashedPassword);
    
                if (decodedHashedPassword.Length == 0)
                {
                    return false;
                }
    
                try
                {
                    // Verify hashing format.
                    if (decodedHashedPassword[0] != 0x01)
                    {
                        // Unknown format header.
                        return false;
                    }
    
                    // Read hashing algorithm version.
                    var prf = (KeyDerivationPrf)ReadNetworkByteOrder(decodedHashedPassword, 1);
    
                    // Read iteration count of the algorithm.
                    var iterCount = (int)ReadNetworkByteOrder(decodedHashedPassword, 5);
    
                    // Read size of the salt.
                    var saltLength = (int)ReadNetworkByteOrder(decodedHashedPassword, 9);
    
                    // Verify the salt size: >= 128 bits.
                    if (saltLength < 128 / 8)
                    {
                        return false;
                    }
    
                    // Read the salt.
                    var salt = new byte[saltLength];
                    Buffer.BlockCopy(decodedHashedPassword, 13, salt, 0, salt.Length);
    
                    // Verify the subkey length >= 128 bits.
                    var subkeyLength = decodedHashedPassword.Length - 13 - salt.Length;
                    if (subkeyLength < 128 / 8)
                    {
                        return false;
                    }
    
                    // Read the subkey.
                    var expectedSubkey = new byte[subkeyLength];
                    Buffer.BlockCopy(decodedHashedPassword, 13 + salt.Length, expectedSubkey, 0, expectedSubkey.Length);
    
                    // Hash the given password and verify it against the expected subkey.
                    var actualSubkey = KeyDerivation.Pbkdf2(password, salt, prf, iterCount, subkeyLength);
                    return ByteArraysEqual(actualSubkey, expectedSubkey);
                }
                catch
                {
                    // This should never occur except in the case of a malformed payload, where
                    // we might go off the end of the array. Regardless, a malformed payload
                    // implies verification failed.
                    return false;
                }
            }
    
            private static uint ReadNetworkByteOrder(byte[] buffer, int offset)
            {
                return ((uint)(buffer[offset + 0]) << 24)
                    | ((uint)(buffer[offset + 1]) << 16)
                    | ((uint)(buffer[offset + 2]) << 8)
                    | ((uint)(buffer[offset + 3]));
            }
    
            private static void WriteNetworkByteOrder(byte[] buffer, int offset, uint value)
            {
                buffer[offset + 0] = (byte)(value >> 24);
                buffer[offset + 1] = (byte)(value >> 16);
                buffer[offset + 2] = (byte)(value >> 8);
                buffer[offset + 3] = (byte)(value >> 0);
            }
    
            // Compares two byte arrays for equality.
            // The method is specifically written so that the loop is not optimized.
            [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
            private static bool ByteArraysEqual(byte[] a, byte[] b)
            {
                if (ReferenceEquals(a, b))
                {
                    return true;
                }
    
                if (a == null || b == null || a.Length != b.Length)
                {
                    return false;
                }
    
                var areSame = true;
                for (var i = 0; i < a.Length; i++)
                {
                    areSame &= (a[i] == b[i]);
                }
                return areSame;
            }
        }
    }
    
    

    简单说明,网站用户密码加密就使用该方法,这段代码是从开源nuget包CryptoHelper中扒下来的。

    雪花算法实现

    在类库中添加IDCode文件夹,在IDCode文件夹中继续添加Snowflake文件夹,该文件夹下新建三个类:DisposableAction.cs、IdWorker.cs、TimeExtensions.cs
    DisposableAction.cs:

    using System;
    
    namespace MS.Common.IDCode
    {
        public class DisposableAction : IDisposable
        {
            readonly Action _action;
    
            public DisposableAction(Action action)
            {
                if (action == null)
                    throw new ArgumentNullException("action");
                _action = action;
            }
    
            public void Dispose()
            {
                _action();
            }
        }
    }
    

    IdWorker.cs:

    /** Copyright 2010-2012 Twitter, Inc.*/
    /**
     * An object that generates IDs.
     * This is broken into a separate class in case
     * we ever want to support multiple worker threads
     * per process
     */
    using System;
    
    namespace MS.Common.IDCode
    {
        public class IdWorker
        {
            //基准时间
            public const long Twepoch = 1288834974657L;
            //机器标识位数
            const int WorkerIdBits = 5;
            //数据标志位数
            const int DatacenterIdBits = 5;
            //序列号识位数
            const int SequenceBits = 12;
            //机器ID最大值
            const long MaxWorkerId = -1L ^ (-1L << WorkerIdBits);
            //数据标志ID最大值
            const long MaxDatacenterId = -1L ^ (-1L << DatacenterIdBits);
            //序列号ID最大值
            private const long SequenceMask = -1L ^ (-1L << SequenceBits);
            //机器ID偏左移12位
            private const int WorkerIdShift = SequenceBits;
            //数据ID偏左移17位
            private const int DatacenterIdShift = SequenceBits + WorkerIdBits;
            //时间毫秒左移22位
            public const int TimestampLeftShift = SequenceBits + WorkerIdBits + DatacenterIdBits;
    
            private long _sequence = 0L;
            private long _lastTimestamp = -1L;
    
            public long WorkerId { get; protected set; }
            public long DatacenterId { get; protected set; }
            public long Sequence
            {
                get { return _sequence; }
                internal set { _sequence = value; }
            }
    
            public IdWorker(long workerId, long datacenterId, long sequence = 0L)
            {
                // 如果超出范围就抛出异常
                if (workerId > MaxWorkerId || workerId < 0)
                {
                    throw new ArgumentException(string.Format("worker Id 必须大于0,且不能大于MaxWorkerId: {0}", MaxWorkerId));
                }
    
                if (datacenterId > MaxDatacenterId || datacenterId < 0)
                {
                    throw new ArgumentException(string.Format("region Id 必须大于0,且不能大于MaxWorkerId: {0}", MaxDatacenterId));
                }
    
                //先检验再赋值
                WorkerId = workerId;
                DatacenterId = datacenterId;
                _sequence = sequence;
            }
    
            readonly object _lock = new Object();
            public virtual long NextId()
            {
                lock (_lock)
                {
                    var timestamp = TimeGen();
                    if (timestamp < _lastTimestamp)
                    {
                        throw new Exception(string.Format("时间戳必须大于上一次生成ID的时间戳.  拒绝为{0}毫秒生成id", _lastTimestamp - timestamp));
                    }
    
                    //如果上次生成时间和当前时间相同,在同一毫秒内
                    if (_lastTimestamp == timestamp)
                    {
                        //sequence自增,和sequenceMask相与一下,去掉高位
                        _sequence = (_sequence + 1) & SequenceMask;
                        //判断是否溢出,也就是每毫秒内超过1024,当为1024时,与sequenceMask相与,sequence就等于0
                        if (_sequence == 0)
                        {
                            //等待到下一毫秒
                            timestamp = TilNextMillis(_lastTimestamp);
                        }
                    }
                    else
                    {
                        //如果和上次生成时间不同,重置sequence,就是下一毫秒开始,sequence计数重新从0开始累加,
                        //为了保证尾数随机性更大一些,最后一位可以设置一个随机数
                        _sequence = 0;//new Random().Next(10);
                    }
    
                    _lastTimestamp = timestamp;
                    return ((timestamp - Twepoch) << TimestampLeftShift) | (DatacenterId << DatacenterIdShift) | (WorkerId << WorkerIdShift) | _sequence;
                }
            }
    
            // 防止产生的时间比之前的时间还要小(由于NTP回拨等问题),保持增量的趋势.
            protected virtual long TilNextMillis(long lastTimestamp)
            {
                var timestamp = TimeGen();
                while (timestamp <= lastTimestamp)
                {
                    timestamp = TimeGen();
                }
                return timestamp;
            }
    
            // 获取当前的时间戳
            protected virtual long TimeGen()
            {
                return TimeExtensions.CurrentTimeMillis();
            }
        }
    }
    

    TimeExtensions.cs:

    using System;
    
    namespace MS.Common.IDCode
    {
        public static class TimeExtensions
        {
            public static Func<long> currentTimeFunc = InternalCurrentTimeMillis;
    
            public static long CurrentTimeMillis()
            {
                return currentTimeFunc();
            }
    
            public static IDisposable StubCurrentTime(Func<long> func)
            {
                currentTimeFunc = func;
                return new DisposableAction(() =>
                {
                    currentTimeFunc = InternalCurrentTimeMillis;
                });
            }
    
            public static IDisposable StubCurrentTime(long millis)
            {
                currentTimeFunc = () => millis;
                return new DisposableAction(() =>
                {
                    currentTimeFunc = InternalCurrentTimeMillis;
                });
            }
    
            private static readonly DateTime Jan1st1970 = new DateTime
               (1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
    
            private static long InternalCurrentTimeMillis()
            {
                return (long)(DateTime.UtcNow - Jan1st1970).TotalMilliseconds;
            }
        }
    }
    
    

    说明:这部分代码是从snowflake-net中扒来的,使用方法readme里也有,注意应尽量保证全局单例的情况下使用该方法生成ID

    总结/说明

    • 主要添加了一些常用扩展方法(我自己常用的)
    • 实际上传至github项目中还有封装的guid、随机数方法,没有在文中贴出来,有兴趣可以去项目中查看,在IDCode-Guid、IDCode-Random中

    项目完成后,如下图所示

  • 相关阅读:
    明白python文件如何组织,理解建立源文件
    Python特殊语法:filter、map、reduce、lambda [转]
    html标签属性大全
    使用Webdriver执行JS小结
    Sublime Text ——3200破解补丁
    JavaScript——JS屏蔽F12和右键
    Typecho——数据库无法连接问题
    vscode——配置git的path
    windows电脑连接蓝牙耳机的正确步骤
    eolinker——添加项目成员
  • 原文地址:https://www.cnblogs.com/kasnti/p/12237686.html
Copyright © 2020-2023  润新知