当先锋百科网

首页 1 2 3 4 5 6 7

让框架去做http解包封包等,让我们的精力用在应用层开发
MVC模式
M: model,操作数据库gorm
view 视图 处理模板页面
contoller 控制器 路由 +逻辑函数

解决gin相关代码飘红的问题

记得启用gomodule
go env -w GO111MODULE=on

然后到相应目录下执行 go mod init xxx
go mod tidy 
这样应该可以解决代码飘红,说找不到对应包的问题

由于墙的原因 需要更换代理请执行 go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/
  • go语言中init函数会在main函数之前运行而且无需调用自动运行

一、初步使用

  • 搭建好go的环境,目录结构如下
    在这里插入图片描述
//main.go
package main

import "github.com/gin-gonic/gin"
func main()	{
	gin.Default()
}
go mod init quickstart
go mod tidy
  • 如果下载失败,换成golang官方源试试
 go env -w GOPROXY=https://proxy.golang.org,direct
  • 搭建一个基础的后端
//main.go
package main

import "github.com/gin-gonic/gin"
func getuser(ctx *gin.Context){
	ctx.JSON(200,gin.H{
		"username":"you",
	})
}
func main()	{
	//获取引擎对象,即路由对象
	r:=gin.Default()

	//路由映射函数
	r.GET("/user",getuser)
	//启动:默认本机8080端口 类似django的runserver
	r.Run("127.0.0.1:8081")
}

访问相应路径如下
在这里插入图片描述

二、路由系统初识

2.1 服务端对客户端发起各种请求方式的处理

2.1.1 同一个资源的不同请求方式

//路由映射函数同一个路由,不同的方法执行不同的逻辑
r.GET("/book", func(context *gin.Context) {
	context.JSON(200,gin.H{
		"msg":"查询成功",
	})

})
r.POST("/book", func(context *gin.Context) {
	context.JSON(200,gin.H{
		"msg":"新增成功",
	})

})

r.PUT("/book", func(context *gin.Context) {
	context.JSON(200,gin.H{
		"msg":"修改成功",
	})

})

r.DELETE("/book", func(context *gin.Context) {
	context.JSON(200,gin.H{
		"msg":"删除成功",
	})
})

2.1.2 Any

// any请求方式都可以访问
r.Any("/index", func(context *gin.Context) {
	context.JSON(200,gin.H{
		"msg":"任何方式都可以访问",
	})

})

2.1.3 NoRoute


//所有路由都无法访问时,不管何种请求方式,走noroute时返回相应信息
r.NoRoute( func(context *gin.Context) {
	context.JSON(404,gin.H{
		"msg":"404 not find",
	})

})

2.2 路由分组

  • gin框架没有像django那样的路由分组需要自己写 逻辑都差不多 实现解耦,代码具有可读性
  • 当然你需要进入这个目录下 执行
go mod init route
go mod tidy 

在这里插入图片描述
入口

// main.go
package main

import "github.com/gin-gonic/gin"
import . "route/route"

func main()	{
	//获取引擎对象,即路由对象
	r:=gin.Default()
	//初始化路由
	InitBookRoute(r)
	//启动:默认本机8080端口
	r.Run("127.0.0.1:8081")
}

路由层

  • bookRoute:=r.Group(“/books”) 相当于/books赋值给了bookRoute
// route/book.go
package route

import "github.com/gin-gonic/gin"
import . "route/core" //导入业务层函数
func InitBookRoute(r *gin.Engine){
	//路由分组 后面调用时候可以不写前缀了
	bookRoute:=r.Group("/books")
	bookRoute.GET("/", Book)
	bookRoute.POST("/", BookAdd)
	bookRoute.PUT("/", BookEdit)
	bookRoute.DELETE("/", BookDelete)
}

业务层函数

// core/book.go
package core

import "github.com/gin-gonic/gin"

func Book(context *gin.Context) {
	context.JSON(200, gin.H{
		"msg": "查询成功",
	})
}

