Golang基础

不求外物,不惧内心,不避因果

1. 简述

1.1 背景

  • Go语言自己的早期源码使用C语言和汇编语言写成

  • Go的三个作者分别是:Rob Pike(罗伯.派克),Ken Thompson(肯.汤普森)和Robert Griesemer(罗伯特.格利茨默)

  • 2009年11月 GO语言第一个版本发布。2012年3月 第一个正式版本Go1.0发布

  • 到go1.8时,相同业务场景下的GC时延已经可以从go1.1的数秒,控制在1ms以内。GC问题的解决,可以说GO语言在服务端开发方面,几乎抹平了所有的弱点

  • 一家叫做 Docker 的公司。就是使用 Go 进行项目开发,并促进了计算机领域的容器行业,进而出现了像 Kubernetes 这样的项目

1.2 定义

将简单、实用体现得淋漓尽致的一种编程语言

1.3 GO项目

Docker 是一种操作系统层面的虚拟化技术,可以在操作系统和应用程序之间进行隔离,也可以称之为容器。Docker 可以在一台物理服务器上快速运行一个或多个实例。例如,启动一个 CentOS 操作系统,并在其内部命令行执行指令后结束,整个过程就像自己在操作系统一样高效。

Go语言自己的早期源码使用C语言和汇编语言写成。从 Go 1.5 版本后,完全使用Go语言自身进行编写。Go语言的源码对了解Go语言的底层调度有极大的参考意义,建议希望对Go语言有深入了解的读者读一读。

Google 公司开发的构建于 Docker 之上的容器调度服务,用户可以通过 Kubernetes 集群进行云端容器集群管理。系统会自动选取合适的工作节点来执行具体的容器集群调度处理工作。其核心概念是 Container Pod(容器仓)

一款分布式、可靠的 KV 存储系统,可以快速进行云配置。由 CoreOS 开发并维护键值存储系统,它使用Go语言编写,并通过 Raft 一致性算法处理日志复制以保证强一致性

beego 是一个类似 Python 的 Tornado 框架,采用了 RESTFul 的设计思路,使用Go语言编写的一个极轻量级、高可伸缩性和高性能的 Web 应用框架

一款快速构建模块化的 Web 应用的Go语言框架

国产的优秀分布式 Redis 解决方案。可以将 codis 理解成为 Web 服务领域的 Nginx,它实现了对 Redis 的反向代理和负载均衡。

Go语言强大的调试器,被很多集成环境和编辑器整合。

1.4 用处

  • 区块链
  • 云平台
  • 嵌入式
  • 网络编程
  • 服务器编程

1.5 编程库

Go语言标准库包名 功 能
bufio 带缓冲的 I/O 操作
bytes 实现字节操作
container 封装堆、列表和环形列表等容器
crypto 加密算法
database 数据库驱动和接口
debug 各种调试文件格式访问及调试功能
encoding 常见算法如 JSON、XML、Base64 等
flag 命令行解析
fmt 格式化操作
go Go语言的词法、语法树、类型等。可通过这个包进行代码信息提取和修改
html HTML 转义及模板系统
image 常见图形格式的访问及生成
io 实现 I/O 原始访问接口及访问封装
math 数学库
net 网络库,支持 Socket、HTTP、邮件、RPC、SMTP 等
os 操作系统平台不依赖平台操作封装
path 兼容各操作系统的路径操作实用函数
plugin Go 1.7 加入的插件系统。支持将代码编译为插件,按需加载
reflect 语言反射支持。可以动态获得代码中的类型信息,获取和修改变量的值
regexp 正则表达式封装
runtime 运行时接口
sort 排序接口
strings 字符串转换、解析及实用函数
time 时间接口
text 文本模板及 Token 词法器

2. 下载和环境

尽量使用压缩包安装方式,注意GoWorks工作目录下三个包 bin src pkg

2.1 下载go

1
2
3
4
5
6
7
# 安装目录
GOROOT D:\install\go
# 工作目录
GOPATH D:\GoWorks
# 运行go.exe目录
PATH %GOROOT%\bin
Path %GOPATH%\bin
  • 进行检测是否成功
1
2
3
go env
go version
go build
程序名 程序用途
dlv.exe go 语言调试工具
gocode.exe go语言代码检查,自动补全
godef.exe go语言代码定义和引用的跳转
golint.exe go语言代码规范检查
go-outline.exe 用于在Go源文件中提取JSON形式声明的简单工具
gopkgs.exe 快速列出可用包的工具
gorename.exe 在Go源代码中执行标识符的精确类型安全重命名
goreturns.exe 类似fmt和import的工具,使用零值填充Go返回语句以匹配func返回类型
go-symbols.exe 从go源码树中提取JSON形式的包符号的工具
gotour.exe go语言指南网页版
guru.exe go语言源代码有关工具,如代码高亮等

3. Go基础

3.1 hello.go

在Go语言里,命名为main的包具有特殊的含义。Go语言的编译程序会试图把这种名字的包编译为二进制可执行文件。所有用Go语言编译的可执行程序都必须有一个名叫main的包,一个可执行程序 有且仅有一个 main包

1
2
3
4
5
6
7
package main

import "fmt"

func main() {
fmt.Println("hello world")
}
1
go run  hello.go

当编译器发现某个包的名字为main时,它一定也会发现名为main()的函数,否则不会创建可执行文件。main()函数是程序的入口,所以,如果没有这个函数,程序就没有办法开始执行。程序编译时,会使用声明main包的代码所在的目录的目录名作为二进制可执行文件的文件名。

3.2 Go注释

方便其他人理解代码

1
2
3
4
5
6
7
8
//我是单行注释

/*
我是多行注释
我是多行注释
我是多行注释
我是多行注释
*/

3.3 变量

能够指向内存地址的可以变化的值,Go语言是静态类型语言

  1. 变量的定义(声明)
  • 格式
