GO
add by nohi 20220505
资料
- https://www.runoob.com/go/go-tutorial.html
- 参考:https://www.w3cschool.cn/yqbmht/fxmdgcqm.html
- demo: git@github.com:thisisnohi/demo_go.git
目标及过程
- 基础
- web框架
- Go 数据库操作
- 微服务
基础
数据类型
布尔型:true false
数字类型
uint8/16/32/64 int8/16/32/64 float32/64 complex64/128
Byte rune(类似int32) unint(32/64) int(uint) uinptr(无符号整形,用于存放一个指针)
字符串类型
派生类型
- (a) 指针类型(Pointer)
- (b) 数组类型
- (c) 结构化类型(struct)
- (d) Channel 类型
- (e) 函数类型
- (f) 切片类型
- (g) 接口类型(interface)
- (h) Map 类型
变量、常量
定义:
var a, b, c string = "1", "2", "3" d, e, f := "aaaa", "bbb", ""
常量
const b string = "abc" const b = "abc" const ( a = "abc" b = len(a) c = unsafe.Sizeof(a) ) const ( a = iota b = iota c = iota )
数组
定义
var variable_name [SIZE] variable_type => var balance [10] float32 初始化 var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0} balance := [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0} var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0} 或 balance := [...]float32{1000.0, 2 // 将索引为 1 和 3 的元素初始化 balance := [5]float32{1:2.0,3:7.0}
多维数组
var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type // Step 1: 创建数组 values := [][]int{} // Step 2: 使用 append() 函数向空的二维数组添加两行一维数组 row1 := []int{1, 2, 3} row2 := []int{4, 5, 6} // 初始化 a := [3][4]int{ {0, 1, 2, 3} , /* 第一行索引为 0 */ {4, 5, 6, 7} , /* 第二行索引为 1 */ {8, 9, 10, 11}, /* 第三行索引为 2 */ }
指针
- &获取内存地址
- var var_name *var-type
- 指针数组: var ptr [MAX]*int;
结构体
定义
type struct_variable_type struct { member definition member definition ... member definition }
切片
定义
var slice1 []type = make([]type, len) 也可以简写为 slice1 := make([]type, len) make([]T, length, capacity) capacity 为可选参数。
Range
for key, value := range oldMap {
newMap[key] = value
}
for key := range oldMap
for key, _ := range oldMap
Map(集合)
定义
/* 声明变量,默认 map 是 nil */ var map_variable map[key_data_type]value_data_type /* 使用 make 函数 */ map_variable := make(map[key_data_type]value_data_type)
实例
var cMap map[int]string cMap = make(map[int]string) fmt.Println(cMap) cMap[1] = "aaaa" cMap[2] = "2222" fmt.Println(cMap)
delete() 函数
/* 创建map */ countryCapitalMap := map[string]string{"France": "Paris", "Italy": "Rome", "Japan": "Tokyo", "India": "New delhi"} fmt.Println("原始地图") /* 打印地图 */ for country := range countryCapitalMap { fmt.Println(country, "首都是", countryCapitalMap[country]) } /*删除元素*/ delete(countryCapitalMap, "France") fmt.Println("法国条目被删除") fmt.Println("删除元素后地图") /*打印地图*/ for country := range countryCapitalMap { fmt.Println(country, "首都是", countryCapitalMap[country]) }
语言类型转换
- type_name(expression) type_name 为类型,expression 为表达式。
接口
定义:
/* 定义接口 */ type interface_name interface { method_name1 [return_type] method_name2 [return_type] method_name3 [return_type] ... method_namen [return_type] } /* 定义结构体 */ type struct_name struct { /* variables */ } /* 实现接口方法 */ func (struct_name_variable struct_name) method_name1() [return_type] { /* 方法实现 */ } ... func (struct_name_variable struct_name) method_namen() [return_type] { /* 方法实现*/ }
实例
type Phone interface { call() } type Nokia struct { } type Iphone struct { } func (nokia Nokia) call() { fmt.Println("this is nokia") } func (iphone Iphone) call() { var phone Phone phone = new(Nokia) phone.call() phone = new(Iphone) phone.call()
错误处理
并发
Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。
goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。
go 函数名( 参数列表 )
go f(x, y, z)
通道(channel)
ch <- v // 把 v 发送到通道 ch v := <-ch // 从 ch 接收数据 // 并把值赋给 v ch := make(chan int)
缓冲区:ch := make(chan int, 100)
WEB框架
Gin:https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzI3MjU4Njk3Ng==&action=getalbum&album_id=1362784031968149504&scene=173&from_msgid=2247484397&from_itemidx=1&count=3&nolastread=1#wechat_redirect
安装
get 时timeout
export GO111MODULE=on export GOPROXY=https://goproxy.cn
go get -u github.com/gin-gonic/gin
demo
import ( "fmt" "github.com/gin-gonic/gin" ) func main() { fmt.Println("1111") r := gin.Default() r.GET("/", func(c *gin.Context) { c.JSON(200, gin.H{ "Blog": "www.flysnow.org", "wechat": "flysnow_org", }) }) r.Run(":8080") }
路由及路由参数
// 路由参数
r.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id")
fmt.Println("id:" + id)
c.String(200, "This user id is %s", id)
})
// 路由参数
r.GET("/users/start/*id", func(c *gin.Context) {
id := c.Param("id")
fmt.Println("id:" + id)
c.String(200, "This user id is %s", id)
})
查询参数
// http://127.0.0.1:8080/?wechat=thisisnohi&a=1&a=2&a=3&map[a]=m1&map[1]=1111
r.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{
"Blog": "www.flysnow.org",
"wechat": "flysnow_org",
"abc": c.Query("wechat"),
"a": c.QueryArray("a"),
"b": "默认值:" + c.DefaultQuery("b", "0"),
"map": c.QueryMap("map"),
})
})
表单参数
// curl -d wechat=1111 http://127.0.0.1:8080/users
r.POST("/users", func(c *gin.Context) {
//创建一个用户
c.JSON(200, gin.H{
"Blog": "www.flysnow.org",
"wechat": "flysnow_org",
"abc": c.PostForm("wechat"),
})
})
分组路由
g := r.Group("/v1")
{
g.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{
"Blog": "www.flysnow.org====g1",
"abc": c.Query("wechat"),
"a": c.QueryArray("a"),
"b": "默认值:" + c.DefaultQuery("b", "0"),
"map": c.QueryMap("map"),
})
})
g.GET("/users", func(c *gin.Context) {
c.JSON(200, users)
})
}
路由中间件
g2 := r.Group("/v2", func(c *gin.Context) { fmt.Println("=======v2 1111======") fmt.Println("a:" + c.Query("a")) fmt.Println(c.QueryArray("a")) }, func(c *gin.Context) { fmt.Println("=======v2 22222======") fmt.Println("a:" + c.Query("a")) })
JSONP跨域
解决跨域问题的办法有CORS、代理和JSONP,这里结合Gin,主要介绍JSONP模式
样例
<!--这里必须是application/javascript,否则无法加载sayHello函数 --> <script type="application/javascript"> function sayHello(data){ alert(JSON.stringify(data)) } sayHello("{'title':'TEST', 'MSG': 'this is msg'}") </script> <script type="text/javascript" src="http://localhost:8080/jsonp?callback=sayHello"></script>
JSON劫持
r.SecureJsonPrefix("for(;;);") groupJsonp.GET("/secureJson", func(c *gin.Context) { c.SecureJSON(200, a) })
html/template
group.GET("/", func(c *gin.Context) {
c.Status(200)
const templateText = `this is {{ printf "%s" .}}`
tmpl, err := template.New("htmlTest").Parse(templateText)
if err != nil {
log.Fatalf("parsing: %s", err)
}
tmpl.Execute(c.Writer, "nohi.online")
})
r.LoadHTMLFiles("html/index.html")
group.GET("/index", func(c *gin.Context) {
c.HTML(200, "index.html", gin.H{"a": "aaa", "name": "NOHI"})
})
html/index.html
<h2>this is html/template</h2>
<p>Hello {{ .a }}</p>
<p>Hello {{ .name }}</p>
中间件
Basic Authorization
group.Use(gin.BasicAuth(gin.Accounts{"admin": "123456", "nohi": "nohi"})) group.GET("/", func(c *gin.Context) { ... })
数据库操作
参考:https://segmentfault.com/a/1190000038632750
gorm
go get -u gorm.io/gorm go get -u gorm.io/driver/mysql
test: TestGoDbGorm.http
微服务
框架名 | 开源时间 | 官网/主文档 | github | github star |
---|---|---|---|---|
go-zero | 2020 | https://go-zero.dev | https://github.com/zeromicro/go-zero | 15.9K |
go-kratos | 2019 | https://go-kratos.dev/ | https://github.com/go-kratos/kratos | 17.1K |
tars-go | 2018 | https://tarscloud.gitbook.io/tarsdocs/ | https://github.com/TarsCloud/TarsGo | 3K |
dubbo-go | 2019 | https://dubbo.apache.org/zh/docs/languages/golang/ | https://github.com/apache/dubbo-go | 3.9K |
go-micro | 2015 | - | https://github.com/asim/go-micro | 17.9K |
go-kit | 2015 | - | https://github.com/go-kit/kit | 22.7K |
jupiter | 2020 | https://jupiter.douyu.com/ | https://github.com/zeromicro/go-zero | 3.6K |
go-zero
介绍:https://go-zero.dev/cn/docs/introduction/
文档:https://go-zero.dev/cn/docs/prepare/dev-flow
视频教程:https://www.bilibili.com/video/BV1ou411q7SC/?spm_id_from=pageDriver
安装
goctl
go install github.com/zeromicro/go-zero/tools/goctl@latest
protoc & protoc-gen-go (安装较慢)
goctl env check -i -f --verbose
etcd
https://cloud.tencent.com/developer/article/1824472
安装官方:https://github.com/etcd-io/etcd/releases
- 启动:/tmp/etcd-download-test/etcd
redis
创建mall工程
- mkdir go-zero-demo
- cd go-zero-demo
- go mod init go-zero-demo
注
goctl rpc protoc user.proto --go_out=./types --go-grpc_out=./types --zrpc_out=.
goctl api go -api order.api -dir .
1.添加api文件
vi order.api
type(
OrderReq {
Id string `path:"id"`
}
OrderReply {
Id string `json:"id"`
Name string `json:"name"`
}
)
service order {
@handler getOrder
get /api/order/get/:id (OrderReq) returns (OrderReply)
}
2.生成服务文件
goctl api go -api order.api -dir .
3.添加user rpc配置
vim internal/config/config.go
package config import ( "github.com/zeromicro/go-zero/zrpc" "github.com/zeromicro/go-zero/rest" ) type Config struct { rest.RestConf UserRpc zrpc.RpcClientConf }
model生成
方式一:ddl
cd service/user/model
goctl model mysql ddl -src user.sql -dir . -c
方式二: datasource
goctl model mysql datasource -url="$datasource" -table="user" -c -dir .
方式三:intellij插件
在Goland中,右键user.sql
,依次进入并点击New
->Go Zero
->Model Code
即可生成,或者打开user.sql
文件, 进入编辑区,使用快捷键Command+N
(for mac OS)或者 alt+insert
(for windows),选择Mode Code
即可
api生成
方式一:命令行
cd book/service/user/api
goctl api go -api user.api -dir .
goctl api go -api *.api -dir ../ --style=goZero
方式二:intellij插件
在 user.api
文件右键,依次点击进入 New
->Go Zero
->Api Code
,进入目标目录选择,即api源码的目标存放目录,默认为user.api所在目录,选择好目录后点击OK即可
rpc
命令
goctl rpc protoc user.proto --go_out=./types --go-grpc_out=./types --zrpc_out=. goctl rpc protoc user.proto --go_out=../ --go-grpc_out=../ --zrpc_out=.. --style=goZero
middleware
视频:https://www.bilibili.com/video/BV1ou411q7SC/?spm_id_from=pageDriver
源码:
常用命令
# 生成api业务代码 , 进入"服务/cmd/api/desc"目录下,执行下面命令
# goctl api go -api *.api -dir ../ --style=goZero
# 1)goctl >= 1.3 进入"服务/cmd/rpc/pb"目录下,执行下面命令
# goctl rpc protoc *.proto --go_out=../ --go-grpc_out=../ --zrpc_out=../ --style=goZero
# 去除proto中的json的omitempty
# mac: sed -i "" 's/,omitempty//g' *.pb.go
# linux: sed -i 's/,omitempty//g' *.pb.go
# 创建kafka的topic
# kafka-topics.sh --create --zookeeper zookeeper:2181 --replication-factor 1 -partitions 1 --topic {topic}
# 查看消费者组情况
# kafka-consumer-groups.sh --bootstrap-server kafka:9092 --describe --group {group}
# 命令行消费
# ./kafka-console-consumer.sh --bootstrap-server kafka:9092 --topic looklook-log --from-beginning
# 命令生产
# ./kafka-console-producer.sh --bootstrap-server kafka:9092 --topic second
无service的proto
protoc -I ./ --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. userModel.proto
sql2pb
参考:https://github.com/Mikaelemmmm/sql2pb
sql2pb -go_package ./pb -host localhost -port 3306 -package pb -user root -password 密码 -schema go_zero -service_name ordercenter > ordercenter.prot
疑问
调用rpc后返回对象,如何定义、使用?
Logx 使用