TokenGX CAPTCHA - 客户端集成指南

快速接入自托管人机验证服务

开源 · 自托管 · 无追踪

快速开始

1
获取凭证

访问 cap.tokengx.cn 注册账号并创建站点,获取:

  • Site Key - 前端使用
  • Secret Key - 后端验证使用
2
前端集成

在 HTML 中引入 cap-widget 并使用

3
后端验证

在后端验证 cap-widget 返回的 token

前端集成

方式一:HTML Script 标签(推荐)

<!-- 引入 TokenGX CAPTCHA(品牌版) -->
<script src="https://cap.tokengx.cn/cap-widget.js"></script>

<!-- 或使用官方 CDN 版本 -->
<!-- <script src="https://cdn.jsdelivr.net/npm/cap-widget"></script> -->

<!-- 在表单中使用 -->
<form id="myForm">
  <input type="tel" name="phone" placeholder="手机号码">
  <input type="password" name="password" placeholder="密码">

  <!-- CAPTCHA 组件 -->
  <cap-widget data-cap-api-endpoint="https://cap.tokengx.cn/YOUR_SITE_KEY/"></cap-widget>

  <button type="submit">登录</button>
</form>

<script>
  const widget = document.querySelector('cap-widget');
  let captchaToken = null;

  // 监听验证成功事件
  widget.addEventListener('solve', (e) => {
    captchaToken = e.detail.token;
    console.log('验证通过');
  });

  // 监听验证失败事件
  widget.addEventListener('error', (e) => {
    captchaToken = null;
    console.error('验证失败:', e.detail);
  });

  // 表单提交时携带 token
  document.getElementById('myForm').addEventListener('submit', async (e) => {
    e.preventDefault();

    if (!captchaToken) {
      alert('请先完成人机验证');
      return;
    }

    const formData = new FormData(e.target);
    formData.append('captchaToken', captchaToken);

    // 发送到后端
    const response = await fetch('/api/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        phone: formData.get('phone'),
        password: formData.get('password'),
        captchaToken: captchaToken
      })
    });

    const result = await response.json();
    if (result.success) {
      window.location.href = '/dashboard';
    } else {
      alert(result.error || '登录失败');
    }
  });
</script>

方式二:npm 安装(React/Vue 等框架)

npm install cap-widget

React 示例

import { useEffect, useRef, useState } from 'react';
import 'cap-widget';

export default function LoginForm() {
  const widgetRef = useRef(null);
  const [captchaToken, setCaptchaToken] = useState(null);

  useEffect(() => {
    const widget = widgetRef.current;
    if (!widget) return;

    const handleSolve = (e) => {
      setCaptchaToken(e.detail.token);
    };

    const handleError = () => {
      setCaptchaToken(null);
    };

    widget.addEventListener('solve', handleSolve);
    widget.addEventListener('error', handleError);

    return () => {
      widget.removeEventListener('solve', handleSolve);
      widget.removeEventListener('error', handleError);
    };
  }, []);

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!captchaToken) {
      alert('请先完成人机验证');
      return;
    }
    // 提交逻辑...
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="tel" name="phone" placeholder="手机号码" />
      <input type="password" name="password" placeholder="密码" />
      <cap-widget
        ref={widgetRef}
        data-cap-api-endpoint="https://cap.tokengx.cn/YOUR_SITE_KEY/"
      />
      <button type="submit">登录</button>
    </form>
  );
}

Vue 3 示例

<template>
  <form @submit.prevent="handleSubmit">
    <input v-model="phone" type="tel" placeholder="手机号码">
    <input v-model="password" type="password" placeholder="密码">
    <cap-widget
      ref="widget"
      :data-cap-api-endpoint="`https://cap.tokengx.cn/${siteKey}/`"
      @solve="onSolve"
      @error="onError"
    />
    <button type="submit">登录</button>
  </form>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import 'cap-widget';

const siteKey = 'YOUR_SITE_KEY';
const widget = ref(null);
const captchaToken = ref(null);
const phone = ref('');
const password = ref('');

const onSolve = (e) => {
  captchaToken.value = e.detail.token;
};

const onError = () => {
  captchaToken.value = null;
};

const handleSubmit = async () => {
  if (!captchaToken.value) {
    alert('请先完成人机验证');
    return;
  }
  // 提交逻辑...
};
</script>
💡 提示

data-cap-api-endpoint 的格式为: https://cap.tokengx.cn/{SiteKey}/
注意末尾需要有斜杠 /

后端验证

Node.js / Express

const express = require('express');
const app = express();

const CAPTCHA_SECRET_KEY = process.env.CAPTCHA_SECRET_KEY;