1
var [name变量名] [type变量类型]
  • eg
1
2
3
4
5
6
7
8
9
10
11
12
13
14
	var (
name string
age int
money float64
isLike bool
)
fmt.Println(name, age,money, isLike )
// 空格 0 0 false
/*
整形和浮点型变量的默认值为0和0.0
字符串变量的默认值为空字符串
布尔型变量默认为false
切片、函数、指针变量的默认为nil
*/
  1. 初始化
  • 格式
1
2
//变量声明
[name变量名] = [value变量值]
  • eg
1
2
3
4
name = "dd"
age = 12
money = 12.45
isLike = true
  1. 声明并初始化

由于使用了 := ,而不是赋值的 = ,因此推导声明写法的左值必须是没有被定义过的,若定义过,将会发生编译错误

  • 格式
1
[name未声明变量名] := [value变量值]
  • eg
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
	name := "dd"
age := 12
money := 12.45
isLike := true
//值改变,内存地址不变
name = "tt"
//交换,int会去掉float64小数部分
age, money = int(money), float64(age)
fmt.Println(name, age, money, isLike)
fmt.Printf("%T,%T,%T,%T", name, age, money, isLike)
fmt.Printf("%p,%p,%p,%p", &name, &age, &money, &isLike)

/*
dd 12 12.45 true
string,int,float64,bool
0xc000050270,0xc000018098,0xc0000180b0,0xc0000180b8
*/
  1. 匿名变量

匿名变量的特点是一个下划线 “_”,但任何赋给这个标识符的值都将被抛弃,可以极大地增强代码的灵活性,匿名变量不占用内存空间,不会分配内存。

  • 格式
1
_ := [func/value]
  • eg
1
2
3
4
5
6
7
8
func test() (int, int) {
return 100, 200
}
func main() {
a,_ := test()
_,b := test()
fmt.println(a,b)
}
  1. 变量作用域

一个变量(常量、类型或函数)在程序中都有一定的作用范围,称之为作用域,遵循就近原则

  • 局部变量

在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,函数的参数和返回值变量都属于局部变量

  • 全局变量

在函数体外声明的变量称之为全局变量,只需要在一个源文件中定义,就可以在所有源文件中使用,当然,不包含这个全局变量的源文件需要使用 import 关键字 引入
全局变量必须以 var 关键字开头,如果想要在外部包中使用全局变量的首字母必须大写

  • 注意

Go语言程序中全局变量与局部变量名称可以相同,但是函数体内的局部变量会被优先考虑!

3.4 常量

是一个简单值得标识符,在程序运行时,不会被修改的量

  1. 普通常量
  • 格式
1
const [name常量名] [type] = [value]
  • eg
1
2
3
const url string = "ssss.xx"
const t, b, c = "s", 3.12, true
fmt.Println(t, b, c)
  1. 特殊常量iota

未赋值的常量向前面的常量寻找

  • 格式
1
const [name常量名] [type] = iota        
  • eg
1
2
3
4
5
6
7
8
9
10
const (
a = iota //iota==0
b //iota==1
c //iota==2
d = "kuangshen" //iota==3
e = iota //iota==4
f = 100 //iota==5
g //iota==6
h = iota //iota==7
)

3.5 数据类型

  1. 布尔型
1
var isFlang bool //默认false
  1. 数字型
  • 整数型
1
var age int//默认0
序号 类型和描述
1 uint8无符号8位整型(O到255)
2 uint16无符号16位整型(O到65535)
3 uint32无符号32位整型(O到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)
  • 浮点型

float32 不要进行运算,会精度损失

1
2
var money float64
fmt.Printf("%.3f", money)//这里打印浮点数类型为%.3f四舍五入,默认是6位小数打印
序号 类型和描述
1 float32 IEEE-754 32位浮点型数
2 float64 lEEE-75464位浮点型数
3 complex64 32位实数和虚数
4 complex128 64位实数和虚数
  • 其他别名
序号 类型和描述
1 byte类似于uint8
2 rune类似于int32
3 uInt32或64位
4 int与uint一样大小
5 uintprt无符号整形,用于存放一个指针
  1. 字符

单引号

1
2
3
4
5
6
//英文编码表 ASCII字符表
//汉字编码表 :GBK
//全世界编码表:Unicode
v1 := '在'
fmt.Printf("%T,%d", v1, v1)
//int32,22312
  1. 字符串

双引号

1
2
3
	v2 := "在"
fmt.Printf("%T,%s", v2, v2)
//string,在
  • 字符串连接和转义
1
fmt.Printf("%T,%s", v2, v2+"gg\"ff")

3.6 数据类型转换

数据类型转换都为显示,Go语言不存在隐式类型转换,每个分配的内存不同,bool类型和整数类型不能相互转换

1
c := float64(a)

3.7 运算符

  1. 算术运算符
运算符 描述 实例
+ 相加 A+B输出结果30
- 相减 A-B输出结果-10
* 相乘 A*B输出结果为200
/ 相除 B/A输出结果为2
% 求余 B%A输出结果为0
++ 自增 A++输出结果11
自减 A–输出结果为9
1
2
3
4
v1 := 10
v2 := 3
fmt.Println(v1 / v2)
fmt.Println(v1 % v2)
  1. 关系运算符
运算符 描述 实例
== 检查两个值是否相等,如果相等返回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
  1. 逻辑运算符
运算符 描述 实例
&& 逻辑AND运算符,如果两边的操作数都是True,则条件True,否则为False。 A&&B 为false
|| 逻辑OR运算符,如果两边的操作数有一个True,则条件True,否则为False。 A||B为true
逻辑NOT运算符,如果条件为True,则逻辑NOT条件False,否则为True。 !(A&&B )为true
  1. 位运算符

加密、解密、底层

