登录和注册(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)
}