Go调用base64captcha验证码

Go使用base64captcha生成验证码并保存到redis,以及一些其它的逻辑。

使用了go-logger库作为日志采集工具,需要go版本 >= 1.22.4,推荐使用g管理go版本工具

前端代码使用vite脚手架简单生成Vue3+ElementPlus的小demo
http.js---->封装Axios
import axios from 'axios';

const instance = axios.create({
    baseURL: 'http://192.168.40.182:8001', // 替换为你的API基础URL
    timeout: 1000, // 请求超时时间
    headers: {
        'Content-Type': 'application/json', // 根据需要设置内容类型
    }
});

export default instance;
router.js---->vue-router 声明路由
import { createWebHashHistory, createRouter } from 'vue-router'
import Login from './components/Login.vue'
import Home from './components/Home.vue'
import Index from './components/Index.vue'

const routes = [
    { path: '/', component: Index },
    { path: '/login', component: Login },
    { path: '/home', component: Home }
]

const router = createRouter({
    history: createWebHashHistory(),
    routes,
})

export default router
vite-config.js---->自动导入UI插件
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
  server: {
    host: '0.0.0.0',
    port: '8000'
  }
})
main.js---->将路由挂载App实例
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'

createApp(App).use(router).mount('#app')
Login.vue---->登陆组件
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'
import instance from '../http'
import { useRouter } from 'vue-router'


const ruleFormRef = ref<FormInstance>()
const isTimeDisable = ref(false)
const ruleForm = reactive({
  username: '',
  password: '',
  code: '',
})
const router = useRouter() // 获取路由实例
// 用于存储验证码的Base64数据
const captchaImage = ref('')
// 验证码倒计时
let codeTime = ref(60)
// 验证码的内容
const codeContent = ref('点击获取验证码')

const CheckUser = (rule: any, value: any, callback: any) => {
  if (ruleForm.username === '') {
    callback(new Error('用户名不能为空'))
  }
  callback()
}
const CheckPass = (rule: any, value: any, callback: any) => {
  if (ruleForm.password === '') {
        callback(new Error('密码不能为空'))
    }
    callback()
}

const CheckCode = (rule: any, value: any, callback: any) => {    
    if (ruleForm.code === '') {
        callback(new Error('验证码不能为空'))
    }
    callback()
}

// 前端规则验证
const rules = reactive({
  username: [{ validator: CheckUser, trigger: 'blur' }],
  password: [{ validator: CheckPass, trigger: 'blur' }],
  code: [{ validator: CheckCode, trigger: 'blur' }],
})

// 重置输入
const resetForm = (formEl: FormInstance | undefined) => {
  if (!formEl) return
  formEl.resetFields()
}

// 提交数据表单到HOME
const submitForm =  (formEl: FormInstance | undefined) => {
  if (!formEl) return
  formEl.validate(async (valid) => {
    if (valid) {
      // 表单填写合法,发送请求到后端
      try{
        const response = await instance.post('/login',ruleForm)
        ElMessage.success({message: response.data.msg})
        localStorage.setItem('accessToken',response.data.accessToken)
        setTimeout(() => {
          // 跳转至Home
          router.push({ path: '/home', query: { access: response.data.accessToken } })
        }, 1000);
      }catch(err){
        console.log(err.response.data.msg)
        ElMessage.error(err.response.data.msg)
      }
    } else {
      alert('数据不合法')
    }
  })
}

// 获取验证码
const refreshCaptcha = async () => {
    if (ruleForm.username == "") {
        alert("请先输入用户名")
        return
    }
    // 根据需要修改
    const requestData  = {username:ruleForm.username}
    // 验证码倒计时
    const timer = setInterval(() => {
    if (codeTime.value > 0) {
      isTimeDisable.value = true
      codeContent.value = `验证码于(${codeTime.value}s)后过期`;
      codeTime.value --
    }else {
      clearInterval(timer);
      isTimeDisable.value = false
      codeContent.value = "重新获取验证码"
      codeTime.value = 60
    }  
  },1000)
    try {
      const response = await instance.get('/code', requestData);
      // 后端返回的验证码是以 Base64 形式存储在data.captcha字段中
      captchaImage.value = response.data.captchaImg;
    } catch (error) {
        alert(error); // 捕获并打印错误
    }
} 

