Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

YourTJCaptcha

基于同济大学校园场景的九宫格图片验证码服务,采用无状态 JWT 方案,支持 Vercel 部署。

功能特性

  • 验证题型:随机出现"选择同济校内"或"选择非同济校内"两种题型
  • 无状态设计:基于 JWT 的无状态验证,无需服务端存储
  • Serverless 部署:适配 Vercel 等 Serverless 平台
  • 服务:提供完整的 API 接口和演示页面
  • 后端验证:提供 /api/siteverify 供后端服务核验通行证

验证规则

题型 1:选择同济校内场景

  • 提示:选择下列在同济校内的有:
  • 规则:只选择 public/assets/tongji/ 中的图片才算通过

题型 2:选择非同济校内场景

  • 提示:选择下列不在同济校内的有:
  • 规则:只选择 public/assets/other/ 中的图片才算通过

目录结构

TongjiCaptcha/
├── api/                    # Vercel Serverless Functions
│   ├── captcha.ts         # 生成验证码挑战
│   ├── verify.ts          # 验证用户答案
│   └── siteverify.ts      # 后端核验通行证
├── src/                    # 前端源码
│   ├── index.ts           # 主入口
│   ├── data.ts            # 数据处理逻辑
│   ├── types.ts           # TypeScript 类型定义
│   ├── tongjiAssets.ts    # 同济资源管理
│   ├── assets/            # 静态资源
│   └── character/         # 字符资源
├── public/                 # 公共资源
│   └── assets/
│       ├── tongji/        # 同济校内图片(必需)
│       └── other/         # 非同济图片(必需)
├── package.json
├── tsconfig.json
├── vercel.json            # Vercel 部署配置
└── .env.example           # 环境变量示例

快速开始

环境要求

  • Node.js >= 18
  • npm 或 pnpm

安装依赖

npm install
# 或
pnpm install

配置环境变量

复制 .env.example 为 .env 并配置:

cp .env.example .env

编辑 .env 文件:

PORT=3001                    # 本地开发端口
JWT_SECRET=your-secret-key   # JWT 密钥(生产环境必须设置)

准备图片资源

在 public/assets/ 目录下创建两个文件夹:

  1. tongji/:放置同济校内场景图片
  2. other/:放置非同济场景图片

支持的图片格式:.jpg、.jpeg、.png、.webp

本地开发

npm run dev

访问 http://localhost:3001 查看演示页面。

生产构建

npm run build
npm start

API 接口文档

1. 获取验证码挑战

GET /api/captcha

响应示例:

{
  "puzzle_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "questionType": "TONGJI_IN",
  "prompt": "选择下列在同济校内的有:",
  "images": [
    "/assets/tongji/1.jpg",
    "/assets/tongji/2.jpg",
    "/assets/other/1.jpg",
    "... 共9张"
  ]
}

字段说明:

  • puzzle_token:加密的题目令牌,包含答案信息
  • questionType:题型类型
    • TONGJI_IN:选择同济校内场景
    • TONGJI_NOT_IN:选择非同济场景
  • prompt:提示文本
  • images:9 张图片的 URL 数组

2. 验证用户答案

POST /api/verify

请求体:

{
  "puzzle_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "selected_indices": [0, 3, 5]
}

成功响应:

{
  "success": true,
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "duration": 1234
}

失败响应:

{
  "success": false,
  "message": "验证失败"
}

字段说明:

  • success:验证是否成功
  • token:通行证令牌(验证成功时返回)
  • duration:验证耗时(毫秒)

3. 后端核验通行证

POST /api/siteverify

请求体:

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

响应:

{
  "success": true
}

说明: 供后端服务调用,验证用户提交的通行证是否有效。

Vercel 部署

1. 准备工作

  • 注册 Vercel 账号
  • 安装 Vercel CLI:npm i -g vercel

2. 配置环境变量

在 Vercel 项目设置中添加环境变量:

JWT_SECRET=your-production-secret-key

3. 部署

vercel

