【go语言开发】gorm库连接和操作mysql,实现一个简单的用户注册和登录

本文主要介绍使用gorm库连接和操作mysql,首先安装gorm和mysql依赖库;然后初始化mysql,配置连接池等基本信息;然后建表、完成dao、controller开发;最后在swagger中测试

文章目录

欢迎大家访问个人博客网址:https://www.maogeshuo.com,博主努力更新中...

参考文档:

前言

GORM 是一个强大的 ORM(对象关系映射)库,可以简化数据库操作并提供方便的查询方法。它提供了一种简单而强大的方式来处理数据库操作,包括连接到数据库、定义数据模型、执行查询、插入、更新和删除数据等功能。

以下是 GORM 库的一些主要特点和优点:

  • 支持多种数据库引擎:GORM 支持多种主流的数据库引擎,如 MySQL、PostgreSQL、SQLite、SQL Server 等。

  • 自动迁移:通过 GORM,你可以使用简单的代码就能自动创建、更新数据库表结构,而无需手动编写 SQL。

  • 链式方法:GORM 提供了丰富的链式方法,用于构建复杂的查询条件,并支持预加载相关数据,实现数据的懒加载。

  • 事务支持:GORM 提供了事务支持,确保在数据操作时的原子性和一致性。

  • 回调函数:你可以注册各种回调函数,以在特定事件发生时执行自定义逻辑,如在保存数据之前或之后执行某些操作。

  • 软删除:GORM 支持软删除功能,即标记删除数据而非真正从数据库中删除,方便数据恢复和数据保留。

  • 关系映射:GORM 可以轻松地定义模型之间的关系,如一对一、一对多、多对多等,并提供方便的方法来处理这些关系。

  • 性能优化:GORM 对数据库操作进行了优化,提供了缓存、批量插入等功能,以提高性能。

安装依赖库

当使用 GORM 时,首先需要安装 GORM 包和相应的数据库驱动。

go 复制代码
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql

数据库初始化

初始化mysql,我这里只配置了两张表,设置连接池相关参数

go 复制代码
package core

import (
	"code-go/model/do"
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"log"
	"time"
)

var DB *gorm.DB

// InitMysql 初始化mysql
func InitMysql() (*gorm.DB, error) {
	mysqlConfig := Config.Database.Mysql
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
		mysqlConfig.UserName,
		mysqlConfig.Password,
		mysqlConfig.Host,
		mysqlConfig.Port,
		mysqlConfig.Database)
	LOG.Println("dsn: ", dsn)
	dbMysql, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		log.Println("open db_mysql error ", err)
		return nil, err
	}
	DB = dbMysql

	//迁移表
	autoMigrateTable()

	// 是否打开日志
	if mysqlConfig.LogMode {
		dbMysql.Debug()
	}

	db, _ := dbMysql.DB()
	//设置连接池的最大闲置连接数
	db.SetMaxIdleConns(10)
	//设置连接池中的最大连接数量
	db.SetMaxOpenConns(100)
	//设置连接的最大复用时间
	db.SetConnMaxLifetime(10 * time.Second)
	return dbMysql, nil
}

// 自动迁移表
func autoMigrateTable() {
	err := DB.AutoMigrate(&do.User{}, &do.OperationLog{})
	if err != nil {
		LOG.Error("迁移表结构失败:", err)
	}
}

SetMaxIdleConns :设置最大空闲连接数,目前默认值为2,0<= n<=MaxIdleConns
SetMaxOpenConns : 设置最大连接数,默认为0
SetConnMaxLifetime :设置连接的最大存活时间,当d<=0,connections are not closed due to a connection's age

至于其他的配置,查看源码和官方文档https://gorm.io/docs/

账号注册和登录

创建实体类User

go 复制代码
package do

import (
	"gorm.io/gorm"
)

type User struct {
	gorm.Model
	Username     string  `gorm:"type:varchar(20);not null;unique;index:idx_username" json:"username"`
	Password     string  `gorm:"size:255;not null" json:"password,omitempty"`
	Mobile       string  `gorm:"type:varchar(11);not null;unique" json:"mobile"`
	Avatar       string  `gorm:"type:varchar(255)" json:"avatar"`
	Nickname     *string `gorm:"type:varchar(20)" json:"nickname"`
	Introduction *string `gorm:"type:varchar(255)" json:"introduction"`
	Status       uint    `gorm:"type:tinyint(1);default:1;comment:'1正常, 2禁用'" json:"status"`
	Creator      string  `gorm:"type:varchar(20);" json:"creator"`
	Roles        []*Role `gorm:"many2many:user_roles" json:"roles"`
}

dao

go 复制代码
package dao

import (
	"code-go/core"
	"code-go/model/do"
)

