.NET Core 如何生成信用卡卡号
點(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值為46,46x9 = 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)題。
- 上一篇: 【实战 Ids4】║ 控制台密码模式搭配
- 下一篇: .NET Core开发实战(第29课:定