运算符 描述 实例
& 按位与运算符”&”是双目运算符。都是1结果为1,否则是0 (A&B)结果为12,二进制为0000 1100
| 按位或运算符”|”是双目运算符。只要有一个1就为1,都是0结果为0,否则是1 (A |B)结果为61,二进制0011 1101
^ 按位异或运算符”A”是双目运算符。不同则为1,相同为0 (A^B)结果为49,二进制为0011 0001
&^ 位清空,a &^b,对于b上的每个数值,如果为0,则取a对应位上的数值,如果为1,则取0. (A&^B)结果为48,二进制为0011 0000
<< 左移运算符”<<”是双目运算符。左移n位就是乘以2的n次方。其功能把”<<”左边的运算数的各二进位全部左移若干位,由”<<”右边的数指定移动的位数,高位丢弃,低位补O。 A<<2结果为240,二进制为1111 0000
>> 右移运算符”>>”是双目运算符。右移n位就是除以2的n次方。其功能是把”>>”左边的运算数的各二进位全部右移若干位,”>>”右边的数指定移动的位数。 A>>2结果为15,二进制为0000 1111
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
	v1 := 10
v2 := 3
fmt.Printf("%b\n", v1)
fmt.Printf("%b\n", v2)
v3 := v1 & v2
fmt.Printf("%b\n", v3)
v4 := v1 | v2
fmt.Printf("%b\n", v4)
v5 := v1 ^ v2
fmt.Printf("%b\n", v5)
v6 := v1 << 2
fmt.Printf("%b\n", v6)
v7 := v2 >> 2
fmt.Printf("%b\n", v7)
1010
11
10
1011
1001
101000
0
  1. 赋值运算符
运算符 描述 实例
= 简单的赋值运算符,将一个表达式的值赋给一个左值 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=C2
!= 按位或后赋值 C|=2等于C=C|=2
  1. 其他运算符
运算符 描述 实例
& 返回变量存储地址 &a;将给出变量的实际地址
* 指针变量 *a;是一个指针变量

3.8 输入输出

1
2
3
4
5
6
7
8
9
	var x int
var y float64
//Print
//Printf 格式化
//Println 换行
fmt.Println("请输入")

fmt.Scanln(&x, &y)
fmt.Println(x, y)

3.9 编码规范

  • 当命名(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的public) ;

  • 命名如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的private )

  • 保持package的名字和目录名保持一致,尽量采取有意义的包名,简短,有意义,尽量和标准库不要冲突。包名应该为小写单词,不要使用下划线或者混合大小写。

  • 尽量采取有意义的文件名,简短,有意义,应该为小写单词,使用下划线分隔各个单词(蛇形命名)。

3.10 流程控制

  1. 顺序结构

:从上到下,逐行执行。默认的逻辑

  1. 选择结构
  • if
1
2
3
4
5
6
a := 2
if a > 12 {
fmt.Print("大于12")
} else {
fmt.Print("《")
}
  • switch
1
2
3
4
5
6
7
8
9
10
11
12
13
a := 2
switch a {
case -1, 0:
fmt.Print("0或小于0")
case 1:
fmt.Print("1")
case 2:
fmt.Print("2")
fallthrough //贯穿,会继续执行下一个,结束用break
default:
fmt.Print("大于2")

}
  • select
  1. 循环结构
  • for
1
2
3
4
5
6
7
8
9
10
11
12
for i := 1; i <= 9; i++ {
for j := 1; j <= i; j++ {
fmt.Printf("%d*%d=%d\t", j, i, i*j)
}
fmt.Println()
}
str := "xx xy"
//第二种循环输出字符串
for i, v := range str {
fmt.Print(i)
fmt.Printf("%c", v)
}
  1. break与continue

break 跳出,continue结束当前继续

3.11 string

string字符串里面字符是不能修改的,Go中的字符串是一个字节的切片,可以通过将其内容封装在””中来创建字符串,Go中的字符串Unicode兼容的,并且是UTF-8编码,字符串是一些字节的集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
str := "xx xy"
//获取字符串长度
fmt.Println(len(str))
//获取指定字符
fmt.Println(str[0])
fmt.Printf("%c\n", str[4])
//循环输出
for i := 0; i < len(str); i++ {
fmt.Printf("%c\n", str[i])
}
//第二种循环输出
for i, v := range str {
fmt.Print(i)
fmt.Printf("%c", v)
}

3.12 函数

  1. 定义

函数的名称,返回类型和参数的代码块

  • 格式
1
2
3
func [函数名](函数参数...)(函数返回值) {
函数体
}
  1. 可变参数函数

如果一个函数的参数时可变参数,同时还有其他的参数,可变参数要放在列表的最后,一个函数的参数列表最多只能有一个可变参数

1
2
3
4
5
6
7
8
func myfunc(nums ...int) {
sum := 0
for i := 0; i < len(nums); i++ {
sum += nums[i]
}
fmt.Println(sum)

}
  1. 参数传递
  • 值传递[深克隆]:数值,布尔,数组,字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
	arr := [4]int{1, 2, 3, 4}
zhi(arr)
fmt.Println("修改后", arr)
func zhi(arr1 [4]int) {
fmt.Println(arr1)
arr1[0] = 0
fmt.Println("修改后", arr1)
}
//arr数组不会改变,只是深克隆了arr,改变的数据属于arr1数组
/*
修改前 [1 2 3 4]
修改后 [0 2 3 4]
调用后 [1 2 3 4]
*/
  • 引用传递【不会拷贝,传送是地址】:slice,map,chan
1
2
3
4
5
6
7
8
9
10
11
12
13
14
	s1 := []int{1, 2, 3, 4}
qiepian(s1)
fmt.Println("调用切片后", s1)
func qiepian(s2 []int) {
fmt.Println("切片修改前", s2)
s2[0] = 0
fmt.Println("切片修改后", s2)
}

