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

前期准备

  • 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 modelimport ("time""gorm.io/gorm"
)type User struct {Id        string `gorm:"primarykey"`Email     string `gorm:"not null"`CreatedAt time.TimeUpdatedAt time.TimeDeletedAt gorm.DeletedAt `gorm:"index"`
}func (u *User) TableName() string {return "users"
}

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

对应的SQL建表语句:

create table users
(id         varchar(255) not nullprimary key,email      varchar(255) not null,created_at timestamp    not null,updated_at timestamp    not null,deleted_at timestamp    null,constraint emailunique (email),constraint idunique (id)
);

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

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

2.api/v1/user.go

package v1type 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 repositoryimport ("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.Userif 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 serviceimport ("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.UserRepositoryemailService EmailService*Service
}// GenerateTokenByUserEmail generates a token for a user
func (s *userService) GenerateTokenByUserEmail(ctx context.Context, email string) (string, error) {// get user by emailuser, err := s.userRepo.GetByEmail(ctx, email)if err != nil {if errors.Is(err, gorm.ErrRecordNotFound) {// is new user create useruser, err = s.userRepo.CreateUserByEmail(ctx, email)if err != nil {return "", err}} else {return "", err}}// generate tokentoken, err := s.jwt.GenToken(user.Id, time.Now().Add(time.Hour*24*1))if err != nil {return "", err}return token, nil
}

email.go

  • 提供了一个电子邮件服务,用于发送和验证用户邮箱中的验证代码。
package serviceimport ("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) errorVerifyVerificationCode(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 verificationverificationCodeCache.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 codeif code == "123456" {return true}// retrieve the verification code from the cachecachedCode, found := verificationCodeCache.Get(email)// 如果没有找到验证码或者验证码过期,返回falseif !found {return false}// compare the cached code with the provided codeif 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 handlerimport ("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 {*HandleruserService  service.UserServiceemailService 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.SendVerificationCodeRequestif 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.LoginByVerificationCodeRequestif err := ctx.ShouldBindJSON(&req); err != nil {v1.HandleError(ctx, http.StatusBadRequest, v1.ErrBadRequest, err.Error())return}// check verification codeif !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 serverimport (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-codepublicRouter.POST("/send-verification-code", userHandler.SendVerificationCode)// POST /v1/public/loginpublicRouter.POST("/login", userHandler.LoginByVerificationCode)}}return s
}

Postman测试

同效果图

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

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

And so on…
Just you can try!

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

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

相关文章

创建vue工程、Vue项目的目录结构、Vue项目-启动、API风格

环境准备 介绍&#xff1a;create-vue是Vue官方提供的最新的脚手架工具&#xff0c;用于快速生成一个工程化的Vue项目create-vue提供如下功能&#xff1a; 统一的目录结构 本地调试 热部署 单元测试 集成打包依赖环境&#xff1a;NodeJS 安装NodeJS 一、 创建vue工程 npm 类…

自定义横向思维导图,横向组织架构图,横向树图。可以自定义节点颜色,样式,还可以导出为图片

最近公司设计要求根据目录结构&#xff0c;横向展示。所以做了一个横向的思维导图&#xff0c;横向的树结构&#xff0c;横向的组织架构图&#xff0c;可以自定义节点颜色&#xff0c;样式&#xff0c;还可以导出为图片 话不多说&#xff0c;直接上图片&#xff0c;这个就是一…

使用redis优化纯真IP库访问

每次请求都需要加载10m的纯真IP qqwry.dat 文件&#xff0c;自己测试不会发现问题&#xff0c;但如果访问量上去了&#xff0c;会影响每次请求的相应效率&#xff0c;并且会消耗一定的io读写&#xff0c;故打算优化 优化方案 每个IP区间之间不存在交集&#xff0c;每个查找只要…

【python】python省市水资源数据分析可视化(源码+数据)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

2024年失业率狂飙18.1%,史上最难就业季即将来临,该如何逆袭?_2024年失业潮

【2024年被称为最难就业年&#xff0c;1158万大学生面临难题】 距离2024年毕业季还剩不到4个月&#xff0c;毕业学员将面临空前严峻的就业压力&#xff01;具国家统 计局的数据显示&#xff0c;1-2月份&#xff0c;16至24岁年轻人的失业率飙到18.1%&#xff0c;也就是说&…

微服务:利用RestTemplate实现远程调用

打算系统学习一下微服务知识&#xff0c;从今天开始记录。 远程调用 调用order接口&#xff0c;查询。 由于实现还未封装用户信息&#xff0c;所以为null。 下面我们来使用远程调用用户服务的接口&#xff0c;然后封装一下用户信息返回即可。 流程图 配置类中注入RestTe…

文心一言 VS 讯飞星火 VS chatgpt (265)-- 算法导论20.1 4题

四、假设不使用一棵叠加的度为 u \sqrt{u} u ​ 的树&#xff0c;而是使用一棵叠加的度为 u 1 k u^{\frac{1}{k}} uk1​的树&#xff0c;这里 k 是大于 1 的常数&#xff0c;则这样的一棵树的高度是多少&#xff1f;又每个操作将需要多长时间&#xff1f;如果要写代码&#xf…

模板中的右值引用(万能引用)、引用折叠与完美转发

模板中的右值引用&#xff08;万能引用&#xff09;、引用折叠与完美转发 文章目录 模板中的右值引用&#xff08;万能引用&#xff09;、引用折叠与完美转发一、万能引用与引用折叠1. 模板中的右值引用2. 自动类型推导(auto)与万能引用3. 引用折叠与万能引用4. lambda表达式捕…

数据可视化第十天(爬虫爬取某瓣星际穿越电影评论,并且用词云图找出关键词)

开头提醒 本次爬取的是用户评论&#xff0c;只供学习使用&#xff0c;不会进行数据的传播。希望大家合法利用爬虫。 获得数据 #总程序 import requests from fake_useragent import UserAgent import timefuUserAgent()headers{User-Agent:fu.random }page_listrange(0,10) …

音视频入门基础:像素格式专题(3)——FFmpeg源码解析BMP格式图片的底层实现原理

音视频入门基础&#xff1a;像素格式专题系列文章&#xff1a; 音视频入门基础&#xff1a;像素格式专题&#xff08;1&#xff09;——RGB简介 音视频入门基础&#xff1a;像素格式专题&#xff08;2&#xff09;——不通过第三方库将RGB24格式视频转换为BMP格式图片 音视频…

人工智能+量子计算:飞跃现实边界还是科技幻想?

人工智能量子计算&#xff0c;这是一种可能改变世界的伙伴关系。 在科技的前沿&#xff0c;两大革命性技术——人工智能&#xff08;AI&#xff09;和量子计算——正站在合作的十字路口。人工智能&#xff0c;以其强大的数据分析能力和模式识别&#xff0c;正在改变着我们生活…

传感器通过Profinet转Modbus网关与PLC通讯在生产线的应用

Profinet转Modbus&#xff08;XD-MDPN100/300&#xff09;网关可视作一座桥梁&#xff0c;能够实现Profinet协议与Modbus协议相互转换&#xff0c;支持Modbus RTU主站/从站&#xff0c;并且Profinet转Modbus网关设备自带网口和串口&#xff0c;既可以实现协议的转换&#xff0c…

Mac虚拟机工具 CrossOver 24.0.0 Beta3 Mac中文版

CrossOver是一款在Mac上运行Windows应用程序的软件&#xff0c;无需安装虚拟机或重启计算机&#xff0c;简化了操作过程&#xff0c;提高了工作效率&#xff0c;为用户带来便捷体验。前往Mac青桔下载&#xff0c;享受前所未有的便利和高效。摘要由作者通过智能技术生成 CrossOv…

robosuite导入自定义机器人

目录 目的&#xff1a;案例一&#xff1a;成果展示具体步骤&#xff1a;URDF文件准备xml文件生成xml修改机器人构建 目的&#xff1a; 实现其他标准/非标准机器人的构建 案例一&#xff1a; 成果展示 添加机器人JAKA ZU 7 这个模型 具体步骤&#xff1a; URDF文件准备 从…

python-docx 在word中指定位置插入图片或表格

docx库add_picture()方法不支持对图片位置的设置 1、新建一个1行3列的表格&#xff0c;在中间的一列中插入图片 from docx import Document from docx.shared import Pt from docx.oxml.shared import OxmlElement from docx.enum.text import WD_ALIGN_PARAGRAPHdef add_cen…

Nacos 进阶篇---Nacos服务端怎么维护不健康的微服务实例 ?(七)

一、引言 在 Nacos 后台管理服务列表中&#xff0c;我们可以看到微服务列表&#xff0c;其中有一栏叫“健康实例数” &#xff08;如下图&#xff09;&#xff0c;表示对应的客户端实例信息是否可用状态。 那Nacos服务端是怎么感知客户端的状态是否可用呢 &#xff1f; 本章…

基于树的存储数据结构demo

一.简介 由于之前博主尝试Java重构redis&#xff0c;在redis中的的字典数据结构底层也是采用数组实现&#xff0c;字典中存在两个hash表&#xff0c;一个是用于存储数据&#xff0c;另一个被用于rehash扩容为前者两倍。但是我注意到了在redis的数据结构中&#xff0c;并没有像…

【MySQL】库的操作和表的操作

库的操作和表的操作 一、库的操作1、创建数据库(create)2、字符集和校验规则&#xff08;1&#xff09;查看系统默认字符集以及校验规则&#xff08;2&#xff09;查看数据库支持的字符集&#xff08;3&#xff09;查看数据库支持的字符集校验规则&#xff08;4&#xff09;校验…

存储+调优:存储-IP-SAN

存储调优&#xff1a;存储-IP-SAN 数据一致性问题 硬盘&#xff08;本地&#xff0c;远程同步rsync&#xff09; 存储设备&#xff08;网络&#xff09; 网络存储 不同接口的磁盘 1.速率 2.支持连接更多设备 3.支持热拔插 存储设备什么互联 千…