</script>

<template>
    <el-card style="width: 440px;height: 350px;">
    <template #header>
      <div class="card-header">
        <span >登陆验证功能</span>
      </div>
        </template #body>
         <el-form
            ref="ruleFormRef"
            :model="ruleForm"
            status-icon
            :rules="rules"
            label-width="10px"
            class="demo-ruleForm"
            >
            <el-form-item label="" prop="username">
                <el-input v-model="ruleForm.username"  autocomplete="off" placeholder="请输入用户名"/>
            </el-form-item>
            <el-form-item label="" prop="password">
                <el-input v-model="ruleForm.password" type="password" autocomplete="off" placeholder="请输入密码" />
            </el-form-item>
            <el-form-item label="" prop="code" >
                <el-input v-model="ruleForm.code"  autocomplete="off" style="width:150px" placeholder="请输入验证码"/>
                <div class="code">
                    <img v-if="captchaImage":src="captchaImage" alt=""/>
                    <el-button :disabled="isTimeDisable" @click="refreshCaptcha" style="height: 10px; font-size: smaller; margin-left:-30px; border:none" >{{codeContent}}</el-button>
				</div>
            </el-form-item>
                <div class="foot">
                    <el-button type="primary" @click="submitForm(ruleFormRef)">
                    提交
                    </el-button>
                    <el-button @click="resetForm(ruleFormRef)">
                    重置
                    </el-button>
                </div>
        </el-form>
  </el-card>
</template>

<style scoped>
.foot {
  margin-top:40px;
}
.code {
  display: flex;  flex-direction:column; justify-content: space-between; margin-left: 30%;
}
.code img{
  margin-left: -40px;
  width: 140px;
  height: 50px;
}
</style>
Index.vue---->登陆引导
<script setup lang='ts'>

</script>

<template>
<div>This is Index</div>
<router-link to="/login">点击跳转至登陆页</router-link>
</template>

<style lang='scss' scoped>

</style>
Home.vue---->需要权限访问页
<script setup lang='ts'>
import { ref, onMounted } from 'vue';
import { ElMessage } from 'element-plus';

const accessToken = ref(localStorage.getItem('accessToken')); // 假设你将 accessToken 存储在 localStorage

onMounted(() => {
  if (!accessToken.value) {
    ElMessage.warning('无权限');
  }
});
</script>

<template>
  <div v-if="accessToken">This is Home</div>
  <div v-else style="display: none;"></div> <!-- 为了保持结构一致,您也可以选择不渲染这个 div -->
</template>

<style lang='scss' scoped>

</style>
后端代码使用base64captcha生成存储至redis集群中,只需调用方法
目录结构说明
D:.
├─captcha_Handler 	# 验证码逻辑
├─constants			# 一些常量
├─Controller		# 接口定义
├─Db_Handler		# 数据库逻辑
├─Middle			# 定义中间件
├─model				# 一些模型
├─Redis_Handler		# redis逻辑
├─Log_Handler		# 日志实例
└─Tools				# 一些工具
redis_Handler---->redis实例
package Redis_Handler

import (
	"context"
	"github.com/donnie4w/go-logger/logger"
	"github.com/redis/go-redis/v9"
	"time"
)

type RedisClient struct {
	Ctx             context.Context
	RedisClusterCfg []string
	Client          *redis.ClusterClient
	Logger          *logger.Logging
}