func BookAdd(context *gin.Context) {
	context.JSON(200, gin.H{
		"msg": "新增成功",
	})
}

func BookEdit(context *gin.Context) {
	context.JSON(200, gin.H{
		"msg": "编辑成功",
	})
}

func BookDelete(context *gin.Context) {
	context.JSON(200, gin.H{
		"msg": "删除成功",
	})
}

三、请求与响应

3.1 请求

3.1.1 请求基本信息

  • 获取请求方式
  • 获取url 此方法是带url玩整路径如?a=1
  • 获取路由 此方法只获取路由 可以对路由做权限
  • 某个请求头
  • 所有请求头
  • 获取远程主机地址
fmt.Println("", context.Request.RemoteAddr)   // 127.0.0.1:50429 带端口
fmt.Println("RemoteIP: ", context.RemoteIP()) //127.0.0.1
fmt.Println("ClientIP: ", context.ClientIP()) //127.0.0.1

3.1.2 获取get请求数据

r.GET("/index", func(context *gin.Context) {

	//获取get请求方式数据 http://127.0.0.1:8081/index?kw=you
	fmt.Println("kw:", context.Query("kw")) //kw: you
	//设置默认值 取不到存在默认值 you
	fmt.Println(context.DefaultQuery("dkw:", "you")) //you
	context.String(200, "index")

	//取get数据如果没有提交则是false
	kw, ok := context.GetQuery("kw")
	if !ok {
		fmt.Println("参数不存在:", ok) //参数不存在: false
	}
	println(kw)

})

3.1.3 获取post请求数据

r.POST("/data", func(context *gin.Context) {
	//PostForm接收form表单的数据 如 Content-Type: application/x-www-form-urlencoded user=youfei&pwd=123 或Content-Type: multipart/form-data;{"pwd":"123","user":"youfei"}
	//如果直接发json数据则不可以 Content-Type: application/json;
	user := context.PostForm("user")
	pwd := context.PostForm("pwd")
	//设置默认值 取不到存在默认值
	fmt.Println(context.DefaultPostForm("user", "'fei"))
	//取get数据如果没有提交则是false
	fmt.Println(context.GetPostForm("user"))
	//获取同一个键的多个值 返回切片
	fmt.Println(context.PostFormArray("user")) //[youfei zhang]

	context.JSON(200, gin.H{
		"user": user,
		"pwd":  pwd,
	})
})

3.1.4 获取shouldbind(常用)

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
)

type User struct {
	User string `json:"name"` //传参传name 标记json就需要传 content-type json
	Pwd  int    `json:"pwd"`
}

func main() {
	r := gin.Default()
	//shouldbind函数 根据content-type解析 需要结构体 常用
	r.POST("/shouldBind", func(context *gin.Context) {
	
		user := User{}
		//关于json的反序列化--映射到结构体对象上去 shouldbind根据结构体里字段的标签来处理是何种类型的content-type 如果是form表单,那么就要标记为 `form`
		context.ShouldBind(&user)
		context.JSON(200, gin.H{
			"data": user,
		})
	})
}

3.2 响应

  • 目录结构

在这里插入图片描述

package main
import "github.com/gin-gonic/gin"
//和django比较像 都是要返回render 一个页面
func main(){
	//返回一个默认的路由引擎
	r := gin.Default()

	//注册templates下所有的html文件
	r.LoadHTMLGlob("templates/*")

	//返回字符串
	r.GET("/test01", func(context *gin.Context) {
		context.String(200,"hello world")
	})

	//返回html code码 html名称 obj 修改静态文件不用重启程序
	r.GET("/test02", func(context *gin.Context) {
		context.HTML(200,"index.html",nil)
		
	})
	//返回json数据
	r.POST("/test03", func(context *gin.Context) {
		context.JSON(200,gin.H{
			"user_id": 1001,
			"username": "you",
		})

	})
	//返回xml数据
	r.POST("/test04", func(context *gin.Context) {
		context.XML(200,gin.H{
			"user_id": 1001,
			"username": "you",
			"friends": []string{"a","b","c"},
		})

	})
	//返回protobuf数据(后面补)
	//返回静态文件(配置)

	//设置静态资源的路径 参数一路由 参数二 本地真实路径
	r.Static("/static","./statics")

	r.Run("127.0.0.1:8081")
}