func InsertUser(user do.User) error {
	tx := core.DB.Create(&user)
	if tx.Error != nil {
		core.LOG.Println("insert user in do fail")
		return tx.Error
	}
	return nil
}

func GetUserByUsername(userName string) *do.User {
	var user *do.User
	tx := core.DB.Model(&do.User{}).Where("username=?", userName).Find(&user)
	if tx.Error != nil {
		core.LOG.Println("Query user by username fail")
	}
	return user
}

controller

go 复制代码
package api

import (
	"code-go/app/admin/dao"
	"code-go/common"
	"code-go/core"
	"code-go/global"
	"code-go/model/do"
	"code-go/model/vo"
	"code-go/util"
	"fmt"
	"github.com/gin-gonic/gin"
	"net/http"
	"strconv"
	"time"
)

// Register 用户注册
//
//	@Summary	用户注册
//	@Produce	json
//	@Router		/api/user/register [post]
//	@Param		username	query	string	true	"用户名"
//	@Param		password	query	string	true	"密码"
//	@Param		mobile		query	string	true	"电话"
func Register(c *gin.Context) {
	userName := c.Query("username")
	password := c.Query("password")
	mobile := c.Query("mobile")
	if userName == "" || password == "" {
		core.LOG.Println("输入的用户名和密码为空")
		c.JSON(http.StatusOK, common.FailWithCodeMsg(common.VALILD_FAIL, "输入的用户名或密码为空"))
		return
	}

	// 查询用户
	daoUser := dao.GetUserByUsername(userName)
	if daoUser.Username == userName {
		core.LOG.Printf("用户: %s 已存在\n", userName)
		c.JSON(http.StatusOK, common.OkWithData("输入的用户已存在"))
		return
	}

	// 生成用户
	genPasswd := util.GenPasswd(password)
	var user do.User
	user.Mobile = mobile
	user.Username = userName
	user.Password = genPasswd
	user.Status = core.User_status_OK

	err := dao.InsertUser(user)
	if err != nil {
		core.LOG.Println("插入用户失败 ", err)
		c.JSON(http.StatusOK, common.FailWithCodeMsg(common.INSERT_DB_FAIL, "插入用户失败"))
		return
	}
	c.JSON(http.StatusOK, common.Ok())

}

// Login 用户登录
//
//	@Summary	用户登录
//	@Produce	json
//	@Router		/api/user/login [post]
//	@Param		username	query	string	true	"用户名"
//	@Param		password	query	string	true	"密码"
func Login(c *gin.Context) {
	userLogin := vo.UserLoginReqVo{}
	err := c.ShouldBindQuery(&userLogin)
	if err != nil {
		core.LOG.Println("用户登录:参数绑定失败")
		c.JSON(http.StatusOK, common.FailWithMsg("参数绑定失败"))
		return
	}
	userName := userLogin.Username
	password := userLogin.Password
	if userName == "" || password == "" {
		core.LOG.Println("用户登录:输入的用户名和密码为空")
		c.JSON(http.StatusOK, common.FailWithCodeMsg(common.VALILD_FAIL, "输入的用户名和密码为空"))
		return
	}
	// 校验格式
	isMatched := util.ValidateUserName(userName)
	if !isMatched {
		core.LOG.Println("用户登录:用户名格式校验失败")
		c.JSON(http.StatusOK, common.FailWithCodeMsg(common.VALILD_FAIL, "用户名格式校验失败"))
		return
	}
	//isMatched = util.ValidatePassword(password)
	//if !isMatched {
	//	global.Log.Println("用户登录:密码格式校验失败")
	//	c.JSON(http.StatusOK, common.FailWithCodeMsg(common.VALILD_FAIL, "密码格式校验失败"))
	//	return
	//}

	// TODO: 生成验证码

	// 数据库查询
	user := dao.GetUserByUsername(userName)
	if user.Username == "" {
		core.LOG.Println("用户登录:未查询到用户")
		c.JSON(http.StatusOK, common.FailWithCodeMsg(common.USER_NOT_EXIST, common.GetMapInfo(common.USER_NOT_EXIST)))
		return
	}
	//校验登录密码是否和数据库一致
	isPasswordMatch := util.ComparePasswd(user.Password, password)
	if !isPasswordMatch {
		core.LOG.Println("用户登录:用户密码输入错误")
		c.JSON(http.StatusOK, common.FailWithCodeMsg(common.USER_PASSWORD_NOT_MATCHED,
			common.GetMapInfo(common.USER_PASSWORD_NOT_MATCHED)))
		return
	}
	// TODO:生成token
	token, err := util.GenerateToken(strconv.Itoa(int(user.ID)), user.Username)
	if err != nil {
		core.LOG.Println("用户登录:生成token失败")
		c.JSON(http.StatusOK, common.FailWithCodeMsg(common.AUTHORIZATION_FAIL, "生成token失败"))
		return
	}
	c.Header(global.Authorization, token)

	// 写入redis
	redisToken := fmt.Sprintf("user-token-%s", userName)
	isOk := core.Redis.SetEX(redisToken, token, 7*24*time.Hour)
	if isOk == false {
		core.LOG.Println("用户登录:设置值到redis")
		c.JSON(http.StatusOK, common.FailWithCodeMsg(common.REDIS_SET_FAIL, "设置值到Redis失败"))
		return
	}
	userVo := vo.ConvertToUserResVo(user)
	c.Header(global.Authorization, token)
	c.JSON(http.StatusOK, gin.H{
		"token": token,
		"user":  userVo,
	})

}