/*
切片修改前 [1 2 3 4]
切片修改后 [0 2 3 4]
调用切片后 [0 2 3 4]
*/
  1. 递归函数

十分耗费内存,不建议使用

1
2
3
4
5
6
7
8
	sum := digui(5)
fmt.Println(sum)
func digui(n int) int {
if n == 1 {
return 1
}
return digui(n-1) + n
}
  1. defer函数

推迟、延迟最后执行,先进后出,类似于栈,参数按照顺序传送进去,延迟执行

1
2
3
4
5
6
7
8
9
10
func main() {

f("1")
f("2")
defer f("3")
f("4")
}
func f(s string) {
fmt.Println(s)
}
  1. 函数数据类型(引用数据类型)

函数在Go语言中是复合类型,可以看做是一种特殊的变量。函数名()︰调用返回结果, 函数名:指向函数体的内存地址,一种特殊类型的指针变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
func本身就是一个数据类型
1、创建一个函数
2、在mian方法定义相同类型的函数
3、将定义后的函数赋值个另一个函数
*/
func main() {
//2、在mian方法定义相同类型的函数
var fg func(int, int)

//3、将定义后的函数赋值个另一个函数
fg = ff
fg(1, 2)

}

//1、创建一个函数
func ff(a, b int) {
fmt.Println(a, b)
}
  1. 匿名函数

Go语言是支持函数式编程:
1、将匿名函数作为另外一个函数的参数,回调函数
2、将匿名函数作为另外一个函数的返回值,可以形成闭包结构

1
2
3
4
5
6
7
8
func(a, b int) {
fmt.Println("我是带参匿名函数f")
}(1, 2)

f5 := func(a, b int) int {
return a + b
}(1, 2)
fmt.Println("带参且有返回值匿名函数:", f5)
  1. 回调函数

将函数作为参数传入另外函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func main() {
r1 := add(1, 2)
fmt.Println(r1)

//3、调用高阶函数时,传入其他方法的函数
r3 := oper(3, 4, add)
fmt.Println(r3)
r4 := oper(8, 4, sub)
fmt.Println(r4)
}
//1、创建一个高阶函数oper,传如一个函数类型的参数且有返回值
func oper(a, b int, fun func(int, int) int) int {
r2 := fun(a, b)
return r2
}
//2、创建其他方法的函数
func add(a, b int) int {
return a + b
}
//2、创建其他方法的函数
func sub(a, b int) int {
return a - b
}
  1. 闭包

个外层函数中,有内层函数,该内层函数中,会操作外层函数的局部变量并且该外层函数的返回值就是这个内层函数。
这个内层函数和外层函数的局部变量,统称为闭包结构。
局部变量的生命周期就会发生改变,正常的局部变量会随着函数的调用而创建,随着函数的结束而销毁但是闭包结构中的外层函数的局部变量并不会随着外层函数的结束而销毁,因为内层函数还在继续使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package main

import "fmt"

func main() {
//定义一个变量 接受返回值(函数)
r1 := increment()
fmt.Println(r1)

v1 := r1()
fmt.Println(v1)
//不断调用自增+1
v2 := r1()
fmt.Println(v2)
//不断调用自增+1
fmt.Println(r1())
fmt.Println(r1())
fmt.Println(r1())
fmt.Println(r1())
fmt.Println("====================")
// ==========================================================
r2 := increment()
v3 := r2()
fmt.Println(v3)
fmt.Println(r2())
fmt.Println(r2())
fmt.Println(r2())
fmt.Println(r1())
fmt.Println(r2())
}

// 返回一个函数
func increment() func() int {

//局部变量
i := 0
//定义一个匿名函数 给变量自增并返回
fun := func() int {
//内层函数,没有执行
i++
return i
}
return fun
}
/*
输出结果:
0xd5ea00
1
2
3
4
5
6
====================
1
2
3
4
7
5

*/

3.13 泛型

Go的1.18版本包括了参数类型参数的实现,也就是俗称的泛型,不限制传进去的参数类型Type,减少重复逻辑

  1. 发现问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func printIntSlice(arr interface{}) {
//类型断言
for _, v := range arr.([]int) {
fmt.Println(v)
}
}
func printStringSlice(arr interface{}) {
for _, v := range arr.([]string) {
fmt.Println(v)
}
}
//这样很不方便
s1 := []int{1, 2, 3, 4}
s2 := []string{"ni", "hao", "ma"}
printIntSlice(s1)
printStringSlice(s2)
  1. 改变代码
1
2
3
4
5
6
7
8
fanxing1(s1)
fanxing1(s2)
//泛型 任意类型any,比较类型comparable,也可以string|int
func fanxing1[T any](arr []T, args ...T) {
for _, v := range arr {
fmt.Println("泛型", v)
}
}
  1. 泛型类型
1
2
3
4
5
6
7
type slice[T int|float32|float64][]T
var a Slice[int] = []int{1, 1, 1}
type Mymap[Key string | int, value float32 | float64] map[Key]value
var m1 Mymap[string, float64] = map[string]float64{
"Go": 9.0,
"Java": 8.0,
}

Map 中的KEY和VALUE是类型形参
int|string 是KEY的类型约束, float32 | float64是VALUE的类型约束
KEY int|string, VALUE float32| float64整个一串文本因为定义了所有形参所以被称为类型形参列表Map[KEY,VALUE]是泛型类型,类型的名字就叫Map[KEY,VALUE]
var a MyMap[string,float64]= xx 中的string和float64是类型实参,用于分别替换KEY和VALUE,实例化出了具体的类型MyMap[string, float64]

  1. 泛型函数
1
2
3
4
5
6
7
8
9
10
11
12
// 1、定义泛型切片
type MySlice[T int | float64] []T

