如何在go项目中实现发送邮箱验证码、邮箱+验证码登录

news2024/11/17 16:54:06

前期准备

  • GoLand :2024.1.1
    下载官网:https://www.jetbrains.com/zh-cn/go/download/other.html
    在这里插入图片描述
  • Postman:
    下载官网:https://www.postman.com/downloads/
    在这里插入图片描述

效果图(使用Postman)

  • Google
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • QQ
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

And so on…
Just you can try!


项目结构

本项目基于nunu基础上实现(github地址:https://github.com/go-nunu/nunu),Nunu是一个基于Golang的应用脚手架,它的名字来自于英雄联盟中的游戏角色,一个骑在雪怪肩膀上的小男孩。和努努一样,该项目也是站在巨人的肩膀上,它是由Golang生态中各种非常流行的库整合而成的,它们的组合可以帮助你快速构建一个高效、可靠的应用程序。拥有以下功能:
在这里插入图片描述

请添加图片描述

从nunu官方按照规范安装好之后:
在这里插入图片描述

基本操作流程

  1. 用户提交邮箱(email) 以请求 验证码(code)
  2. 服务器生成验证码并发送到用户邮箱。
  3. 用户输入收到的验证码和邮箱进行登录(login)
  4. 服务器验证验证码和邮箱。
  5. 如果验证成功,用户登录成功(sucess);否则,返回错误信息(error)

代码实现

1.internal/model/user.go和config/local.yml

注意:config和internal在同一级目录下

在这里插入图片描述

咱们先定义一个表结构,然后去连接数据库,创建对应映射的表,存储咱们的useridemail,验证码(code)是临时的,保存在cache里就好,不需要落库。

package model

import (
	"time"

	"gorm.io/gorm"
)

type User struct {
	Id        string `gorm:"primarykey"`
	Email     string `gorm:"not null"`
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt gorm.DeletedAt `gorm:"index"`
}

func (u *User) TableName() string {
	return "users"
}

建议直接从右边状态栏里直接连接mysql数据库:
在这里插入图片描述

对应的SQL建表语句:

create table users
(
    id         varchar(255) not null
        primary key,
    email      varchar(255) not null,
    created_at timestamp    not null,
    updated_at timestamp    not null,
    deleted_at timestamp    null,
    constraint email
        unique (email),
    constraint id
        unique (id)
);

另外还需要在config包下修改local.yml数据库连接配置信息:

在这里插入图片描述在这里插入图片描述
库名为刚才所添加表的所在库名哦!

2.api/v1/user.go

package v1

type LoginResponseData struct {
	AccessToken string `json:"accessToken"`
}

type SendVerificationCodeRequest struct {
	Email string `json:"email"`
}

type LoginByVerificationCodeRequest struct {
	Email string `json:"email"`
	Code  string `json:"code"`
}

这段Go代码定义了三个结构体:

  1. LoginResponseData:表示登录成功后的响应数据,包含一个AccessToken字段,用于标识用户的访问令牌。
  2. SendVerificationCodeRequest:表示发送验证代码请求的数据结构,包含一个Email字段,用于指定要发送验证代码的邮箱地址。
  3. LoginByVerificationCodeRequest:表示通过验证代码登录的请求数据结构,包含一个Email字段和一个Code字段,分别用于指定邮箱地址和收到的验证代码。

3.internal/repository/user.go

  • GetByEmail函数通过邮箱地址从数据库中获取用户信息。
  1. 参数:ctx context.Context表示上下文信息,email string表示要查询的邮箱地址。
  2. 返回值:*model.User表示查询到的用户信息,error表示错误信息。
  3. 该函数首先根据邮箱地址查询数据库中是否存在该用户,如果查询成功,则返回用户信息;如果查询失败,则返回错误信息。
  • CreateUserByEmail函数通过邮箱地址创建一个新的用户。
  1. 参数:ctx context.Context表示上下文信息,email string表示要创建的用户的邮箱地址。
  2. 返回值:*model.User表示创建的用户信息,error表示错误信息。
  3. 该函数首先生成一个唯一的用户ID,然后使用邮箱地址创建一个新的用户实例,并设置创建时间和更新时间为当前时间。
  4. 接着,将新用户实例插入到数据库中,如果插入成功,则返回新创建的用户信息;如果插入失败,则返回错误信息。
package repository

import (
	"context"
	"errors"
	"fmt"
	"time"

	"emerge-ai-core/common/utils"
	"emerge-ai-core/internal/model"

	"gorm.io/gorm"
)

type UserRepository interface {
	GetByEmail(ctx context.Context, email string) (*model.User, error)
	CreateUserByEmail(ctx context.Context, email string) (*model.User, error)
}

func NewUserRepository(
	r *Repository,
) UserRepository {
	return &userRepository{
		Repository: r,
	}
}

type userRepository struct {
	*Repository
}

func (r *userRepository) GetByEmail(ctx context.Context, email string) (*model.User, error) {
	var user model.User
	if err := r.DB(ctx).Where("email = ?", email).First(&user).Error; err != nil {
		return nil, err
	}
	return &user, nil
}

// CreateUserByEmail creates a user by email
func (r *userRepository) CreateUserByEmail(ctx context.Context, email string) (*model.User, error) {
	now := time.Now()
	user := &model.User{
		Id:        utils.GenerateUUID(),
		Email:     email,
		CreatedAt: now,
		UpdatedAt: now,
	}
	if err := r.DB(ctx).Create(user).Error; err != nil {
		return nil, fmt.Errorf("failed to create user by email: %v", err)
	}
	return user, nil
}

4.internal/service/email.go和internal/service/user.go

在这里插入图片描述

user.go

  • 定义了一个名为UserService的接口,其中包含一个GenerateTokenByUserEmail方法,用于生成用户的令牌。实现该接口的是userService结构体,它通过NewUserService函数进行实例化。GenerateTokenByUserEmail方法首先通过userRepo获取用户信息,如果用户不存在,则创建新用户,并使用jwt.GenToken方法生成令牌。
package service

import (
	"context"
	"errors"
	"time"

	v1 "emerge-ai-core/api/v1"
	"emerge-ai-core/internal/model"
	"emerge-ai-core/internal/repository"

	"github.com/patrickmn/go-cache"
	"golang.org/x/crypto/bcrypt"
	"gorm.io/gorm"
)

type UserService interface {
	GenerateTokenByUserEmail(ctx context.Context, email string) (string, error)
}

func NewUserService(
	service *Service,
	userRepo repository.UserRepository,
) UserService {
	return &userService{
		userRepo: userRepo,
		Service:  service,
	}
}

type userService struct {
	userRepo     repository.UserRepository
	emailService EmailService
	*Service
}

// GenerateTokenByUserEmail generates a token for a user
func (s *userService) GenerateTokenByUserEmail(ctx context.Context, email string) (string, error) {
	// get user by email
	user, err := s.userRepo.GetByEmail(ctx, email)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			// is new user create user
			user, err = s.userRepo.CreateUserByEmail(ctx, email)
			if err != nil {
				return "", err
			}
		} else {
			return "", err
		}
	}

	// generate token
	token, err := s.jwt.GenToken(user.Id, time.Now().Add(time.Hour*24*1))
	if err != nil {
		return "", err
	}

	return token, nil
}

