From 4fcd5d1df5f9cc648e88ac120c71e9b3cb1eab50 Mon Sep 17 00:00:00 2001 From: dengqichen Date: Mon, 17 Nov 2025 17:26:10 +0800 Subject: [PATCH] dasdasd --- .env | 7 + .windsurf/tasks/capsolver-debug.md | 241 ++++++++++++++ .../tasks/capsolver-fix-implementation.md | 199 ++++++++++++ TASK_REMOVE_CAPSOLVER.md | 107 +++++++ package.json | 1 + src/tools/account-register/sites/windsurf.js | 293 ++++++++++++++---- src/tools/database/README.md | 169 ++++++++++ src/tools/database/account-repository.js | 189 +++++++++++ src/tools/database/config.js | 24 ++ src/tools/database/connection.js | 85 +++++ src/tools/database/index.js | 37 +++ src/tools/database/init.sql | 37 +++ 12 files changed, 1326 insertions(+), 63 deletions(-) create mode 100644 .windsurf/tasks/capsolver-debug.md create mode 100644 .windsurf/tasks/capsolver-fix-implementation.md create mode 100644 TASK_REMOVE_CAPSOLVER.md create mode 100644 src/tools/database/README.md create mode 100644 src/tools/database/account-repository.js create mode 100644 src/tools/database/config.js create mode 100644 src/tools/database/connection.js create mode 100644 src/tools/database/index.js create mode 100644 src/tools/database/init.sql diff --git a/.env b/.env index 344dd71..efca345 100644 --- a/.env +++ b/.env @@ -2,3 +2,10 @@ ADSPOWER_USER_ID=k1728p8l ADSPOWER_API_KEY=35de43696f6241f3df895f2f48777a99 # 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 diff --git a/.windsurf/tasks/capsolver-debug.md b/.windsurf/tasks/capsolver-debug.md new file mode 100644 index 0000000..d5d56f4 --- /dev/null +++ b/.windsurf/tasks/capsolver-debug.md @@ -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. **根据结果**: 选择对应的修复方案 diff --git a/.windsurf/tasks/capsolver-fix-implementation.md b/.windsurf/tasks/capsolver-fix-implementation.md new file mode 100644 index 0000000..91e8a2b --- /dev/null +++ b/.windsurf/tasks/capsolver-fix-implementation.md @@ -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行)保持不变 diff --git a/TASK_REMOVE_CAPSOLVER.md b/TASK_REMOVE_CAPSOLVER.md new file mode 100644 index 0000000..3f5d766 --- /dev/null +++ b/TASK_REMOVE_CAPSOLVER.md @@ -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() +- [ ] 验证: 检查代码逻辑完整性 diff --git a/package.json b/package.json index 5ff92d6..406e185 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "dotenv": "^17.2.3", "imap": "^0.8.19", "mailparser": "^3.6.5", + "mysql2": "^3.6.5", "node-capsolver": "^1.2.0", "puppeteer": "npm:rebrowser-puppeteer@^23.9.0", "puppeteer-real-browser": "^1.4.4" diff --git a/src/tools/account-register/sites/windsurf.js b/src/tools/account-register/sites/windsurf.js index 3588618..e82225f 100644 --- a/src/tools/account-register/sites/windsurf.js +++ b/src/tools/account-register/sites/windsurf.js @@ -17,6 +17,7 @@ const logger = require('../../../shared/logger'); const EmailVerificationService = require('../email-verification'); const { DEFAULT_CONFIG } = require('../config'); const CardGenerator = require('../../card-generator/generator'); +const database = require('../../database'); class WindsurfRegister { constructor() { @@ -30,6 +31,12 @@ class WindsurfRegister { this.currentStep = 0; this.accountData = null; + // 记录注册时间和额外信息 + this.registrationTime = null; + this.quotaInfo = null; + this.billingInfo = null; + this.cardInfo = null; + // 定义所有步骤 this.steps = [ { id: 1, name: '填写基本信息', method: 'step1_fillBasicInfo' }, @@ -39,10 +46,9 @@ class WindsurfRegister { { id: 5, name: '选择计划', method: 'step5_selectPlan' }, { id: 6, name: '填写支付信息', method: 'step6_fillPayment' }, { 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.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, '等待浏览器完全准备...'); await this.human.randomDelay(2000, 3000); @@ -969,6 +936,15 @@ class WindsurfRegister { logger.info(this.siteName, ` → 有效期: ${card.month}/${card.year}`); logger.info(this.siteName, ` → CVV: ${card.cvv}`); + // 保存卡信息供后续使用 + this.cardInfo = { + number: card.number, + month: card.month, + year: card.year, + cvv: card.cvv, + country: 'MO' + }; + // 2. 点击选择"银行卡"支付方式 logger.info(this.siteName, ' → 选择银行卡支付方式...'); const cardRadio = await this.page.$('input[type="radio"][value="card"]'); @@ -1051,12 +1027,99 @@ class WindsurfRegister { logger.info(this.siteName, ' → 点击订阅按钮...'); await this.human.randomDelay(2000, 3000); - const submitButton = await this.page.$('button[type="submit"][data-testid="hosted-payment-submit-button"]'); - if (submitButton) { + // 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"]'); + if (!submitButton) { + logger.warn(this.siteName, ' → 未找到订阅按钮'); + break; + } + + if (retryCount > 0) { + logger.info(this.siteName, ` → 第 ${retryCount + 1} 次尝试提交...`); + } + await submitButton.click(); 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, ' → 将持续等待直到支付完成(无时间限制)...'); @@ -1069,6 +1132,7 @@ class WindsurfRegister { // 检测是否已经离开 Stripe 页面(支付成功的标志) if (!currentUrl.includes('stripe.com') && !currentUrl.includes('checkout.stripe.com')) { paymentComplete = true; + paymentSuccess = true; const totalTime = ((Date.now() - paymentStartTime) / 1000).toFixed(1); // 记录注册时间(支付成功的时间) @@ -1087,6 +1151,7 @@ class WindsurfRegister { await new Promise(resolve => setTimeout(resolve, 500)); } + } // 额外等待页面稳定 await this.human.randomDelay(2000, 3000); @@ -1116,7 +1181,12 @@ class WindsurfRegister { try { 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(() => { // 查找包含"取消"或"打开Windsurf"的按钮 const buttons = Array.from(document.querySelectorAll('button')); @@ -1144,13 +1214,13 @@ class WindsurfRegister { }); if (closeDialog) { - logger.success(this.siteName, ` → ✓ 已关闭弹窗(点击了"${closeDialog}")`); + logger.success(this.siteName, ` → ✓ 已关闭HTML弹窗(点击了"${closeDialog}")`); await this.human.randomDelay(1000, 2000); } else { - logger.info(this.siteName, ' → 未检测到弹窗'); + logger.success(this.siteName, ' → ✓ 已尝试关闭原生对话框(ESC键)'); } } catch (e) { - logger.info(this.siteName, ' → 未检测到弹窗或已自动关闭'); + logger.info(this.siteName, ` → 关闭弹窗时出错: ${e.message}`); } // 1. 跳转到订阅使用页面 @@ -1240,6 +1310,10 @@ class WindsurfRegister { logger.info(this.siteName, ''); + // 保存订阅信息供后续使用 + this.quotaInfo = quotaInfo; + this.billingInfo = billingInfo; + this.currentStep = 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 - 选项 @@ -1291,20 +1471,7 @@ class WindsurfRegister { } const stepInfo = this.getCurrentStepInfo(); - 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, '浏览器保持打开状态'); - } + logger.success(this.siteName, `\n✅ 所有步骤已完成 (${fromStep} 到 ${this.currentStep})`); return { success: true, diff --git a/src/tools/database/README.md b/src/tools/database/README.md new file mode 100644 index 0000000..f96d113 --- /dev/null +++ b/src/tools/database/README.md @@ -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 diff --git a/src/tools/database/account-repository.js b/src/tools/database/account-repository.js new file mode 100644 index 0000000..bae9549 --- /dev/null +++ b/src/tools/database/account-repository.js @@ -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; diff --git a/src/tools/database/config.js b/src/tools/database/config.js new file mode 100644 index 0000000..78bc133 --- /dev/null +++ b/src/tools/database/config.js @@ -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' + } +}; diff --git a/src/tools/database/connection.js b/src/tools/database/connection.js new file mode 100644 index 0000000..3e56337 --- /dev/null +++ b/src/tools/database/connection.js @@ -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(); diff --git a/src/tools/database/index.js b/src/tools/database/index.js new file mode 100644 index 0000000..9badfb5 --- /dev/null +++ b/src/tools/database/index.js @@ -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(); + } +}; diff --git a/src/tools/database/init.sql b/src/tools/database/init.sql new file mode 100644 index 0000000..a7bae9f --- /dev/null +++ b/src/tools/database/init.sql @@ -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);