• 【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 get -u gorm.io/gorm
    go get -u gorm.io/driver/mysql
    
    • 1
    • 2

    数据库初始化

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

    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)
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    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

    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"`
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    dao

    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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    controller

    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)))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178

    router配置

    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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    测试

    使用swagger测试如下,可以确认登录成功,且有token生成
    在这里插入图片描述
    在这里插入图片描述

  • 相关阅读:
    有刷与无刷电机的原理
    网络安全(黑客)自学
    Better And Better连Mac OS都在效仿的软件
    【Tensorflow+自然语言处理+LSTM】搭建智能聊天客服机器人实战(附源码、数据集和演示 超详细)
    2022 极术通讯-安谋科技“星辰”STAR-MC2处理器初探
    R语言实战应用精讲50篇(二十三)-贝叶斯理论重要概念: 可信度Credibility, 模型Models, 和参数Parameters
    1985-2020年全国各省一二三产业就业人数/各省分产业就业人数数据(无缺失)
    异步非阻塞python3代码
    【2023云栖】大模型驱动DataWorks数据开发治理平台智能化升级
    Codeforces Round 895 (Div. 3)
  • 原文地址:https://blog.csdn.net/baidu_33256174/article/details/136520102