------------------------------------------------- 2023.328更新-----------------------------------------------------------------------

四、模板语法

和django的模板语法大差不差,这种一般叫做MTV m:指模型orm那类,t指template,模板文件,v指view,视图函数;都素是render一个渲染好的html文件,前端拿去展示。

  • 模板语法函数如下(django直接是 ">="之类) e:equal,lt:less than,gt;great than
    在这里插入图片描述
    项目目录如下
    在这里插入图片描述
  • 变量一律用{{}}包裹

4.1 就是"."

  • {{ . }}
    "."指代 gin.H中所有的变量
//main.go
package main

import "github.com/gin-gonic/gin"

func main(){
	//默认引擎
	r := gin.Default()
	//加载html
	r.LoadHTMLGlob("templates/*")

	name := "you"
	books :=[]string{"曾国藩的中年突围","红星照耀中国","陶庵梦忆"}
	//var stu01 = map[string]string{"name":"you","age":"18"}

	//声明一个map 键是string 值为空接口 啥子都能装
	stu_map := map[string]interface{}{
		"name":"phil","age":18,
	}

	type Person struct {
		Name string //大写外面才能读得到
		Age int64
	}
	person1 := Person{"you",18}
	//结构体的属性(相当于键)不用加 ""
	person2 := []Person{{Name:"you",Age:19},{Name:"san",Age:20}}
	r.GET("index", func(context *gin.Context) {
		context.HTML(200, "index.html",gin.H{
			"name":name,
			"books":books,
			"stu_map":stu_map,
			"Person":person1,
			"person2":person2,

		})
	})
	//启动
	r.Run("127.0.0.1:8081")

}
{{/*index.html*/}}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<p>变量渲染</p>
{{.}}}
<li>{{.name}}</li>
</body>
</html>
  • "."的结果就是gin.H的所有值 是一个大的map 通过 .key来取数据的values) 第一次.key是指gin.H的key 如上面的Person
    在这里插入图片描述

4.2 模板获取变量

  • 参数传递参考第四章节的main.go

<p>取所有值</p>
<li>{{.}}</li>
<p>取某个值</p>
<li>{{.name}}</li>
<p>取切片的索引</p>
<li>{{index .books 0}}</li>
<p>map取值</p>
<li>{{.stu_map.name}}</li>
<p>struct取值</p>
<li>{{.Person.Name}}</li>
<p>管道(age会传到 %v 这个地方)</p>
<li>{{.Person.Age | printf "姓名:%s\n 年龄:%v" "yuan" }}</li>

<h3>条件判断 </h3>
{{/*结尾需要写{{end}}} django是 {{endif}} {{endfor}}*/}}
{{if eq .Person.Age 18}}
    {{index .books 0}}
{{else if lt .Person.Age 18}}
    <p>这是else if</p>
{{end}}

<h3>循环判断</h3>
{{/*<h3> 变量复制使用$符号</h3>*/}}
{{range $key,$value := .stu_map}}
<p>{{$key}}: {{$value}}</p>
{{end}}
{{/*先取到切片在进行操作*/}}
{{range $value := .person2}}
    <p> {{$value.Name}}</p>
{{end}}

在这里插入图片描述

  • 在循环分支语句里是取不到 .里的东西的因此需要重新赋值 会报错
  • 在全局进行赋值 如 {{$localname := .name}}然后再去分支里取
{{/*<p>person2 := []Person{{Name:"you",Age:19},{Name:"san",Age:20}}"</p>*/}}
<h3>在range循环里面使用全局变量需要重新赋值 他是取不到 </h3>
{{$localname := .name}}
<p>{{index .books 0}}</p>
{{$localbook := .books}}

{{range $value := .person2}}
    <p> {{$value.Name}}</p>

