.NET Core 如何生成信用卡卡号

点击上方蓝字关注“汪宇杰博客”

导语

上个月我写了《.NET Core 如何验证信用卡卡号》,不少朋友表示挺有兴趣。在金融科技行业的实际工作中,通常还需要生成信用卡卡号用来测试,今天我就来教大家如何生成信用卡卡号。

上回的改进

上篇文章写完后,我对代码进行了一些改进,除了使用方法上的差别,还改进了一处潜在的性能问题。

原本将卡号字符串转换为int数组的函数为:

public static int[] GetDigitsArrayFromCardNumber(string cardNumber)

{

    var digits = cardNumber.Select(p => int.Parse(p.ToString())).ToArray();

    return digits;

}

它所存在的问题是,为了将 char 类型转换为 int,做了一次 ToString() 操作,尽管 .NET CLR 会在内存里保留相同内容的 string,但不必要的 string 分配仍会有一定的开销。对于信用卡卡号,此处的 char 一定是代表数字的字符,不可能是其他英文字符或符号,因此可以通过 ASCII 运算来进行高效转换。

我们只需要用 char 减去 '0',即可得到对应的 int 类型,例如 '8' - '0' = 8:

还记得大学计算机基础课里学的 ASCII 码 吗?字符 8 的 ASCII 码为 56,字符 0 的 ASCII 码为 48,因为 56 - 48 = 8,因此字符 8 - 字符 0 = 8。

至于性能的对比,争论再多理论也不如实际测一下有说服力。我们用两种方法,均执行 996007 次(嗯?这个数字有点眼熟),对比总时间。

转换string类型,耗时20ms

使用char计算,耗时 1ms

所以,不要小看这些“骚操作”,平时代码里看到同事这么写不要觉得只是在装逼。尽管有时候代码阅读体验没有那么直观,但如果你的业务面临苛刻的压力时,能够明显体验到性能区别。.NET Core 的基础类库源代码里也有不少类似这样的基础类型骚操作,有兴趣的读者可以去翻翻。

然而装逼,是人类社会的刚需,光用char计算逼格还不够,还记得上回的 Luhn 算法吗?原来的代码如下,我只是把维基百科上公开定义的算法直接翻译成C#:

public static bool IsLuhnValid(int[] digits)

{

    var sum = 0;

    var alt = false;

    for (var i = digits.Length - 1; i >= 0; i--)

    {

        if (alt)

        {

            digits[i] *= 2;

            if (digits[i] > 9)

            {

                digits[i] -= 9;

            }

        }

        sum += digits[i];

        alt = !alt;

    }

    return sum % 10 == 0;

}

而C#就应该用出C#的味道不是?代码逼格化以后:

public static bool IsLuhnValid(int[] digits)

{

    var sum = digits.Reverse()

        .Select((digit, i) =>

                (i + 1) % 2 == 0

                ? digit * 2 > 9 ? digit * 2 - 9 : digit * 2

                : digit)

        .Sum();

    return sum % 10 == 0;

}

深藏功与名。

生成卡号

上回理解了 Luhn 算法之后,我们不难发现,验证卡号的精髓无非在于最后的校验位(Check Digit)。也就是说,生成卡号其实只要生成有效的校验位,其他数字随机,只要校验位正确,就可以通过 Luhn 检查。

校验位生成

还记得校验位怎么来的吗?就拿上回的例子卡号 6011000990139424,去掉校验位4以后,计算的SUM值为4646x9 = 414,尾数为4,即校验位。因此对于我们自己随机生成的卡号,也只要计算除了校验位以外的SUM,然后乘以9,再取尾数即可

因为计算SUM的方法很相似,只是用来翻倍-9的奇偶位不同,所以我重构一下代码,将计算逻辑封装:

public static bool IsLuhnValid(int[] digits)

{

    var sum = CalculateSum(digits, 1);

    return sum % 10 == 0;

}

