This commit is contained in:
dengqichen 2025-11-17 17:26:10 +08:00
parent 5e6a0adc73
commit 4fcd5d1df5
12 changed files with 1326 additions and 63 deletions

7
.env
View File

@ -2,3 +2,10 @@
ADSPOWER_USER_ID=k1728p8l ADSPOWER_USER_ID=k1728p8l
ADSPOWER_API_KEY=35de43696f6241f3df895f2f48777a99 ADSPOWER_API_KEY=35de43696f6241f3df895f2f48777a99
# ADSPOWER_API=http://local.adspower.net:50325 # ADSPOWER_API=http://local.adspower.net:50325
# MySQL 数据库配置
MYSQL_HOST=172.22.222.111
MYSQL_PORT=3306
MYSQL_USER=windsurf-auto-register
MYSQL_PASSWORD=Qichen5210523
MYSQL_DATABASE=auto_register

View File

@ -0,0 +1,241 @@
# CapSolver 验证失败问题分析
## 问题现象
使用 CapSolver API Key 配置后,无法自动绕过 Cloudflare Turnstile 验证。
## 系统架构分析
### 当前配置状态
- ✅ API Key 已配置: `CAP-0FCDDA4906E87D9F4FF68EAECD34E320876FBA70E4F30EA1ADCD264EDB15E4BF`
- ✅ 扩展路径存在: `extensions/capsolver/`
- ✅ 配置文件正确: `extensions/capsolver/assets/config.js`
- ✅ 扩展关键文件完整:
- manifest.json ✓
- config.js ✓
- cloudflare-content.js ✓
- inject-turnstile.js ✓
- background.js ✓
### 扩展工作机制
#### 1. 扩展注入流程
```
manifest.json (content_scripts)
cloudflare-content.js (run_at: document_start)
inject-turnstile.js (注入到页面)
拦截 window.turnstile.render()
发送消息给 background.js
调用 CapSolver API
返回 token 并触发回调
```
#### 2. inject-turnstile.js 的关键逻辑
- 创建 `window.turnstile` Proxy
- 拦截 `render()` 方法
- 提取 sitekey 和 callback
- 发送 `registerTurnstile` 消息
- 监听 `turnstileSolved` 消息
- 调用原始回调函数
#### 3. windsurf.js 的等待逻辑
```javascript
// 步骤2: 507-542行
if (this.capsolverKey) {
// 等待按钮激活(检测扩展是否完成)
while (elapsed < 60000) {
const buttonEnabled = await this.page.evaluate(() => {
const button = document.querySelector('button');
return button && !button.disabled && button.textContent.trim() === 'Continue';
});
if (buttonEnabled) {
// 成功
break;
}
await new Promise(resolve => setTimeout(resolve, 2000));
elapsed = Date.now() - startTime;
}
}
```
## 可能的问题原因
### 问题1: 扩展未正确加载 ⭐最可能⭐
**症状**:
- 扩展文件存在但未被浏览器激活
- 控制台没有扩展日志
- inject-turnstile.js 未注入到页面
**原因**:
1. Windows 路径问题已修复第241-246行
2. Manifest V3 的 service worker 限制
3. Chrome 扩展权限问题
**验证方法**:
```javascript
// 在页面执行
console.log(window.turnstile); // 应该是Proxy对象
console.log(window.registerTurnstileData); // 应该有sitekey
```
### 问题2: Turnstile API未加载完成
**症状**:
- 第495行报错: "Turnstile API 未加载"
- scripts数组为空
**原因**:
1. Cloudflare CDN 被墙(中国大陆网络)
2. 扩展阻止了 Turnstile 脚本加载
3. 页面导航过快,脚本未加载完
**当前处理**:
- 第502-503行: 等待30秒重试
### 问题3: 扩展脚本执行时机问题
**症状**:
- inject-turnstile.js 注入太晚
- window.turnstile 已经被原始脚本创建
- Proxy 拦截失败
**原因**:
- cloudflare-content.js 的 run_at: "document_start" 可能不够早
- React 应用的 Turnstile 组件可能动态加载
### 问题4: API Key 或余额问题
**症状**:
- 扩展调用 API 失败
- 60秒超时
**验证**:
- 检查 CapSolver 控制台余额
- 检查 API Key 是否有效
- 检查网络能否访问 api.capsolver.com
### 问题5: Content Script与页面隔离
**症状**:
- Content script 无法访问页面的 window 对象
- inject-turnstile.js 需要通过 injected script 运行
**可能问题**:
- 扩展的 web_accessible_resources 配置
- script 注入方式不对
## 调试建议
### 步骤1: 验证扩展是否加载
```bash
# 运行注册流程
node src/cli.js register -s windsurf --keep-browser-open
# 在浏览器控制台检查:
chrome://extensions/
# 查看 "Captcha Solver" 是否已启用
# 查看是否有错误
```
### 步骤2: 检查页面注入
在 Turnstile 页面打开控制台:
```javascript
// 检查扩展是否注入
console.log(window.turnstile);
// 应该输出: Proxy {render: ƒ, reset: ƒ, ...}
// 检查注册数据
console.log(window.registerTurnstileData);
// 应该输出: {sitekey: "0x4AAA..."}
// 检查回调
console.log(window.turnstileCallback);
// 应该是一个函数
```
### 步骤3: 检查扩展日志
```javascript
// 监听消息
window.addEventListener('message', (event) => {
if (event.data?.type === 'registerTurnstile') {
console.log('扩展已注册Turnstile:', event.data);
}
if (event.data?.type === 'turnstileSolved') {
console.log('扩展已解决:', event.data);
}
});
```
### 步骤4: 网络检查
```bash
# 检查能否访问 Cloudflare
curl https://challenges.cloudflare.com/turnstile/v0/api.js
# 检查能否访问 CapSolver
curl https://api.capsolver.com/
# 如果被墙,需要代理
```
## 推荐修复方案
### 方案A: 添加详细日志 ⭐推荐⭐
在 windsurf.js 中添加调试输出:
```javascript
// 步骤2: 设置密码后
// 检查扩展注入状态
const extensionStatus = await this.page.evaluate(() => {
return {
hasTurnstileProxy: window.turnstile && window.turnstile.toString().includes('Proxy'),
hasRegisterData: !!window.registerTurnstileData,
hasCallback: !!window.turnstileCallback,
sitekey: window.registerTurnstileData?.sitekey
};
});
logger.info(this.siteName, '扩展状态:', JSON.stringify(extensionStatus, null, 2));
```
### 方案B: 强制等待 Turnstile API
```javascript
// 在步骤2中点击Continue之前
await this.page.waitForFunction(
() => typeof window.turnstile !== 'undefined',
{ timeout: 30000 }
);
```
### 方案C: 手动注入 inject-turnstile.js
如果扩展未正确加载,可以手动注入:
```javascript
const fs = require('fs');
const injectScript = fs.readFileSync(
path.join(projectRoot, 'extensions/capsolver/assets/inject/inject-turnstile.js'),
'utf8'
);
await this.page.evaluateOnNewDocument(injectScript);
```
### 方案D: 使用 Chrome DevTools Protocol
```javascript
const client = await this.page.target().createCDPSession();
await client.send('Page.addScriptToEvaluateOnNewDocument', {
source: injectScript
});
```
## 下一步行动
1. **立即执行**: 添加详细调试日志方案A
2. **验证扩展**: 运行测试并检查浏览器扩展状态
3. **收集信息**:
- 扩展是否加载?
- window.turnstile 是否是 Proxy
- 控制台有什么错误?
4. **根据结果**: 选择对应的修复方案

