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

微服务

框架名开源时间官网/主文档githubgithub star
go-zero2020https://go-zero.dev在新窗口打开https://github.com/zeromicro/go-zero在新窗口打开15.9K
go-kratos2019https://go-kratos.dev/在新窗口打开https://github.com/go-kratos/kratos在新窗口打开17.1K
tars-go2018https://tarscloud.gitbook.io/tarsdocs/在新窗口打开https://github.com/TarsCloud/TarsGo在新窗口打开3K
dubbo-go2019https://dubbo.apache.org/zh/docs/languages/golang/在新窗口打开https://github.com/apache/dubbo-go在新窗口打开3.9K
go-micro2015-https://github.com/asim/go-micro在新窗口打开17.9K
go-kit2015-https://github.com/go-kit/kit在新窗口打开22.7K
jupiter2020https://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 使用

上次更新:
贡献者: NOHI