gin gorm学习笔记

news2024/11/14 3:06:31

代码仓库
https://gitee.com/zhupeng911/go-advanced.git
https://gitee.com/zhupeng911/go-project.git

1. gin介绍

Gin 是使用纯 Golang 语言实现的 HTTP Web框架,Gin接口设计简洁,提供类似Martini的API,性能极高,现在被广泛使用。

主要特性

  • 快速 - 基于 Radix 树的路由,小内存占用,没有反射,可预测的 API 性能。

  • 支持中间件 - 传入的 HTTP 请求可以由一系列**中间件**和最终操作来处理。 例如:Logger,Authorization,最终操作 DB。

  • 路由组 - 更好地组织路由。例如将需要授权和不需要授权的API分组,不同版本的api分组。分组可嵌套,且性能不受影响。

  • Crash 处理 - Gin 可以 catch 一个发生在 HTTP 请求中的 panic 并 recover 它。这样,你的服务器将始终可用。例如,你可以向 Sentry 报告这个 panic!

  • **JSON 验证 **- Gin 可以解析并验证请求的 JSON,例如检查所需值的存在。

  • 内置渲染 - Gin 原生为 JSON,XML 和 HTML 渲染提供了易于使用的 API。

官方文档

// 下载gin框架
go get -u github.com/gin-gonic/gin

2. gin关键技术点

2.1 数据解析和参数绑定

为了能够更方便的获取请求相关参数,提高开发效率,基于请求的Content-Type识别请求数据类型并利用反射机制自动提取请求中QueryString、form表单、JSON等参数到结构体中,后端接收并解析该结构体。

type Student struct {
	UserName     string `form:"user_name" json:"user_name" binding:"required"`
	UserPassword string `form:"user_password" json:"user_password" binding:"required"`
}

func TestGin006() {
	router := gin.Default()
	// 1.绑定JSON的示例 ({"user_name": "小王子", "user_password": "123456"})
	router.POST("/loginjson", func(c *gin.Context) {
		var stu Student
		// ShouldBind()会根据请求的Content-Type自行选择绑定器
		if err := c.ShouldBind(&stu); err == nil {
			fmt.Printf("stu: %v \n", stu)
			c.JSON(http.StatusOK, gin.H{
				"user_name":     stu.UserName,
				"user_password": stu.UserPassword,
			})
		} else {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		}
	})

	// 2.绑定form表单示例 (user=小王子i&password=123456)
	router.POST("/loginForm", func(c *gin.Context) {
		var stu Student
		if err := c.ShouldBind(&stu); err == nil {
			c.JSON(http.StatusOK, gin.H{
				"user":     stu.UserName,
				"password": stu.UserPassword,
			})
		} else {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		}
	})

	// 3.绑定QueryString示例 (/loginQuery?user_name=q1mi&user_password=123456)
	router.GET("/loginQuery", func(c *gin.Context) {
		var stu Student
		if err := c.ShouldBind(&stu); err == nil {
			c.JSON(http.StatusOK, gin.H{
				"user":     stu.UserName,
				"password": stu.UserPassword,
			})
		} else {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		}
	})

	router.Run(":8080")
}

2.2 gin中间件

中间件是Gin的精髓,一个个中间件组成一条中间件链,对HTTP Request请求进行拦截处理,实现了代码的解耦和分离,每个中间件只需要处理自己需要处理的业务。

简单来说,Gin中间件的作用有两个:

(1)Web请求到到达我们定义的HTTP请求处理方法之前,拦截请求并进行相应处理(比如:权限验证,数据过滤等),这个可以类比为前置拦截器前置过滤器;

(2)在我们处理完成请求并响应客户端时,拦截响应并进行相应的处理(比如:添加统一响应部头或数据格式等),这可以类型为后置拦截器后置过滤器

2.2.1 定义中间件

在Gin框架中,中间件的类型定义如下所示,可以看出,中间件实际上就是一个以gin.Context为返回值的函数而已,与我们定义处理HTTP请求的Handler本质上是一样的,并没有什么神秘可言。

// ProgramTimeCost a、定义中间件:统计接口耗时的中间件
func ProgramTimeCost() gin.HandlerFunc {
	return func(c *gin.Context) {
		fmt.Printf("中间件---ProgramTimeCost in... \n")
		start := time.Now()
		c.Next() // 调用该请求的剩余处理程序
		// c.Abort() // 不调用该请求的剩余处理程序
		cost := time.Since(start)
		fmt.Printf("该接口耗时: %v \n", cost)
		fmt.Printf("中间件---ProgramTimeCost out... \n")
	}
}