View File

@ -0,0 +1,199 @@
# CapSolver API 注入修复实施记录
## 修改完成时间
2025-11-17 14:47
## 实施的修改
### 文件: `src/tools/account-register/sites/windsurf.js`
### 方法: `solveWithCapSolver()` (第767-962行)
### 修改内容
#### 1. 改进 Token 注入第773-783行
**修改前**: 仅简单设置 input.value
```javascript
await this.page.evaluate((solution) => {
document.querySelector('input[name="cf-turnstile-response"]').value = solution;
}, token);
```
**修改后**: 注入 token 并触发事件
```javascript
await this.page.evaluate((token) => {
const input = document.querySelector('input[name="cf-turnstile-response"]');
if (input) {
input.value = token;
// 触发各种事件以通知页面
['input', 'change', 'blur'].forEach(eventType => {
input.dispatchEvent(new Event(eventType, { bubbles: true }));
});
}
}, token);
```
**改进**: 触发 DOM 事件,让监听器检测到 token 变化
#### 2. 添加回调函数触发第787-862行
**新增功能**: 多种方法查找并触发 Turnstile 回调
- **方法A**: 查找 React 组件回调 `window.cf__reactTurnstileOnLoad`
- **方法B**: 从 DOM 元素 `[data-callback]` 属性获取回调名
- **方法C**: 使用 `window.turnstile.reset()``window.turnstile.execute()` API
- **方法D**: 遍历 window 对象查找包含 'turnstile' 的函数
**日志输出**: 显示每个方法的触发结果(成功 ✓ 或失败 ✗)
#### 3. 添加按钮强制激活第876-906行
**新增功能**: 后备方案,确保按钮可点击
```javascript
const buttonActivated = await this.page.evaluate(() => {
const buttons = Array.from(document.querySelectorAll('button'));
buttons.forEach(btn => {
if (btn.textContent.trim() === 'Continue' ||
btn.textContent.trim() === 'Submit') {
if (btn.disabled) {
btn.disabled = false;
btn.removeAttribute('disabled');
btn.classList.remove('disabled');
}
}
});
});
```
**改进**: 即使回调失败,也能确保按钮可点击
#### 4. 改进验证完成检测第908-962行
**修改前**:
- 检查 iframe 状态(可能无法访问)
- 超时时间 20 秒
- 失败时等待 30 秒手动操作
**修改后**:
- **检查1**: 按钮必须激活
- **检查2**: hidden input 必须有值
- **检查3**: iframe 状态(可选)
- 超时时间 10 秒(缩短)
- 轮询间隔 500ms更频繁
- 失败时输出详细调试信息
- 如果按钮已激活且有 token认为成功
- 失败时等待 10 秒(缩短)
**调试信息输出**:
```javascript
{
buttonDisabled: false,
buttonText: "Continue",
hasToken: true,
tokenLength: 2000
}
```
## 关键改进点
### 1. 完整的验证流程模拟
- ✅ Token 注入
- ✅ 事件触发
- ✅ 回调调用
- ✅ 按钮激活
- ✅ 状态验证
### 2. 多重保障机制
- 4 种回调触发方法
- 强制按钮激活(后备)
- 灵活的状态检测
### 3. 详细的调试信息
- Token 长度显示
- 每个回调的触发结果
- 按钮激活状态
- 最终状态快照
### 4. 更合理的超时处理
- 缩短等待时间10秒而非30秒
- 提供详细的失败原因
- 智能判断是否继续
## 测试步骤
1. **运行注册流程**:
```bash
node src/cli.js register -s windsurf --keep-browser-open
```
2. **观察日志输出**:
- `[CapSolver] 注入 token 到页面 (长度: XXX)...`
- `[CapSolver] 回调触发结果:`
- 查看哪些方法成功(✓)
- 查看哪些方法失败(✗)
- `[CapSolver] 已强制激活按钮``按钮已经是激活状态`
- `[CapSolver] ✓ 验证完成确认通过`
3. **检查浏览器**:
- Turnstile checkbox 是否勾选?
- Continue 按钮是否可点击?
- 是否自动进入下一步?
4. **如果失败**:
- 查看 `[CapSolver] 当前状态:` 输出
- 判断是回调问题、按钮问题还是 iframe 问题
- 根据具体问题选择下一步方案
## 预期结果
### 成功场景
```
[Windsurf] [CapSolver] ✓ 获取到token: 0.kDqN3tMdKWreLsL90...
[Windsurf] [CapSolver] 注入 token 到页面 (长度: 2048)...
[Windsurf] [CapSolver] ✓ Token 已注入到 hidden input
[Windsurf] [CapSolver] 尝试触发 Turnstile 回调...
[Windsurf] [CapSolver] 回调触发结果:
[Windsurf] ✓ cf__reactTurnstileOnLoad
[Windsurf] ✓ window.turnstileCallback
[Windsurf] [CapSolver] 按钮已经是激活状态
[Windsurf] [CapSolver] ✓ 验证完成确认通过
[Windsurf] [CapSolver] ✓ 已点击 Continue 按钮
[Windsurf] 步骤 2 完成
```
### 部分成功场景
```
[Windsurf] [CapSolver] 回调触发结果:
[Windsurf] ✗ cf__reactTurnstileOnLoad: not a function
[Windsurf] ✓ data-callback: onTurnstileComplete
[Windsurf] [CapSolver] ✓ 已强制激活按钮
[Windsurf] [CapSolver] ✓ 验证完成确认通过
```
→ 至少一个回调成功即可
### 失败场景
```
[Windsurf] [CapSolver] ⚠️ 未找到任何回调函数
[Windsurf] [CapSolver] ✓ 已强制激活按钮
[Windsurf] [CapSolver] 验证完成检测超时
[Windsurf] [CapSolver] 当前状态: {"buttonDisabled":false,"buttonText":"Continue","hasToken":true,"tokenLength":2048}
[Windsurf] [CapSolver] ✓ 状态检查通过,继续执行
```
→ 按钮已激活且有 token继续执行
## 后续方案
如果当前修改仍然失败,下一步考虑:
### 方案3: 提前拦截 Turnstile 初始化
`initBrowser()` 中使用 `evaluateOnNewDocument()` 提前注入拦截代码
### 方案2: 使用 CDP 操作 iframe
通过 Chrome DevTools Protocol 直接访问 Turnstile iframe
### 方案4: 混合方案
不用完整扩展,只注入 `inject-turnstile.js` 脚本
## 备注
- 此修改不影响扩展加载方式第214-300行
- 废弃的 API 方法仍保留但已标记 `@deprecated`
- 步骤2密码设置中的扩展等待逻辑第507-542行保持不变

