From 1826b811959336745863542d768df908cd61267b Mon Sep 17 00:00:00 2001 From: dengqichen Date: Thu, 20 Nov 2025 21:41:52 +0800 Subject: [PATCH] dasdasd --- src/shared/libs/card-generator/config.js | 41 ++-- src/shared/libs/card-generator/generator.js | 214 ++++++++++++++------ 2 files changed, 182 insertions(+), 73 deletions(-) diff --git a/src/shared/libs/card-generator/config.js b/src/shared/libs/card-generator/config.js index 6c85e1c..ff4bf37 100644 --- a/src/shared/libs/card-generator/config.js +++ b/src/shared/libs/card-generator/config.js @@ -6,21 +6,36 @@ const CARD_TYPES = { unionpay: { name: '中国银联 (UnionPay)', - // 多个 9 位银联卡 BIN 前缀(基于真实成功案例和同系列扩展) - // 策略:优先使用已验证成功的 622836754 和其同系列变体 + // 银联前缀配置:支持基础 BIN + 扩展段 + 权重 + // 策略:95% 使用真实成功案例系列,5% 探测其他银行(收集数据) prefixes: [ - '622836754', // ✅ 原始成功案例(最高优先级) - '622836755', '622836756', '622836757', // 同系列变体 - '622836758', '622836759', '622836750', // 继续同系列 - '622836751', '622836752', '622836753', // 更多同系列 + // ========== 主力军:622836 系列(95% 权重,有真实成功案例支撑)========== + { + bin: '622836', + extension: '754', + weight: 60, // 最高优先级:真实成功案例 + allowPatterns: true // 可套用成功案例的后缀模式 + }, + // 同系列相邻卡段(保留较高权重,与真实案例同源) + { bin: '622836', extension: '755', weight: 10, allowPatterns: true }, + { bin: '622836', extension: '756', weight: 8, allowPatterns: true }, + { bin: '622836', extension: '757', weight: 8, allowPatterns: true }, + { bin: '622836', extension: '758', weight: 5, allowPatterns: true }, + { bin: '622836', extension: '759', weight: 4, allowPatterns: true }, + + // ========== 探测部队:其他银行BIN(5% 权重,纯随机生成,数据收集用)========== + { bin: '620010', weight: 0.3, allowPatterns: false, issuer: '深圳发展银行' }, + { bin: '620021', weight: 0.3, allowPatterns: false, issuer: '交通银行' }, + { bin: '620048', weight: 0.2, allowPatterns: false, issuer: '中银快付' }, + { bin: '620059', weight: 0.2, allowPatterns: false, issuer: '中国工商银行' }, + { bin: '620086', weight: 0.3, allowPatterns: false, issuer: '工商银行' }, + { bin: '620107', weight: 0.3, allowPatterns: false, issuer: '中国建设银行' }, + { bin: '620148', weight: 0.2, allowPatterns: false, issuer: '中国农业银行' }, + { bin: '620200', weight: 0.4, allowPatterns: false, issuer: '中国银行' }, + { bin: '621214', weight: 0.3, allowPatterns: false, issuer: '招商银行' }, + // 备注:allowPatterns: false 表示不使用成功案例模式,只用纯随机策略 + // 如果这些BIN有成功案例,再更新配置并提高权重 ], - // 备用:如果同系列都失败,尝试其他真实 BIN - // 暂时注释掉未验证的 BIN,减少失败率 - // prefixes_backup: [ - // '622206000', '622210000', // 工商银行 - // '622620000', // 民生银行 - // '622588000', // 招商银行 - // ], length: 16, cvvLength: 3, useLuhn: true, diff --git a/src/shared/libs/card-generator/generator.js b/src/shared/libs/card-generator/generator.js index 30c219d..880d14d 100644 --- a/src/shared/libs/card-generator/generator.js +++ b/src/shared/libs/card-generator/generator.js @@ -84,88 +84,182 @@ class CardGenerator { */ _generateCardNumberInternal(type, config) { const { prefix, prefixes, length, useLuhn, successfulPatterns, generation } = config; - - // 支持单个 prefix 或多个 prefixes - let selectedPrefix = prefix; - if (prefixes && Array.isArray(prefixes) && prefixes.length > 0) { - // 从 prefixes 数组中随机选择一个 - selectedPrefix = prefixes[randomInt(0, prefixes.length - 1)]; + + const prefixInfo = this.selectPrefix(prefix, prefixes); + const selectedPrefix = prefixInfo.fullPrefix; + + if (!selectedPrefix) { + throw new ValidationError('card-generator', `卡类型 ${type} 未配置有效的前缀`); } - - // 如果有成功案例和生成策略配置,使用三策略混合(优化版) - if (successfulPatterns && generation) { + + if (selectedPrefix.length >= length) { + throw new ValidationError('card-generator', `前缀长度(${selectedPrefix.length}) 不得大于或等于卡号总长度(${length})`); + } + + const digitsNeeded = length - selectedPrefix.length - 1; + if (digitsNeeded <= 0) { + throw new ValidationError('card-generator', `前缀配置错误:无法为 ${type} 生成剩余位数`); + } + + const patternLength = successfulPatterns && successfulPatterns.length > 0 + ? successfulPatterns[0].length + : null; + const canUseAdvancedStrategies = Boolean( + generation && + successfulPatterns && + successfulPatterns.length > 0 && + prefixInfo.supportsPatterns && + patternLength === digitsNeeded + 1 + ); + + if (canUseAdvancedStrategies) { const rand = Math.random(); - - // 策略1(20%):加权生成(基于统计分布) + if (rand < 0.2) { - return this.generateByWeights(selectedPrefix, length); + return this.generateByWeights(selectedPrefix, digitsNeeded, length); } - // 策略2(60%):变异真实案例(提高到60%,因为这是最可靠的) - else if (rand < 0.8) { - return this.generateByMutation(selectedPrefix, successfulPatterns, generation.mutationDigits); + if (rand < 0.8) { + return this.generateByMutation(selectedPrefix, successfulPatterns, generation.mutationDigits, digitsNeeded, length); } - // 策略3(20%):完全随机(降低到20%) + // 其余概率走纯随机策略 } - - // 策略3:纯随机生成(默认或回退) + if (useLuhn) { return generateLuhnNumber(selectedPrefix, length); - } else { - const remainingLength = length - selectedPrefix.length; - return selectedPrefix + randomDigits(remainingLength); } + + const remainingLength = length - selectedPrefix.length; + return selectedPrefix + randomDigits(remainingLength); + } + + selectPrefix(defaultPrefix, prefixes) { + const options = []; + + if (Array.isArray(prefixes) && prefixes.length > 0) { + prefixes.forEach((item) => { + if (!item) return; + + if (typeof item === 'string') { + options.push({ + fullPrefix: item, + weight: 1, + supportsPatterns: true + }); + return; + } + + if (typeof item === 'object') { + const bin = typeof item.bin === 'string' ? item.bin : ''; + const extension = typeof item.extension === 'string' ? item.extension : ''; + const fullPrefix = typeof item.fullPrefix === 'string' ? item.fullPrefix : `${bin}${extension}`; + + if (!fullPrefix) { + return; + } + + const weight = Number.isFinite(item.weight) && item.weight > 0 ? item.weight : 1; + const supportsPatterns = item.allowPatterns !== false; + + options.push({ fullPrefix, weight, supportsPatterns }); + } + }); + } + + if (options.length === 0 && defaultPrefix) { + return { fullPrefix: defaultPrefix, supportsPatterns: true }; + } + + if (options.length === 0) { + return { fullPrefix: null, supportsPatterns: false }; + } + + const totalWeight = options.reduce((sum, item) => sum + item.weight, 0); + let randomValue = Math.random() * totalWeight; + + for (const option of options) { + randomValue -= option.weight; + if (randomValue <= 0) { + return option; + } + } + + return options[options.length - 1]; } /** * 基于真实案例变异生成卡号 * @param {string} prefix - BIN前缀 - * @param {Array} patterns - 成功案例的后7位 - * @param {Array} mutationDigits - 变异数字个数[min, max] + * @param {Array} patterns - 成功案例的后缀(含校验位) + * @param {Array} mutationDigits - 变异数字个数[min, max] + * @param {number} digitsNeeded - 需要生成的主体位数(不含校验位) + * @param {number} totalLength - 卡号总长度 * @returns {string} */ - generateByMutation(prefix, patterns, mutationDigits) { - // 随机选择一个成功案例 + generateByMutation(prefix, patterns, mutationDigits, digitsNeeded, totalLength) { + if (!Array.isArray(patterns) || patterns.length === 0) { + return generateLuhnNumber(prefix, totalLength); + } + const basePattern = patterns[randomInt(0, patterns.length - 1)]; - - // 随机决定改变几个数字 - const changeCount = randomInt(mutationDigits[0], mutationDigits[1]); - const mutated = basePattern.split(''); - - // 改变指定数量的数字 + if (typeof basePattern !== 'string' || basePattern.length !== digitsNeeded + 1) { + return generateLuhnNumber(prefix, totalLength); + } + + const bodyDigits = basePattern.slice(0, digitsNeeded).split(''); + + let minChanges = 1; + let maxChanges = 1; + if (Array.isArray(mutationDigits) && mutationDigits.length > 0) { + const [minRaw, maxRaw] = mutationDigits; + if (Number.isFinite(minRaw)) { + minChanges = Math.max(0, minRaw); + } + if (Number.isFinite(maxRaw)) { + maxChanges = Math.max(minChanges, maxRaw); + } else { + maxChanges = Math.max(minChanges, minChanges); + } + } + + maxChanges = Math.min(maxChanges, digitsNeeded); + minChanges = Math.min(minChanges, maxChanges); + const changeCount = maxChanges > 0 ? randomInt(minChanges, maxChanges) : 0; + const changedPositions = new Set(); for (let i = 0; i < changeCount; i++) { let pos; do { - pos = randomInt(0, mutated.length - 1); - } while (changedPositions.has(pos)); // 避免重复位置 - + pos = randomInt(0, bodyDigits.length - 1); + } while (changedPositions.has(pos)); + changedPositions.add(pos); - - // 生成不同的数字 + let newDigit; do { newDigit = randomInt(0, 9).toString(); - } while (newDigit === mutated[pos]); - - mutated[pos] = newDigit; + } while (newDigit === bodyDigits[pos]); + + bodyDigits[pos] = newDigit; } - - // 组合前缀和变异后的后7位(去掉最后一位校验位) - const prefix15 = prefix + mutated.join('').slice(0, -1); - - // 重新计算Luhn校验位 - const checkDigit = this.calculateLuhnCheckDigit(prefix15); - - return prefix15 + checkDigit; + + const partial = prefix + bodyDigits.join(''); + const checkDigit = this.calculateLuhnCheckDigit(partial); + + return partial + checkDigit; } - + /** * 基于统计权重生成卡号(60个成功案例的分布) * @param {string} prefix - BIN前缀 - * @param {number} length - 总长度 + * @param {number} digitsNeeded - 需要生成的主体位数(不含校验位) + * @param {number} totalLength - 卡号总长度 * @returns {string} */ - generateByWeights(prefix, length) { + generateByWeights(prefix, digitsNeeded, totalLength) { + if (digitsNeeded <= 0) { + return generateLuhnNumber(prefix, totalLength); + } + // 基于60个成功案例的位置权重(频率分布) const positionWeights = [ [7, 5, 8, 2, 5, 7, 6, 7, 6, 7], // 位置1 @@ -177,21 +271,21 @@ class CardGenerator { [7, 3, 7, 2, 9, 6, 4, 6, 9, 7], // 位置7 ]; - // 生成6位(前15位去掉最后一位校验位) + if (digitsNeeded > positionWeights.length) { + return generateLuhnNumber(prefix, totalLength); + } + let pattern = ''; - for (let pos = 0; pos < 6; pos++) { + for (let pos = 0; pos < digitsNeeded; pos++) { const weights = positionWeights[pos]; const digit = this.weightedRandomDigit(weights); pattern += digit; } - - // 组合前缀 - const prefix15 = prefix + pattern; - - // 计算Luhn校验位 - const checkDigit = this.calculateLuhnCheckDigit(prefix15); - - return prefix15 + checkDigit; + + const partial = prefix + pattern; + const checkDigit = this.calculateLuhnCheckDigit(partial); + + return partial + checkDigit; } /**