// GetUserByUsername 根据用户名查询用户
//
//	@Summary	用户登录
//	@Produce	json
//	@Router		/api/user/getUserByName [get]
//	@Param		username	query	string	true	"用户名"	maxlength(20)
func GetUserByUsername(c *gin.Context) {
	userName := c.Query("username")
	user := dao.GetUserByUsername(userName)
	if user.Username == "" {
		c.JSON(http.StatusOK, common.FailWithMsg("未查询到该用户"))
		return
	}
	userVo := vo.ConvertToUserResVo(user)
	c.JSON(http.StatusOK, common.OkWithData(userVo))
}

// GetAllUser 查询所有的用户
//
//	@Summary	查询所有的用户
//	@Produce	json
//	@Router		/api/user/getAllUser [get]
//	@Param		pagenum		query	int	true	"页数"
//	@Param		pagesize	query	int	true	"页面大小"
func GetAllUser(c *gin.Context) {
	pageNum, _ := strconv.Atoi(c.Query("pagenum"))
	pageSize, _ := strconv.Atoi(c.Query("pagesize"))
	core.LOG.Printf("pagenum: %d, pagesize: %d\n", pageNum, pageSize)
	if pageNum <= 0 {
		pageNum = 0
	}
	if pageSize <= 0 {
		pageSize = 1
	}
	users, total := dao.GetUser(pageNum, pageSize)

	c.JSON(http.StatusOK, common.OkWithData(common.NewPageRes(users, total)))
}

router配置

go 复制代码
package middleware

import (
	"code-go/app/admin/api"
	_ "code-go/docs"
	"github.com/gin-gonic/gin"
	swaggerFiles "github.com/swaggo/files"     // swagger embed files
	ginSwagger "github.com/swaggo/gin-swagger" // gin-swagger middleware
)

// InitRouter 初始化Router
func InitRouter() *gin.Engine {
	g := gin.New()
	g.Use(gin.Recovery())
	g.Use(Logger())
	g.Use(Cors())

	// 需授权
	auth := g.Group("/api")
	{
		auth.GET("/user/getAllUser", api.GetAllUser)
		auth.POST("/user/register", api.Register)
		auth.POST("/user/login", api.Login)
		auth.GET("/user/getUserByName", api.GetUserByUsername)
	}

	// 无需授权
	norm := g.Group("/")
	{
		norm.GET("/getIp", api.GetIp)
		norm.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))

		norm.GET("/captcha", api.GetCaptcha)
		norm.POST("/checkCaptcha", api.CheckCaptcha)
	}
	return g
}

测试

使用swagger测试如下,可以确认登录成功,且有token生成

相关推荐
AI原吾7 分钟前
掌握Python-uinput:打造你的输入设备控制大师
开发语言·python·apython-uinput
机器视觉知识推荐、就业指导7 分钟前
Qt/C++事件过滤器与控件响应重写的使用、场景的不同
开发语言·数据库·c++·qt
毕设木哥9 分钟前
25届计算机专业毕设选题推荐-基于python的二手电子设备交易平台【源码+文档+讲解】
开发语言·python·计算机·django·毕业设计·课程设计·毕设
珞瑜·9 分钟前
Matlab R2024B软件安装教程
开发语言·matlab
weixin_4554461710 分钟前
Python学习的主要知识框架
开发语言·python·学习
孤寂大仙v15 分钟前
【C++】STL----list常见用法
开发语言·c++·list
她似晚风般温柔7891 小时前
Uniapp + Vue3 + Vite +Uview + Pinia 分商家实现购物车功能(最新附源码保姆级)
开发语言·javascript·uni-app
咩咩大主教1 小时前
C++基于select和epoll的TCP服务器
linux·服务器·c语言·开发语言·c++·tcp/ip·io多路复用
FuLLovers2 小时前
2024-09-13 冯诺依曼体系结构 OS管理 进程
linux·开发语言
王中阳Go2 小时前
字节跳动的微服务独家面经
微服务·面试·golang