107
TASK_REMOVE_CAPSOLVER.md Normal file
View File

@ -0,0 +1,107 @@
# 任务:删除 CAPSOLVER 相关代码
## Analysis
### CAPSOLVER 代码位置分析
通过完整阅读 `windsurf.js` 文件,发现 CAPSOLVER 相关代码分布在以下位置:
#### 1. 构造函数中的初始化 (第34-40行)
- 初始化 `this.capsolverKey` 变量
- 输出 CapSolver 启用/未配置的日志
#### 2. 浏览器初始化中的提示 (第319-327行)
- 在 `initBrowser()` 方法中提示用户安装 CapSolver 扩展
#### 3. 步骤2中的自动验证处理 (第543-580行)
- 在 `step2_setPassword()` 中使用 CapSolver 扩展自动处理 Turnstile
- 包含等待验证完成的逻辑
#### 4. 废弃的 API 方法 (第597-1021行)
- `solveWithCapSolver()` 方法
- 已标记为 @deprecated
- 使用 CapSolver API 方式(不是扩展方式)
#### 5. Cloudflare 验证处理方法 (第1023-1176行)
- `handleCloudflareVerification()` 方法
- 前半部分使用 CapSolver 扩展自动处理
- 后半部分回退到手动模式(使用 CloudflareHandler
### 核心依赖关系
- **步骤2 (`step2_setPassword()`)**: 依赖 CapSolver 进行 Turnstile 验证
- **手动验证回退**: 已有 `CloudflareHandler` 作为手动验证方案
- **环境变量**: `CAPSOLVER_API_KEY``.env` 文件中配置
### 删除策略
1. **保留手动验证逻辑**: CloudflareHandler 必须保留
2. **删除 CapSolver 扩展相关代码**: 包括初始化、提示、自动处理逻辑
3. **删除废弃的 API 方法**: 完整移除 `solveWithCapSolver()` 方法
4. **简化验证流程**: 直接使用手动验证,不再尝试自动验证
## Proposed Solution
### 方案选择:渐进式删除
采用安全的渐进式删除策略,确保每一步修改后代码仍可正常运行:
1. **删除构造函数中的 CapSolver 初始化**
- 移除 `this.capsolverKey` 变量
- 移除相关日志输出
2. **删除浏览器初始化中的 CapSolver 提示**
- 移除扩展安装提示
3. **简化 step2_setPassword() 方法**
- 移除 CapSolver 扩展自动处理部分第543-580行
- 保留按钮点击和页面跳转逻辑
4. **删除 solveWithCapSolver() 方法**
- 完整移除整个方法第597-1021行
5. **简化 handleCloudflareVerification() 方法**
- 移除 CapSolver 扩展自动处理部分第1027-1074行
- 直接使用 CloudflareHandler 手动验证
### 优势
- 代码更简洁,移除未使用的自动化依赖
- 保留完整的手动验证流程
- 不影响现有功能逻辑
## Plan
### 步骤1: 删除构造函数中的 CapSolver 初始化
- 文件: `windsurf.js`
- 行数: 34-40
- 操作: 删除 `this.capsolverKey` 及相关日志
### 步骤2: 删除浏览器初始化中的提示
- 文件: `windsurf.js`
- 行数: 319-327
- 操作: 删除 CapSolver 扩展提示
### 步骤3: 简化 step2_setPassword() 方法
- 文件: `windsurf.js`
- 行数: 543-580
- 操作: 删除 CapSolver 自动验证逻辑
### 步骤4: 删除 solveWithCapSolver() 方法
- 文件: `windsurf.js`
- 行数: 597-1021
- 操作: 完整删除整个方法及其注释
### 步骤5: 简化 handleCloudflareVerification() 方法
- 文件: `windsurf.js`
- 行数: 1027-1074
- 操作: 删除 CapSolver 扩展处理部分,直接使用手动验证
## Progress
- [ ] 步骤1: 删除构造函数中的 CapSolver 初始化
- [ ] 步骤2: 删除浏览器初始化中的提示
- [ ] 步骤3: 简化 step2_setPassword()
- [ ] 步骤4: 删除 solveWithCapSolver() 方法
- [ ] 步骤5: 简化 handleCloudflareVerification()
- [ ] 验证: 检查代码逻辑完整性

View File

@ -26,6 +26,7 @@
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"imap": "^0.8.19", "imap": "^0.8.19",
"mailparser": "^3.6.5", "mailparser": "^3.6.5",
"mysql2": "^3.6.5",
"node-capsolver": "^1.2.0", "node-capsolver": "^1.2.0",
"puppeteer": "npm:rebrowser-puppeteer@^23.9.0", "puppeteer": "npm:rebrowser-puppeteer@^23.9.0",
"puppeteer-real-browser": "^1.4.4" "puppeteer-real-browser": "^1.4.4"

View File

@ -17,6 +17,7 @@ const logger = require('../../../shared/logger');
const EmailVerificationService = require('../email-verification'); const EmailVerificationService = require('../email-verification');
const { DEFAULT_CONFIG } = require('../config'); const { DEFAULT_CONFIG } = require('../config');
const CardGenerator = require('../../card-generator/generator'); const CardGenerator = require('../../card-generator/generator');
const database = require('../../database');
class WindsurfRegister { class WindsurfRegister {
constructor() { constructor() {
@ -30,6 +31,12 @@ class WindsurfRegister {
this.currentStep = 0; this.currentStep = 0;
this.accountData = null; this.accountData = null;
// 记录注册时间和额外信息
this.registrationTime = null;
this.quotaInfo = null;
this.billingInfo = null;
this.cardInfo = null;
// 定义所有步骤 // 定义所有步骤
this.steps = [ this.steps = [
{ id: 1, name: '填写基本信息', method: 'step1_fillBasicInfo' }, { id: 1, name: '填写基本信息', method: 'step1_fillBasicInfo' },
@ -39,10 +46,9 @@ class WindsurfRegister {
{ id: 5, name: '选择计划', method: 'step5_selectPlan' }, { id: 5, name: '选择计划', method: 'step5_selectPlan' },
{ id: 6, name: '填写支付信息', method: 'step6_fillPayment' }, { id: 6, name: '填写支付信息', method: 'step6_fillPayment' },
{ id: 7, name: '获取订阅信息', method: 'step7_getSubscriptionInfo' }, { id: 7, name: '获取订阅信息', method: 'step7_getSubscriptionInfo' },
{ id: 8, name: '保存到数据库', method: 'step8_saveToDatabase' },
{ id: 9, name: '清理并关闭浏览器', method: 'step9_clearAndCloseBrowser' },
]; ];
// 记录注册时间
this.registrationTime = null;
} }
/** /**
@ -268,45 +274,6 @@ class WindsurfRegister {
logger.success(this.siteName, '✓ AdsPower 浏览器连接成功'); logger.success(this.siteName, '✓ AdsPower 浏览器连接成功');
logger.info(this.siteName, '✓ 使用真实指纹,可同时绕过 Cloudflare 和 Stripe'); logger.info(this.siteName, '✓ 使用真实指纹,可同时绕过 Cloudflare 和 Stripe');
// 清除所有浏览器数据(类似 Ctrl+Shift+Delete
logger.info(this.siteName, ' → 清除所有浏览器数据Cookies、Cache、Storage等...');
try {
// 使用 Chrome DevTools Protocol 进行深度清理
const client = await this.page.target().createCDPSession();
// 1. 清除浏览器 Cookies
await client.send('Network.clearBrowserCookies');
logger.success(this.siteName, ' → ✓ 已清除所有 Cookies');
// 2. 清除浏览器缓存
await client.send('Network.clearBrowserCache');
logger.success(this.siteName, ' → ✓ 已清除浏览器缓存');
// 3. 清除所有存储数据localStorage, sessionStorage, IndexedDB, WebSQL, Cache Storage, Service Workers
await client.send('Storage.clearDataForOrigin', {
origin: '*',
storageTypes: 'all'
});
logger.success(this.siteName, ' → ✓ 已清除所有存储数据');
// 4. 额外清理:访问目标网站并清除其存储
await this.page.goto('https://windsurf.com', { waitUntil: 'domcontentloaded' });
await this.page.evaluate(() => {
try {
localStorage.clear();
sessionStorage.clear();
} catch (e) {}
});
// 5. 关闭 CDP 会话
await client.detach();
logger.success(this.siteName, ' → ✓ 浏览器数据清除完成(全新状态)');
} catch (e) {
logger.warn(this.siteName, ` → 清除浏览器数据失败: ${e.message}`);
}
logger.info(this.siteName, '等待浏览器完全准备...'); logger.info(this.siteName, '等待浏览器完全准备...');
await this.human.randomDelay(2000, 3000); await this.human.randomDelay(2000, 3000);
@ -969,6 +936,15 @@ class WindsurfRegister {
logger.info(this.siteName, ` → 有效期: ${card.month}/${card.year}`); logger.info(this.siteName, ` → 有效期: ${card.month}/${card.year}`);
logger.info(this.siteName, ` → CVV: ${card.cvv}`); logger.info(this.siteName, ` → CVV: ${card.cvv}`);
// 保存卡信息供后续使用
this.cardInfo = {
number: card.number,
month: card.month,
year: card.year,
cvv: card.cvv,
country: 'MO'
};
// 2. 点击选择"银行卡"支付方式 // 2. 点击选择"银行卡"支付方式
logger.info(this.siteName, ' → 选择银行卡支付方式...'); logger.info(this.siteName, ' → 选择银行卡支付方式...');
const cardRadio = await this.page.$('input[type="radio"][value="card"]'); const cardRadio = await this.page.$('input[type="radio"][value="card"]');
@ -1051,12 +1027,99 @@ class WindsurfRegister {
logger.info(this.siteName, ' → 点击订阅按钮...'); logger.info(this.siteName, ' → 点击订阅按钮...');
await this.human.randomDelay(2000, 3000); await this.human.randomDelay(2000, 3000);
// 9. 点击订阅按钮并检测卡片拒绝(支持重试)
const maxRetries = 5; // 最多重试5次
let retryCount = 0;
let paymentSuccess = false;
while (!paymentSuccess && retryCount < maxRetries) {
const submitButton = await this.page.$('button[type="submit"][data-testid="hosted-payment-submit-button"]'); const submitButton = await this.page.$('button[type="submit"][data-testid="hosted-payment-submit-button"]');
if (submitButton) { if (!submitButton) {
logger.warn(this.siteName, ' → 未找到订阅按钮');
break;
}
if (retryCount > 0) {
logger.info(this.siteName, ` → 第 ${retryCount + 1} 次尝试提交...`);
}
await submitButton.click(); await submitButton.click();
logger.success(this.siteName, ' → ✓ 已点击订阅按钮'); logger.success(this.siteName, ' → ✓ 已点击订阅按钮');
// 等待支付处理完成检测离开Stripe页面 // 等待一下让页面响应
await this.human.randomDelay(2000, 3000);
// 检测是否出现"银行卡被拒绝"错误
const cardRejected = await this.page.evaluate(() => {
const errorDiv = document.querySelector('.FieldError-container');
if (errorDiv) {
const errorText = errorDiv.textContent;
return errorText.includes('银行卡被拒绝') ||
errorText.includes('card was declined') ||
errorText.includes('被拒绝');
}
return false;
});
if (cardRejected) {
retryCount++;
logger.warn(this.siteName, ` → ⚠️ 银行卡被拒绝!(第 ${retryCount}/${maxRetries} 次)`);
if (retryCount >= maxRetries) {
logger.error(this.siteName, ` → ✗ 已达到最大重试次数 (${maxRetries})`);
throw new Error(`银行卡被拒绝,已重试 ${maxRetries}`);
}
// 生成新的银行卡
logger.info(this.siteName, ' → 生成新的银行卡信息...');
const cardGen = new CardGenerator();
const newCard = cardGen.generate('unionpay');
logger.info(this.siteName, ` → 新卡号: ${newCard.number}`);
logger.info(this.siteName, ` → 有效期: ${newCard.month}/${newCard.year}`);
logger.info(this.siteName, ` → CVV: ${newCard.cvv}`);
// 更新卡信息
this.cardInfo = {
number: newCard.number,
month: newCard.month,
year: newCard.year,
cvv: newCard.cvv,
country: 'MO'
};
// 清空并重新填写卡号
logger.info(this.siteName, ' → 清空并重新填写卡号...');
const cardNumberField = await this.page.$('#cardNumber');
await cardNumberField.click({ clickCount: 3 });
await this.page.keyboard.press('Backspace');
await this.human.randomDelay(500, 1000);
await cardNumberField.type(newCard.number, { delay: 250 });
// 清空并重新填写有效期
logger.info(this.siteName, ' → 清空并重新填写有效期...');
const cardExpiryField = await this.page.$('#cardExpiry');
await cardExpiryField.click({ clickCount: 3 });
await this.page.keyboard.press('Backspace');
await this.human.randomDelay(300, 500);
const expiry = `${newCard.month}${newCard.year}`;
await cardExpiryField.type(expiry, { delay: 250 });
// 清空并重新填写CVC
logger.info(this.siteName, ' → 清空并重新填写CVC...');
const cardCvcField = await this.page.$('#cardCvc');
await cardCvcField.click({ clickCount: 3 });
await this.page.keyboard.press('Backspace');
await this.human.randomDelay(300, 500);
await cardCvcField.type(newCard.cvv, { delay: 250 });
logger.success(this.siteName, ' → ✓ 已更新银行卡信息,准备重试...');
await this.human.randomDelay(2000, 3000);
// 继续下一次循环重试
continue;
}
// 没有错误,等待支付处理完成
logger.info(this.siteName, ' → 等待支付处理...'); logger.info(this.siteName, ' → 等待支付处理...');
logger.info(this.siteName, ' → 将持续等待直到支付完成(无时间限制)...'); logger.info(this.siteName, ' → 将持续等待直到支付完成(无时间限制)...');
@ -1069,6 +1132,7 @@ class WindsurfRegister {
// 检测是否已经离开 Stripe 页面(支付成功的标志) // 检测是否已经离开 Stripe 页面(支付成功的标志)
if (!currentUrl.includes('stripe.com') && !currentUrl.includes('checkout.stripe.com')) { if (!currentUrl.includes('stripe.com') && !currentUrl.includes('checkout.stripe.com')) {
paymentComplete = true; paymentComplete = true;
paymentSuccess = true;
const totalTime = ((Date.now() - paymentStartTime) / 1000).toFixed(1); const totalTime = ((Date.now() - paymentStartTime) / 1000).toFixed(1);
// 记录注册时间(支付成功的时间) // 记录注册时间(支付成功的时间)
@ -1087,6 +1151,7 @@ class WindsurfRegister {
await new Promise(resolve => setTimeout(resolve, 500)); await new Promise(resolve => setTimeout(resolve, 500));
} }
}
// 额外等待页面稳定 // 额外等待页面稳定
await this.human.randomDelay(2000, 3000); await this.human.randomDelay(2000, 3000);
@ -1116,7 +1181,12 @@ class WindsurfRegister {
try { try {
await this.page.waitForTimeout(2000); // 等待弹窗出现 await this.page.waitForTimeout(2000); // 等待弹窗出现
// 尝试查找并点击"取消"或"打开Windsurf"按钮 // 方法1: 尝试按ESC键关闭浏览器原生对话框
logger.info(this.siteName, ' → 尝试按ESC键关闭原生对话框...');
await this.page.keyboard.press('Escape');
await this.human.randomDelay(500, 1000);
// 方法2: 尝试查找并点击网页内的按钮如果是HTML弹窗
const closeDialog = await this.page.evaluate(() => { const closeDialog = await this.page.evaluate(() => {
// 查找包含"取消"或"打开Windsurf"的按钮 // 查找包含"取消"或"打开Windsurf"的按钮
const buttons = Array.from(document.querySelectorAll('button')); const buttons = Array.from(document.querySelectorAll('button'));
@ -1144,13 +1214,13 @@ class WindsurfRegister {
}); });
if (closeDialog) { if (closeDialog) {
logger.success(this.siteName, ` → ✓ 已关闭弹窗(点击了"${closeDialog}"`); logger.success(this.siteName, ` → ✓ 已关闭HTML弹窗(点击了"${closeDialog}"`);
await this.human.randomDelay(1000, 2000); await this.human.randomDelay(1000, 2000);
} else { } else {
logger.info(this.siteName, ' → 未检测到弹窗'); logger.success(this.siteName, ' → ✓ 已尝试关闭原生对话框ESC键');
} }
} catch (e) { } catch (e) {
logger.info(this.siteName, ' → 未检测到弹窗或已自动关闭'); logger.info(this.siteName, ` → 关闭弹窗时出错: ${e.message}`);
} }
// 1. 跳转到订阅使用页面 // 1. 跳转到订阅使用页面
@ -1240,6 +1310,10 @@ class WindsurfRegister {
logger.info(this.siteName, ''); logger.info(this.siteName, '');
// 保存订阅信息供后续使用
this.quotaInfo = quotaInfo;
this.billingInfo = billingInfo;
this.currentStep = 7; this.currentStep = 7;
logger.success(this.siteName, `步骤 7 完成`); logger.success(this.siteName, `步骤 7 完成`);
@ -1249,6 +1323,112 @@ class WindsurfRegister {
} }
} }
/**
* 步骤8: 保存到数据库
*/
async step8_saveToDatabase() {
logger.info(this.siteName, `[步骤 8/${this.getTotalSteps()}] 保存到数据库`);
try {
// 初始化数据库连接
logger.info(this.siteName, ' → 连接数据库...');
await database.initialize();
// 准备账号数据
const accountData = {
email: this.accountData.email,
password: this.accountData.password,
firstName: this.accountData.firstName,
lastName: this.accountData.lastName,
registrationTime: this.registrationTime,
quotaUsed: this.quotaInfo ? parseFloat(this.quotaInfo.used) : 0,
quotaTotal: this.quotaInfo ? parseFloat(this.quotaInfo.total) : 0,
billingDays: this.billingInfo ? parseInt(this.billingInfo.days) : null,
billingDate: this.billingInfo ? this.billingInfo.date : null,
paymentCardNumber: this.cardInfo ? this.cardInfo.number : null,
paymentCountry: this.cardInfo ? this.cardInfo.country : 'MO',
status: 'active'
};
// 保存到数据库
logger.info(this.siteName, ' → 保存账号信息...');
const accountRepo = database.getRepository('account');
const accountId = await accountRepo.create(accountData);
logger.success(this.siteName, ` → ✓ 账号信息已保存到数据库 (ID: ${accountId})`);
logger.info(this.siteName, ` → 邮箱: ${accountData.email}`);
logger.info(this.siteName, ` → 配额: ${accountData.quotaUsed} / ${accountData.quotaTotal}`);
logger.info(this.siteName, ` → 卡号: ${accountData.paymentCardNumber}`);
this.currentStep = 8;
logger.success(this.siteName, `步骤 8 完成`);
} catch (error) {
logger.error(this.siteName, `保存到数据库失败: ${error.message}`);
throw error;
}
}
/**
* 步骤9: 清理并关闭浏览器
*/
async step9_clearAndCloseBrowser() {
logger.info(this.siteName, `[步骤 9/${this.getTotalSteps()}] 清理并关闭浏览器`);
try {
// 清除所有浏览器数据(类似 Ctrl+Shift+Delete
logger.info(this.siteName, ' → 清除所有浏览器数据Cookies、Cache、Storage等...');
try {
// 使用 Chrome DevTools Protocol 进行深度清理
const client = await this.page.target().createCDPSession();
// 1. 清除浏览器 Cookies
await client.send('Network.clearBrowserCookies');
logger.success(this.siteName, ' → ✓ 已清除所有 Cookies');
// 2. 清除浏览器缓存
await client.send('Network.clearBrowserCache');
logger.success(this.siteName, ' → ✓ 已清除浏览器缓存');
// 3. 清除所有存储数据localStorage, sessionStorage, IndexedDB, WebSQL, Cache Storage, Service Workers
await client.send('Storage.clearDataForOrigin', {
origin: '*',
storageTypes: 'all'
});
logger.success(this.siteName, ' → ✓ 已清除所有存储数据');
// 4. 额外清理:访问目标网站并清除其存储
await this.page.goto('https://windsurf.com', { waitUntil: 'domcontentloaded' });
await this.page.evaluate(() => {
try {
localStorage.clear();
sessionStorage.clear();
} catch (e) {}
});
// 5. 关闭 CDP 会话
await client.detach();
logger.success(this.siteName, ' → ✓ 浏览器数据清除完成(全新状态)');
} catch (e) {
logger.warn(this.siteName, ` → 清除浏览器数据失败: ${e.message}`);
}
// 关闭浏览器
logger.info(this.siteName, ' → 关闭浏览器...');
await this.closeBrowser();
logger.success(this.siteName, ' → ✓ 浏览器已关闭');
this.currentStep = 9;
logger.success(this.siteName, `步骤 9 完成`);
} catch (error) {
logger.error(this.siteName, `清理并关闭浏览器失败: ${error.message}`);
throw error;
}
}
/** /**
* 执行注册流程 * 执行注册流程
* @param {Object} options - 选项 * @param {Object} options - 选项
@ -1291,20 +1471,7 @@ class WindsurfRegister {
} }
const stepInfo = this.getCurrentStepInfo(); const stepInfo = this.getCurrentStepInfo();
logger.success(this.siteName, `\n已完成步骤 ${fromStep}${this.currentStep}`); logger.success(this.siteName, `\n✅ 所有步骤已完成 (${fromStep}${this.currentStep})`);
if (this.currentStep < this.getTotalSteps()) {
logger.info(this.siteName, `剩余步骤需要手动完成或等待后续开发`);
}
// 不自动关闭浏览器,让用户查看结果
if (!options.keepBrowserOpen) {
logger.info(this.siteName, '10秒后关闭浏览器...');
await this.page.waitForTimeout(10000);
await this.closeBrowser();
} else {
logger.info(this.siteName, '浏览器保持打开状态');
}
return { return {
success: true, success: true,

View File

@ -0,0 +1,169 @@
# Database Tool - MySQL 数据库工具
## 📋 表设计
### windsurf_accounts 表
```sql
CREATE TABLE `windsurf_accounts` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
`email` VARCHAR(255) NOT NULL COMMENT '邮箱地址',
`password` VARCHAR(255) NOT NULL COMMENT '密码',
`first_name` VARCHAR(100) NOT NULL COMMENT '名',
`last_name` VARCHAR(100) NOT NULL COMMENT '姓',
`registration_time` DATETIME COMMENT '注册时间',
`quota_used` DECIMAL(10,2) DEFAULT 0.00 COMMENT '已使用配额',
`quota_total` DECIMAL(10,2) DEFAULT 0.00 COMMENT '总配额',
`billing_days` INT COMMENT '下次账单天数',
`billing_date` VARCHAR(50) COMMENT '账单日期',
`payment_card_number` VARCHAR(20) COMMENT '支付卡号(完整)',
`payment_country` VARCHAR(10) DEFAULT 'MO' COMMENT '支付国家代码',
`status` ENUM('active', 'expired', 'error') DEFAULT 'active' COMMENT '账号状态',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
UNIQUE KEY `uk_email` (`email`),
KEY `idx_status` (`status`),
KEY `idx_registration_time` (`registration_time`),
KEY `idx_created_at` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Windsurf 账号表';
```
### 字段说明
| 字段名 | 类型 | 说明 | 备注 |
|--------|------|------|------|
| id | BIGINT | 主键ID | 自增 |
| email | VARCHAR(255) | 邮箱地址 | 唯一索引 |
| password | VARCHAR(255) | 密码 | 明文存储(测试用) |
| first_name | VARCHAR(100) | 名 | - |
| last_name | VARCHAR(100) | 姓 | - |
| registration_time | DATETIME | 注册时间 | 支付成功时间 |
| quota_used | DECIMAL(10,2) | 已使用配额 | 默认0 |
| quota_total | DECIMAL(10,2) | 总配额 | 默认0 |
| billing_days | INT | 下次账单天数 | 可空 |
| billing_date | VARCHAR(50) | 账单日期 | 可空 |
| payment_card_number | VARCHAR(20) | 支付卡号 | **完整卡号** |
| payment_country | VARCHAR(10) | 支付国家 | 默认MO(澳门) |
| status | ENUM | 账号状态 | active/expired/error |
| created_at | TIMESTAMP | 记录创建时间 | 自动 |
| updated_at | TIMESTAMP | 记录更新时间 | 自动 |
## 🔧 使用方式
### 1. 初始化数据库
```bash
# 登录 MySQL
mysql -h 172.22.222.111 -P 3306 -u windsurf-auto-register -p
# 执行初始化脚本
source src/tools/database/init.sql
```
### 2. 配置环境变量
`.env` 文件中:
```bash
MYSQL_HOST=172.22.222.111
MYSQL_PORT=3306
MYSQL_USER=windsurf-auto-register
MYSQL_PASSWORD=Qichen5210523
MYSQL_DATABASE=auto_register
```
### 3. 在代码中使用
```javascript
const database = require('./src/tools/database');
// 初始化连接
await database.initialize();
// 获取 Repository
const accountRepo = database.getRepository('account');
// 创建账号
const accountId = await accountRepo.create({
email: 'test@example.com',
password: 'Password123!',
firstName: 'John',
lastName: 'Doe',
registrationTime: new Date(),
quotaTotal: 600.00,
paymentCardNumber: '6228367541234567',
paymentCountry: 'MO',
status: 'active'
});
// 查询账号
const account = await accountRepo.findByEmail('test@example.com');
// 更新账号
await accountRepo.update('test@example.com', {
quotaUsed: 10.5,
status: 'active'
});
// 关闭连接
await database.close();
```
## 🏗️ 设计模式
### 单例模式 (Singleton)
`DatabaseConnection` 使用单例模式,确保全局只有一个数据库连接池。
### 仓储模式 (Repository)
`AccountRepository` 封装所有数据库操作,业务逻辑无需关心 SQL 细节。
### 工厂模式 (Factory)
`database.getRepository()` 使用工厂方法获取不同的 Repository。
## 📁 目录结构
```
src/tools/database/
├── index.js # 工具入口
├── connection.js # 数据库连接(单例)
├── config.js # 配置文件
├── account-repository.js # 账号仓储
├── init.sql # 初始化脚本
└── README.md # 说明文档
```
## 🔄 扩展性
### 添加新表
1. 在 `init.sql` 添加建表语句
2. 创建对应的 Repository 文件(如 `user-repository.js`
3. 在 `index.js` 注册新的 Repository
示例:
```javascript
// src/tools/database/index.js
getRepository(name) {
const repositories = {
account: new AccountRepository(connection),
user: new UserRepository(connection) // 新增
};
return repositories[name];
}
```
## ⚠️ 注意事项
1. **安全性**: 当前密码为明文存储,仅用于测试环境
2. **连接池**: 默认最大连接数为10可在 `config.js` 调整
3. **重复插入**: 邮箱已存在时会自动更新记录
4. **事务**: 暂未实现,复杂操作需自行管理事务
## 📊 数据库配置信息
- **地址**: 172.22.222.111:3306
- **用户**: windsurf-auto-register
- **密码**: Qichen5210523
- **数据库**: auto_register

View File

@ -0,0 +1,189 @@
/**
* Account Repository - 账号数据访问层Repository 模式
*/
const logger = require('../../shared/logger');
class AccountRepository {
constructor(connection) {
this.db = connection;
}
/**
* 创建账号记录
*/
async create(accountData) {
const {
email,
password,
firstName,
lastName,
registrationTime,
quotaUsed,
quotaTotal,
billingDays,
billingDate,
paymentCardNumber,
paymentCountry,
status
} = accountData;
const sql = `
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
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`;
const params = [
email,
password,
firstName,
lastName,
registrationTime,
quotaUsed || 0,
quotaTotal || 0,
billingDays || null,
billingDate || null,
paymentCardNumber || null,
paymentCountry || 'MO',
status || 'active'
];
try {
const result = await this.db.query(sql, params);
logger.success('AccountRepository', `✓ Account created: ${email} (ID: ${result.insertId})`);
return result.insertId;
} catch (error) {
if (error.code === 'ER_DUP_ENTRY') {
logger.warn('AccountRepository', `Account already exists: ${email}`);
// 如果账号已存在,尝试更新
return await this.update(email, accountData);
}
throw error;
}
}
/**
* 更新账号记录
*/
async update(email, accountData) {
const updates = [];
const params = [];
if (accountData.password !== undefined) {
updates.push('password = ?');
params.push(accountData.password);
}
if (accountData.firstName !== undefined) {
updates.push('first_name = ?');
params.push(accountData.firstName);
}
if (accountData.lastName !== undefined) {
updates.push('last_name = ?');
params.push(accountData.lastName);
}
if (accountData.registrationTime !== undefined) {
updates.push('registration_time = ?');
params.push(accountData.registrationTime);
}
if (accountData.quotaUsed !== undefined) {
updates.push('quota_used = ?');
params.push(accountData.quotaUsed);
}
if (accountData.quotaTotal !== undefined) {
updates.push('quota_total = ?');
params.push(accountData.quotaTotal);
}
if (accountData.billingDays !== undefined) {
updates.push('billing_days = ?');
params.push(accountData.billingDays);
}
if (accountData.billingDate !== undefined) {
updates.push('billing_date = ?');
params.push(accountData.billingDate);
}
if (accountData.paymentCardNumber !== undefined) {
updates.push('payment_card_number = ?');
params.push(accountData.paymentCardNumber);
}
if (accountData.paymentCountry !== undefined) {
updates.push('payment_country = ?');
params.push(accountData.paymentCountry);
}
if (accountData.status !== undefined) {
updates.push('status = ?');
params.push(accountData.status);
}
if (updates.length === 0) {
logger.warn('AccountRepository', 'No fields to update');
return null;
}
params.push(email);
const sql = `UPDATE windsurf_accounts SET ${updates.join(', ')} WHERE email = ?`;
const result = await this.db.query(sql, params);
logger.success('AccountRepository', `✓ Account updated: ${email}`);
return result.affectedRows;
}
/**
* 查询账号
*/
async findByEmail(email) {
const sql = 'SELECT * FROM windsurf_accounts WHERE email = ?';
const rows = await this.db.query(sql, [email]);
return rows.length > 0 ? rows[0] : null;
}
/**
* 查询所有账号
*/
async findAll(options = {}) {
const { status, limit = 100, offset = 0 } = options;
let sql = 'SELECT * FROM windsurf_accounts';
const params = [];
if (status) {
sql += ' WHERE status = ?';
params.push(status);
}
sql += ' ORDER BY created_at DESC LIMIT ? OFFSET ?';
params.push(limit, offset);
return await this.db.query(sql, params);
}
/**
* 删除账号
*/
async delete(email) {
const sql = 'DELETE FROM windsurf_accounts WHERE email = ?';
const result = await this.db.query(sql, [email]);
logger.info('AccountRepository', `Account deleted: ${email}`);
return result.affectedRows;
}
/**
* 统计账号数量
*/
async count(status = null) {
let sql = 'SELECT COUNT(*) as total FROM windsurf_accounts';
const params = [];
if (status) {
sql += ' WHERE status = ?';
params.push(status);
}
const rows = await this.db.query(sql, params);
return rows[0].total;
}
}
module.exports = AccountRepository;

View File

@ -0,0 +1,24 @@
/**
* Database Configuration - 数据库配置
*/
module.exports = {
// 默认连接配置
connection: {
host: process.env.MYSQL_HOST || '172.22.222.111',
port: parseInt(process.env.MYSQL_PORT || '3306'),
user: process.env.MYSQL_USER || 'windsurf-auto-register',
password: process.env.MYSQL_PASSWORD || 'Qichen5210523',
database: process.env.MYSQL_DATABASE || 'auto_register',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0,
enableKeepAlive: true,
keepAliveInitialDelay: 0
},
// 表名配置
tables: {
accounts: 'windsurf_accounts'
}
};

View File

@ -0,0 +1,85 @@
/**
* Database Connection - 数据库连接管理单例模式
*/
const mysql = require('mysql2/promise');
const logger = require('../../shared/logger');
const { connection: defaultConfig } = require('./config');
class DatabaseConnection {
constructor() {
if (DatabaseConnection.instance) {
return DatabaseConnection.instance;
}
this.pool = null;
DatabaseConnection.instance = this;
}
/**
* 初始化连接池
*/
async initialize(config) {
if (this.pool) {
logger.warn('Database', 'Connection pool already initialized');
return this.pool;
}
const dbConfig = config || defaultConfig;
try {
this.pool = mysql.createPool(dbConfig);
// 测试连接
const connection = await this.pool.getConnection();
await connection.ping();
connection.release();
logger.success('Database', `✓ Connected to MySQL: ${dbConfig.host}:${dbConfig.port}/${dbConfig.database}`);
return this.pool;
} catch (error) {
logger.error('Database', `Failed to connect: ${error.message}`);
throw error;
}
}
/**
* 获取连接池
*/
getPool() {
if (!this.pool) {
throw new Error('Database pool not initialized. Call initialize() first.');
}
return this.pool;
}
/**
* 执行查询
*/
async query(sql, params) {
const pool = this.getPool();
try {
const [rows] = await pool.execute(sql, params);
return rows;
} catch (error) {
logger.error('Database', `Query failed: ${error.message}`);
logger.error('Database', `SQL: ${sql}`);
throw error;
}
}
/**
* 关闭连接池
*/
async close() {
if (this.pool) {
await this.pool.end();
this.pool = null;
logger.info('Database', 'Connection pool closed');
}
}
}
// 导出单例实例
module.exports = new DatabaseConnection();

View File

@ -0,0 +1,37 @@
/**
* Database Tool - 数据库工具入口
*/
const connection = require('./connection');
const AccountRepository = require('./account-repository');
module.exports = {
name: 'database',
alias: 'db',
description: 'MySQL数据库工具',
/**
* 初始化数据库连接
*/
async initialize(config) {
return await connection.initialize(config);
},
/**
* 获取 Repository
*/
getRepository(name) {
const repositories = {
account: new AccountRepository(connection)
};
return repositories[name];
},
/**
* 关闭数据库连接
*/
async close() {
return await connection.close();
}
};

View File

@ -0,0 +1,37 @@
-- ============================================
-- Windsurf Auto Register 数据库初始化脚本
-- ============================================
-- 创建数据库(如果不存在)
CREATE DATABASE IF NOT EXISTS `auto_register`
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
USE `auto_register`;
-- 创建 Windsurf 账号表
CREATE TABLE IF NOT EXISTS `windsurf_accounts` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
`email` VARCHAR(255) NOT NULL COMMENT '邮箱地址',
`password` VARCHAR(255) NOT NULL COMMENT '密码',
`first_name` VARCHAR(100) NOT NULL COMMENT '',
`last_name` VARCHAR(100) NOT NULL COMMENT '',
`registration_time` DATETIME COMMENT '注册时间',
`quota_used` DECIMAL(10,2) DEFAULT 0.00 COMMENT '已使用配额',
`quota_total` DECIMAL(10,2) DEFAULT 0.00 COMMENT '总配额',
`billing_days` INT COMMENT '下次账单天数',
`billing_date` VARCHAR(50) COMMENT '账单日期',
`payment_card_number` VARCHAR(20) COMMENT '支付卡号',
`payment_country` VARCHAR(10) DEFAULT 'MO' COMMENT '支付国家代码',
`status` ENUM('active', 'expired', 'error') DEFAULT 'active' COMMENT '账号状态',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
UNIQUE KEY `uk_email` (`email`),
KEY `idx_status` (`status`),
KEY `idx_registration_time` (`registration_time`),
KEY `idx_created_at` (`created_at`)
) 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);