private static int CalculateSum(int[] digits, int bitShift = 0)

{

    var sum = digits.Reverse()

                        .Select((digit, i) =>

                                (i + bitShift) % 2 == 0

                                ? digit * 2 > 9 ? digit * 2 - 9 : digit * 2

                                : digit)

                        .Sum();

    return sum;

}

生成校验位就能这么操作:

public static int GenerateCheckDigit(int[] digits)

{

    var sum = CalculateSum(digits);

    var lastDigit = sum * 9 % 10;

    return lastDigit;

}

该函数的 digits 参数接受的值是不包含校验位的信用卡其余卡号,例如还是之前的例子 6011000990139424,去掉校验位4,传给 GenerateCheckDigit() 的为 601100099013942。因为少了一位,所以bitShift参数就用默认值0,以确保奇偶位不会错位。

% 10 用来高性能取尾数。嗯?差点又 ToString() 了是吗?

测试计算结果准确,如下:

随机数骚操作

可能大家觉得C#生成随机数有什么难的,不就是一个 Random 类型吗?但实际情况是,如果Random在static修饰符的情况下,这可不一定线程安全,具体原因不在本文讨论范围内,直接给出解决方案。(嗯,差点加锁了是吗?性能可没这个好)

private static int _seed = Environment.TickCount;

private static readonly ThreadLocal<Random> Random =

    new ThreadLocal<Random>(() => new Random(Interlocked.Increment(ref _seed)));

参考:https://stackoverflow.com/questions/19270507/correct-way-to-use-random-in-multithread-application

Put Together

实际生成信用卡卡号,一般会给定BIN,因此我的函数设计为接受BIN前缀、卡号位数,生成符合 Luhn 的随机卡号。

public static string GenerateCardNumber(string bin, int length)

{

    int[] digits = new int[length];

    var prefixDigits = bin.Select(p => p - '0').ToArray();

    for (var i = 0; i < prefixDigits.Length; i++)

    {

        digits[i] = prefixDigits[i];

    }

    for (var i = bin.Length; i < length - 1; i++)

    {

        var digit = Random.Value.Next(0, 10);

        digits[i] = digit;

    }

    digits[length - 1] = Luhn.GenerateCheckDigit(digits[..(length -1)]);

    return string.Join(null, digits);

}

还是考虑到性能,我没有用 StringBuilder 拼接卡号,更没有用 string += 拼接。设计类库给别人你用的话,一定要注意场景,在我的实际工作中,生成卡号往往是大批量操作,有性能要求,所以写代码要尽量拷问每一处细节。

使用方法:

var bin = "485246";

int length = 16;

var cn = CreditCardGenerator.GenerateCardNumber(bin, length);

Assert.IsNotEmpty(cn);

Assert.IsTrue(cn.Length == length);

var result = CreditCardValidator.ValidCardNumber(cn);

Assert.IsTrue(result.CardNumberFormat == CardNumberFormat.Valid_LuhnOnly

              || result.CardNumberFormat == CardNumberFormat.Valid_BINTest);

批量生成:

var bin = "485246";

int length = 16;

var cardNumbers = new List<string>();

for (int i = 0; i < 128; i++)

{

    var cn = CreditCardGenerator.GenerateCardNumber(bin, length);

    cardNumbers.Add(cn);

}

var isUnique = cardNumbers.GroupBy(x => x).All(g => g.Count() == 1);

Assert.IsTrue(isUnique);

项目依然在我的交友平台上:https://github.com/EdiWang/Edi.CreditCardUtils

大家如果有什么建议,或是再能进一步改进优化,欢迎提交流及PR。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/310984.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python怎么爬虎牙_使用python爬虫框架scrapy抓取虎牙主播数据

前言本文利用python的scrapy框架对虎牙web端的主播、主播订阅数、主播当前观看人数等基本数据进行抓取&#xff0c;并将抓取到的数据以csv格数输出&#xff0c;以及存储到mongodb中思路观察虎牙网站后确认所有频道url都在www.huya.com/g中的&#xff0c;而主播房间数据则是ajax…

