A:Common配置

This commit is contained in:
jimmy committed 2025-11-13 19:12:19 +08:00
1 parent 61e769a0f6
commit c8fde9759f
8 files changed
+3742 -703

No files matched your search

@@ -0,0 +1,530 @@
namespace Common.ExpandFun;
/// <summary>
/// DateTime 扩展方法
/// 提供日期格式化、时间戳转换、时区转换、日期计算等功能
/// </summary>
public static class DateTimeExpand
{
#region 日期格式化
/// <summary>
/// 格式化为中文日期字符串(yyyy年MM月dd日)
/// </summary>
public static string ToChineseDateString(this DateTime dt)
{
return $"{dt.Year}年{dt.Month:D2}月{dt.Day:D2}日";
}
/// <summary>
/// 格式化为中文日期时间字符串(yyyy年MM月dd日 HH:mm:ss)
/// </summary>
public static string ToChineseDateTimeString(this DateTime dt)
{
return $"{dt.Year}年{dt.Month:D2}月{dt.Day:D2}日 {dt.Hour:D2}:{dt.Minute:D2}:{dt.Second:D2}";
}
/// <summary>
/// 格式化为友好的相对时间字符串(如"刚刚"、"5分钟前"、"昨天"等)
/// </summary>
public static string ToFriendlyString(this DateTime dt)
{
var span = DateTime.Now - dt;
if (span.TotalSeconds < 60)
return "刚刚";
if (span.TotalMinutes < 60)
return $"{(int)span.TotalMinutes}分钟前";
if (span.TotalHours < 24)
return $"{(int)span.TotalHours}小时前";
if (span.TotalDays < 2)
return "昨天";
if (span.TotalDays < 7)
return $"{(int)span.TotalDays}天前";
if (span.TotalDays < 30)
return $"{(int)(span.TotalDays / 7)}周前";
if (span.TotalDays < 365)
return $"{(int)(span.TotalDays / 30)}个月前";
return $"{(int)(span.TotalDays / 365)}年前";
}
/// <summary>
/// 格式化为 ISO 8601 字符串(yyyy-MM-ddTHH:mm:ss.fffZ)
/// </summary>
public static string ToIso8601String(this DateTime dt)
{
return dt.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
}
/// <summary>
/// 格式化为标准日期字符串(yyyy-MM-dd)
/// </summary>
public static string ToDateString(this DateTime dt)
{
return dt.ToString("yyyy-MM-dd");
}
/// <summary>
/// 格式化为标准日期时间字符串(yyyy-MM-dd HH:mm:ss)
/// </summary>
public static string ToDateTimeString(this DateTime dt)
{
return dt.ToString("yyyy-MM-dd HH:mm:ss");
}
#endregion
#region 时间戳转换
/// <summary>
/// 转换为 Unix 时间戳(秒)
/// </summary>
public static long ToUnixTimeSeconds(this DateTime dt)
{
return new DateTimeOffset(dt).ToUnixTimeSeconds();
}
/// <summary>
/// 转换为 Unix 时间戳(毫秒)
/// </summary>
public static long ToUnixTimeMilliseconds(this DateTime dt)
{
return new DateTimeOffset(dt).ToUnixTimeMilliseconds();
}
/// <summary>
/// 从 Unix 时间戳(秒)创建 DateTime
/// </summary>
public static DateTime FromUnixTimeSeconds(long seconds)
{
return DateTimeOffset.FromUnixTimeSeconds(seconds).DateTime;
}
/// <summary>
/// 从 Unix 时间戳(毫秒)创建 DateTime
/// </summary>
public static DateTime FromUnixTimeMilliseconds(long milliseconds)
{
return DateTimeOffset.FromUnixTimeMilliseconds(milliseconds).DateTime;
}
/// <summary>
/// 转换为 JavaScript 时间戳(毫秒)
/// </summary>
public static long ToJavaScriptTimestamp(this DateTime dt)
{
return dt.ToUnixTimeMilliseconds();
}
/// <summary>
/// 从 JavaScript 时间戳(毫秒)创建 DateTime
/// </summary>
public static DateTime FromJavaScriptTimestamp(long timestamp)
{
return FromUnixTimeMilliseconds(timestamp);
}
#endregion
#region 时区转换
/// <summary>
/// 转换为指定时区的时间
/// </summary>
/// <param name="dt">源时间</param>
/// <param name="targetTimeZoneId">目标时区 ID(如 "China Standard Time")</param>
/// <returns>转换后的时间</returns>
public static DateTime ConvertToTimeZone(this DateTime dt, string targetTimeZoneId)
{
var targetTimeZone = TimeZoneInfo.FindSystemTimeZoneById(targetTimeZoneId);
return TimeZoneInfo.ConvertTime(dt, targetTimeZone);
}
/// <summary>
/// 转换为 UTC 时间
/// </summary>
public static DateTime ToUtc(this DateTime dt)
{
if (dt.Kind == DateTimeKind.Utc)
return dt;
return TimeZoneInfo.ConvertTimeToUtc(dt);
}
/// <summary>
/// 转换为本地时间
/// </summary>
public static DateTime ToLocal(this DateTime dt)
{
if (dt.Kind == DateTimeKind.Local)
return dt;
return dt.ToLocalTime();
}
/// <summary>
/// 从 UTC 转换为指定时区
/// </summary>
public static DateTime FromUtcToTimeZone(this DateTime utcDateTime, string timeZoneId)
{
if (utcDateTime.Kind != DateTimeKind.Utc)
throw new ArgumentException("DateTime must be UTC", nameof(utcDateTime));
var targetTimeZone = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId);
return TimeZoneInfo.ConvertTimeFromUtc(utcDateTime, targetTimeZone);
}
#endregion
#region 日期计算
/// <summary>
/// 获取当前月份的第一天
/// </summary>
public static DateTime StartOfMonth(this DateTime dt)
{
return new DateTime(dt.Year, dt.Month, 1, 0, 0, 0, dt.Kind);
}
/// <summary>
/// 获取当前月份的最后一天
/// </summary>
public static DateTime EndOfMonth(this DateTime dt)
{
return new DateTime(dt.Year, dt.Month, DateTime.DaysInMonth(dt.Year, dt.Month), 23, 59, 59, 999, dt.Kind);
}
/// <summary>
/// 获取当前年份的第一天
/// </summary>
public static DateTime StartOfYear(this DateTime dt)
{
return new DateTime(dt.Year, 1, 1, 0, 0, 0, dt.Kind);
}
/// <summary>
/// 获取当前年份的最后一天
/// </summary>
public static DateTime EndOfYear(this DateTime dt)
{
return new DateTime(dt.Year, 12, 31, 23, 59, 59, 999, dt.Kind);
}
/// <summary>
/// 获取当天的开始时间(00:00:00)
/// </summary>
public static DateTime StartOfDay(this DateTime dt)
{
return dt.Date;
}
/// <summary>
/// 获取当天的结束时间(23:59:59.999)
/// </summary>
public static DateTime EndOfDay(this DateTime dt)
{
return dt.Date.AddDays(1).AddMilliseconds(-1);
}
/// <summary>
/// 获取当前周的第一天(周一)
/// </summary>
public static DateTime StartOfWeek(this DateTime dt, DayOfWeek firstDayOfWeek = DayOfWeek.Monday)
{
int diff = (7 + (dt.DayOfWeek - firstDayOfWeek)) % 7;
return dt.AddDays(-diff).Date;
}
/// <summary>
/// 获取当前周的最后一天(周日)
/// </summary>
public static DateTime EndOfWeek(this DateTime dt, DayOfWeek firstDayOfWeek = DayOfWeek.Monday)
{
return dt.StartOfWeek(firstDayOfWeek).AddDays(6).EndOfDay();
}
/// <summary>
/// 添加工作日(跳过周末)
/// </summary>
public static DateTime AddWorkDays(this DateTime dt, int workDays)
{
int direction = workDays < 0 ? -1 : 1;
int daysToAdd = Math.Abs(workDays);
DateTime result = dt;
while (daysToAdd > 0)
{
result = result.AddDays(direction);
if (result.DayOfWeek != DayOfWeek.Saturday && result.DayOfWeek != DayOfWeek.Sunday)
{
daysToAdd--;
}
}
return result;
}
/// <summary>
/// 计算两个日期之间的工作日数量
/// </summary>
public static int WorkDaysBetween(this DateTime startDate, DateTime endDate)
{
if (startDate > endDate)
(startDate, endDate) = (endDate, startDate);
int workDays = 0;
for (DateTime date = startDate; date <= endDate; date = date.AddDays(1))
{
if (date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek != DayOfWeek.Sunday)
{
workDays++;
}
}
return workDays;
}
/// <summary>
/// 计算年龄
/// </summary>
public static int CalculateAge(this DateTime birthDate, DateTime? referenceDate = null)
{
var reference = referenceDate ?? DateTime.Today;
int age = reference.Year - birthDate.Year;
if (reference.Month < birthDate.Month || (reference.Month == birthDate.Month && reference.Day < birthDate.Day))
{
age--;
}
return age;
}
/// <summary>
/// 获取季度(1-4)
/// </summary>
public static int GetQuarter(this DateTime dt)
{
return (dt.Month - 1) / 3 + 1;
}
/// <summary>
/// 获取季度的第一天
/// </summary>
public static DateTime StartOfQuarter(this DateTime dt)
{
int quarter = dt.GetQuarter();
int month = (quarter - 1) * 3 + 1;
return new DateTime(dt.Year, month, 1, 0, 0, 0, dt.Kind);
}
/// <summary>
/// 获取季度的最后一天
/// </summary>
public static DateTime EndOfQuarter(this DateTime dt)
{
var startOfQuarter = dt.StartOfQuarter();
return startOfQuarter.AddMonths(3).AddDays(-1).EndOfDay();
}
#endregion
#region 时间范围判断
/// <summary>
/// 判断是否在指定时间范围内
/// </summary>
public static bool IsBetween(this DateTime dt, DateTime start, DateTime end)
{
return dt >= start && dt <= end;
}
/// <summary>
/// 判断是否是今天
/// </summary>
public static bool IsToday(this DateTime dt)
{
return dt.Date == DateTime.Today;
}
/// <summary>
/// 判断是否是昨天
/// </summary>
public static bool IsYesterday(this DateTime dt)
{
return dt.Date == DateTime.Today.AddDays(-1);
}
/// <summary>
/// 判断是否是明天
/// </summary>
public static bool IsTomorrow(this DateTime dt)
{
return dt.Date == DateTime.Today.AddDays(1);
}
/// <summary>
/// 判断是否是周末
/// </summary>
public static bool IsWeekend(this DateTime dt)
{
return dt.DayOfWeek == DayOfWeek.Saturday || dt.DayOfWeek == DayOfWeek.Sunday;
}
/// <summary>
/// 判断是否是工作日
/// </summary>
public static bool IsWorkDay(this DateTime dt)
{
return !dt.IsWeekend();
}
/// <summary>
/// 判断是否是闰年
/// </summary>
public static bool IsLeapYear(this DateTime dt)
{
return DateTime.IsLeapYear(dt.Year);
}
/// <summary>
/// 判断是否在过去
/// </summary>
public static bool IsPast(this DateTime dt)
{
return dt < DateTime.Now;
}
/// <summary>
/// 判断是否在未来
/// </summary>
public static bool IsFuture(this DateTime dt)
{
return dt > DateTime.Now;
}
/// <summary>
/// 判断是否是本周
/// </summary>
public static bool IsThisWeek(this DateTime dt)
{
var now = DateTime.Now;
return dt.StartOfWeek() == now.StartOfWeek();
}
/// <summary>
/// 判断是否是本月
/// </summary>
public static bool IsThisMonth(this DateTime dt)
{
var now = DateTime.Now;
return dt.Year == now.Year && dt.Month == now.Month;
}
/// <summary>
/// 判断是否是本年
/// </summary>
public static bool IsThisYear(this DateTime dt)
{
return dt.Year == DateTime.Now.Year;
}
#endregion
#region 时间差计算
/// <summary>
/// 获取距离现在的友好时间差描述
/// </summary>
public static string GetTimeSpanFromNow(this DateTime dt)
{
var span = DateTime.Now - dt;
return FormatTimeSpan(span);
}
/// <summary>
/// 获取距离指定时间的友好时间差描述
/// </summary>
public static string GetTimeSpanFrom(this DateTime dt, DateTime other)
{
var span = other - dt;
return FormatTimeSpan(span);
}
private static string FormatTimeSpan(TimeSpan span)
{
var abs = span.Duration();
if (abs.TotalDays >= 365)
return $"{(int)(abs.TotalDays / 365)}年";
if (abs.TotalDays >= 30)
return $"{(int)(abs.TotalDays / 30)}个月";
if (abs.TotalDays >= 1)
return $"{(int)abs.TotalDays}天";
if (abs.TotalHours >= 1)
return $"{(int)abs.TotalHours}小时";
if (abs.TotalMinutes >= 1)
return $"{(int)abs.TotalMinutes}分钟";
return $"{(int)abs.TotalSeconds}秒";
}
#endregion
#region 时间舍入
/// <summary>
/// 舍入到最近的小时
/// </summary>
public static DateTime RoundToHour(this DateTime dt)
{
return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, 0, 0, dt.Kind);
}
/// <summary>
/// 舍入到最近的分钟
/// </summary>
public static DateTime RoundToMinute(this DateTime dt)
{
return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 0, dt.Kind);
}
/// <summary>
/// 舍入到最近的秒
/// </summary>
public static DateTime RoundToSecond(this DateTime dt)
{
return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second, dt.Kind);
}
/// <summary>
/// 向上舍入到指定分钟间隔
/// </summary>
public static DateTime CeilingToMinutes(this DateTime dt, int minutes)
{
var ticks = TimeSpan.FromMinutes(minutes).Ticks;
return new DateTime((dt.Ticks + ticks - 1) / ticks * ticks, dt.Kind);
}
/// <summary>
/// 向下舍入到指定分钟间隔
/// </summary>
public static DateTime FloorToMinutes(this DateTime dt, int minutes)
{
var ticks = TimeSpan.FromMinutes(minutes).Ticks;
return new DateTime(dt.Ticks / ticks * ticks, dt.Kind);
}
#endregion
}
-338
View File
@@ -1,338 +0,0 @@
namespace Common;
/// <summary>
/// Global 轻量级 DI 容器使用示例
/// </summary>
public static class GlobalUsageExamples
{
/// <summary>
/// 基础使用示例
/// </summary>
public static void BasicUsageExamples()
{
// 1. 注册和获取单例服务
var config = new AppConfig { AppName = "MyApp", Version = "1.0" };
Global.RegisterSingleton(config);
var retrievedConfig = Global.Get<AppConfig>();
Console.WriteLine($"App: {retrievedConfig.AppName} v{retrievedConfig.Version}");
// 2. 使用工厂方法注册单例
Global.RegisterSingleton(() => new DatabaseConnection("connection-string"));
var db = Global.Get<DatabaseConnection>();
// 3. 注册瞬态服务(每次获取都是新实例)
Global.RegisterTransient(() => new RequestContext
{
RequestId = Guid.NewGuid().ToString(),
Timestamp = DateTime.Now
});
var request1 = Global.Get<RequestContext>();
var request2 = Global.Get<RequestContext>();
// request1 和 request2 是不同的实例
// 4. 尝试获取服务
if (Global.TryGet<ILogger>(out var logger))
{
logger.Log("Service found");
}
else
{
Console.WriteLine("Logger not registered");
}
// 5. 获取服务或默认值
var fallbackLogger = Global.GetOrDefault<ILogger>(new ConsoleLogger());
// 6. 检查服务是否注册
if (Global.IsRegistered<AppConfig>())
{
Console.WriteLine("AppConfig is registered");
}
}
/// <summary>
/// 服务变更通知示例
/// </summary>
public static void ChangeNotificationExamples()
{
// 1. 订阅服务变更通知
Global.Subscribe<AppConfig>(eventArgs =>
{
Console.WriteLine($"Config changed at {eventArgs.ChangedAt}");
Console.WriteLine($"Old: {eventArgs.OldValue?.AppName ?? "null"}");
Console.WriteLine($"New: {eventArgs.NewValue.AppName}");
});
// 2. 注册初始配置
var config = new AppConfig { AppName = "MyApp", Version = "1.0" };
Global.RegisterSingleton(config);
// 3. 更新配置(会触发通知)
var newConfig = new AppConfig { AppName = "MyApp", Version = "2.0" };
Global.Update(newConfig);
// 4. 也可以在注册时就设置为通知模式
var anotherConfig = new AppConfig { AppName = "MyApp", Version = "3.0" };
Global.RegisterSingleton(anotherConfig, notifyIfExists: true); // 会触发通知
}
/// <summary>
/// 实时配置更新示例
/// </summary>
public static void LiveConfigurationExample()
{
// 场景:应用程序配置可以动态更新,所有组件都能收到通知
// 1. 定义配置变更处理器
void OnConfigChanged(ServiceChangedEventArgs<AppConfig> e)
{
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Configuration updated:");
Console.WriteLine($" Old Version: {e.OldValue?.Version}");
Console.WriteLine($" New Version: {e.NewValue.Version}");
// 在这里可以执行配置变更后的操作
// 例如:重新连接数据库、刷新缓存等
}
// 2. 订阅配置变更
Global.Subscribe<AppConfig>(OnConfigChanged);
// 3. 注册初始配置
Global.RegisterSingleton(new AppConfig { AppName = "MyApp", Version = "1.0" });
// 4. 模拟配置文件被修改,更新配置
Thread.Sleep(1000);
Global.Update(new AppConfig { AppName = "MyApp", Version = "1.1" });
Thread.Sleep(1000);
Global.Update(new AppConfig { AppName = "MyApp", Version = "1.2" });
// 5. 取消订阅
Global.Unsubscribe<AppConfig>(OnConfigChanged);
}
/// <summary>
/// 多订阅者示例
/// </summary>
public static void MultipleSubscribersExample()
{
// 场景:多个组件都关心某个服务的变更
// 订阅者 1:UI 组件
Global.Subscribe<UserSession>(e =>
{
Console.WriteLine($"[UI] User session updated: {e.NewValue.Username}");
// 更新UI显示
});
// 订阅者 2:日志组件
Global.Subscribe<UserSession>(e =>
{
Console.WriteLine($"[LOG] Session change detected at {e.ChangedAt}");
// 记录日志
});
// 订阅者 3:分析组件
Global.Subscribe<UserSession>(e =>
{
Console.WriteLine($"[ANALYTICS] Tracking user: {e.NewValue.Username}");
// 发送分析数据
});
// 注册会话
Global.RegisterSingleton(new UserSession { Username = "user1", LoginTime = DateTime.Now });
// 更新会话(所有订阅者都会收到通知)
Thread.Sleep(100);
Global.Update(new UserSession { Username = "user2", LoginTime = DateTime.Now });
}
/// <summary>
/// 服务生命周期对比示例
/// </summary>
public static void LifetimeComparisonExample()
{
// 1. 单例服务:整个应用生命周期内只有一个实例
Global.RegisterSingleton(() => new CacheService());
var cache1 = Global.Get<CacheService>();
var cache2 = Global.Get<CacheService>();
Console.WriteLine($"Singleton: {ReferenceEquals(cache1, cache2)}"); // True
// 2. 瞬态服务:每次获取都是新实例
Global.RegisterTransient(() => new OperationContext());
var ctx1 = Global.Get<OperationContext>();
var ctx2 = Global.Get<OperationContext>();
Console.WriteLine($"Transient: {ReferenceEquals(ctx1, ctx2)}"); // False
}
/// <summary>
/// 服务管理示例
/// </summary>
public static void ServiceManagementExample()
{
// 注册多个服务
Global.RegisterSingleton(new AppConfig { AppName = "MyApp" });
Global.RegisterSingleton(() => new DatabaseConnection("conn-string"));
Global.RegisterTransient(() => new RequestContext());
// 查看已注册服务数量
Console.WriteLine($"Total services: {Global.Count}");
// 注销特定服务
if (Global.Unregister<RequestContext>())
{
Console.WriteLine("RequestContext unregistered");
}
// 清除所有服务
Global.Clear();
Console.WriteLine($"Services after clear: {Global.Count}");
}
/// <summary>
/// 错误处理示例
/// </summary>
public static void ErrorHandlingExample()
{
try
{
// 尝试获取未注册的服务
var service = Global.Get<ILogger>();
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
try
{
// 尝试更新瞬态服务
Global.RegisterTransient(() => new RequestContext());
Global.Update(new RequestContext()); // 会抛出异常
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
// 安全的获取方式
if (Global.TryGet<ILogger>(out var logger))
{
logger.Log("Success");
}
else
{
Console.WriteLine("Logger not available, using fallback");
}
}
/// <summary>
/// 工厂模式集成示例
/// </summary>
public static void FactoryPatternExample()
{
// 使用工厂方法创建复杂对象
Global.RegisterSingleton(() =>
{
var config = LoadConfigFromFile();
ValidateConfig(config);
return config;
});
// 延迟初始化:只有在第一次 Get 时才会调用工厂方法
var config = Global.Get<AppConfig>(); // 此时才执行工厂方法
// 后续获取都返回同一个实例
var sameConfig = Global.Get<AppConfig>(); // 直接返回,不再调用工厂
}
/// <summary>
/// 线程安全示例
/// </summary>
public static void ThreadSafetyExample()
{
// 注册服务
Global.RegisterSingleton(() =>
{
Thread.Sleep(100); // 模拟复杂的初始化过程
return new CacheService();
});
// 多线程并发获取(保证只创建一次)
var tasks = Enumerable.Range(0, 10).Select(i =>
Task.Run(() =>
{
var service = Global.Get<CacheService>();
Console.WriteLine($"Thread {i}: {service.GetHashCode()}");
})
);
Task.WaitAll(tasks.ToArray());
// 所有线程获取的都是同一个实例
}
#region 辅助类型
private class AppConfig
{
public string AppName { get; set; } = "";
public string Version { get; set; } = "";
}
private class DatabaseConnection
{
public string ConnectionString { get; }
public DatabaseConnection(string connectionString)
{
ConnectionString = connectionString;
}
}
private class RequestContext
{
public string RequestId { get; set; } = Guid.NewGuid().ToString();
public DateTime Timestamp { get; set; } = DateTime.Now;
}
private interface ILogger
{
void Log(string message);
}
private class ConsoleLogger : ILogger
{
public void Log(string message) => Console.WriteLine($"[LOG] {message}");
}
private class UserSession
{
public string Username { get; set; } = "";
public DateTime LoginTime { get; set; }
}
private class CacheService
{
private readonly Dictionary<string, object> _cache = new();
}
private class OperationContext
{
public Guid OperationId { get; } = Guid.NewGuid();
}
private static AppConfig LoadConfigFromFile()
{
// 模拟从文件加载配置
return new AppConfig { AppName = "MyApp", Version = "1.0" };
}
private static void ValidateConfig(AppConfig config)
{
if (string.IsNullOrEmpty(config.AppName))
throw new InvalidOperationException("AppName cannot be empty");
}
#endregion
}
+693
View File
@@ -0,0 +1,693 @@
# Sharp.Common
一个功能丰富的 C# 通用工具库,提供依赖注入、函数式错误处理、字符串扩展、JSON 序列化等常用功能。
## 特性
- ✅ **轻量级 DI 容器** - 线程安全的依赖注入容器,支持单例和瞬态生命周期,带变更通知
- ✅ **Result 模式** - 函数式错误处理,避免异常,支持 LINQ 和异步操作
- ✅ **自定义异常** - 带错误码和建议的异常类型
- ✅ **字符串扩展** - URL 验证、安全类型转换等实用方法
- ✅ **日期时间扩展** - 日期格式化、时间戳转换、时区转换、工作日计算
- ✅ **加密工具** - AES/RSA加密、MD5/SHA256哈希、Base64编解码、密码哈希、HMAC签名
- ✅ **验证工具** - 邮箱、手机号、身份证、URL验证,与Result模式无缝集成
- ✅ **JSON 序列化** - 预配置的 JSON 序列化,支持 camelCase 和 IPAddress
- ✅ **系统工具** - 动态端口分配、管理员权限检查
## 快速开始
### 安装
将 `Common` 项目引用添加到你的项目中:
```xml
<ItemGroup>
<ProjectReference Include="path\to\Sharp\Common\Common.csproj"/>
</ItemGroup>
```
### 基础使用
#### 1. 依赖注入容器 (Global)
```csharp
using Common;
// 注册单例服务
var config = new AppConfig { AppName = "MyApp" };
Global.RegisterSingleton(config);
// 获取服务
var retrievedConfig = Global.Get<AppConfig>();
// 订阅变更通知
Global.Subscribe<AppConfig>(e =>
{
Console.WriteLine($"Config changed: {e.OldValue?.AppName} -> {e.NewValue.AppName}");
});
// 更新服务(触发通知)
Global.Update(new AppConfig { AppName = "UpdatedApp" });
// 注册瞬态服务(每次获取都创建新实例)
Global.RegisterTransient(() => new RequestContext());
```
#### 2. Result 模式
```csharp
using Common.Results;
// 创建成功结果
var success = Result.Success(42);
// 创建失败结果
var failure = Result.Failure<int>("Something went wrong");
var failureWithCode = Result.Failure<int>(404L, "Not found");
// 使用 Try 安全执行操作
var result = Result.Try(() => int.Parse("123"));
// 函数式链式操作
var processedResult = Result.Success(10)
.Map(x => x * 2) // 转换值
.Where(x => x > 15, "Too small") // 过滤
.Bind(x => Result.Success(x.ToString()));
// 条件执行
result
.OnSuccess(value => Console.WriteLine($"Success: {value}"))
.OnFailure((ex, msg) => Console.WriteLine($"Error: {msg}"));
// LINQ 查询语法
var combined = from x in Result.Success(5)
from y in Result.Success(10)
select x + y;
```
#### 3. 字符串扩展
```csharp
using Common.ExpandFun;
// URL 验证
bool isValid = "https://example.com".IsValidUrl(); // true
// 安全类型转换
int port = "8080".ToInt(80); // 8080
int invalid = "abc".ToInt(80); // 80 (使用默认值)
int? nullable = "123".TryToInt(); // 123
double price = "99.99".ToDouble(); // 99.99
double? weight = "invalid".TryToDouble(); // null
```
#### 4. JSON 序列化
```csharp
using Common.Utils;
// 序列化(使用 camelCase)
var person = new Person { Name = "John", Age = 30 };
var json = JsonHelper.ToJson(person);
// {"name": "John", "age": 30}
// 反序列化
var loaded = JsonHelper.FromJson<Person>(json);
// 支持 IPAddress
var networkInfo = new NetworkInfo
{
ServerIp = IPAddress.Parse("192.168.1.1")
};
var jsonWithIp = JsonHelper.ToJson(networkInfo);
```
#### 5. 系统工具
```csharp
using Common.Utils;
// 获取可用端口
int port = SystemHelper.GetAvailablePort();
Console.WriteLine($"Server listening on port {port}");
// 检查管理员权限(仅 Windows)
if (OperatingSystem.IsWindows())
{
bool isAdmin = SystemHelper.IsAdministrator();
if (!isAdmin)
{
Console.WriteLine("Please run as administrator");
}
}
```
#### 6. 加密工具
```csharp
using Common.Utils;
// AES 加密/解密
var encrypted = CryptoHelper.AesEncrypt("敏感数据", "MyPassword123");
var decrypted = CryptoHelper.AesDecrypt(encrypted, "MyPassword123");
// RSA 公钥加密
var (publicKey, privateKey) = CryptoHelper.GenerateRsaKeys();
var rsaEncrypted = CryptoHelper.RsaEncrypt("Secret", publicKey);
var rsaDecrypted = CryptoHelper.RsaDecrypt(rsaEncrypted, privateKey);
// 哈希
var md5 = CryptoHelper.Md5Hash("data");
var sha256 = CryptoHelper.Sha256Hash("data");
// 密码哈希和验证(PBKDF2)
var hashedPassword = CryptoHelper.HashPassword("MySecurePassword");
bool isValid = CryptoHelper.VerifyPassword("MySecurePassword", hashedPassword);
// HMAC 签名
var signature = CryptoHelper.HmacSha256("message", "secret");
bool verified = CryptoHelper.VerifyHmacSha256("message", signature, "secret");
```
#### 7. 日期时间扩展
```csharp
using Common.ExpandFun;
var date = DateTime.Now;
// 日期格式化
var chinese = date.ToChineseDateString(); // "2024年01月15日"
var friendly = date.ToFriendlyString(); // "5分钟前"
var iso = date.ToIso8601String(); // "2024-01-15T12:30:45.123Z"
// 时间戳转换
long timestamp = date.ToUnixTimeSeconds();
var fromTimestamp = DateTimeExpand.FromUnixTimeSeconds(timestamp);
// 日期计算
var monthStart = date.StartOfMonth();
var monthEnd = date.EndOfMonth();
var nextWorkDay = date.AddWorkDays(5);
int workDays = date.WorkDaysBetween(date.AddDays(10));
int age = birthDate.CalculateAge();
// 时间范围判断
bool isToday = date.IsToday();
bool isWeekend = date.IsWeekend();
bool inRange = date.IsBetween(start, end);
```
#### 8. 验证工具
```csharp
using Common.Utils;
using Common.Results;
// 基础验证
bool isValidEmail = ValidationHelper.IsValidEmail("user@example.com");
bool isValidMobile = ValidationHelper.IsValidChinaMobile("13812345678");
bool isValidIdCard = ValidationHelper.IsValidChinaIdCard("110105199001150011");
// Result 模式集成
var emailResult = ValidationHelper.ValidateEmail("user@example.com");
if (emailResult.IsSuccess)
{
Console.WriteLine($"Valid email: {emailResult.Value}");
}
// 密码强度验证
var passwordResult = ValidationHelper.ValidatePassword(
"MyPass123!",
minLength: 8,
requireDigit: true,
requireUppercase: true,
requireSpecialChar: true
);
// 组合验证
var result = ValidationHelper.ValidateAll(
userInput,
i => ValidationHelper.ValidateNotEmpty(i, "用户名"),
i => ValidationHelper.ValidateLength(i, 3, 20, "用户名"),
i => ValidationHelper.ValidateRegex(i, "^[a-zA-Z0-9_]+$", "用户名只能包含字母、数字和下划线")
);
// 从身份证提取信息
var birthDate = ValidationHelper.ExtractBirthDateFromIdCard("110105199001150011");
var gender = ValidationHelper.ExtractGenderFromIdCard("110105199001150011");
```
#### 9. 自定义异常
```csharp
using Common.ExpandException;
// 创建带错误码和建议的异常
throw new ExceptionExpand(
404L,
"Resource not found",
"Check the resource ID and try again"
);
// 使用预定义异常
throw ExampleExceptionEnum.ProcessGetException;
// 在 Result 中使用
var exception = new ExceptionExpand(1001L, "Business error", "Check input");
var result = Result.Failure<string>(exception);
Console.WriteLine(result.ErrorCode); // 1001
```
## 详细文档
### Global - 依赖注入容器
#### 注册服务
```csharp
// 注册单例(直接实例)
Global.RegisterSingleton(new AppConfig());
// 注册单例(工厂方法,延迟初始化)
Global.RegisterSingleton(() => new DatabaseConnection("connection-string"));
// 注册瞬态服务
Global.RegisterTransient(() => new RequestContext { Id = Guid.NewGuid() });
// 通用注册方法
Global.Register(() => new CacheService(), ServiceLifetime.Singleton);
```
#### 获取服务
```csharp
// 直接获取(不存在会抛异常)
var config = Global.Get<AppConfig>();
// 安全获取
if (Global.TryGet<ILogger>(out var logger))
{
logger.Log("Found logger");
}
// 获取或使用默认值
var logger = Global.GetOrDefault<ILogger>(new ConsoleLogger());
```
#### 变更通知
```csharp
// 订阅变更
Global.Subscribe<AppConfig>(e =>
{
Console.WriteLine($"Changed at: {e.ChangedAt}");
Console.WriteLine($"Old: {e.OldValue?.AppName}");
Console.WriteLine($"New: {e.NewValue.AppName}");
});
// 更新服务(触发通知)
Global.Update(new AppConfig { AppName = "New Version" });
// 取消订阅
Global.Unsubscribe<AppConfig>(handler);
```
#### 服务管理
```csharp
// 检查服务是否注册
bool exists = Global.IsRegistered<AppConfig>();
// 注销服务
Global.Unregister<AppConfig>();
// 清除所有服务
Global.Clear();
// 获取服务数量
int count = Global.Count;
```
### Result - 函数式错误处理
#### 创建 Result
```csharp
// 成功
var success = Result.Success(); // 无返回值
var successWithValue = Result.Success(42);
// 失败
var failure1 = Result.Failure<int>("Error message");
var failure2 = Result.Failure<int>(new Exception("Error"));
var failure3 = Result.Failure<int>(404L, "Not found");
```
#### Try 模式
```csharp
// 同步
var result = Result.Try(() => int.Parse("123"));
// 异步
var asyncResult = await Result.TryAsync(async () =>
{
await Task.Delay(100);
return 42;
});
```
#### 函数式操作
```csharp
// Map: 转换成功值
var doubled = Result.Success(10).Map(x => x * 2);
// Bind: 链式操作(返回 Result)
var chained = Result.Success(10)
.Bind(x => x > 5 ? Result.Success(x * 2) : Result.Failure<int>("Too small"));
// Where/Ensure: 过滤和验证
var filtered = Result.Success(10)
.Where(x => x > 5, "Value must be > 5")
.Ensure(x => x < 100, "Value must be < 100");
```
#### 值提取
```csharp
var result = Result.Success(42);
// 不同的提取方式
int? nullable = result.GetOrNullable(); // 42
int withDefault = result.GetOrDefault(0); // 42
int orThrow = result.GetOrThrow(); // 42 或抛出异常
int orElse = result.GetOrElse(() => 0); // 42 或执行函数
```
#### 条件执行
```csharp
result
.OnSuccess(value => Console.WriteLine($"Success: {value}"))
.OnFailure((ex, msg) => Console.WriteLine($"Error: {msg}"));
// Match 模式
var message = result.Match(
onSuccess: value => $"Success: {value}",
onFailure: (ex, msg) => $"Error: {msg}"
);
```
#### LINQ 支持
```csharp
var result = from x in Result.Success(5)
from y in Result.Success(10)
where x > 0 && y > 0
select x + y;
```
#### 错误码支持
```csharp
var result = Result.Failure<int>(404L, "Not found");
// 检查错误码
if (result.HasErrorCode(404L))
{
Console.WriteLine("Resource not found");
}
// 获取错误码
long code = result.GetErrorCodeOrDefault(-1);
// 从 ExceptionExpand 创建
var exception = new ExceptionExpand(1001L, "Error", "Suggestion");
var fromException = ResultExtensions.FromExceptionExpand<int>(exception);
```
### 扩展方法
#### StringExpand
```csharp
// URL 验证
"https://example.com".IsValidUrl(); // true
"not-a-url".IsValidUrl(); // false
// 整数转换
"123".ToInt(); // 123
"abc".ToInt(999); // 999 (默认值)
"456".TryToInt(); // 456?
// 浮点数转换
"3.14".ToDouble(); // 3.14
"abc".ToDouble(0.0); // 0.0 (默认值)
"2.5".TryToDouble(); // 2.5?
```
### JsonHelper
```csharp
// 序列化
var json = JsonHelper.ToJson(myObject);
// 反序列化
var obj = JsonHelper.FromJson<MyType>(json);
// 特性
// - 使用 camelCase 命名
// - 格式化输出(缩进)
// - 支持枚举转字符串
// - 支持 IPAddress 类型
```
### SystemHelper
```csharp
// 获取可用端口(用于动态端口绑定)
int port = SystemHelper.GetAvailablePort();
// 检查管理员权限(仅 Windows)
if (OperatingSystem.IsWindows())
{
bool isAdmin = SystemHelper.IsAdministrator();
}
```
## 实际应用场景
### 场景 1:配置热更新
```csharp
// 订阅配置变更
Global.Subscribe<AppConfig>(e =>
{
Console.WriteLine($"Configuration updated: v{e.OldValue?.Version} -> v{e.NewValue.Version}");
// 重新初始化依赖配置的组件
RefreshComponents();
});
// 注册初始配置
Global.RegisterSingleton(LoadConfiguration());
// 文件监控器检测到配置文件变更
void OnConfigFileChanged()
{
var newConfig = LoadConfiguration();
Global.Update(newConfig); // 自动通知所有订阅者
}
```
### 场景 2:数据验证流水线
```csharp
public Result<User> ValidateAndCreateUser(string email, string password, int age)
{
return email.AsResult()
.Ensure(e => !string.IsNullOrEmpty(e), 400L, "Email is required")
.Ensure(e => e.Contains("@"), 400L, "Invalid email format")
.Bind(e => ValidatePassword(password)
.Map(_ => e))
.Bind(e => ValidateAge(age)
.Map(_ => e))
.Map(e => new User { Email = e, Age = age });
}
private Result<string> ValidatePassword(string password)
{
return password.AsResult()
.Ensure(p => p.Length >= 8, 400L, "Password must be at least 8 characters");
}
private Result<int> ValidateAge(int age)
{
return age.AsResult()
.Ensure(a => a >= 18, 400L, "User must be at least 18 years old")
.Ensure(a => a <= 120, 400L, "Invalid age");
}
```
### 场景 3:集成测试动态端口
```csharp
[TestClass]
public class IntegrationTests
{
private TestServer _server;
[TestInitialize]
public void Setup()
{
// 动态分配端口,避免端口冲突
var port = SystemHelper.GetAvailablePort();
_server = new TestServer(port);
_server.Start();
}
[TestCleanup]
public void Cleanup()
{
_server?.Stop();
}
[TestMethod]
public async Task TestApiEndpoint()
{
var client = new HttpClient();
var response = await client.GetAsync($"http://localhost:{_server.Port}/api/health");
Assert.IsTrue(response.IsSuccessStatusCode);
}
}
```
### 场景 4:API 错误处理
```csharp
public async Task<Result<User>> GetUserAsync(int userId)
{
return await Result.TryAsync(async () =>
{
var response = await _httpClient.GetAsync($"/api/users/{userId}");
if (!response.IsSuccessStatusCode)
{
return response.StatusCode switch
{
HttpStatusCode.NotFound => Result.Failure<User>(404L, "User not found"),
HttpStatusCode.Unauthorized => Result.Failure<User>(401L, "Unauthorized"),
_ => Result.Failure<User>(500L, "Server error")
};
}
var json = await response.Content.ReadAsStringAsync();
var user = JsonHelper.FromJson<User>(json);
return user != null
? Result.Success(user)
: Result.Failure<User>("Failed to parse user data");
});
}
// 使用
var result = await GetUserAsync(123);
result
.OnSuccess(user => Console.WriteLine($"Found user: {user.Name}"))
.OnFailure((ex, msg, code) =>
{
if (code == 404L)
Console.WriteLine("User not found, creating new user...");
else
Console.WriteLine($"Error {code}: {msg}");
});
```
## 测试
项目包含完整的单元测试,位于 `test/Common.Test` 目录:
- `GlobalTests.cs` - DI 容器测试(包含线程安全测试)
- `ResultTests.cs` - Result 模式测试(包含 LINQ 和异步测试)
- `ExceptionExpandTests.cs` - 自定义异常测试
- `StringExpandTests.cs` - 字符串扩展测试
- `JsonHelperTests.cs` - JSON 序列化测试
- `SystemHelperTests.cs` - 系统工具测试
运行测试:
```bash
cd test/Common.Test
dotnet test
```
## 最佳实践
### DI 容器
1. **生命周期选择**
- 使用 **Singleton** 用于:配置对象、数据库连接池、缓存服务
- 使用 **Transient** 用于:请求上下文、操作上下文
2. **变更通知**
- 订阅者应该是轻量级的,避免长时间阻塞
- 订阅者中不要抛出异常(已被容器捕获和忽略)
3. **线程安全**
- 容器本身是线程安全的
- 单例服务的初始化是线程安全的(双重检查锁定)
### Result 模式
1. **何时使用 Result**
- 可预期的业务错误(验证失败、资源不存在)
- 外部 API 调用
- 需要传递错误码的场景
2. **何时使用异常**
- 不可预期的程序错误(NullReferenceException、OutOfMemoryException)
- 系统级错误
3. **链式操作**
- 使用 `Map` 转换成功值
- 使用 `Bind` 链接返回 Result 的操作
- 使用 `Ensure` 进行验证
- 失败会自动传播,不需要手动检查
## 项目结构
```
Sharp/Common/
├── Global.cs # DI 容器
├── ExpandException/
│ ├── ExceptionExpand.cs # 自定义异常
│ └── ExampleExceptionEnum.cs # 预定义异常
├── ExpandFun/
│ └── StringExpand.cs # 字符串扩展
├── Results/
│ ├── Result.cs # Result 核心类
│ └── ResultExtensions.cs # Result 扩展方法
└── Utils/
├── JsonHelper.cs # JSON 工具
└── SystemHelper.cs # 系统工具
```
## 许可
本项目为内部库,供团队项目使用。
## 贡献
欢迎提交 Issue 和 Pull Request。
## 版本历史
### v1.0.0
- ✅ 轻量级 DI 容器(支持生命周期和变更通知)
- ✅ Result 模式(完整的函数式错误处理)
- ✅ 自定义异常(带错误码)
- ✅ 字符串扩展(URL 验证、类型转换)
- ✅ JSON 序列化(camelCase、IPAddress 支持)
- ✅ 系统工具(动态端口、管理员检查)
@@ -1,365 +0,0 @@
using Common.ExpandException;
namespace Common.Results;
/// <summary>
/// Examples demonstrating how to use the Result《T》 class and its extensions
/// </summary>
public static class ResultUsageExamples
{
/// <summary>
/// Basic usage examples
/// </summary>
public static void BasicUsageExamples()
{
// Creating successful results
var successResult = Result.Success(42);
var voidSuccessResult = Result.Success();
// Creating failed results
var failureResult = Result.Failure<int>("Something went wrong");
var exceptionResult = Result.Failure<int>(new ArgumentException("Invalid argument"));
// Creating failed results with error codes
var errorCodeResult = Result.Failure<int>(404L, "Not found");
var errorCodeWithExceptionResult = Result.Failure<int>(500L, "Internal server error", new Exception("Database connection failed"));
// Using Try patterns for safe execution
var tryResult = Result.Try(() => int.Parse("123"));
var tryFailResult = Result.Try(() => int.Parse("invalid"));
// Async Try patterns
var asyncResult = Result.TryAsync(async () => await SomeAsyncOperation());
}
/// <summary>
/// Extension methods examples
/// </summary>
public static void ExtensionMethodsExamples()
{
var result = Result.Success(42);
// Safe value extraction
var nullableValue = result.GetOrNullable(); // Returns 42 for value types
var defaultValue = result.GetOrDefault(0); // Returns 42
var throwValue = result.GetOrThrow(); // Returns 42
// Alternative providers
var alternative = result.GetOrElse(() => 0);
var altResult = result.OrElse(() => Result.Success(0));
// Conversion methods
string? nullValue = null;
var nullResult = nullValue.AsResultOrFailure(); // Creates failure result
var valueResult = "test".AsResult(); // Creates success result
// Working with failures
var failResult = Result.Failure<int>("error");
var failNullable = failResult.GetOrNullable(); // Returns null
var failDefault = failResult.GetOrDefault(999); // Returns 999
var exception = failResult.ExceptionOrNull(); // Returns null (no exception, just message)
var errorMsg = failResult.ErrorMessageOrNull(); // Returns "error"
// Working with error codes
var errorCodeResult = Result.Failure<int>(404L, "Not found");
var errorCode = errorCodeResult.ErrorCodeOrNull(); // Returns 404
var hasSpecificCode = errorCodeResult.HasErrorCode(404L); // Returns true
var codeOrDefault = errorCodeResult.GetErrorCodeOrDefault(); // Returns 404
}
/// <summary>
/// Functional programming examples
/// </summary>
public static void FunctionalExamples()
{
var result = Result.Success(10);
// Map: Transform successful values
var doubled = result.Map(x => x * 2); // Result<int> with value 20
// Bind: Chain operations that return Results
var chained = result
.Bind(x => x > 5 ? Result.Success(x * 2) : Result.Failure<int>("Too small"))
.Bind(x => Result.Success(x.ToString()));
// Filter with Where/Ensure
var filtered = result
.Where(x => x > 5, "Value must be greater than 5")
.Ensure(x => x < 100, "Value must be less than 100");
// Filter with error codes
var filteredWithCode = result
.Where(x => x > 5, 400L, "Value must be greater than 5")
.Ensure(x => x < 100, 422L, "Value must be less than 100");
// Conditional execution
result.OnSuccess(value => Console.WriteLine($"Value: {value}"));
result.OnFailure((ex, msg) => Console.WriteLine($"Error: {msg}"));
result.OnFailure((ex, msg, code) => Console.WriteLine($"Error {code}: {msg}"));
// Pattern matching
var message = result.Match(
onSuccess: value => $"Success: {value}",
onFailure: (ex, msg) => $"Error: {msg}");
var messageWithCode = result.Match(
onSuccess: value => $"Success: {value}",
onFailure: (ex, msg, code) => $"Error {code}: {msg}");
}
/// <summary>
/// LINQ query syntax examples
/// </summary>
public static void LinqExamples()
{
// Using LINQ query syntax with Results
var result1 = Result.Success(5);
var result2 = Result.Success(10);
var combined = from x in result1
from y in result2
where x > 0 && y > 0
select x + y;
// More complex LINQ operations
var processed = from value in Result.Success("hello")
let upper = value.ToUpper()
where upper.Length > 3
select $"Processed: {upper}";
}
/// <summary>
/// Async operations examples
/// </summary>
public static async Task AsyncExamples()
{
// Async Try patterns
var asyncResult = await Result.TryAsync(async () =>
{
await Task.Delay(100);
return 42;
});
// Working with async results
if (asyncResult.IsSuccess)
{
await Task.Delay(50);
Console.WriteLine($"Processing: {asyncResult.Value}");
}
// Async mapping and binding
var mappedResult = await Task.FromResult(Result.Success(10))
.MapAsync(async value =>
{
await Task.Delay(50);
return value * 2;
});
var boundResult = await Task.FromResult(Result.Success(5))
.BindAsync(async value =>
{
await Task.Delay(50);
return value > 0 ? Result.Success(value * 2) : Result.Failure<int>("Invalid");
});
}
/// <summary>
/// Error handling patterns
/// </summary>
public static void ErrorHandlingExamples()
{
// Multiple validation steps
var input = "123";
var validationResult = input.AsResult()
.Ensure(s => !string.IsNullOrEmpty(s), "Input cannot be empty")
.Bind(s => Result.Try(() => int.Parse(s)))
.Ensure(i => i > 0, "Number must be positive")
.Map(i => i * 2);
// Handling different error scenarios
validationResult
.OnSuccess(value => Console.WriteLine($"Valid result: {value}"))
.OnFailure((ex, msg, code) =>
{
if (ex is FormatException)
Console.WriteLine("Invalid number format");
else if (code.HasValue)
Console.WriteLine($"Validation error {code}: {msg}");
else
Console.WriteLine($"Validation error: {msg}");
});
// Providing fallback values
var safeResult = validationResult.OrElse(() => Result.Success(0));
var finalValue = validationResult.GetOrElse(() => 0);
// Error code specific handling
var safeWithCodeHandling = validationResult.OrElse((ex, msg, code) =>
{
return code switch
{
400L => Result.Success(0), // Bad request -> default
422L => Result.Success(-1), // Validation -> sentinel
_ => Result.Success(999) // Unknown -> special value
};
});
}
/// <summary>
/// Integration with existing code examples
/// </summary>
public static void IntegrationExamples()
{
// Converting existing exception-throwing code
var legacyResult = Result.Try(() => RiskyOperation());
// Working with nullable types
string? nullableString = GetNullableString();
var nullableResult = nullableString.AsResultOrFailure("String was null");
// Converting Result back to exceptions when needed
try
{
var value = legacyResult.GetOrThrow();
// Use value...
}
catch (Exception ex)
{
// Handle exception...
}
// Ignoring typed results when only success/failure matters
var voidResult = Result.Success(42).Ignore();
}
/// <summary>
/// Error code specific examples
/// </summary>
public static void ErrorCodeExamples()
{
// Creating failures with error codes
var notFoundResult = Result.Failure<string>(404L, "Resource not found");
var validationResult = Result.Failure<int>(422L, "Invalid input");
var serverError = Result.Failure<object>(500L, "Internal server error", new Exception("Database connection failed"));
// Using ExceptionExpand with error codes
var customException = new ExceptionExpand(1001L, "Custom business error", "Check your input parameters");
var fromException = ResultExtensions.FromExceptionExpand<string>(customException);
// Error code checking and handling
if (notFoundResult.HasErrorCode(404L))
{
Console.WriteLine("Handle not found error");
}
// Switch on error codes
var errorCode = validationResult.GetErrorCodeOrDefault();
var response = errorCode switch
{
404L => "Resource not found",
422L => "Validation failed",
500L => "Server error",
_ => "Unknown error"
};
// Error code in functional chains
var processedResult = Result.Success("user@example.com")
.Ensure(email => email.Contains("@"), 400L, "Invalid email format")
.Bind(email => ValidateEmailDomain(email))
.Map(email => $"Welcome, {email}");
// Handling specific error codes
processedResult.OnFailure((ex, msg, code) =>
{
var logLevel = code switch
{
400L => "WARN", // Validation errors
404L => "INFO", // Not found
500L => "ERROR", // Server errors
_ => "DEBUG"
};
Console.WriteLine($"[{logLevel}] Error {code}: {msg}");
});
// Creating error code specific results
var unauthorizedResult = ResultExtensions.FailWithCode<string>(401L, "Unauthorized access");
var forbiddenResult = Result.Failure<string>(403L, "Insufficient permissions");
// Combining results with error code preservation
var combinedResult = from user in GetUserById(123)
from permissions in GetUserPermissions(user)
where permissions.CanRead
select user;
}
/// <summary>
/// HTTP status code style error handling
/// </summary>
public static void HttpStatusCodeStyleExamples()
{
// Common HTTP status codes as error codes
var badRequest = Result.Failure<string>(400L, "Bad Request");
var unauthorized = Result.Failure<string>(401L, "Unauthorized");
var forbidden = Result.Failure<string>(403L, "Forbidden");
var notFound = Result.Failure<string>(404L, "Not Found");
var conflict = Result.Failure<string>(409L, "Conflict");
var unprocessable = Result.Failure<string>(422L, "Unprocessable Entity");
var internalError = Result.Failure<string>(500L, "Internal Server Error");
// Business logic error codes
var businessError = Result.Failure<string>(1001L, "Insufficient balance");
var domainError = Result.Failure<string>(2001L, "Invalid user state");
var integrationError = Result.Failure<string>(3001L, "External service unavailable");
// Error code ranges for different error types
var validationError = Result.Failure<string>(4001L, "Required field missing"); // 4xxx: Validation
var businessLogicError = Result.Failure<string>(5001L, "Business rule violation"); // 5xxx: Business
var systemError = Result.Failure<string>(6001L, "Database connection failed"); // 6xxx: System
}
// Helper methods for examples
private static int RiskyOperation() => throw new InvalidOperationException("Something went wrong");
private static string? GetNullableString() => null;
private static async Task<string> SomeAsyncOperation()
{
await Task.Delay(100);
return "async result";
}
private static Result<string> ValidateEmailDomain(string email)
{
var domain = email.Split('@').LastOrDefault();
return domain switch
{
"blocked.com" => Result.Failure<string>(403L, "Domain is blocked"),
null => Result.Failure<string>(400L, "Invalid email format"),
_ => Result.Success(email)
};
}
private static Result<User> GetUserById(int id)
{
return id switch
{
123 => Result.Success(new User { Id = 123, Name = "John" }),
_ => Result.Failure<User>(404L, "User not found")
};
}
private static Result<UserPermissions> GetUserPermissions(User user)
{
return Result.Success(new UserPermissions { CanRead = true, CanWrite = false });
}
private class User
{
public int Id { get; set; }
public string Name { get; set; } = "";
}
private class UserPermissions
{
public bool CanRead { get; set; }
public bool CanWrite { get; set; }
}
}
+515
View File
@@ -0,0 +1,515 @@
using System.Text.RegularExpressions;
using Common.Results;
namespace Common.Utils;
/// <summary>
/// 验证工具类
/// 提供常见数据验证功能,并与 Result 模式集成
/// </summary>
public static class ValidationHelper
{
#region 邮箱验证
private static readonly Regex EmailRegex = new(
@"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
RegexOptions.Compiled | RegexOptions.IgnoreCase
);
/// <summary>
/// 验证邮箱地址格式
/// </summary>
/// <param name="email">邮箱地址</param>
/// <returns>是否有效</returns>
public static bool IsValidEmail(string? email)
{
if (string.IsNullOrWhiteSpace(email))
return false;
return EmailRegex.IsMatch(email);
}
/// <summary>
/// 验证邮箱地址格式(Result 模式)
/// </summary>
/// <param name="email">邮箱地址</param>
/// <param name="errorCode">错误码(默认 400)</param>
/// <returns>验证结果</returns>
public static Result<string> ValidateEmail(string? email, long errorCode = 400)
{
if (string.IsNullOrWhiteSpace(email))
return Result.Failure<string>(errorCode, "邮箱地址不能为空");
if (!EmailRegex.IsMatch(email))
return Result.Failure<string>(errorCode, "邮箱地址格式无效");
return Result.Success(email);
}
#endregion
#region 手机号验证
// 中国大陆手机号:1 开头,第二位是 3-9,共 11 位
private static readonly Regex ChinaMobileRegex = new(
@"^1[3-9]\d{9}$",
RegexOptions.Compiled
);
/// <summary>
/// 验证中国大陆手机号格式
/// </summary>
/// <param name="mobile">手机号</param>
/// <returns>是否有效</returns>
public static bool IsValidChinaMobile(string? mobile)
{
if (string.IsNullOrWhiteSpace(mobile))
return false;
return ChinaMobileRegex.IsMatch(mobile);
}
/// <summary>
/// 验证中国大陆手机号格式(Result 模式)
/// </summary>
/// <param name="mobile">手机号</param>
/// <param name="errorCode">错误码(默认 400)</param>
/// <returns>验证结果</returns>
public static Result<string> ValidateChinaMobile(string? mobile, long errorCode = 400)
{
if (string.IsNullOrWhiteSpace(mobile))
return Result.Failure<string>(errorCode, "手机号不能为空");
if (!ChinaMobileRegex.IsMatch(mobile))
return Result.Failure<string>(errorCode, "手机号格式无效(应为11位数字,1开头)");
return Result.Success(mobile);
}
/// <summary>
/// 验证国际手机号格式(包含国际区号)
/// </summary>
/// <param name="mobile">手机号(格式:+区号-号码,如 +86-13812345678)</param>
/// <returns>是否有效</returns>
public static bool IsValidInternationalMobile(string? mobile)
{
if (string.IsNullOrWhiteSpace(mobile))
return false;
// 匹配 +区号-号码 或 +区号号码 格式
var regex = new Regex(@"^\+\d{1,3}[-\s]?\d{6,14}$");
return regex.IsMatch(mobile);
}
#endregion
#region 身份证验证
// 18 位身份证号(最后一位可以是数字或 X)
private static readonly Regex IdCardRegex = new(
@"^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$",
RegexOptions.Compiled
);
/// <summary>
/// 验证中国大陆身份证号格式(18位)
/// </summary>
/// <param name="idCard">身份证号</param>
/// <returns>是否有效</returns>
public static bool IsValidChinaIdCard(string? idCard)
{
if (string.IsNullOrWhiteSpace(idCard))
return false;
if (!IdCardRegex.IsMatch(idCard))
return false;
// 验证校验码
return ValidateIdCardChecksum(idCard);
}
/// <summary>
/// 验证中国大陆身份证号格式(Result 模式)
/// </summary>
/// <param name="idCard">身份证号</param>
/// <param name="errorCode">错误码(默认 400)</param>
/// <returns>验证结果</returns>
public static Result<string> ValidateChinaIdCard(string? idCard, long errorCode = 400)
{
if (string.IsNullOrWhiteSpace(idCard))
return Result.Failure<string>(errorCode, "身份证号不能为空");
if (!IdCardRegex.IsMatch(idCard))
return Result.Failure<string>(errorCode, "身份证号格式无效(应为18位)");
if (!ValidateIdCardChecksum(idCard))
return Result.Failure<string>(errorCode, "身份证号校验码无效");
return Result.Success(idCard.ToUpperInvariant());
}
private static bool ValidateIdCardChecksum(string idCard)
{
if (idCard.Length != 18)
return false;
// 加权因子
int[] weights = { 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 };
// 校验码对应值
char[] checkCodes = { '1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2' };
int sum = 0;
for (int i = 0; i < 17; i++)
{
if (!char.IsDigit(idCard[i]))
return false;
sum += (idCard[i] - '0') * weights[i];
}
int mod = sum % 11;
char expectedCheckCode = checkCodes[mod];
char actualCheckCode = char.ToUpperInvariant(idCard[17]);
return expectedCheckCode == actualCheckCode;
}
/// <summary>
/// 从身份证号提取出生日期
/// </summary>
/// <param name="idCard">身份证号</param>
/// <returns>出生日期(如果格式无效返回 null)</returns>
public static DateTime? ExtractBirthDateFromIdCard(string? idCard)
{
if (string.IsNullOrWhiteSpace(idCard) || idCard.Length != 18)
return null;
try
{
int year = int.Parse(idCard.Substring(6, 4));
int month = int.Parse(idCard.Substring(10, 2));
int day = int.Parse(idCard.Substring(12, 2));
return new DateTime(year, month, day);
}
catch
{
return null;
}
}
/// <summary>
/// 从身份证号提取性别
/// </summary>
/// <param name="idCard">身份证号</param>
/// <returns>性别(Male/Female,如果格式无效返回 null)</returns>
public static string? ExtractGenderFromIdCard(string? idCard)
{
if (string.IsNullOrWhiteSpace(idCard) || idCard.Length != 18)
return null;
try
{
int genderCode = int.Parse(idCard.Substring(16, 1));
return genderCode % 2 == 0 ? "Female" : "Male";
}
catch
{
return null;
}
}
#endregion
#region URL 验证
/// <summary>
/// 验证 URL 格式(使用 Uri.TryCreate)
/// </summary>
/// <param name="url">URL</param>
/// <returns>是否有效</returns>
public static bool IsValidUrl(string? url)
{
if (string.IsNullOrWhiteSpace(url))
return false;
return Uri.TryCreate(url, UriKind.Absolute, out var uriResult)
&& (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps);
}
/// <summary>
/// 验证 URL 格式(Result 模式)
/// </summary>
/// <param name="url">URL</param>
/// <param name="errorCode">错误码(默认 400)</param>
/// <returns>验证结果</returns>
public static Result<string> ValidateUrl(string? url, long errorCode = 400)
{
if (string.IsNullOrWhiteSpace(url))
return Result.Failure<string>(errorCode, "URL 不能为空");
if (!Uri.TryCreate(url, UriKind.Absolute, out var uriResult))
return Result.Failure<string>(errorCode, "URL 格式无效");
if (uriResult.Scheme != Uri.UriSchemeHttp && uriResult.Scheme != Uri.UriSchemeHttps)
return Result.Failure<string>(errorCode, "URL 必须是 HTTP 或 HTTPS 协议");
return Result.Success(url);
}
#endregion
#region 数值范围验证
/// <summary>
/// 验证整数在指定范围内(Result 模式)
/// </summary>
/// <param name="value">要验证的值</param>
/// <param name="min">最小值(包含)</param>
/// <param name="max">最大值(包含)</param>
/// <param name="fieldName">字段名称</param>
/// <param name="errorCode">错误码(默认 400)</param>
/// <returns>验证结果</returns>
public static Result<int> ValidateRange(int value, int min, int max, string fieldName = "值", long errorCode = 400)
{
if (value < min || value > max)
return Result.Failure<int>(errorCode, $"{fieldName}必须在 {min} 到 {max} 之间");
return Result.Success(value);
}
/// <summary>
/// 验证浮点数在指定范围内(Result 模式)
/// </summary>
public static Result<double> ValidateRange(double value, double min, double max, string fieldName = "值", long errorCode = 400)
{
if (value < min || value > max)
return Result.Failure<double>(errorCode, $"{fieldName}必须在 {min} 到 {max} 之间");
return Result.Success(value);
}
#endregion
#region 字符串长度验证
/// <summary>
/// 验证字符串长度(Result 模式)
/// </summary>
/// <param name="value">字符串值</param>
/// <param name="minLength">最小长度</param>
/// <param name="maxLength">最大长度</param>
/// <param name="fieldName">字段名称</param>
/// <param name="errorCode">错误码(默认 400)</param>
/// <returns>验证结果</returns>
public static Result<string> ValidateLength(string? value, int minLength, int maxLength, string fieldName = "字段", long errorCode = 400)
{
if (string.IsNullOrEmpty(value))
return Result.Failure<string>(errorCode, $"{fieldName}不能为空");
if (value.Length < minLength)
return Result.Failure<string>(errorCode, $"{fieldName}长度不能少于 {minLength} 个字符");
if (value.Length > maxLength)
return Result.Failure<string>(errorCode, $"{fieldName}长度不能超过 {maxLength} 个字符");
return Result.Success(value);
}
/// <summary>
/// 验证字符串非空(Result 模式)
/// </summary>
public static Result<string> ValidateNotEmpty(string? value, string fieldName = "字段", long errorCode = 400)
{
if (string.IsNullOrWhiteSpace(value))
return Result.Failure<string>(errorCode, $"{fieldName}不能为空");
return Result.Success(value);
}
#endregion
#region 密码强度验证
/// <summary>
/// 验证密码强度
/// </summary>
/// <param name="password">密码</param>
/// <param name="minLength">最小长度(默认 8)</param>
/// <param name="requireDigit">是否必须包含数字</param>
/// <param name="requireLowercase">是否必须包含小写字母</param>
/// <param name="requireUppercase">是否必须包含大写字母</param>
/// <param name="requireSpecialChar">是否必须包含特殊字符</param>
/// <returns>是否符合强度要求</returns>
public static bool IsStrongPassword(
string? password,
int minLength = 8,
bool requireDigit = true,
bool requireLowercase = true,
bool requireUppercase = true,
bool requireSpecialChar = true)
{
if (string.IsNullOrWhiteSpace(password))
return false;
if (password.Length < minLength)
return false;
if (requireDigit && !password.Any(char.IsDigit))
return false;
if (requireLowercase && !password.Any(char.IsLower))
return false;
if (requireUppercase && !password.Any(char.IsUpper))
return false;
if (requireSpecialChar && !password.Any(c => !char.IsLetterOrDigit(c)))
return false;
return true;
}
/// <summary>
/// 验证密码强度(Result 模式)
/// </summary>
public static Result<string> ValidatePassword(
string? password,
int minLength = 8,
bool requireDigit = true,
bool requireLowercase = true,
bool requireUppercase = true,
bool requireSpecialChar = true,
long errorCode = 400)
{
if (string.IsNullOrWhiteSpace(password))
return Result.Failure<string>(errorCode, "密码不能为空");
if (password.Length < minLength)
return Result.Failure<string>(errorCode, $"密码长度不能少于 {minLength} 个字符");
var errors = new List<string>();
if (requireDigit && !password.Any(char.IsDigit))
errors.Add("至少包含一个数字");
if (requireLowercase && !password.Any(char.IsLower))
errors.Add("至少包含一个小写字母");
if (requireUppercase && !password.Any(char.IsUpper))
errors.Add("至少包含一个大写字母");
if (requireSpecialChar && !password.Any(c => !char.IsLetterOrDigit(c)))
errors.Add("至少包含一个特殊字符");
if (errors.Any())
return Result.Failure<string>(errorCode, $"密码强度不足:{string.Join("、", errors)}");
return Result.Success(password);
}
#endregion
#region 通用正则表达式验证
/// <summary>
/// 使用自定义正则表达式验证
/// </summary>
/// <param name="value">要验证的值</param>
/// <param name="pattern">正则表达式模式</param>
/// <param name="errorMessage">错误消息</param>
/// <param name="errorCode">错误码(默认 400)</param>
/// <returns>验证结果</returns>
public static Result<string> ValidateRegex(string? value, string pattern, string errorMessage = "格式无效", long errorCode = 400)
{
if (string.IsNullOrWhiteSpace(value))
return Result.Failure<string>(errorCode, "值不能为空");
var regex = new Regex(pattern);
if (!regex.IsMatch(value))
return Result.Failure<string>(errorCode, errorMessage);
return Result.Success(value);
}
#endregion
#region IP 地址验证
/// <summary>
/// 验证 IPv4 地址格式
/// </summary>
public static bool IsValidIPv4(string? ip)
{
if (string.IsNullOrWhiteSpace(ip))
return false;
return System.Net.IPAddress.TryParse(ip, out var address)
&& address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork;
}
/// <summary>
/// 验证 IP 地址格式(Result 模式)
/// </summary>
public static Result<string> ValidateIPAddress(string? ip, long errorCode = 400)
{
if (string.IsNullOrWhiteSpace(ip))
return Result.Failure<string>(errorCode, "IP 地址不能为空");
if (!System.Net.IPAddress.TryParse(ip, out _))
return Result.Failure<string>(errorCode, "IP 地址格式无效");
return Result.Success(ip);
}
#endregion
#region 组合验证
/// <summary>
/// 组合多个验证规则(所有规则都必须通过)
/// </summary>
/// <typeparam name="T">值类型</typeparam>
/// <param name="value">要验证的值</param>
/// <param name="validators">验证器列表</param>
/// <returns>验证结果</returns>
public static Result<T> ValidateAll<T>(T value, params Func<T, Result<T>>[] validators)
{
var result = Result.Success(value);
foreach (var validator in validators)
{
result = result.Bind(validator);
if (result.IsFailure)
break;
}
return result;
}
/// <summary>
/// 组合多个验证规则(任一规则通过即可)
/// </summary>
/// <typeparam name="T">值类型</typeparam>
/// <param name="value">要验证的值</param>
/// <param name="validators">验证器列表</param>
/// <returns>验证结果</returns>
public static Result<T> ValidateAny<T>(T value, params Func<T, Result<T>>[] validators)
{
var errors = new List<string>();
foreach (var validator in validators)
{
var result = validator(value);
if (result.IsSuccess)
return result;
if (result.ErrorMessage != null)
errors.Add(result.ErrorMessage);
}
return Result.Failure<T>($"所有验证规则都未通过:{string.Join("; ", errors)}");
}
#endregion
}