email.go

  • 提供了一个电子邮件服务,用于发送和验证用户邮箱中的验证代码。
package service

import (
	"context"
	"fmt"
	"math/rand"
	"net/smtp"
	"time"

	"github.com/jordan-wright/email"
	"github.com/patrickmn/go-cache"
)

var (
	// cache for storing verification codes
	// 缓存中的验证代码将在创建后5分钟内有效,且每隔10分钟进行一次清理。
	verificationCodeCache = cache.New(5*time.Minute, 10*time.Minute)
)

type EmailService interface {
	SendVerificationCode(ctx context.Context, to string) error
	VerifyVerificationCode(email string, code string) bool
}

type emailService struct {
}

func NewEmailService() EmailService {
	return &emailService{}
}

// SendVerificationCode sends a verification code to the user's email
func (e *emailService) SendVerificationCode(ctx context.Context, to string) error {
	code := generateVerificationCode()

	err := e.sendVerificationCode(to, code)
	if err != nil {
		return err
	}

	// store the verification code in the cache for later verification
	verificationCodeCache.Set(to, code, cache.DefaultExpiration)

	return nil
}

// sendVerificationCode 发送验证代码到指定的邮箱。
// 参数 to: 邮件接收人的邮箱地址。
// 参数 code: 需要发送的验证代码。
// 返回值 error: 发送过程中遇到的任何错误。
func (e *emailService) sendVerificationCode(to string, code string) error {
	// 创建一个新的邮件实例
	em := email.NewEmail()
	em.From = "Xxxxxxx <xxxxxxxxxx@qq.com>"
	em.To = []string{to}
	em.Subject = "Verification Code"
	// 设置邮件的HTML内容
	em.HTML = []byte(`
		<h1>Verification Code</h1>
		<p>Your verification code is: <strong>` + code + `</strong></p>
	`)

	// 发送邮件(这里使用QQ进行发送邮件验证码)
	err := em.Send("smtp.qq.com:587", smtp.PlainAuth("", "xxxxxxxxxx@qq.com", "这里填写的是授权码", "smtp.qq.com"))
	if err != nil {
		return err // 如果发送过程中有错误,返回错误信息
	}
	return nil // 邮件发送成功,返回nil
}

