GO
环境配置
go环境
下载go并安装(win下),环境变量他自己要配置上
https://dl.google.com/go/go1.21.3.windows-amd64.msi
验证是否安装成功:
//打开cmd
go version
VSCODE环境
下载VSCODE…略
配置VSCODE的环境
下载插件
go开发工具包
打开cmd,或者VSCODE自带的终端,逐条复制
//打开go mod ,go mod相当于java的maven
go env -w GO111MODULE=on
//设置代理
go env -w GOPROXY=https://goproxy.cn,direct
然后再在VSCODE上下载工具包
Ctrl+Shift+P
输入go
点第一个,安装工具包
全选,确定
等待下载
go mod
go modules是官方推荐的替换GOPATH而诞生的依赖库管理工具(相当于java的maven),之前的包都放在GOPATH了
首先自己下载的go路径应该是在C盘
go env
这样创建一个初始化案例(先转到需要初始化的文件下)
go mod init example.com/m
发现出现了像配置文件的东西,这是用于记录依赖库和版本号的东西
如果使用网上说的初始化
PS D:\work\GoWork\modtest> go mod init
go: cannot determine module path for source directory D:\work\GoWork\modtest (outside GOPATH, module path must be specified)Example usage:'go mod init example.com/m' to initialize a v0 or v1 module 'go mod init example.com/m/v2' to initialize a v2 module Run 'go help mod init' for more information.
会提示没有指定module
意思是后面要取一个模块的名字,打开生成的go.mod文件就能看见,说明这个模块名字是module
GO的语法
GO的小知识点
go的程序中一行就表示一个语句的结束,如果要多个语句写一行就必须用";"人为区分,实际开发中不用写分号
go的注释
//单行注释/*多行注释
*/
标识符:用来命名变量.类型等程序实体,一个标识符就是一个或多个字母和数字,下划线组成,第一个字符不能是数字(就是java的变量名)
字符串连接
package main
import "fmt"
func main() {fmt.Println("Google" + "Runoob")//输出GoogleRunoob
}
GO的空格
分隔标识符、关键字、运算符和表达式,以提高代码的可读性
一般VSCODE会自动帮忙填空格
在函数调用时,函数名和左边等号之间要使用空格,参数之间也要使用空格。
例如:
result := add(2, 3)
关键字和保留字
关键字
下面列举了 Go 代码中会使用到的 25 个关键字或保留字:
break | default | func | interface | select |
---|---|---|---|---|
case | defer | go | map | struct |
chan | else | goto | package | switch |
const | fallthrough | if | range | type |
continue | for | import | return | var |
除了以上介绍的这些关键字,Go 语言还有 36 个预定义标识符:
append | bool | byte | cap | close | complex | complex64 | complex128 | uint16 |
---|---|---|---|---|---|---|---|---|
copy | false | float32 | float64 | imag | int | int8 | int16 | uint32 |
int32 | int64 | iota | len | make | new | nil | panic | uint64 |
println | real | recover | string | true | uint | uint8 | uintptr |
程序一般由关键字、常量、变量、运算符、类型和函数组成。
程序中可能会使用到这些分隔符:括号 (),中括号 [] 和大括号 {}。
程序中可能会使用到这些标点符号:.、,、;、: 和 …。
数据类型
按照语言分:
序号 | 类型和描述 |
---|---|
1 | 布尔型 布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。 |
2 | 数字类型 整型 int 和浮点型 float32、float64,Go 语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码。 |
3 | 字符串类型: 字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。 |
4 | 派生类型: 包括:(a) 指针类型(Pointer)(b) 数组类型© 结构化类型(struct)(d) Channel 类型(e) 函数类型(f) 切片类型(g) 接口类型(interface)(h) Map 类型 |
数字类型
Go 也有基于架构的类型,例如:int、uint 和 uintptr。
序号 | 类型和描述 |
---|---|
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) |
浮点型
序号 | 类型和描述 |
---|---|
1 | float32 IEEE-754 32位浮点型数 |
2 | float64 IEEE-754 64位浮点型数 |
3 | complex64 32 位实数和虚数 |
4 | complex128 64 位实数和虚数 |
其他数字类型
以下列出了其他更多的数字类型:
序号 | 类型和描述 |
---|---|
1 | byte 类似 uint8 |
2 | rune 类似 int32 |
3 | uint 32 或 64 位 |
4 | int 与 uint 一样大小 |
5 | uintptr 无符号整型,用于存放一个指针 |
四种变量的声明方式
单变量,多变量,全局变量,变量类型
package mainimport ("fmt"
)// 声明全局变量 方法一,方法二,方法三
var gA int = 100
var gB = "qwd"//方法四的 :=只能用在函数体内
//gc :=200func main() {//方法一:声明一个变量,默认值是0var a intfmt.Println("a = ", a)//方法二:声明一个变量,初始化一个值var b int = 100fmt.Println("b = ", b)//方法三:初始化值的时候,省去数据类型,自动匹配数据类型var c = 1fmt.Println("c =", c)var d = "1"fmt.Printf("d=%s ,type of d = %T\n", d, d) //输出这里用的Printf,,输出类型//方法四,常用的,省去var关键字,自动匹配e := 100.12fmt.Println("e=", e)//声明多变量//var xx,yy int=100,120var xx, yy = "a0", 12fmt.Println("xx=", xx, ",yy=", yy)//多行的,多变量声明var (vv int = 100jj bool = true)fmt.Println("vv=", vv, ",jj=", jj)}
int的零值为0
bool的零值为false
字符串的零值为""
下面类型的零值为nil
var a *int
var a []int
var a map[string] int
var a chan int
var a func(string) int
var a error // error 是接口
值与引用
使用等号就是将一个变量的值赋值给另一个变量,例如j=i,实际上是在内存中将i的值进行了拷贝
获取内存地址: &i
也称为指针
变量需要被使用,不然会报错
极简的混合赋值:
a, b, c := 5, 7, "abc"
格式化从字符串
Go 语言中使用 fmt.Sprintf 或 fmt.Printf 格式化字符串并赋值给新串:
- Sprintf 根据格式化参数生成格式化的字符串并返回该字符串。
- Printf 根据格式化参数生成格式化的字符串并写入标准输出。
GO语言常量
在程序运行时不会改变的量
const b = "ab"
常量枚举
const(Unknown=0Female=1Male=2
)
常量函数使用
可以使用len(),cap(), unsafe.Sizeof()计算值,函数必须是内置函数
package mainimport "unsafe"
const (a = "abc"b = len(a)c = unsafe.Sizeof(a)
)func main(){println(a, b, c)
}
iota(特殊常量)
可被编译器修改的常量,在const关键词出现时重置为0,
在const中每新增一行常量声明就使iota计数一次
用法:
package mainimport "fmt"func main() {const (a = iota //0b //1c //2d = "ha" //独立值,iota += 1e //"ha" iota += 1f = 100 //iota +=1g //100 iota +=1h = iota //7,恢复计数i //8)fmt.Println(a,b,c,d,e,f,g,h,i)//0 1 2 ha ha 100 100 7 8
}
GO运算符
算术运算符
下表列出了所有Go语言的算术运算符。假定 A 值为 10,B 值为 20。
运算符 | 描述 | 实例 |
---|---|---|
+ | 相加 | A + B 输出结果 30 |
- | 相减 | A - B 输出结果 -10 |
* | 相乘 | A * B 输出结果 200 |
/ | 相除 | B / A 输出结果 2 |
% | 求余 | B % A 输出结果 0 |
++ | 自增 | A++ 输出结果 11 |
– | 自减 | A-- 输出结果 9 |
关系运算符
下表列出了所有Go语言的关系运算符。假定 A 值为 10,B 值为 20。
运算符 | 描述 | 实例 |
---|---|---|
== | 检查两个值是否相等,如果相等返回 True 否则返回 False。 | (A == B) 为 False |
!= | 检查两个值是否不相等,如果不相等返回 True 否则返回 False。 | (A != B) 为 True |
> | 检查左边值是否大于右边值,如果是返回 True 否则返回 False。 | (A > B) 为 False |
< | 检查左边值是否小于右边值,如果是返回 True 否则返回 False。 | (A < B) 为 True |
>= | 检查左边值是否大于等于右边值,如果是返回 True 否则返回 False。 | (A >= B) 为 False |
<= | 检查左边值是否小于等于右边值,如果是返回 True 否则返回 False。 | (A <= B) 为 True |
逻辑运算符
下表列出了所有Go语言的逻辑运算符。假定 A 值为 True,B 值为 False。
运算符 | 描述 | 实例 |
---|---|---|
&& | 逻辑 AND 运算符。 如果两边的操作数都是 True,则条件 True,否则为 False。 | (A && B) 为 False |
|| | 逻辑 OR 运算符。 如果两边的操作数有一个 True,则条件 True,否则为 False。 | (A || B) 为 True |
! | 逻辑 NOT 运算符。 如果条件为 True,则逻辑 NOT 条件 False,否则为 True。 | !(A && B) 为 True |
位运算符
位运算符对整数在内存中的二进制位进行操作。
下表列出了位运算符 &, |, 和 ^ 的计算:
p | q | p & q | p | q | p ^ q |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 1 | 1 |
1 | 1 | 1 | 1 | 0 |
1 | 0 | 0 | 1 | 1 |
赋值运算符
下表列出了所有Go语言的赋值运算符。
运算符 | 描述 | 实例 |
---|---|---|
= | 简单的赋值运算符,将一个表达式的值赋给一个左值 | C = A + B 将 A + B 表达式结果赋值给 C |
+= | 相加后再赋值 | C += A 等于 C = C + A |
-= | 相减后再赋值 | C -= A 等于 C = C - A |
*= | 相乘后再赋值 | C *= A 等于 C = C * A |
/= | 相除后再赋值 | C /= A 等于 C = C / A |
%= | 求余后再赋值 | C %= A 等于 C = C % A |
<<= | 左移后赋值 | C <<= 2 等于 C = C << 2 |
>>= | 右移后赋值 | C >>= 2 等于 C = C >> 2 |
&= | 按位与后赋值 | C &= 2 等于 C = C & 2 |
^= | 按位异或后赋值 | C ^= 2 等于 C = C ^ 2 |
|= | 按位或后赋值 | C |= 2 等于 C = C | 2 |
下表列出了Go语言的其他运算符。
运算符 | 描述 | 实例 |
---|---|---|
& | 返回变量存储地址 | &a; 将给出变量的实际地址。 |
* | 指针变量。 | *a; 是一个指针变量 |
条件运算符
Go 语言提供了以下几种条件判断语句:
语句 | 描述 |
---|---|
if 语句 | if 语句 由一个布尔表达式后紧跟一个或多个语句组成。 |
if…else 语句 | if 语句 后可以使用可选的 else 语句, else 语句中的表达式在布尔表达式为 false 时执行。 |
if 嵌套语句 | 你可以在 if 或 else if 语句中嵌入一个或多个 if 或 else if 语句。 |
switch 语句 | switch 语句用于基于不同条件执行不同动作。 |
select 语句 | select 语句类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。 |
注意:Go 没有三目运算符,所以不支持 ?: 形式的条件判断。
switch var1 {case val1:...case val2:...default:...
}
循环语句
计算1到10的数字之和
//没得括号,而且{必须放在后面
package mainimport "fmt"func main() {sum := 0for i := 0; i <= 10; i++ {sum += i}fmt.Println(sum)
}
使用循环外变量
package mainimport "fmt"func main() {sum := 1for ; sum <= 10; {sum += sum}fmt.Println(sum)// 这样写也可以,更像 While 语句形式for sum <= 10{sum += sum}fmt.Println(sum)
}
死循环
就一个for加花括号
package mainimport "fmt"func main() {sum := 0for {sum++ // 无限循环下去}fmt.Println(sum) // 无法输出
}
停止死循环就用 ctrl+C
迭代输出
package mainimport "fmt"func main() {strings := []string{"google", "runoob"}for i, s := range strings {fmt.Println(i, s)}numbers := [6]int{1, 2, 3, 5}for i, x := range numbers {fmt.Printf("第 %d 位 x 的值 = %d\n", i, x)}
}
迭代输出省略key和value
package main
import "fmt"func main() {map1 := make(map[int]float32)map1[1] = 1.0map1[2] = 2.0map1[3] = 3.0map1[4] = 4.0// 读取 key 和 valuefor key, value := range map1 {fmt.Printf("key is: %d - value is: %f\n", key, value)}// 读取 keyfor key := range map1 {fmt.Printf("key is: %d\n", key)}// 读取 valuefor _, value := range map1 {fmt.Printf("value is: %f\n", value)}
}
输出
key is: 4 - value is: 4.000000
key is: 1 - value is: 1.000000
key is: 2 - value is: 2.000000
key is: 3 - value is: 3.000000
key is: 1
key is: 2
key is: 3
key is: 4
value is: 1.000000
value is: 2.000000
value is: 3.000000
value is: 4.000000
函数
最少有个main()函数
命名格式:
func function_name( [parameter list] ) [return_types] {函数体
}
func:函数由 func 开始声明
function_name:函数名称,参数列表和返回值类型构成了函数签名。
parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
函数体:函数定义的代码集合。
例子:
/* 函数返回两个数的最大值 */
func max(num1, num2 int) int {/* 声明局部变量 */var result intif (num1 > num2) {result = num1} else {result = num2}return result
}
GO函数可以返回多个值
package mainimport "fmt"func swap(x, y string) (string, string) {return y, x
}func main() {a, b := swap("Google", "Runoob")fmt.Println(a, b)
}
函数的值传递
/* 定义相互交换值的函数 */
func swap(x, y int) int {var temp inttemp = x /* 保存 x 的值 */x = y /* 将 y 值赋给 x */y = temp /* 将 temp 值赋给 y*/return temp;
}
后面的int是指(int,int),好像可以简写
引用传递
/* 定义交换值函数*/
func swap(x *int, y *int) {var temp inttemp = *x /* 保持 x 地址上的值 */*x = *y /* 将 y 值赋给 x */*y = temp /* 将 temp 值赋给 y */
}
变量作用域
函数内定义的变量称为局部变量
函数外定义的变量称为全局变量
函数定义中的变量称为形式参数
数组
例子:
var balance [10]float32
var numbers [5]int
var numbers = [5]int{1, 2, 3, 4, 5}
numbers := [5]int{1, 2, 3, 4, 5}
多维数组:
a := [3][4]int{ {0, 1, 2, 3} , /* 第一行索引为 0 */{4, 5, 6, 7} , /* 第二行索引为 1 */{8, 9, 10, 11}, /* 第三行索引为 2 */
}var threedim [5][10][4]int
函数里的数组:
void myFunction(param []int)
{
.
.
.
}
指针
取地址符&
取值 *
package mainimport "fmt"func main() {var a int= 20 /* 声明实际变量 */var ip *int /* 声明指针变量 */ip = &a /* 指针变量的存储地址 */fmt.Printf("a 变量的地址是: %x\n", &a )/* 指针变量的存储地址 */fmt.Printf("ip 变量储存的指针地址: %x\n", ip )/* 使用指针访问值 */fmt.Printf("*ip 变量的值: %d\n", *ip )
}
空指针
if(ptr != nil) /* ptr 不是空指针 */
if(ptr == nil) /* ptr 是空指针 */
指针交换
package mainimport "fmt"func main() {/* 定义局部变量 */var a int = 100var b int= 200a, b = b, afmt.Printf("交换后 a 的值 : %d\n", a )fmt.Printf("交换后 b 的值 : %d\n", b )
}
结构体
相当于对象
package mainimport "fmt"type Books struct {title stringauthor stringsubject stringbook_id int
}func main() {// 创建一个新的结构体fmt.Println(Books{"Go 语言", "www.runoob.com", "Go 语言教程", 6495407})// 也可以使用 key => value 格式fmt.Println(Books{title: "Go 语言", author: "www.runoob.com", subject: "Go 语言教程", book_id: 6495407})// 忽略的字段为 0 或 空fmt.Println(Books{title: "Go 语言", author: "www.runoob.com"})//访问结构成员var book1 Booksbook1.author = "这是作者"book1.book_id = 123fmt.Println(book1)fmt.Println(book1.author)}
Go语言切片
相当于arraylist,可以动态追加元素的数组(动态数组)
切片不需要说明长度。
或使用 make() 函数来创建切片:
var slice1 []type = make([]type, len)也可以简写为slice1 := make([]type, len)
len()和cap()函数
len()是计算长度的,cap()是测量切片最长长度(额外长度?)
切片截取
package mainimport "fmt"func main() {/* 创建切片 */numbers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8}printSlice(numbers)/* 打印原始切片 */fmt.Println("numbers ==", numbers)/* 打印子切片从索引1(包含) 到索引4(不包含)*/fmt.Println("numbers[1:4] ==", numbers[1:4])/* 默认下限为 0*/fmt.Println("numbers[:3] ==", numbers[:3])/* 默认上限为 len(s)*/fmt.Println("numbers[4:] ==", numbers[4:])numbers1 := make([]int, 0, 5)printSlice(numbers1)/* 打印子切片从索引 0(包含) 到索引 2(不包含) */number2 := numbers[:2]printSlice(number2)/* 打印子切片从索引 2(包含) 到索引 5(不包含) */number3 := numbers[2:5]printSlice(number3)}func printSlice(x []int) {fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}
append()和copy()
例:
numbers = append(numbers,-2)//追加数字"-2"
numbers = append(numbers,-2,2,99)//追加数字"-2,2,99"//创建切片numbers1,为上个切片的两倍容量
numbers1 := make([]int, len(numbers), (cap(numbers))*2)//从numbers拷贝到numbers1
copy(numbers1,numbers)
Range(迭代元素)
用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素
for key, value := range numbers {fmt.Printf("%d-->%d", key, value)}//key在map之类的正常用,在切片/数组之类的key是数组索引
可以迭代字符串,迭代出来是Unicode
Map
键值对
//创建一个空map,键是string,值是int
m := make(map[string]int)
//创建带有初始容量的
m := make(map[string]int,10)
//创建自带键值对的
// 使用字面量创建 Map
m := map[string]int{"apple": 1,"banana": 2,"orange": 3,
}// 获取键值对
value := m["apple"]
value, ok := m["pear"]
//如果键不存在,ok 的值为 false,v2 的值为该类型的零值
//这里不能用bool,因为bool是关键字
//也记得不要取名为map,map也是关键字,小写有些是关键字//修改元素
m["apple"]=5//获取Map的长度
len:=len(m)//遍历
for k,v:=range m{fmt.Printf("key=%s,value=%d\n",k,v)
}//删除元素
delete(m,"banana")
递归
记得写好退出条件
func recursion() {recursion() /* 函数调用自身 */
}func main() {recursion()
}
类型转换
整形转浮点
var a int = 10
var b float64 = float64(a)
//貌似像强转,但是换了括号位置
字符串类型转换
字符串转成整数
var str string="10"
var num int
num,_=strconv.Atoi(str)
//conv的意思是转换器/卷积
//Atoi的意思是ASCII to Integer
整数转换成字符串
num := 123
str := strconv.Itoa(num)
浮点转换字符串
num := 3.14
str := strconv.FormatFloat(num, 'f', 2, 64)
//f是格式说明符, 2是精度 小数点后两位, 64是表示浮点的位数 表示双精度
可以兼容的转换(int8转int64之类的)
var i8 int8 = 12var i64 = int64(i)
接口
接口可以让我们将不同类型绑定到一组公共方法上,从而实现多态和灵活的设计
Go的接口是隐式实现的(如果一个类型实现了一个接口定义的所有方法,那么它就自动地实现了该接口)
package mainimport ("fmt"
)//接口类型
type Phone interface {call()
}//诺基亚手机类型,用结构体代替对象
type NokiaPhone struct {str string
}//诺基亚手机的call方法 ,带有参数,是为了类似于java中的this
func (nokiaPhone NokiaPhone) call() {nokiaPhone.str = "I am Nokia"fmt.Println(nokiaPhone.str, ", I can call you! ")
}//苹果手机的结构体
type IPhone struct {}//苹果手机的call方法
func (iPhone IPhone) call() {fmt.Println("I am iPhone, I can call you!")
}func main() {var phone Phonephone = new(NokiaPhone)phone.call()phone = new(IPhone)phone.call()}
错误处理
内置错误接口提供了简单的错误处理机制
error类型是一个接口类型
在返回值的后面加上错误处理
func Sqrt(f float64) (float64, error) {if f < 0 {return 0, errors.New("math: square root of negative number")}// 实现
}
自定义异常结构(类)
package mainimport ("fmt"
)// 定义一个 DivideError 结构
type DivideError struct {dividee intdivider int
}// 实现 `error` 接口
func (de *DivideError) Error() string {strFormat := `Cannot proceed, the divider is zero.dividee: %ddivider: 0
`return fmt.Sprintf(strFormat, de.dividee)
}// 定义 `int` 类型除法运算的函数
func Divide(varDividee int, varDivider int) (result int, errorMsg string) {if varDivider == 0 {dData := DivideError{dividee: varDividee,divider: varDivider,}errorMsg = dData.Error()return} else {return varDividee / varDivider, ""}}func main() {// 正常情况if result, errorMsg := Divide(100, 10); errorMsg == "" {fmt.Println("100/10 = ", result)}// 当除数为零的时候会返回错误信息if _, errorMsg := Divide(100, 0); errorMsg != "" {fmt.Println("errorMsg is: ", errorMsg)}}
并发
通过关键字开启goroutine
goroutine就是轻量级线程,
//开启新线程方法
go func1(x,y,z)//go是关键字,func1是方法,xyz是参数
通道(channel)
传递数据的一个数据结构
用于两个goroutine传递指定类型的值来同步运行和通讯
操作符<-用于指定通道方向,没有指定方向就是表示双向通道
//channel创建
ch :=make(chan int)ch <- v //把v发送给ch
v :<- ch //从ch里接收数据,赋值给v
默认情况不带缓冲区,意思就是像水龙头接水一样,错过了就不管
带缓冲的通道
ch :=make(chan int, 100)//100是指,可hu
package mainimport ("fmt"
)func fibonacci(n int, c chan int) {x, y := 0, 1for i := 0; i < n; i++ {c <- xx, y = y, x+y}close(c)
}func main() {c := make(chan int, 10)go fibonacci(cap(c), c)// range 函数遍历每个从通道接收到的数据,因为 c 在发送完 10 个// 数据之后就关闭了通道,所以这里我们 range 函数在接收到 10 个数据// 之后就结束了。如果上面的 c 通道不关闭,那么 range 函数就不// 会结束,从而在接收第 11 个数据的时候就阻塞了。for i := range c {fmt.Println(i)}
}
小练习:
Gin
Gin是一个 go 写的 web 框架,具有高性能的优点。官方地址:https://github.com/gin-gonic/gin
Gin用下面语句导一下包,就能用
go get -u github.com/gin-gonic/gin
Gin的简单实践:
package mainimport ("fmt""github.com/gin-gonic/gin""os""time"
)func main() {router := gin.Default()router.Use(RequestTimingMiddleware)router.GET("/hi", func(context *gin.Context) {context.JSON(200, gin.H{"msg": "hello"})})router.GET("/healthz", func(context *gin.Context) {//获取请求头requestHeader := context.Request.Header//添加VERSION环境变量requestHeader.Add("VERSION", os.Getenv("VERSION"))//Server 端记录访问日志包括客户端 IP,HTTP 返回码,输出到 server 端的标准输出fmt.Printf("Client IP为%s, HTTP Status: %d, Method: %s, Path: %s\n", context.ClientIP(), context.Writer.Status(), context.Request.Method, context.Request.URL.Path)//迭代设置请求头for key, values := range requestHeader {for _, value := range valu2es {context.Writer.Header().Set(key, value)}}//返回context.JSON(200, gin.H{"msg": "返回请求"})})router.Run(":8099")
}// 时间计算中间件
func RequestTimingMiddleware(c *gin.Context) {startTime := time.Now()c.Next()endTime := time.Now()countTime := endTime.Sub(startTime)fmt.Printf("耗时: %v\n", countTime)
}