// 2、定义泛型函数 传参类型为泛型
func (s MySlice[T]) Sum() T {
var sum T
for _, v := range s {
sum += v
fmt.Println(s)
}
return sum
}
  1. 自定义泛型类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//1、定义接口,约束类型为int
type Myint interface {
int | int16 | int32 | int64
}
//2、自定义约束
func GetNum[T Myint](a, b T) T {
if a > b {
return a
} else {
return b
}
}
//3、方法实现
fmt.Println(GetNum(1, 2))

4. GO Web开发

4.1 web开发框架

  • Beego
  • Gin
  • Echo
  • Iris

4.2 Gin开发

Gin 是一个用 Go (Golang) 编写的 HTTP Web 框架。 它具有类似 Martini 的 API,但性能比 Martini 快 40 倍。

  1. 下载gin框架
1
2
3
4
# 得到go.mod文件
go mod init go.mod
go get -u github.com/gin-gonic/gin
# 最新可以使用go install
  1. 进行开发
  • 前端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="../static/css/1.css">
</head>
<body>
<!-- <script src="/static/js/1.js"> -->
首页
{{ .message }}

<form action="/user/add" method="post">
<input type="text" name="userName">
<input type="password" name="password">
<input type="submit" value="提交">
</form>
</body>
</html>
  • 后端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package main

