# go-study **Repository Path**: eeee8998/go-study ## Basic Information - **Project Name**: go-study - **Description**: 学习golang过程中笔记整理 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-07-04 - **Last Updated**: 2022-07-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 基础 ### 第一个go程序 让我们从第一个程序运行 来认识go 首先我们创建一个helloword文件夹 在helloword文件夹下创建一个main.go文件 里面内容如下显示 ```go package main import "fmt" func main() { fmt.Println("Hello, World!") } ``` 然后打开终端运行 go run main.go ![image-20220630090559845](/Users/chenboyu/Library/Application Support/typora-user-images/image-20220630090559845.png) 如图显示 终端会显示 打印出Hello, World! ### init函数 和 main函数 go语言中`init`函数用于包`(package)`的初始化 有下面的特征: ``` 1 init函数是用于程序执行前做包的初始化的函数,比如初始化包里的变量等 2 每个包可以拥有多个init函数 3 包的每个源文件也可以拥有多个init函数 4 同一个包中多个init函数的执行顺序go语言没有明确的定义(说明) 5 不同包的init函数按照包导入的依赖关系决定该初始化函数的执行顺序 6 init函数不能被其他函数调用,而是在main函数执行之前,自动被调用 ``` main函数 ``` Go语言程序的默认入口函数(主函数):func main() 函数体用{}一对括号包裹。 func main(){ //函数体 } ``` Init函数和main函数的异同 ``` 相同点: 两个函数在定义时不能有任何的参数和返回值,且Go程序自动调用。 不同点: init可以应用于任意包中,且可以重复定义多个。 main函数只能用于main包中,且只能定义一个。 ``` 两个函数的执行顺序: 对同一个go文件的`init()`调用顺序是从上到下的。 对同一个package中不同文件是按文件名字符串比较“从小到大”顺序调用各文件中的`init()`函数。 对于不同的`package`,如果不相互依赖的话,按照main包中”先`import`的后调用”的顺序调用其包中的`init()`,如果`package`存在依赖,则先调用最早被依赖的`package`中的`init()`,最后调用`main`函数。 如果`init`函数中使用了`println()`或者`print()`你会发现在执行过程中这两个不会按照你想象中的顺序执行。这两个函数官方只推荐在测试环境中使用,对于正式环境不要使用。 ### 变量 和 常量 #### 变量 *第一种方式:直接定义变量类型 如果不赋值 那么他会自动给一个l类型所对应的默认值 * ```go var a int fmt.Println(a) ``` 变量声明未赋值时候默认值常用的有以下几种方式 | 类型 | 默认值 | | ------------------- | ------ | | int ( int32 int64) | 0 | | uint(uint32 uint64) | 0 | | float32 float64 | 0.0 | | bool | false | | string | "" | *第二种:通过 := 定义变量 自动推导类型 但是注意这种方式只能在函数里面定义 并且左侧变量不能是已经命名过得 否则会报错* ```go b := 10 fmt.Println(b) //10 ``` *第三种 var 变量 = value 通过值赋值 定义类型* ```go var c = 11 fmt.Println(c) //11 ``` 多个变量声明。多个变量声明和赋值 ```go var ( className string classAge int ) className = "Go" classAge = 1 fmt.Println(className, classAge) // Go 1 // 多个变量声明 var a, b, c int fmt.Println(a, b, c) // 0 0 0 // 变量声明和赋值 var d, e, f = 1, 2, 3 fmt.Println(d, e, f) // 1 2 3 // 变量声明和赋值 g, h, i := 1, 2, 3 fmt.Println(g, h, i) // 1 2 3 ``` #### 常量 常量就是定义后不会发生变化的变量,go语言中用const关键字声明 ```go const pi = 3.14 const fullScore = 100 ``` 当然常量和变量一样可以多常量一起声明 ```go const ( pi = 3.14 fullScore = 100 ) fmt.Print(pi,fullScore) //3.12 100 ``` #### iota iota是常量中的特殊常量,在定义常量过程中 他会进行累加 ```go package main import "fmt" func main(){ const ( a iota b c d ) fmt.Print(a,b,c,d)//0 1 2 3 } ``` 当然iota也可以进行打断 当我中间打断时候改变类型时候。后面的会用上面的默认值 ```go package main import "fmt" // 常量 const 和 iota func main() { const ( a = iota b c = "张三" d ) fmt.Println(a, b, c, d)// 0 1 张三 张三 } ``` 若我在打断的基础上重新设置iota 那么程序将会在一次进行重新累加 ```go package main import "fmt" // 常量 const 和 iota func main() { const ( a = iota b c = "张三" d = iota e f ) fmt.Println(a, b, c, d, e, f)//0 1 张三 3 4 5 } ``` Iota 默认是从0 累加 当然我们也可以自己定义 通过运算符设置从指定的数字累加 ```go const ( h = iota * 10 i j k l ) fmt.Print(h, i, j, k, l)//0 10 20 30 40 ``` iota常用来定义枚举使用 ```go type Operate int// type 关键字是用来定义类型的 这里意思是 Operate是一个int类型 const ( Add Operate = iota Edit View ) ``` ### 运算符 go中常用的运算符有算术运算符,关系运算符,逻辑运算符,赋值运算符,位运算符,其他运算符 Go 语言内置的运算符: - 算术运算符 - +、-、*、/、%、++、-- - 关系运算符 - ==、!=、>、<、>=、<=、 - 逻辑运算符 - &&、||、! - 位运算符 - &、|、^ - 赋值运算符 - =、+=、-=、*=、/=、%=、<<=、>>=、&=、^=、!= - 其他运算符 - &(取地址)、*(指针取值) ### 基本类型 **布尔型** 布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。 **数字类型** 整型 int 和浮点型 float32、float64,Go 语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码。 | 序号 | 类型和描述 | | :--- | :----------------------------------------------------------- | | 1 | **uint8** 无符号 8 位整型 (0 到 255) | | 2 | **uint16** 无符号 16 位整型 (0 到 65535) | | 3 | **uint32** 无符号 32 位整型 (0 到 4294967295) | | 4 | **uint64** 无符号 64 位整型 (0 到 18446744073709551615) | | 5 | **int8** 有符号 8 位整型 (-128 到 127) | | 6 | **int16** 有符号 16 位整型 (-32768 到 32767) | | 7 | **int32** 有符号 32 位整型 (-2147483648 到 2147483647) | | 8 | **int64** 有符号 64 位整型 (-9223372036854775808 到 9223372036854775807) | **字符串类型:** 字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。 ### 数组和切片 #### 数组 go中数组是固定长度并且是固定类型的序列 如何定义数组 ```go var students [10]string ``` 以上是定义一个students的数组 他是string类型 并且只有10个长度。此时默认值是["",",",",",",",",","] 如果想在定义数组时候初始化值 应该在上面表达式后面添加{},大括号里面的值就是初始化的值 ```go var arr = [3]int{1,2,3} for _,val := range arr{ fmt.Print(val)//1,2,3 } ``` 数组在函数传递参数时候是值传递,函数内部修改不会影响外部数组的值 #### 切片 切片是动态数组,长度不会固定。 ##### 声明方式 声明切片的四种方式: (1)、*var variable []type 可以定义切片 此时切片是一个空[] 没有len 没有cap* 并且切片是一个nil ```go var slice1 []int fmt.Println(slice1) ``` (2)、通过类型推断和make函数结合创建 ```go slice2 := make([]int, 5) //默认五个元素都是0 fmt.Println(slice2) ``` (3)、var variable []type 和make函数结合 ```go var slice3 []int = make([]int, 3) fmt.Println(slice3) ``` (4)、通过类型推到并且初始化数组内容 此时cap和初始化的len一样 ```go slice4 := []int{1, 2, 3} fmt.Println(slice4) ``` len()函数和cap函数 (1)、len()函数是计算切片的长度 当切片初始化时候,会产生一个长度(nil切片除外),我们通常用len()来计算 (2)、cap() 函数是计算切片的容量,make函数的第三个参数就是初始化cap的值,当我们操作切片时候,比如append追加元素时候。如果初始化切片的长度是3,cap也是3.那么在追加一个时候,go会自动扩展cap容量,他会在目前的cap容量追加初始化的cap值。 ##### 切片操作 (1)、追加 append ```go numbers := []int{1, 2, 3} fmt.Println(len(numbers), cap(numbers))// 3 3 numbers = append(numbers, 2) fmt.Println(len(numbers), cap(numbers))// 4 6 ``` (2)、截取 可以通过下标来进行数据截取,具体看下方代码示例: ```go package main import "fmt" func main() { slice := []int{1, 2, 3, 4, 5} // 默认下限0 fmt.Println(slice[:3]) //[1 2 3] // 默认上限 slice的长度 fmt.Println(slice[2:]) //[3 4 5] // 截取2-4的切片 fmt.Println(slice[2:4]) //[3 4] } ``` ### map map的三种声明方式 (1)、*第一种声明方式 此时是没有分配内存空间的 他是nil 在使用前 需要make函数 分配数据空间* ```go var map1 map[string]string fmt.Println(map1) if map1 == nil { fmt.Println("this is nil") } ``` (2)、*第二种方式 直接通过make 生成 后面可以加第二个参数 代表初始化时候分配的key的个数* ```go map2 := make(map[string]string) map2["name"] = "张三" map2["age"] = "18" fmt.Println(map2) ``` (3)、*第三种方式 通过大括号初始化* ```go map3 := map[string]string{ "name": "张三", "age": "18", } fmt.Println(map3) ``` map基本使用方式 添加: ```go map2 := make(map[string]string) map2["name"] = "张三" map2["age"] = "18" ``` 遍历: ```go for key,val := range map2{ fmt.Println("key=",key) fmt.Println("val=",val) } ``` 删除: ```go delete(map2,"age") ``` 修改: ```go map["name"] = "李四" ``` ### 指针 Go语言中的函数传参都是值拷贝,当我们想要修改某个变量的时候,我们可以创建一个指向该变量地址的指针变量。传递数据使用指针,而无须拷贝数据。类型指针不能进行偏移和运算。Go语言中的指针操作非常简单,只需要记住两个符号:`&`(取地址)和`*`(根据地址取值)。 每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。Go语言中使用&字符放在变量前面对变量进行“取地址”操作。 Go语言中的值类型`(int、float、bool、string、array、struct)`都有对应的指针类型,如:`*int、*int64、*string`等 ```go package main import "fmt" func changeNumber(age int) { age = 100 } func changePointerNum(c *int) { *c = 1000 } func main() { age := 10 changeNumber(age) fmt.Println(age) fmt.Println("----------------------------") changePointerNum(&age) fmt.Println(age) } ``` 运行结果如下: ![image-20220702131454775](/Users/chenboyu/Library/Application Support/typora-user-images/image-20220702131454775.png) ### defer defer关键字表示延迟调用,他是在函数执行完毕之后执行defer后面的表达式。 ```go package main import "fmt" func deferFunc1() { fmt.Println("first defer end") } func deferFunc2() { fmt.Println("second defer end") } func deferFunc3() { fmt.Println("third defer end") } func main() { defer deferFunc1() defer deferFunc2() defer deferFunc3() fmt.Println("main end") } ``` 运行结果如下![image-20220702123503783](/Users/chenboyu/Library/Application Support/typora-user-images/image-20220702123503783.png) 分析一下为什么结果是这个: 首先运行main.go文件,程序会运行main函数,而defer是在函数执行完成后才会执行,那么首先会打印 main end。当main函数执行完毕,此时defer会采用压栈的方式,第一个defer 后面的函数 会存到栈的最底下 依次类推 deferFunc3 会在栈的最上方,当所有的defer执行完后,此时栈里面的函数会出栈,第一个出去的是deferFunc3,最后出去的是deferFunc1,所以输出上面的结果。 ## 流程控制 ### 条件语句 #### if - if - if ··· else - if ··· else if ··· else ```go package main import "fmt" func main() { // 根据分数来判断是否及格,如果分数大于90 说明成绩优秀 如果分数在60-90 说明成绩良好 如果分数小于60 说明小明成绩不合格 、 score := 80 if score > 90 { fmt.Printf("%d 分数优秀\n", score) } else if score > 60 { fmt.Printf("%d 分数良好\n", score) } else { fmt.Printf("%d 分数不合格\n", score) } } ``` #### switch switch注意点如下: - 左花括号{必须与switch处于同一行 - 单个case中,可以出现多个结果选项 ```go package main import "fmt" func main() { score := "A" switch score { case "A": fmt.Printf("优秀") case "B": fmt.Printf("良好") case "C": fmt.Printf("一般") case "D": fmt.Printf("不合格") } } ``` ### 循环语句 *用法一:for 赋值语句; 判断条件语句; 赋值变量递增或者递减语句 { }* ```go for i := 0; i < 3; i++ { fmt.Println("i=,", i) } ``` *用法二:for 条件 {}* ```go age := 0 for age < 18 { fmt.Printf("%d\n", age) age++ } ``` *用法三:for {} 死循环 需要配合if 来判断* ```go j := 1 for { j++ fmt.Println("loop j =", j) if j > 10 { break } } ``` *用法四: range 循环* ```go // 这种循环可以对 字符串、数组、切片、字典等进行迭代,获取元素。 // 只获取索引(字典就是键) var a [3]int for i := range a { fmt.Printf("%d %d\n", i, a[i]) } // 获取index 和 value for i, v := range a { fmt.Printf("%d %d\n", i, v) } // 如果不想获取index 索引 则可以用_代替 for _, v := range a { fmt.Println(v) } // 遍历map时候 for key , value := range map {} ages := map[string]int{ "张三": 20, "李四": 32, } for name := range ages { fmt.Printf("%s\t%d\n", name, ages[name]) } ``` ## 函数 ### 定义 go中函数定义是函数名,参数列表, 返回值列表和函数体。如果函数没有返回值,则返回列表可以省略,方式如下 ```go func changeNum () { fmt.Println("first func") } ``` ### 参数 函数定义时候可以传入一个或者多个参数,此时可叫做形参。当调用函数时候传入的参数叫做实参。 函数传参有以下二种方式 (1)、值传递:指在函数调用时候传入的是变量的拷贝,不会影响到外面实际的参数。 ```go package main import "fmt" func changeNumber(n int) { n = 11 } func main() { a := 100 changeNumber(a) fmt.Print(a)//100 } ``` (2)、引用传递:指在函数调用时候传入额是变量的地址,如果函数修改参数 那么会影响外面实际变量 ```go package main import "fmt" func changePointerNumber(n *int) { *n = 11 } func main() { a := 100 changePointerNumber(&a) fmt.Print(a)//11 } ``` ### 匿名函数 ```go package main import "fmt" func main() { // 定义一个匿名函数并且自执行 func() { fmt.Println("this is a func") }() } ``` ### 返回值 ```go package main import "fmt" func foo1(a string, b int) int { fmt.Println("a = ", a) fmt.Println("b = ", b) c := 100 return c } //返回多个返回值,匿名的 func foo2(a string, b int) (int, int) { fmt.Println("a = ", a) fmt.Println("b = ", b) return 666, 777 } //返回多个返回值, 有形参名称的 func foo3(a string, b int) (r1 int, r2 int) { fmt.Println("---- foo3 ----") fmt.Println("a = ", a) fmt.Println("b = ", b) //r1 r2 属于foo3的形参, 初始化默认的值是0 //r1 r2 作用域空间 是foo3 整个函数体的{}空间 fmt.Println("r1 = ", r1) fmt.Println("r2 = ", r2) //给有名称的返回值变量赋值 r1 = 1000 r2 = 2000 return } func foo4(a string, b int) (r1, r2 int) { fmt.Println("---- foo4 ----") fmt.Println("a = ", a) fmt.Println("b = ", b) //给有名称的返回值变量赋值 r1 = 1000 r2 = 2000 return } func main() { c := foo1("abc", 555) fmt.Println("c = ", c) ret1, ret2 := foo2("haha", 999) fmt.Println("ret1 = ", ret1, " ret2 = ", ret2) ret1, ret2 = foo3("foo3", 333) fmt.Println("ret1 = ", ret1, " ret2 = ", ret2) ret1, ret2 = foo4("foo4", 444) fmt.Println("ret1 = ", ret1, " ret2 = ", ret2) } ``` ## 面向对象 ### type关键字 go 中可以通过type来声明一个数据类型,比如: ```go type myString string ``` 以上表示我声明一个myString类型 他是string一种别名 ### 结构体struct #### 定义 结构体可以理解为把多个不同的类型结合成一个复杂类型,结构体与数组一样,都是值传递 ```go type Animal struct { Name string Category string } ``` #### 使用 *实例化struct 的几种方式:* (1)、*定义一个struct 通过var 定义的 结构体 并未初始化,他是一个默认的结构体 他不是nil* ```go var a1 Animal a1.Name = "小黑" a1.Category = "dog" ``` (2)、*简短定义 直接赋值属性* ```go var a2 = Animal{ Name: "小白", Category: "cat", } fmt.Println(a2) ``` (3)、*结构体后面加 {},表示声明结构体* ```go var a3 = Animal{} a3.Name = "小红" a3.Category = "pig" fmt.Println(a3) ``` (4)、通过go内置的new方法,来分配内存初始化。new后可以直接访问。 ```go var a4 = new(Animal) a4.name = "花花" ``` #### 访问字段 通过变量名,使用逗号`(.)`,可以访问结构体类型中的字段,或为字段赋值,也可以对字段进行取址(&)操作。 ```go fmt.Println(a3.name)//输出:小红 a3.name = "小花" fmt.Println(a3.name)//输出:小花 category := &a3.Category *category = "cat" fmt.Println(a3.Category)//cat ``` #### func go可以给struct设置方法 格式是 func() method() {} ```go func (a *Animal) ChangeName(name string) { a.Name = name } func main(){ var a3 = Animal{} a3.Name = "小红" a3.Category = "pig" fmt.Println(a3) a3.ChangeName("花花") fmt.Println(a3)//{花花 pig} } ``` #### 继承 go可以通过在struct中把需要继承的struct直接当属性写入 即可继承 先定义父类: ```go // 父类 type Human struct { Name string Age int } func (human *Human) Eat() { fmt.Println("human eat") } func (human *Human) Sleep() { fmt.Println("human sleep") } ``` 在定义子类: ```go //子类 type Student struct { Human Score int } // 可以重写父类的eat 方法 func (student *Student) Eat() { fmt.Println("student eat") } ``` 继承的关键语句在于在定义子类struct时候把父类写进去。 完全代码如下: ```go package main import "fmt" // 父类 type Human struct { Name string Age int } func (human *Human) Eat() { fmt.Println("human eat") } func (human *Human) Sleep() { fmt.Println("human sleep") } // ---------------------------------------------------------------- //子类 type Student struct { Human Score int } // 可以重写父类的eat 方法 func (student *Student) Eat() { fmt.Println("student eat") } func main() { var xiaoming Student xiaoming.Name = "xiaoming" xiaoming.Eat() xiaoming.Sleep() } ``` 上面代码运行结果为:![image-20220702202212823](/Users/chenboyu/Library/Application Support/typora-user-images/image-20220702202212823.png) ### 接口interface 在Go语言中,如果自定义类型(比如`struct`)实现了某个`interface`中的所有方法,那么就可以说这个类型实现了这个接口。 ```go package main import "fmt" type Animal interface { Eat() GetType() string GetName() string } type Dog struct { } func (dog *Dog) Eat() { } func (dog *Dog) GetType() string { return "dog" } func (dog *Dog) GetName() string { return "xiaohua" } func main() { dog := new(Dog) fmt.Println(dog.GetName()) fmt.Println(dog.GetType()) fmt.Println("interface") } ``` interface可以作为函数传参万能类型 ### reflect 变量结构包括type和value,type表示变量对应的类型,value就是变量的值 reflect.Type和reflect.Value Reflect.Type用来获取数据类型的type,reflect.Value用来获取具体的值。下面结合解析结构体标签tag来理解下。 ```go package main import ( "fmt" "reflect" ) type Persons struct { Name string `json:"name" info:"name"` Age int `json:"age"` Sex int `json:"sex" info:"sex"` } func findTag(str interface{}) { t := reflect.TypeOf(str).Elem() for i := 0; i < t.NumField(); i++ { tag := t.Field(i).Tag.Get("info") fmt.Println(tag) } } func main() { var person Persons findTag(&person) } ``` ## 并发 ### 介绍 #### 进程和线程 ``` A. 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。 B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。 C.一个进程可以创建和撤销多个线程;同一个进程中的多个线程之间可以并发执行。 ``` #### 并发和并行 ``` A. 多线程程序在一个核的cpu上运行,就是并发。 B. 多线程程序在多个核的cpu上运行,就是并行。 ``` ### goroutine Go语言中使用goroutine非常简单,只需要在调用函数的时候在前面加上go关键字,就可以为一个函数创建一个goroutine。 一个goroutine必定对应一个函数,可以创建多个goroutine去执行相同的函数。 创建第一个goroutine ```go package main import "fmt" func subGo() { i := 0 // for { i++ fmt.Println("i=", i) } } func main() { go subGo() j := 0 // for { j++ fmt.Println("main j=", j) } } ``` ![image-20220702220745227](/Users/chenboyu/Library/Application Support/typora-user-images/image-20220702220745227.png) ### channel #### 定义 channel是2个goroutine之间通信的桥梁。可以通过go内置的make函数创建。 ```go c:= make(chan int) ``` #### 数据发送和接收 ```go package main import "fmt" func main() { c := make(chan int) go func() { defer fmt.Println("sub goroutine end") fmt.Println("sub goroutine正在执行") c <- 1 // 表示把value写入管道c }() fmt.Println("main 正在执行") mainNum := <-c fmt.Println("c=", mainNum) fmt.Println("main goroutine is end") } ``` 执行之后结果如下: ![image-20220702222012044](/Users/chenboyu/Library/Application Support/typora-user-images/image-20220702222012044.png) 现在来解释下上面执行结果: 调用main函数时候:go 对应的函数是一个子go程,还没有挂起时候,main里面会先打印。当mainNum接收c的值时候,要等子进程进行c写值。go进程中会先打印,然后将值写入c,函数结束后执行defer。当main接收后 才会往下进行。总结一下:当c为无缓存channel时候。main程和sub程时候谁先执行到写值和传值时候要阻塞(也叫锁),要等到双方数据打通后 才会把锁打开,程序才能继续下去 #### 有缓冲和无缓冲 带缓冲区channel:定义声明时候制定了缓冲区大小(长度),可以保存多个数据。 不带缓冲区channel:只能存一个数据,并且只有当该数据被取出时候才能存下一个数据。 ```go ch := make(chan int) //不带缓冲区 ch := make(chan int ,10) //带缓冲区 ``` 带缓存通道当数据写入次数到达定义的长度时候 这时候会堵塞,知道外面接收后,才会继续写入。 #### close go中可以通过close关键字关闭通道,当通道关闭后 不能在继续写入。 #### range 循环管道 需要注意的是: - 使用range循环管道,如果管道未关闭会引发deadlock错误。 - 如果采用for死循环已经关闭的管道,当管道没有数据时候,读取的数据会是管道的默认值,并且循环不会退出。 ``` package main import ( "fmt" ) func main() { rangechannel := make(chan int,4) for i := 0;i < 4;i++{ rangechannel <- i } close(rangechannel) fmt.Println("data lenght: ",len(rangechannel)) for v,ok := range rangechannel { //循环管道 fmt.Println(v) } fmt.Printf("data lenght: %d",len(rangechannel)) } ``` ### ## gin ### 第一个gin程序 ```go package main import "github.com/gin-gonic/gin" func main() { router := gin.Default() router.GET("/", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "hello world!", }) }) router.Run(":8088") } ``` ![image-20220703130317780](/Users/chenboyu/Library/Application Support/typora-user-images/image-20220703130317780.png) ![image-20220703130341668](/Users/chenboyu/Library/Application Support/typora-user-images/image-20220703130341668.png) ### 请求的几种方式 #### get ```go router.GET("/", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "hello world!", }) }) ``` #### post ```go router.POST("/", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "hello world!", }) }) ``` #### delete ```go router.DELETE("/", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "hello world!", }) }) ``` #### put ```go router.PUT("/", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "hello world!", }) }) ``` ### query和form #### query - URL参数可以通过DefaultQuery()或Query()方法获取 - DefaultQuery()若参数不存在,返回默认值。Query()不存在返回的是空串 ```go package main import ( "github.com/gin-gonic/gin" ) // urlQuery 获取 url中?后面携带的参数 func urlQueryHandle(ctx *gin.Context) { // 如果指定的key 没有对应的值就使用默认值 username1 := ctx.DefaultQuery("username", "张三") // 根据key 获取值 username2 := ctx.Query("username") address := ctx.Query("address") // 返回JSON 字符串 ctx.JSON(200, gin.H{ "message": "ok", "username1": username1, "username2": username2, "address": address, }) } func main() { // 创建默认路由 r := gin.Default() // 绑定路由规则 r.GET("/user", urlQueryHandle) // 监听端口 r.Run(":8088") } ``` ![image-20220704135336589](/Users/chenboyu/Library/Application Support/typora-user-images/image-20220704135336589.png) #### form表单参数 Gin 在post请求时候接收的参数 一般通过ctx.PostForm("xx字段获取") ```go func getFormJsonHandler(ctx *gin.Context) { username := ctx.PostForm("username") // 返回JSON 字符串 ctx.JSON(200, gin.H{ "message": "ok", "username": username, }) } ``` ### 文件上传 #### 单个文件 ```go package main import ( "fmt" "log" "net/http" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() // 为 multipart forms 设置较低的内存限制 (默认是 32 MiB) router.MaxMultipartMemory = 8 << 20 // 8 MiB router.POST("/upload", func(c *gin.Context) { // 单文件 file, _ := c.FormFile("file") log.Println(file.Filename) dst := "./" + file.Filename // 上传文件至指定的完整文件路径 c.SaveUploadedFile(file, dst) c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename)) }) router.Run(":8088") } ``` 测试效果: ![image-20220703143129587](/Users/chenboyu/Library/Application Support/typora-user-images/image-20220703143129587.png) #### 多个文件 ```go package main import ( "fmt" "net/http" "github.com/gin-gonic/gin" ) // gin的helloWorld func main() { // 1.创建路由 // 默认使用了2个中间件Logger(), Recovery() r := gin.Default() // 限制表单上传大小 8MB,默认为32MB r.MaxMultipartMemory = 8 << 20 r.POST("/upload", func(c *gin.Context) { form, err := c.MultipartForm() if err != nil { c.String(http.StatusBadRequest, fmt.Sprintf("get err %s", err.Error())) } // 获取所有图片 files := form.File["files"] // 遍历所有图片 for _, file := range files { // 逐个存 if err := c.SaveUploadedFile(file, file.Filename); err != nil { c.String(http.StatusBadRequest, fmt.Sprintf("upload err %s", err.Error())) return } } c.String(200, fmt.Sprintf("upload ok %d files", len(files))) }) //默认端口号是8080 r.Run(":8088") } ``` 测试效果: ![image-20220703142002850](/Users/chenboyu/Library/Application Support/typora-user-images/image-20220703142002850.png) ### 使用中间件 gin可以在访问路由前做一些权限认证,通过use使用中间件: ```go package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/", func(c *gin.Context) { c.JSON(200, "首页") }) adminGroup := r.Group("/admin") adminGroup.Use(gin.BasicAuth(gin.Accounts{ "admin": "123456", })) adminGroup.GET("/index", func(c *gin.Context) { c.JSON(200, "后台首页") }) r.Run(":8088") } ``` ### 路由分组 ```go package main import "github.com/gin-gonic/gin" func main() { router := gin.Default() // 简单的路由组: v1 v1 := router.Group("/v1") { v1.POST("/login", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "hello login!", }) }) v1.POST("/submit", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "hello submit!", }) }) } // 简单的路由组: v2 v2 := router.Group("/v2") { v2.POST("/login", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "hello login!", }) }) v2.POST("/submit", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "hello submit!", }) }) } router.Run(":8088") } func loginController() { } ```