async function verifyCaptcha(token) {
  try {
    const response = await fetch('https://cap.tokengx.cn/siteverify', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        response: token,
        secret: CAPTCHA_SECRET_KEY
      })
    });
    const data = await response.json();
    return data.success === true;
  } catch (error) {
    console.error('Captcha verification error:', error);
    return false;
  }
}

app.post('/api/login', async (req, res) => {
  const { phone, password, captchaToken } = req.body;

  // 验证 CAPTCHA
  if (!captchaToken || !(await verifyCaptcha(captchaToken))) {
    return res.status(400).json({
      success: false,
      error: '人机验证失败,请重试'
    });
  }

  // 验证通过,处理登录逻辑
  // ...
});

Python / Flask

import requests
from flask import Flask, request, jsonify

app = Flask(__name__)

CAPTCHA_SECRET_KEY = 'sk-your-secret-key'

def verify_captcha(token):
    try:
        response = requests.post(
            'https://cap.tokengx.cn/siteverify',
            json={
                'response': token,
                'secret': CAPTCHA_SECRET_KEY
            }
        )
        data = response.json()
        return data.get('success', False)
    except Exception as e:
        print(f'Captcha verification error: {e}')
        return False

@app.route('/api/login', methods=['POST'])
def login():
    data = request.json
    token = data.get('captchaToken')

    # 验证 CAPTCHA
    if not token or not verify_captcha(token):
        return jsonify({
            'success': False,
            'error': '人机验证失败,请重试'
        }), 400

    # 验证通过,处理登录逻辑
    # ...
    return jsonify({'success': True})

PHP

<?php
function verifyCaptcha($token) {
    $secretKey = 'sk-your-secret-key';
    
    $response = file_get_contents('https://cap.tokengx.cn/siteverify', false, stream_context_create([
        'http' => [
            'method' => 'POST',
            'header' => 'Content-Type: application/json',
            'content' => json_encode([
                'response' => $token,
                'secret' => $secretKey
            ])
        ]
    ]));
    
    $data = json_decode($response, true);
    return $data['success'] === true;
}

// 在登录处理中使用
$captchaToken = $_POST['captchaToken'] ?? '';

if (!$captchaToken || !verifyCaptcha($captchaToken)) {
    http_response_code(400);
    echo json_encode([
        'success' => false,
        'error' => '人机验证失败,请重试'
    ]);
    exit;
}

// 验证通过,处理登录逻辑
// ...
?>

环境变量配置

# .env
CAPTCHA_SECRET_KEY=sk-your-secret-key
NEXT_PUBLIC_CAPTCHA_SITE_KEY=your-site-key

API 参考

前端事件

事件名 说明 回调参数
solve 验证成功 e.detail.token - 三段式 token
error 验证失败 e.detail.code - 错误码
progress 验证进度 e.detail.progress - 进度百分比
reset 重置验证

前端属性

属性名 说明 示例值
data-cap-api-endpoint API 端点(必填) https://cap.tokengx.cn/YOUR_SITE_KEY/
data-cap-lang 语言 zhenja
data-cap-hidden-field-name 隐藏表单字段名 captcha_token
data-cap-worker-count Worker 数量 4

后端验证 API

POST https://cap.tokengx.cn/siteverify

请求

{
  "response": "siteKey:challenge:signature",
  "secret": "sk-your-secret-key"
}

成功响应

{
  "success": true
}

失败响应

{
  "success": false,
  "error": "Invalid token"
}
⚠️ 注意

response 参数必须是三段式格式: siteKey:challenge:signature
这是 Cap Widget 自动生成的格式,直接传递即可。

自定义样式

cap-widget {
  --cap-widget-width: 280px;
  --cap-widget-height: 60px;
  --cap-background: #ffffff;
  --cap-border-color: #e0e0e0;
  --cap-color: #333333;
  --cap-border-radius: 12px;
  --cap-checkbox-size: 20px;
  --cap-checkbox-background: #f0f0f0;
  --cap-checkbox-border: #ccc;
  --cap-checkmark: #000;
}

常见问题

Q: Widget 不显示?

1. 检查 data-cap-api-endpoint 格式是否正确(末尾需有 /
2. 检查浏览器控制台是否有错误
3. 确认 CSP 配置允许加载 cap.tokengx.cn 的资源

Q: 验证失败?

1. 检查 Secret Key 是否正确
2. 确认 token 是三段式格式
3. 检查网络连接是否正常

Q: 如何重置验证?

const widget = document.querySelector('cap-widget');
widget.reset();

Q: 支持哪些浏览器?

- Chrome 60+
- Firefox 60+
- Safari 12+
- Edge 79+