import (
"encoding/json"
"fmt"
"net/http"

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

func main() {
//获取访问地址
ginServer := gin.Default()
//加载静态页面
ginServer.LoadHTMLGlob("templates/*")
//获取静态
ginServer.Static("/static", "./static")
//响应给前端
ginServer.GET("/index", func(c *gin.Context) {
// c.JSON(200, gin.H{
// "message": "hello",
// })
c.HTML(http.StatusOK, "index.html", gin.H{
"message": "hello",
})
})
//获取请求参数
ginServer.GET("/user/info", func(ctx *gin.Context) {
userId := ctx.Query("userId")
userName := ctx.Query("userName")
ctx.JSON(http.StatusOK, gin.H{
"userId": userId,
"userName": userName,
})
})
//restful
ginServer.GET("/user/info/:userId/:userName", func(ctx *gin.Context) {
userId := ctx.Param("userId")
userName := ctx.Param("userName")
ctx.JSON(http.StatusOK, gin.H{
"userId": userId,
"userName": userName,
})
})
//后端传递接口json
ginServer.GET("/json", func(ctx *gin.Context) {
jsonStr := []byte("{\"name\":\"张三\",\"age\":18,\"high\":true,\"sex\":\"男\",\"class\":{\"name\":\"1班\",\"level\":3}}")
fmt.Println(jsonStr)
var m map[string]interface{}
err := json.Unmarshal(jsonStr, &m)
if err != nil {
fmt.Println(err)
}
ctx.JSON(http.StatusOK, m)

})
//输出 : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"}
ginServer.GET("/asciiJSON", func(ctx *gin.Context) {
data := map[string]interface{}{
"lang": "GO语言",
"tag": "<br>",
}
ctx.AsciiJSON(http.StatusOK, data)
})
//获取前端表单
ginServer.POST("/user/add", func(ctx *gin.Context) {
userName := ctx.PostForm("userName")
password := ctx.PostForm("password")
ctx.JSON(http.StatusOK, gin.H{
"msg": "OK",
"userName": userName,
"password": password,
})

})
//路由
ginServer.GET("/router", func(ctx *gin.Context) {
ctx.Redirect(http.StatusMovedPermanently, "https://www.bilibili.com")
})

//设置访问端口
ginServer.Run(":8080")
}

4.3 xorm开发

  1. 简述

xorm是一个Go语言的ORM库

  1. 开发环境
  • 初始化
1
2
3
4
5
6
go mod init go.mod
go get -u github.com/go-xorm/xorm
go get -u github.com/go-sql-driver/mysql
# 引用项目需要的依赖增加到go.mod文件。
# 去掉go.mod文件中项目不需要的依赖。
go mod tidy
  • xorm开发

import 下划线(如:_ “github.com/go-sql-driver/mysql”)的作用:当导入一个包时,该包下的文件里所有 init() 函数 都会被执行

import 和引用的包名之间加点(.)操作的含义就是这个包导入之后在调用这个包的函数时,可以省略前缀的包名 import . “fmt”

别名操作顾名思义可以把包命名成另一个用起来容易记忆的名字import f “fmt”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
package main

import (
"fmt"
"time"

_ "github.com/go-sql-driver/mysql"
"xorm.io/xorm"
)

func main() {
var (
userName string = "root"
password string = "root"
ipAddress string = "127.0.0.1"
port int = 3306
dbName string = "temp1"
charset string = "utf8mb4"
)
//构建数据库连接信息
dataSourceName := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", userName, password, ipAddress, port, dbName, charset)
engine, err := xorm.NewEngine("mysql", dataSourceName)
if err != nil {
fmt.Println("数据库连接失败", err)
}

type TempUser struct {
Id int64
Name string
Salt string
Age int
Passwd string `xorm:"varchar(200)"`
Created time.Time `xorm:"created"`
Updated time.Time `xorm:"updated"`
}
err1 := engine.Sync2(new(TempUser))
if err1 != nil {
fmt.Println("同步失败", err1)
}
//插入开始
// user := TempUser{Id: 100, Name: "xxy", Salt: "11", Age: 25, Passwd: "123"}
// var users []TempUser
// users = append(users, TempUser{Id: 1001, Name: "xxy", Salt: "11", Age: 25, Passwd: "123"})
// users = append(users, TempUser{Id: 1002, Name: "xxy", Salt: "11", Age: 25, Passwd: "123"})
// users = append(users, TempUser{Id: 1003, Name: "xxy", Salt: "11", Age: 25, Passwd: "123"})
// affected, _ := engine.Insert(&users)
// fmt.Println(affected)
// if affected >= 1 {
// fmt.Println("插入成功")
// } else {
// fmt.Println("插入失败")
// }
//插入结束
//更新开始
// var ids = []int64{100, 1001}
// user := TempUser{Name: "zzxxy", Salt: "10", Age: 24, Passwd: "123"}
// n, _ := engine.ID(100).Update(&user)
// n, err2 := engine.In("Id", ids).Update(&user)
// if err2 != nil {
// fmt.Println("更新失败", err2)
// }
// fmt.Println(n)
//更新结束
//删除开始
// user := TempUser{Name: "zzxxy", Salt: "10", Age: 24, Passwd: "123"}
// n, _ := engine.ID(10).Delete(&user)
// if n >= 1 {
// fmt.Printf("删除%d条数据", n)
// }
//删除结束
//查询开始
//Query
// results, _ := engine.Query("select * from temp_user LIMIT 0, 200")
// results1, _ := engine.Where("salt = 10").Query("select * from temp_user")

// results2, _ := engine.QueryString("select * from temp_user LIMIT 0, 200")
// results3, _ := engine.Where("salt = 10").QueryString("select * from temp_user")

// results4, _ := engine.QueryInterface("select * from temp_user LIMIT 0, 200")
// results5, _ := engine.Where("salt = 11").QueryInterface("select * from temp_user")
// fmt.Println("Query----------")
// fmt.Println(results)
// fmt.Println("Query条件----------")
// fmt.Println(results1)
// fmt.Println("QueryString----------")
// fmt.Println(results2)
// fmt.Println("QueryString条件----------")
// fmt.Println(results3)
// fmt.Println("QueryInterface----------")
// fmt.Println(results4)
// fmt.Println("QueryInterface条件----------")
// fmt.Println(results5)
//Get查询单条记录
// user := TempUser{Name: "xxy"}
// engine.Where("name = ?", user.Name).Desc("id").Get(&user)
// fmt.Println(user)
//Get获取指定字段
// var name string
// engine.Table(&user).Where("id = ?", 100).Cols("name").Get(&name)
// fmt.Println(name)
//Find找寻多条记录,当然可以使用Join和extends来组合使用
// var users []TempUser
// engine.Where("age=?", 25).And("passwd=?", 123).Limit(10, 0).Find(&users)
// fmt.Println(users)
//获取记录条数
// counts, _ := engine.Count(&user)
// fmt.Println(counts)
//Iterate或Rows遍历数据
// engine.Iterate(&TempUser{Passwd: "123"}, func(idx int, bean interface{}) error {
// user := bean.(*TempUser)
// fmt.Println(user)
// return nil
// })
//Rows遍历数据
// rows, _ := engine.Rows(&TempUser{Passwd: "123"})
// defer rows.Close()
// userBean := new(TempUser)
// for rows.Next() {
// rows.Scan(userBean)
// fmt.Println(userBean)
// }
//查询结束
//执行SQL开始
// affected, err2 := engine.Exec("update temp_user set Age = ? where Name = ?", 24, "xxy")
// if err2 != nil {
// fmt.Println("更新失败")
// }
// fmt.Println("更新成功", affected)
//执行SQL结束
//事务开始
session := engine.NewSession()
defer session.Close()
session.Begin()
defer func() {
err := recover()
if err != nil {
fmt.Println(err)
fmt.Println("rollback")
session.Rollback()
} else {
session.Commit()
}
}()
//增加
user1 := TempUser{Id: 111, Name: "xxy", Salt: "11", Age: 25, Passwd: "123"}
if _, err := session.Insert(&user1); err != nil {
panic(err)
}
//修改
user2 := TempUser{Id: 111, Name: "xxyz", Salt: "112", Age: 25, Passwd: "123"}
if _, err := session.Where("id=?", user2.Id).Update(&user2); err != nil {
panic(err)
}
//删除
if _, err := session.Exec("delete from temp_user where id = 100"); err != nil {
panic(err)
}
//事务结束
}

4.4 gRPC

  1. 简述

一个高性能、开源的通用RPC框架

  1. 优点
  • 更高效的进程通信:使用基于protocol buffer在Http2 中以二进制协议通信,而不是JSON、XML文本格式,序列化和反序列化
  • 简单定义的服务接口、易扩展
  • 强类型、跨语言
  • 一元RPC、服务端流、客户端流、双工流
  1. 下载和环境配置
1
2
3
4
# 配置系统环境Path
D:\install\protoc-22.2-win64\bin
# 测试是否成功
protoc

Protocol Buffers一种数据描述语言编译器,不依赖于语言和平台并且可扩展性极强,广泛用于各种结构化信息存储和交换

  • 安装grpc核心库
1
2
3
4
5
6
# 初始化
go mod init go.mod
# 新版grpc核心库
go get -u google.golang.org/grpc
# 旧版grpc核心库
go install github.com/golang/protobuf/protoc-gen-go
  • 安装go的protoc代码生成工具库

已经下载的可以使用go install,没有下载用go get

1
2
3
4
5
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
# go.mod模板需要
go get google.golang.org/protobuf/cmd/protoc-gen-go
go get google.golang.org/grpc/cmd/protoc-gen-go-grpc

完成后可以查看自己gowork目录下bin有没有exe文件

  1. 编写proto文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//这是说明我们使用的是proto3语法
syntax="proto3";

//这部分的内容是关于最后生成的go文件是处在哪个目录哪个包中,代表在当前目录生成,service代表了生成的go文件的包名是service
option go_package =".;server";

//然后我们需要定义一个服务,在这个服务中需要有一个方法,这个方法可以接受客户端的参数,再返回服务端的响应
//其实很容易可以看出,我们定义了一个Server,称为SayHello,这个服只有一个rpc方法,名为SayHello。
//这个方法会发送一个HelloRequest,然后返回一个HelloResponse。
service SayHello{
rpc SayHello(HelloRequest) returns (HelloResponse){}
}

//message关键字,其实你可以理解为Golang中的结构体。
//这里比较特别的是变量后面的“赋值”。注意,这里并不是赋值,而是定义这个变量在这个message中的位置。
message HelloRequest{
string requestName = 1;
}
message HelloResponse{
string responseMsg = 1;
}
  1. 在/proto目录下执行,会在/proto生成go文件
1
2
3
# --go_out不是命令,加配置环境path %GOPATH%\bin
protoc --go_out=. hello.proto
protoc --go-grpc_out=. hello.proto
  1. proto文件介绍
  • message

message:protobuf 中定义一个消息类型式是通过关键字message字段指定的。消息就是需要传输数据格式定义。message 关键字类似于C++中的class,JAVA中的class,go中的struct。在消息中承载的数据分别对应于每一个字段,其中每个字段都有一个名字和一种类型,一个proto文件中可以定义多个消息类型

  • 字段规则

optional:消息体中的可选字段,protobuf3没有了required,optional等说明关键字,都默认为optional,

repeate:消息体中可重复字段,重复的值的顺序会被保留着go中重复的回被定义为切片

  • 消息号

在消息体的定义中,每个字段都必须要有一个唯一的标识号,标识号是[1,2^29-1]范围内的一个整数。

  • 嵌套信息
1
2
3
4
5
6
7
8
9
10
11
12
message PersonInfo{
message Person{
string name = 1;
int32 height = 2;
repeated int32 weight = 3;
}
repeated Person info = 1;
}
//引用
message PersonMessage{
PersonInfo.Person info = 1;
}
  • 服务定义

如果想要将消息类型用在PRC系统中,可以在==.proto文件中定义一个RPC服务接口==,protocol buffer编译器将会根据所选择的不同语言生成服务接口代码及存根

1
2
3
4
service SayHello{
# rpc 服务函数名(参数) 返回 (返回参数)
rpc SayHello(HelloRequest) returns (HelloResponse){}
}
  1. 服务端编写
  • 创建gRPC Server 对象,你可以理解为它是Server端的抽象对象
  • 将server(其包含需要被调用的服务端接口)注册到gRPC Server的内部注册中心,这样可以在接受到请求时,通过内部的服务发现,发现该服务端接口并转接进行逻辑处理
  • 创建Listen,监听TCP端口
  • gRPC Server 开始 lis.Accept,知道Stop

无法导入

1
go mod tidy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

import (
"context"
"fmt"
"net"

pb "go.mod/grpcdemo/hello-server/proto"

"google.golang.org/grpc"
)

// hello
type server struct {
pb.UnimplementedSayHelloServer
}

func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) {
fmt.Printf("hello" + req.RequestName)
return &pb.HelloResponse{ResponseMsg: "hello" + req.RequestName}, nil
}
func main() {
//开启端口
Listen, _ := net.Listen("tcp", ":9090")
//创建grpc服务,注册,提供接口
grpcServer := grpc.NewServer()
pb.RegisterSayHelloServer(grpcServer, &server{})
//监听,启动服务
err := grpcServer.Server(Listen)
if err != nil {
fmt.Printf("启动失败: %v", err)
return
}
}

  1. 客户端编写
  • 创建与给定目标(服务端)的连接交互
  • 创建server的客户端对象
  • 发送RPC请求,等待同步响应,得到回调后返回响应结果
  • 输出响应结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import (
"context"
"fmt"

"log"

pb "go.mod/grpcdemo/hello-client/proto"

"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)

func main() {
//连接
conn, err := grpc.Dial("127.0.0.1:9090", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("不能连接:%v", err)
}
//关闭
defer conn.Close()
//建立连接
client := pb.NewSayHelloClient(conn)
//进行请求
resp, err := client.SayHello(context.Background(), &pb.HelloRequest{RequestName: " xxy"})
if err != nil {
log.Fatalf("不能请求:%v", err)
}
//获得内容输出打印
fmt.Println(resp.GetResponseMsg())

}
  1. 认证-安全

gRPC 通常默认是使用 protobuf 来作为传输协议,grpc将各种认证方式浓缩统一到一个凭证上,可以单独使用一种凭证,也可以多种凭证组合,提供统一的API认证方式

  • 方式
    • SSL/TLS 认证方式(采用 http2 协议)
    • 基于 Token 的认证方式(基于安全连接)
    • 不采用任何措施的连接,这是不安全的连接(默认采用 http1)
    • 自定义的身份认证
  1. SSL/TLS认证方式
  • SSL/TLS官网下载

  • 便捷版安装包

  • 证书参数

    • key:服务器上的私钥文件,用于对发送给客户端数据的加密,以及对从客户端接收到的数据解密
    • csr:证书签名请求文件,用于提交给证书颁发机构(CA) 对证书签名
    • crt:由证书颁发机构(CA) 签名后的证书,或者是开发者自签名的证书,包含证书持有人的信息,持有人的公钥,以及签名者的签名等信息
    • pem: 是基于Base64编码的证书格式,扩展名包括PEM、CRT和CER
  • 生成参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 1、生成私钥
openssl genrsa -out server.key 2048
# 2、生成证书 全部回车即可,可以不填
openssl req -new -x509 -key server.key -out server.crt -days 3600
# 国家名称
Country Name (2 letter code) [AU]:
# 省名称
State or Province Name (full name) [Some-State]:
# 城市名称
Locality Name (eg, city) []:
# 公司组织名称
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
# 部门名称
Organizational Unit Name (eg, section) []:
# 服务器or网站名称
Common Name (e.g. server FQDN or YOUR name) []:
# 邮件
Email Address []:
# 3、生成 csr
openssl req -new -key server.key -out server.csr
  • 更改openssl.cnf
1
2
3
4
5
6
7
8
# 更改openssl.cnf 
#1) 复制一份你安装的openssl的bin目录里面的openssl.cnf文件到你项目所在的目录
#2) 找到 [ CA_default ] 打开 copy_extensions = copy (就是把前面的#去掉)
#3)找到 [ req ] 打开 req_extensions = v3_req # The extensions to add to a certificate request
#4) 找到 [ v3_req ] 添加 subjectAltName = @alt_names
#5) 添加新的标签[ alt_name ] 和标签字段
#指定特定的网站才能访问
DNS.1 = *.newbie.com
  • 生成证书
1
2
3
4
5
6
7
8
9
#生成证书私钥test.key
openssl genpkey -algorithm RSA -out test.key

#通过私钥test.key生成证书请求文件test.csr (注意cfg和cnf)
openssl req -new -nodes -key test.key -out test.csr -days 3650 -subj "/C=cn/OU=myorg/O=mycomp/CN=myname" -config ./openssl.cnf -extensions v3_req
#test.csr是上面生成的证书请求文件。ca.crt/server.key是CA证书文件和key,用来对test.csr进行签名认证。这两个文件在第一部分生成。

#生成SAN证书 pem
openssl x509 -req -days 365 -in test.csr -out test.pem -CA server.crt -CAkey server.key -CAcreateserial -extfile ./openssl.cnf -extensions v3_req
  • Server端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main

import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"log"
"net"
pb "xxb-grpc-study/hello-server/proto"
)

