dasdasd
This commit is contained in:
parent
675fcea97b
commit
1c642a8236
15
add-card-fields.sql
Normal file
15
add-card-fields.sql
Normal 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;
|
||||
@ -12,7 +12,8 @@ const CARD_TYPES = {
|
||||
cvvLength: 3,
|
||||
useLuhn: true,
|
||||
|
||||
// 成功案例的后7位模式(43个真实案例,统计分析显示接近均匀随机分布)
|
||||
// 成功案例的后7位模式(60个真实成功支付案例)
|
||||
// 统计分析显示:位置4的数字0占20%,位置2的数字4占18.3%
|
||||
successfulPatterns: [
|
||||
'1130577', '0744030', '9888788', '9131205', '1450744',
|
||||
'7238010', '7300364', '0814288', '6042579', '6361755',
|
||||
@ -22,7 +23,10 @@ const CARD_TYPES = {
|
||||
'2464926', '2487000', '5452860', '8491592', '5022853',
|
||||
'5864858', '4742832', '0023658', '7416988', '7093159',
|
||||
'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'
|
||||
],
|
||||
|
||||
// 生成策略配置
|
||||
|
||||
@ -7,35 +7,96 @@ const { CARD_TYPES, EXPIRY_CONFIG } = require('./config');
|
||||
const { ValidationError } = require('../../errors');
|
||||
|
||||
class CardGenerator {
|
||||
constructor() {
|
||||
constructor(database = null) {
|
||||
this.cardTypes = CARD_TYPES;
|
||||
this.expiryConfig = EXPIRY_CONFIG;
|
||||
this.usedNumbers = new Set(); // 去重机制:记录已生成的卡号
|
||||
this.database = database; // 数据库连接(可选)
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成卡号(混合策略:变异真实案例 + 纯随机)
|
||||
* 生成卡号(带去重机制 + 数据库查重)
|
||||
* @param {string} type - 卡类型
|
||||
* @returns {string}
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
generateCardNumber(type) {
|
||||
async generateCardNumber(type) {
|
||||
const config = this.cardTypes[type];
|
||||
if (!config) {
|
||||
throw new ValidationError('card-generator', `Unknown card type: ${type}`);
|
||||
}
|
||||
|
||||
const { prefix, length, useLuhn, successfulPatterns, generation } = config;
|
||||
// 尝试生成唯一卡号(最多100次)
|
||||
let attempts = 0;
|
||||
const maxAttempts = 100;
|
||||
|
||||
// 如果有成功案例和生成策略配置,使用混合策略
|
||||
if (successfulPatterns && generation) {
|
||||
const useMutation = Math.random() < generation.mutationRate;
|
||||
while (attempts < maxAttempts) {
|
||||
const cardNumber = this._generateCardNumberInternal(type, config);
|
||||
|
||||
if (useMutation) {
|
||||
// 策略1:基于真实案例变异
|
||||
return this.generateByMutation(prefix, successfulPatterns, generation.mutationDigits);
|
||||
// 检查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;
|
||||
}
|
||||
|
||||
// 策略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();
|
||||
|
||||
// 策略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 {
|
||||
@ -87,6 +148,58 @@ class CardGenerator {
|
||||
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 - 不含校验位的卡号
|
||||
@ -141,10 +254,10 @@ class CardGenerator {
|
||||
/**
|
||||
* 生成完整的信用卡信息
|
||||
* @param {string} type - 卡类型,默认'unionpay'
|
||||
* @returns {Object}
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
generate(type = 'unionpay') {
|
||||
const number = this.generateCardNumber(type);
|
||||
async generate(type = 'unionpay') {
|
||||
const number = await this.generateCardNumber(type);
|
||||
const expiry = this.generateExpiry();
|
||||
const cvv = this.generateCVV(type);
|
||||
|
||||
@ -161,12 +274,12 @@ class CardGenerator {
|
||||
* 批量生成
|
||||
* @param {number} count - 数量
|
||||
* @param {string} type - 卡类型
|
||||
* @returns {Array}
|
||||
* @returns {Promise<Array>}
|
||||
*/
|
||||
generateBatch(count, type = 'unionpay') {
|
||||
async generateBatch(count, type = 'unionpay') {
|
||||
const cards = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
cards.push(this.generate(type));
|
||||
cards.push(await this.generate(type));
|
||||
}
|
||||
return cards;
|
||||
}
|
||||
|
||||
@ -24,6 +24,9 @@ class AccountRepository {
|
||||
billingDays,
|
||||
billingDate,
|
||||
paymentCardNumber,
|
||||
paymentCardExpiryMonth,
|
||||
paymentCardExpiryYear,
|
||||
paymentCardCvv,
|
||||
paymentCountry,
|
||||
status,
|
||||
isOnSale
|
||||
@ -33,8 +36,9 @@ class AccountRepository {
|
||||
INSERT INTO windsurf_accounts (
|
||||
email, password, first_name, last_name, registration_time,
|
||||
quota_used, quota_total, billing_days, billing_date,
|
||||
payment_card_number, payment_country, status, is_on_sale
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
payment_card_number, payment_card_expiry_month, payment_card_expiry_year, payment_card_cvv,
|
||||
payment_country, status, is_on_sale
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
|
||||
const params = [
|
||||
@ -48,6 +52,9 @@ class AccountRepository {
|
||||
billingDays || null,
|
||||
billingDate || null,
|
||||
paymentCardNumber || null,
|
||||
paymentCardExpiryMonth || null,
|
||||
paymentCardExpiryYear || null,
|
||||
paymentCardCvv || null,
|
||||
paymentCountry || 'MO',
|
||||
status || 'active',
|
||||
isOnSale !== undefined ? isOnSale : false
|
||||
@ -110,6 +117,18 @@ class AccountRepository {
|
||||
updates.push('payment_card_number = ?');
|
||||
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) {
|
||||
updates.push('payment_country = ?');
|
||||
params.push(accountData.paymentCountry);
|
||||
|
||||
@ -2285,6 +2285,9 @@ class WindsurfRegister {
|
||||
billingDays: this.billingInfo ? parseInt(this.billingInfo.days) : null,
|
||||
billingDate: this.billingInfo ? this.billingInfo.date : 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',
|
||||
status: 'active',
|
||||
isOnSale: false
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
const SiteAdapter = require('../core/site-adapter');
|
||||
const AccountDataGenerator = require('../../../shared/libs/account-generator');
|
||||
const CardGenerator = require('../../../shared/libs/card-generator');
|
||||
const database = require('../../../shared/libs/database');
|
||||
|
||||
/**
|
||||
* Windsurf 站点适配器
|
||||
@ -11,7 +12,8 @@ class WindsurfAdapter extends SiteAdapter {
|
||||
|
||||
// 数据生成器
|
||||
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 cardInfo = this.cardGen.generate();
|
||||
const cardInfo = await this.cardGen.generate();
|
||||
|
||||
// 存储到上下文
|
||||
this.context.data = {
|
||||
@ -245,12 +247,12 @@ class WindsurfAdapter extends SiteAdapter {
|
||||
/**
|
||||
* 重新生成银行卡(用于重试)
|
||||
*/
|
||||
regenerateCard() {
|
||||
const newCard = this.cardGen.generate();
|
||||
async regenerateCard() {
|
||||
const newCard = await this.cardGen.generate();
|
||||
this.context.data.card = newCard;
|
||||
const bin = newCard.number.substring(0, 9);
|
||||
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,
|
||||
billingDate: billingInfo ? billingInfo.date : 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',
|
||||
status: 'active',
|
||||
isOnSale: false
|
||||
|
||||
@ -22,6 +22,9 @@ CREATE TABLE IF NOT EXISTS `windsurf_accounts` (
|
||||
`billing_days` INT COMMENT '下次账单天数',
|
||||
`billing_date` VARCHAR(50) 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 '支付国家代码',
|
||||
`status` ENUM('active', 'expired', 'error') DEFAULT 'active' COMMENT '账号状态',
|
||||
`is_on_sale` BOOLEAN DEFAULT FALSE COMMENT '是否已上架销售',
|
||||
@ -30,9 +33,16 @@ CREATE TABLE IF NOT EXISTS `windsurf_accounts` (
|
||||
UNIQUE KEY `uk_email` (`email`),
|
||||
KEY `idx_status` (`status`),
|
||||
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 账号表';
|
||||
|
||||
-- 插入示例数据(可选)
|
||||
-- INSERT INTO `windsurf_accounts` (email, password, first_name, last_name, quota_total)
|
||||
-- VALUES ('test@example.com', 'Password123!', 'John', 'Doe', 600.00);
|
||||
-- INSERT INTO `windsurf_accounts` (
|
||||
-- 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'
|
||||
-- );
|
||||
|
||||
Loading…
Reference in New Issue
Block a user