// NewRedisClient 初始化redis客户端实例
func NewRedisClient(RedisClusterCfg []string, ctx context.Context, myLog *logger.Logging) *RedisClient {
	return &RedisClient{
		RedisClusterCfg: RedisClusterCfg,
		Client: redis.NewClusterClient(&redis.ClusterOptions{
			Addrs:        RedisClusterCfg,
			PoolSize:     10,               // 最大连接数
			MinIdleConns: 2,                // 最小闲置连接数
			DialTimeout:  30 * time.Second, // 闲置连接超时
		}),
		Ctx:    ctx,
		Logger: myLog,
	}
}

// SetKV 存储数据 有效期60秒
func (r *RedisClient) SetKV(key, value string) error {
	err := r.Client.Set(r.Ctx, key, value, 60*time.Second).Err()
	if err != nil {
		r.Logger.Errorf("存储验证码失败")
		return err
	}
	return nil
}

// GetKV 获取数据 有效期30秒
func (r *RedisClient) GetKV(key string) (string, error) {
	cmd := r.Client.Get(r.Ctx, key)
	if cmd.Err() != nil {
		r.Logger.Errorf("获取验证码失败")
		return "", cmd.Err()
	}
	return cmd.Val(), nil
}

func (r *RedisClient) DelKV(key string) error {
	cmd := r.Client.Del(r.Ctx, key)
	if cmd.Err() != nil {
		r.Logger.Errorf("删除验证码失败")
		return cmd.Err()
	}
	return nil
}

// DelRepeatKey 数据冗余清洗
func (r *RedisClient) DelRepeatKey(ClientIp string) error {
	exists, err := r.Client.Exists(r.Ctx, ClientIp).Result()
	if err != nil {
		r.Logger.Errorf("数据冗余清洗失败")
		return err
	}
	if exists != 0 {
		OldCaptchaId := r.Client.Get(r.Ctx, ClientIp).Val()
		err := r.DelKV(OldCaptchaId)
		if err != nil {
			r.Logger.Errorf("验证码校验完成后删除失败")
		}
	}
	return nil
}
db_handler---->数据库实例
package Db_Handler

import (
	"com.login.www/model"
	"database/sql"
	"errors"
	"fmt"
	"github.com/donnie4w/go-logger/logger"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/schema"
	"strconv"
	"time"
)

type DbClient struct {
	DB     *gorm.DB
	Dsn    string
	sqlDB  *sql.DB
	logger *logger.Logging
}

// NewDbClient 初始化数据库客户端实例
func NewDbClient(dsn string, myLog *logger.Logging) *DbClient {
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true,
		},
	})
	if err != nil {
		myLog.Infof("连接数据库失败")
		panic("failed to connect database")
	}
	sqlDB, err := db.DB()
	if err != nil {
		myLog.Infof("连接数据库失败")
		panic("failed to get database connection")
	}
	// 设置连接池
	sqlDB.SetMaxIdleConns(10)           // 最大空闲连接数
	sqlDB.SetMaxOpenConns(20)           // 最大打开连接数
	sqlDB.SetConnMaxLifetime(time.Hour) // 连接最大生命周期
	return &DbClient{
		Dsn:    dsn,
		DB:     db,
		sqlDB:  sqlDB,
		logger: myLog,
	}
}

func (db *DbClient) Close() error {
	if db.sqlDB == nil {
		db.logger.Error("数据库关闭失败")
		return errors.New("database connection is not initialized")
	}
	return db.sqlDB.Close() // 使用 db.sqlDB
}

func (db *DbClient) GetUserUsername(username string) (string, error) {
	var user model.User
	if err := db.DB.Where("username = ?", username).Unscoped().Find(&user).Error; err != nil {
		return "", err
	}
	return user.Username, nil
}

func (db *DbClient) GetUserPassword(username string) (string, error) {
	var user model.User
	if err := db.DB.Where("username = ?", username).Unscoped().Find(&user).Error; err != nil {
		return "", err
	}
	return user.Password, nil
}