PS:内置中间件
在这里插入图片描述

2.2.2 全局使用中间件

使用gin.Engine结构体的Use()方法便可以在所有请求应用中间件。

// TestGin010 
func TestGin010() {
	r := gin.Default()
	// 注册一个全局中间件,可以一次性注册多个中间件
	r.Use(ProgramTimeCost())
	r.GET("/index", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"msg": "index"}) })
	r.GET("/login", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"msg": "login"}) })
	r.Run()
}

2.2.3 分组使用中间件

更多的时候,我们会根据业务不同划分不同路由分组(RouterGroup ),不同的路由分组再应用不同的中间件,在这种下就是在路由分组中局部使用中间件。

// TestGin010 
func TestGin010() {
	router := gin.New()
	user := router.Group("user", ProgramTimeCost()
	{
	   user.GET("info", func(context *gin.Context) {
	      ...
	   })
	   user.GET("article", func(context *gin.Context) {
	      ...
	   })
	}
}

2.2.4 单个路由使用中间件

除了路由分组,在单个请求路由中也可以应用中间件

// TestGin010 
func TestGin010() {
	r := gin.Default()
	// 给路由单独注册中间件(一个接口可注册多个中间件)
	r.GET("/logout", ProgramTimeCost(), func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"msg":  "logout",
			"name": name,
		})
	})
	r.Run()
}

2.3 gin路由组

gin 框架中采用的路由库是基于httprouter实现的,并支持Restful风格的API。为了管理具有相同前缀的URL, 将拥有URL共同前缀的路由划分为一组。

// TestGin009 路由组
func TestGin009() {
	r := gin.Default()
	// 1.普通路由
	r.GET("/index", func(c *gin.Context) {})
	r.GET("/login", func(c *gin.Context) {})
	r.POST("/login", func(c *gin.Context) {})

	// 2.没有匹配到路由的请求返回统一404页面
	r.LoadHTMLFiles("./static/404.html")
	r.NoRoute(func(c *gin.Context) {
		c.HTML(http.StatusNotFound, "404.html", nil)
	})

	//3.路由组 将拥有共同URL前缀的路由划分为一个路由组
	//为路由组注册中间件方法一
	userGroup := r.Group("/user", ProgramTimeCost())
	{
		userGroup.GET("/queryName", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{
				"path": "/user/queryName",
			})
		})
		userGroup.GET("/queryAge", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{
				"path": "/user/queryAge",
			})
		})

		// 路由组也是支持嵌套的
		//为路由组注册中间件方法二
		shopGroup := userGroup.Group("/shop")
		shopGroup.Use(ProgramTimeCost())
		{
			shopGroup.GET("/queryShopName", func(c *gin.Context) {
				c.JSON(http.StatusOK, gin.H{
					"path": "/user/shop/queryShopName",
				})
			})
			shopGroup.GET("/queryShopAddr", func(c *gin.Context) {
				c.JSON(http.StatusOK, gin.H{
					"path": "/user/shop/queryShopAddr",
				})
			})
		}
	}
	r.Run()
}

3. gorm介绍

The fantastic ORM library for Golang aims to be developer friendly.

gorm是GoLang实现的,在GitHub上活跃度很高的对象关系映射框架(ORM)。它的作用是在关系型数据库和对象之间作一个映射,这样,我们在具体的操作数据库的时候,就不需要再去和复杂的SQL语句打交道,只要像平时操作对象一样操作它就可以了。

## 必须安装gorm
go get -u gorm.io/gorm     
## 安装相应的数据库驱动。GORM 官方支持的数据库类型有: MySQL, PostgreSQL, SQlite, SQL Server
go get -u gorm.io/driver/mysql  

4. gorm常用示例

4.1 连接数据库

连接不同的数据库都需要导入对应数据的驱动程序,GORM已经贴心的为我们包装了一些驱动程序,只需要按如下方式导入需要的数据库驱动即可:

GORM 官方支持的数据库类型有: MySQL, PostgreSQL, SQlite, SQL Server

连接MySQL

import (
  "github.com/jinzhu/gorm"
  _ "github.com/jinzhu/gorm/dialects/mysql"
)

func main() {
  db, err := gorm.Open("mysql", "username:password@(localhost)/dbname?charset=utf8mb4&parseTime=True&loc=Local")
  defer db.Close()
}

