88bf必发娱乐【NumberValidators】大陆身份证验证

需要说明的是这里的大陆身份证识别并不是公安局联网的识别,而是按国标GB
11643进行的验证,所以其验证结果只能说符合国标规范,但不能保证该身份证一定真实存在,如果你实际需求是希望身份证一定真实存在,那么你可以在通过此类库初步验证后,再调用第三方(或牛逼的可以直连公安,毕竟所有的第三方其数据来源必定是公安局)以降低调用成本(公安调用一次两块钱,还不是有钱就能调用!!!)

同大陆身份证验证一样,该部分是按照国家增值税发票代码的定制规则,进行发票代码验证,如果需要查验发票信息是否正确,应该通过第三方接口(大约一毛钱查验一次),或者直接上国家税务总局全国增值税发票查验平台进行查验。

转到正题,在NumberValidators中,大陆身份证相关的代码均在NumberValidators.IdentityCards下,具体的验证实现为:ID18Validator(第二代身份证,长度为18),ID15Validator(第一代身份证,长度为15),而IDValidatorHelper则为所有实现了IIDValidator(身份证识别接口)且完全按照 ID{Length}Validator 格式命名的身份证识别自动适配静态类(Length为证件号码长度),除基础的验证外,还提供了TryPromotion方法(一代身份证升位成二代身份证)

目前能识别的增值税发票代码包含以下几类:增值税专用发票增值税普通发票(纸质非卷票)增值税普通发票(卷票)增值税电子普通发票。在类库中,增值税代码验证相关的代码均在NumberValidators.Invoices下,其包含接口定义以及具体实现。

IIDValidator(身份证识别接口)定义如下

IVATCodeValidator(增值税代码识别接口)定义如下:

    /// <summary>
    /// 所有号码验证类均需实现的基础接口定义
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface IValidator<out T>
        where T : ValidationResult, new()
    {
        /// <summary>
        /// 随机生成一个符合规则的号码
        /// </summary>
        /// <returns></returns>
        string GenerateRandomNumber();
        /// <summary>
        /// 验证号码是否正确
        /// </summary>
        /// <param name="number"></param>
        /// <returns></returns>
        T Validate(string number);
    }

    /// <summary>
    /// 身份证验证接口
    /// </summary>
    public interface IIDValidator : IValidator<IDValidationResult>
    {
        /// <summary>
        /// 用于验证的字典数据
        /// </summary>
        IValidationDictionary<int, string> Dictionary { get; set; }
        /// <summary>
        /// 生成身份证号码
        /// </summary>
        /// <param name="areaNumber">行政区划编号</param>
        /// <param name="birthDay">出生日期</param>
        /// <param name="sequenceNumber">顺序号</param>
        /// <returns></returns>
        string GenerateID(int areaNumber, DateTime birthDay, int sequenceNumber);
        /// <summary>
        /// 验证身份证是否正确
        /// </summary>
        /// <param name="idNumber">待验证的证件号码</param>
        /// <param name="minYear">允许最小年份,默认0</param>
        /// <param name="validLimit">验证区域级别,默认AreaValidLimit.Province</param>
        /// <param name="ignoreCheckBit">是否忽略校验位验证,默认false</param>
        /// <returns>验证结果</returns>
        IDValidationResult Validate(string idNumber, ushort minYear = 0, AreaValidLimit validLimit = AreaValidLimit.Province, bool ignoreCheckBit = false);
    }  
    /// <summary>
    /// 增值税发票代码验证接口
    /// </summary>
    public interface IVATCodeValidator <out TResult>: IValidator<TResult>
        where TResult : VATCodeValidationResult, new()
    {
        /// <summary>
        /// 用于验证的字典数据
        /// </summary>
        IValidationDictionary<int, string> Dictionary { get; set; }
        /// <summary>
        /// 生成增值税发票代码
        /// </summary>
        /// <param name="areaNumber">行政区划</param>
        /// <param name="year">年份</param>
        /// <param name="batch">批次</param>
        /// <param name="kind">要生成的发票类型</param>
        /// <returns></returns>
        string GenerateVATCode(int areaNumber, ushort year, ushort batch, VATKind kind);
        /// <summary>
        /// 发票代码验证
        /// </summary>
        /// <param name="vatCode">待验证的发票代码</param>
        /// <param name="kind">要验证的发票类型,不指定则传null</param>
        /// <param name="minYear">允许的最小年份(注:2012年1月1日营改增开始上海试点)</param>
        /// <returns></returns>
        TResult Validate(string vatCode, VATKind? kind = null, ushort minYear = 2012);
    }