func (db *DbClient) GetUserId(username string) (string, error) {
	var user model.User
	if err := db.DB.Where("username = ?", username).Unscoped().Find(&user).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			fmt.Println("如果用户不存在,用户ID的值是:", user.Userid)
			// 如果记录不存在,返回特定错误
			return "", fmt.Errorf("user not found")
		}
		return "", err // 返回其他可能的错误
	}
	return strconv.Itoa(user.Userid), nil
}
middle_auth---->有关JWT的中间件
package Middle

import (
	tools "com.login.www/Tools"
	"com.login.www/constants"
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
	"time"
)

type JwtPayLoad struct {
	UserId   int    `json:"userid"`
	UserName string `json:"username"`
}

type CustomClaims struct {
	JwtPayLoad
	jwt.RegisteredClaims
}

// GenerateTokens 生成JWT token
func GenerateTokens(userid int, username string) (string, error) {
	// 创建登陆token
	accessClaims := CustomClaims{
		JwtPayLoad: JwtPayLoad{
			UserId:   userid,
			UserName: username,
		},
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(constants.AccessTokenExpiration)),
		},
	}
	accessTokenObj := jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims)
	accessToken, err := accessTokenObj.SignedString(constants.JwtSecretKey)
	if err != nil {
		return "", err
	}

	return accessToken, nil
}

// ValidateToken token校验
func ValidateToken(tokenString string) (*jwt.Token, error) {
	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, errors.New("unexpected signing method")
		}
		return constants.JwtSecretKey, nil
	})
	if err != nil {
		return nil, err
	}
	return token, nil
}

// TokenAuthMiddleware 验证有没有Token
func TokenAuthMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 获取accessToken
		tokenString := c.GetHeader("Authorization")
		// 如果没有accessToken
		if tokenString == "" {
			c.JSON(constants.ErrorCodeAccessTokenExpired, gin.H{"code": constants.ErrorCodeAccessTokenExpired, "msg": "没有accessToken"})
			c.Abort()
			return
		}

		// 去除 "Bearer " 前缀
		tokenString = tools.CleanStr(tokenString)
		fmt.Println(tokenString)
		token, err := ValidateToken(tokenString)

		if err != nil || !token.Valid {
			fmt.Println(err)
			c.JSON(constants.ErrorCodeAccessTokenExpired, gin.H{"code": constants.ErrorCodeAccessTokenExpired, "error": "accessToken不正确或已过期"})
			c.Abort()
			return
		}
		c.Next()
	}
}
middle_core---->解决跨域问题
package Middle

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

// Cors 解决前后端跨域
func Cors() gin.HandlerFunc {
	return func(c *gin.Context) {
		method := c.Request.Method
		c.Header("Access-Control-Allow-Origin", "*") // 可将将 * 替换为指定的域名
		c.Header("Access-Control-Allow-Methods", "*")
		c.Header("Access-Control-Allow-Headers", "*")
		c.Header("Access-Control-Expose-Headers", "*")
		c.Header("Access-Control-Allow-Credentials", "true")
		if method == "OPTIONS" {
			c.AbortWithStatus(http.StatusNoContent)
		}
		c.Next()
	}
}
constants
package constants

import (
	"github.com/mojocn/base64Captcha"
	"image/color"
	"time"
)

const (
	// AccessTokenExpiration JWT过期时间
	AccessTokenExpiration = 10 * time.Minute
	// ErrorCodeAccessTokenExpired 返回码
	ErrorCodeAccessTokenExpired = 292
	// Dsn 数据源连接串
	Dsn = "dbuser1:NSD2021@tedu.cn@tcp(192.168.40.199:13306)/user?charset=utf8mb4&parseTime=True&loc=Local"
)

