diff --git a/Admin.NET/Admin.NET.Core/Attribute/IdCardNoAttribute.cs b/Admin.NET/Admin.NET.Core/Attribute/IdCardNoAttribute.cs new file mode 100644 index 00000000..fe750a43 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Attribute/IdCardNoAttribute.cs @@ -0,0 +1,40 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 身份证号码合规性校验特性 +/// +[SuppressSniffer] +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true, Inherited = true)] +public class IdCardNoAttribute : ValidationAttribute +{ + /// + /// 是否允许空字符串 + /// + private bool AllowEmptyStrings { get; set; } = false; + + /// + /// 允许空值,有值才验证,默认 false + /// + private bool AllowNullValue { get; set; } = false; + + protected override ValidationResult IsValid(object value, ValidationContext validationContext) + { + // 判断是否允许空值 + if (AllowNullValue && value == null) return ValidationResult.Success; + + string idCardNo = value?.ToString(); + + // 是否允许空字符串 + if (AllowEmptyStrings && string.IsNullOrEmpty(idCardNo)) return ValidationResult.Success; + + if (!IdCardHelper.CheckIdCard(idCardNo)) return new ValidationResult($"身份证号码({idCardNo})校验未通过, 请检查证件号的有效性!"); + + return ValidationResult.Success; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/IdCardHelper.cs b/Admin.NET/Admin.NET.Core/Utils/IdCardHelper.cs new file mode 100644 index 00000000..8d38479c --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/IdCardHelper.cs @@ -0,0 +1,235 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using System.Globalization; + +namespace Admin.NET.Core; + +public class IdCardHelper +{ + /// + /// 验证身份证合理性 + /// + /// + /// + public static bool CheckIdCard(string idNumber) + { + return CheckIdCard18(idNumber); + } + + /// + /// 18位身份证号码验证(该实现严格遵循GB11643-1999标准) + /// + private static bool CheckIdCard18(string idNumber) + { + // 检查长度是否为18位 + if (idNumber?.Length != 18) return false; + + // 数字验证:前17位必须为数字,最后一位为数字或X/x + for (int i = 0; i < 17; i++) + { + if (!char.IsDigit(idNumber[i])) + return false; + } + char lastChar = idNumber[17]; + if (!char.IsDigit(lastChar) && char.ToUpperInvariant(lastChar) != 'X') + return false; + + // 省份验证 + string[] provinceCodes = { "11", "22", "35", "44", "53", "12", "23", "36", "45", "54", "13", + "31", "37", "46", "61", "14", "32", "41", "50", "62", "15", "33", + "42", "51", "63", "21", "34", "43", "52", "64", "65", "71", "81", "82", "91" }; + if (!provinceCodes.Contains(idNumber.Substring(0, 2))) + return false; + + // 生日验证(使用明确格式) + if (!DateTime.TryParseExact( + $"{idNumber.Substring(6, 4)}-{idNumber.Substring(10, 2)}-{idNumber.Substring(12, 2)}", + "yyyy-MM-dd", + CultureInfo.InvariantCulture, + DateTimeStyles.None, + out _)) + { + return false; + } + + // 校验码验证 + int[] weightFactors = { 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 }; + char[] checkCodes = "10X98765432".ToCharArray(); + + int sum = 0; + for (int i = 0; i < 17; i++) + { + sum += weightFactors[i] * (idNumber[i] - '0'); + } + + int checkIndex = sum % 11; + char expectedCode = checkCodes[checkIndex]; + + // 不区分大小写比较 + if (char.ToUpperInvariant(idNumber[17]) != char.ToUpperInvariant(expectedCode)) + return false; + + return true; + } + + /// + /// 15位身份证号码验证 + /// + private static bool CheckIdCard15(string idNumber) + { + long n = 0; + if (long.TryParse(idNumber, out n) == false || n < Math.Pow(10, 14)) + { + return false;//数字验证 + } + string address = "11x22x35x44x53x12x23x36x45x54x13x31x37x46x61x14x32x41x50x62x15x33x42x51x63x21x34x43x52x64x65x71x81x82x91"; + if (address.IndexOf(idNumber.Remove(2)) == -1) + { + return false;//省份验证 + } + string birth = idNumber.Substring(6, 6).Insert(4, "-").Insert(2, "-"); + DateTime time = new DateTime(); + if (DateTime.TryParse(birth, out time) == false) + { + return false;//生日验证 + } + return true; + } + + /// + /// 根据身份证号获取性别代码(Code) + /// + /// + /// + public static GenderEnum GetGenderCode(string idNumber) + { + string gender = GetGender(idNumber); + return gender switch + { + "男" => GenderEnum.Male, + "女" => GenderEnum.Female, + "未知的性别" => GenderEnum.Unknown, + "未说明的性别" => GenderEnum.Unspecified, + _ => GenderEnum.Unknown, + }; + } + + /// + /// 根据身份证号获取性别 + /// + /// + /// + public static string GetGender(string idNumber) + { + if (string.IsNullOrWhiteSpace(idNumber)) return string.Empty; + try + { + string strSex = ""; + if (idNumber.Length == 18) strSex = idNumber.Substring(14, 3); + + //性别代码为偶数是女性奇数为男性 + strSex = int.Parse(strSex) % 2 == 0 ? "女" : "男"; + return strSex; + } + catch + { + return string.Empty; + } + } + + /// + /// 根据身份证号,计算精确的年龄 + /// + /// 生日 + /// + public static string GetAge(string idNumber) + { + if (string.IsNullOrWhiteSpace(idNumber)) return null; + + try + { + DateTime birthDate = DateTime.Today; + if (idNumber.Length == 18) + { + birthDate = new DateTime( + int.Parse(idNumber.Substring(6, 4)), + int.Parse(idNumber.Substring(10, 2)), + int.Parse(idNumber.Substring(12, 2))); + } + else if (idNumber.Length == 15) + { + birthDate = new DateTime( + int.Parse("19" + idNumber.Substring(6, 2)), + int.Parse(idNumber.Substring(8, 2)), + int.Parse(idNumber.Substring(10, 2))); + } + + DateTime today = DateTime.Today; + double totalDays = (today - birthDate).TotalDays; + double exactAge = totalDays / 365.2425; // 使用天文年平均值 + exactAge = Math.Round(exactAge, 1); + + return exactAge.ToString("0.0"); + } + catch + { + return string.Empty; + } + } + + /// + /// 根据身份证号获取出生日期 + /// + /// + /// + public static DateTime GetBirthday(string idNumber) + { + if (string.IsNullOrWhiteSpace(idNumber)) return default; + + try + { + string birthday = ""; + if (idNumber.Length == 18) + birthday = idNumber.Substring(6, 4) + "-" + idNumber.Substring(10, 2) + "-" + idNumber.Substring(12, 2); + + if (idNumber.Length == 15) + birthday = "19" + idNumber.Substring(6, 2) + "-" + idNumber.Substring(8, 2) + "-" + idNumber.Substring(10, 2); + + return DateTime.Parse(birthday); + } + catch + { + return default; + } + } + + /// + /// 根据出生日期,计算精确的年龄 + /// + /// 生日 + /// + public static string GetAgeByBirthDay(string birthDay) + { + string age = ""; + try + { + DateTime birthDate = DateTime.Parse(birthDay); + DateTime nowDateTime = DateTime.Now; + int _age = nowDateTime.Year - birthDate.Year; + //再考虑月、天的因素 + if (nowDateTime.Month < birthDate.Month || (nowDateTime.Month == birthDate.Month && nowDateTime.Day < birthDate.Day)) + { + _age--; + } + return _age.ToString(); + } + catch + { + return age; + } + } +} \ No newline at end of file