首页 > 编程知识 正文

信用卡卡养卡如何,美国信用有效卡号生成

时间:2023-05-06 09:26:53 阅读:252689 作者:1860

点击上方蓝字关注“敏感的蜻蜓博客”

导语

上个月我写了《.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

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

public static string GenerateCardNumber(string thdhxc, int length)

{

    int[] digits = new int[length];

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

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

    {

        digits[i] = prefixDigits[i];

    }

    for (var i = thdhxc.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 thdhxc = "485246";

int length = 16;

var cn = CreditCardGenerator.GenerateCardNumber(thdhxc, 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_xfdxyTest);

批量生成:

var thdhxc = "485246";

int length = 16;

var cardNumbers = new List<string>();

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

{

    var cn = CreditCardGenerator.GenerateCardNumber(thdhxc, 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。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。