var (
	// JwtSecretKey JWT加盐
	JwtSecretKey = []byte("you_secret_salt")
	// RedisCfg redis配置
	RedisCfg = []string{
		"192.168.40.180:7000",
		"192.168.40.180:7001",
		"192.168.40.180:7002",
		"192.168.40.181:7000",
		"192.168.40.181:7001",
		"192.168.40.181:7002",
	}
	// MaxHeight ... 验证码的各项参数
	MaxHeight       = 40
	MaxWidth        = 160
	Fonts           = []string{"wqy-microhei.ttc"}
	FontsStorage    base64Captcha.FontsStorage
	NoiseCount      = 10
	ShowLineOptions = 0.9
	Bg              = color.RGBA{
		R: 0,
		G: 255,
		B: 255,
		A: 0,
	}
)
tools_CreateLogPath---->获取日志路径
package tools

import (
	"os"
	"path/filepath"
)

func CreateLogPath() string {
	rootPath, _ := os.Getwd()
	logDir := filepath.Join(rootPath, "record.log")
	return logDir
}
tools_getclient---->获取客户端的IP地址,根据这个IP来清洗redis中冗余的数据
package tools

import "github.com/gin-gonic/gin"

// ClientIP 获取客户端真实IP
func ClientIP(c *gin.Context) string {
	ip := c.Request.Header.Get("X-Forwarded-For")
	if ip == "" {
		ip = c.Request.Header.Get("X-Real-IP")
	}
	if ip == "" {
		ip = c.ClientIP() // 最后fallback
	}
	return ip
}
tools_strclean---->删除前缀,提取Token
package tools

import "strings"
// CleanStr 提取token
func CleanStr(str string) string {
	str = strings.Trim(str, `"`)
	str = strings.TrimPrefix(str, "Bearer ")
	return str
}
controller_code.go---->生成验证码接口
package Controller

import (
	tools "com.login.www/Tools"
	"com.login.www/captcha_Handler"
	"github.com/donnie4w/go-logger/logger"
	"github.com/gin-gonic/gin"
	"github.com/mojocn/base64Captcha"
	"image/color"
	"net/http"
)

// Code 生成验证码接口
func Code(c *gin.Context, maxHeight, maxWidth, noiseCount, showLineOptions int, bg color.RGBA, fontsStorage base64Captcha.FontsStorage, fonts []string, rdsStore *captcha_Handler.RedisStore, myLog *logger.Logging) {
	clientId := tools.ClientIP(c)
	captchaID, captchaImg, captchaAnswer, err := captcha_Handler.GenerateCaptcha(maxHeight, maxWidth, noiseCount, showLineOptions, bg, fontsStorage, fonts, clientId, rdsStore, myLog)
	if err != nil {
		myLog.Errorf("用户%s获取验证码失败", clientId)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "生成验证码失败"})
		return
	}
	myLog.Infof("用户%s获取验证码成功", clientId)
	c.JSON(http.StatusOK, gin.H{"captcha_id": captchaID, "captchaImg": captchaImg, "captchaAnswer": captchaAnswer})
}
controller_Login---->登陆校验接口
package Controller

import (
	"com.login.www/Db_Handler"
	"com.login.www/Middle"
	tools "com.login.www/Tools"
	"com.login.www/captcha_Handler"
	"com.login.www/model"
	"github.com/donnie4w/go-logger/logger"
	"github.com/gin-gonic/gin"
	"net/http"
	"strconv"
)