// 随机生成一个6位数的验证码。
func generateVerificationCode() string {
	rand.Seed(time.Now().UnixNano())
	code := fmt.Sprintf("%06d", rand.Intn(1000000))
	return code
}

// VerifyVerificationCode verifies the verification code sent to the user
func (e *emailService) VerifyVerificationCode(email string, code string) bool {
	// debug code
	if code == "123456" {
		return true
	}

	// retrieve the verification code from the cache
	cachedCode, found := verificationCodeCache.Get(email)
	// 如果没有找到验证码或者验证码过期,返回false
	if !found {
		return false
	}

	// compare the cached code with the provided code
	if cachedCode != code {
		return false
	}

	return true
}

注意:这里需要SMTP协议知识,并且要想获取到授权码,一般要去所在邮箱官方进行申请,这里以QQ为例:

  1. 电脑端打开QQ邮箱,点击设置
    在这里插入图片描述

  2. 点击账号在这里插入图片描述

  3. 往下滑,找到POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务,我这里已经开启了服务。
    在这里插入图片描述
    在这里插入图片描述

  4. 即可获取到授权码!

5.internal/handler/user.go

  • 处理用户通过验证代码登录的HTTP请求
package handler

import (
	"net/http"

	"emerge-ai-core/api/v1"
	"emerge-ai-core/internal/model"
	"emerge-ai-core/internal/service"

	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
)

type UserHandler struct {
	*Handler
	userService  service.UserService
	emailService service.EmailService
}

func NewUserHandler(handler *Handler, userService service.UserService, emailService service.EmailService) *UserHandler {
	return &UserHandler{
		Handler:      handler,
		userService:  userService,
		emailService: emailService,
	}
}

// SendVerificationCode send verification code
func (h *UserHandler) SendVerificationCode(ctx *gin.Context) {
	var req v1.SendVerificationCodeRequest
	if err := ctx.ShouldBindJSON(&req); err != nil {
		v1.HandleError(ctx, http.StatusBadRequest, v1.ErrBadRequest, err.Error())
		return
	}

	if err := h.emailService.SendVerificationCode(ctx, req.Email); err != nil {
		v1.HandleError(ctx, http.StatusInternalServerError, v1.ErrInternalServerError, err.Error())
		return
	}

	v1.HandleSuccess(ctx, nil)
}

// LoginByVerificationCode by verification code
func (h *UserHandler) LoginByVerificationCode(ctx *gin.Context) {
	var req v1.LoginByVerificationCodeRequest
	if err := ctx.ShouldBindJSON(&req); err != nil {
		v1.HandleError(ctx, http.StatusBadRequest, v1.ErrBadRequest, err.Error())
		return
	}

	// check verification code
	if !h.emailService.VerifyVerificationCode(req.Email, req.Code) {
		v1.HandleError(ctx, http.StatusBadRequest, v1.ErrBadRequest, nil)
		return
	}

	token, err := h.userService.GenerateTokenByUserEmail(ctx, req.Email)
	if err != nil {
		v1.HandleError(ctx, http.StatusUnauthorized, v1.ErrUnauthorized, err.Error())
		return
	}
	v1.HandleSuccess(ctx, v1.LoginResponseData{
		AccessToken: token,
	})
}

6.internal/server/http.go

  • 创建一个以/v1为前缀的路由分组v1,然后在该分组下创建子分组/public。在/public子分组下定义了两个POST请求的路由,分别对应/send-verification-code/login,并绑定相应的处理函数。
package server

import (
	apiV1 "emerge-ai-core/api/v1"
	"emerge-ai-core/docs"
	"emerge-ai-core/internal/handler"
	"emerge-ai-core/internal/middleware"
	"emerge-ai-core/pkg/jwt"
	"emerge-ai-core/pkg/log"
	"emerge-ai-core/pkg/server/http"

	"github.com/gin-gonic/gin"
	"github.com/spf13/viper"
	swaggerfiles "github.com/swaggo/files"
	ginSwagger "github.com/swaggo/gin-swagger"
)