//hello server 服务器端
type server struct {
pb.UnimplementedSayHelloServer
}

//方法重写
func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) {
return &pb.HelloResponse{ResponseMsg: "hello" + req.RequestName}, nil
}

func main() {
//从提供的根证书颁发机构证书文件构建TLS凭据,两个参数分别是自签名证书和私钥文件 改
creds, _ := credentials.NewServerTLSFromFile("D:\\Go_Dev\\src\\xxb-grpc-study\\key\\test.pem", "D:\\Go_Dev\\src\\xxb-grpc-study\\key\\test.key")
//开启端口
listen, _ := net.Listen("tcp", ":9090")
//创建grpc服务 改
grpcServer := grpc.NewServer(grpc.Creds(creds))
//在grpc服务端中去注册我们自己编写的服务,注册一定是引用注册
pb.RegisterSayHelloServer(grpcServer, &server{})
//启动服务
err := grpcServer.Serve(listen)
if err != nil {
log.Println("启动失败~")
}
}
  • Client端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"log"
pb "xxb-grpc-study/hello-client/proto"
)

func main() {
//改
creds, _ := credentials.NewClientTLSFromFile("D:\\Go_Dev\\src\\xxb-grpc-study\\key\\test.pem", "*.newbie.com")

//WithTransportCredentials返回一个DialOption,用于配置连接级别的安全凭证(例如,TLS/SSL)。这不能与WithCredentialsBundle一起使用。改
conn, err := grpc.Dial("127.0.0.1:9090", grpc.WithTransportCredentials(creds))
if err != nil {
log.Fatalf("Did not connect:%v", err)
}
defer conn.Close()
//建立连接
client := pb.NewSayHelloClient(conn)
//rpc方法调用
resp, _ := client.SayHello(context.Background(), &pb.HelloRequest{RequestName: " newbie"})
fmt.Println(resp.GetResponseMsg())
}
  1. Token认证
  • Server端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package main

