A:Common配置
This commit is contained in:
1 parent
61e769a0f6
commit
c8fde9759f
8 files changed
+3742
-703
No files matched your search
@@ -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
|
||||
}
|
||||
Reference in new issue
Block a user