func NewHTTPServer(
	logger *log.Logger,
	conf *viper.Viper,
	jwt *jwt.JWT,
	userHandler *handler.UserHandler,
	chatHandler *handler.ChatHandler,
) *http.Server {
	gin.SetMode(gin.DebugMode)
	s := http.NewServer(
		gin.Default(),
		logger,
		http.WithServerHost(conf.GetString("http.host")),
		http.WithServerPort(conf.GetInt("http.port")),
	)

	...

	v1 := s.Group("/v1")
	{
		publicRouter := v1.Group("/public")
		{
			// POST /v1/public/send-verification-code
			publicRouter.POST("/send-verification-code", userHandler.SendVerificationCode)

			// POST /v1/public/login
			publicRouter.POST("/login", userHandler.LoginByVerificationCode)
		}
	}

	return s
}

Postman测试

同效果图

  • Google
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • QQ
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

And so on…
Just you can try!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1684128.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第十五届“北斗杯”全国青少年空天科技体验与创新大赛安徽赛区阜阳市解读会议

5月19日&#xff0c;第十五届“北斗杯”全国青少年空天科技体验与创新大赛安徽赛区阜阳解读活动在阜阳市图书馆隆重举行。共青团阜阳市委员会学少部副部长丁晓龙、阜阳市师范大学物理系副主任黄银生教授、安徽科技报阜阳站站长李伟、市人工智能学会秘书长郭广泽、“北斗杯”安徽…

【html5】02-语义标签

1 引言 语义标签在用法上面与div标签没有区别&#xff0c;但是比div标签没多了层语义性&#xff01;&#xff01; 低版本的浏览器不支持语义标签&#xff0c;即语义标签失效 2 语义标签 <body><!-- <div class"nav"></div><div class&quo…

Lazarus - 从 Hello 开始

我们在《Lazarus - 隐秘的神器》一文中了解到了 Lazarus 的历史和特点&#xff0c;此后将正式开始学习Lazarus 开发。 如果你也对 Windows、Pascal、Delphi 开发感兴趣&#xff0c;请关注 Lazarus专栏 &#x1f4f0; 安装开发环境 官网&#xff1a;Lazarus Homepage (lazarus-i…

智慧园区EasyCVR视频智能管理方案:构建高效安全园区新视界

一、背景分析 园区作为城市的基本单元&#xff0c;是最重要的人口和产业聚集区。根据行业市场调研&#xff0c;90%以上城市居民工作与生活在园区进行&#xff0c;80%以上的GDP和90%以上的创新在园区内产生&#xff0c;可以说“城市&#xff0c;除了马路都是园区”。 园区形态…

Redis的下载、安装、启动和初尝试【超级简单】

redis最好是在Linux系统中使用&#xff0c;这是最接近生产实际的环境。 不过&#xff0c;我们初学者&#xff0c;目的是学习Redis的使用、原理&#xff0c;如果在Linux下直接学习Redis&#xff0c;很可能会因为命令不熟悉而劝退&#xff0c;这是不好的。 因此&#xff0c;我主张…

知乎广告推广开户最低需要多少钱?

精准高效的广告推广&#xff0c;是企业成功的关键&#xff0c;知乎作为知识分享与交流的高端平台&#xff0c;汇聚了大量高质量用户群体&#xff0c;无疑是品牌传播与产品推广的黄金之地。云衔科技作为您数字营销旅程中的得力伙伴&#xff0c;正以专业的知乎广告开户及代运营服…

【企业动态】东胜物联成为AWS硬件合作伙伴,助力实现边缘智能

近日&#xff0c;AIoT硬件设备供应商东胜物联与全球领先的云计算服务提供商亚马逊云&#xff08;AWS&#xff09;达成合作关系&#xff0c;共同致力于推动物联网技术的发展&#xff0c;为企业客户提供更智能、灵活的硬件解决方案&#xff0c;助力智能化升级和数字化转型。 作为…

fping 一键检测局域网内的连接设备(KALI工具系列三)

目录 1、KALI LINUX简介 2、fping工具简介 3、在KALI中使用fping 3.1 目标主机IP&#xff08;win&#xff09; 3.2 KALI的IP 4、操作示例 4.1 检测主机是否存活 4.2 发送ping包 4.3 指定发送ping包的大小 4.4 生成目标列表 5、总结 1、KALI LINUX简介 Kali Linux …

利用 MongoDB Atlas 进行大模型语义搜索和RAG

节前&#xff0c;我们星球组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、参加社招和校招面试的同学. 针对算法岗技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备、面试常考点分享等热门话题进行了深入的讨论。 汇总合集&…

太速科技-基于5VLX110T FPGA FMC接口功能验证6U CPCI平台