{{if eq $value.Name $localname}}
    {{index $localbook 0}}
{{else if eq $value.Age 18}}
    <p>这是else if</p>
{{end}}

{{end}}

在这里插入图片描述

五、gorm的使用

gorm官方文档
yuan老师文档

5.1 创建数据库链接

// 创建数据库链接
//parseTime=True:将 MySQL 中的时间类型(如 datetime、timestamp 等)解析为 Go 中的时间类型(time.Time)。如果不设置此参数,查询出来的时间类型会是 []uint8,需要手动转换为 time.Time。
//loc=Local:指定时区为本地时区。如果不设置此参数,时间会被转换为 UTC 时间,需要手动转换为本地时区
//DSN(Data Source Name),用于指定数据库的连接信息。
dsn := "用户名:密码@tcp(127.0.0.1:3306)/数据库名?charset=utf8mb4&parseTime=True&loc=Local"
newLogger := logger.New(
	log.New(os.Stdout, "\r\n", log.LstdFlags), //标准输出流(os.Stdout) 在每条日志之间添加了一个回车换行符("\r\n"),时间戳格式为标准格式(log.LstdFlags)
	logger.Config{
		LogLevel: logger.Info, //记录的日志级别
	},
)

//db接收*gorm.DB 类型的对象 gorm.Config{} 是 gorm.Open 函数的第二个参数,用于指定 GORM 库的一些配置
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{Logger: newLogger}) //配置了日志器
  • 模型语句 gorm:"primaryKey" 标签会定义字段的信息
//创建模型类
type BaseModel struct {
	ID int `gorm:"primaryKey"`
	Name string `gorm:"type:varchar(32);unique;not null"`
	CreateTime *time.Time `grom:"autoCreateTime"`
	UpdateTime *time.Time `grom:"autoCreateTime"`
}

type Teacher struct{
	BaseModel
	Sno int
	Pwd string `gorm:"type:varchar(100);not null"`
	Tel string `gorm:"type:char(11);"`
	Birth *time.Time
	}
  • 迁移模型类 模型转换为sql

模型会执行下面等语句字段全小写,int 会转换为bigint

上面的表teachers为复数,这是gorm造成的,若想使用单数则需要写个函数

  • 函数名称为TableName 继承Teacher结构体 返回 什么 表的名字就是什么
func (t Teacher)TableName() string  {
	return "teacher"
}

继续运行了一下,存在两张表了
在这里插入图片描述

5.2 gorm标签

  • 主键标签
primaryKey
  • 自增
autoIncrement
  • 默认值0
default:0
  • 注释
comment:主键id
  • 非空
not null
  • 指定列名
column:my_name
  • 一个例子结构体和对应的sql语句
type Teacher struct {
	Id     int `gorm:"primaryKey;autoIncrement;comment:主键id"` //所谓蛇形复数
	Tno    int `gorm:"default:0"`
	Name   string `gorm:"type:varchar(10);not null"`
	Pwd    string `gorm:"type:varchar(100);not null"`
	Tel    string `gorm:"type:char(11);column:my_name"`
	Birth  *time.Time //它的零值(默认值)将是time.Time{},而不是 nil,因为 time.Time 是值类型,它的默认值是其零值。如果你想要在这个字段中存储 NULL 值,就需要使用 *time.Time 类型,并将其设置为 nil。
	Remark string `gorm:"type:varchar(255);"`
	CreatTime *time.Time `gorm:"autoCreateTime;default null"`
	DeletedTime *time.Time `gorm:"default null"`
	UpdateTime  *time.Time `gorm:"autoUpdateTime;default null"`
}
CREATE TABLE `teachers` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '''主键id''',
  `tno` bigint DEFAULT '0',
  `name` varchar(10) COLLATE utf8mb4_general_ci NOT NULL,
  `pwd` varchar(100) COLLATE utf8mb4_general_ci NOT NULL,
  `my_name` char(11) COLLATE utf8mb4_general_ci DEFAULT NULL,
  `birth` datetime(3) DEFAULT NULL,
  `remark` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
  `creat_time` datetime(3) DEFAULT NULL,
  `deleted_time` datetime(3) DEFAULT NULL,
  `update_time` datetime(3) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

