fastapi: 生成图形验证码

一,安装用到的第三方库

 % pip install fastapi uvicorn captcha

二,代码

库

from captcha.image import ImageCaptcha

# 模拟 Redis 或 Session 存储,实际生产中应使用 Redis 并设置过期时间
captcha_store = {}

# 初始化验证码生成器
image_generator = ImageCaptcha(width=160, height=60)

调用:生成验证码图片

"""前台首页。"""

from fastapi import APIRouter, Depends

from app.core.captcha import captcha_store, image_generator
from app.core.response import success

import uuid
import random
import string
import base64

router = APIRouter(prefix="/capt",tags=["前台-首页"])

@router.get("/captcha")
def get_captcha():
    """生成图形验证码,直接以 JSON 形式返回 Base64 字符串和 ID"""
    captcha_text = "".join(random.choices(string.ascii_uppercase + string.digits, k=4))
    captcha_id = str(uuid.uuid4())
    captcha_store[captcha_id] = captcha_text.lower()

    # 1. 将生成的验证码图片写入内存缓冲区 (BytesIO)
    img_buffer = image_generator.generate(captcha_text)

    # 2. 将二进制数据转为 Base64 编码的字节,再解码为字符串
    img_base64 = base64.b64encode(img_buffer.getvalue()).decode("utf-8")

    # 3. 拼接成前端 <img> 标签可以直接识别的 Data URL 格式
    captcha_image_url = f"data:image/png;base64,{img_base64}"

    # 4. 直接返回标准的 JSON 对象
    cont = {
        "captcha_id": captcha_id,
        "captcha_image": captcha_image_url
    }
    return success(cont)

校验

class LoginRequest(BaseModel):
    username: str
    password: str
    captcha_id: str
    captcha_code: str

@app.post("/api/login")
def login(data: LoginRequest):
    if data.captcha_id not in captcha_store:
        raise HTTPException(status_code=400, detail="验证码已过期,请刷新重试")
    
    correct_code = captcha_store[data.captcha_id]
    del captcha_store[data.captcha_id] # 及时销毁
    
    if data.captcha_code.lower() != correct_code:
        raise HTTPException(status_code=400, detail="验证码错误")
        
    if data.username == "admin" and data.password == "123456":
        return {"status": "success", "message": "登录成功!"}
    
    raise HTTPException(status_code=400, detail="用户名或密码错误")

前端:

<script setup>
import { ref, reactive, onMounted } from 'vue';
import axios from 'axios';

const form = reactive({
  username: '',
  password: '',
  captcha_id: '',     // 用于和后端对暗号
  captcha_code: ''    // 用户输入的验证码文本
});

const captchaImage = ref(''); // 直接存储 Base64 字符串

// 获取/刷新图形验证码
const refreshCaptcha = async () => {
  try {
    // 像标准 API 一样直接 GET 请求 JSON 数据
    const response = await axios.get('http://127.0.0');
    
    // 直接从响应体(res.data)中取出 ID 和 Base64 图片
    form.captcha_id = response.data.captcha_id;
    captchaImage.value = response.data.captcha_image;
  } catch (error) {
    alert('获取验证码失败,请检查网络!');
  }
};

const handleLogin = async () => {
  try {
    const res = await axios.post('http://127.0.0', form);
    alert(res.data.message);
  } catch (error) {
    alert(error.response?.data?.detail || '登录失败');
    form.captcha_code = '';
    refreshCaptcha(); // 失败立即刷新
  }
};

onMounted(() => {
  refreshCaptcha();
});
</script>

html

<!-- 验证码图片展示区 -->
<img 
  v-if="captchaImage" 
  :src="captchaImage" 
  @click="refreshCaptcha" 
  title="点击刷新验证码"
  class="captcha-img" 
/>
<span v-else @click="refreshCaptcha" class="loading-text">加载中...</span>

三,测试效果

image

posted @ 2026-10-04 21:26  刘宏缔的架构森林  阅读(6)  评论(0)    收藏  举报