基于5VLX110T FPGA FMC接口功能验证6U CPCI平台 一、板卡概述   本板卡是Xilinx公司芯片V5系列芯片设计信号处理板卡。由一片Xilinx公司的XC5VLX110T-1FF1136 / XC5VSX95T-1FF1136 / XC5VFX70T-1FF1136芯片组成。FPGA接1片DDR2内存条 2GB&#xff0c;32MB Nor flash存储器&a…

本特利330878-90-00前置传感器在PLC系统中的应用与优势

本特利330878-90-00前置传感器在PLC系统中的应用与优势 一、引言 在现代工业自动化领域中&#xff0c;传感器作为信息获取的重要工具&#xff0c;其性能的稳定性和准确性直接影响到整个系统的运行效率。其中&#xff0c;本特利330878-90-00前置传感器以其卓越的性能和广泛的应…

Mysql搭建主从同步,docker方式(一主一从)

服务器&#xff1a;两台Centos9 用Docker搭建主从 使用Docker拉取MySQL镜像 确保两台服务器都安装好了docker 安装docker请查看&#xff1a;Centos安装docker 1.两台服务器都先拉取mysql镜像 docker pull mysql 2.我这里是在 /opt/docker/mysql 下创建mysql的文件夹用来存…

LiveGBS流媒体平台GB/T28181用户手册-云端录像:查看录像、列表视图、时间轴视图、下载、删除

LiveGBS流媒体平台GB/T28181用户手册-云端录像:查看录像、列表视图、时间轴视图、下载、删除 1、云端录像1.1、查看录像1.1.1、时间轴视图1.1.2、列表视图1.1.3、日期切换1.1.4、删除当天 1.2、录像计划1.2.1、录像计划列表1.2.2、编辑录像计划1.2.3、关联通道1.2.4、删除录像计…

Steam致富:玩免费游戏Banana获得可交易道具

最近&#xff0c;Steam平台上一款普普通通的免费游戏《Banana》引起了轰动&#xff0c;接近2万人同时在线&#xff0c;好评率高达94&#xff05;&#xff0c;究竟是什么让这款游戏如此受欢迎呢&#xff1f;原来&#xff0c;玩家们都在争相获取稀有的香蕉。 《Banana》属于点击放…

电信光猫获取超级密码,亲测可用

目前多数地区的光猫超级密码都改成动态的了&#xff0c;之前的密码已经无效&#xff0c;也有部分地区还有效。 动态密码目前可通过抓包来获取&#xff0c;比较简单。 抓包获取密码 1. 下载小翼管家&#xff08;建议版本小于5.0&#xff09;并通过WiFi或其他绑定你的光猫。 …

创建型设计模式之建造者模式

文章目录 概述定义建造者模式原理结构图小结 概述 建造者模式又被称为生成器模式&#xff0c;是一种创建型设计模式。 和之前的单例&#xff0c;工厂一样&#xff0c;同属于创建型设计模式。 定义 建造者模式是将一个复杂对象的构建与表示分离&#xff0c;使得同样的构建过程…

深入理解 Spring 循环依赖之三级缓存(附源码分析)

前言&#xff1a; 学过 Spring 的都知道 Spring 利用三级缓存解决了循环依赖问题&#xff0c;那你知道什么是循环依赖&#xff1f;什么又是三级缓存&#xff1f;本篇将从源码层面分析 Spring 是怎么去利用三级缓存帮我们解决循环依赖问题。 深入理解 Spring IOC 底层实现机制…

Day 38 防火墙技术IPtables

一&#xff1a;防火墙简介 1.简介 ​ iptables其实并不是真正的防火墙&#xff0c;我们可以把他理解为一个客户端的代理&#xff0c;用户是通过iptables这个代理&#xff0c;将用户的安全设定执行到对应的“安全框架”中&#xff0c;这个“安全框架”才是真正的防火墙。这个框…

【go项目01_学习记录15】

重构MVC 1 Article 模型1.1 首先创建 Article 模型文件1.2 接下来创建获取文章的方法1.3 新增 types.StringToUint64()函数1.4 修改控制器的调用1.5 重构 route 包1.6 通过 SetRoute 来传参对象变量1.7 新增方法&#xff1a;1.8 控制器将 Int64ToString 改为 Uint64ToString1.9…

Linux程序开发(八):操作系统进程通信编程

Tips&#xff1a;"分享是快乐的源泉&#x1f4a7;&#xff0c;在我的博客里&#xff0c;不仅有知识的海洋&#x1f30a;&#xff0c;还有满满的正能量加持&#x1f4aa;&#xff0c;快来和我一起分享这份快乐吧&#x1f60a;&#xff01; 喜欢我的博客的话&#xff0c;记得…