《C++ Primer》7.3.4节练习

练习7.32: 要想让clear函数作为Screen的友元&#xff0c;只需要在Screen类中做出友元声明即可。本题的真正关键之处是程序的组织结构&#xff0c;我们必须首先定义Window_mgr类&#xff0c;其中声明clear函数&#xff0c;但是不能定义它&#xff1b;接下来定义Screen类&#xf…

.NET Core开发实战(第29课:定义仓储:使用EF Core实现仓储层)--学习笔记

29 | 定义仓储&#xff1a;使用EF Core实现仓储层首先定义仓储层的接口&#xff0c;以及仓储层实现的基类&#xff0c;抽象类仓储层的接口namespace GeekTime.Infrastructure.Core {/// <summary>/// 包含普通实体的仓储/// 约束 TEntity 必须是继承 Entity 的基类&#…

ueditor单图上传iframe跨域_UEditor单图上传(simpleupload)跨域问题解决方案

代码实现首先我们需要在ueditor.all.js文件中找到原本的单图上传部分的代码搜索关键字 simpleupload&#xff0c;如下图所示&#xff1a;然后找到上传图片的代码片段&#xff0c;如下图所示&#xff1a;然后把 domUtils.on的 input 绑定的事件注释掉或删除掉替换成以下代码:inp…

StringBuilder内存碎片对性能的影响

TL;DR:StringBuilder内部是由多段 char[]组成的半自动链表&#xff0c;因此频繁从中间修改 StringBuilder&#xff0c;会将原本连续的内存分隔为多段&#xff0c;从而影响读取/遍历性能。连续内存与不连续内存的性能差&#xff0c;可能高达 1600倍。背景用 StringBuilder的用户…

java 双击_利用java开发一个双击执行的小程序

之前我们利用java写了很多东西&#xff0c;但是好像都没有什么实际意义。因为有意义桌面小程序怎么都得有个界面&#xff0c;可是界面又不太好搞。或者 了解到这一层的人就少之又少了。呀&#xff0c;是不是还得开辟一些版面来介绍awt和 swing。。。算了 先把这个 双击执行的小…

开发人员如何学习 Kubernetes

虽然“容器编排平台”还没有被整个行业大范围采用&#xff0c;但在这一领域 Kubernetes 已经战胜其他选手&#xff0c;成为了事实标准。近两年的 Web 开发技术社区&#xff0c;随便打开一两个群&#xff0c;你都能看到人们在谈 Kubernetes。很多开发人员&#xff0c;包括曾经的…

安装 java decompiler_Eclipse离线安装Java Decompiler插件(反编译)

Java Decompiler是Java语言的反编译工具&#xff0c;具体介绍见博客Java Decompiler(Java反编译工具)1、下载插件Eclipe的Java Decompiler插件名为JD-Eclipse&#xff0c;2、安装插件Ecipse安装JD-Eclipse(即Java Decompiler)插件步骤如下&#xff1a;打开Help --> Install …

给 ABP vNext 应用安装私信模块

在上一节五分钟完成 ABP vNext 通讯录 App 开发 中&#xff0c;我们用完成了通讯录 App 的基础开发。这本章节&#xff0c;我们会给通讯录 App 安装私信模块&#xff0c;使不同用户能够通过相互发送消息&#xff0c;并接收新私信的通知。在章节的最后&#xff0c;笔者将演示模块…

《C++ Primer》7.5.2节练习

练习7.41: #include <iostream> #include <string> using namespace std;class Sales_data {friend std::istream &read(std::istream &is, Sales_data &item);friend std::ostream &print(std::ostream &os, const Sales_data &item);pu…

零基础玩视频号?创作运营变现,你要的干货都在这了!

