(三)登录和注册(handle_auto.go)

news2024/11/24 18:37:54

登录和注册(handle_auto.go)

在这里插入图片描述

文章目录

  • 登录和注册(handle_auto.go)
    • 一、所需要的结构体信息
    • 二、注册
    • 三、登录
    • 四、退出

一、所需要的结构体信息

type UserAuth struct{}

type LoginReq struct {
	Username string `json:"username" binding:"required"`
	Password string `json:"password" binding:"required"`
}

type RegisterReq struct {
	Username string `json:"username" binding:"required"`
	Password string `json:"password" binding:"required,min=4,max=20"`
	Code     string `json:"code" binding:"required"`
}

type LoginVO struct {
	model.UserInfo
	// 点赞 Set: 用于记录用户点赞过的文章, 评论
	ArticleLikeSet []string `json:"article_like_set"`
	CommentLikeSet []string `json:"comment_like_set"`
	Token          string   `json:"token"`
}

二、注册

因为原本的作者没有写注册的功能,我就自己写了一个简单的注册功能。

1.注册的路由

base.POST("/register", userAuthAPI.Register)    // 注册

2.验证码的功能函数

handler层的函数

  • 使用qq邮箱发送验证
  • 限制发送时间
  • 使用redis存储验证码,时间1分钟
func (*UserAuth) SendCode(c *gin.Context) {
	// 发送验证码限制于1分钟发一次
	elapsedTime := time.Since(lastEmailSent)
	remaingTime := time.Minute - elapsedTime
	if elapsedTime < time.Minute {
		c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("发送验证码过于频繁,请稍后再试,剩余时间:%d秒", int(remaingTime.Seconds()))})
		return
	}
	// 从请求参数中获取邮箱地址
	email := c.Query("email")
	if email == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "未提供邮箱地址"})
		return
	}
	// 发送邮件验证码
	err := SendEmail(email, c)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "发送邮件失败"})
		return
	}
	// 发送邮件成功,返回成功响应给前端
	lastEmailSent = time.Now()
	ReturnSuccess(c, "验证码发送成功")
}

// 发送邮件到指定邮箱
func SendEmail(email string, c *gin.Context) error {
	// 邮箱配置信息
	config := gomail.NewDialer("smtp.qq.com", 465, "xxx@qq.com", "自己的授权码")
	//config.TLSConfig.ServerName = "smtp.qq.com"
	//config.TLSConfig.InsecureSkipVerify = false
	// 创建邮件内容
	message := gomail.NewMessage()
	message.SetHeader("From", "xxx@qq.com")
	message.SetHeader("To", email)
	message.SetHeader("Subject", "验证码")
	// 获取随机验证码
	computing := utils2.GetRandNums(6)
	message.SetBody("text/html", fmt.Sprintf("欢迎使用席万里的博客系统,这是您的注册验证码:%s", computing))
	// 连接并发送邮件
	dialer, err := config.Dial()
	if err != nil {
		return err
	}
	defer dialer.Close()
	if err = gomail.Send(dialer, message); err != nil {
		return err
	}
	// 将验证码存到redis中,确保输入的是正确的
	rdb := GetRDB(c)
	// 以email为key,mima为val存入redis,只存在1分钟
	rdb.Set(rctx, email, computing, time.Minute)
	return nil
}

3.注册的主功能函数

  • 用到了密码加密
  • 随机验证码的生成
  • 数据的写入
func (*UserAuth) Register(c *gin.Context) {
	var req RegisterReq
	if err := c.ShouldBindJSON(&req); err != nil {
		ReturnError(c, g2.ErrRequest, err)
		return
	}
	db := GetDB(c)
	rdb := GetRDB(c)
	// 检查邮箱是否已经被注册
	_, err := model.GetUserAuthInfoByName(db, req.Username)
	if err != nil {
		if !errors.Is(err, gorm.ErrRecordNotFound) {
			ReturnError(c, g2.ErrDbOp, err)
			return
		}
	} else {
		ReturnError(c, g2.ErrMailAleradyUsed, "邮箱已经被使用")
		return
	}
	// 取出来验证码码做判断 看是否正确
	computing, err := rdb.Get(rctx, req.Username).Result()
	if err != nil {
		// 处理获取验证码失败的情况
		ReturnError(c, g2.FailResult, err)
		return
	}
	// 如果验证码不正确,则返回错误信息
	if computing != req.Code {
		ReturnError(c, g2.ErrVerificationCode, "验证码错误")
		return
	}
	// 将密码加密
	hashpassword, _ := utils2.BcryptHash(req.Password)
	err = model.RegisterDB(db, req.Username, hashpassword)
	if err != nil {
		ReturnError(c, g2.ErrDbOp, "注册失败")
	}
	ReturnSuccess(c, "注册成功")
}

4.数据库层的邮箱查重函数

func GetUserAuthInfoByName(db *gorm.DB, name string) (*UserAuth, error) {
	var userAuth UserAuth
	result := db.Where(&UserAuth{Username: name}).First(&userAuth)
	return &userAuth, result.Error
}

5.数据查入、生成随机验证码、mimajiammi

// 数据查入
// 用户注册 用户名,密码
func RegisterDB(db *gorm.DB, username, hashpassword string) error {
	// 创建新用户认证信息
	newUserAuth := &UserAuth{
		Model: Model{
			CreatedAt: time.Now(),
			UpdatedAt: time.Now(),
		},
		Username: username,
		Password: hashpassword,
	}
	// 保存新用户认证信息到数据库
	if err := db.Create(newUserAuth).Error; err != nil {
		return err
	}
	return nil
}
// 生成随机验证码
func GetRandNums(digits int) string {
	rand.Seed(time.Now().UnixNano())
	numeric := [10]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	var sb strings.Builder
	for i := 0; i < digits; i++ {
		fmt.Fprintf(&sb, "%d", numeric[rand.Intn(len(numeric))])
	}
	return sb.String()
}

// 密码加密
func BcryptHash(str string) (string, error) {
	bytes, err := bcrypt.GenerateFromPassword([]byte(str), bcrypt.DefaultCost)
	return string(bytes), err
}

三、登录

登录功能也用到了查重,校验密码hash等功能,这里不在讲述。

  • 生成jwt token
  • 使用MD5加密
  • 使用Session存储认证ID
  • 使用Redis存储在线信息
func (*UserAuth) Login(c *gin.Context) {
	var req LoginReq
	if err := c.ShouldBindJSON(&req); err != nil {
		ReturnError(c, g2.ErrRequest, err)
		return
	}
	db := GetDB(c)
	rdb := GetRDB(c)
	userAuth, err := model.GetUserAuthInfoByName(db, req.Username)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			ReturnError(c, g2.ErrUserNotExist, nil)
			return
		}
		ReturnError(c, g2.ErrDbOp, err)
		return
	}
	// 检查密码是否正确
	if !utils2.BcryptCheck(req.Password, userAuth.Password) {
		ReturnError(c, g2.ErrPassword, nil)
		return
	
	// 获取 IP 相关信息 FIXME: 好像无法读取到 ip 信息
	ipAddress := utils2.IP.GetIpAddress(c)
	ipSource := utils2.IP.GetIpSourceSimpleIdle(ipAddress)

	userInfo, err := model.GetUserInfoById(db, userAuth.UserInfoId)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			ReturnError(c, g2.ErrUserNotExist, nil)
			return
		}
		ReturnError(c, g2.ErrDbOp, err)
		return
	}
	roleIds, err := model.GetRoleIdsByUserId(db, userAuth.ID)
	if err != nil {
		ReturnError(c, g2.ErrDbOp, err)
		return
	}
	articleLikeSet, err := rdb.SMembers(rctx, g2.ARTICLE_USER_LIKE_SET+strconv.Itoa(userAuth.ID)).Result()
	if err != nil {
		ReturnError(c, g2.ErrDbOp, err)
		return
	}
	commentLikeSet, err := rdb.SMembers(rctx, g2.COMMENT_USER_LIKE_SET+strconv.Itoa(userAuth.ID)).Result()
	if err != nil {
		ReturnError(c, g2.ErrDbOp, err)
		return
	}
	// 登录信息正确, 生成 Token
	// UUID 生成方法: ip + 浏览器信息 + 操作系统信息
	// uuid := utils.MD5(ipAddress + browser + os)
	conf := g.Conf.JWT
	token, err := jwt.GenToken(conf.Secret, conf.Issuer, int(conf.Expire), userAuth.ID, roleIds)
	if err != nil {
		ReturnError(c, g2.ErrTokenCreate, err)
		return
	}
	// 更新用户验证信息: ip 信息 + 上次登录时间
	err = model.UpdateUserLoginInfo(db, userAuth.ID, ipAddress, ipSource)
	if err != nil {
		ReturnError(c, g2.ErrDbOp, err)
		return
	}
	slog.Info("用户登录成功: " + userAuth.Username)
	session := sessions.Default(c)
	session.Set(g2.CTX_USER_AUTH, userAuth.ID)
	session.Save()
	// 删除 Redis 中的离线状态
	offlineKey := g2.OFFLINE_USER + strconv.Itoa(userAuth.ID)
	rdb.Del(rctx, offlineKey).Result()
	ReturnSuccess(c, LoginVO{
		UserInfo: *userInfo,
		ArticleLikeSet: articleLikeSet,
		CommentLikeSet: commentLikeSet,
		Token:          token,
	})
}

1.use Redis 缓存用户收藏和喜欢的文章

	articleLikeSet, err := rdb.SMembers(rctx, g2.ARTICLE_USER_LIKE_SET+strconv.Itoa(userAuth.ID)).Result()
	if err != nil {
		ReturnError(c, g2.ErrDbOp, err)
		return
	}
	commentLikeSet, err := rdb.SMembers(rctx, g2.COMMENT_USER_LIKE_SET+strconv.Itoa(userAuth.ID)).Result()
	if err != nil {
		ReturnError(c, g2.ErrDbOp, err)
		return
	}

四、退出

func (*UserAuth) Logout(c *gin.Context) {
	c.Set(g2.CTX_USER_AUTH, nil)

	// 已经退出登录
	auth, _ := CurrentUserAuth(c)
	if auth == nil {
		ReturnSuccess(c, nil)
		return
	}

	session := sessions.Default(c)
	session.Delete(g2.CTX_USER_AUTH)
	session.Save()

	// 删除 Redis 中的在线状态
	rdb := GetRDB(c)
	onlineKey := g2.ONLINE_USER + strconv.Itoa(auth.ID)
	rdb.Del(rctx, onlineKey)

	ReturnSuccess(c, nil)
}

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

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

相关文章

面试经典150题——求根节点到叶节点数字之和

​ 1. 题目描述 2. 题目分析与解析 2.1 思路一——DFS 理解问题&#xff1a; 首先要理解题目的要求&#xff0c;即对于给定的二叉树&#xff0c;我们需要找出从根节点到所有叶子节点的所有路径&#xff0c;然后将每一条路径上的数字组成一个整数&#xff0c;最后求出这些整数…

【静态分析】静态分析笔记08 - 指针分析 - 上下文敏感

参考&#xff1a; 【课程笔记】南大软件分析课程8——指针分析-上下文敏感&#xff08;课时11/12&#xff09; - 简书 ------------------------------------------------------------------------------------------------------------- 1. 上下文不敏感的问题 说明&#…

最详细步骤解决:Apps targeting Android12 and higher are required to specify...

问题原因&#xff1a; 当targetSdkVersion>31时&#xff0c;需要在AndroidManifest.xml中配置android:exported的值&#xff0c;该值为boolean类型。 android:exported解释&#xff1a; activity 是否可由其他应用的组件启动&#xff1a; 如果设为 "true"&#…

python中开发页面的两种方法:Qt Designer(PyQt图形化界面拖拽开发App界面)以及Django(开发Web应用框架)

一、开发独立的窗口&#xff0c;App的那种&#xff0c;可使用tkinter或者PyQt 使用PyQt时&#xff0c;里面有个工具Qt Designer&#xff0c;是一个可视化的界面设计工具&#xff0c;可以通过拖拽等方式来设计界面。下面就是Qt Designer的操作界面&#xff1a; 参考链接如下&am…

Linux——web建立wordpress

下载 [rootnfs-server ~]# yum install php wget https://wordpress.org/latest.tar.gz解压 /var/www/html [rootnfs-server html]# tar -xzvf latest.tar.gz [rootnfs-server html]# rm latest.tar.gz授权 [rootnfs-server html]# chown -R www:www /var/www/html添加文件备…

智能解决装箱问题:使用优化算法实现高效包装

组合优化问题 组合优化&#xff08;Combinatorial Optimization&#xff0c;CO&#xff09;数学优化研究的一个分支。主要关注的是从有限的对象集合中寻找最优解的问题。这个词的由来主要是由“组合”和“优化”两部分构成。“组合”指的是从有限的对象集合中选择一部分的过程…

1-内核开发环境ubuntu+virtualbox+mobaXterm搭建

内核开发环境 ubuntuvirtualboxmobaXterm搭建 目录 内核开发环境 ubuntuvirtualboxmobaXterm搭建 1.virtualbox 安装 2.ubuntu 安装 3.网络设置 4.虚拟机安装ssh 服务&#xff0c;更新ubuntu 源安装基本软件 5.mobaXterm 个人免费版本安装 6.总结 本课程教程从0-1开始教…

Druid高性能数据库连接池?SpringBoot整合MyBatis整合SpringMVC整合Druid

文章目录 Druid高性能数据库连接池&#xff1f;SpringBoot整合MyBatis整合SpringMVC整合Druid异常记录spring-boot-starter-parent作用Druid介绍什么是数据库连接池&#xff1f;为什么选择Druid数据库连接池整合SpringBoot,MyBatis,SpringMVC,Druid到Maven项目的真个流程pom文件…

为什么 React 比 Vue 难?你不知道的前端简史!

CSDN里有一个女粉问我&#xff0c;为什么都在说 React 比 Vue 难? 事实上&#xff0c;我不知道这个说法算是客观事实呢&#xff0c;还是“思想钢印”。 但是我第一反应就是盲猜一手&#xff0c;“React 比 Vue 难”这个说法可能源于后端攻城狮的主观评价&#xff0c;因为传统…

华为Pura 70系列,一种关于世界之美的可能

1874年&#xff0c;莫奈创作了《印象日出》的油画&#xff0c;在艺术界掀起了一场革命。当时的主流艺术&#xff0c;是追求细节写实&#xff0c;追求场面宏大的学院派。他们称莫奈等人是“印象派”&#xff0c;认为莫奈的画追求光影表达&#xff0c;追求描绘抽象的意境&#xf…

Oracle正則匹配練習一

1.使用分割符號 select regexp_substr(A_B_C, [^_], 1, 2) FROM DUAL 2.練習2 SELECT SUBSTR(path_string, INSTR(path_string, \, -1, 2) 1, INSTR(path_string, \, -1) - INSTR(path_string, \, -1, 2) - 1) AS extracted_string FROM (SELECT D:\SKY Image\A0-BOTC8…

一个VUE3的页面demo

样子是这样的 目录是这样的 index.vue文件内容 <template><div class"app-container"><div class"content"><div class"form" v-show"showSearch"><el-formref"queryRef":inline"true&qu…

C语言笔试题之找出数组的最大公约数

找出数组的最大公约数 实例要求 1、给定一个整数数组 &#xff0c;返回数组中最大数和最小数的最大公约数&#xff1b;2、两个数的最大公约数是能够被两个数整除的最大正整数&#xff1b;示例&#xff1a; 实例分析 1、要找到数组中最大数和最小数的最大公约数&#xff1b…

Python3.11修改并运行oneforall

遇到的问题 使用python3.11默认无法运行oneforall脚本&#xff0c;出现如下报错 # 解决方案 修改 /usr/local/lib/python3.11/dist-packages/exrex.py exrex.py具体文件路径报错中会显示 vim /usr/local/lib/python3.11/dist-packages/exrex.py# 修改前 from re import sre…

02_c/c++开源库ZeroMQ

1.安装 C库 libzmq sudo apt install libzmq3-dev 实例: https://zeromq.org/get-started/?languagec&librarylibzmq# 编译依赖: pkg-config --cflags --libs libzmq or cat /usr/lib/x86_64-linux-gnu/pkgconfig/libzmq.pc -isystem /usr/include/mit-krb5 -I/usr/in…

Magnet for Mac:高效窗口管理工具

Magnet for Mac是一款专为Mac用户设计的窗口管理工具&#xff0c;旨在帮助用户更高效地管理和布局多个应用程序窗口&#xff0c;提升工作效率。 Magnet for Mac v2.14.0中文免激活版下载 这款软件拥有直观易用的界面和丰富的功能&#xff0c;支持用户将屏幕分割成多个区域&…

低代码将干掉65%软件开发工作:留给码农的时间不多了

IDC预测&#xff0c;到2024年&#xff0c;约有65%的应用软件将通过低代码开发方式实现&#xff0c;同样&#xff0c;Gartner也曾发布过类似的预测&#xff0c;结果与IDC的预测大致相符。 低代码领域的知名公司Mendix之前发布的一份调研报告指出&#xff1a; “在中国&#xf…

基于Python实现的推箱子小游戏

Python贪吃蛇小游戏实现: 推箱子曾经在我们的童年给我们带来了很多乐趣。推箱子这款游戏现在基本上没人玩了&#xff0c;甚至在新一代人的印象中都已毫无记忆了。。。但是&#xff0c;这款游戏可以在一定程度上锻炼自己的编程能力。 运行效果如图所示&#xff1a; 游戏关卡有点…

C# 生成图形验证码

目录 应用场景 开发运行环境 设计 生成内容 生成图片 实现 核心代码 调用示例 小结 应用场景 我们当用户登录系统时经常会用到图形验证码技术&#xff0c;要求用户识别图片中的内容&#xff0c;并正确输入&#xff0c;方可尝试登录。类似的场景还有用户注册或者涉及…

探秘MySQL主从复制的多种实现方式

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 探秘MySQL主从复制的多种实现方式 前言基于语句的复制原理实现方法应用场景及优缺点应用场景优点缺点 基于行的复制原理实现方法优势和适用性优势适用性 基于混合模式的复制混合模式复制的工作原理混合…