或通过 GitHub 集成自动部署。

4. 自定义域名

在 Vercel 项目设置中配置自定义域名。

前端集成示例

HTML + JavaScript

<!DOCTYPE html>
<html>
<head>
  <title>验证码示例</title>
</head>
<body>
  <div id="captcha-container"></div>
  <button onclick="verifyCaptcha()">提交</button>

  <script>
    let puzzleToken = '';
    let selectedIndices = [];

    // 加载验证码
    async function loadCaptcha() {
      const res = await fetch('/api/captcha');
      const data = await res.json();
      puzzleToken = data.puzzle_token;

      // 渲染九宫格
      const container = document.getElementById('captcha-container');
      container.innerHTML = `<p>${data.prompt}</p>`;
      data.images.forEach((img, index) => {
        const imgEl = document.createElement('img');
        imgEl.src = img;
        imgEl.onclick = () => toggleSelect(index);
        container.appendChild(imgEl);
      });
    }

    function toggleSelect(index) {
      const idx = selectedIndices.indexOf(index);
      if (idx > -1) {
        selectedIndices.splice(idx, 1);
      } else {
        selectedIndices.push(index);
      }
    }

    // 验证
    async function verifyCaptcha() {
      const res = await fetch('/api/verify', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          puzzle_token: puzzleToken,
          selected_indices: selectedIndices
        })
      });
      const data = await res.json();

      if (data.success) {
        console.log('验证成功,通行证:', data.token);
        // 将 token 提交给后端
      } else {
        alert('验证失败,请重试');
        loadCaptcha();
      }
    }

    loadCaptcha();
  </script>
</body>
</html>

React 示例

import { useState, useEffect } from 'react';

function TongjiCaptcha({ onSuccess }) {
  const [captcha, setCaptcha] = useState(null);
  const [selected, setSelected] = useState([]);

  useEffect(() => {
    loadCaptcha();
  }, []);

  const loadCaptcha = async () => {
    const res = await fetch('/api/captcha');
    const data = await res.json();
    setCaptcha(data);
    setSelected([]);
  };

  const toggleSelect = (index) => {
    setSelected(prev =>
      prev.includes(index)
        ? prev.filter(i => i !== index)
        : [...prev, index]
    );
  };

  const verify = async () => {
    const res = await fetch('/api/verify', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        puzzle_token: captcha.puzzle_token,
        selected_indices: selected
      })
    });
    const data = await res.json();

    if (data.success) {
      onSuccess(data.token);
    } else {
      alert('验证失败');
      loadCaptcha();
    }
  };

  if (!captcha) return <div>加载中...</div>;

  return (
    <div>
      <p>{captcha.prompt}</p>
      <div className="grid grid-cols-3 gap-2">
        {captcha.images.map((img, index) => (
          <img
            key={index}
            src={img}
            onClick={() => toggleSelect(index)}
            className={selected.includes(index) ? 'selected' : ''}
          />
        ))}
      </div>
      <button onClick={verify}>提交</button>
    </div>
  );
}

安全建议

  1. JWT 密钥:生产环境必须设置强随机密钥
  2. HTTPS:生产环境必须使用 HTTPS
  3. 速率限制:建议在 API 网关层添加速率限制
  4. 图片资源:定期更新图片库,避免被机器学习破解

常见问题

Q: 如何添加更多图片?

A: 将图片放入 public/assets/tongji/ 或 public/assets/other/ 目录即可,服务会自动扫描。

Q: 支持哪些图片格式?

A: 支持 .jpg、.jpeg、.png、.webp 格式。

Q: 如何自定义题目文本?

A: 修改 api/captcha.ts 中的 prompt 字段。

Q: 验证码有效期是多久?

A: 默认无限期有效,可在 JWT 签发时添加 exp 字段设置过期时间。

技术栈

  • 运行时:Node.js
  • 框架:Vercel Serverless Functions
  • 认证:JWT (jsonwebtoken)
  • 语言:TypeScript

About

YourTJCaptcha人机验证服务

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages