如何在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 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!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java 8--Lambda表达式,Stream流

目录 Lambda表达式 Lambda表达式的由来 Lambda表达式简介 Lambda表达式的结构 Stream流 什么是Stream流&#xff1f; 什么是流呢&#xff1f; Stream流操作 中间操作 终端操作 Lambda表达式 Lambda表达式的由来 Java是面向对象语言&#xff0c;除了部分简单数据类型…

SpringBoot——整合MyBatis

目录 MyBatis 项目总结 1、创建SQL表 2、新建一个SpringBoot项目 3、pom.xml添加依赖 4、application.properties配置文件 5、User实体类 6、UserMapper接口 7、UserMapper.xml映射文件 8、UserController控制器 9、SpringBootMyBatisApplication启动类 10、使用Po…

关于如何创建一个可配置的 SpringBoot Web 项目的全局异常处理

前情概要 这个问题其实困扰了我一周时间&#xff0c;一周都在 Google 上旅游&#xff0c;我要如何动态的设置 RestControllerAdvice 里面的 basePackages 以及 baseClasses 的值呢&#xff1f;经过一周的时间寻求无果之后打算决定放弃的我终于找到了一些关键的线索。 当然在此…

反射的基本知识

基本概念 反射是java在运行过程中的自我观察能力&#xff0c;通过class constructor field method 四个方法来获取一个类的各个组成部分。 反射是在运行状态中对于任意一个类&#xff0c;都能知道这个类的所有属性和方法&#xff1b;对于任意一个对象都能调用它的任意一个方法…

《MySQL是怎样运行的》快速查询秘籍——B+树索引

一.引出索引 前面一章我们说出了数据页的结构&#xff0c;但是如果我们要查找某一条记录的话&#xff0c;怎么办呢&#xff1f; 我们前面知道页与页之间是一个双向链表实现的&#xff0c;我们要找的话&#xff0c;是不是要按照这个链表一个一个找下去&#xff0c;然后找到&am…

数据链路层简单介绍

mac地址&#xff08;物理地址&#xff09; mac地址和ip地址&#xff0c;目的都是为了区分网络上的不同设备的&#xff0c;在最开始的时候&#xff0c;mac地址和ip地址是两伙人&#xff0c;独立各自提出的&#xff0c;ip地址是4个字节&#xff08;早都不够用了&#xff09;&…

【个人商业画布】你有思考过把自己当成一家公司来经营吗?

商业模式画布(Business Model Canvas)&#xff0c;是亚历山大奥斯特瓦德在《商业模式新生代》中提出的一种用于描述商业模式、可视化商业模式、评估商业模式以及改变商业模式的通用语言。它由9个模块构成&#xff0c;帮助创业者理清为“细分客户提供独有价值”&#xff0c;从而…

PersonalLLM——探索LLM是否能根据五大人格特质重新塑造一个新的角色?

1.概述 近年来&#xff0c;大型语言模型&#xff08;LLMs&#xff09;&#xff0c;例如ChatGPT&#xff0c;致力于构建能够辅助人类的个性化人工智能代理&#xff0c;这些代理以进行类似人类的对话为重点。在学术领域&#xff0c;尤其是社会科学中&#xff0c;一些研究报告已经…

溪谷联运SDK功能全面解析

近期&#xff0c;备受用户关注的手游联运10.0.0版本上线了&#xff0c;不少用户也选择了版本更新&#xff0c;其中也再次迎来了SDK的更新。溪谷软件和大家一起盘点一下溪谷SDK的功能都有哪些吧。 一、溪谷SDK具有完整的运营功能和高度扩展性 1.登录&#xff1a;登录是SDK最基础…

简述MyBatis中#{}引用和${}引用的区别

各位大佬光临寒舍&#xff0c;希望各位能赏脸给个三连&#xff0c;谢谢各位大佬了&#xff01;&#xff01;&#xff01; 目录 1.有无预编译 优点 缺点 2.SQL执行的快慢 3.能否被SQL注入 4.参数输入方式 5.总结 1.有无预编译 #{}是有预编译的而${}是没有预编译的&…

