auto-account-machine/src/shared/libs/card-generator/generator.js
2025-11-21 13:27:41 +08:00

417 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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<string>}
*/
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<boolean>}
*/
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<string>} patterns - 成功案例的后缀(含校验位)
* @param {Array<number>} 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<Object>}
*/
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<Array>}
*/
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;