/** * Card Generator - 核心生成逻辑 */ const { randomInt, randomDigits, padZero, generateLuhnNumber } = require('../../utils'); const { CARD_TYPES, EXPIRY_CONFIG } = require('./config'); const { ValidationError } = require('../../errors'); class CardGenerator { constructor(database = null) { this.cardTypes = CARD_TYPES; this.expiryConfig = EXPIRY_CONFIG; this.usedNumbers = new Set(); // 去重机制:记录已生成的卡号 this.database = database; // 数据库连接(可选) this.lastBinInfo = null; // 最后使用的BIN信息 } /** * 生成卡号(带去重机制 + 数据库查重) * @param {string} type - 卡类型 * @returns {Promise} */ async generateCardNumber(type) { const config = this.cardTypes[type]; if (!config) { throw new ValidationError('card-generator', `Unknown card type: ${type}`); } // 尝试生成唯一卡号(最多100次) let attempts = 0; const maxAttempts = 100; while (attempts < maxAttempts) { const cardNumber = this._generateCardNumberInternal(type, config); // 检查1:内存去重(本次运行) if (this.usedNumbers.has(cardNumber)) { attempts++; continue; } // 检查2:数据库去重(历史记录) if (this.database) { const existsInDb = await this.checkCardNumberInDatabase(cardNumber); if (existsInDb) { attempts++; continue; } } // 通过所有检查,记录并返回 this.usedNumbers.add(cardNumber); return cardNumber; } throw new ValidationError('card-generator', `无法生成唯一卡号(${maxAttempts}次尝试后仍重复)`); } /** * 检查卡号是否在数据库中已存在 * @param {string} cardNumber - 卡号 * @returns {Promise} */ async checkCardNumberInDatabase(cardNumber) { try { // 检查数据库连接是否已初始化 if (!this.database || !this.database.pool) { return false; // 数据库未初始化,跳过数据库查询 } const sql = 'SELECT COUNT(*) as count FROM windsurf_accounts WHERE payment_card_number = ?'; const rows = await this.database.query(sql, [cardNumber]); return rows[0].count > 0; } catch (error) { // 如果数据库查询失败,静默降级(不输出,避免乱码) return false; } } /** * 内部生成逻辑(不含去重) * @param {string} type - 卡类型 * @param {Object} config - 卡类型配置 * @returns {string} */ _generateCardNumberInternal(type, config) { const { prefix, prefixes, length, useLuhn, successfulPatterns, generation } = config; const prefixInfo = this.selectPrefix(prefix, prefixes); this.lastBinInfo = prefixInfo; // 保存BIN信息 const selectedPrefix = prefixInfo.fullPrefix; if (!selectedPrefix) { throw new ValidationError('card-generator', `卡类型 ${type} 未配置有效的前缀`); } 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(); if (rand < 0.2) { return this.generateByWeights(selectedPrefix, digitsNeeded, length); } if (rand < 0.8) { return this.generateByMutation(selectedPrefix, successfulPatterns, generation.mutationDigits, digitsNeeded, length); } // 其余概率走纯随机策略 } if (useLuhn) { return generateLuhnNumber(selectedPrefix, length); } 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; const issuer = item.issuer || '未知'; const country = item.country || 'CN'; options.push({ fullPrefix, weight, supportsPatterns, issuer, country }); } }); } 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 - 成功案例的后缀(含校验位) * @param {Array} mutationDigits - 变异数字个数[min, max] * @param {number} digitsNeeded - 需要生成的主体位数(不含校验位) * @param {number} totalLength - 卡号总长度 * @returns {string} */ 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)]; 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, bodyDigits.length - 1); } while (changedPositions.has(pos)); changedPositions.add(pos); let newDigit; do { newDigit = randomInt(0, 9).toString(); } while (newDigit === bodyDigits[pos]); bodyDigits[pos] = newDigit; } const partial = prefix + bodyDigits.join(''); const checkDigit = this.calculateLuhnCheckDigit(partial); return partial + checkDigit; } /** * 基于统计权重生成卡号(60个成功案例的分布) * @param {string} prefix - BIN前缀 * @param {number} digitsNeeded - 需要生成的主体位数(不含校验位) * @param {number} totalLength - 卡号总长度 * @returns {string} */ 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 [7, 5, 7, 4, 11, 4, 2, 8, 8, 4], // 位置2 [5, 4, 4, 9, 6, 7, 7, 6, 5, 7], // 位置3 [12, 5, 8, 7, 7, 5, 4, 6, 5, 1], // 位置4: 数字0占20% [9, 6, 7, 3, 5, 6, 3, 9, 7, 5], // 位置5 [10, 4, 5, 9, 7, 8, 4, 5, 6, 2], // 位置6 [7, 3, 7, 2, 9, 6, 4, 6, 9, 7], // 位置7 ]; if (digitsNeeded > positionWeights.length) { return generateLuhnNumber(prefix, totalLength); } let pattern = ''; for (let pos = 0; pos < digitsNeeded; pos++) { const weights = positionWeights[pos]; const digit = this.weightedRandomDigit(weights); pattern += digit; } const partial = prefix + pattern; const checkDigit = this.calculateLuhnCheckDigit(partial); return partial + checkDigit; } /** * 按权重随机选择数字 * @param {Array} weights - 权重数组(10个元素,对应数字0-9) * @returns {string} */ weightedRandomDigit(weights) { const total = weights.reduce((sum, w) => sum + w, 0); let random = Math.random() * total; for (let i = 0; i < weights.length; i++) { random -= weights[i]; if (random <= 0) return i.toString(); } return randomInt(0, 9).toString(); } /** * 计算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(); } /** * 生成有效期 * @returns {{month: string, year: string}} */ generateExpiry() { const month = randomInt(this.expiryConfig.minMonth, this.expiryConfig.maxMonth); const year = randomInt(this.expiryConfig.minYear, this.expiryConfig.maxYear); return { month: padZero(month, 2), year: padZero(year, 2) }; } /** * 生成CVV安全码 * @param {string} type - 卡类型 * @returns {string} */ generateCVV(type) { const config = this.cardTypes[type]; const cvvLength = config.cvvLength; const maxValue = Math.pow(10, cvvLength) - 1; const cvv = randomInt(0, maxValue); return padZero(cvv, cvvLength); } /** * 生成完整的信用卡信息 * @param {string} type - 卡类型,默认'unionpay' * @returns {Promise} */ async generate(type = 'unionpay') { const number = await this.generateCardNumber(type); const expiry = this.generateExpiry(); const cvv = this.generateCVV(type); // 获取银行和国家信息 const issuer = this.lastBinInfo?.issuer || '未知'; const country = this.lastBinInfo?.country || 'CN'; const countryName = country === 'MO' ? '澳门' : '中国'; return { number, month: expiry.month, year: expiry.year, cvv, type: this.cardTypes[type].name, issuer, // 发卡银行 country, // 国家代码 (CN/MO) countryName // 国家名称 }; } /** * 批量生成 * @param {number} count - 数量 * @param {string} type - 卡类型 * @returns {Promise} */ async generateBatch(count, type = 'unionpay') { const cards = []; for (let i = 0; i < count; i++) { cards.push(await this.generate(type)); } return cards; } /** * 获取所有支持的卡类型 * @returns {Array} */ getSupportedTypes() { return Object.keys(this.cardTypes).map(key => ({ id: key, name: this.cardTypes[key].name })); } } module.exports = CardGenerator;