OceanBase集群如何进行OCP的替换

有OceanBase社区版的用户提出替换 OCP 管控平台的需求。举例来说&#xff0c;之前的OCP平台采用单节点&#xff0c;然而随着OceanBase集群的陆续上线和数量的不断增多&#xff0c;担心单节点的OCP可能面临故障风险&#xff0c;而丧失对OceanBase集群的管控能力。另此外&#xf…

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

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

以Linux为例了解线程

我最近开了几个专栏&#xff0c;诚信互三&#xff01; > |||《算法专栏》&#xff1a;&#xff1a;刷题教程来自网站《代码随想录》。||| > |||《C专栏》&#xff1a;&#xff1a;记录我学习C的经历&#xff0c;看完你一定会有收获。||| > |||《Linux专栏》&#xff1…

IO系列(八) -浅析NIO工作原理

一、简介 现在使用 NIO 的场景越来越多&#xff0c;很多网上的技术框架或多或少的使用 NIO 技术&#xff0c;譬如 Tomcat、Jetty、Netty&#xff0c;学习和掌握 NIO 技术已经不是一个 Java 攻城狮的加分技能&#xff0c;而是一个必备技能。 那什么是 NIO 呢&#xff1f; NIO…

第06章 数据加载、存储与文件格式

以下内容参考自https://github.com/iamseancheney/python_for_data_analysis_2nd_chinese_version/blob/master/%E7%AC%AC05%E7%AB%A0%20pandas%E5%85%A5%E9%97%A8.md 《利用Python进行数据分析第2版》 用以学习和记录。 输入输出通常可以划分为几个大类&#xff1a;读取文本文…

深海奥秘:鳐鱼肽的肌肤之旅

深海&#xff0c;一个神秘又充满生命力的世界&#xff0c;总是带给我们无尽的惊喜。鳐鱼&#xff0c;又被称为“魔鬼鱼”&#xff0c;它的皮肤中含有一种特殊的肽&#xff0c;这种肽不仅分子量适中&#xff0c;易于人体吸收&#xff0c;还具有极高的消化率和生物利用度。来自北…

科技引领乡村振兴新潮流:运用现代信息技术手段,提升农业生产和乡村管理效率,打造智慧化、现代化的美丽乡村

一、引言 随着科技的不断进步&#xff0c;现代信息技术已经渗透到社会的各个领域&#xff0c;成为推动社会发展的重要力量。在乡村振兴战略的背景下&#xff0c;科技的力量同样不容忽视。本文旨在探讨如何运用现代信息技术手段&#xff0c;提升农业生产和乡村管理效率&#xf…

Android软件渲染流程

Android软件渲染流程 一.渲染流程1.VSync信号的监听2.VSync信号触发绘制 二.渲染原理1.画布的获取1.1 渲染缓存的初始化1.2 graphics::Canvas的创建1.3 graphics::Canvas与渲染缓存的绑定1.3.1 SkBitmap的初始化1.3.2 SkiaCanvas与SkBitmap的绑定1.3.3 SkCanvas的创建 2.矩形的…

Transformer系列专题(二)——multi-headed多头注意力机制

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、什么是multi-headed&#xff08;多头注意力机制&#xff09;二、multi-headed三、multi-headed结果四、堆叠多层总结 前言 在实践中&#xff0c;当给定相同…

【数据库基础】基本认识数据库--入门引导

文章目录 什么是数据库&#xff1f;主流数据库基本使用安装MySQL连接服务器服务器、数据库、表关系使用案例数据逻辑存储 MySQL架构SQL语句分类什么叫存储引擎 什么是数据库&#xff1f; 数据库是指在磁盘和内存中存储特定结构组织的数据。数据库通常用于存储于某个系统、组织或…