This commit is contained in:
dengqichen 2025-11-19 16:48:59 +08:00
parent 675fcea97b
commit 1c642a8236
7 changed files with 199 additions and 30 deletions

15
add-card-fields.sql Normal file
View File

@ -0,0 +1,15 @@
-- 添加卡片有效期和CVV字段到 windsurf_accounts 表
-- 执行时间: 2025-11-19
-- 作用: 保存完整的支付卡信息有效期月份、年份、CVV安全码
ALTER TABLE windsurf_accounts
ADD COLUMN payment_card_expiry_month VARCHAR(2) COMMENT '支付卡有效期-月份(01-12)',
ADD COLUMN payment_card_expiry_year VARCHAR(2) COMMENT '支付卡有效期-年份(26-30)',
ADD COLUMN payment_card_cvv VARCHAR(3) COMMENT '支付卡CVV安全码(3位数字)';
-- 添加索引(提升卡号查询性能,用于去重)
CREATE INDEX idx_payment_card_number ON windsurf_accounts(payment_card_number);
-- 验证字段和索引已添加
DESCRIBE windsurf_accounts;
SHOW INDEX FROM windsurf_accounts;

View File

@ -12,7 +12,8 @@ const CARD_TYPES = {
cvvLength: 3, cvvLength: 3,
useLuhn: true, useLuhn: true,
// 成功案例的后7位模式43个真实案例统计分析显示接近均匀随机分布 // 成功案例的后7位模式60个真实成功支付案例
// 统计分析显示位置4的数字0占20%位置2的数字4占18.3%
successfulPatterns: [ successfulPatterns: [
'1130577', '0744030', '9888788', '9131205', '1450744', '1130577', '0744030', '9888788', '9131205', '1450744',
'7238010', '7300364', '0814288', '6042579', '6361755', '7238010', '7300364', '0814288', '6042579', '6361755',
@ -22,7 +23,10 @@ const CARD_TYPES = {
'2464926', '2487000', '5452860', '8491592', '5022853', '2464926', '2487000', '5452860', '8491592', '5022853',
'5864858', '4742832', '0023658', '7416988', '7093159', '5864858', '4742832', '0023658', '7416988', '7093159',
'9198576', '8160064', '6223252', '4873785', '1299976', '9198576', '8160064', '6223252', '4873785', '1299976',
'2940032', '6998937', '5800241' '2940032', '6998937', '5800241', '3770784', '5055812',
'8774419', '6781457', '2738949', '2602400', '8575105',
'6496080', '0057649', '9574719', '8435128', '2797374',
'5956423', '7237848', '0385107', '4252006', '7562054'
], ],
// 生成策略配置 // 生成策略配置

View File

@ -7,35 +7,96 @@ const { CARD_TYPES, EXPIRY_CONFIG } = require('./config');
const { ValidationError } = require('../../errors'); const { ValidationError } = require('../../errors');
class CardGenerator { class CardGenerator {
constructor() { constructor(database = null) {
this.cardTypes = CARD_TYPES; this.cardTypes = CARD_TYPES;
this.expiryConfig = EXPIRY_CONFIG; this.expiryConfig = EXPIRY_CONFIG;
this.usedNumbers = new Set(); // 去重机制:记录已生成的卡号
this.database = database; // 数据库连接(可选)
} }
/** /**
* 生成卡号混合策略变异真实案例 + 纯随机 * 生成卡号带去重机制 + 数据库查重
* @param {string} type - 卡类型 * @param {string} type - 卡类型
* @returns {string} * @returns {Promise<string>}
*/ */
generateCardNumber(type) { async generateCardNumber(type) {
const config = this.cardTypes[type]; const config = this.cardTypes[type];
if (!config) { if (!config) {
throw new ValidationError('card-generator', `Unknown card type: ${type}`); throw new ValidationError('card-generator', `Unknown card type: ${type}`);
} }
const { prefix, length, useLuhn, successfulPatterns, generation } = config; // 尝试生成唯一卡号最多100次
let attempts = 0;
const maxAttempts = 100;
// 如果有成功案例和生成策略配置,使用混合策略 while (attempts < maxAttempts) {
if (successfulPatterns && generation) { const cardNumber = this._generateCardNumberInternal(type, config);
const useMutation = Math.random() < generation.mutationRate;
if (useMutation) { // 检查1内存去重本次运行
// 策略1基于真实案例变异 if (this.usedNumbers.has(cardNumber)) {
return this.generateByMutation(prefix, successfulPatterns, generation.mutationDigits); attempts++;
continue;
} }
// 检查2数据库去重历史记录
if (this.database) {
const existsInDb = await this.checkCardNumberInDatabase(cardNumber);
if (existsInDb) {
attempts++;
continue;
}
}
// 通过所有检查,记录并返回
this.usedNumbers.add(cardNumber);
return cardNumber;
} }
// 策略2纯随机生成默认或回退 throw new ValidationError('card-generator', `无法生成唯一卡号(${maxAttempts}次尝试后仍重复)`);
}
/**
* 检查卡号是否在数据库中已存在
* @param {string} cardNumber - 卡号
* @returns {Promise<boolean>}
*/
async checkCardNumberInDatabase(cardNumber) {
try {
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) {
// 如果数据库查询失败,记录错误但不影响生成(降级为仅内存去重)
console.warn('数据库查询失败,仅使用内存去重:', error.message);
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();
// 策略130%):加权生成(基于统计分布)
if (rand < 0.3) {
return this.generateByWeights(prefix, length);
}
// 策略240%):变异真实案例
else if (rand < 0.7) {
return this.generateByMutation(prefix, successfulPatterns, generation.mutationDigits);
}
// 策略330%):完全随机
}
// 策略3纯随机生成默认或回退
if (useLuhn) { if (useLuhn) {
return generateLuhnNumber(prefix, length); return generateLuhnNumber(prefix, length);
} else { } else {
@ -87,6 +148,58 @@ class CardGenerator {
return prefix15 + checkDigit; 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校验位 * 计算Luhn校验位
* @param {string} partial - 不含校验位的卡号 * @param {string} partial - 不含校验位的卡号
@ -141,10 +254,10 @@ class CardGenerator {
/** /**
* 生成完整的信用卡信息 * 生成完整的信用卡信息
* @param {string} type - 卡类型默认'unionpay' * @param {string} type - 卡类型默认'unionpay'
* @returns {Object} * @returns {Promise<Object>}
*/ */
generate(type = 'unionpay') { async generate(type = 'unionpay') {
const number = this.generateCardNumber(type); const number = await this.generateCardNumber(type);
const expiry = this.generateExpiry(); const expiry = this.generateExpiry();
const cvv = this.generateCVV(type); const cvv = this.generateCVV(type);
@ -161,12 +274,12 @@ class CardGenerator {
* 批量生成 * 批量生成
* @param {number} count - 数量 * @param {number} count - 数量
* @param {string} type - 卡类型 * @param {string} type - 卡类型
* @returns {Array} * @returns {Promise<Array>}
*/ */
generateBatch(count, type = 'unionpay') { async generateBatch(count, type = 'unionpay') {
const cards = []; const cards = [];
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
cards.push(this.generate(type)); cards.push(await this.generate(type));
} }
return cards; return cards;
} }

View File

@ -24,6 +24,9 @@ class AccountRepository {
billingDays, billingDays,
billingDate, billingDate,
paymentCardNumber, paymentCardNumber,
paymentCardExpiryMonth,
paymentCardExpiryYear,
paymentCardCvv,
paymentCountry, paymentCountry,
status, status,
isOnSale isOnSale
@ -33,8 +36,9 @@ class AccountRepository {
INSERT INTO windsurf_accounts ( INSERT INTO windsurf_accounts (
email, password, first_name, last_name, registration_time, email, password, first_name, last_name, registration_time,
quota_used, quota_total, billing_days, billing_date, quota_used, quota_total, billing_days, billing_date,
payment_card_number, payment_country, status, is_on_sale payment_card_number, payment_card_expiry_month, payment_card_expiry_year, payment_card_cvv,
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) payment_country, status, is_on_sale
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`; `;
const params = [ const params = [
@ -48,6 +52,9 @@ class AccountRepository {
billingDays || null, billingDays || null,
billingDate || null, billingDate || null,
paymentCardNumber || null, paymentCardNumber || null,
paymentCardExpiryMonth || null,
paymentCardExpiryYear || null,
paymentCardCvv || null,
paymentCountry || 'MO', paymentCountry || 'MO',
status || 'active', status || 'active',
isOnSale !== undefined ? isOnSale : false isOnSale !== undefined ? isOnSale : false
@ -110,6 +117,18 @@ class AccountRepository {
updates.push('payment_card_number = ?'); updates.push('payment_card_number = ?');
params.push(accountData.paymentCardNumber); params.push(accountData.paymentCardNumber);
} }
if (accountData.paymentCardExpiryMonth !== undefined) {
updates.push('payment_card_expiry_month = ?');
params.push(accountData.paymentCardExpiryMonth);
}
if (accountData.paymentCardExpiryYear !== undefined) {
updates.push('payment_card_expiry_year = ?');
params.push(accountData.paymentCardExpiryYear);
}
if (accountData.paymentCardCvv !== undefined) {
updates.push('payment_card_cvv = ?');
params.push(accountData.paymentCardCvv);
}
if (accountData.paymentCountry !== undefined) { if (accountData.paymentCountry !== undefined) {
updates.push('payment_country = ?'); updates.push('payment_country = ?');
params.push(accountData.paymentCountry); params.push(accountData.paymentCountry);

View File

@ -2285,6 +2285,9 @@ class WindsurfRegister {
billingDays: this.billingInfo ? parseInt(this.billingInfo.days) : null, billingDays: this.billingInfo ? parseInt(this.billingInfo.days) : null,
billingDate: this.billingInfo ? this.billingInfo.date : null, billingDate: this.billingInfo ? this.billingInfo.date : null,
paymentCardNumber: this.cardInfo ? this.cardInfo.number : null, paymentCardNumber: this.cardInfo ? this.cardInfo.number : null,
paymentCardExpiryMonth: this.cardInfo ? this.cardInfo.month : null,
paymentCardExpiryYear: this.cardInfo ? this.cardInfo.year : null,
paymentCardCvv: this.cardInfo ? this.cardInfo.cvv : null,
paymentCountry: this.cardInfo ? this.cardInfo.country : 'MO', paymentCountry: this.cardInfo ? this.cardInfo.country : 'MO',
status: 'active', status: 'active',
isOnSale: false isOnSale: false

View File

@ -1,6 +1,7 @@
const SiteAdapter = require('../core/site-adapter'); const SiteAdapter = require('../core/site-adapter');
const AccountDataGenerator = require('../../../shared/libs/account-generator'); const AccountDataGenerator = require('../../../shared/libs/account-generator');
const CardGenerator = require('../../../shared/libs/card-generator'); const CardGenerator = require('../../../shared/libs/card-generator');
const database = require('../../../shared/libs/database');
/** /**
* Windsurf 站点适配器 * Windsurf 站点适配器
@ -11,7 +12,8 @@ class WindsurfAdapter extends SiteAdapter {
// 数据生成器 // 数据生成器
this.dataGen = new AccountDataGenerator(); this.dataGen = new AccountDataGenerator();
this.cardGen = new CardGenerator(); // 卡号生成器(传入数据库连接以实现数据库去重)
this.cardGen = new CardGenerator(database);
} }
/** /**
@ -25,7 +27,7 @@ class WindsurfAdapter extends SiteAdapter {
// 生成账户数据 // 生成账户数据
const accountData = this.dataGen.generateAccount(); const accountData = this.dataGen.generateAccount();
const cardInfo = this.cardGen.generate(); const cardInfo = await this.cardGen.generate();
// 存储到上下文 // 存储到上下文
this.context.data = { this.context.data = {
@ -245,12 +247,12 @@ class WindsurfAdapter extends SiteAdapter {
/** /**
* 重新生成银行卡用于重试 * 重新生成银行卡用于重试
*/ */
regenerateCard() { async regenerateCard() {
const newCard = this.cardGen.generate(); const newCard = await this.cardGen.generate();
this.context.data.card = newCard; this.context.data.card = newCard;
const bin = newCard.number.substring(0, 9); const bin = newCard.number.substring(0, 9);
this.log('info', `重新生成卡号: ${newCard.number} (BIN: ${bin})`); this.log('info', `重新生成卡号: ${newCard.number} (BIN: ${bin})`);
return newCard; return { card: newCard };
} }
/** /**
@ -478,6 +480,9 @@ class WindsurfAdapter extends SiteAdapter {
billingDays: billingInfo ? parseInt(billingInfo.days) : null, billingDays: billingInfo ? parseInt(billingInfo.days) : null,
billingDate: billingInfo ? billingInfo.date : null, billingDate: billingInfo ? billingInfo.date : null,
paymentCardNumber: card ? card.number : null, paymentCardNumber: card ? card.number : null,
paymentCardExpiryMonth: card ? card.month : null,
paymentCardExpiryYear: card ? card.year : null,
paymentCardCvv: card ? card.cvv : null,
paymentCountry: card ? card.country : 'MO', paymentCountry: card ? card.country : 'MO',
status: 'active', status: 'active',
isOnSale: false isOnSale: false

View File

@ -22,6 +22,9 @@ CREATE TABLE IF NOT EXISTS `windsurf_accounts` (
`billing_days` INT COMMENT '下次账单天数', `billing_days` INT COMMENT '下次账单天数',
`billing_date` VARCHAR(50) COMMENT '账单日期', `billing_date` VARCHAR(50) COMMENT '账单日期',
`payment_card_number` VARCHAR(20) COMMENT '支付卡号', `payment_card_number` VARCHAR(20) COMMENT '支付卡号',
`payment_card_expiry_month` VARCHAR(2) COMMENT '支付卡有效期-月份(01-12)',
`payment_card_expiry_year` VARCHAR(2) COMMENT '支付卡有效期-年份(26-30)',
`payment_card_cvv` VARCHAR(3) COMMENT '支付卡CVV安全码(3位数字)',
`payment_country` VARCHAR(10) DEFAULT 'MO' COMMENT '支付国家代码', `payment_country` VARCHAR(10) DEFAULT 'MO' COMMENT '支付国家代码',
`status` ENUM('active', 'expired', 'error') DEFAULT 'active' COMMENT '账号状态', `status` ENUM('active', 'expired', 'error') DEFAULT 'active' COMMENT '账号状态',
`is_on_sale` BOOLEAN DEFAULT FALSE COMMENT '是否已上架销售', `is_on_sale` BOOLEAN DEFAULT FALSE COMMENT '是否已上架销售',
@ -30,9 +33,16 @@ CREATE TABLE IF NOT EXISTS `windsurf_accounts` (
UNIQUE KEY `uk_email` (`email`), UNIQUE KEY `uk_email` (`email`),
KEY `idx_status` (`status`), KEY `idx_status` (`status`),
KEY `idx_registration_time` (`registration_time`), KEY `idx_registration_time` (`registration_time`),
KEY `idx_created_at` (`created_at`) KEY `idx_created_at` (`created_at`),
KEY `idx_payment_card_number` (`payment_card_number`) COMMENT '卡号索引-用于去重查询'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Windsurf 账号表'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Windsurf 账号表';
-- 插入示例数据(可选) -- 插入示例数据(可选)
-- INSERT INTO `windsurf_accounts` (email, password, first_name, last_name, quota_total) -- INSERT INTO `windsurf_accounts` (
-- VALUES ('test@example.com', 'Password123!', 'John', 'Doe', 600.00); -- email, password, first_name, last_name,
-- quota_total, payment_card_number, payment_card_expiry_month,
-- payment_card_expiry_year, payment_card_cvv
-- ) VALUES (
-- 'test@example.com', 'Password123!', 'John', 'Doe',
-- 600.00, '6228367541234567', '12', '28', '123'
-- );