基于base64Captcha实现验证码功能

该三方库支持5种验证方法:

支持:

  • 数字验证码
  • 公式
  • 字母
  • 汉字
  • 音频

下面的代码中只有数字、字母、数字+字母的形式

在代码的注释中,我已经写出了用法、理解等,代码如下:

Go 复制代码
package main

import (
	// 标准库,提供图像颜色处理
	//"image/color"
	"net/http"
	"time"

	// 三方库解决跨域问题
	"github.com/gin-contrib/cors"

	"github.com/gin-gonic/gin"
	// 三方库实现图片验证码  别名base64Captcha  可支持字母、数字的验证数据
	base64Captcha "github.com/mojocn/base64Captcha"
)

// Math 配置参数
// var (
// 	Height          = 70
// 	Width           = 240
// 	NoiseCount      = 0
// 	ShowLineOptions = base64Captcha.OptionShowHollowLine
// 	BgColor         = &color.RGBA{
// 		R: 144,
// 		G: 238,
// 		B: 144,
// 		A: 10,
// 	}
// 	FontsStorage base64Captcha.FontsStorage
// 	Fonts        []string
// )

// 全局变量  用于模拟存储用户信息的Mysql数据库
var users = make(map[string]string)

var store = base64Captcha.DefaultMemStore
// 音频的验证码自行探索

//var driver = base64Captcha.DefaultDriverDigit // 数字

//自定义验证码:
//var driver = base64Captcha.NewDriverMath(Height, Width, NoiseCount, ShowLineOptions, BgColor, FontsStorage, Fonts)	// 字母
var driver = base64Captcha.NewDriverString(
	80, 240, 6, 1, 4,
	"123456789abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ", // 包含数字和字母的字符集
	nil, nil, nil,
) // 字符串:数字+字母

// 生成验证码并返回图片
func generateCaptcha(c *gin.Context) {
	captcha := base64Captcha.NewCaptcha(driver, store)
	// 生成验证码(返回的 id:验证码id,base64编码的验证码图片,验证码答案,错误信息)
	id, b64, _, err := captcha.Generate()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "验证码生成失败"})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"captcha_id":  id,
		"captcha_img": b64,
	})
}

// 注册接口
func register(c *gin.Context) {
	var json struct {
		Username   string `json:"username"`
		Password   string `json:"password"`
		CaptchaId  string `json:"captcha_id"`
		CaptchaAns string `json:"captcha_ans"`
	}

	if err := c.ShouldBindJSON(&json); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "请求数据格式错误"})
		return
	}

	// 验证验证码
	if !store.Verify(json.CaptchaId, json.CaptchaAns, true) {
		c.JSON(http.StatusBadRequest, gin.H{"error": "验证码错误"})
		return
	}

	// 注册用户(简单模拟)
	if _, exists := users[json.Username]; exists {
		c.JSON(http.StatusBadRequest, gin.H{"error": "用户已存在"})
		return
	}

	// 存储用户数据
	users[json.Username] = json.Password

	c.JSON(http.StatusOK, gin.H{"message": "注册成功"})
}

// 登录接口
func login(c *gin.Context) {
	var json struct {
		Username   string `json:"username"`
		Password   string `json:"password"`
		CaptchaId  string `json:"captcha_id"`
		CaptchaAns string `json:"captcha_ans"`
	}

	if err := c.ShouldBindJSON(&json); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "请求数据格式错误"})
		return
	}

	// 验证验证码(参数:id、answer不用说了;clear:使用后是否清除验证码 )
	if !store.Verify(json.CaptchaId, json.CaptchaAns, true) {
		c.JSON(http.StatusBadRequest, gin.H{"error": "验证码错误"})
		return
	}

	// 验证用户名和密码
	storedPassword, exists := users[json.Username]
	if !exists || storedPassword != json.Password {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "用户名或密码错误"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "登录成功"})
}

func main() {
	r := gin.Default()

	// 配置 CORS 中间件
	r.Use(cors.New(cors.Config{
		AllowOrigins:     []string{"http://127.0.0.1:5500"}, // 前端的 URL
		AllowMethods:     []string{"GET", "POST"},
		AllowHeaders:     []string{"Origin", "Content-Type"},
		ExposeHeaders:    []string{"Content-Length"},
		AllowCredentials: true,
		MaxAge:           12 * time.Hour,
	}))

	// 注册路由
	r.GET("/captcha", generateCaptcha)
	r.POST("/register", register)
	r.POST("/login", login)

	// 启动服务器
	r.Run("127.0.0.1:8080")
}

追问:在验证码进行验证的时候,我只是获取了用户的输入,就直接调用Verify方法进行验证了。那么之前生成的验证码的答案在哪里?

答案:其实该库自己维护一个全局变量,当使用库时,就已经进行了初始化。MemoryStore用来短暂的存储当前生成的验证码。

我们可能最初会想到使用redis数据库进行缓存,但是一般的使用场景中,我们其实只需要一个短暂的答案就可以了,所以并不需要,当然也可以。

想了解的,可以参考:一文搞懂Go整合captcha实现验证码功能-腾讯云开发者社区-腾讯云https://cloud.tencent.com/developer/article/2388090