验证结果IDValidationResult定义如下

增值税发票代码验证定义了两种验证结果
VATCodeValidationResult这是默认验证结果,其定义如下:

    /// <summary>
    /// 号码验证结果类
    /// </summary>
    public class ValidationResult
    {
        /// <summary>
        /// 验证结果是否通过
        /// </summary>
        public bool IsValid { get; internal set; } = true;
        /// <summary>
        /// 如果验证不通过,这里包含验证失败的原因
        /// </summary>
        public IList<string> Errors { get; internal set; } = new List<string>();
        /// <summary>
        /// 当前验证的号码
        /// </summary>
        public string Number { get; internal set; }

        /// <summary>
        /// 添加错误信息
        /// </summary>
        /// <param name="errorMsg"></param>
        /// <param name="parameters"></param>
        internal void AddErrorMessage(string errorMsg, params object[] parameters)
        {
            if (parameters != null && parameters.Length > 0)
            {
                errorMsg = string.Format(errorMsg, parameters);
            }
            this.Errors.Add(errorMsg);
            this.IsValid = false;
        }
    }

    /// <summary>
    /// 身份证验证结果类
    /// </summary>
    public class IDValidationResult : ValidationResult
    {
        /// <summary>
        /// 身份证号码长度
        /// </summary>
        public IDLength IDLength { get; internal set; }
        /// <summary>
        /// 身份证上的出生日期
        /// </summary>
        public DateTime Birthday { get; internal set; }
        /// <summary>
        /// 性别
        /// </summary>
        public Gender Gender { get; internal set; }
        /// <summary>
        /// 行政区划编码
        /// </summary>
        public int AreaNumber { get; internal set; }
        /// <summary>
        /// 身份证颁发行政区域(识别出Depth最深的区域),可通过FullName来获取完整的区域名
        /// </summary>
        public Area RecognizableArea { get; internal set; }
        /// <summary>
        /// 出生登记顺序号
        /// </summary>
        public int Sequence { get; internal set; }
        /// <summary>
        /// 身份证校验码
        /// </summary>
        public char CheckBit { get; internal set; }
    }
    /// <summary>
    /// 增值税发票代码验证结果
    /// </summary>
    public class VATCodeValidationResult : ValidationResult
    {
        /// <summary>
        /// 行政区划代码
        /// </summary>
        public int AreaNumber { get; internal set; }
        /// <summary>
        /// 行政区域名称
        /// </summary>
        public string AreaName { get; internal set; }
        /// <summary>
        /// 发票类型
        /// </summary>
        public VATKind? Category { get; internal set; }
        /// <summary>
        /// 印刷年份
        /// </summary>
        public int Year { get; internal set; }
        /// <summary>
        /// 印刷批次
        /// </summary>
        public int Batch { get; internal set; }
        /// <summary>
        /// 发票联次,仅10位长度和12位长度折叠票发票才有
        /// </summary>
        public int DuplicateNumber { get; internal set; }
    }

可以根据 IsValid
属性来判断是否验证通过(true/false),如果验证失败,Errors
属性则包含了验证失败的原因,具体的错误原因列表如下

VATCode10ValidationResult是在VATCodeValidationResult的基础上,额外定义了发票金额版本,其定义如下:

        /// <summary>
        /// 身份证号码为空
        /// </summary>
        public const string Empty = "身份证号码为空";
        /// <summary>
        /// 错误的身份证号码
        /// </summary>
        public const string Error = "错误的身份证号码";
        /// <summary>
        /// 无效的出生日期
        /// </summary>
        public const string InvalidBirthday = "无效的出生日期";
        /// <summary>
        /// 出生日期超出允许的年份范围
        /// </summary>
        public const string BirthdayYearOutOfRange = "出生日期超出允许的年份范围{0} ~ {1}";
        /// <summary>
        /// 行政区划识别失败
        /// </summary>
        public const string InvalidArea = "行政区划识别失败";
        /// <summary>
        /// 行政区划识别度不足
        /// </summary>
        public const string AreaLimitOutOfRange = "行政区划识别度低于识别级别 {0}";
        /// <summary>
        /// 错误的校验码
        /// </summary>
        public const string InvalidCheckBit = "错误的校验码";
        /// <summary>
        /// 无效实现
        /// </summary>
        public const string InvalidImplement = "未能找到或无效的 {0} 位身份证实现";
        /// <summary>
        /// 长度错误
        /// </summary>
        public const string LengthOutOfRange = "身份证号码非 {0} 位";
    /// <summary>
    /// 增值税发票和普通(纸质)专有的验证结果
    /// </summary>
    public class VATCode10ValidationResult : VATCodeValidationResult
    {
        /// <summary>
        /// 发票金额版本号,仅10位长度发票才有
        /// </summary>
        public AmountVersion AmountVersion { get; internal set; }
    }

而验证通过时,你可以通过通过其它属性来获取一些你可能感兴趣的信息,比如
Birthday(出生日期)、Gender(性别)、RecognizableArea(识别出的完整区域,你可以通过其FullName获取完整的行政区域名称,比如 
上海市市辖区徐汇区)等等……

可根据IsValid来判断验证是否成功,如果验证失败,Errors
属性则包含了验证失败的原因,具体的错误原因列表如下

在身份证识别中,最困难的地方在于行政区划数据整理,而早期的行政区划数据就目前个人而言完全无法获取,所以目前提供用于验证的行政区划数据是基于GBT2260的2013版本,有兴趣的可以去http://www.stats.gov.cn/tjsj/tjbz/xzqhdm/查阅(很遗憾,目前该部分已不再开放,直接返回无权限了,之所以这里还附上地址,只能说可能以后该部分还会开放吧),一代身份证登记时用的是1984年的版本,而在其之后到现在为止,每年都会有行政区域发生调整,所以如果字典数据不全,用目前默认提供的2013版本字典的话,那么要想保证验证的准确性,那么只能判断到行政省的级别(目前还没出现过省级行政区划的变化调整),当然假设也许有人可以弄到所有的行政区划字典(废弃的行政区划编号不会再纳入使用,所以可以保证行政区划编号永久唯一),或者你希望验证的身份证仅属于某些行政区域,那么可以将新的字典赋给
IIDValidator 的 Dictionary 属性来达到全区域验证或特定区域验证的效果。

        /// <summary>
        /// 发票代码为空
        /// </summary>
        public const string Empty = "发票代码为空";
        /// <summary>
        /// 错误的发票代码
        /// </summary>
        public const string Error = "错误的发票代码";
        /// <summary>
        /// 发票年份超出允许的年份范围
        /// </summary>
        public const string YearOutOfRange = "发票年份超出允许的年份范围{0} ~ {1}";
        /// <summary>
        /// 发票发行区域识别失败
        /// </summary>
        public const string InvalidArea = "发票发行区域识别失败";
        /// <summary>
        /// 无效的发票类别
        /// </summary>
        public const string InvalidKind = "无效的发票类别";
        /// <summary>
        /// 发票类别错误,无法生成发票代码
        /// </summary>
        public const string GenerateWrongKind = "发票类别错误,无法生成发票代码";
        /// <summary>
        /// 无效实现
        /// </summary>
        public const string InvalidImplement = "未能找到或无效的 {0} 位发票代码实现";
        /// <summary>
        /// 长度不符
        /// </summary>
        public const string LengthOutOfRange = "发票代码非 {0} 位";