5.3 增删改查

5.3.1 增

  • 新增一条数据
	now := time.Now()
	t1 := Teacher{
		Tno:    001,
		Name:   "游",
		Pwd:    "123",
		Tel:    "136",
		Birth:  &now,
		Remark: "第一位老师",
	}
	//新增数据
	res:=db.Create(&t1)
	fmt.Println(t1) // 可以返回修改后的t1 {4 1 游 123 136 2023-04-24 14:15:57.6376609 +0800 CST m=+5.949186501 第一位老师}
	fmt.Println("错误:",res.Error) //错误: <nil>
	fmt.Println("影响行数:",res.RowsAffected) //影响行数: 1

在生产环境中 查询一般使用 db.Model(&Teacher{})来指定要操作的表,结果可以定义为 map[string]interface{}{}var teacher3 Teacher这把返回数据扫描到相应的结构中

res := map[string]interface{}{}
db.Model(&Teacher{}).First(&res)
fmt.Println(res) //这可以把结果扫描到map里 相当于字典 map[birth:2023-04-24 14:19:17.364 +0800 CST creat_time:<nil> deleted_time:<nil> id:1 my_name:136 name:游 pwd:123 remark:第一位老师 tno:1 update_time:<nil>]
  • 条件查询 支持链式调用可以一直., find放在最后即可 find是用来赋值的把sql的值赋值到go
res1 := map[string]interface{}{}
name := "游"
db.Model(&Teacher{}).Where("name=?",name).Where("id=?",1).Limit(10).Order("id desc").Find(&res1) //SELECT * FROM `teachers` WHERE name='游' AND id=1 ORDER BY id desc LIMIT 10
fmt.Println(res1)
  • 实际业务中,一般使用db.Table指定表明 table_name是指go语言中的表明,如struct中存在SsElectricTicket表使了tablename函数
    func (*SsElectricTicket) TableName() string { return "ss_electric_ticket" }那么他的表名就是ss_electric_ticket
deviceAlarm := []DeviceAlarm{}
//var total int64
db.Table("device_alarm a").Select("a.alarm_at").Joins("LEFT JOIN building_door b on b.id=a.door_id").
	Where("a.deleted_at=0 AND type=2 and a.alarm_at BETWEEN UNIX_TIMESTAMP(?) AND (UNIX_TIMESTAMP(?)) AND a.`status`= 1 and b.area_id in ? ",st,et,areaIds).
	Find(&deviceAlarm)
  • Find和count一般都用作结束语放在sql语句的最后

六、gin校验字段

  • 标签binding校验字段 required必填、 email需要符合email规则、gte=1,lte=100 范围值;
package main

import "github.com/gin-gonic/gin"

type Login struct {
	Username string `json:"username" binding:"required"` //binding:"required" 必传项
	Password string `json:"password" binding:"required"`
	Email    string `json:"email" binding:"required,email"` //binding:"email"` 符合email规则
	Age      int    `json:"age" binding:"required,gte=1,lte=100"` // 大于等于1 小于等于100
}

func loginfunc(c *gin.Context) {
	//1 获取参数
	var login Login
	if err:=c.ShouldBind(&login);err!=nil{
		c.JSON(400,gin.H{
			"code": 401,
			"msg": err.Error(),//
		})
		return
	}
	//2 业务逻辑
}

func main(){
	//初始化gin对象
	r:=gin.Default()
	r.GET("/login",loginfunc)
	r.Run(":9999")
}```
- 发起请求,没传age参数,email格式校验不通过
![在这里插入图片描述](https://img-blog.csdnimg.cn/d4b5c3d291454fa084ab04c2d92f1680.png)

参考如下
[作者:Yuan 链接:http://www.yuan316.com/post/Gin%E6%A1%86%E6%9E%B6/](http://www.yuan316.com/post/Gin%E6%A1%86%E6%9E%B6/)