// MySQl 驱动程序提供了 一些高级配置 可以在初始化过程中使用,例如:
func main() {
	db, err := gorm.Open(mysql.New(mysql.Config{
	  DSN: "gorm:gorm@tcp(127.0.0.1:3306)/gorm?charset=utf8&parseTime=True&loc=Local", // DSN data source name
	  DefaultStringSize: 256, // string 类型字段的默认长度
	  DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
	  DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
	  DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
	  SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置
	}), &gorm.Config{})
  defer db.Close()
}

连接PostgreSQL…等,详见

GORM

4.2 快速入门

// 模型定义
type DFUser struct {
	gorm.Model              // gorm.Model是一个包含了ID, CreatedAt, UpdatedAt, DeletedAt四个字段的Golang结构体
	UserId           uint64 `gorm:"column:user_id;type:int;primary_key;unique;not null"`
	UserName         string `gorm:"column:user_name;type:varchar(255)"`
	UserAge          int    `gorm:"column:user_age;default:0"`
	UserMemberNumber string `gorm:"unique_index;not null"` // 设置会员号(member number)唯一并且不为空
	UserAddress      string `gorm:"index:addr"`            // 给UserAddress字段创建名为addr的索引
	UserBirthday     time.Time
	IgnoreMe         int `gorm:"-"` // 忽略本字段
}

// 设置表名001
func (DFUser) TableName() string {
	return "df_user"
}

func TestGorm001() {
	// 连接数据库mysql
	db, err := gorm.Open("mysql", "root:zhupeng123@(127.0.0.1:3306)/go_db_1?charset=utf8&parseTime=True&loc=Local")
	if err != nil {
		panic(err)
	}
	defer db.Close()

	// 操作数据库-建表
	db.AutoMigrate(&DFUser{})					 // 自动迁移【结构体与表对应,类似于JPA】
	//db.Table("df_user").CreateTable(&DFUser{}) // 手动设置表名002
	//db.SingularTable(true)					 // 禁用默认表名的复数形式,如果置为 true,则 `User` 的默认表名是 `user`

	// 表中添加数据
	dfUser01 := DFUser{UserId: 1000, UserName: "小王子", UserAge: 23, UserMemberNumber: "VIP1000", UserAddress: "江苏南京", UserBirthday: time.Now()}
	dfUser02 := DFUser{UserId: 1001, UserName: "小王子2", UserAge: 24, UserMemberNumber: "VIP1001", UserAddress: "江苏南京", UserBirthday: time.Now()}
	db.Create(&dfUser01)
	db.Create(&dfUser02)

	// 查询
	var user DFUser
	db.First(&user)    // 根据主键查询第一条记录 SELECT * FROM df_user ORDER BY id LIMIT 1;
	db.Last(&user)     // 根据主键查询最后一条记录 SELECT * FROM df_user ORDER BY id DESC LIMIT 1;
	db.Find(&user)     // 查询所有的记录 SELECT * FROM df_user;
	
	db.Where(&DFUser{UserName: "小王子", UserAge: 23}).First(&user)
	// SELECT * FROM df_user WHERE user_name = "朱鹏" AND user_age = 23 LIMIT 1;

	// 更新某个字段
	db.Model(&user).Update("user_name", "zhupeng123")
	// UPDATE users SET name='zhupeng123' WHERE id=111;

	// 更新多个字段
	db.Model(&user).Updates(DFUser{UserName: "zhupeng_update", UserAge: 0, UserBirthday: time.Now()})
	// UPDATE users SET user_name='zhupeng_update', user_age=18, updated_at='2013-11-17 21:34:10' WHERE id=111;

	// 批量更新
	db.Where("id IN (?)", []int{10, 11}).Updates(map[string]interface{}{"user_name": "hello", "user_age": 18})
	// UPDATE users SET user_name='hello', user_age=18 WHERE id IN (10, 11);

	// 删除记录
	db.Delete(&user)
	// DELETE from df_user where id=10;
}

推荐:
普通场景:简单查询用Find+Where的函数结合实现,结合Limit+Offset+Order实现分页等高频功能;
追求性能:可以引入Select避免查询所有字段,但会导致返回结果部分字段不存在的奇怪现象,需要权衡;
复杂查询:例如Join+子查询等,推荐使用下面的原生SQL,用GORM拼接的体验并不好。

4.3 SQL是怎样生成的

两个核心文件

在GORM库中,有两个核心的文件,也是我们调用频率最高的函数所在:chainable_api.go和 finisher_api.go。顾名思义,前者是整个链式调用的中间部分,后者则是最终获取结果的函数。以查询为例:

db.Where(&User{Name: "小王子"}, "name", "Age").Find(&users)

其中Where是chainable,也就是还在拼接SQL条件,Find则是触发真正查询的finisher,从finisher入手,看看一个SQL的到底是怎么在GORM中拼接并执行的。

核心-构建SQL的实现

func BuildQuerySQL(db *gorm.DB) {
  // SQL为空,表示需要自己构建
 if db.Statement.SQL.String() == "" {
  db.Statement.SQL.Grow(100) // 分配初始空间

  if len(db.Statement.Selects) > 0 {
      // 表示只select某几个字段,而不是select *
  } else if db.Statement.Schema != nil && len(db.Statement.Omits) > 0 {
      // Omit表示忽略特定字段
  } else if db.Statement.Schema != nil && db.Statement.ReflectValue.IsValid() {
      // 查询到指定结构体
  }

  // 对join的处理,涉及到多表关联,暂时忽略
  if len(db.Statement.Joins) != 0 {
  } else {
   db.Statement.AddClauseIfNotExists(clause.From{})
  }

    // 用一个map去重,符合名字中的 IfNotExists 含义
  db.Statement.AddClauseIfNotExists(clauseSelect)

    // 最后拼接出完整 SQL 的地方
  db.Statement.Build(db.Statement.BuildClauses...)
 }
}

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

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

相关文章

SpringBoot接口防抖(防重复提交)的一些实现方案

前言 啥是防抖 思路解析 分布式部署下如何做接口防抖? 具体实现 请求锁 唯一key生成 重复提交判断 前言 作为一名老码农,在开发后端Java业务系统,包括各种管理后台和小程序等。在这些项目中,我设计过单/多租户体系系统&a…

如何制作一个分销商城小程序_揭秘分销商城小程序的制作秘籍

打造赚钱神器!揭秘分销商城小程序的制作秘籍 在这个数字化高速发展的时代,拥有一个属于自己的分销商城小程序,已成为众多商家和创业者的必备利器。它不仅能够快速搭建起自己的在线销售渠道,还能够利用分销模式,迅速裂…

机器学习专项课程03:Unsupervised Learning, Recommenders, Reinforcement Learning笔记 Week02

Week 02 of Unsupervised Learning, Recommenders, Reinforcement Learning 课程地址: https://www.coursera.org/learn/unsupervised-learning-recommenders-reinforcement-learning 本笔记包含字幕,quiz的答案以及作业的代码,仅供个人学习…

无人机遥感在农林信息提取中的实现方法与GIS融合应用

在新一轮互联网信息技术大发展的现今,无人机、大数据、人工智能、物联网等新兴技术在各行各业都处于大爆发的前夜。为了将人工智能方法引入农业生产领域。首先在种植、养护等生产作业环节,逐步摆脱人力依赖;在施肥灌溉环节构建智慧节能系统&a…

centos7安装kafka、zookeeper

安装jdk 安装jdk8 安装zookeeper 在指定目录执行下载命令 我是在/newdisk/zookeeper目录下 wget https://archive.apache.org/dist/zookeeper/zookeeper-3.5.8/apache-zookeeper-3.5.8-bin.tar.gz --no-check-certificate下载好后并解压 tar -zxvf apache-zookeeper-3.5…

Kali Linux 2024.1

Kali Linux 2024.1刚刚发布,标志着这个备受欢迎的安全重点Linux发行版在今年的首次重大更新。以其先进的渗透测试和安全审计功能而闻名,它是安全专业人员和爱好者的首选工具。 Kali 2024.1 亮点 本次发布由 Linux 内核 6.6 提供支持,突出了…

Git分布式管理-头歌实验本地版本库

一、本地版本库创建 任务描述 本地Git操作三部曲是“修改-添加-提交”,即先要在本地仓库进行添加、删除或编辑等修改,然后将本地所做的修改添加至暂存区。添加至暂存区的这些本地修改,并未提交到本地仓库,需要执行提交命令才能将暂…

详解Java中集合的List接口实现的ArrayList方法 | Set接口实现的HashSet方法

集合的概念 当我们需要保存一组一样(类型相同)的元素的时候,我们应该使用一个容器来存储,数组就是这样一个容器。 ● 数组的特点: 数组是一组数据类型相同的元素集合; 创建数组时,必须给定…

【CSP试题回顾】202212-1-现值计算

CSP-202212-1-现值计算 解题代码 #include <iostream> #include <vector> #include <cmath> using namespace std;int main() {int n;double k, sumPrice 0;cin >> n >> k;vector<double>priceList(n 1);for (int i 0; i < n; i){…

