一个部署在 Vercel 上的 Gemini Pro 订阅自动激活平台。用户输入卡密和 Google 账号信息,系统自动完成 Gemini Pro 订阅的激活。
整体是一个前后端分离 + Serverless的架构:
┌─────────────────────────────────────────────────────────────┐
│ Vercel │
│ │
│ ┌──────────────┐ ┌──────────────────────────────────┐ │
│ │ 静态前端 │ │ Serverless Functions (api/) │ │
│ │ React SPA │────>│ verify_card.js │ │
│ │ client/dist │ │ submit_order.js │ │
│ │ │ │ query_orders.js │ │
│ └──────────────┘ └───────────────┬──────────────────┘ │
│ │ │
│ ┌──────────────┐ │ │
│ │ Rewrites │ │ │
│ │ (边缘代理) │ │ │
│ │ /proxy/ │ │ │
│ └──────┬───────┘ │ │
└─────────┼─────────────────────────────┼─────────────────────┘
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ gmailcheck 服务 │ │ aicloudv3.top │
│ 邮箱状态检测 │ │ 充值上游 API │
└──────────────────┘ └────────┬─────────┘
│
▼
┌──────────────────┐
│ Google 账号系统 │
│ 登录 + 激活订阅 │
└──────────────────┘
为什么需要 Serverless Functions?
前端(浏览器)不能直接调 aicloudv3.top,因为它不返回 CORS 头,浏览器会拦截跨域请求。所以需要一层服务端代理。Vercel Serverless Functions 就是这个代理层——浏览器请求同域的 /api/xxx,function 在服务端帮你转发给上游。
| 层级 | 技术 | 用途 |
|---|---|---|
| 前端框架 | React 18 | UI 组件化 |
| 路由 | React Router v7 | SPA 路由 |
| HTTP 客户端 | Axios | 前端和 serverless 都用它发请求 |
| 样式 | Tailwind CSS 3 | 原子化 CSS,不写自定义样式文件 |
| 构建工具 | Vite 5 | 开发热更新 + 生产构建 |
| 部署平台 | Vercel | 静态托管 + Serverless Functions + 边缘 Rewrites |
| 后端 | Vercel Serverless Functions (Node.js) | 代理上游 API,处理 session |
gemini-pro/
├── api/ # Vercel Serverless Functions
│ ├── verify_card.js # 验证卡密(代理上游)
│ ├── submit_order.js # 提交订单(核心:内含 session 管理)
│ └── query_orders.js # 查询订单(代理上游)
│
├── client/ # 前端 React 应用
│ ├── index.html # HTML 入口
│ ├── package.json # 前端依赖
│ ├── vite.config.js # Vite 配置(含 dev proxy)
│ ├── tailwind.config.js # Tailwind 品牌色配置
│ ├── postcss.config.js # PostCSS 插件
│ └── src/
│ ├── main.jsx # React 入口
│ ├── index.css # 全局样式(Tailwind 指令 + 字体)
│ ├── App.jsx # 根组件,路由配置
│ ├── api/
│ │ └── order.js # 所有 API 调用封装
│ ├── pages/
│ │ └── RedeemPage.jsx # 主页面,控制整个充值流程
│ └── components/
│ ├── Header.jsx # 顶栏 logo
│ ├── Footer.jsx # 底栏
│ ├── OrderForm.jsx # 一体化表单(卡密+账号+2FA)
│ ├── GmailCheckPanel.jsx # Gmail 邮箱状态检测
│ ├── OrderQueryPanel.jsx # 按卡密查询历史订单
│ ├── OrderTracker.jsx # 订单轮询追踪(5秒一次)
│ ├── OrderResult.jsx # 最终结果展示(成功/失败/取消)
│ ├── ErrorAlert.jsx # 通用错误提示条
│ ├── LoadingSpinner.jsx # 加载动画
│ ├── CdkeyForm.jsx # 独立的卡密验证表单(备用)
│ ├── GoogleAccountForm.jsx # 独立的账号信息表单(备用)
│ └── StepIndicator.jsx # 步骤指示器(备用)
│
├── vercel.json # Vercel 部署配置
├── package.json # 根依赖(serverless 用的 axios)
└── B2B_代理对接文档模板.md # 上游 B2B API 文档(参考)
备用组件说明:
CdkeyForm、GoogleAccountForm、StepIndicator是早期分步流程的设计,后来合并成了OrderForm一个表单。保留在代码中但当前未使用。
1. 打开网站
2. 输入卡密兑换码(如 AP-F9879268E9)
3. 输入 Google 邮箱、密码
4. 选择验证方式:2FA 密钥 或 备用验证码
5. 点击提交
6. 等待系统自动激活(5秒轮询状态)
7. 看到结果:成功 / 失败 / 已取消
用户填写表单
│
▼
RedeemPage.handleSubmitOrder(payload)
│
▼
api/order.js → createOrder(payload)
│
│ POST /api/submit_order
│ Body: { card_key, google_email, google_password, auth_type, auth_secret }
│
▼
Vercel Serverless: api/submit_order.js
│
│ 1) POST aicloudv3.top/api/verify_card → 拿到 Set-Cookie: session=xxx
│ 2) POST aicloudv3.top/api/submit_order → 带上 Cookie: session=xxx
│
▼
返回 { code: 1, msg: "订单提交成功", remain_count: 0 }
│
▼
前端进入 OrderTracker 轮询
│
│ 每 5 秒 POST /api/query_orders { card_key }
│ 查找 google_email 匹配的订单
│
▼
订单状态变为终态(SUCCESS / FAILED / DELETED)
│
▼
OrderResult 展示最终结果
form ──提交──> submitting ──成功──> tracking ──终态──> result
▲ │ │
│ 失败 │
│ │ │
└────────────────┘────────────── 返回首页 ─────────────┘
RedeemPage 是唯一的页面,由三个区域组成:
┌──────────────────────────────────────────┐
│ ┌─────────────────┐ ┌────────────────┐ │
│ │ GmailCheckPanel │ │OrderQueryPanel │ │ ← 顶部双栏
│ │ 邮箱状态检测 │ │ 订单查询 │ │
│ └─────────────────┘ └────────────────┘ │
│ │
│ ┌──────────────────────────────────────┐│
│ │ OrderForm / OrderTracker / Result ││ ← 主区域(根据状态切换)
│ └──────────────────────────────────────┘│
└──────────────────────────────────────────┘
OrderForm — 一体化的充值表单
包含所有输入项:卡密、邮箱、密码、验证方式选择、验证码。提交时组装 payload 传给 RedeemPage。
OrderTracker — 订单状态轮询
提交成功后自动进入此组件。每 5 秒调一次 queryOrdersByCdkey,在返回的订单列表中按 google_email 匹配当前订单,展示实时状态。最多轮询 120 次(10 分钟)。当状态变为 SUCCESS/FAILED/DELETED 时停止轮询,回调 onResult 切到结果页。
GmailCheckPanel — 邮箱预检
调 /proxy/gmailcheck/shaicha3.php 检测 Google 邮箱状态(正常/异常/不存在)。这个走的是 Vercel rewrites 代理到 koko.gmailcheck.com,不走 serverless。
OrderQueryPanel — 历史订单查询
输入卡密查看该卡密关联的所有订单和状态。
四个函数,全部请求 /api/xxx(同域路径):
| 函数 | 请求 | 用途 |
|---|---|---|
validateCdkey(cdkey) |
POST /api/verify_card |
验证卡密有效性 |
createOrder(payload) |
POST /api/submit_order |
提交充值订单 |
queryOrdersByCdkey(cardKey) |
POST /api/query_orders |
按卡密查询订单 |
checkGmail(email) |
POST /proxy/gmailcheck/shaicha3.php |
检测 Gmail 状态 |
- Tailwind CSS — 所有样式都写在 JSX 的
className里,没有单独的组件 CSS - 品牌色 —
brand-500: #4285f4(Google 蓝),在tailwind.config.js中自定义 - 字体 — 优先 Apple 系统字体,降级到微软雅黑、Helvetica
- 背景 —
#f8fafc(浅灰蓝)
api/ 目录下的三个文件就是全部后端代码。每个文件是一个独立的 Vercel Serverless Function。
这是最关键的文件。它不是简单代理,而是在一个函数内串行调用两个上游接口:
// 1) 先调 verify_card,拿到 session cookie
const verifyRes = await axios.post(UPSTREAM + '/api/verify_card', { card_key });
const cookie = verifyRes.headers['set-cookie']; // session=eyJ...
// 2) 带着 cookie 调 submit_order
const submitRes = await axios.post(UPSTREAM + '/api/submit_order', body, {
headers: { Cookie: cookie }
});为什么要这样? 见下面的上游接口对接章节。
前端也需要单独调 verify 来展示卡密余额信息,所以保留了一个纯代理。
查询订单不需要 session,直接转发。
三个 function 共享相同的结构:
- 设置 CORS 头(
Access-Control-Allow-Origin: *) - 处理 OPTIONS 预检
- 检查 HTTP 方法
try/catch包裹,统一的错误处理
这是整个项目最值得学习的部分——怎么和上游 aicloudv3.top 对上的。
| 接口 | 方法 | 功能 |
|---|---|---|
/api/verify_card |
POST | 验证卡密,返回余额和是否可提交 |
/api/submit_order |
POST | 提交充值订单 |
/api/query_orders |
POST | 按卡密查询所有订单 |
verify_card
// 请求
{ "card_key": "AP-F9879268E9" }
// 成功响应
{ "code": 1, "msg": "验证通过", "can_submit": true, "remain_count": 1, "view_only": false }
// 失败响应
{ "code": -1, "msg": "无效的激活码" }submit_order
// 请求
{
"card_key": "AP-F9879268E9",
"google_email": "user@gmail.com",
"google_password": "password",
"auth_type": "2FA", // 或 "BACKUP_CODE"
"auth_secret": "ABCDEFGH...", // 32位密钥 或 备用验证码
"auth_secret_secondary": "" // 可选第二个备用验证码
}
// 成功响应
{ "code": 1, "msg": "订单提交成功", "remain_count": 0 }
// 未验证卡密时
{ "code": -1, "msg": "请先验证激活码" }query_orders
// 请求
{ "card_key": "AP-F9879268E9" }
// 响应
{
"code": 1,
"orders": [
{
"google_email": "user@gmail.com",
"status": "SUCCESS", // QUEUED / PROCESSING / SUCCESS / FAILED / DELETED
"result_message": "订阅成功",
"partner_order_id": "...",
"created_at": "2026-04-17 15:00:00"
}
]
}上游的 verify 和 submit 通过 HTTP session cookie 关联:
verify_card 请求 ──────────> aicloudv3.top
<──────── 响应头: Set-Cookie: session=eyJ2ZXJpZm...
submit_order 请求 ─────────> aicloudv3.top
(带上 Cookie: session=eyJ...)
<──────── { "code": 1, "msg": "订单提交成功" }
Session cookie 的内容(base64 解码后):
{ "verified_activation_code": "AP-F9879268E9" }上游在 submit_order 时检查 cookie 里有没有已验证的卡密,没有就返回"请先验证激活码"。
这就是为什么不能用 Vercel Rewrites 代理——Rewrites 是边缘层的纯 URL 转发,不会把上游的 Set-Cookie 透传回浏览器(域名不匹配)。所以必须用 Serverless Function 在服务端一次性完成 verify + submit。
| auth_type | auth_secret 内容 | 说明 |
|---|---|---|
2FA |
32 位 Base32 TOTP 密钥 | 上游拿密钥自己生成 6 位验证码登录 Google |
BACKUP_CODE |
8 位数字备用验证码 | 直接用这个码登录 Google |
上游
aicloudv3.top收到账号信息后,会自动登录用户的 Google 账号,通过 2FA 验证,然后激活 Gemini Pro 订阅。整个过程对用户透明。
项目里还有一份 B2B_代理对接文档模板.md,记录了另一套上游 API(ethgoogle.top),使用 Bearer Token 鉴权,接口格式不同。当前代码未使用这套接口,但它是 aicloudv3.top 的底层服务提供方(B2B 模式)。
aicloudv3.top(面向代理的前端接口,session 鉴权)
│
▼
ethgoogle.top(底层 B2B API,Bearer Token 鉴权)
│
▼
Google 账号系统
浏览器请求 Vercel 处理
──────────────────────────────────────────────────
GET / → client/dist/index.html(静态文件)
GET /assets/xxx.js → client/dist/assets/xxx.js(静态文件)
POST /api/submit_order → api/submit_order.js(Serverless Function)
POST /api/verify_card → api/verify_card.js(Serverless Function)
POST /api/query_orders → api/query_orders.js(Serverless Function)
POST /proxy/gmailcheck/* → Rewrite 到 koko.gmailcheck.com/*(边缘代理)
GET /anything-else → index.html(SPA fallback)
当前项目没有使用 Vercel 环境变量,上游地址硬编码在 serverless function 里。如果要切换上游,改 api/*.js 里的 UPSTREAM 常量即可。
# 安装依赖
npm install # 根目录(serverless 用的 axios)
cd client && npm install # 前端依赖
# 启动开发服务器
npm run dev # 等于 cd client && npm run dev
# 访问 http://localhost:5173本地开发时,/api/* 请求通过 Vite 的 proxy 配置转发到 aicloudv3.top:
// client/vite.config.js
proxy: {
'/api': {
target: 'https://aicloudv3.top',
changeOrigin: true,
},
}
⚠️ 注意:本地 dev proxy 是简单转发,没有 session cookie 管理,所以本地 submit_order 可能会报"请先验证激活码"。完整流程需要部署到 Vercel 测试。
问题:前端直接请求 aicloudv3.top,浏览器发 OPTIONS 预检,上游不返回 Access-Control-Allow-Origin 头,请求被拦。
解决:所有请求走 Vercel 代理(Serverless Functions 或 Rewrites),浏览器只请求同域的 /api/xxx。
问题:Serverless Function 代理 verify_card 和 submit_order 是两个独立请求,上游返回的 session cookie 没有传递。
排查过程:
- 最初以为是 IP 关联 → 同 IP curl 测试仍然失败
- 用
curl -sv查看完整响应头 → 发现Set-Cookie: session=... - 带 cookie 请求 submit_order → 成功
解决:在 api/submit_order.js 内串行调用 verify + submit,手动提取和传递 session cookie。
问题:尝试用 Vercel 边缘 Rewrites 代理(类似 nginx 反代),但上游 Set-Cookie 的 domain 和 path 对不上浏览器的域名,cookie 被丢弃。
解决:必须用 Serverless Functions 在服务端完成 cookie 管理。Rewrites 只适合不需要 session 的接口(如 gmailcheck)。
问题:上游处理订单很慢(可达 30-60 秒),默认的 axios timeout 和 Vercel function maxDuration 不够。
解决:
- 前端 axios timeout 设为 65 秒
- Vercel function maxDuration 设为 60 秒
- 上游 submit_order 的 axios timeout 设为 60 秒
{ // 构建命令:只构建前端 "buildCommand": "cd client && npm install && npm run build", // 构建产物目录 "outputDirectory": "client/dist", // Serverless Functions 配置 "functions": { "api/**/*.js": { "memory": 1024, // 1GB 内存 "maxDuration": 60 // 最长 60 秒(submit_order 上游很慢) } }, // URL 重写规则(按顺序匹配) "rewrites": [ // Gmail 检测走边缘代理(不需要 session) { "source": "/proxy/gmailcheck/:path*", "destination": "https://koko.gmailcheck.com/:path*" }, // /api 以外的所有路径走 SPA fallback { "source": "/((?!api/).*)", "destination": "/index.html" } ] }