点击蓝字“大白技术控”关注我哟加个“星标★”&#xff0c;每日良时&#xff0c;好文必达&#xff01;不少小伙伴应该已经听说过视频号这个新功能了&#xff0c;视频号是微信内测的短视频功能&#xff0c;本人已经在视频号里刷了2个月了。3月中旬正式开通了视频号 「大白技术控…

Asp.Net Core 中IdentityServer4 实战之 Claim详解

一、前言由于疫情原因&#xff0c;让我开始了以博客的方式来学习和分享技术&#xff08;持续分享的过程也是自己学习成长的过程&#xff09;&#xff0c;同时也让更多的初学者学习到相关知识&#xff0c;如果我的文章中有分析不到位的地方&#xff0c;还请大家多多指教&#xf…

程序员还有35岁的坎吗?

昨天晚上和多年未见的前同事聊天&#xff0c;提到了程序员的年龄歧视问题&#xff1a;自己年龄也 30 出头了&#xff0c;在思考 IT 届流传的 35 岁是一个坎的问题&#xff1b;开始注重提升管理能力&#xff0c;担心35岁之后&#xff0c;一线写代码的岗位不能胜任&#xff1b;公…

java 左移 返回值_java左移右移运算符详解

在阅读源码的过程中&#xff0c;经常会看到这些符号<< &#xff0c;>>&#xff0c;>>>&#xff0c;这些符号在Java中叫移位运算符&#xff0c;在写代码的过程中&#xff0c;虽然我们基本上不会去写这些符号&#xff0c;但需要明白这些符号的运算原理&…

人与人的差距在于认知

作者介绍findyi&#xff0c;腾讯、360码农&#xff0c;前哒哒少儿英语技术VP&#xff0c;现任土豆教育CTO。工作和生活中不光要埋头干活&#xff0c;还要抬头看天。思考总结方法论是提升认知的必备途径&#xff0c;是将碎片化知识总结为动态的智慧的过程。认知有多重要&#xf…

.NET5来了你别慌

近日微软.Net大咖Scott在博客中对外宣传.NET5首个预览版&#xff0c;并且我们可以通过微软的官网下载SDK5和运行库。很多朋友感觉.NetCore3.1还没搞明白&#xff0c;.NET5就来了感觉一下子慌了神。在这里我提醒朋友们&#xff0c;瞬息万变的世界中&#xff0c;总有相对不变的真…

java8 stream 最大值_JDK8-Stream流常用方法

Stream流的使用流操作是Java8提供一个重要新特性&#xff0c;它允许开发人员以声明性方式处理集合&#xff0c;其核心类库主要改进了对集合类的 API和新增Stream操作。Stream类中每一个方法都对应集合上的一种操作。将真正的函数式编程引入到Java中&#xff0c;能 让代码更加简…

周三晚6点半!盛派首席架构师“苏老师”在线解密内部系统框架!

工作中有些事&#xff0c;看起来只用一会会儿就能完成&#xff0c;但真正完成起来&#xff0c;总会遇到一些意想不到的困难&#xff01;你一定碰到过这样的情况——开发时间 2 周的项目&#xff0c;搭框架就要用 1 周&#xff0c;刚开发完&#xff0c;各种调试和修 bug又花去 2…

给微软的日志框架写一个基于委托的日志提供者

动手造轮子&#xff1a;给微软的日志框架写一个基于委托的日志提供者Intro微软的日志框架现在已经比较通用&#xff0c;有时候我们不想使用外部的日志提供者&#xff0c;但又希望提供一个比较简单的委托就可以实现日志记录&#xff0c;于是就有了后面的探索和实现。Solution基于…

C++分析使用拷贝控制成员和调用构造函数的时机

我们来分析下面这段代码&#xff1a; #include <iostream> #include <vector>using namespace std;struct X {X() {cout << "构造函数X()" << endl;}X(const X &) {cout << "拷贝构造函数X(const X&)" << en…