.NET Core 如何生成信用卡卡号
點擊上方藍字關注“汪宇杰博客”
導語
上個月我寫了《.NET Core 如何驗證信用卡卡號》,不少朋友表示挺有興趣。在金融科技行業(yè)的實際工作中,通常還需要生成信用卡卡號用來測試,今天我就來教大家如何生成信用卡卡號。
上回的改進
上篇文章寫完后,我對代碼進行了一些改進,除了使用方法上的差別,還改進了一處潛在的性能問題。
原本將卡號字符串轉換為int數(shù)組的函數(shù)為:
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 一定是代表數(shù)字的字符,不可能是其他英文字符或符號,因此可以通過 ASCII 運算來進行高效轉換。
我們只需要用 char 減去 '0',即可得到對應的 int 類型,例如 '8' - '0' = 8:
還記得大學計算機基礎課里學的 ASCII 碼?嗎?字符 8 的 ASCII 碼為 56,字符 0 的 ASCII 碼為 48,因為 56 - 48 = 8,因此字符 8 - 字符 0 = 8。
至于性能的對比,爭論再多理論也不如實際測一下有說服力。我們用兩種方法,均執(zhí)行?996007?次(嗯?這個數(shù)字有點眼熟),對比總時間。
轉換string類型,耗時20ms
使用char計算,耗時 1ms
所以,不要小看這些“騷操作”,平時代碼里看到同事這么寫不要覺得只是在裝逼。盡管有時候代碼閱讀體驗沒有那么直觀,但如果你的業(yè)務面臨苛刻的壓力時,能夠明顯體驗到性能區(qū)別。.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 算法之后,我們不難發(fā)現(xiàn),驗證卡號的精髓無非在于最后的校驗位(Check Digit)。也就是說,生成卡號其實只要生成有效的校驗位,其他數(shù)字隨機,只要校驗位正確,就可以通過 Luhn 檢查。
校驗位生成
還記得校驗位怎么來的嗎?就拿上回的例子卡號?6011000990139424,去掉校驗位4以后,計算的SUM值為46,46x9 = 414,尾數(shù)為4,即校驗位。因此對于我們自己隨機生成的卡號,也只要計算除了校驗位以外的SUM,然后乘以9,再取尾數(shù)即可。
因為計算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;
}
該函數(shù)的?digits 參數(shù)接受的值是不包含校驗位的信用卡其余卡號,例如還是之前的例子?6011000990139424,去掉校驗位4,傳給?GenerateCheckDigit() 的為?601100099013942。因為少了一位,所以bitShift參數(shù)就用默認值0,以確保奇偶位不會錯位。
% 10 用來高性能取尾數(shù)。嗯?差點又 ToString() 了是嗎?
測試計算結果準確,如下:
隨機數(shù)騷操作
可能大家覺得C#生成隨機數(shù)有什么難的,不就是一個 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,因此我的函數(shù)設計為接受BIN前綴、卡號位數(shù),生成符合 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 += 拼接。設計類庫給別人你用的話,一定要注意場景,在我的實際工作中,生成卡號往往是大批量操作,有性能要求,所以寫代碼要盡量拷問每一處細節(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);
項目依然在我的交友平臺上:https://github.com/EdiWang/Edi.CreditCardUtils
大家如果有什么建議,或是再能進一步改進優(yōu)化,歡迎提交流及PR。
總結
以上是生活随笔為你收集整理的.NET Core 如何生成信用卡卡号的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【实战 Ids4】║ 控制台密码模式搭配
- 下一篇: .NET Core开发实战(第29课:定