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. 算法导论2-4 O(nlgn)时间复杂度求逆序对

    def mergesort(nums,le,ri): if le>ri-2: return 0 mi=le+(ri-le)//2 a=mergesort(nums,le,mi) b=merges ...

  2. 自动构建自己的ASP.NET Core基础镜像

    在开发过程中,我们可以根据自身情况来定制自己的基础镜像,以便加快CI\CD构建速度以及提高开发体验.这里我们就以ASP.NET Core的基础镜像为例来进行讲解. 本次教程代码见开源库:https:/ ...

  3. web项目中设置首页

    1.在web.xml中设置以下代码: <welcome-file-list> <welcome-file>login.jsp</welcome-file> < ...

  4. L1-7 谁是赢家

    思路 这题好简单,可以分析一下,没有别的情况了. 代码 #include <bits/stdc++.h> using namespace std; int main() { int p1, ...

  5. 1069 The Black Hole of Numbers (20分)

    1069 The Black Hole of Numbers (20分) 1. 题目 2. 思路 把输入的数字作为字符串,调用排序算法,求最大最小 3. 注意点 输入的数字的范围是(0, 104), ...

  6. oracle查询第几行到第几行的数据

    我想查询10条到20条的数据 注意: 1.大数在前,小数在后面 2.都是小于 () minus (); 运行结果:

  7. webpack4.41.0配置一(基础配置webpack文件,入口出口,实现打包)

    1.查看node.js版本.npm版本和webpack版本(使用webpack4时,请确保node.js的版本>=8.9.4) 2.我先重新卸载了webpack和webpack-cli(全局) ...

  8. AAC MDCT

    AAC采用MDCT进行时频变换. 在编码端,以block为单位取出N个sample,乘以合适的window function后再进行MDCT.N通常为2048,256. 每个输入到MDCT的sampl ...

  9. python+selenium:浏览器webdriver操作(1)--基本对象定位

    1.简单对象的定位-----自动化测试的核心 对象的定位应该是自动化测试的核心,要想操作一个对象,首先应该识别这个对象.一个对象就是一个人一样,他会有各种的特征(属性),如比我们可以通过一个人的身份证 ...

  10. 1.BMap(百度地图)第二次加载显示不全

    问题: bmap第一次加载显示没问题: 第二次 再次加载这个页面时,地图的显示出现了问题: . 分析问题出现原因:你要确保dom创建后且处于显示状态(即display不能为none)才能再次初始化地图 ...