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

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

文章目录

  • 前言
  • 安装依赖库
  • 数据库初始化
  • 账号注册和登录
    • 创建实体类User
    • dao
    • controller
    • router配置
  • 测试

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

参考文档:

  • 【go语言开发】yaml文件配置和解析
  • 【go语言开发】loglus日志框架的使用
  • 【go语言开发】swagger安装和使用

前言

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

数据库初始化

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

package coreimport ("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.Mysqldsn := 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

package doimport ("gorm.io/gorm"
)type User struct {gorm.ModelUsername     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

package daoimport ("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.Usertx := 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

package apiimport ("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.Useruser.Mobile = mobileuser.Username = userNameuser.Password = genPasswduser.Status = core.User_status_OKerr := 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.Usernamepassword := userLogin.Passwordif 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:生成tokentoken, 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)// 写入redisredisToken := 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配置

package middlewareimport ("code-go/app/admin/api"_ "code-go/docs""github.com/gin-gonic/gin"swaggerFiles "github.com/swaggo/files"     // swagger embed filesginSwagger "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生成
在这里插入图片描述
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Maya笔记 软选择

文章目录 1什么是软选择2注意3如何打开软选择3.1方法一3.2方法二 4调整软选择的范围5衰减模式5.1体积模式5.2表面模式 6衰减曲线 1什么是软选择 也就是渐变选择&#xff0c;从中心点向外影响力度越来越小 软选择针对的是点线面这些模型元素 下图中展示了对被软选择的区域移动…

答题pk小程序源码技术大解析

答题pk小程序源码解析 在数字化时代&#xff0c;小程序因其便捷性、即用性而受到广泛欢迎。其中&#xff0c;答题pk小程序更是成为了一种寓教于乐的现象。它不仅为用户提供了趣味性的知识竞技平台&#xff0c;还为企业、教育机构等提供了互动营销和知识传播的新途径。本文将对…

通过勒索病毒攻击案例,思考勒索病毒攻击现象与趋势

前言 2019年针对企业的勒索病毒攻击越来越多&#xff0c;仿佛全球都在被勒索&#xff0c;基本上每天都会有关于勒索病毒攻击的案例被曝光&#xff0c;勒索病毒攻击已经成为全球最大的网络安全威胁&#xff0c;同时也被国际刑警组织认定为全球危害最大的网络犯罪组织活动&#…

STM32各外设初始化步骤

1、GPIO初始化步骤 1、使能GPIO时钟 2、初始化GPIO的输入/输出模式 3、设置GPIO的输出值或获取GPIO的输入值 GPIO_InitTypeDef GPIO_InitStruct;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitStruct.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStruct.GPIO_Pin…

【前端系列】vue

这里写目录标题 一、Vue简介1.1 主流前端框架/库简介 二、下载和安装Vue2.1 下载2.2 安装完成后&#xff0c;检查2.3创建全局安装目录和缓存日志目录2.4 为了下载包快速&#xff0c;改源为淘宝镜像2.5 查看npm配置修改是否成功 三、配置环境变量环境变量—用户变量—选中Path—…

基于逻辑回归实现乳腺癌预测(机械学习与大数据)

基于逻辑回归实现乳腺癌预测 将乳腺癌数据集拆分成训练集和测试集&#xff0c;搭建一个逻辑回归模型&#xff0c;对训练集进行训练&#xff0c;然后分别对训练集和测试集进行预测。输出以下结果&#xff1a; 该模型在训练集上的准确率&#xff0c;在测试集上的准确率、召回率和…

实践航拍小目标检测,基于YOLOv7【tiny/l/x】不同系列参数模型开发构建无人机航拍场景下的小目标检测识别分析系统

关于无人机相关的场景在我们之前的博文也有一些比较早期的实践&#xff0c;感兴趣的话可以自行移步阅读即可&#xff1a; 《deepLabV3Plus实现无人机航拍目标分割识别系统》 《基于目标检测的无人机航拍场景下小目标检测实践》 《助力环保河道水质监测&#xff0c;基于yolov…

剑指offer 二维数组中的查找 C++

目录 前言 一、题目 二、解题思路 1.直接查找 2.二分法 三、输出结果 前言 最近在牛客网刷题&#xff0c;刷到二维数组的查找&#xff0c;在这里记录一下做题过程 一、题目 描述 在一个二维数组中&#xff08;每个一维数组的长度相同&#xff09;&#xff0c;每一行都按照…

Android 日志原理解析

一、Logcat 二、Dumpsys C:\Users\pengcheng.ding>adb shell dumpsys --help usage: dumpsysTo dump all services. or:dumpsys [-t TIMEOUT] [--priority LEVEL] [--clients] [--dump] [--pid] [--thread] [--help | -l | --skip SERVICES | SERVICE [ARGS]]--help: show…

[嵌入式系统-37]:龙芯1B 开发学习套件 -6-协处理器CP0之CPU异常处理与外部中断控制器的中断处理

目录 一、CP0概述 1.1 CP0概述 1.2 龙芯异常exception与中断interrupt的区别 二、CPU协处理器的异常处理 三、外部中断与外部中断控制器 3.1 外部中断源 3.2 如何配置外部中断源 3.3 外部中断的中断向量表 3.2.1 软件中断向量表结构定义&#xff1a;ls1b_irq.c 3.2.2…

将ppt里的视频导出来

将ppt的后缀从pptx改为zip 找到【media】里面有存放图片和音频以及视频&#xff0c;看文件名后缀可以找到&#xff0c;mp4的即为视频&#xff0c;直接复制粘贴到桌面即可。 关闭压缩软件把ppt后缀改回&#xff0c;不影响ppt正常使用。

C++对象模型剖析(六)一一Data语义学(三)

Data 语义学&#xff08;三&#xff09; “继承” 与 Data member 上期的这个继承的模块我们还剩下一个虚拟继承&#xff08;virtual inheritance&#xff09;没有讲&#xff0c;现在我们就来看看吧。 虚拟继承&#xff08;Virtual Inheritance&#xff09; 虚拟继承本质就是…

leetcode 3.6

Leetcode hot 100 一.矩阵1.旋转图像 二.链表1. 相交链表2.反转链表3.回文链表4.环形链表5.环形链表 II 一.矩阵 1.旋转图像 旋转图像 观察规律可得&#xff1a; matrix[i][j] 最终会被交换到 matrix [j][n−i−1]位置&#xff0c;最初思路是直接上三角交换&#xff0c;但是会…

SpringCloud(20)之Skywalking Agent原理剖析

一、Agent原理剖析 使用Skywalking的时候&#xff0c;并没有修改程序中任何一行 Java 代码&#xff0c;这里便使用到了 Java Agent 技术&#xff0c;我 们接下来展开对Java Agent 技术的学习。 1.1 Java Agent Java Agent 是从 JDK1.5 开始引入的&#xff0c;算是一个比较老的…

深入理解 Vuex:从基础到应用场景

前言 在之前的文章中&#xff0c;我们已经对 Vue.js 有了一定的了解。今天我们要对Vue官方的状态共享管理器Vuex进行详细讲解&#xff0c;将其基本吃透&#xff0c;目标是面对大多数业务需求&#xff1b; 一、介绍 Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用…

C++——string类

前言&#xff1a;哈喽小伙伴们&#xff0c;从这篇文章开始我们将进行若干个C中的重要的类容器的学习。本篇文章将讲解第一个类容器——string。 目录 一.什么是string类 二.string类常见接口 1.string类对象的常见构造 2.string类对象的容量操作 3. string类对象的访问及遍…

代码随想录第51天|● 300.最长递增子序列 ● 674. 最长连续递增序列 ● 718. 最长重复子数组

文章目录 ● 300.最长递增子序列思路代码&#xff1a; ● 674. 最长连续递增序列思路&#xff1a;代码&#xff1a; ● 718. 最长重复子数组思路&#xff1a;代码一&#xff1a;dp二维数组代码二&#xff1a;滚动数组 ● 300.最长递增子序列 思路 dp[i]表示i之前包括i的以nums…

从 Language Model 到 Chat Application:对话接口的设计与实现

作者&#xff1a;网隐 RTP-LLM 是阿里巴巴大模型预测团队开发的大模型推理加速引擎&#xff0c;作为一个高性能的大模型推理解决方案&#xff0c;它已被广泛应用于阿里内部。本文从对话接口的设计出发&#xff0c;介绍了业界常见方案&#xff0c;并分享了 RTP-LLM 团队在此场景…

MySQL下实现纯SQL语句的递归查询

需求 有一个部门表&#xff0c;部门表中有一个字段用于定义它的父部门&#xff1b; 在实际业务中有一个『部门中心』的业务&#xff1b; 比如采购单&#xff0c;我们需要显示本部门及子部门的采购单显示出来。 结构 数据如下&#xff1a; 实现方式如下&#xff1a; WITH RECUR…

Vue点击切换组件颜色

例如我有一个这样的组件&#xff0c;我希望在点击组件之后由蓝色变成橙色 先把原来的代码附上(简化掉了叉号&#xff09;&#xff1a; <div v-for"(item, index) in words" :key"index" class"scrollbar-demo-item"><span>{{ item …