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中

项目完成后,如下图所示

ASP.NET Core搭建多层网站架构【2-公共基础库】的更多相关文章

  1. ASP.NET Core搭建多层网站架构【0-前言】

    2020/01/26, ASP.NET Core 3.1, VS2019 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构 目录 0-前言 1-项目结构分层建立 2-公共基 ...

  2. ASP.NET Core搭建多层网站架构【1-项目结构分层建立】

    2020/01/26, ASP.NET Core 3.1, VS2019 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构[1-项目结构分层建立] 文章目录 此分支项目代码 ...

  3. ASP.NET Core搭建多层网站架构【3-xUnit单元测试之简单方法测试】

    2020/01/28, ASP.NET Core 3.1, VS2019, xUnit 2.4.0 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构[3-xUnit单元测试 ...

  4. ASP.NET Core搭建多层网站架构【4-工作单元和仓储设计】

    2020/01/28, ASP.NET Core 3.1, VS2019, Microsoft.EntityFrameworkCore.Relational 3.1.1 摘要:基于ASP.NET Co ...

  5. ASP.NET Core搭建多层网站架构【5-网站数据库实体设计及映射配置】

    2020/01/29, ASP.NET Core 3.1, VS2019, EntityFrameworkCore 3.1.1, Microsoft.Extensions.Logging.Consol ...

  6. ASP.NET Core搭建多层网站架构【6-注册跨域、网站核心配置】

    2020/01/29, ASP.NET Core 3.1, VS2019, NLog.Web.AspNetCore 4.9.0 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站 ...

  7. ASP.NET Core搭建多层网站架构【7-使用NLog日志记录器】

    2020/01/29, ASP.NET Core 3.1, VS2019, NLog.Web.AspNetCore 4.9.0 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站 ...

  8. ASP.NET Core搭建多层网站架构【8.1-使用ViewModel注解验证】

    2020/01/29, ASP.NET Core 3.1, VS2019 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构[8.1-使用ViewModel注解验证] 使用V ...

  9. ASP.NET Core搭建多层网站架构【8.2-使用AutoMapper映射实体对象】

    2020/01/29, ASP.NET Core 3.1, VS2019, AutoMapper.Extensions.Microsoft.DependencyInjection 7.0.0 摘要:基 ...

随机推荐

  1. 编写自定义的django-admin命令

    先写标题内容后续补充上 官方文档如下 :http://python.usyiyi.cn/documents/django_182/howto/custom-management-commands.ht ...

  2. C语言 strlen

    C语言 strlen #include <string.h> size_t strlen(const char *s); 功能:计算指定指定字符串s的长度,不包含字符串结束符‘\0’ 参数 ...

  3. 959F - Mahmoud and Ehab and yet another xor task xor+dp(递推形)+离线

    959F - Mahmoud and Ehab and yet another xor task xor+dp+离线 题意 给出 n个值和q个询问,询问l,x,表示前l个数字子序列的异或和为x的子序列 ...

  4. docker dial unix /var/run/docker.sock: connect: permission denied

    Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker. ...

  5. Hadoop2.0之YARN组件

    官方文档:https://hadoop.apache.org/docs/stable/,目前官方已经是3.x,但yarn机制没有太大变化 一.简介 在Hadoop1.0中,没有yarn,所有的任务调度 ...

  6. 如何对字符串进行码表替换转码--加密encode

      # encoding:utf-8 import base64 from string import maketrans OLD_STR = "ABCDEFGHIJKLMNOPQRSTUV ...

  7. swiper移动端全屏播放动态获取数据

    html:  <link rel="stylesheet" href="css/swiper.min.css">   <div class=& ...

  8. Codeforces Round #610 (Div. 2)E(模拟,DFS)

    先找到一条多边形的边,然后循环一圈输出多边形上的点.把每个三角形看作一个结点,以两个三角形之间公用边为边建立一张图,DFS输出叶子结点,则得到先切后切的顺序. #define HAVE_STRUCT_ ...

  9. 【PAT甲级】1103 Integer Factorization (30 分)

    题意: 输入三个正整数N,K,P(N<=400,K<=N,2<=P<=7),降序输出由K个正整数的P次方和为N的等式,否则输出"Impossible". / ...

  10. Python 树莓派 引脚

    #!/usr/bin/python3 import RPi.GPIO as GPIO import time GPIO.setmode(GPIO.BCM) GPIO.setup(18, GPIO.OU ...