// Login 校验登陆接口
func Login(c *gin.Context, db *Db_Handler.DbClient, rdsStore *captcha_Handler.RedisStore, myLog *logger.Logging) {
	clientIp := tools.ClientIP(c)
	var loginForm model.LoginForm
	if err := c.ShouldBindJSON(&loginForm); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"msg": "输入的参数不合法,请重新输入"})
		return
	}
	// 创建 RedisStore 实例
	captchaId, err := captcha_Handler.GetCodeFromClientIp(clientIp, rdsStore)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"msg": "验证码已过期,请重试"})
		return
	}
	ok := rdsStore.Verify(captchaId, loginForm.Code, true)
	if !ok {
		c.JSON(499, gin.H{"msg": "验证码填写错误,请重试"})
		return
	}

	_, err = db.GetUserUsername(loginForm.Username)
	if err != nil {
		myLog.Infof("客户%s登陆失败,用户没有注册?", loginForm.Username)
		c.JSON(http.StatusForbidden, gin.H{
			"msg": "用户尚未注册",
		})
		return
	}

	UserPasswd, err := db.GetUserPassword(loginForm.Username)
	if loginForm.Password != UserPasswd {
		myLog.Infof("客户%s登陆失败,密码不对", loginForm.Username)
		c.JSON(http.StatusForbidden, gin.H{
			"msg": "登陆失败,用户密码不正确,请重新填写",
		})
		return
	}
	captcha_Handler.RemoveKeyByClientId(clientIp, rdsStore)
	myLog.Infof("客户%s删除冗余验证码成功", loginForm.Username)
	userId, _ := db.GetUserId(loginForm.Username)
	userid, err := strconv.Atoi(userId)
	tokens, err := Middle.GenerateTokens(userid, loginForm.Username)
	myLog.Infof("客户%s登陆系统成功", loginForm.Username)
	c.JSON(200, gin.H{
		"msg":         "登陆成功",
		"accessToken": tokens,
	})
}
controller_Home---->需要权限访问的接口
package Controller

import (
	"github.com/gin-gonic/gin"
)

// Home 访问权限页
func Home(c *gin.Context) {
	// 逻辑
}
captcha_Handler---->验证码的逻辑
package captcha_Handler

import (
	"com.login.www/Redis_Handler"
	"com.login.www/model"
	"errors"
	"fmt"
	"github.com/donnie4w/go-logger/logger"
	"github.com/mojocn/base64Captcha"
	"image/color"
)

type RedisStore struct {
	Store     *model.Store
	rdsClient *Redis_Handler.RedisClient
	myLog     *logger.Logging
}

// NewRedisStore 是 RedisStore 的构造函数
func NewRedisStore(redisClient *Redis_Handler.RedisClient, myLog *logger.Logging) *RedisStore {
	return &RedisStore{rdsClient: redisClient, myLog: myLog}
}

func (r RedisStore) Set(id string, value string) error {
	err := r.rdsClient.SetKV(id, value)
	if err != nil {
		return errors.New("保存验证码到redis失败了")
	}
	return nil
}

func (r RedisStore) Get(id string, clear bool) string {
	val, err := r.rdsClient.GetKV(id)
	if err != nil {
		return err.Error()
	}
	if clear {
		//clear为true,验证通过,删除这个验证码
		err := r.rdsClient.DelKV(id)
		if err != nil {
			err := errors.New("移除通过的验证码失败")
			return err.Error()
		}
	}
	return val
}

func (r RedisStore) Verify(id, answer string, clear bool) bool {
	v := r.Get(id, false)
	if v == answer {
		if clear { // 只有在验证通过且需要清除时,才删除
			_ = r.rdsClient.DelKV(id)
		}
		return true
	}
	return false
}

// GenerateCaptcha 生成验证码
func GenerateCaptcha(maxHeight, maxWidth, noiseCount, showLineOptions int, bg color.RGBA, fontsStorage base64Captcha.FontsStorage, fonts []string, clientId string, store *RedisStore, myLog *logger.Logging) (string, string, string, error) {
	err := store.rdsClient.DelRepeatKey(clientId)
	if err != nil {
		myLog.Errorf("客户端%s删除冗余验证码失败: %s", clientId, err)
		return "", "", "", fmt.Errorf("删除冗余验证码失败: %w", err)
	}
	driver := base64Captcha.NewDriverMath(maxHeight, maxWidth, noiseCount, showLineOptions, &bg, fontsStorage, fonts)
	captcha := base64Captcha.NewCaptcha(driver, store)

	captchaID, img, answer, err := captcha.Generate()
	if err != nil {
		myLog.Infof("客户端%s生成验证码并存入redis失败,error:%s:", clientId, err)
	}
	myLog.Infof("客户端%s生成验证码并存入redis成功:", clientId)

	// 绑定用户IP地址和验证码ID
	err = store.rdsClient.SetKV(clientId, captchaID)
	if err != nil {
		myLog.Errorf("客户端%s生成验证码失败: %s", clientId, err)
		return "", "", "", fmt.Errorf("生成验证码失败: %w", err)
	}
	myLog.Infof("客户端%s生成验证码成功", clientId)

	return captchaID, img, answer, nil
}