上面啰嗦了那么多都还没到怎么使用,其实验证非常简单,就一个Validate方法,如果你仅需要验证二代18位身份证的话,那么你可以只使用
ID18Validator,同理如果你只需要验证一代15位身份证也只需要使用ID15Validator(一般不可能会有这种情况,毕竟目前为止一代身份证基本都不被认可,且还在有效期内的应该也为数不多了),当然为了方便使用,你也可以直接使用IDValidatorHelper来进行一个简单的快速调用(这里需要说明的是反射创建的具体验证实例是会被缓存在内存中的,所以这里性能问题基本可以忽略,但这时你就不能通过字典赋值方式来完善或调整验证结果),所以你可以通过以下几种方式进行验证

因为目前类库中已经完整收集了所有发票代码中支持的行政区划编号(可在航信官网上查看都有哪些区域存在税务局),所以暂时不再需要自行传递Dictionary来进行支持区域的修正。

            string id = "32021919900101003X";
            var valid = new ID15Validator().Validate(id);
            valid = new ID18Validator()
            {
                //按需对Dictionary赋值
            }.Validate(id);
            valid = IDValidatorHelper.Validate(id);

目前IVATCodeValidator包含VATCode10Validator以及VATCode12Validator两种具体实现

注意这里的代码并没使用可选参数,你可以按需传递相关参数,然后除了上面的验证,你还可以通过GenerateRandomNumber或GenerateID生成身份证,需要注意的是IDValidatorHelper并不提供生成身份证的功能,所以这里必须通过ID18Validator或ID15Validator来生成身份证,与GenerateRandomNumber不同,GenerateID方法支持直接传入行政区划编号,且验证规则为10~999999内的数字,需要注意的是此时该行政区划编号可以不在行政区划字典范围内,即此时不会去验证该编号是否在字典中存在,而GenerateRandomNumber是随机从行政区划字典中取一个区县级区域,简单的代码如下

  • VATCode10Validator
    对应长度为10的发票代码,包含增值税专用发票、增值税普通发票
  • VATCode12Validator
    对应长度为12的发票代码,包含增值税普通发票、增值税普通发票(卷票)、增值税电子普通发票
  • VATCodeValidatorHelper
    为静态类,用于辅助验证,其内部简单的封装了按发票代码长度调用对应的IVATCodeValidator实现
            var no = new ID15Validator().GenerateRandomNumber();
            no = new ID18Validator().GenerateID(3202, new DateTime(2000, 1, 1), 1);

使用例子如下

  如果你在实际使用中有发现问题或Bug,可以在此处回复或至git上建立Issue。

            Console.WriteLine("***增值税发票***");
            var vat10Validator = new VATCode10Validator();
            var vat12Validator = new VATCode12Validator();
            Console.WriteLine("随机的增值税发票:" + vat10Validator.GenerateRandomNumber());
            Console.WriteLine("生成指定的增值税专用发票:" + vat10Validator.GenerateVATCode(3700, 2017, 1, Invoices.VATKind.Special));
            Console.WriteLine("生成指定的10位增值税普通发票:" + vat10Validator.GenerateVATCode(1100, 2017, 2, Invoices.VATKind.Plain));
            Console.WriteLine("生成指定的12位增值税普通发票:" + vat12Validator.GenerateVATCode(1100, 2018, 6, Invoices.VATKind.Plain));
            Console.WriteLine("随机的增值税电子/卷票/普票:" + vat12Validator.GenerateRandomNumber());
            string[] vatArr = { "031001600311", "3100153130", "011001800304" };
            foreach (var vat in vatArr)
            {
                var valid = VATCodeValidatorHelper.Validate(vat, minYear: 2012);
                Console.WriteLine("{0}验证结果:{1} 类型{2} 行政区划名称({3}) 验证结果类型:{4}", vat, valid.IsValid, valid.Category, valid.AreaName, valid);
            }

PS:目前1.0版本中VATCode12Validator未支持12位的增值税普通发票,如果需要支持,需从git上下载代码后自行生成dll