前端验证代码:(比较简陋,我直接把登录和注册放到一起,可以直接尝试)

index.html

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>登录和注册</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      padding: 20px;
    }
    input {
      margin: 10px 0;
      padding: 8px;
    }
    #captchaImg {
      margin-top: 10px;
      cursor: pointer;
    }
  </style>
</head>
<body>
  <h2>注册</h2>
  <label for="registerUsername">用户名</label>
  <input type="text" id="registerUsername" required>
  <br>
  <label for="registerPassword">密码</label>
  <input type="password" id="registerPassword" required>

  <br>
  <label for="registerCaptchaAns">验证码</label>
  <input type="text" id="registerCaptchaAns" required>
  <br>
  <img id="registerCaptchaImg" src="" alt="验证码" onclick="getCaptcha('register')" title="点击刷新验证码">
  <input type="hidden" id="registerCaptchaId">
  <br>

  <button onclick="register()">注册</button>

  <h2>登录</h2>
  <label for="loginUsername">用户名</label>
  <input type="text" id="loginUsername" required>
  <br>
  <label for="loginPassword">密码</label>
  <input type="password" id="loginPassword" required>

  <br>
  <label for="loginCaptchaAns">验证码</label>
  <input type="text" id="loginCaptchaAns" required>
  <br>
  <img id="loginCaptchaImg" src="" alt="验证码" onclick="getCaptcha('login')" title="点击刷新验证码">
  <input type="hidden" id="loginCaptchaId">
  <br>
  
  <button onclick="login()">登录</button>

  <script src="scripts.js"></script>
</body>
</html>

scripts.js

javascript 复制代码
// 获取验证码函数
function getCaptcha(context) {
    fetch('http://localhost:8080/captcha')
      .then(response => response.json())
      .then(data => {
        if (context === 'register') {
          document.getElementById('registerCaptchaImg').src = data.captcha_img;
          document.getElementById('registerCaptchaId').value = data.captcha_id;
        } else if (context === 'login') {
          document.getElementById('loginCaptchaImg').src = data.captcha_img;
          document.getElementById('loginCaptchaId').value = data.captcha_id;
        }
      })
      .catch(error => {
        console.error('Error fetching captcha:', error);
      });
  }
  
  // 注册函数
  function register() {
    const username = document.getElementById('registerUsername').value;
    const password = document.getElementById('registerPassword').value;
    const captchaId = document.getElementById('registerCaptchaId').value;
    const captchaAns = document.getElementById('registerCaptchaAns').value;
  
    const data = {
      username,
      password,
      captcha_id: captchaId,
      captcha_ans: captchaAns,
    };
  
    fetch('http://localhost:8080/register', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(data),
    })
      .then(response => response.json())
      .then(data => {
        if (data.error) {
          alert('注册失败: ' + data.error);
          getCaptcha('register'); // 刷新验证码
        } else {
          alert('注册成功');
          getCaptcha('register'); // 刷新验证码
        }
      })
      .catch(error => {
        console.error('Error during registration:', error);
      });
  }
  
  // 登录函数
  function login() {
    const username = document.getElementById('loginUsername').value;
    const password = document.getElementById('loginPassword').value;
    const captchaId = document.getElementById('loginCaptchaId').value;
    const captchaAns = document.getElementById('loginCaptchaAns').value;
  
    const data = {
      username,
      password,
      captcha_id: captchaId,
      captcha_ans: captchaAns,
    };
  
    fetch('http://localhost:8080/login', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(data),
    })
      .then(response => response.json())
      .then(data => {
        if (data.error) {
          alert('登录失败: ' + data.error);
          getCaptcha('login'); // 刷新验证码
        } else {
          alert('登录成功');
          getCaptcha('login'); // 刷新验证码
        }
      })
      .catch(error => {
        console.error('Error during login:', error);
      });
  }
  
  // 页面加载时自动获取注册和登录验证码
  window.onload = function () {
    getCaptcha('register');
    getCaptcha('login');
  };
  
相关推荐
未来可期LJ6 分钟前
【C++复习第5小节】类和对象
开发语言·c++
~狂想家~8 分钟前
使用C语言库函数格式化输入时格式类型与数据类型不匹配导致程序异常
c语言·开发语言
越甲八千11 分钟前
深入理解STL list erase
开发语言·c++·list
sukalot21 分钟前
windows C#-命名实参和可选实参(上)
开发语言·c#
精神病不行计算机不上班32 分钟前
[C++]类的继承
开发语言·c++
人类群星闪耀时33 分钟前
使用Python实现天文数据分析:探索宇宙的奥秘
开发语言·python·数据分析
Q_192849990642 分钟前
基于Spring Boot的小区车辆管理系统
java·spring boot·后端
Mr. zhihao1 小时前
Spring Boot 条件注解:@ConditionalOnProperty 完全解析
java·spring boot·后端
豆本-豆豆奶1 小时前
用python实现滑雪小游戏,附源码
开发语言·python·pygame
梧桐树04291 小时前
python IO编程:序列化
开发语言·python