import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"log"
pb "xxb-grpc-study/hello-client/proto"
)

type ClientTokenAuth struct {
}

func (c ClientTokenAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
return map[string]string{
"appId": "newbie",
"appKey": "123123",
}, nil
}
func (c ClientTokenAuth) RequireTransportSecurity() bool {
return false
}

func main() {
//creds, _ := credentials.NewClientTLSFromFile("D:\\Go_Dev\\src\\xxb-grpc-study\\key\\test.pem", "*.newbie.com")

//WithTransportCredentials返回一个DialOption,用于配置连接级别的安全凭证(例如,TLS/SSL)。这不能与WithCredentialsBundle一起使用。
var opts []grpc.DialOption
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
opts = append(opts, grpc.WithPerRPCCredentials(new(ClientTokenAuth)))

conn, err := grpc.Dial("127.0.0.1:9090", opts...)
if err != nil {
log.Fatalf("Did not connect:%v", err)
}
defer conn.Close()
//建立连接
client := pb.NewSayHelloClient(conn)
//rpc方法调用
resp, _ := client.SayHello(context.Background(), &pb.HelloRequest{RequestName: " newbie"})
fmt.Println(resp.GetResponseMsg())
}
  • client端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package main

import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"log"
pb "xxb-grpc-study/hello-client/proto"
)

type ClientTokenAuth struct {
}

func (c ClientTokenAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
return map[string]string{
"appId": "newbie",
"appKey": "123123",
}, nil
}
func (c ClientTokenAuth) RequireTransportSecurity() bool {
return false
}

func main() {
//creds, _ := credentials.NewClientTLSFromFile("D:\\Go_Dev\\src\\xxb-grpc-study\\key\\test.pem", "*.newbie.com")

//WithTransportCredentials返回一个DialOption,用于配置连接级别的安全凭证(例如,TLS/SSL)。这不能与WithCredentialsBundle一起使用。
var opts []grpc.DialOption
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
opts = append(opts, grpc.WithPerRPCCredentials(new(ClientTokenAuth)))

conn, err := grpc.Dial("127.0.0.1:9090", opts...)
if err != nil {
log.Fatalf("Did not connect:%v", err)
}
defer conn.Close()
//建立连接
client := pb.NewSayHelloClient(conn)
//rpc方法调用
resp, _ := client.SayHello(context.Background(), &pb.HelloRequest{RequestName: " newbie"})
fmt.Println(resp.GetResponseMsg())
}

5. 总结

了解最基础的Go语言,语言只是工具,思想永远最重要