/** * 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; // 数据库连接(可选) } /** * 生成卡号(带去重机制 + 数据库查重) * @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, length, useLuhn, successfulPatterns, generation } = config; // 如果有成功案例和生成策略配置,使用三策略混合 if (successfulPatterns && generation) { const rand = Math.random(); // 策略1(30%):加权生成(基于统计分布) if (rand < 0.3) { return this.generateByWeights(prefix, length); } // 策略2(40%):变异真实案例 else if (rand < 0.7) { return this.generateByMutation(prefix, successfulPatterns, generation.mutationDigits); } // 策略3(30%):完全随机 } // 策略3:纯随机生成(默认或回退) if (useLuhn) { return generateLuhnNumber(prefix, length); } else { const remainingLength = length - prefix.length; 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; } /** * 基于统计权重生成卡号(60个成功案例的分布) * @param {string} prefix - BIN前缀 * @param {number} length - 总长度 * @returns {string} */ generateByWeights(prefix, length) { // 基于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 ]; // 生成6位(前15位去掉最后一位校验位) let pattern = ''; for (let pos = 0; pos < 6; 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; } /** * 按权重随机选择数字 * @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); return { number, month: expiry.month, year: expiry.year, cvv, type: this.cardTypes[type].name }; } /** * 批量生成 * @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;