// GetCodeFromClientIp 根据用户名获取验证码ID
func GetCodeFromClientIp(clientId string, store *RedisStore) (captchaId string, err error) {
	captchaId, err = store.rdsClient.GetKV(clientId)
	if err != nil {
		return "", err
	}
	return captchaId, nil
}

func RemoveKeyByClientId(clientIp string, store *RedisStore) {
	_ = store.rdsClient.DelKV(clientIp)
}
model_LoginForm---->登陆表单
package model

// LoginForm 登陆表单
type LoginForm struct {
	Username string `form:"username" binding:"required" json:"username"`
	Password string `form:"password" binding:"required" json:"password"`
	Code     string `form:"code" binding:"required" json:"code"`
}
model_RedisStore---->实现base64包接口
package model

// Store 实现验证码接口
type Store interface {
	//Set 验证码存入验证码池的方法,id为键,验证码为值
	Set(id string, value string) error

	//Get 获取验证码的方法
	Get(id string, clear bool) string

	//Verify 校验验证码的方法
	Verify(id, answer string, clear bool) bool
}
model_User---->用户模型
package model

import "gorm.io/gorm"

type User struct {
	gorm.Model
	Userid   int    `json:"userid,omitempty" yaml:"userid" gorm:"userid"`
	Username string `json:"username,omitempty" yaml:"username" gorm:"username"`
	Password string `json:"password,omitempty" yaml:"password" gorm:"password"`
}
main.go
package main

import (
	"com.login.www/Controller"
	"com.login.www/Db_Handler"
	"com.login.www/Log_Handler"
	"com.login.www/Middle"
	"com.login.www/Redis_Handler"
	tools "com.login.www/Tools"
	"com.login.www/captcha_Handler"
	"com.login.www/constants"
	"context"
	"github.com/donnie4w/go-logger/logger"
	"github.com/gin-gonic/gin"
)

var (
	db       *Db_Handler.DbClient
	rdb      *Redis_Handler.RedisClient
	ctx      = context.Background()
	rdsStore *captcha_Handler.RedisStore
	myLog    *logger.Logging
)

func init() {
	// 初始化日志输出
	myLog = Log_Handler.InitLogger(tools.CreateLogPath())
	// 初始化 Db 客户端
	db = Db_Handler.NewDbClient(constants.Dsn, myLog)
	// 初始化 Redis 客户端
	rdb = Redis_Handler.NewRedisClient(constants.RedisCfg, ctx, myLog)
	// 初始化RedisStore
	rdsStore = captcha_Handler.NewRedisStore(rdb, myLog)
}

func main() {
	r := gin.Default()
	r.Use(Middle.Cors())
	myLog.Info("开放跨域请求")
	r.GET("/code", func(c *gin.Context) {
		Controller.Code(c, constants.MaxHeight, constants.MaxWidth, constants.NoiseCount, int(constants.ShowLineOptions), constants.Bg, constants.FontsStorage, constants.Fonts, rdsStore, myLog)
	})
	r.POST("/login", func(c *gin.Context) {
		Controller.Login(c, db, rdsStore, myLog)
	})
	r.Use(Middle.TokenAuthMiddleware())
	r.POST("/home", Controller.Home)

	myLog.Infof("应用程序在8001端口启动中")
	if err := r.Run("192.168.40.1:8001").Error(); err != "" {
		myLog.Errorf("应用启动失败:%s", err)
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值