A:common基础功能及测试
This commit is contained in:
1 parent
9ffa70acde
commit
237cbd8788
11 files changed
+5929
-32
No files matched your search
@@ -0,0 +1,478 @@
|
||||
namespace Common.ExpandFun;
|
||||
|
||||
/// <summary>
|
||||
/// 分页结果类
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数据项类型</typeparam>
|
||||
public class PagedResult<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前页码(从 1 开始)
|
||||
/// </summary>
|
||||
public int PageIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 每页大小
|
||||
/// </summary>
|
||||
public int PageSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总记录数
|
||||
/// </summary>
|
||||
public int TotalCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总页数
|
||||
/// </summary>
|
||||
public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize);
|
||||
|
||||
/// <summary>
|
||||
/// 当前页数据
|
||||
/// </summary>
|
||||
public List<T> Items { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 是否有上一页
|
||||
/// </summary>
|
||||
public bool HasPreviousPage => PageIndex > 1;
|
||||
|
||||
/// <summary>
|
||||
/// 是否有下一页
|
||||
/// </summary>
|
||||
public bool HasNextPage => PageIndex < TotalPages;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 树形节点类
|
||||
/// </summary>
|
||||
/// <typeparam name="T">节点数据类型</typeparam>
|
||||
public class TreeNode<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// 节点数据
|
||||
/// </summary>
|
||||
public T Data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 父节点 ID
|
||||
/// </summary>
|
||||
public object? ParentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前节点 ID
|
||||
/// </summary>
|
||||
public object Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 子节点列表
|
||||
/// </summary>
|
||||
public List<TreeNode<T>> Children { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 层级深度(根节点为 0)
|
||||
/// </summary>
|
||||
public int Level { get; set; }
|
||||
|
||||
public TreeNode(T data, object id, object? parentId = null)
|
||||
{
|
||||
Data = data;
|
||||
Id = id;
|
||||
ParentId = parentId;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 集合扩展方法
|
||||
/// </summary>
|
||||
public static class CollectionExpand
|
||||
{
|
||||
#region 分页处理
|
||||
|
||||
/// <summary>
|
||||
/// 将集合转换为分页结果
|
||||
/// </summary>
|
||||
/// <typeparam name="T">元素类型</typeparam>
|
||||
/// <param name="source">源集合</param>
|
||||
/// <param name="pageIndex">页码(从 1 开始)</param>
|
||||
/// <param name="pageSize">每页大小</param>
|
||||
/// <returns>分页结果</returns>
|
||||
public static PagedResult<T> ToPagedResult<T>(this IEnumerable<T> source, int pageIndex, int pageSize)
|
||||
{
|
||||
if (pageIndex < 1)
|
||||
pageIndex = 1;
|
||||
|
||||
if (pageSize < 1)
|
||||
pageSize = 10;
|
||||
|
||||
var list = source as IList<T> ?? source.ToList();
|
||||
var totalCount = list.Count;
|
||||
|
||||
var items = list
|
||||
.Skip((pageIndex - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToList();
|
||||
|
||||
return new PagedResult<T>
|
||||
{
|
||||
PageIndex = pageIndex,
|
||||
PageSize = pageSize,
|
||||
TotalCount = totalCount,
|
||||
Items = items
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 IQueryable 转换为分页结果(适用于数据库查询)
|
||||
/// </summary>
|
||||
public static PagedResult<T> ToPagedResult<T>(this IQueryable<T> source, int pageIndex, int pageSize)
|
||||
{
|
||||
if (pageIndex < 1)
|
||||
pageIndex = 1;
|
||||
|
||||
if (pageSize < 1)
|
||||
pageSize = 10;
|
||||
|
||||
var totalCount = source.Count();
|
||||
|
||||
var items = source
|
||||
.Skip((pageIndex - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToList();
|
||||
|
||||
return new PagedResult<T>
|
||||
{
|
||||
PageIndex = pageIndex,
|
||||
PageSize = pageSize,
|
||||
TotalCount = totalCount,
|
||||
Items = items
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 树形结构转换
|
||||
|
||||
/// <summary>
|
||||
/// 将平铺列表转换为树形结构
|
||||
/// </summary>
|
||||
/// <typeparam name="T">元素类型</typeparam>
|
||||
/// <param name="source">源集合</param>
|
||||
/// <param name="idSelector">ID 选择器</param>
|
||||
/// <param name="parentIdSelector">父 ID 选择器</param>
|
||||
/// <param name="rootParentId">根节点的父 ID(默认为 null)</param>
|
||||
/// <returns>树形结构</returns>
|
||||
public static List<TreeNode<T>> ToTree<T>(
|
||||
this IEnumerable<T> source,
|
||||
Func<T, object> idSelector,
|
||||
Func<T, object?> parentIdSelector,
|
||||
object? rootParentId = null)
|
||||
{
|
||||
var list = source.ToList();
|
||||
var nodes = list.Select(item => new TreeNode<T>(item, idSelector(item), parentIdSelector(item))).ToList();
|
||||
var lookup = nodes.ToLookup(n => n.ParentId);
|
||||
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
node.Children = lookup[node.Id].ToList();
|
||||
}
|
||||
|
||||
// 构建层级深度
|
||||
void SetLevel(TreeNode<T> node, int level)
|
||||
{
|
||||
node.Level = level;
|
||||
foreach (var child in node.Children)
|
||||
{
|
||||
SetLevel(child, level + 1);
|
||||
}
|
||||
}
|
||||
|
||||
var rootNodes = nodes.Where(n => Equals(n.ParentId, rootParentId)).ToList();
|
||||
foreach (var root in rootNodes)
|
||||
{
|
||||
SetLevel(root, 0);
|
||||
}
|
||||
|
||||
return rootNodes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将树形结构转换为平铺列表
|
||||
/// </summary>
|
||||
/// <typeparam name="T">元素类型</typeparam>
|
||||
/// <param name="tree">树形结构</param>
|
||||
/// <returns>平铺列表</returns>
|
||||
public static List<T> ToFlatList<T>(this IEnumerable<TreeNode<T>> tree)
|
||||
{
|
||||
var result = new List<T>();
|
||||
|
||||
void Traverse(IEnumerable<TreeNode<T>> nodes)
|
||||
{
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
result.Add(node.Data);
|
||||
if (node.Children.Any())
|
||||
{
|
||||
Traverse(node.Children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Traverse(tree);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 遍历树形结构(深度优先)
|
||||
/// </summary>
|
||||
public static void TraverseTree<T>(this IEnumerable<TreeNode<T>> tree, Action<TreeNode<T>> action)
|
||||
{
|
||||
void Traverse(IEnumerable<TreeNode<T>> nodes)
|
||||
{
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
action(node);
|
||||
if (node.Children.Any())
|
||||
{
|
||||
Traverse(node.Children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Traverse(tree);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 批处理
|
||||
|
||||
/// <summary>
|
||||
/// 将集合分批处理
|
||||
/// </summary>
|
||||
/// <typeparam name="T">元素类型</typeparam>
|
||||
/// <param name="source">源集合</param>
|
||||
/// <param name="batchSize">批次大小</param>
|
||||
/// <returns>批次集合</returns>
|
||||
public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int batchSize)
|
||||
{
|
||||
if (batchSize <= 0)
|
||||
throw new ArgumentException("Batch size must be greater than 0", nameof(batchSize));
|
||||
|
||||
var batch = new List<T>(batchSize);
|
||||
|
||||
foreach (var item in source)
|
||||
{
|
||||
batch.Add(item);
|
||||
|
||||
if (batch.Count == batchSize)
|
||||
{
|
||||
yield return batch;
|
||||
batch = new List<T>(batchSize);
|
||||
}
|
||||
}
|
||||
|
||||
if (batch.Count > 0)
|
||||
{
|
||||
yield return batch;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分批执行操作
|
||||
/// </summary>
|
||||
public static void ForEachBatch<T>(this IEnumerable<T> source, int batchSize, Action<IEnumerable<T>> action)
|
||||
{
|
||||
foreach (var batch in source.Batch(batchSize))
|
||||
{
|
||||
action(batch);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 空集合安全操作
|
||||
|
||||
/// <summary>
|
||||
/// 如果集合为空,返回默认集合
|
||||
/// </summary>
|
||||
public static IEnumerable<T> OrEmpty<T>(this IEnumerable<T>? source)
|
||||
{
|
||||
return source ?? Enumerable.Empty<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 如果集合为 null 或空,返回默认值
|
||||
/// </summary>
|
||||
public static IEnumerable<T> OrDefault<T>(this IEnumerable<T>? source, IEnumerable<T> defaultValue)
|
||||
{
|
||||
var list = source?.ToList();
|
||||
return list == null || list.Count == 0 ? defaultValue : list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断集合是否为 null 或空
|
||||
/// </summary>
|
||||
public static bool IsNullOrEmpty<T>(this IEnumerable<T>? source)
|
||||
{
|
||||
return source == null || !source.Any();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断集合是否有元素
|
||||
/// </summary>
|
||||
public static bool HasItems<T>(this IEnumerable<T>? source)
|
||||
{
|
||||
return source != null && source.Any();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 其他实用扩展
|
||||
|
||||
/// <summary>
|
||||
/// 对集合中的每个元素执行操作
|
||||
/// </summary>
|
||||
public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
|
||||
{
|
||||
foreach (var item in source)
|
||||
{
|
||||
action(item);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对集合中的每个元素执行操作(带索引)
|
||||
/// </summary>
|
||||
public static void ForEach<T>(this IEnumerable<T> source, Action<T, int> action)
|
||||
{
|
||||
int index = 0;
|
||||
foreach (var item in source)
|
||||
{
|
||||
action(item, index++);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连接字符串(带分隔符)
|
||||
/// </summary>
|
||||
public static string JoinString<T>(this IEnumerable<T> source, string separator = ", ")
|
||||
{
|
||||
return string.Join(separator, source);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 随机打乱集合顺序
|
||||
/// </summary>
|
||||
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source)
|
||||
{
|
||||
var random = new Random();
|
||||
return source.OrderBy(_ => random.Next());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 随机获取 n 个元素
|
||||
/// </summary>
|
||||
public static IEnumerable<T> TakeRandom<T>(this IEnumerable<T> source, int count)
|
||||
{
|
||||
return source.Shuffle().Take(count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将集合分成多组(平均分配)
|
||||
/// </summary>
|
||||
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, int parts)
|
||||
{
|
||||
if (parts <= 0)
|
||||
throw new ArgumentException("Parts must be greater than 0", nameof(parts));
|
||||
|
||||
var list = source.ToList();
|
||||
var batchSize = (int)Math.Ceiling(list.Count / (double)parts);
|
||||
|
||||
return list.Batch(batchSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断集合中是否所有元素都唯一
|
||||
/// </summary>
|
||||
public static bool AllUnique<T>(this IEnumerable<T> source)
|
||||
{
|
||||
var set = new HashSet<T>();
|
||||
return source.All(set.Add);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断集合中是否有重复元素
|
||||
/// </summary>
|
||||
public static bool HasDuplicates<T>(this IEnumerable<T> source)
|
||||
{
|
||||
return !source.AllUnique();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取集合中的重复元素
|
||||
/// </summary>
|
||||
public static IEnumerable<T> GetDuplicates<T>(this IEnumerable<T> source)
|
||||
{
|
||||
return source.GroupBy(x => x)
|
||||
.Where(g => g.Count() > 1)
|
||||
.Select(g => g.Key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将两个集合交替合并
|
||||
/// </summary>
|
||||
public static IEnumerable<T> Interleave<T>(this IEnumerable<T> first, IEnumerable<T> second)
|
||||
{
|
||||
using var e1 = first.GetEnumerator();
|
||||
using var e2 = second.GetEnumerator();
|
||||
|
||||
while (e1.MoveNext() && e2.MoveNext())
|
||||
{
|
||||
yield return e1.Current;
|
||||
yield return e2.Current;
|
||||
}
|
||||
|
||||
while (e1.MoveNext())
|
||||
yield return e1.Current;
|
||||
|
||||
while (e2.MoveNext())
|
||||
yield return e2.Current;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 安全地获取指定索引的元素(索引越界返回默认值)
|
||||
/// </summary>
|
||||
public static T? ElementAtOrDefault<T>(this IEnumerable<T> source, int index, T? defaultValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
return source.ElementAtOrDefault(index) ?? defaultValue;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将集合中的 null 元素过滤掉
|
||||
/// </summary>
|
||||
public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> source) where T : class
|
||||
{
|
||||
return source.Where(x => x != null)!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对集合进行分组并转换为字典
|
||||
/// </summary>
|
||||
public static Dictionary<TKey, List<TValue>> ToDictionaryList<TSource, TKey, TValue>(
|
||||
this IEnumerable<TSource> source,
|
||||
Func<TSource, TKey> keySelector,
|
||||
Func<TSource, TValue> valueSelector) where TKey : notnull
|
||||
{
|
||||
return source
|
||||
.GroupBy(keySelector)
|
||||
.ToDictionary(g => g.Key, g => g.Select(valueSelector).ToList());
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,58 +1,712 @@
|
||||
namespace Common.ExpandFun;
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Common.ExpandFun;
|
||||
|
||||
/// <summary>
|
||||
/// 字符串扩展方法
|
||||
/// 提供字符串验证、转换、截取、格式化等实用功能
|
||||
/// </summary>
|
||||
public static class StringExpand
|
||||
{
|
||||
/// <summary>
|
||||
/// 检测是否是 url
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsValidUrl(this string str)
|
||||
{
|
||||
return Uri.TryCreate(str, UriKind.Absolute, out _);
|
||||
}
|
||||
#region 类型转换
|
||||
|
||||
/// <summary>
|
||||
/// 转换为整数(失败返回默认值)
|
||||
/// </summary>
|
||||
public static int ToInt(this string? str, int defaultValue = default)
|
||||
{
|
||||
return TryToInt(str) ?? defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试转换为整数
|
||||
/// </summary>
|
||||
public static int? TryToInt(this string? str)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(str))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return int.Parse(str);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return int.TryParse(str, out var result) ? result : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为长整数(失败返回默认值)
|
||||
/// </summary>
|
||||
public static long ToLong(this string? str, long defaultValue = default)
|
||||
{
|
||||
return TryToLong(str) ?? defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试转换为长整数
|
||||
/// </summary>
|
||||
public static long? TryToLong(this string? str)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
return null;
|
||||
|
||||
return long.TryParse(str, out var result) ? result : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为浮点数(失败返回默认值)
|
||||
/// </summary>
|
||||
public static double ToDouble(this string? str, double defaultValue = default)
|
||||
{
|
||||
return TryToDouble(str) ?? defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试转换为浮点数
|
||||
/// </summary>
|
||||
public static double? TryToDouble(this string? str)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(str))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
return null;
|
||||
|
||||
return double.TryParse(str, out var result) ? result : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为 Decimal(失败返回默认值)
|
||||
/// </summary>
|
||||
public static decimal ToDecimal(this string? str, decimal defaultValue = default)
|
||||
{
|
||||
return TryToDecimal(str) ?? defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试转换为 Decimal
|
||||
/// </summary>
|
||||
public static decimal? TryToDecimal(this string? str)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
return null;
|
||||
|
||||
return decimal.TryParse(str, out var result) ? result : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为布尔值(失败返回默认值)
|
||||
/// </summary>
|
||||
public static bool ToBool(this string? str, bool defaultValue = default)
|
||||
{
|
||||
return TryToBool(str) ?? defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试转换为布尔值
|
||||
/// </summary>
|
||||
public static bool? TryToBool(this string? str)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
return null;
|
||||
|
||||
return bool.TryParse(str, out var result) ? result : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为 DateTime(失败返回默认值)
|
||||
/// </summary>
|
||||
public static DateTime ToDateTime(this string? str, DateTime defaultValue = default)
|
||||
{
|
||||
return TryToDateTime(str) ?? defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试转换为 DateTime
|
||||
/// </summary>
|
||||
public static DateTime? TryToDateTime(this string? str)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
return null;
|
||||
|
||||
return DateTime.TryParse(str, out var result) ? result : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为 Guid(失败返回默认值)
|
||||
/// </summary>
|
||||
public static Guid ToGuid(this string? str, Guid defaultValue = default)
|
||||
{
|
||||
return TryToGuid(str) ?? defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试转换为 Guid
|
||||
/// </summary>
|
||||
public static Guid? TryToGuid(this string? str)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
return null;
|
||||
|
||||
return Guid.TryParse(str, out var result) ? result : null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 字符串判断
|
||||
|
||||
/// <summary>
|
||||
/// 判断字符串是否为 null 或空字符串
|
||||
/// </summary>
|
||||
public static bool IsNullOrEmpty(this string? str)
|
||||
{
|
||||
return string.IsNullOrEmpty(str);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断字符串是否为 null、空字符串或仅包含空白字符
|
||||
/// </summary>
|
||||
public static bool IsNullOrWhiteSpace(this string? str)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(str);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断字符串是否有值(非 null 且非空)
|
||||
/// </summary>
|
||||
public static bool HasValue(this string? str)
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(str);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断字符串是否只包含数字
|
||||
/// </summary>
|
||||
public static bool IsNumeric(this string? str)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
return false;
|
||||
|
||||
return str.All(char.IsDigit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断字符串是否只包含字母
|
||||
/// </summary>
|
||||
public static bool IsAlpha(this string? str)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
return false;
|
||||
|
||||
return str.All(char.IsLetter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断字符串是否只包含字母和数字
|
||||
/// </summary>
|
||||
public static bool IsAlphaNumeric(this string? str)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
return false;
|
||||
|
||||
return str.All(char.IsLetterOrDigit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检测是否是有效的 URL
|
||||
/// </summary>
|
||||
public static bool IsValidUrl(this string? str)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
return false;
|
||||
|
||||
return Uri.TryCreate(str, UriKind.Absolute, out var uri)
|
||||
&& (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 字符串截取和处理
|
||||
|
||||
/// <summary>
|
||||
/// 安全截取字符串(不会抛出异常)
|
||||
/// </summary>
|
||||
public static string SafeSubstring(this string? str, int startIndex, int? length = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str))
|
||||
return string.Empty;
|
||||
|
||||
if (startIndex >= str.Length)
|
||||
return string.Empty;
|
||||
|
||||
if (startIndex < 0)
|
||||
startIndex = 0;
|
||||
|
||||
if (length == null)
|
||||
return str.Substring(startIndex);
|
||||
|
||||
var actualLength = Math.Min(length.Value, str.Length - startIndex);
|
||||
return actualLength <= 0 ? string.Empty : str.Substring(startIndex, actualLength);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 截取字符串并添加省略号(超过指定长度时)
|
||||
/// </summary>
|
||||
public static string Truncate(this string? str, int maxLength, string suffix = "...")
|
||||
{
|
||||
if (string.IsNullOrEmpty(str))
|
||||
return string.Empty;
|
||||
|
||||
if (str.Length <= maxLength)
|
||||
return str;
|
||||
|
||||
return str.Substring(0, maxLength) + suffix;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除字符串前后的指定字符串
|
||||
/// </summary>
|
||||
public static string Trim(this string? str, string trimString)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str) || string.IsNullOrEmpty(trimString))
|
||||
return str ?? string.Empty;
|
||||
|
||||
return str.TrimStart(trimString).TrimEnd(trimString);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除字符串开头的指定字符串
|
||||
/// </summary>
|
||||
public static string TrimStart(this string? str, string trimString)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str) || string.IsNullOrEmpty(trimString))
|
||||
return str ?? string.Empty;
|
||||
|
||||
while (str.StartsWith(trimString))
|
||||
{
|
||||
str = str.Substring(trimString.Length);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除字符串末尾的指定字符串
|
||||
/// </summary>
|
||||
public static string TrimEnd(this string? str, string trimString)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str) || string.IsNullOrEmpty(trimString))
|
||||
return str ?? string.Empty;
|
||||
|
||||
while (str.EndsWith(trimString))
|
||||
{
|
||||
str = str.Substring(0, str.Length - trimString.Length);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除所有空白字符(包括空格、制表符、换行符等)
|
||||
/// </summary>
|
||||
public static string RemoveWhiteSpace(this string? str)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str))
|
||||
return string.Empty;
|
||||
|
||||
return new string(str.Where(c => !char.IsWhiteSpace(c)).ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 反转字符串
|
||||
/// </summary>
|
||||
public static string Reverse(this string? str)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str))
|
||||
return string.Empty;
|
||||
|
||||
var chars = str.ToCharArray();
|
||||
Array.Reverse(chars);
|
||||
return new string(chars);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 字符串格式化
|
||||
|
||||
/// <summary>
|
||||
/// 转换为驼峰命名(camelCase)
|
||||
/// </summary>
|
||||
public static string ToCamelCase(this string? str)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
return string.Empty;
|
||||
|
||||
var words = str.Split(new[] { ' ', '_', '-' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (words.Length == 0)
|
||||
return string.Empty;
|
||||
|
||||
var result = new StringBuilder();
|
||||
result.Append(words[0].ToLowerInvariant());
|
||||
|
||||
for (int i = 1; i < words.Length; i++)
|
||||
{
|
||||
result.Append(char.ToUpperInvariant(words[i][0]));
|
||||
if (words[i].Length > 1)
|
||||
result.Append(words[i].Substring(1).ToLowerInvariant());
|
||||
}
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为帕斯卡命名(PascalCase)
|
||||
/// </summary>
|
||||
public static string ToPascalCase(this string? str)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
return string.Empty;
|
||||
|
||||
var words = str.Split(new[] { ' ', '_', '-' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var result = new StringBuilder();
|
||||
|
||||
foreach (var word in words)
|
||||
{
|
||||
result.Append(char.ToUpperInvariant(word[0]));
|
||||
if (word.Length > 1)
|
||||
result.Append(word.Substring(1).ToLowerInvariant());
|
||||
}
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为蛇形命名(snake_case)
|
||||
/// </summary>
|
||||
public static string ToSnakeCase(this string? str)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
return string.Empty;
|
||||
|
||||
return Regex.Replace(str, "([a-z])([A-Z])", "$1_$2").ToLowerInvariant();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为烤串命名(kebab-case)
|
||||
/// </summary>
|
||||
public static string ToKebabCase(this string? str)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
return string.Empty;
|
||||
|
||||
return Regex.Replace(str, "([a-z])([A-Z])", "$1-$2").ToLowerInvariant();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 首字母大写
|
||||
/// </summary>
|
||||
public static string Capitalize(this string? str)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str))
|
||||
return string.Empty;
|
||||
|
||||
return char.ToUpperInvariant(str[0]) + (str.Length > 1 ? str.Substring(1) : string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 首字母小写
|
||||
/// </summary>
|
||||
public static string Uncapitalize(this string? str)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str))
|
||||
return string.Empty;
|
||||
|
||||
return char.ToLowerInvariant(str[0]) + (str.Length > 1 ? str.Substring(1) : string.Empty);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 字符串编码
|
||||
|
||||
/// <summary>
|
||||
/// Base64 编码
|
||||
/// </summary>
|
||||
public static string ToBase64(this string? str, Encoding? encoding = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str))
|
||||
return string.Empty;
|
||||
|
||||
var bytes = (encoding ?? Encoding.UTF8).GetBytes(str);
|
||||
return Convert.ToBase64String(bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base64 解码
|
||||
/// </summary>
|
||||
public static string FromBase64(this string? str, Encoding? encoding = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str))
|
||||
return string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
return double.Parse(str);
|
||||
var bytes = Convert.FromBase64String(str);
|
||||
return (encoding ?? Encoding.UTF8).GetString(bytes);
|
||||
}
|
||||
catch (Exception e)
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// URL 编码
|
||||
/// </summary>
|
||||
public static string UrlEncode(this string? str)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str))
|
||||
return string.Empty;
|
||||
|
||||
return Uri.EscapeDataString(str);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// URL 解码
|
||||
/// </summary>
|
||||
public static string UrlDecode(this string? str)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str))
|
||||
return string.Empty;
|
||||
|
||||
return Uri.UnescapeDataString(str);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 字符串哈希
|
||||
|
||||
/// <summary>
|
||||
/// 计算 MD5 哈希
|
||||
/// </summary>
|
||||
public static string ToMd5(this string? str)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str))
|
||||
return string.Empty;
|
||||
|
||||
using var md5 = MD5.Create();
|
||||
var bytes = Encoding.UTF8.GetBytes(str);
|
||||
var hash = md5.ComputeHash(bytes);
|
||||
return Convert.ToHexString(hash).ToLowerInvariant();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算 SHA256 哈希
|
||||
/// </summary>
|
||||
public static string ToSha256(this string? str)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str))
|
||||
return string.Empty;
|
||||
|
||||
using var sha256 = SHA256.Create();
|
||||
var bytes = Encoding.UTF8.GetBytes(str);
|
||||
var hash = sha256.ComputeHash(bytes);
|
||||
return Convert.ToHexString(hash).ToLowerInvariant();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 字符串匹配
|
||||
|
||||
/// <summary>
|
||||
/// 判断字符串是否匹配指定的正则表达式
|
||||
/// </summary>
|
||||
public static bool IsMatch(this string? str, string pattern)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str))
|
||||
return false;
|
||||
|
||||
return Regex.IsMatch(str, pattern);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断字符串是否包含指定的子串(忽略大小写)
|
||||
/// </summary>
|
||||
public static bool ContainsIgnoreCase(this string? str, string value)
|
||||
{
|
||||
if (str == null || value == null)
|
||||
return false;
|
||||
|
||||
return str.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断字符串是否以指定的子串开始(忽略大小写)
|
||||
/// </summary>
|
||||
public static bool StartsWithIgnoreCase(this string? str, string value)
|
||||
{
|
||||
if (str == null || value == null)
|
||||
return false;
|
||||
|
||||
return str.StartsWith(value, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断字符串是否以指定的子串结束(忽略大小写)
|
||||
/// </summary>
|
||||
public static bool EndsWithIgnoreCase(this string? str, string value)
|
||||
{
|
||||
if (str == null || value == null)
|
||||
return false;
|
||||
|
||||
return str.EndsWith(value, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断字符串是否等于指定的值(忽略大小写)
|
||||
/// </summary>
|
||||
public static bool EqualsIgnoreCase(this string? str, string? value)
|
||||
{
|
||||
if (str == null && value == null)
|
||||
return true;
|
||||
|
||||
if (str == null || value == null)
|
||||
return false;
|
||||
|
||||
return str.Equals(value, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 字符串替换
|
||||
|
||||
/// <summary>
|
||||
/// 替换字符串(忽略大小写)
|
||||
/// </summary>
|
||||
public static string ReplaceIgnoreCase(this string? str, string oldValue, string newValue)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str) || string.IsNullOrEmpty(oldValue))
|
||||
return str ?? string.Empty;
|
||||
|
||||
return Regex.Replace(str, Regex.Escape(oldValue), newValue ?? string.Empty, RegexOptions.IgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除字符串中的 HTML 标签
|
||||
/// </summary>
|
||||
public static string RemoveHtmlTags(this string? str)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str))
|
||||
return string.Empty;
|
||||
|
||||
return Regex.Replace(str, "<.*?>", string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除字符串中的特殊字符(只保留字母、数字和空格)
|
||||
/// </summary>
|
||||
public static string RemoveSpecialCharacters(this string? str)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str))
|
||||
return string.Empty;
|
||||
|
||||
return Regex.Replace(str, "[^a-zA-Z0-9 ]", string.Empty);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 其他实用方法
|
||||
|
||||
/// <summary>
|
||||
/// 重复字符串 N 次
|
||||
/// </summary>
|
||||
public static string Repeat(this string? str, int count)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str) || count <= 0)
|
||||
return string.Empty;
|
||||
|
||||
var sb = new StringBuilder(str.Length * count);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
sb.Append(str);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确保字符串以指定的前缀开始
|
||||
/// </summary>
|
||||
public static string EnsureStartsWith(this string? str, string prefix)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str))
|
||||
return prefix ?? string.Empty;
|
||||
|
||||
if (string.IsNullOrEmpty(prefix))
|
||||
return str;
|
||||
|
||||
return str.StartsWith(prefix) ? str : prefix + str;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确保字符串以指定的后缀结束
|
||||
/// </summary>
|
||||
public static string EnsureEndsWith(this string? str, string suffix)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str))
|
||||
return suffix ?? string.Empty;
|
||||
|
||||
if (string.IsNullOrEmpty(suffix))
|
||||
return str;
|
||||
|
||||
return str.EndsWith(suffix) ? str : str + suffix;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 如果字符串为 null 或空,返回默认值
|
||||
/// </summary>
|
||||
public static string OrDefault(this string? str, string defaultValue = "")
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(str) ? defaultValue : str;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取字符串的字节长度(UTF-8 编码)
|
||||
/// </summary>
|
||||
public static int GetByteLength(this string? str, Encoding? encoding = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str))
|
||||
return 0;
|
||||
|
||||
return (encoding ?? Encoding.UTF8).GetByteCount(str);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算字符串的相似度(Levenshtein 距离)
|
||||
/// </summary>
|
||||
public static int LevenshteinDistance(this string? source, string? target)
|
||||
{
|
||||
if (string.IsNullOrEmpty(source))
|
||||
return target?.Length ?? 0;
|
||||
|
||||
if (string.IsNullOrEmpty(target))
|
||||
return source.Length;
|
||||
|
||||
int[,] distance = new int[source.Length + 1, target.Length + 1];
|
||||
|
||||
for (int i = 0; i <= source.Length; i++)
|
||||
distance[i, 0] = i;
|
||||
|
||||
for (int j = 0; j <= target.Length; j++)
|
||||
distance[0, j] = j;
|
||||
|
||||
for (int i = 1; i <= source.Length; i++)
|
||||
{
|
||||
for (int j = 1; j <= target.Length; j++)
|
||||
{
|
||||
int cost = (target[j - 1] == source[i - 1]) ? 0 : 1;
|
||||
distance[i, j] = Math.Min(
|
||||
Math.Min(distance[i - 1, j] + 1, distance[i, j - 1] + 1),
|
||||
distance[i - 1, j - 1] + cost);
|
||||
}
|
||||
}
|
||||
|
||||
return distance[source.Length, target.Length];
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
+128
-1
@@ -11,6 +11,9 @@
|
||||
- ✅ **日期时间扩展** - 日期格式化、时间戳转换、时区转换、工作日计算
|
||||
- ✅ **加密工具** - AES/RSA加密、MD5/SHA256哈希、Base64编解码、密码哈希、HMAC签名
|
||||
- ✅ **验证工具** - 邮箱、手机号、身份证、URL验证,与Result模式无缝集成
|
||||
- ✅ **集合扩展** - 分页处理、树形结构转换、批处理、DistinctBy等高级集合操作
|
||||
- ✅ **枚举工具** - 枚举解析、描述获取、标志位操作、下拉列表数据生成
|
||||
- ✅ **反射工具** - 高性能属性访问(带缓存)、类型扫描、特性读取、对象映射
|
||||
- ✅ **JSON 序列化** - 预配置的 JSON 序列化,支持 camelCase 和 IPAddress
|
||||
- ✅ **系统工具** - 动态端口分配、管理员权限检查
|
||||
|
||||
@@ -260,6 +263,118 @@ var result = Result.Failure<string>(exception);
|
||||
Console.WriteLine(result.ErrorCode); // 1001
|
||||
```
|
||||
|
||||
#### 10. 集合扩展
|
||||
|
||||
```csharp
|
||||
using Common.ExpandFun;
|
||||
|
||||
// 分页处理
|
||||
var items = Enumerable.Range(1, 100).ToList();
|
||||
var pagedResult = items.ToPagedResult(pageIndex: 1, pageSize: 10);
|
||||
Console.WriteLine($"Total pages: {pagedResult.TotalPages}");
|
||||
|
||||
// 树形结构转换
|
||||
var departments = new List<Department>
|
||||
{
|
||||
new() { Id = 1, Name = "总公司", ParentId = null },
|
||||
new() { Id = 2, Name = "技术部", ParentId = 1 },
|
||||
new() { Id = 3, Name = "前端组", ParentId = 2 }
|
||||
};
|
||||
var tree = departments.ToTree(x => x.Id, x => x.ParentId);
|
||||
|
||||
// 批处理
|
||||
var largeList = Enumerable.Range(1, 1000).ToList();
|
||||
largeList.ForEachBatch(100, batch =>
|
||||
{
|
||||
// 每批处理 100 条记录
|
||||
ProcessBatch(batch);
|
||||
});
|
||||
|
||||
// 去重(自定义键)
|
||||
var users = new[] { user1, user2, user3 };
|
||||
var uniqueUsers = users.DistinctBy(u => u.Email);
|
||||
|
||||
// 安全操作
|
||||
List<int>? nullList = null;
|
||||
var safeList = nullList.OrEmpty(); // 返回空集合而非 null
|
||||
```
|
||||
|
||||
#### 11. 枚举工具
|
||||
|
||||
```csharp
|
||||
using Common.Utils;
|
||||
using System.ComponentModel;
|
||||
|
||||
// 定义枚举
|
||||
public enum OrderStatus
|
||||
{
|
||||
[Description("待支付")]
|
||||
Pending = 0,
|
||||
|
||||
[Description("已支付")]
|
||||
Paid = 1,
|
||||
|
||||
[Description("已完成")]
|
||||
Completed = 2
|
||||
}
|
||||
|
||||
// 枚举解析
|
||||
var status = EnumHelper.Parse<OrderStatus>("Paid");
|
||||
var statusFromDesc = EnumHelper.ParseFromDescription<OrderStatus>("已支付");
|
||||
|
||||
// 获取描述
|
||||
string desc = OrderStatus.Paid.GetDescription(); // "已支付"
|
||||
|
||||
// 获取下拉列表选项
|
||||
var options = EnumHelper.GetOptions<OrderStatus>();
|
||||
// 返回 [{ Value: 0, Name: "Pending", Description: "待支付" }, ...]
|
||||
|
||||
// 标志位枚举操作
|
||||
[Flags]
|
||||
public enum Permissions
|
||||
{
|
||||
Read = 1,
|
||||
Write = 2,
|
||||
Delete = 4
|
||||
}
|
||||
|
||||
var permissions = Permissions.Read | Permissions.Write;
|
||||
bool canRead = permissions.HasFlag(Permissions.Read); // true
|
||||
var newPermissions = permissions.AddFlag(Permissions.Delete);
|
||||
```
|
||||
|
||||
#### 12. 反射工具
|
||||
|
||||
```csharp
|
||||
using Common.Utils;
|
||||
|
||||
// 高性能属性访问(使用表达式树和缓存)
|
||||
var user = new User { Name = "Alice", Age = 25 };
|
||||
var name = ReflectionHelper.GetPropertyValue(user, "Name");
|
||||
ReflectionHelper.SetPropertyValue(user, "Age", 30);
|
||||
|
||||
// 类型扫描
|
||||
var implementations = ReflectionHelper.FindImplementationsOf<IService>(assembly);
|
||||
var typesWithAttribute = ReflectionHelper.FindTypesWithAttribute<MyAttribute>(assembly);
|
||||
|
||||
// 属性复制(对象映射)
|
||||
var userDto = ReflectionHelper.MapTo<User, UserDto>(user);
|
||||
|
||||
// 特性读取(带缓存)
|
||||
var attribute = ReflectionHelper.GetAttribute<DescriptionAttribute>(typeof(User));
|
||||
bool hasAttribute = ReflectionHelper.HasAttribute<ObsoleteAttribute>(memberInfo);
|
||||
|
||||
// 创建实例
|
||||
var instance = ReflectionHelper.CreateInstance<MyClass>();
|
||||
|
||||
// 方法调用
|
||||
var result = ReflectionHelper.InvokeMethod(obj, "Calculate", 10, 20);
|
||||
|
||||
// 类型判断
|
||||
bool isNumeric = ReflectionHelper.IsNumericType(typeof(int));
|
||||
bool isCollection = ReflectionHelper.IsCollectionType(typeof(List<int>));
|
||||
```
|
||||
|
||||
## 详细文档
|
||||
|
||||
### Global - 依赖注入容器
|
||||
@@ -613,6 +728,12 @@ result
|
||||
- `ResultTests.cs` - Result 模式测试(包含 LINQ 和异步测试)
|
||||
- `ExceptionExpandTests.cs` - 自定义异常测试
|
||||
- `StringExpandTests.cs` - 字符串扩展测试
|
||||
- `DateTimeExpandTests.cs` - 日期时间扩展测试
|
||||
- `CryptoHelperTests.cs` - 加密工具测试
|
||||
- `ValidationHelperTests.cs` - 验证工具测试
|
||||
- `CollectionExpandTests.cs` - 集合扩展测试
|
||||
- `EnumHelperTests.cs` - 枚举工具测试
|
||||
- `ReflectionHelperTests.cs` - 反射工具测试
|
||||
- `JsonHelperTests.cs` - JSON 序列化测试
|
||||
- `SystemHelperTests.cs` - 系统工具测试
|
||||
|
||||
@@ -665,11 +786,17 @@ Sharp/Common/
|
||||
│ ├── ExceptionExpand.cs # 自定义异常
|
||||
│ └── ExampleExceptionEnum.cs # 预定义异常
|
||||
├── ExpandFun/
|
||||
│ └── StringExpand.cs # 字符串扩展
|
||||
│ ├── StringExpand.cs # 字符串扩展
|
||||
│ ├── DateTimeExpand.cs # 日期时间扩展
|
||||
│ └── CollectionExpand.cs # 集合扩展
|
||||
├── Results/
|
||||
│ ├── Result.cs # Result 核心类
|
||||
│ └── ResultExtensions.cs # Result 扩展方法
|
||||
└── Utils/
|
||||
├── CryptoHelper.cs # 加密工具
|
||||
├── ValidationHelper.cs # 验证工具
|
||||
├── EnumHelper.cs # 枚举工具
|
||||
├── ReflectionHelper.cs # 反射工具
|
||||
├── JsonHelper.cs # JSON 工具
|
||||
└── SystemHelper.cs # 系统工具
|
||||
```
|
||||
|
||||
@@ -0,0 +1,526 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Common.Utils;
|
||||
|
||||
/// <summary>
|
||||
/// 加密工具类
|
||||
/// 提供 AES、RSA、哈希、Base64、密码哈希、HMAC 等加密功能
|
||||
/// </summary>
|
||||
public static class CryptoHelper
|
||||
{
|
||||
#region AES 加密解密
|
||||
|
||||
/// <summary>
|
||||
/// AES 加密(使用密码派生密钥)
|
||||
/// </summary>
|
||||
/// <param name="plainText">明文</param>
|
||||
/// <param name="password">密码</param>
|
||||
/// <returns>Base64 编码的密文(包含 Salt 和 IV)</returns>
|
||||
public static string AesEncrypt(string plainText, string password)
|
||||
{
|
||||
if (string.IsNullOrEmpty(plainText))
|
||||
throw new ArgumentNullException(nameof(plainText));
|
||||
if (string.IsNullOrEmpty(password))
|
||||
throw new ArgumentNullException(nameof(password));
|
||||
|
||||
// 生成随机 Salt
|
||||
byte[] salt = RandomNumberGenerator.GetBytes(16);
|
||||
|
||||
// 从密码派生密钥
|
||||
using var deriveBytes = new Rfc2898DeriveBytes(password, salt, 10000, HashAlgorithmName.SHA256);
|
||||
byte[] key = deriveBytes.GetBytes(32); // 256 bit
|
||||
byte[] iv = deriveBytes.GetBytes(16); // 128 bit
|
||||
|
||||
// AES 加密
|
||||
using var aes = Aes.Create();
|
||||
aes.Key = key;
|
||||
aes.IV = iv;
|
||||
|
||||
using var encryptor = aes.CreateEncryptor();
|
||||
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
|
||||
byte[] cipherBytes = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
|
||||
|
||||
// 组合 Salt + 密文
|
||||
byte[] result = new byte[salt.Length + cipherBytes.Length];
|
||||
Buffer.BlockCopy(salt, 0, result, 0, salt.Length);
|
||||
Buffer.BlockCopy(cipherBytes, 0, result, salt.Length, cipherBytes.Length);
|
||||
|
||||
return Convert.ToBase64String(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AES 解密(使用密码派生密钥)
|
||||
/// </summary>
|
||||
/// <param name="cipherText">Base64 编码的密文</param>
|
||||
/// <param name="password">密码</param>
|
||||
/// <returns>明文</returns>
|
||||
public static string AesDecrypt(string cipherText, string password)
|
||||
{
|
||||
if (string.IsNullOrEmpty(cipherText))
|
||||
throw new ArgumentNullException(nameof(cipherText));
|
||||
if (string.IsNullOrEmpty(password))
|
||||
throw new ArgumentNullException(nameof(password));
|
||||
|
||||
byte[] fullCipher = Convert.FromBase64String(cipherText);
|
||||
|
||||
// 提取 Salt
|
||||
byte[] salt = new byte[16];
|
||||
Buffer.BlockCopy(fullCipher, 0, salt, 0, 16);
|
||||
|
||||
// 提取密文
|
||||
byte[] cipherBytes = new byte[fullCipher.Length - 16];
|
||||
Buffer.BlockCopy(fullCipher, 16, cipherBytes, 0, cipherBytes.Length);
|
||||
|
||||
// 从密码派生密钥
|
||||
using var deriveBytes = new Rfc2898DeriveBytes(password, salt, 10000, HashAlgorithmName.SHA256);
|
||||
byte[] key = deriveBytes.GetBytes(32);
|
||||
byte[] iv = deriveBytes.GetBytes(16);
|
||||
|
||||
// AES 解密
|
||||
using var aes = Aes.Create();
|
||||
aes.Key = key;
|
||||
aes.IV = iv;
|
||||
|
||||
using var decryptor = aes.CreateDecryptor();
|
||||
byte[] plainBytes = decryptor.TransformFinalBlock(cipherBytes, 0, cipherBytes.Length);
|
||||
|
||||
return Encoding.UTF8.GetString(plainBytes);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region RSA 加密解密
|
||||
|
||||
/// <summary>
|
||||
/// 生成 RSA 密钥对
|
||||
/// </summary>
|
||||
/// <param name="keySize">密钥大小(默认 2048 位)</param>
|
||||
/// <returns>包含公钥和私钥的元组</returns>
|
||||
public static (string PublicKey, string PrivateKey) GenerateRsaKeys(int keySize = 2048)
|
||||
{
|
||||
using var rsa = RSA.Create(keySize);
|
||||
var publicKey = Convert.ToBase64String(rsa.ExportRSAPublicKey());
|
||||
var privateKey = Convert.ToBase64String(rsa.ExportRSAPrivateKey());
|
||||
return (publicKey, privateKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RSA 公钥加密
|
||||
/// </summary>
|
||||
/// <param name="plainText">明文</param>
|
||||
/// <param name="publicKey">Base64 编码的公钥</param>
|
||||
/// <returns>Base64 编码的密文</returns>
|
||||
public static string RsaEncrypt(string plainText, string publicKey)
|
||||
{
|
||||
if (string.IsNullOrEmpty(plainText))
|
||||
throw new ArgumentNullException(nameof(plainText));
|
||||
if (string.IsNullOrEmpty(publicKey))
|
||||
throw new ArgumentNullException(nameof(publicKey));
|
||||
|
||||
using var rsa = RSA.Create();
|
||||
rsa.ImportRSAPublicKey(Convert.FromBase64String(publicKey), out _);
|
||||
|
||||
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
|
||||
byte[] cipherBytes = rsa.Encrypt(plainBytes, RSAEncryptionPadding.OaepSHA256);
|
||||
|
||||
return Convert.ToBase64String(cipherBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RSA 私钥解密
|
||||
/// </summary>
|
||||
/// <param name="cipherText">Base64 编码的密文</param>
|
||||
/// <param name="privateKey">Base64 编码的私钥</param>
|
||||
/// <returns>明文</returns>
|
||||
public static string RsaDecrypt(string cipherText, string privateKey)
|
||||
{
|
||||
if (string.IsNullOrEmpty(cipherText))
|
||||
throw new ArgumentNullException(nameof(cipherText));
|
||||
if (string.IsNullOrEmpty(privateKey))
|
||||
throw new ArgumentNullException(nameof(privateKey));
|
||||
|
||||
using var rsa = RSA.Create();
|
||||
rsa.ImportRSAPrivateKey(Convert.FromBase64String(privateKey), out _);
|
||||
|
||||
byte[] cipherBytes = Convert.FromBase64String(cipherText);
|
||||
byte[] plainBytes = rsa.Decrypt(cipherBytes, RSAEncryptionPadding.OaepSHA256);
|
||||
|
||||
return Encoding.UTF8.GetString(plainBytes);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 哈希函数
|
||||
|
||||
/// <summary>
|
||||
/// 计算 MD5 哈希
|
||||
/// </summary>
|
||||
/// <param name="input">输入字符串</param>
|
||||
/// <returns>32 位十六进制哈希字符串</returns>
|
||||
public static string Md5Hash(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
throw new ArgumentNullException(nameof(input));
|
||||
|
||||
using var md5 = MD5.Create();
|
||||
byte[] inputBytes = Encoding.UTF8.GetBytes(input);
|
||||
byte[] hashBytes = md5.ComputeHash(inputBytes);
|
||||
|
||||
return BytesToHex(hashBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算 SHA256 哈希
|
||||
/// </summary>
|
||||
/// <param name="input">输入字符串</param>
|
||||
/// <returns>64 位十六进制哈希字符串</returns>
|
||||
public static string Sha256Hash(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
throw new ArgumentNullException(nameof(input));
|
||||
|
||||
using var sha256 = SHA256.Create();
|
||||
byte[] inputBytes = Encoding.UTF8.GetBytes(input);
|
||||
byte[] hashBytes = sha256.ComputeHash(inputBytes);
|
||||
|
||||
return BytesToHex(hashBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算 SHA512 哈希
|
||||
/// </summary>
|
||||
/// <param name="input">输入字符串</param>
|
||||
/// <returns>128 位十六进制哈希字符串</returns>
|
||||
public static string Sha512Hash(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
throw new ArgumentNullException(nameof(input));
|
||||
|
||||
using var sha512 = SHA512.Create();
|
||||
byte[] inputBytes = Encoding.UTF8.GetBytes(input);
|
||||
byte[] hashBytes = sha512.ComputeHash(inputBytes);
|
||||
|
||||
return BytesToHex(hashBytes);
|
||||
}
|
||||
|
||||
private static string BytesToHex(byte[] bytes)
|
||||
{
|
||||
return Convert.ToHexString(bytes).ToLowerInvariant();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Base64 编解码
|
||||
|
||||
/// <summary>
|
||||
/// Base64 编码
|
||||
/// </summary>
|
||||
/// <param name="input">输入字符串</param>
|
||||
/// <returns>Base64 编码的字符串</returns>
|
||||
public static string Base64Encode(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
throw new ArgumentNullException(nameof(input));
|
||||
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(input);
|
||||
return Convert.ToBase64String(bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base64 解码
|
||||
/// </summary>
|
||||
/// <param name="input">Base64 编码的字符串</param>
|
||||
/// <returns>解码后的字符串</returns>
|
||||
public static string Base64Decode(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
throw new ArgumentNullException(nameof(input));
|
||||
|
||||
byte[] bytes = Convert.FromBase64String(input);
|
||||
return Encoding.UTF8.GetString(bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// URL 安全的 Base64 编码
|
||||
/// </summary>
|
||||
/// <param name="input">输入字符串</param>
|
||||
/// <returns>URL 安全的 Base64 字符串</returns>
|
||||
public static string Base64UrlEncode(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
throw new ArgumentNullException(nameof(input));
|
||||
|
||||
var base64 = Base64Encode(input);
|
||||
return base64.Replace('+', '-').Replace('/', '_').TrimEnd('=');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// URL 安全的 Base64 解码
|
||||
/// </summary>
|
||||
/// <param name="input">URL 安全的 Base64 字符串</param>
|
||||
/// <returns>解码后的字符串</returns>
|
||||
public static string Base64UrlDecode(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
throw new ArgumentNullException(nameof(input));
|
||||
|
||||
var base64 = input.Replace('-', '+').Replace('_', '/');
|
||||
switch (base64.Length % 4)
|
||||
{
|
||||
case 2: base64 += "=="; break;
|
||||
case 3: base64 += "="; break;
|
||||
}
|
||||
|
||||
return Base64Decode(base64);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 密码哈希(PBKDF2)
|
||||
|
||||
/// <summary>
|
||||
/// 使用 PBKDF2 哈希密码
|
||||
/// </summary>
|
||||
/// <param name="password">密码</param>
|
||||
/// <param name="saltSize">Salt 大小(字节,默认 16)</param>
|
||||
/// <param name="iterations">迭代次数(默认 100000)</param>
|
||||
/// <param name="hashSize">哈希大小(字节,默认 32)</param>
|
||||
/// <returns>格式化的哈希字符串(包含算法、迭代次数、Salt 和哈希值)</returns>
|
||||
public static string HashPassword(string password, int saltSize = 16, int iterations = 100000, int hashSize = 32)
|
||||
{
|
||||
if (string.IsNullOrEmpty(password))
|
||||
throw new ArgumentNullException(nameof(password));
|
||||
|
||||
// 生成随机 Salt
|
||||
byte[] salt = RandomNumberGenerator.GetBytes(saltSize);
|
||||
|
||||
// 使用 PBKDF2 计算哈希
|
||||
using var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations, HashAlgorithmName.SHA256);
|
||||
byte[] hash = pbkdf2.GetBytes(hashSize);
|
||||
|
||||
// 格式: $pbkdf2-sha256$迭代次数$Salt$Hash
|
||||
return $"$pbkdf2-sha256${iterations}${Convert.ToBase64String(salt)}${Convert.ToBase64String(hash)}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证密码
|
||||
/// </summary>
|
||||
/// <param name="password">要验证的密码</param>
|
||||
/// <param name="hashedPassword">存储的哈希密码</param>
|
||||
/// <returns>密码是否匹配</returns>
|
||||
public static bool VerifyPassword(string password, string hashedPassword)
|
||||
{
|
||||
if (string.IsNullOrEmpty(password))
|
||||
throw new ArgumentNullException(nameof(password));
|
||||
if (string.IsNullOrEmpty(hashedPassword))
|
||||
throw new ArgumentNullException(nameof(hashedPassword));
|
||||
|
||||
try
|
||||
{
|
||||
// 解析哈希字符串
|
||||
var parts = hashedPassword.Split('$');
|
||||
if (parts.Length != 5 || parts[1] != "pbkdf2-sha256")
|
||||
return false;
|
||||
|
||||
int iterations = int.Parse(parts[2]);
|
||||
byte[] salt = Convert.FromBase64String(parts[3]);
|
||||
byte[] storedHash = Convert.FromBase64String(parts[4]);
|
||||
|
||||
// 计算输入密码的哈希
|
||||
using var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations, HashAlgorithmName.SHA256);
|
||||
byte[] computedHash = pbkdf2.GetBytes(storedHash.Length);
|
||||
|
||||
// 常量时间比较,防止时序攻击
|
||||
return CryptographicOperations.FixedTimeEquals(storedHash, computedHash);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region HMAC 签名
|
||||
|
||||
/// <summary>
|
||||
/// 使用 HMAC-SHA256 计算签名
|
||||
/// </summary>
|
||||
/// <param name="message">消息</param>
|
||||
/// <param name="secret">密钥</param>
|
||||
/// <returns>十六进制签名字符串</returns>
|
||||
public static string HmacSha256(string message, string secret)
|
||||
{
|
||||
if (string.IsNullOrEmpty(message))
|
||||
throw new ArgumentNullException(nameof(message));
|
||||
if (string.IsNullOrEmpty(secret))
|
||||
throw new ArgumentNullException(nameof(secret));
|
||||
|
||||
byte[] keyBytes = Encoding.UTF8.GetBytes(secret);
|
||||
byte[] messageBytes = Encoding.UTF8.GetBytes(message);
|
||||
|
||||
using var hmac = new HMACSHA256(keyBytes);
|
||||
byte[] hashBytes = hmac.ComputeHash(messageBytes);
|
||||
|
||||
return BytesToHex(hashBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证 HMAC-SHA256 签名
|
||||
/// </summary>
|
||||
/// <param name="message">消息</param>
|
||||
/// <param name="signature">签名</param>
|
||||
/// <param name="secret">密钥</param>
|
||||
/// <returns>签名是否有效</returns>
|
||||
public static bool VerifyHmacSha256(string message, string signature, string secret)
|
||||
{
|
||||
if (string.IsNullOrEmpty(message))
|
||||
throw new ArgumentNullException(nameof(message));
|
||||
if (string.IsNullOrEmpty(signature))
|
||||
throw new ArgumentNullException(nameof(signature));
|
||||
if (string.IsNullOrEmpty(secret))
|
||||
throw new ArgumentNullException(nameof(secret));
|
||||
|
||||
var computedSignature = HmacSha256(message, secret);
|
||||
return string.Equals(computedSignature, signature, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 HMAC-SHA512 计算签名
|
||||
/// </summary>
|
||||
/// <param name="message">消息</param>
|
||||
/// <param name="secret">密钥</param>
|
||||
/// <returns>十六进制签名字符串</returns>
|
||||
public static string HmacSha512(string message, string secret)
|
||||
{
|
||||
if (string.IsNullOrEmpty(message))
|
||||
throw new ArgumentNullException(nameof(message));
|
||||
if (string.IsNullOrEmpty(secret))
|
||||
throw new ArgumentNullException(nameof(secret));
|
||||
|
||||
byte[] keyBytes = Encoding.UTF8.GetBytes(secret);
|
||||
byte[] messageBytes = Encoding.UTF8.GetBytes(message);
|
||||
|
||||
using var hmac = new HMACSHA512(keyBytes);
|
||||
byte[] hashBytes = hmac.ComputeHash(messageBytes);
|
||||
|
||||
return BytesToHex(hashBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证 HMAC-SHA512 签名
|
||||
/// </summary>
|
||||
/// <param name="message">消息</param>
|
||||
/// <param name="signature">签名</param>
|
||||
/// <param name="secret">密钥</param>
|
||||
/// <returns>签名是否有效</returns>
|
||||
public static bool VerifyHmacSha512(string message, string signature, string secret)
|
||||
{
|
||||
if (string.IsNullOrEmpty(message))
|
||||
throw new ArgumentNullException(nameof(message));
|
||||
if (string.IsNullOrEmpty(signature))
|
||||
throw new ArgumentNullException(nameof(signature));
|
||||
if (string.IsNullOrEmpty(secret))
|
||||
throw new ArgumentNullException(nameof(secret));
|
||||
|
||||
var computedSignature = HmacSha512(message, secret);
|
||||
return string.Equals(computedSignature, signature, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 辅助方法
|
||||
|
||||
/// <summary>
|
||||
/// 生成随机字符串
|
||||
/// </summary>
|
||||
/// <param name="length">长度</param>
|
||||
/// <param name="useSpecialChars">是否包含特殊字符</param>
|
||||
/// <returns>随机字符串</returns>
|
||||
public static string GenerateRandomString(int length, bool useSpecialChars = false)
|
||||
{
|
||||
const string letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
const string numbers = "0123456789";
|
||||
const string special = "!@#$%^&*()_+-=[]{}|;:,.<>?";
|
||||
|
||||
var chars = letters + numbers;
|
||||
if (useSpecialChars)
|
||||
chars += special;
|
||||
|
||||
var result = new char[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
result[i] = chars[RandomNumberGenerator.GetInt32(chars.Length)];
|
||||
}
|
||||
|
||||
return new string(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成随机密码
|
||||
/// </summary>
|
||||
/// <param name="length">密码长度(默认 16)</param>
|
||||
/// <param name="requireDigit">是否必须包含数字</param>
|
||||
/// <param name="requireLowercase">是否必须包含小写字母</param>
|
||||
/// <param name="requireUppercase">是否必须包含大写字母</param>
|
||||
/// <param name="requireSpecialChar">是否必须包含特殊字符</param>
|
||||
/// <returns>随机密码</returns>
|
||||
public static string GeneratePassword(
|
||||
int length = 16,
|
||||
bool requireDigit = true,
|
||||
bool requireLowercase = true,
|
||||
bool requireUppercase = true,
|
||||
bool requireSpecialChar = true)
|
||||
{
|
||||
if (length < 4)
|
||||
throw new ArgumentException("Password length must be at least 4", nameof(length));
|
||||
|
||||
const string lowercase = "abcdefghijklmnopqrstuvwxyz";
|
||||
const string uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
const string digits = "0123456789";
|
||||
const string special = "!@#$%^&*()_+-=[]{}|;:,.<>?";
|
||||
|
||||
var chars = "";
|
||||
var password = new List<char>();
|
||||
|
||||
// 确保满足要求
|
||||
if (requireLowercase)
|
||||
{
|
||||
chars += lowercase;
|
||||
password.Add(lowercase[RandomNumberGenerator.GetInt32(lowercase.Length)]);
|
||||
}
|
||||
if (requireUppercase)
|
||||
{
|
||||
chars += uppercase;
|
||||
password.Add(uppercase[RandomNumberGenerator.GetInt32(uppercase.Length)]);
|
||||
}
|
||||
if (requireDigit)
|
||||
{
|
||||
chars += digits;
|
||||
password.Add(digits[RandomNumberGenerator.GetInt32(digits.Length)]);
|
||||
}
|
||||
if (requireSpecialChar)
|
||||
{
|
||||
chars += special;
|
||||
password.Add(special[RandomNumberGenerator.GetInt32(special.Length)]);
|
||||
}
|
||||
|
||||
// 填充剩余长度
|
||||
for (int i = password.Count; i < length; i++)
|
||||
{
|
||||
password.Add(chars[RandomNumberGenerator.GetInt32(chars.Length)]);
|
||||
}
|
||||
|
||||
// 随机打乱
|
||||
for (int i = password.Count - 1; i > 0; i--)
|
||||
{
|
||||
int j = RandomNumberGenerator.GetInt32(i + 1);
|
||||
(password[i], password[j]) = (password[j], password[i]);
|
||||
}
|
||||
|
||||
return new string(password.ToArray());
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,402 @@
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Common.Utils;
|
||||
|
||||
/// <summary>
|
||||
/// 枚举选项(用于下拉列表等场景)
|
||||
/// </summary>
|
||||
public class EnumOption
|
||||
{
|
||||
/// <summary>
|
||||
/// 枚举值(整数)
|
||||
/// </summary>
|
||||
public int Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 枚举名称
|
||||
/// </summary>
|
||||
public string Name { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 枚举描述(来自 Description 特性)
|
||||
/// </summary>
|
||||
public string Description { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 枚举显示文本(优先使用 Description,否则使用 Name)
|
||||
/// </summary>
|
||||
public string DisplayText => string.IsNullOrEmpty(Description) ? Name : Description;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 枚举工具类
|
||||
/// 提供枚举解析、描述获取、标志位操作等功能
|
||||
/// </summary>
|
||||
public static class EnumHelper
|
||||
{
|
||||
#region 枚举解析
|
||||
|
||||
/// <summary>
|
||||
/// 从字符串解析枚举值
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">枚举类型</typeparam>
|
||||
/// <param name="value">字符串值</param>
|
||||
/// <param name="ignoreCase">是否忽略大小写(默认 true)</param>
|
||||
/// <returns>枚举值,解析失败返回 null</returns>
|
||||
public static TEnum? Parse<TEnum>(string? value, bool ignoreCase = true) where TEnum : struct, Enum
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return null;
|
||||
|
||||
if (Enum.TryParse<TEnum>(value, ignoreCase, out var result))
|
||||
return result;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从字符串解析枚举值(带默认值)
|
||||
/// </summary>
|
||||
public static TEnum ParseOrDefault<TEnum>(string? value, TEnum defaultValue, bool ignoreCase = true)
|
||||
where TEnum : struct, Enum
|
||||
{
|
||||
return Parse<TEnum>(value, ignoreCase) ?? defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从整数值解析枚举
|
||||
/// </summary>
|
||||
public static TEnum? ParseFromInt<TEnum>(int value) where TEnum : struct, Enum
|
||||
{
|
||||
if (Enum.IsDefined(typeof(TEnum), value))
|
||||
return (TEnum)(object)value;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从描述文本解析枚举值
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">枚举类型</typeparam>
|
||||
/// <param name="description">描述文本</param>
|
||||
/// <returns>枚举值,未找到返回 null</returns>
|
||||
public static TEnum? ParseFromDescription<TEnum>(string? description) where TEnum : struct, Enum
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(description))
|
||||
return null;
|
||||
|
||||
foreach (var field in typeof(TEnum).GetFields())
|
||||
{
|
||||
if (field.GetCustomAttribute<DescriptionAttribute>() is { } attribute)
|
||||
{
|
||||
if (attribute.Description.Equals(description, StringComparison.OrdinalIgnoreCase))
|
||||
return (TEnum)field.GetValue(null)!;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 枚举描述
|
||||
|
||||
/// <summary>
|
||||
/// 获取枚举的描述文本(来自 Description 特性)
|
||||
/// </summary>
|
||||
/// <param name="value">枚举值</param>
|
||||
/// <returns>描述文本,如果没有 Description 特性则返回枚举名称</returns>
|
||||
public static string GetDescription(this Enum value)
|
||||
{
|
||||
var field = value.GetType().GetField(value.ToString());
|
||||
if (field == null)
|
||||
return value.ToString();
|
||||
|
||||
if (field.GetCustomAttribute<DescriptionAttribute>() is { } attribute)
|
||||
return attribute.Description;
|
||||
|
||||
return value.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取枚举类型的所有描述文本
|
||||
/// </summary>
|
||||
public static Dictionary<TEnum, string> GetAllDescriptions<TEnum>() where TEnum : struct, Enum
|
||||
{
|
||||
return Enum.GetValues<TEnum>()
|
||||
.ToDictionary(value => value, value => ((Enum)(object)value).GetDescription());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 枚举列表
|
||||
|
||||
/// <summary>
|
||||
/// 获取枚举的所有值
|
||||
/// </summary>
|
||||
public static TEnum[] GetValues<TEnum>() where TEnum : struct, Enum
|
||||
{
|
||||
return Enum.GetValues<TEnum>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取枚举的所有名称
|
||||
/// </summary>
|
||||
public static string[] GetNames<TEnum>() where TEnum : struct, Enum
|
||||
{
|
||||
return Enum.GetNames<TEnum>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取枚举选项列表(用于下拉框等)
|
||||
/// </summary>
|
||||
public static List<EnumOption> GetOptions<TEnum>() where TEnum : struct, Enum
|
||||
{
|
||||
return Enum.GetValues<TEnum>()
|
||||
.Select(value => new EnumOption
|
||||
{
|
||||
Value = Convert.ToInt32(value),
|
||||
Name = value.ToString(),
|
||||
Description = ((Enum)(object)value).GetDescription()
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取枚举的键值对字典(值 -> 名称)
|
||||
/// </summary>
|
||||
public static Dictionary<int, string> GetDictionary<TEnum>() where TEnum : struct, Enum
|
||||
{
|
||||
return Enum.GetValues<TEnum>()
|
||||
.ToDictionary(value => Convert.ToInt32(value), value => value.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取枚举的键值对字典(值 -> 描述)
|
||||
/// </summary>
|
||||
public static Dictionary<int, string> GetDictionaryWithDescription<TEnum>() where TEnum : struct, Enum
|
||||
{
|
||||
return Enum.GetValues<TEnum>()
|
||||
.ToDictionary(
|
||||
value => Convert.ToInt32(value),
|
||||
value => ((Enum)(object)value).GetDescription()
|
||||
);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 枚举验证
|
||||
|
||||
/// <summary>
|
||||
/// 判断枚举值是否已定义
|
||||
/// </summary>
|
||||
public static bool IsDefined<TEnum>(int value) where TEnum : struct, Enum
|
||||
{
|
||||
return Enum.IsDefined(typeof(TEnum), value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断枚举值是否已定义
|
||||
/// </summary>
|
||||
public static bool IsDefined<TEnum>(string name, bool ignoreCase = true) where TEnum : struct, Enum
|
||||
{
|
||||
return Enum.TryParse<TEnum>(name, ignoreCase, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断枚举值是否已定义
|
||||
/// </summary>
|
||||
public static bool IsDefined<TEnum>(TEnum value) where TEnum : struct, Enum
|
||||
{
|
||||
return Enum.IsDefined(typeof(TEnum), value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 标志位枚举操作
|
||||
|
||||
/// <summary>
|
||||
/// 判断枚举是否包含指定标志位
|
||||
/// </summary>
|
||||
public static bool HasFlag<TEnum>(this TEnum value, TEnum flag) where TEnum : struct, Enum
|
||||
{
|
||||
return ((Enum)(object)value).HasFlag((Enum)(object)flag);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加标志位
|
||||
/// </summary>
|
||||
public static TEnum AddFlag<TEnum>(this TEnum value, TEnum flag) where TEnum : struct, Enum
|
||||
{
|
||||
int intValue = Convert.ToInt32(value);
|
||||
int intFlag = Convert.ToInt32(flag);
|
||||
return (TEnum)(object)(intValue | intFlag);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除标志位
|
||||
/// </summary>
|
||||
public static TEnum RemoveFlag<TEnum>(this TEnum value, TEnum flag) where TEnum : struct, Enum
|
||||
{
|
||||
int intValue = Convert.ToInt32(value);
|
||||
int intFlag = Convert.ToInt32(flag);
|
||||
return (TEnum)(object)(intValue & ~intFlag);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换标志位(有则移除,无则添加)
|
||||
/// </summary>
|
||||
public static TEnum ToggleFlag<TEnum>(this TEnum value, TEnum flag) where TEnum : struct, Enum
|
||||
{
|
||||
int intValue = Convert.ToInt32(value);
|
||||
int intFlag = Convert.ToInt32(flag);
|
||||
return (TEnum)(object)(intValue ^ intFlag);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取枚举中设置的所有标志位
|
||||
/// </summary>
|
||||
public static IEnumerable<TEnum> GetFlags<TEnum>(this TEnum value) where TEnum : struct, Enum
|
||||
{
|
||||
foreach (TEnum flag in Enum.GetValues<TEnum>())
|
||||
{
|
||||
if (value.HasFlag(flag))
|
||||
yield return flag;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从多个标志位组合成枚举值
|
||||
/// </summary>
|
||||
public static TEnum CombineFlags<TEnum>(params TEnum[] flags) where TEnum : struct, Enum
|
||||
{
|
||||
int result = 0;
|
||||
foreach (var flag in flags)
|
||||
{
|
||||
result |= Convert.ToInt32(flag);
|
||||
}
|
||||
return (TEnum)(object)result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 枚举转换
|
||||
|
||||
/// <summary>
|
||||
/// 枚举转整数
|
||||
/// </summary>
|
||||
public static int ToInt<TEnum>(this TEnum value) where TEnum : struct, Enum
|
||||
{
|
||||
return Convert.ToInt32(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 枚举转长整数
|
||||
/// </summary>
|
||||
public static long ToLong<TEnum>(this TEnum value) where TEnum : struct, Enum
|
||||
{
|
||||
return Convert.ToInt64(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 整数转枚举
|
||||
/// </summary>
|
||||
public static TEnum ToEnum<TEnum>(this int value) where TEnum : struct, Enum
|
||||
{
|
||||
return (TEnum)(object)value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 枚举信息
|
||||
|
||||
/// <summary>
|
||||
/// 获取枚举的数量
|
||||
/// </summary>
|
||||
public static int GetCount<TEnum>() where TEnum : struct, Enum
|
||||
{
|
||||
return Enum.GetValues<TEnum>().Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取枚举的最小值
|
||||
/// </summary>
|
||||
public static TEnum GetMinValue<TEnum>() where TEnum : struct, Enum
|
||||
{
|
||||
return Enum.GetValues<TEnum>().MinBy(v => Convert.ToInt32(v));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取枚举的最大值
|
||||
/// </summary>
|
||||
public static TEnum GetMaxValue<TEnum>() where TEnum : struct, Enum
|
||||
{
|
||||
return Enum.GetValues<TEnum>().MaxBy(v => Convert.ToInt32(v));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断枚举类型是否为标志位枚举(有 Flags 特性)
|
||||
/// </summary>
|
||||
public static bool IsFlagsEnum<TEnum>() where TEnum : struct, Enum
|
||||
{
|
||||
return typeof(TEnum).GetCustomAttribute<FlagsAttribute>() != null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 枚举比较
|
||||
|
||||
/// <summary>
|
||||
/// 比较两个枚举值是否相等
|
||||
/// </summary>
|
||||
public static bool Equals<TEnum>(TEnum value1, TEnum value2) where TEnum : struct, Enum
|
||||
{
|
||||
return EqualityComparer<TEnum>.Default.Equals(value1, value2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断枚举值是否在指定列表中
|
||||
/// </summary>
|
||||
public static bool In<TEnum>(this TEnum value, params TEnum[] values) where TEnum : struct, Enum
|
||||
{
|
||||
return values.Contains(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断枚举值是否不在指定列表中
|
||||
/// </summary>
|
||||
public static bool NotIn<TEnum>(this TEnum value, params TEnum[] values) where TEnum : struct, Enum
|
||||
{
|
||||
return !values.Contains(value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 随机枚举
|
||||
|
||||
/// <summary>
|
||||
/// 获取随机枚举值
|
||||
/// </summary>
|
||||
public static TEnum GetRandom<TEnum>() where TEnum : struct, Enum
|
||||
{
|
||||
var values = Enum.GetValues<TEnum>();
|
||||
var random = new Random();
|
||||
return values[random.Next(values.Length)];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取随机枚举值(排除指定值)
|
||||
/// </summary>
|
||||
public static TEnum GetRandomExcept<TEnum>(params TEnum[] except) where TEnum : struct, Enum
|
||||
{
|
||||
var values = Enum.GetValues<TEnum>().Except(except).ToArray();
|
||||
if (values.Length == 0)
|
||||
throw new InvalidOperationException("No available enum values after exclusion");
|
||||
|
||||
var random = new Random();
|
||||
return values[random.Next(values.Length)];
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,873 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Common.Utils;
|
||||
|
||||
/// <summary>
|
||||
/// 文件操作工具类
|
||||
/// 提供文件读写、复制、移动、删除、搜索、大小格式化、哈希计算等功能
|
||||
/// </summary>
|
||||
public static class FileHelper
|
||||
{
|
||||
#region 文件读取
|
||||
|
||||
/// <summary>
|
||||
/// 读取文件所有文本内容
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
/// <param name="encoding">编码方式(默认 UTF-8)</param>
|
||||
/// <returns>文件内容</returns>
|
||||
public static string ReadAllText(string filePath, Encoding? encoding = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
throw new FileNotFoundException($"File not found: {filePath}");
|
||||
|
||||
return File.ReadAllText(filePath, encoding ?? Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取文件所有行
|
||||
/// </summary>
|
||||
public static string[] ReadAllLines(string filePath, Encoding? encoding = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
throw new FileNotFoundException($"File not found: {filePath}");
|
||||
|
||||
return File.ReadAllLines(filePath, encoding ?? Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取文件所有字节
|
||||
/// </summary>
|
||||
public static byte[] ReadAllBytes(string filePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
throw new FileNotFoundException($"File not found: {filePath}");
|
||||
|
||||
return File.ReadAllBytes(filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 逐行读取大文件(不会一次性加载到内存)
|
||||
/// </summary>
|
||||
public static IEnumerable<string> ReadLines(string filePath, Encoding? encoding = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
throw new FileNotFoundException($"File not found: {filePath}");
|
||||
|
||||
return File.ReadLines(filePath, encoding ?? Encoding.UTF8);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 文件写入
|
||||
|
||||
/// <summary>
|
||||
/// 写入文本到文件(覆盖原内容)
|
||||
/// </summary>
|
||||
public static void WriteAllText(string filePath, string content, Encoding? encoding = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
|
||||
EnsureDirectoryExists(filePath);
|
||||
File.WriteAllText(filePath, content, encoding ?? Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入文本到文件(追加内容)
|
||||
/// </summary>
|
||||
public static void AppendAllText(string filePath, string content, Encoding? encoding = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
|
||||
EnsureDirectoryExists(filePath);
|
||||
File.AppendAllText(filePath, content, encoding ?? Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入行到文件(覆盖原内容)
|
||||
/// </summary>
|
||||
public static void WriteAllLines(string filePath, IEnumerable<string> lines, Encoding? encoding = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
|
||||
EnsureDirectoryExists(filePath);
|
||||
File.WriteAllLines(filePath, lines, encoding ?? Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入字节到文件(覆盖原内容)
|
||||
/// </summary>
|
||||
public static void WriteAllBytes(string filePath, byte[] bytes)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
|
||||
EnsureDirectoryExists(filePath);
|
||||
File.WriteAllBytes(filePath, bytes);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 文件操作
|
||||
|
||||
/// <summary>
|
||||
/// 复制文件
|
||||
/// </summary>
|
||||
/// <param name="sourceFilePath">源文件路径</param>
|
||||
/// <param name="destFilePath">目标文件路径</param>
|
||||
/// <param name="overwrite">是否覆盖已存在的文件(默认 true)</param>
|
||||
public static void CopyFile(string sourceFilePath, string destFilePath, bool overwrite = true)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sourceFilePath))
|
||||
throw new ArgumentNullException(nameof(sourceFilePath));
|
||||
if (string.IsNullOrWhiteSpace(destFilePath))
|
||||
throw new ArgumentNullException(nameof(destFilePath));
|
||||
|
||||
if (!File.Exists(sourceFilePath))
|
||||
throw new FileNotFoundException($"Source file not found: {sourceFilePath}");
|
||||
|
||||
EnsureDirectoryExists(destFilePath);
|
||||
File.Copy(sourceFilePath, destFilePath, overwrite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移动文件
|
||||
/// </summary>
|
||||
public static void MoveFile(string sourceFilePath, string destFilePath, bool overwrite = true)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sourceFilePath))
|
||||
throw new ArgumentNullException(nameof(sourceFilePath));
|
||||
if (string.IsNullOrWhiteSpace(destFilePath))
|
||||
throw new ArgumentNullException(nameof(destFilePath));
|
||||
|
||||
if (!File.Exists(sourceFilePath))
|
||||
throw new FileNotFoundException($"Source file not found: {sourceFilePath}");
|
||||
|
||||
EnsureDirectoryExists(destFilePath);
|
||||
File.Move(sourceFilePath, destFilePath, overwrite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除文件(如果存在)
|
||||
/// </summary>
|
||||
public static void DeleteFile(string filePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
|
||||
if (File.Exists(filePath))
|
||||
File.Delete(filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断文件是否存在
|
||||
/// </summary>
|
||||
public static bool FileExists(string filePath)
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(filePath) && File.Exists(filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重命名文件
|
||||
/// </summary>
|
||||
public static void RenameFile(string filePath, string newFileName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
if (string.IsNullOrWhiteSpace(newFileName))
|
||||
throw new ArgumentNullException(nameof(newFileName));
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
throw new FileNotFoundException($"File not found: {filePath}");
|
||||
|
||||
var directory = Path.GetDirectoryName(filePath);
|
||||
var newFilePath = Path.Combine(directory ?? "", newFileName);
|
||||
File.Move(filePath, newFilePath);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 目录操作
|
||||
|
||||
/// <summary>
|
||||
/// 创建目录(如果不存在)
|
||||
/// </summary>
|
||||
public static void CreateDirectory(string directoryPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(directoryPath))
|
||||
throw new ArgumentNullException(nameof(directoryPath));
|
||||
|
||||
if (!Directory.Exists(directoryPath))
|
||||
Directory.CreateDirectory(directoryPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除目录(包括子目录和文件)
|
||||
/// </summary>
|
||||
public static void DeleteDirectory(string directoryPath, bool recursive = true)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(directoryPath))
|
||||
throw new ArgumentNullException(nameof(directoryPath));
|
||||
|
||||
if (Directory.Exists(directoryPath))
|
||||
Directory.Delete(directoryPath, recursive);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断目录是否存在
|
||||
/// </summary>
|
||||
public static bool DirectoryExists(string directoryPath)
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(directoryPath) && Directory.Exists(directoryPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确保文件所在目录存在(不存在则创建)
|
||||
/// </summary>
|
||||
public static void EnsureDirectoryExists(string filePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
return;
|
||||
|
||||
var directory = Path.GetDirectoryName(filePath);
|
||||
if (!string.IsNullOrWhiteSpace(directory) && !Directory.Exists(directory))
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 复制目录(包括所有子目录和文件)
|
||||
/// </summary>
|
||||
public static void CopyDirectory(string sourceDir, string destDir, bool recursive = true)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sourceDir))
|
||||
throw new ArgumentNullException(nameof(sourceDir));
|
||||
if (string.IsNullOrWhiteSpace(destDir))
|
||||
throw new ArgumentNullException(nameof(destDir));
|
||||
|
||||
var dir = new DirectoryInfo(sourceDir);
|
||||
if (!dir.Exists)
|
||||
throw new DirectoryNotFoundException($"Source directory not found: {sourceDir}");
|
||||
|
||||
// 创建目标目录
|
||||
Directory.CreateDirectory(destDir);
|
||||
|
||||
// 复制文件
|
||||
foreach (var file in dir.GetFiles())
|
||||
{
|
||||
var targetFilePath = Path.Combine(destDir, file.Name);
|
||||
file.CopyTo(targetFilePath, true);
|
||||
}
|
||||
|
||||
// 递归复制子目录
|
||||
if (recursive)
|
||||
{
|
||||
foreach (var subDir in dir.GetDirectories())
|
||||
{
|
||||
var targetDirPath = Path.Combine(destDir, subDir.Name);
|
||||
CopyDirectory(subDir.FullName, targetDirPath, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移动目录
|
||||
/// </summary>
|
||||
public static void MoveDirectory(string sourceDir, string destDir)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sourceDir))
|
||||
throw new ArgumentNullException(nameof(sourceDir));
|
||||
if (string.IsNullOrWhiteSpace(destDir))
|
||||
throw new ArgumentNullException(nameof(destDir));
|
||||
|
||||
if (!Directory.Exists(sourceDir))
|
||||
throw new DirectoryNotFoundException($"Source directory not found: {sourceDir}");
|
||||
|
||||
Directory.Move(sourceDir, destDir);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 文件搜索
|
||||
|
||||
/// <summary>
|
||||
/// 获取目录下的所有文件
|
||||
/// </summary>
|
||||
/// <param name="directoryPath">目录路径</param>
|
||||
/// <param name="searchPattern">搜索模式(例如 "*.txt")</param>
|
||||
/// <param name="recursive">是否搜索子目录(默认 false)</param>
|
||||
/// <returns>文件路径列表</returns>
|
||||
public static string[] GetFiles(string directoryPath, string searchPattern = "*", bool recursive = false)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(directoryPath))
|
||||
throw new ArgumentNullException(nameof(directoryPath));
|
||||
|
||||
if (!Directory.Exists(directoryPath))
|
||||
return Array.Empty<string>();
|
||||
|
||||
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
|
||||
return Directory.GetFiles(directoryPath, searchPattern, searchOption);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取目录下的所有子目录
|
||||
/// </summary>
|
||||
public static string[] GetDirectories(string directoryPath, string searchPattern = "*", bool recursive = false)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(directoryPath))
|
||||
throw new ArgumentNullException(nameof(directoryPath));
|
||||
|
||||
if (!Directory.Exists(directoryPath))
|
||||
return Array.Empty<string>();
|
||||
|
||||
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
|
||||
return Directory.GetDirectories(directoryPath, searchPattern, searchOption);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 搜索文件(支持多个模式)
|
||||
/// </summary>
|
||||
public static IEnumerable<string> SearchFiles(string directoryPath, string[] searchPatterns, bool recursive = false)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(directoryPath))
|
||||
throw new ArgumentNullException(nameof(directoryPath));
|
||||
|
||||
if (!Directory.Exists(directoryPath))
|
||||
return Enumerable.Empty<string>();
|
||||
|
||||
var files = new List<string>();
|
||||
foreach (var pattern in searchPatterns)
|
||||
{
|
||||
files.AddRange(GetFiles(directoryPath, pattern, recursive));
|
||||
}
|
||||
|
||||
return files.Distinct();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 文件信息
|
||||
|
||||
/// <summary>
|
||||
/// 获取文件大小(字节)
|
||||
/// </summary>
|
||||
public static long GetFileSize(string filePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
throw new FileNotFoundException($"File not found: {filePath}");
|
||||
|
||||
return new FileInfo(filePath).Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 格式化文件大小(自动选择单位:B, KB, MB, GB, TB)
|
||||
/// </summary>
|
||||
public static string FormatFileSize(long bytes, int decimalPlaces = 2)
|
||||
{
|
||||
string[] units = { "B", "KB", "MB", "GB", "TB", "PB" };
|
||||
|
||||
if (bytes == 0)
|
||||
return $"0 {units[0]}";
|
||||
|
||||
var unitIndex = 0;
|
||||
double size = bytes;
|
||||
|
||||
while (size >= 1024 && unitIndex < units.Length - 1)
|
||||
{
|
||||
size /= 1024;
|
||||
unitIndex++;
|
||||
}
|
||||
|
||||
return $"{Math.Round(size, decimalPlaces)} {units[unitIndex]}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取文件扩展名(包含点号,例如 ".txt")
|
||||
/// </summary>
|
||||
public static string GetExtension(string filePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
return string.Empty;
|
||||
|
||||
return Path.GetExtension(filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取文件名(不含扩展名)
|
||||
/// </summary>
|
||||
public static string GetFileNameWithoutExtension(string filePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
return string.Empty;
|
||||
|
||||
return Path.GetFileNameWithoutExtension(filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取文件名(含扩展名)
|
||||
/// </summary>
|
||||
public static string GetFileName(string filePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
return string.Empty;
|
||||
|
||||
return Path.GetFileName(filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取文件所在目录路径
|
||||
/// </summary>
|
||||
public static string? GetDirectoryName(string filePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
return null;
|
||||
|
||||
return Path.GetDirectoryName(filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取文件创建时间
|
||||
/// </summary>
|
||||
public static DateTime GetCreationTime(string filePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
throw new FileNotFoundException($"File not found: {filePath}");
|
||||
|
||||
return File.GetCreationTime(filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取文件最后修改时间
|
||||
/// </summary>
|
||||
public static DateTime GetLastWriteTime(string filePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
throw new FileNotFoundException($"File not found: {filePath}");
|
||||
|
||||
return File.GetLastWriteTime(filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取文件最后访问时间
|
||||
/// </summary>
|
||||
public static DateTime GetLastAccessTime(string filePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
throw new FileNotFoundException($"File not found: {filePath}");
|
||||
|
||||
return File.GetLastAccessTime(filePath);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 文件哈希
|
||||
|
||||
/// <summary>
|
||||
/// 计算文件的 MD5 哈希
|
||||
/// </summary>
|
||||
public static string GetFileMd5(string filePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
throw new FileNotFoundException($"File not found: {filePath}");
|
||||
|
||||
using var md5 = MD5.Create();
|
||||
using var stream = File.OpenRead(filePath);
|
||||
var hash = md5.ComputeHash(stream);
|
||||
return Convert.ToHexString(hash).ToLowerInvariant();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算文件的 SHA256 哈希
|
||||
/// </summary>
|
||||
public static string GetFileSha256(string filePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
throw new FileNotFoundException($"File not found: {filePath}");
|
||||
|
||||
using var sha256 = SHA256.Create();
|
||||
using var stream = File.OpenRead(filePath);
|
||||
var hash = sha256.ComputeHash(stream);
|
||||
return Convert.ToHexString(hash).ToLowerInvariant();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算文件的 SHA512 哈希
|
||||
/// </summary>
|
||||
public static string GetFileSha512(string filePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
throw new FileNotFoundException($"File not found: {filePath}");
|
||||
|
||||
using var sha512 = SHA512.Create();
|
||||
using var stream = File.OpenRead(filePath);
|
||||
var hash = sha512.ComputeHash(stream);
|
||||
return Convert.ToHexString(hash).ToLowerInvariant();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 比较两个文件是否相同(通过 SHA256 哈希)
|
||||
/// </summary>
|
||||
public static bool AreFilesEqual(string filePath1, string filePath2)
|
||||
{
|
||||
if (!File.Exists(filePath1) || !File.Exists(filePath2))
|
||||
return false;
|
||||
|
||||
// 先比较大小
|
||||
if (GetFileSize(filePath1) != GetFileSize(filePath2))
|
||||
return false;
|
||||
|
||||
// 再比较哈希
|
||||
return GetFileSha256(filePath1) == GetFileSha256(filePath2);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 路径操作
|
||||
|
||||
/// <summary>
|
||||
/// 组合路径
|
||||
/// </summary>
|
||||
public static string CombinePaths(params string[] paths)
|
||||
{
|
||||
if (paths == null || paths.Length == 0)
|
||||
throw new ArgumentException("At least one path is required", nameof(paths));
|
||||
|
||||
return Path.Combine(paths);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 规范化路径(转换为绝对路径,统一分隔符)
|
||||
/// </summary>
|
||||
public static string NormalizePath(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
return string.Empty;
|
||||
|
||||
return Path.GetFullPath(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取相对路径
|
||||
/// </summary>
|
||||
public static string GetRelativePath(string basePath, string targetPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(basePath))
|
||||
throw new ArgumentNullException(nameof(basePath));
|
||||
if (string.IsNullOrWhiteSpace(targetPath))
|
||||
throw new ArgumentNullException(nameof(targetPath));
|
||||
|
||||
return Path.GetRelativePath(basePath, targetPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断路径是否为绝对路径
|
||||
/// </summary>
|
||||
public static bool IsAbsolutePath(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
return false;
|
||||
|
||||
return Path.IsPathFullyQualified(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成唯一文件名(如果文件已存在,添加数字后缀)
|
||||
/// </summary>
|
||||
public static string GetUniqueFilePath(string filePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
return filePath;
|
||||
|
||||
var directory = Path.GetDirectoryName(filePath) ?? "";
|
||||
var fileNameWithoutExt = Path.GetFileNameWithoutExtension(filePath);
|
||||
var extension = Path.GetExtension(filePath);
|
||||
|
||||
var counter = 1;
|
||||
string newFilePath;
|
||||
|
||||
do
|
||||
{
|
||||
var newFileName = $"{fileNameWithoutExt} ({counter}){extension}";
|
||||
newFilePath = Path.Combine(directory, newFileName);
|
||||
counter++;
|
||||
} while (File.Exists(newFilePath));
|
||||
|
||||
return newFilePath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取临时文件路径
|
||||
/// </summary>
|
||||
public static string GetTempFilePath(string? extension = null)
|
||||
{
|
||||
var tempPath = Path.GetTempFileName();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(extension))
|
||||
{
|
||||
if (!extension.StartsWith('.'))
|
||||
extension = "." + extension;
|
||||
|
||||
var newPath = Path.ChangeExtension(tempPath, extension);
|
||||
File.Move(tempPath, newPath);
|
||||
return newPath;
|
||||
}
|
||||
|
||||
return tempPath;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 文件类型判断
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否为文本文件(根据扩展名)
|
||||
/// </summary>
|
||||
public static bool IsTextFile(string filePath)
|
||||
{
|
||||
var extension = GetExtension(filePath).ToLowerInvariant();
|
||||
string[] textExtensions = { ".txt", ".cs", ".xml", ".json", ".html", ".css", ".js", ".ts", ".md", ".log", ".csv", ".sql" };
|
||||
return textExtensions.Contains(extension);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否为图片文件(根据扩展名)
|
||||
/// </summary>
|
||||
public static bool IsImageFile(string filePath)
|
||||
{
|
||||
var extension = GetExtension(filePath).ToLowerInvariant();
|
||||
string[] imageExtensions = { ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".svg", ".webp", ".ico" };
|
||||
return imageExtensions.Contains(extension);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否为视频文件(根据扩展名)
|
||||
/// </summary>
|
||||
public static bool IsVideoFile(string filePath)
|
||||
{
|
||||
var extension = GetExtension(filePath).ToLowerInvariant();
|
||||
string[] videoExtensions = { ".mp4", ".avi", ".mkv", ".mov", ".wmv", ".flv", ".webm", ".m4v" };
|
||||
return videoExtensions.Contains(extension);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否为音频文件(根据扩展名)
|
||||
/// </summary>
|
||||
public static bool IsAudioFile(string filePath)
|
||||
{
|
||||
var extension = GetExtension(filePath).ToLowerInvariant();
|
||||
string[] audioExtensions = { ".mp3", ".wav", ".flac", ".aac", ".ogg", ".wma", ".m4a" };
|
||||
return audioExtensions.Contains(extension);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否为压缩文件(根据扩展名)
|
||||
/// </summary>
|
||||
public static bool IsArchiveFile(string filePath)
|
||||
{
|
||||
var extension = GetExtension(filePath).ToLowerInvariant();
|
||||
string[] archiveExtensions = { ".zip", ".rar", ".7z", ".tar", ".gz", ".bz2", ".xz" };
|
||||
return archiveExtensions.Contains(extension);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 文件监控
|
||||
|
||||
/// <summary>
|
||||
/// 创建文件监视器
|
||||
/// </summary>
|
||||
/// <param name="directoryPath">监视的目录</param>
|
||||
/// <param name="filter">文件过滤器(例如 "*.txt")</param>
|
||||
/// <param name="includeSubdirectories">是否监视子目录</param>
|
||||
/// <returns>文件监视器</returns>
|
||||
public static FileSystemWatcher CreateFileWatcher(
|
||||
string directoryPath,
|
||||
string filter = "*.*",
|
||||
bool includeSubdirectories = false)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(directoryPath))
|
||||
throw new ArgumentNullException(nameof(directoryPath));
|
||||
|
||||
if (!Directory.Exists(directoryPath))
|
||||
throw new DirectoryNotFoundException($"Directory not found: {directoryPath}");
|
||||
|
||||
var watcher = new FileSystemWatcher(directoryPath)
|
||||
{
|
||||
Filter = filter,
|
||||
IncludeSubdirectories = includeSubdirectories,
|
||||
NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.LastWrite
|
||||
};
|
||||
|
||||
return watcher;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 文件清理
|
||||
|
||||
/// <summary>
|
||||
/// 删除目录中的所有文件(保留目录结构)
|
||||
/// </summary>
|
||||
public static void ClearDirectory(string directoryPath, string searchPattern = "*", bool recursive = false)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(directoryPath))
|
||||
throw new ArgumentNullException(nameof(directoryPath));
|
||||
|
||||
if (!Directory.Exists(directoryPath))
|
||||
return;
|
||||
|
||||
var files = GetFiles(directoryPath, searchPattern, recursive);
|
||||
foreach (var file in files)
|
||||
{
|
||||
DeleteFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除超过指定天数的文件
|
||||
/// </summary>
|
||||
public static int DeleteOldFiles(string directoryPath, int daysOld, string searchPattern = "*", bool recursive = false)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(directoryPath))
|
||||
throw new ArgumentNullException(nameof(directoryPath));
|
||||
|
||||
if (!Directory.Exists(directoryPath))
|
||||
return 0;
|
||||
|
||||
var cutoffDate = DateTime.Now.AddDays(-daysOld);
|
||||
var files = GetFiles(directoryPath, searchPattern, recursive);
|
||||
var deletedCount = 0;
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (GetLastWriteTime(file) < cutoffDate)
|
||||
{
|
||||
DeleteFile(file);
|
||||
deletedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return deletedCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空回收站中的文件(Windows)
|
||||
/// </summary>
|
||||
public static void EmptyRecycleBin()
|
||||
{
|
||||
// 注意:这个功能需要 Windows API,在跨平台应用中需要特殊处理
|
||||
// 这里仅提供接口,实际实现需要根据平台调整
|
||||
throw new NotImplementedException("This feature requires platform-specific implementation");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 文件属性
|
||||
|
||||
/// <summary>
|
||||
/// 设置文件为只读
|
||||
/// </summary>
|
||||
public static void SetReadOnly(string filePath, bool isReadOnly = true)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
throw new FileNotFoundException($"File not found: {filePath}");
|
||||
|
||||
var fileInfo = new FileInfo(filePath);
|
||||
if (isReadOnly)
|
||||
fileInfo.Attributes |= FileAttributes.ReadOnly;
|
||||
else
|
||||
fileInfo.Attributes &= ~FileAttributes.ReadOnly;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断文件是否为只读
|
||||
/// </summary>
|
||||
public static bool IsReadOnly(string filePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
throw new FileNotFoundException($"File not found: {filePath}");
|
||||
|
||||
var fileInfo = new FileInfo(filePath);
|
||||
return (fileInfo.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置文件为隐藏
|
||||
/// </summary>
|
||||
public static void SetHidden(string filePath, bool isHidden = true)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
throw new FileNotFoundException($"File not found: {filePath}");
|
||||
|
||||
var fileInfo = new FileInfo(filePath);
|
||||
if (isHidden)
|
||||
fileInfo.Attributes |= FileAttributes.Hidden;
|
||||
else
|
||||
fileInfo.Attributes &= ~FileAttributes.Hidden;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断文件是否为隐藏
|
||||
/// </summary>
|
||||
public static bool IsHidden(string filePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
throw new FileNotFoundException($"File not found: {filePath}");
|
||||
|
||||
var fileInfo = new FileInfo(filePath);
|
||||
return (fileInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,26 +1,398 @@
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Common.Utils;
|
||||
|
||||
/// <summary>
|
||||
/// JSON 序列化工具类
|
||||
/// 基于 System.Text.Json 提供 JSON 序列化、反序列化、格式化等功能
|
||||
/// </summary>
|
||||
public static class JsonHelper
|
||||
{
|
||||
#region 预定义选项
|
||||
|
||||
/// <summary>
|
||||
/// 驼峰命名选项(默认格式化输出)
|
||||
/// </summary>
|
||||
public static readonly JsonSerializerOptions CamelCaseOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
WriteIndented = true,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
Converters =
|
||||
{
|
||||
new IpAddressConverter(), new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, allowIntegerValues: false)
|
||||
new IpAddressConverter(),
|
||||
new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, allowIntegerValues: false)
|
||||
},
|
||||
};
|
||||
|
||||
public static string ToJson<T>(T value) =>
|
||||
JsonSerializer.Serialize(value, CamelCaseOptions);
|
||||
/// <summary>
|
||||
/// 驼峰命名选项(紧凑格式,不格式化输出)
|
||||
/// </summary>
|
||||
public static readonly JsonSerializerOptions CamelCaseCompactOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
WriteIndented = false,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
Converters =
|
||||
{
|
||||
new IpAddressConverter(),
|
||||
new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, allowIntegerValues: false)
|
||||
},
|
||||
};
|
||||
|
||||
public static T? FromJson<T>(string json) =>
|
||||
JsonSerializer.Deserialize<T>(json, CamelCaseOptions);
|
||||
/// <summary>
|
||||
/// 蛇形命名选项(snake_case)
|
||||
/// </summary>
|
||||
public static readonly JsonSerializerOptions SnakeCaseOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
WriteIndented = true,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
Converters =
|
||||
{
|
||||
new IpAddressConverter(),
|
||||
new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseLower, allowIntegerValues: false)
|
||||
},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 默认选项(保留原始命名,格式化输出)
|
||||
/// </summary>
|
||||
public static readonly JsonSerializerOptions DefaultOptions = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
Converters =
|
||||
{
|
||||
new IpAddressConverter(),
|
||||
new JsonStringEnumConverter(allowIntegerValues: false)
|
||||
},
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
#region 基本序列化/反序列化
|
||||
|
||||
/// <summary>
|
||||
/// 序列化对象为 JSON 字符串(使用驼峰命名)
|
||||
/// </summary>
|
||||
public static string ToJson<T>(T value, bool useCompact = false)
|
||||
{
|
||||
var options = useCompact ? CamelCaseCompactOptions : CamelCaseOptions;
|
||||
return JsonSerializer.Serialize(value, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 序列化对象为 JSON 字符串(自定义选项)
|
||||
/// </summary>
|
||||
public static string ToJson<T>(T value, JsonSerializerOptions options)
|
||||
{
|
||||
return JsonSerializer.Serialize(value, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 JSON 字符串反序列化对象(使用驼峰命名)
|
||||
/// </summary>
|
||||
public static T? FromJson<T>(string json)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
return default;
|
||||
|
||||
return JsonSerializer.Deserialize<T>(json, CamelCaseOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 JSON 字符串反序列化对象(自定义选项)
|
||||
/// </summary>
|
||||
public static T? FromJson<T>(string json, JsonSerializerOptions options)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
return default;
|
||||
|
||||
return JsonSerializer.Deserialize<T>(json, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试从 JSON 字符串反序列化对象
|
||||
/// </summary>
|
||||
public static bool TryFromJson<T>(string? json, out T? result)
|
||||
{
|
||||
result = default;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
result = JsonSerializer.Deserialize<T>(json, CamelCaseOptions);
|
||||
return result != null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 文件操作
|
||||
|
||||
/// <summary>
|
||||
/// 序列化对象到文件
|
||||
/// </summary>
|
||||
public static void ToJsonFile<T>(string filePath, T value, bool useCompact = false)
|
||||
{
|
||||
var json = ToJson(value, useCompact);
|
||||
File.WriteAllText(filePath, json, Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从文件反序列化对象
|
||||
/// </summary>
|
||||
public static T? FromJsonFile<T>(string filePath)
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
throw new FileNotFoundException($"File not found: {filePath}");
|
||||
|
||||
var json = File.ReadAllText(filePath, Encoding.UTF8);
|
||||
return FromJson<T>(json);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试从文件反序列化对象
|
||||
/// </summary>
|
||||
public static bool TryFromJsonFile<T>(string filePath, out T? result)
|
||||
{
|
||||
result = default;
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText(filePath, Encoding.UTF8);
|
||||
return TryFromJson(json, out result);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Stream 操作
|
||||
|
||||
/// <summary>
|
||||
/// 序列化对象到流
|
||||
/// </summary>
|
||||
public static async Task SerializeToStreamAsync<T>(Stream stream, T value, bool useCompact = false, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var options = useCompact ? CamelCaseCompactOptions : CamelCaseOptions;
|
||||
await JsonSerializer.SerializeAsync(stream, value, options, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从流反序列化对象
|
||||
/// </summary>
|
||||
public static async Task<T?> DeserializeFromStreamAsync<T>(Stream stream, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await JsonSerializer.DeserializeAsync<T>(stream, CamelCaseOptions, cancellationToken);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region JSON 格式化/压缩
|
||||
|
||||
/// <summary>
|
||||
/// 格式化 JSON 字符串(美化输出)
|
||||
/// </summary>
|
||||
public static string FormatJson(string json)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
return string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
return JsonSerializer.Serialize(doc.RootElement, new JsonSerializerOptions { WriteIndented = true });
|
||||
}
|
||||
catch
|
||||
{
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 压缩 JSON 字符串(移除空格和换行)
|
||||
/// </summary>
|
||||
public static string CompactJson(string json)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
return string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
return JsonSerializer.Serialize(doc.RootElement, new JsonSerializerOptions { WriteIndented = false });
|
||||
}
|
||||
catch
|
||||
{
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region JSON 验证和查询
|
||||
|
||||
/// <summary>
|
||||
/// 验证 JSON 字符串格式是否有效
|
||||
/// </summary>
|
||||
public static bool IsValidJson(string? json)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 JSON 字符串中提取指定路径的值
|
||||
/// </summary>
|
||||
/// <param name="json">JSON 字符串</param>
|
||||
/// <param name="path">路径(例如 "user.name")</param>
|
||||
/// <returns>提取的值(字符串形式)</returns>
|
||||
public static string? GetValueByPath(string json, string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json) || string.IsNullOrWhiteSpace(path))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
var element = doc.RootElement;
|
||||
|
||||
foreach (var key in path.Split('.'))
|
||||
{
|
||||
if (element.ValueKind == JsonValueKind.Object && element.TryGetProperty(key, out var property))
|
||||
{
|
||||
element = property;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return element.ToString();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 深拷贝
|
||||
|
||||
/// <summary>
|
||||
/// 通过 JSON 序列化实现对象深拷贝
|
||||
/// </summary>
|
||||
public static T? DeepClone<T>(T obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return default;
|
||||
|
||||
var json = ToJson(obj);
|
||||
return FromJson<T>(json);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 合并 JSON
|
||||
|
||||
/// <summary>
|
||||
/// 合并两个 JSON 对象(后者覆盖前者)
|
||||
/// </summary>
|
||||
public static string MergeJson(string json1, string json2)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json1))
|
||||
return json2 ?? string.Empty;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(json2))
|
||||
return json1;
|
||||
|
||||
try
|
||||
{
|
||||
using var doc1 = JsonDocument.Parse(json1);
|
||||
using var doc2 = JsonDocument.Parse(json2);
|
||||
|
||||
var merged = new Dictionary<string, JsonElement>();
|
||||
|
||||
// 添加第一个 JSON 的所有属性
|
||||
foreach (var property in doc1.RootElement.EnumerateObject())
|
||||
{
|
||||
merged[property.Name] = property.Value.Clone();
|
||||
}
|
||||
|
||||
// 用第二个 JSON 的属性覆盖
|
||||
foreach (var property in doc2.RootElement.EnumerateObject())
|
||||
{
|
||||
merged[property.Name] = property.Value.Clone();
|
||||
}
|
||||
|
||||
return JsonSerializer.Serialize(merged, CamelCaseOptions);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return json1;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 类型转换
|
||||
|
||||
/// <summary>
|
||||
/// 将对象转换为字典(通过 JSON 序列化/反序列化)
|
||||
/// </summary>
|
||||
public static Dictionary<string, object?>? ToDictionary(object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return null;
|
||||
|
||||
var json = ToJson(obj);
|
||||
return FromJson<Dictionary<string, object?>>(json);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从字典创建对象(通过 JSON 序列化/反序列化)
|
||||
/// </summary>
|
||||
public static T? FromDictionary<T>(Dictionary<string, object?> dict)
|
||||
{
|
||||
if (dict == null)
|
||||
return default;
|
||||
|
||||
var json = ToJson(dict);
|
||||
return FromJson<T>(json);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -0,0 +1,525 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Common.Utils;
|
||||
|
||||
/// <summary>
|
||||
/// 属性访问器(用于缓存,提高反射性能)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">类型</typeparam>
|
||||
internal class PropertyAccessor<T>
|
||||
{
|
||||
public Func<T, object?>? Getter { get; set; }
|
||||
public Action<T, object?>? Setter { get; set; }
|
||||
public PropertyInfo PropertyInfo { get; set; }
|
||||
|
||||
public PropertyAccessor(PropertyInfo propertyInfo)
|
||||
{
|
||||
PropertyInfo = propertyInfo;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 反射工具类
|
||||
/// 提供类型扫描、属性访问、特性读取等功能,并通过缓存提高性能
|
||||
/// </summary>
|
||||
public static class ReflectionHelper
|
||||
{
|
||||
// 属性访问器缓存
|
||||
private static readonly ConcurrentDictionary<Type, Dictionary<string, object>> PropertyAccessorCache = new();
|
||||
|
||||
// 特性缓存
|
||||
private static readonly ConcurrentDictionary<MemberInfo, object[]> AttributeCache = new();
|
||||
|
||||
// 类型缓描
|
||||
private static readonly ConcurrentDictionary<Assembly, Type[]> TypeCache = new();
|
||||
|
||||
#region 类型扫描
|
||||
|
||||
/// <summary>
|
||||
/// 从程序集中获取所有类型
|
||||
/// </summary>
|
||||
/// <param name="assembly">程序集</param>
|
||||
/// <param name="useCache">是否使用缓存(默认 true)</param>
|
||||
/// <returns>类型数组</returns>
|
||||
public static Type[] GetTypes(Assembly assembly, bool useCache = true)
|
||||
{
|
||||
if (useCache && TypeCache.TryGetValue(assembly, out var cachedTypes))
|
||||
return cachedTypes;
|
||||
|
||||
var types = assembly.GetTypes();
|
||||
|
||||
if (useCache)
|
||||
TypeCache.TryAdd(assembly, types);
|
||||
|
||||
return types;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从当前程序集中查找指定基类的所有子类
|
||||
/// </summary>
|
||||
/// <typeparam name="TBase">基类类型</typeparam>
|
||||
/// <param name="includeAbstract">是否包含抽象类(默认 false)</param>
|
||||
/// <returns>子类类型列表</returns>
|
||||
public static List<Type> FindSubclassesOf<TBase>(bool includeAbstract = false)
|
||||
{
|
||||
return FindSubclassesOf(typeof(TBase), includeAbstract);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从当前程序集中查找指定基类的所有子类
|
||||
/// </summary>
|
||||
public static List<Type> FindSubclassesOf(Type baseType, bool includeAbstract = false)
|
||||
{
|
||||
var assembly = Assembly.GetCallingAssembly();
|
||||
return GetTypes(assembly)
|
||||
.Where(t => t.IsClass && baseType.IsAssignableFrom(t) && t != baseType)
|
||||
.Where(t => includeAbstract || !t.IsAbstract)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从指定程序集中查找实现指定接口的所有类型
|
||||
/// </summary>
|
||||
/// <typeparam name="TInterface">接口类型</typeparam>
|
||||
/// <param name="assembly">程序集</param>
|
||||
/// <param name="includeAbstract">是否包含抽象类(默认 false)</param>
|
||||
/// <returns>实现类型列表</returns>
|
||||
public static List<Type> FindImplementationsOf<TInterface>(Assembly assembly, bool includeAbstract = false)
|
||||
{
|
||||
var interfaceType = typeof(TInterface);
|
||||
return GetTypes(assembly)
|
||||
.Where(t => t.IsClass && interfaceType.IsAssignableFrom(t) && t != interfaceType)
|
||||
.Where(t => includeAbstract || !t.IsAbstract)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找带有指定特性的所有类型
|
||||
/// </summary>
|
||||
/// <typeparam name="TAttribute">特性类型</typeparam>
|
||||
/// <param name="assembly">程序集</param>
|
||||
/// <param name="inherit">是否搜索继承链(默认 true)</param>
|
||||
/// <returns>类型列表</returns>
|
||||
public static List<Type> FindTypesWithAttribute<TAttribute>(Assembly assembly, bool inherit = true)
|
||||
where TAttribute : Attribute
|
||||
{
|
||||
return GetTypes(assembly)
|
||||
.Where(t => t.GetCustomAttribute<TAttribute>(inherit) != null)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 属性访问(高性能)
|
||||
|
||||
/// <summary>
|
||||
/// 获取属性值(使用缓存的访问器,比直接反射快)
|
||||
/// </summary>
|
||||
public static object? GetPropertyValue<T>(T obj, string propertyName)
|
||||
{
|
||||
var accessor = GetPropertyAccessor<T>(propertyName);
|
||||
return accessor?.Getter?.Invoke(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置属性值(使用缓存的访问器)
|
||||
/// </summary>
|
||||
public static void SetPropertyValue<T>(T obj, string propertyName, object? value)
|
||||
{
|
||||
var accessor = GetPropertyAccessor<T>(propertyName);
|
||||
accessor?.Setter?.Invoke(obj, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取属性访问器(内部使用表达式树生成,首次慢,后续快)
|
||||
/// </summary>
|
||||
private static PropertyAccessor<T>? GetPropertyAccessor<T>(string propertyName)
|
||||
{
|
||||
var type = typeof(T);
|
||||
var cache = PropertyAccessorCache.GetOrAdd(type, _ => new Dictionary<string, object>());
|
||||
|
||||
lock (cache)
|
||||
{
|
||||
if (cache.TryGetValue(propertyName, out var cachedAccessor))
|
||||
return (PropertyAccessor<T>)cachedAccessor;
|
||||
|
||||
var propertyInfo = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
|
||||
if (propertyInfo == null)
|
||||
return null;
|
||||
|
||||
var accessor = new PropertyAccessor<T>(propertyInfo);
|
||||
|
||||
// 使用表达式树生成 Getter
|
||||
if (propertyInfo.CanRead)
|
||||
{
|
||||
var instance = Expression.Parameter(type, "instance");
|
||||
var property = Expression.Property(instance, propertyInfo);
|
||||
var convert = Expression.Convert(property, typeof(object));
|
||||
accessor.Getter = Expression.Lambda<Func<T, object?>>(convert, instance).Compile();
|
||||
}
|
||||
|
||||
// 使用表达式树生成 Setter
|
||||
if (propertyInfo.CanWrite)
|
||||
{
|
||||
var instance = Expression.Parameter(type, "instance");
|
||||
var value = Expression.Parameter(typeof(object), "value");
|
||||
var convert = Expression.Convert(value, propertyInfo.PropertyType);
|
||||
var property = Expression.Property(instance, propertyInfo);
|
||||
var assign = Expression.Assign(property, convert);
|
||||
accessor.Setter = Expression.Lambda<Action<T, object?>>(assign, instance, value).Compile();
|
||||
}
|
||||
|
||||
cache[propertyName] = accessor;
|
||||
return accessor;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有属性名称
|
||||
/// </summary>
|
||||
public static List<string> GetPropertyNames<T>(BindingFlags flags = BindingFlags.Public | BindingFlags.Instance)
|
||||
{
|
||||
return typeof(T).GetProperties(flags).Select(p => p.Name).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有属性信息
|
||||
/// </summary>
|
||||
public static PropertyInfo[] GetProperties<T>(BindingFlags flags = BindingFlags.Public | BindingFlags.Instance)
|
||||
{
|
||||
return typeof(T).GetProperties(flags);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 特性读取(带缓存)
|
||||
|
||||
/// <summary>
|
||||
/// 获取类型的特性(带缓存)
|
||||
/// </summary>
|
||||
public static TAttribute? GetAttribute<TAttribute>(Type type, bool inherit = true)
|
||||
where TAttribute : Attribute
|
||||
{
|
||||
var attributes = GetAttributes<TAttribute>(type, inherit);
|
||||
return attributes.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取类型的所有指定特性(带缓存)
|
||||
/// </summary>
|
||||
public static TAttribute[] GetAttributes<TAttribute>(Type type, bool inherit = true)
|
||||
where TAttribute : Attribute
|
||||
{
|
||||
var cached = AttributeCache.GetOrAdd(type, _ => type.GetCustomAttributes(inherit).ToArray());
|
||||
return cached.OfType<TAttribute>().ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取成员的特性(带缓存)
|
||||
/// </summary>
|
||||
public static TAttribute? GetAttribute<TAttribute>(MemberInfo member, bool inherit = true)
|
||||
where TAttribute : Attribute
|
||||
{
|
||||
var attributes = GetAttributes<TAttribute>(member, inherit);
|
||||
return attributes.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取成员的所有指定特性(带缓存)
|
||||
/// </summary>
|
||||
public static TAttribute[] GetAttributes<TAttribute>(MemberInfo member, bool inherit = true)
|
||||
where TAttribute : Attribute
|
||||
{
|
||||
var cached = AttributeCache.GetOrAdd(member, _ => member.GetCustomAttributes(inherit).ToArray());
|
||||
return cached.OfType<TAttribute>().ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断类型是否有指定特性
|
||||
/// </summary>
|
||||
public static bool HasAttribute<TAttribute>(Type type, bool inherit = true)
|
||||
where TAttribute : Attribute
|
||||
{
|
||||
return GetAttribute<TAttribute>(type, inherit) != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断成员是否有指定特性
|
||||
/// </summary>
|
||||
public static bool HasAttribute<TAttribute>(MemberInfo member, bool inherit = true)
|
||||
where TAttribute : Attribute
|
||||
{
|
||||
return GetAttribute<TAttribute>(member, inherit) != null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 创建实例
|
||||
|
||||
/// <summary>
|
||||
/// 创建类型实例(使用无参构造函数)
|
||||
/// </summary>
|
||||
public static T? CreateInstance<T>() where T : class
|
||||
{
|
||||
return Activator.CreateInstance<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建类型实例(使用无参构造函数)
|
||||
/// </summary>
|
||||
public static object? CreateInstance(Type type)
|
||||
{
|
||||
return Activator.CreateInstance(type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建类型实例(使用有参构造函数)
|
||||
/// </summary>
|
||||
public static object? CreateInstance(Type type, params object[] args)
|
||||
{
|
||||
return Activator.CreateInstance(type, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试创建类型实例
|
||||
/// </summary>
|
||||
public static bool TryCreateInstance<T>(out T? instance) where T : class
|
||||
{
|
||||
try
|
||||
{
|
||||
instance = CreateInstance<T>();
|
||||
return instance != null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
instance = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 属性复制
|
||||
|
||||
/// <summary>
|
||||
/// 将源对象的属性值复制到目标对象(同名属性)
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">源类型</typeparam>
|
||||
/// <typeparam name="TTarget">目标类型</typeparam>
|
||||
/// <param name="source">源对象</param>
|
||||
/// <param name="target">目标对象</param>
|
||||
/// <param name="ignoreCase">是否忽略属性名大小写(默认 false)</param>
|
||||
public static void CopyProperties<TSource, TTarget>(TSource source, TTarget target, bool ignoreCase = false)
|
||||
{
|
||||
if (source == null || target == null)
|
||||
return;
|
||||
|
||||
var sourceType = typeof(TSource);
|
||||
var targetType = typeof(TTarget);
|
||||
|
||||
var sourceProperties = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(p => p.CanRead)
|
||||
.ToList();
|
||||
|
||||
var targetProperties = targetType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(p => p.CanWrite)
|
||||
.ToDictionary(
|
||||
p => p.Name,
|
||||
p => p,
|
||||
ignoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal
|
||||
);
|
||||
|
||||
foreach (var sourceProp in sourceProperties)
|
||||
{
|
||||
if (targetProperties.TryGetValue(sourceProp.Name, out var targetProp))
|
||||
{
|
||||
// 检查类型是否兼容
|
||||
if (targetProp.PropertyType.IsAssignableFrom(sourceProp.PropertyType))
|
||||
{
|
||||
var value = sourceProp.GetValue(source);
|
||||
targetProp.SetValue(target, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建目标类型实例并复制属性
|
||||
/// </summary>
|
||||
public static TTarget? MapTo<TSource, TTarget>(TSource source, bool ignoreCase = false)
|
||||
where TTarget : class, new()
|
||||
{
|
||||
if (source == null)
|
||||
return null;
|
||||
|
||||
var target = new TTarget();
|
||||
CopyProperties(source, target, ignoreCase);
|
||||
return target;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 方法调用
|
||||
|
||||
/// <summary>
|
||||
/// 调用对象的方法
|
||||
/// </summary>
|
||||
/// <param name="obj">对象实例</param>
|
||||
/// <param name="methodName">方法名</param>
|
||||
/// <param name="parameters">参数</param>
|
||||
/// <returns>方法返回值</returns>
|
||||
public static object? InvokeMethod(object obj, string methodName, params object[] parameters)
|
||||
{
|
||||
var type = obj.GetType();
|
||||
var method = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance);
|
||||
|
||||
if (method == null)
|
||||
throw new InvalidOperationException($"Method '{methodName}' not found on type '{type.FullName}'");
|
||||
|
||||
return method.Invoke(obj, parameters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调用静态方法
|
||||
/// </summary>
|
||||
public static object? InvokeStaticMethod(Type type, string methodName, params object[] parameters)
|
||||
{
|
||||
var method = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static);
|
||||
|
||||
if (method == null)
|
||||
throw new InvalidOperationException($"Static method '{methodName}' not found on type '{type.FullName}'");
|
||||
|
||||
return method.Invoke(null, parameters);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 类型判断
|
||||
|
||||
/// <summary>
|
||||
/// 判断类型是否为可空类型
|
||||
/// </summary>
|
||||
public static bool IsNullableType(Type type)
|
||||
{
|
||||
return Nullable.GetUnderlyingType(type) != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取可空类型的基础类型
|
||||
/// </summary>
|
||||
public static Type GetUnderlyingType(Type type)
|
||||
{
|
||||
return Nullable.GetUnderlyingType(type) ?? type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断类型是否为值类型
|
||||
/// </summary>
|
||||
public static bool IsValueType(Type type)
|
||||
{
|
||||
return type.IsValueType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断类型是否为引用类型
|
||||
/// </summary>
|
||||
public static bool IsReferenceType(Type type)
|
||||
{
|
||||
return !type.IsValueType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断类型是否为数字类型
|
||||
/// </summary>
|
||||
public static bool IsNumericType(Type type)
|
||||
{
|
||||
type = GetUnderlyingType(type);
|
||||
|
||||
return type == typeof(byte) || type == typeof(sbyte) ||
|
||||
type == typeof(short) || type == typeof(ushort) ||
|
||||
type == typeof(int) || type == typeof(uint) ||
|
||||
type == typeof(long) || type == typeof(ulong) ||
|
||||
type == typeof(float) || type == typeof(double) ||
|
||||
type == typeof(decimal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断类型是否为集合类型
|
||||
/// </summary>
|
||||
public static bool IsCollectionType(Type type)
|
||||
{
|
||||
return type.IsArray ||
|
||||
(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>)) ||
|
||||
type.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 泛型操作
|
||||
|
||||
/// <summary>
|
||||
/// 判断类型是否为泛型类型
|
||||
/// </summary>
|
||||
public static bool IsGenericType(Type type)
|
||||
{
|
||||
return type.IsGenericType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取泛型参数类型
|
||||
/// </summary>
|
||||
public static Type[] GetGenericArguments(Type type)
|
||||
{
|
||||
return type.GetGenericArguments();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造泛型类型(例如:List<> + int => List<int>)
|
||||
/// </summary>
|
||||
public static Type MakeGenericType(Type genericTypeDefinition, params Type[] typeArguments)
|
||||
{
|
||||
return genericTypeDefinition.MakeGenericType(typeArguments);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 缓存管理
|
||||
|
||||
/// <summary>
|
||||
/// 清除所有缓存
|
||||
/// </summary>
|
||||
public static void ClearCache()
|
||||
{
|
||||
PropertyAccessorCache.Clear();
|
||||
AttributeCache.Clear();
|
||||
TypeCache.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除属性访问器缓存
|
||||
/// </summary>
|
||||
public static void ClearPropertyAccessorCache()
|
||||
{
|
||||
PropertyAccessorCache.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除特性缓存
|
||||
/// </summary>
|
||||
public static void ClearAttributeCache()
|
||||
{
|
||||
AttributeCache.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除类型缓存
|
||||
/// </summary>
|
||||
public static void ClearTypeCache()
|
||||
{
|
||||
TypeCache.Clear();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in new issue
Block a user