Skip to content

图形验证码

我来介绍使用laravel11与 react 和扩展包 mews/captcha 实现网站的图形验证码。如果你使用其他语言,也可以帮助你提供思路。

后端

首先访问 mews/captcha 安装并配置扩展包

composer require mews/captcha

然后执行命令生成配置文件

php artisan vendor:publish

创建控制器 CaptchaController.php 用来生成验证码图像,对!是blob图像不是链接。

为什么生成 图像数据呢?因为在前后端分离时,如果使用url,前端vite使用代理 和使用 url 时,是不同的域,会造成 session 会话是不同的,结果是验证码验证失败。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Mews\Captcha\Facades\Captcha;
use Symfony\Component\Mime\Encoder\Base64Encoder;

//图形验证码
class CaptchaController extends Controller
{
    public function __invoke()
    {
    		//生成数学计算验证码
        return Captcha::create('math');
    }
}

前端

下面的前端环境是 vite、react ,我们首先配置 vite的代理

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import path from 'path'
import { TanStackRouterVite } from '@tanstack/router-plugin/vite'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react(), TanStackRouterVite(),],
  resolve: {
    alias: { '@': path.resolve(__dirname, 'src') },
  },
  server: {
    proxy: {
      '/api': {
        target: 'http://php.test',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
})

下面创建 axios 的请求接口,使用的是 tanstack query 库,来获取后端的验证码图形数据

import { useAxios } from "@/hooks/useAxios";
import { useSuspenseQuery } from "@tanstack/react-query";

//图像验证码
export const useGetCaptcha = () => {
	const { axiosInstance } = useAxios()
	return useSuspenseQuery({
		queryKey: ['captcha'],
		queryFn: async () => {
			const data = (await axiosInstance.get<Blob>('/captcha_img', {
				responseType: 'blob'
			})).data

			return URL.createObjectURL(data)
		}
	})
}

接下来开发图形验证码组件,以下代码是我写的项目的代码,包括以下内容

  • 表单验证处理
  • 获取与显示图形验证码
import { useGetCaptcha } from '@/services/captcha'
import { useValidateStore } from '@/store/useValidateStore'
import { useQueryClient } from '@tanstack/react-query'
import classNames from 'classnames'
import { Input } from '../ui/input'
import { FieldValidateError } from './FieldValidateError'
import { IFieldComponent } from './types'

export function FieldImageCaptcha({ form, name, className, ...props }: IFieldComponent) {
  const removeError = useValidateStore((s) => s.removeError)
  //获取验证码图像数据 blob
  const { data: captcha } = useGetCaptcha()
  const queryClient = useQueryClient()
  return (
    <div className={classNames(className)}>
      <form.Field
        name={name}
        children={(field) => (
          <div>
            <div className='flex items-stretch gap-2'>
              <div className='flex-1'>
                <Input
                  {...props}
                  value={field.state.value}
                  onChange={(e) => field.handleChange(e.target.value)}
                  onFocus={() => removeError(name)}
                />
              </div>
              <div className='overflow-hidden border rounded-lg cursor-pointer '>
                <img
                  src={captcha}
                  onClick={async () => {
                    await queryClient.invalidateQueries({ queryKey: ['captcha'] })
                  }}
                />
              </div>
            </div>
            <FieldValidateError errors={field.state.meta.errors} name={name} />
          </div>
        )}
      />
    </div>
  )
}

其实你只需要关注以下两行就行。

//根据上面的接口获取验证码图像数据
const { data: captcha } = useGetCaptcha()
...
//显示验证码图像
<img
  src={captcha}
  onClick={async () => {
    await queryClient.invalidateQueries({ queryKey: ['captcha'] })
  }}
/>