This commit is contained in:
dengqichen 2025-11-19 15:58:01 +08:00
parent 26988d3de8
commit fe15b877ab
2 changed files with 96 additions and 16 deletions

View File

@ -7,10 +7,25 @@ const CARD_TYPES = {
unionpay: { unionpay: {
name: '中国银联 (UnionPay)', name: '中国银联 (UnionPay)',
// 固定前缀(用户提供的成功案例都是这个前缀) // 固定前缀(用户提供的成功案例都是这个前缀)
prefix: '622836754', prefix: '622836754', // 固定BIN前缀
length: 16, length: 16,
cvvLength: 3, cvvLength: 3,
useLuhn: true // 使用Luhn算法校验 useLuhn: true,
// 成功案例的后7位模式用于智能生成
successfulPatterns: [
'5055812', '8774419', '6781457', '2738949',
'2602400', '8575105', '6496080', '0057649',
'9574719', '8435128', '2797374', '5956423',
'7237848', '0385107', '4252006', '7562054'
],
// 生成策略配置
generation: {
mutationRate: 0.5, // 50% 使用变异策略
randomRate: 0.5, // 50% 使用纯随机
mutationDigits: [1, 2] // 变异时改变1-2个数字
}
}, },
visa: { visa: {
name: 'Visa', name: 'Visa',

View File

@ -13,7 +13,7 @@ class CardGenerator {
} }
/** /**
* 生成卡号 * 生成卡号混合策略变异真实案例 + 纯随机
* @param {string} type - 卡类型 * @param {string} type - 卡类型
* @returns {string} * @returns {string}
*/ */
@ -23,28 +23,93 @@ class CardGenerator {
throw new ValidationError('card-generator', `Unknown card type: ${type}`); throw new ValidationError('card-generator', `Unknown card type: ${type}`);
} }
const { length, useLuhn } = config; const { prefix, length, useLuhn, successfulPatterns, generation } = config;
// 支持多个前缀(随机选择)或单个前缀 // 如果有成功案例和生成策略配置,使用混合策略
let prefix; if (successfulPatterns && generation) {
if (Array.isArray(config.prefixes)) { const useMutation = Math.random() < generation.mutationRate;
// 从数组中随机选择一个前缀
const randomIndex = randomInt(0, config.prefixes.length - 1); if (useMutation) {
prefix = config.prefixes[randomIndex]; // 策略1基于真实案例变异
} else { return this.generateByMutation(prefix, successfulPatterns, generation.mutationDigits);
// 兼容旧配置单个prefix }
prefix = config.prefix;
} }
// 策略2纯随机生成默认或回退
if (useLuhn) { if (useLuhn) {
// 使用Luhn算法生成
return generateLuhnNumber(prefix, length); return generateLuhnNumber(prefix, length);
} else { } else {
// 纯随机生成
const remainingLength = length - prefix.length; const remainingLength = length - prefix.length;
return prefix + randomDigits(remainingLength); return prefix + randomDigits(remainingLength);
} }
} }
/**
* 基于真实案例变异生成卡号
* @param {string} prefix - BIN前缀
* @param {Array} patterns - 成功案例的后7位
* @param {Array} mutationDigits - 变异数字个数[min, max]
* @returns {string}
*/
generateByMutation(prefix, patterns, mutationDigits) {
// 随机选择一个成功案例
const basePattern = patterns[randomInt(0, patterns.length - 1)];
// 随机决定改变几个数字
const changeCount = randomInt(mutationDigits[0], mutationDigits[1]);
const mutated = basePattern.split('');
// 改变指定数量的数字
const changedPositions = new Set();
for (let i = 0; i < changeCount; i++) {
let pos;
do {
pos = randomInt(0, mutated.length - 1);
} while (changedPositions.has(pos)); // 避免重复位置
changedPositions.add(pos);
// 生成不同的数字
let newDigit;
do {
newDigit = randomInt(0, 9).toString();
} while (newDigit === mutated[pos]);
mutated[pos] = newDigit;
}
// 组合前缀和变异后的后7位去掉最后一位校验位
const prefix15 = prefix + mutated.join('').slice(0, -1);
// 重新计算Luhn校验位
const checkDigit = this.calculateLuhnCheckDigit(prefix15);
return prefix15 + checkDigit;
}
/**
* 计算Luhn校验位
* @param {string} partial - 不含校验位的卡号
* @returns {string}
*/
calculateLuhnCheckDigit(partial) {
let sum = 0;
let isEven = true;
for (let i = partial.length - 1; i >= 0; i--) {
let digit = parseInt(partial[i]);
if (isEven) {
digit *= 2;
if (digit > 9) digit -= 9;
}
sum += digit;
isEven = !isEven;
}
return ((10 - (sum % 10)) % 10).toString();
}
/** /**
* 生成有效期 * 生成有效期