日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪(fǎng)問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) >

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

發(fā)布時(shí)間:2023/12/4 56 豆豆
生活随笔 收集整理的這篇文章主要介紹了 .NET Core 如何生成信用卡卡号 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

點(diǎn)擊上方藍(lán)字關(guān)注“汪宇杰博客”

導(dǎo)語(yǔ)

上個(gè)月我寫(xiě)了《.NET Core 如何驗(yàn)證信用卡卡號(hào)》,不少朋友表示挺有興趣。在金融科技行業(yè)的實(shí)際工作中,通常還需要生成信用卡卡號(hào)用來(lái)測(cè)試,今天我就來(lái)教大家如何生成信用卡卡號(hào)。

上回的改進(jìn)

上篇文章寫(xiě)完后,我對(duì)代碼進(jìn)行了一些改進(jìn),除了使用方法上的差別,還改進(jìn)了一處潛在的性能問(wèn)題。

原本將卡號(hào)字符串轉(zhuǎn)換為int數(shù)組的函數(shù)為:

public static int[] GetDigitsArrayFromCardNumber(string cardNumber)

{

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

? ? return digits;

}

它所存在的問(wèn)題是,為了將 char 類(lèi)型轉(zhuǎn)換為 int,做了一次 ToString() 操作,盡管 .NET CLR 會(huì)在內(nèi)存里保留相同內(nèi)容的 string,但不必要的 string 分配仍會(huì)有一定的開(kāi)銷(xiāo)。對(duì)于信用卡卡號(hào),此處的 char 一定是代表數(shù)字的字符,不可能是其他英文字符或符號(hào),因此可以通過(guò) ASCII 運(yùn)算來(lái)進(jìn)行高效轉(zhuǎn)換。

我們只需要用 char 減去 '0',即可得到對(duì)應(yīng)的 int 類(lèi)型,例如 '8' - '0' = 8:

還記得大學(xué)計(jì)算機(jī)基礎(chǔ)課里學(xué)的 ASCII 碼?嗎?字符 8 的 ASCII 碼為 56,字符 0 的 ASCII 碼為 48,因?yàn)?56 - 48 = 8,因此字符 8 - 字符 0 = 8。

至于性能的對(duì)比,爭(zhēng)論再多理論也不如實(shí)際測(cè)一下有說(shuō)服力。我們用兩種方法,均執(zhí)行?996007?次(嗯?這個(gè)數(shù)字有點(diǎn)眼熟),對(duì)比總時(shí)間。

轉(zhuǎn)換string類(lèi)型,耗時(shí)20ms

使用char計(jì)算,耗時(shí) 1ms

所以,不要小看這些“騷操作”,平時(shí)代碼里看到同事這么寫(xiě)不要覺(jué)得只是在裝逼。盡管有時(shí)候代碼閱讀體驗(yàn)沒(méi)有那么直觀(guān),但如果你的業(yè)務(wù)面臨苛刻的壓力時(shí),能夠明顯體驗(yàn)到性能區(qū)別。.NET Core 的基礎(chǔ)類(lèi)庫(kù)源代碼里也有不少類(lèi)似這樣的基礎(chǔ)類(lèi)型騷操作,有興趣的讀者可以去翻翻。

然而裝逼,是人類(lèi)社會(huì)的剛需,光用char計(jì)算逼格還不夠,還記得上回的 Luhn 算法嗎?原來(lái)的代碼如下,我只是把維基百科上公開(kāi)定義的算法直接翻譯成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#就應(yīng)該用出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;

}

深藏功與名。

生成卡號(hào)

上回理解了 Luhn 算法之后,我們不難發(fā)現(xiàn),驗(yàn)證卡號(hào)的精髓無(wú)非在于最后的校驗(yàn)位(Check Digit)。也就是說(shuō),生成卡號(hào)其實(shí)只要生成有效的校驗(yàn)位,其他數(shù)字隨機(jī),只要校驗(yàn)位正確,就可以通過(guò) Luhn 檢查。

校驗(yàn)位生成

還記得校驗(yàn)位怎么來(lái)的嗎?就拿上回的例子卡號(hào)?6011000990139424,去掉校驗(yàn)位4以后,計(jì)算的SUM值為4646x9 = 414,尾數(shù)為4,即校驗(yàn)位。因此對(duì)于我們自己隨機(jī)生成的卡號(hào),也只要計(jì)算除了校驗(yàn)位以外的SUM,然后乘以9,再取尾數(shù)即可

因?yàn)橛?jì)算SUM的方法很相似,只是用來(lái)翻倍-9的奇偶位不同,所以我重構(gòu)一下代碼,將計(jì)算邏輯封裝:

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;

}

生成校驗(yàn)位就能這么操作:

public static int GenerateCheckDigit(int[] digits)

{

? ? var sum = CalculateSum(digits);

? ? var lastDigit = sum * 9 % 10;

? ? return lastDigit;

}

該函數(shù)的?digits 參數(shù)接受的值是不包含校驗(yàn)位的信用卡其余卡號(hào),例如還是之前的例子?6011000990139424,去掉校驗(yàn)位4,傳給?GenerateCheckDigit() 的為?601100099013942。因?yàn)樯倭艘晃?#xff0c;所以bitShift參數(shù)就用默認(rèn)值0,以確保奇偶位不會(huì)錯(cuò)位。

% 10 用來(lái)高性能取尾數(shù)。嗯?差點(diǎn)又 ToString() 了是嗎?

測(cè)試計(jì)算結(jié)果準(zhǔn)確,如下:

隨機(jī)數(shù)騷操作

可能大家覺(jué)得C#生成隨機(jī)數(shù)有什么難的,不就是一個(gè) Random 類(lèi)型嗎?但實(shí)際情況是,如果Random在static修飾符的情況下,這可不一定線(xiàn)程安全,具體原因不在本文討論范圍內(nèi),直接給出解決方案。(嗯,差點(diǎn)加鎖了是嗎?性能可沒(méi)這個(gè)好)

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

實(shí)際生成信用卡卡號(hào),一般會(huì)給定BIN,因此我的函數(shù)設(shè)計(jì)為接受BIN前綴、卡號(hào)位數(shù),生成符合 Luhn 的隨機(jī)卡號(hào)。

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);

}

還是考慮到性能,我沒(méi)有用 StringBuilder 拼接卡號(hào),更沒(méi)有用 string += 拼接。設(shè)計(jì)類(lèi)庫(kù)給別人你用的話(huà),一定要注意場(chǎng)景,在我的實(shí)際工作中,生成卡號(hào)往往是大批量操作,有性能要求,所以寫(xiě)代碼要盡量拷問(wèn)每一處細(xì)節(jié)。

使用方法:

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);

項(xiàng)目依然在我的交友平臺(tái)上:https://github.com/EdiWang/Edi.CreditCardUtils

大家如果有什么建議,或是再能進(jìn)一步改進(jìn)優(yōu)化,歡迎提交流及PR。

總結(jié)

以上是生活随笔為你收集整理的.NET Core 如何生成信用卡卡号的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。