分享7款前端动画特效(附效果图及在线演示)

分享7款好玩的前端动画特效 其中有CSS动画、SVG动画、js小游戏等等 下方效果图可能不是特别的生动 那么你可以点击在线预览进行查看相应的动画特效 同时也是可以下载该资源的 canvas彩色画树特效 基于canvas实现的画树特效 同时还可选择树枝的初始数目进行彩色树生成 以下效果…

用docker部署后端项目

一、搭建局域网 1.1、介绍前后端项目搭建 需要4台服务器&#xff0c;在同一个局域网中 1.2、操作 # 搭建net-ry局域网&#xff0c;用于部署若依项目 net-ry&#xff1a;名字 docker network create net-ry --subnet172.68.0.0/16 --gateway172.68.0.1#查看 docker network ls…

【力扣精选算法100道】——存在重复元素 1 or 2 (哈希)

目录 &#x1f6a9;存在重复元素1 &#x1f388;了解题意 &#x1f388;算法原理 &#x1f388;实现代码 &#x1f6a9;存在重复元素2 &#x1f388;了解题意 &#x1f388;算法原理 &#x1f388;代码实现 217. 存在重复元素 - 力扣&#xff08;LeetCode&#xff09; …

Zynq—AD9238数据采集DDR3缓存千兆以太网发送实验(二)

Zynq—AD9238数据采集DDR3缓存千兆以太网发送实验&#xff08;前导&#xff09; Zynq—AD9238数据采集DDR3缓存千兆以太网发送实验&#xff08;一&#xff09; 五、实验目的 本次实验使用电脑上的网络调试助手&#xff0c;将命令帧通过以太网芯片RTL8211&#xff08;RGMII接口…

C#,最小代价多边形三角剖分MCPT(Minimum Cost Polygon Triangulation)算法与源代码

1 最小代价多边形三角剖分算法 凸多边形的三角剖分是通过在非相邻顶点&#xff08;角点&#xff09;之间绘制对角线来形成的&#xff0c;这样对角线就不会相交。问题是如何以最小的代价找到三角剖分的代价。三角剖分的代价是其组成三角形的权重之和。每个三角形的重量是其周长…

FPFH特征提取以及匹配(matlab代码免费)

FPFH特征提取时谁提出的&#xff0c;尊重一下原创&#xff1a; [1] Rusu, Radu Bogdan, Nico Blodow, and Michael Beetz. “Fast point feature histograms (FPFH) for 3D registration.” In 2009 IEEE International Conference on Robotics and Automation, pp. 3212-3217…

websocket在java中的使用教程

本文从websocket服务端和客户端两个方面简单介绍下websocket在java中的使用。 一、websocket服务端&#xff08;WebSocketServer&#xff09; websocket服务端是以本机作为消息的接受端&#xff0c;用于接受客户端websocket发送过来的消息&#xff0c;并可以通过客户端的webs…

06_netdev网卡设备内核模块

01_basicLinux内核模块-CSDN博客文章浏览阅读315次&#xff0c;点赞3次&#xff0c;收藏3次。环境IDubuntuMakefilemodules:clean:basic.creturn 0;运行效果。https://blog.csdn.net/m0_37132481/article/details/136157384my_netdev.c #include <linux/kernel.h> #incl…

Unity 使用AddListener监听事件与取消监听

在Unity中&#xff0c;有时候我们会动态监听组件中的某个事件。当我们使用代码动态加载多次&#xff0c;每次动态加载后我们会发现原来的和新的事件都会监听&#xff0c;如若我们只想取代原来的监听事件&#xff0c;那么就需要取消监听再添加监听了。 如实现如下需求&#xff…

yml代替properties文件进行springboot项目配置

任务&#xff1a;使用yml格式文件代替properties格式文件进行便捷有效的springboot项目配置。 原先&#xff1a; 在与application.properties文件同级目录下新建application.yml文件&#xff0c;以上配置内容修改为&#xff1a; 注&#xff1a;yml文件的一些编写规范

前端爬虫+可视化Demo

爬虫简介 可以把互联网比做成一张 “大网”&#xff0c;爬虫就是在这张大网上不断爬取信息的程序。 爬虫是请求网站并提取数据的自动化程序。 省流&#xff1a;Demo实现前置知识&#xff1a; JS 基础Node 基础 &#xff08;1&#xff09;爬虫基本工作流程&#xff1a; 向…