tonglin0325的个人主页

golang学习笔记——gorm

gen是gorm官方推出的一个GORM代码生成工具

官方文档:https://gorm.io/zh_CN/gen/

1.使用gen框架生成model和dao

安装gorm gen

1
2
go get -u gorm.io/gen

假设有如下用户表

1
2
3
4
5
6
7
8
CREATE TABLE user
(
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`username` varchar(128) NOT NULL COMMENT '用户名',
`email` varchar(128) NOT NULL COMMENT '邮箱',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

在cmd目录下创建gen/generate.go文件

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
package main

import (
"fmt"

"gorm.io/driver/mysql"
"gorm.io/gen"
"gorm.io/gorm"
)

const MySQLDSN = "root:123456@tcp(127.0.0.1:55000)/default?charset=utf8mb4&parseTime=True"

func connectDB(dsn string) *gorm.DB {
db, err := gorm.Open(mysql.Open(dsn))
if err != nil {
panic(fmt.Errorf("connect mysql fail: %w", err))
}
return db
}

func main() {
// 指定生成代码的具体相对目录(相对当前文件),默认为:./query
// 默认生成需要使用WithContext之后才可以查询的代码,但可以通过设置gen.WithoutContext禁用该模式
g := gen.NewGenerator(gen.Config{
// 默认会在 OutPath 目录生成CRUD代码,并且同目录下生成 model 包
// 所以OutPath最终package不能设置为model,在有数据库表同步的情况下会产生冲突
// 若一定要使用可以通过ModelPkgPath单独指定model package的名称
OutPath: "./internal/dao",
ModelPkgPath: "./internal/model",

// gen.WithoutContext:禁用WithContext模式
// gen.WithDefaultQuery:生成一个全局Query对象Q
// gen.WithQueryInterface:生成Query接口
Mode: gen.WithoutContext | gen.WithDefaultQuery | gen.WithQueryInterface,
})

// 通常复用项目中已有的SQL连接配置db(*gorm.DB)
// 非必需,但如果需要复用连接时的gorm.Config或需要连接数据库同步表信息则必须设置
g.UseDB(connectDB(MySQLDSN))

// 从连接的数据库为所有表生成Model结构体和CRUD代码
//g.ApplyBasic(g.GenerateAllTable()...)

// 也可以手动指定需要生成代码的数据表
//g.ApplyBasic(g.GenerateModel("user"), g.GenerateModel("role"))

// 还可以指定只生成model
var models = [...]string{"user"}
for _, tableName := range models {
// 只生成model
g.GenerateModel(tableName)
// 生成model和query
//tableModel := g.GenerateModel(tableName)
//g.ApplyBasic(tableModel)
}

//g.ApplyInterface(func(model.Filter) {}, g.GenerateModel("user"))
// 执行并生成代码
g.Execute()
}

运行generate.go,将会生成model和dao文件

需要注意mysql的tinyint(1)生成的时候会映射成bool,但是tinyint(1)的实际范围为-128~127,这可能会有些问题;而tinyint(4)生成的时候会映射成int32

2.gorm框架CRUD

1.insert

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func init() {
SetDefault(database.DB)
}

func Test_userDo_Create(t *testing.T) {
user := model.User{
Username: "test",
Email: "test@test",
}
err := User.Create(&user)
if err != nil {
fmt.Println("create user fail")
}
}

2.delete

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func Test_userDo_Delete(t *testing.T) {
/*
user := model.User{
ID: 1,
}
result, err := User.Delete(&user)
*/
result, err := User.Where(User.ID.Eq(2)).Delete()
if err != nil {
fmt.Println("delete user fail")
}
fmt.Println(result)
}

3.update

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func Test_userDo_Update(t *testing.T) {
/*
user := model.User{
ID: 2,
Username: "test2",
Email: "test2@test",
}
result, err := User.Updates(&user)
*/
result, err := User.
Where(User.ID.Eq(2)).
Update(User.Username, "test22")

if err != nil {
fmt.Println("update user fail")
}
fmt.Println(result.RowsAffected)
}

4.select

1
2
3
4
5
6
7
8
9
func Test_userDo_Scan(t *testing.T) {
user := model.User{}
err := User.Where(User.ID.Eq(2)).Scan(&user)
if err != nil {
fmt.Println("scan user fail")
}
fmt.Println(user)
}

参考:GORM Gen使用指南

5.Gorm处理可变结果集

在上面例子中使用scan获得查询结果的时候,字段的个数是固定的,如果当字段的个数是不定长度的时候,可以使用gorm来处理可变结果集

可以将**[]interface{}或者[]orm.Params**来保存查询结果,参考:Golang开发实践:把数据库数据保存到map[string]interface{}中 或者【巧妙】GO + MySQL的通用查询方法

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
func (repo *userRepo) ListUsers(ctx context.Context) (*model.User, error) {
connect, err := repo.data.db.DB()
if err != nil {
repo.log.Error(fmt.Sprintf("connect fail, error: %v", err))
return nil, err
}
rows, err := connect.Query("select * from `user`")
if err != nil {
repo.log.Error(fmt.Sprintf("query fail, error: %v", err))
return nil, err
}
defer rows.Close()
cols, err := rows.Columns()
if err != nil {
return nil, err
}
values := make([]interface{}, 0)
for i := 0; i < len(cols); i++ {
var value interface{}
values = append(values, &amp;value)
}
for rows.Next() {
err = rows.Scan(values...)
if err != nil {
repo.log.Error(fmt.Sprintf("scan fail, error: %v", err))
return nil, err
}
for k, v := range values {
key := cols[k]
var rawValue = *(v.(*interface{}))
switch v := rawValue.(type) {
case string:
fmt.Print("key=>" + key + ":string ")
fmt.Print(v)
case int32:
fmt.Print("key=>" + key + ":int32 ")
fmt.Println(v)
case []uint8:
fmt.Print("key=>" + key + ",type=>uint8[],value=>")
fmt.Print(string(v))
fmt.Print(" ")
default:
fmt.Print(v)
}
}
fmt.Println()
}
return nil, nil
}

输出结果

1
2
3
4
5
6
key=>id,type=>uint8[],value=>5 key=>username,type=>uint8[],value=>test key=>email,type=>uint8[],value=>test@test 
key=>id,type=>uint8[],value=>6 key=>username,type=>uint8[],value=>test key=>email,type=>uint8[],value=>test@test
key=>id,type=>uint8[],value=>7 key=>username,type=>uint8[],value=>test key=>email,type=>uint8[],value=>test@test
key=>id,type=>uint8[],value=>8 key=>username,type=>uint8[],value=>test key=>email,type=>uint8[],value=>test@test
key=>id,type=>uint8[],value=>9 key=>username,type=>uint8[],value=>test key=>email,type=>uint8[],value=>test@test

6.分页

可以使用gorm的scopes来实现分页(先count再分页),参考:Gorm Scopes复用你的逻辑学习gorm系列十之:使用gorm.Scopes函数复用你的查询逻辑

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
func Paginate(pageNum int, pageSize int) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
if pageNum <= 0 {
pageNum = 1
}
if pageSize > 100 {
pageSize = 100
} else if pageSize <= 0 {
pageSize = 10
}
offset := (pageNum - 1) * pageSize
return db.Offset(offset).Limit(pageSize)
}
}

func (repo *userRepo) ListUser(ctx context.Context, pageNum int, pageSize int) ([]*model.User, error) {
var result []*model.User
var total int64
err := repo.data.db.Model(&amp;model.User{}).Count(&amp;total).Error
if err != nil {
repo.log.Error(fmt.Sprintf("list user fail, error: %v", err))
}
err = repo.data.db.Scopes(Paginate(pageNum, pageSize)).Find(&amp;result).Error
if err != nil {
repo.log.Error(fmt.Sprintf("update user fail, error: %v", err))
return nil, err
}
return result, nil
}

测试分页函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func Test_userRepo_ListUsers(t *testing.T) {
data, _, err := NewData(zap_logger, db, rdb)
if err != nil {
panic(err)
}
userRepo := userRepo{data: data, log: zap_logger}
result, err := userRepo.ListUser(context.Background(), 1, 5)
if err != nil {
fmt.Println(err)
}
for _, user := range result {
fmt.Println(user)
}
}

输出

1
2
3
4
5
6
&amp;{5 test test@test}
&amp;{6 test test@test}
&amp;{7 test test@test}
&amp;{8 test test@test}
&amp;{9 test test@test}

Mybatis Page Helper分页插件原理:Mybatis分页插件PageHelper的配置和使用方法

3.gorm框架自定义SQL

gorm还支持编写sql模板,来添加自定义sql的函数逻辑,其中使用的语法是text template,可以参考:

全文 >>

go学习笔记——分布式事务框架DTM

DTM是一款开源的GO语言分布式事务管理器,解决跨数据库、跨服务、跨语言栈更新数据的一致性问题。

DTM可适合多语言栈的公司使用。方便go、python、php、nodejs、ruby、c# 各类语言使用。

支持的分布式事务协议包括:SAGA,TCC,XA和二阶段消息这几种模式。

官方网站:https://dtm.pub/

1.启动DTM服务

参考:https://dtm.pub/guide/install.html#%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%8C%85%E4%B8%8B%E8%BD%BD%E5%AE%89%E8%A3%85

项目

1
2
https://github.com/dtm-labs/dtm

git dtm项目

1
2
git clone https://github.com/dtm-labs/dtm &amp;&amp; cd dtm

启动dtm

1
2
./dtm

dtm运行后,会监听两个端口

1
2
3
http:36789
grpc:36790

2.分布式事务协议

1.SAGA模式

Saga核心思想是将长事务拆分为多个短事务,由Saga事务协调器协调,如果每个短事务都成功提交完成,那么全局事务就正常完成,如果某个步骤失败,则根据相反顺序一次调用补偿操作。

适用场景:适合需要最终一致性的大型跨服务事务,且允许一定时间的临时不一致。

优点:实现简单,不需要锁定资源,性能高。

缺点:数据一致性较弱,可能存在短时间的不一致。

2.TCC模式

TCC分为3个阶段

  • Try 资源预留阶段:尝试执行,完成所有业务检查(一致性), 预留必须业务资源(准隔离性)
  • Confirm 确认阶段:如果所有分支的Try都成功了,则走到Confirm阶段。Confirm真正执行业务,不作任何业务检查,只使用 Try 阶段预留的业务资源
  • Cancel 取消阶段:如果所有分支的Try有一个失败了,则走到Cancel阶段。Cancel释放 Try 阶段预留的业务资源。

适用场景:适合强一致性要求的场景,例如支付扣款、库存预扣等需要确保每个操作的资源在短时间内完全一致。

优点:可以精细控制资源的锁定和释放,提高一致性。

缺点:实现复杂,需为每个操作编写补偿逻辑。

3.二阶段消息模式

4.XA模式

总结

  • 二阶段消息模式: 适合不需要回滚的场景
  • saga模式: 适合需要回滚的场景
  • tcc事务模式: 适合一致性要求较高的场景
  • xa事务模式: 适合并发要求不高,没有数据库行锁争抢的场景

全文 >>

golang项目引用GitHub私有仓库module

1.创建go module项目

module的名字假设为go-test

module项目创建成功后,将go.mod文件中的 module “go-test” 修改成

1
2
module "github.com/tonglin0325/go-test"

避免引用的时候go get报错,如下

1
2
3
4
5
go get github.com/tonglin0325/go-test@latest
go: github.com/tonglin0325/go-test@latest (v1.0.0) requires github.com/tonglin0325/go-test@v1.0.0: parsing go.mod:
module declares its path as: go-test
but was required as: github.com/tonglin0325/go-test

在module中写好代码后上传到github上,并在github上给项目打上tag

如果GitHub repository设置成public的话,此时就可以使用如下命令引用这个module了

1
2
3
4
➜  /Users/lintong/coding/go/gin-template git:(main) ✗ $ go get github.com/tonglin0325/go-test@latest
go: downloading github.com/tonglin0325/go-test v1.0.1
go: added github.com/tonglin0325/go-test v1.0.1

2.引用私有仓库的go module

1.Github私有仓库

如果使用的私有仓库是github,则电脑配置了github的私钥的话,可以直接使用go get命令来引用私有仓库的依赖

如果没有设置的话,则可以使用github的token来下载依赖

在github用户settings的Developer settings中创建一个token,token name假设为goland

指定go module的项目,并设置contents设置为read only

创建后获得token

重新设置github使用账号+token来拉取依赖

1
2
git config --global url."https://tonglin0325:your_token@github.com".insteadOf "https://github.com"

查看git配置是否添加成功

1
2
git config --global -l

如果添加错误可以unset之后再重新set

1
2
git config --global --unset xxx

指定拉取go-test依赖的时候使用私有仓库

1
2
go env -w GOPRIVATE=github.com/tonglin0325/go-test

查看go环境变量

1
2
go env

如果设置失败,可以删除后再添加

1
2
go env -u GOPRIVATE

最后使用go get命令引用依赖

1
2
go get github.com/tonglin0325/go-test@latest

2.Gitlab私有仓库

如果使用的私有仓库是gitlab,则可以参考如下文档,在 ~/.netrc 文件中配置gitlab的access token

Use a project as a Go package

然后使用如下命令来get依赖

1
2
GOPRIVATE=gitlab.xx.com go get gitlab.xx.com/xx/xx

全文 >>

k8s学习笔记——Istio和Kong

服务网格(Service Mesh)是一种用于处理微服务架构中服务间通信的基础设施层。它的主要作用是提供可靠的服务发现、负载均衡、故障恢复、指标监控和安全性,通常无需对服务代码进行大量修改。服务网格通过在每个服务实例旁边部署一个轻量级代理(sidecar)来实现这些功能。

1.Istio#

Istio 是一个开源的服务网格(Service Mesh),透明地层叠到现有的分布式应用程序之上。Istio 强大的功能提供了一种统一且更高效的方式来保护、连接和监控服务。Istio 是实现负载均衡、服务间身份验证和监控的途径——几乎无需修改服务代码。它为你提供:

  • 集群中服务间的安全通信,采用双向 TLS 加密,基于强身份的身份验证和授权
  • HTTP、gRPC、WebSocket 和 TCP 流量的自动负载均衡
  • 通过丰富的路由规则、重试、故障切换和故障注入对流量行为进行细粒度控制
  • 支持访问控制、速率限制和配额的可插拔策略层和配置 API
  • 集群内所有流量(包括集群入口和出口)的自动化指标、日志和跟踪

参考:https://istio.io/latest/docs/overview/what-is-istio/

Istio 的流量管理模型源于和服务一起部署的 Envoy 代理。 网格内服务发送和接收的所有 data plane 流量都经由 Envoy 代理, 这让控制网格内的流量变得异常简单,而且不需要对服务做任何的更改。

Envoy 是在 Istio 里使用的高性能代理,用于为所有服务网格里的服务调度进出的流量。

参考:https://istio.io/latest/zh/docs/concepts/traffic-management/

2.Kong#

Kong 提供了两个主要产品:Kong Gateway 和 Kuma。

Kong Gateway 是一个云原生 API 网关,专注于管理、配置和路由 API 请求。它提供了强大的插件系统,可以扩展其功能以实现身份验证、速率限制、日志记录等功能。Kong Gateway 主要用于处理进出 API 的流量,但它不具备完整的服务网格功能。

Kuma 是 Kong 开发的一个服务网格,旨在简化服务间通信的管理。Kuma 提供了服务网格的所有核心功能,包括服务发现、负载均衡、故障恢复、可观察性和安全性。Kuma 可以部署在多个环境中,包括 Kubernetes 和虚拟机。

k8s学习笔记——ingress

Ingress 提供从集群外部到集群内服务(service)的 HTTP 和 HTTPS 路由。 流量路由由 Ingress 资源所定义的规则来控制。

下面是 Ingress 的一个简单示例,可将所有流量都发送到同一 Service:

ingress-diagram

通过配置,Ingress 可为 Service 提供外部可访问的 URL、对其流量作负载均衡、 终止 SSL/TLS,以及基于名称的虚拟托管等能力。 Ingress 控制器 负责完成 Ingress 的工作,具体实现上通常会使用某个负载均衡器, 不过也可以配置边缘路由器或其他前端来帮助处理流量。

参考:https://kubernetes.io/zh-cn/docs/concepts/services-networking/ingress/

典型的Ingress 控制器有 ingress-nginxIstio Ingress(一个基于 Istio 的 Ingress 控制器),用于 Kubernetes 的 Kong Ingress 控制器(一个用来驱动 Kong Gateway 的 Ingress 控制器)

参考:https://kubernetes.io/zh-cn/docs/concepts/services-networking/ingress-controllers/

Presto学习笔记——Go客户端连接Presto

1.查询PrestoDB(facebook版本)

1.创建PrestoDB环境

使用docker创建presto测试环境

1
2
https://hub.docker.com/r/prestodb/presto/tags

拉取镜像

1
2
docker pull prestodb/presto:0.284

启动

1
2
docker run -p 8080:8080 -ti -v /Users/lintong/Downloads/config.properties:/opt/presto-server/etc/config.properties -v /Users/lintong/Downloads/jvm.config:/opt/presto-server/etc/jvm.config prestodb/presto:0.284

其中config.properties配置文件

1
2
3
4
5
6
7
8
coordinator=true
node-scheduler.include-coordinator=true
http-server.http.port=8080
query.max-memory=5GB
query.max-memory-per-node=1GB
discovery-server.enabled=true
discovery.uri=http://localhost:8080

jvm.config

1
2
3
4
5
6
7
8
9
10
-server
-Xmx4G
-XX:+UseG1GC
-XX:G1HeapRegionSize=32M
-XX:+UseGCOverheadLimit
-XX:+ExplicitGCInvokesConcurrent
-XX:+HeapDumpOnOutOfMemoryError
-XX:+ExitOnOutOfMemoryError
-Djdk.attach.allowAttachSelf=true

配置参考:https://github.com/prestodb/presto/tree/master/docker/etc

https://prestodb.github.io/docs/current/installation/deployment.html

访问localhost:8080

2.使用client连接PrestoDB

可以使用presto-go-client,官方文档

1
2
https://github.com/prestodb/presto-go-client

引入依赖

1
2
go get github.com/prestodb/presto-go-client/presto

go连接数据库使用的是database/sql,还需要额外引入具体数据库的driver,比如

presto的github.com/prestodb/presto-go-client/presto

mysql的github.com/go-sql-driver/mysql

impala的github.com/bippio/go-impala

查询presto代码

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
import (
"database/sql"
"fmt"
_ "github.com/prestodb/presto-go-client/presto"
)

func main() {
dsn := "http://user@localhost:8080?catalog=default&amp;schema=test"
db, err := sql.Open("presto", dsn)
if err != nil {
fmt.Println(err)
}

rows, err := db.Query("SELECT name, age FROM foobar WHERE id=?")
if err != nil {
fmt.Println(err)
}
defer func() {
_ = db.Close()
}()
// 迭代结果行
for rows.Next() {
var col1 string
var col2 int
if err := rows.Scan(&amp;col1, &amp;col2); err != nil {
fmt.Println("扫描行失败:", err)
return
}
fmt.Println(col1, col2)
}
}

参考:golang+presto查询在数据平台中ad hoc查询

如果想同时查询多行的话,也可以先定义一个struct

1
2
3
4
5
6
7
type YourTable struct {
  Col1 string
  Col2 int
  Col3 float64
  // 其他列...
}

然后在迭代查询的时候使用这个struct

1
2
3
4
5
6
7
8
9
10
// 迭代结果行
for rows.Next() {
  var row YourTable
  if err := rows.Scan(&amp;row.Col1, &amp;row.Col2, &amp;row.Col3); err != nil {
    fmt.Println("扫描行失败:", err)
    return
  }
  fmt.Println(row)
}

3.使用REST API连接PrestoDB

参考官方文档

1
2
https://prestodb.io/docs/current/develop/client-protocol.html

其实presto-go-client底层也是使用presto的REST API的POST请求来提交任务,参考源码

1
2
https://github.com/prestodb/presto-go-client/tree/master/presto#L635

2.查询Trino(社区版本)

1.创建Trino环境

创建Trino环境用于测试可以使用docker

1
2
https://hub.docker.com/r/trinodb/trino/tags

拉取镜像

1
2
docker pull trinodb/trino:435

启动

1
2
docker run -p 8080:8080 -ti trinodb/trino:435

访问localhost:8080,默认密码不启用,随便输入一个用户名

界面

2.使用client连接trino

可以使用trino-go-client,官方文档

1
2
https://github.com/trinodb/trino-go-client

引入依赖

1
2
go get github.com/trinodb/trino-go-client/trino

查询trino代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import (
"database/sql"
"fmt"
)
import _ "github.com/trinodb/trino-go-client/trino"

func main() {
dsn := "http://user@localhost:8080?catalog=default&amp;schema=test"
db, err := sql.Open("trino", dsn)
if err != nil {
fmt.Println(err)
}
defer func() {
_ = db.Close()
}()
db.Query("SELECT * FROM foobar WHERE id=?", 1, sql.Named("X-Trino-User", string("Alice")))
}

3.使用REST API连接TrinoDB

参考官方文档

1
2
https://trino.io/docs/current/develop/client-protocol.html

其实trino-go-client底层也是使用trino的REST API的POST请求来提交任务,参考源码

1
2
https://github.com/trinodb/trino-go-client/blob/master/trino/trino.go#L820

全文 >>

go学习笔记——wire依赖注入

wire是google开源的使用依赖注入来自动连接组件的代码生成工具

安装

1
2
go install github.com/google/wire/cmd/wire@latest

官方使用文档:

https://github.com/google/wire/blob/main/docs/guide.md

文档参考:

手把手,带你从零封装Gin框架(十二):使用 Wire 依赖注入重构

golang中的依赖注入之wire

参考项目:

https://github.com/jassue/gin-wire

如果遇到下面报错

1
2
3
4
wire: /xxx/cmd/server/wire.go:17:1: inject wireApp: unused provider set "ProviderSet"
wire: xx/cmd/server: generate failed
wire: at least one generate failure

这是因为设置了依赖注入的方法没能找到调用者,在gin项目中,调用的顺序一般是data层->service层->handler层->router层->httpserver->app

所以需要把调用的ProviderSet在wire.go文件中完整的写出来,不能出现中间中断的情况,wire_gen才能正常的生成

比如写了data,service,router,httpserver和app,但是漏了handler,这样就会报上面的错误

如果只写了router,httpserver和app,这样是可以正常生成的

可以参考:https://github.com/jassue/gin-wire/blob/main/cmd/app/wire_gen.go 中的调用层次

全文 >>

screen使用教程

在terminal上使用跳板机远程登录其他机器的时候,经常会因为和跳板机的连接断开而丢失会话,如下

这时候可以使用screen命令来创建和恢复会话

1.创建会话

1
2
3
4
screen 
或者
screen -S session_name

这时我们就进到了一个screen会话中,比如我们进到/tmp目录下

2.查看现有的会话

这时候我们把这个terminal窗口直接关闭掉,另外开启一个terminal,输入如下命令来查看现有的会话

1
2
screen -ls

会发现当前的会话仍然存在,处于分离状态

3.恢复会话

1
screen -r session_name

会发现我们刚才的/tmp目录的会话就被恢复了

参考:Linux–screen远程必备 #screen常用命令-会话的创建、恢复、删除(&重命名删除)用法

go学习笔记——常用命令

1.查找go依赖

go依赖可以去下面网站查找package

1
2
https://pkg.go.dev/

比如

1
2
https://pkg.go.dev/github.com/confluentinc/confluent-kafka-go#section-readme

2.go切换源

1
2
3
4
5
6
7
# 启用 Go Modules 功能
go env -w GO111MODULE=on
# 切换源
go env -w GOPROXY=https://goproxy.io,direct
# 确认是否生效
go env

3.安装指定依赖

1
2
3
go get -u github.com/confluentinc/confluent-kafka-go/kafka
go get: added github.com/confluentinc/confluent-kafka-go v1.9.2

如果想安装特定版本的依赖

1
2
go get github.com/confluentinc/confluent-kafka-go/kafka@v1.9.2

4.下载依赖

1
2
go mod download

5.清理无用的依赖

下载缺失依赖,并清理无用的依赖(包括清理 go.mod 中的记录)

1
2
go mod tidy

go.mod文件中就会出现所安装的依赖

参考:真官方依赖管理 Go Modules 怎么玩(从入门到放弃)

6.在项目根目录下会生成go.mod文件

1
2
go mod init [模块名]

全文 >>

go学习笔记——gin框架

gin是一款轻量级的go web开发框架,官方文档

1
2
https://gin-gonic.com/docs/examples/

1.gin web项目结构

参考

1
2
https://github.com/voyagegroup/gin-boilerplate

gin+protobuf wire参考

1
2
https://github.com/mohuishou/blog-code/tree/main/01-go-training/04-project/10-layout

2.gin web quick start

1
2
https://gin-gonic.com/docs/quickstart/

在官方文档中提供了2个quick start的demo,一个稍微复杂,一个比较简单

简单的例子如下,创建一个/ping接口,返回pong

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

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

func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run() // listen and serve on 0.0.0.0:8080
}

3.路由分组

如果gin项目的接口比较多的话,可以使用路由分组

1
2
https://gin-gonic.com/docs/examples/grouping-routes/

参考:【Go】基于 Gin 从0到1搭建 Web 管理后台系统后端服务(三)路由、自定义校验器和 Redis

4.数据绑定

将请求的参数传递给接口中的变量,需要使用gin的数据绑定

如果是path parameter

使用c.Param(“name”)

1
2
https://gin-gonic.com/docs/examples/param-in-path/

使用c.ShouldBindUri(&person)

1
2
https://gin-gonic.com/docs/examples/bind-uri/

如果是query parameter或者post请求的form-data

使用c.ShouldBind(&user)

1
2
https://gin-gonic.com/docs/examples/bind-query-or-post/

5.统一的response

可以如下定义统一的接口response,其中的interface{}类似java中的泛型T

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type ControllerResponse struct {
Code int
Msg string
Data interface{}
}

func Response(ctx *gin.Context, code int, msg string, data interface{}) {
resp := ControllerResponse{Code: code, Msg: msg, Data: data}
ctx.JSON(code, resp)
ctx.Abort()
}

func Success(ctx *gin.Context, msg string, data interface{}) {
Response(ctx, 200, msg, data)
}

func Fail(ctx *gin.Context, msg string, data interface{}) {
Response(ctx, 500, msg, data)
}

参考:go语言web开发系列之十五:gin框架统一定义API错误码

6.Gin的middleware中间件

1.使用middleware

gin的middleware需要实现gin.HandlerFunc,可以用其来实现诸如jwt,auth等功能,参考

https://github.com/gin-gonic/contrib/blob/master/jwt/jwt.go

https://gin-gonic.com/zh-cn/docs/examples/using-middleware/

下面定义了3个middleware

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
// MiddleWare1 定义中间件1
func MiddleWare1() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("前 m1")
c.Next()
fmt.Println("后 m1")
}
}

// MiddleWare2 定义中间件2
func MiddleWare2() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("前 m2")
c.Next()
fmt.Println("后 m2")
}
}

// MiddleWare3 定义中间件3
func MiddleWare3() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("前 m3")
c.Next()
fmt.Println("后 m3")
}
}

在gin中添加middleware

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func main() {
// 创建路由
engine := gin.Default()

// 注册中间件
engine.Use(MiddleWare1(), MiddleWare2(), MiddleWare3())

// 路由规则
engine.GET("/ping", func(c *gin.Context) {
fmt.Println("hello world")
c.JSON(200, gin.H{"msg": "pong"})
})
err:=engine.Run(":3000")
if err != nil {
fmt.Println(err)
}
}

输出如下

1
2
3
4
5
6
7
8
9
前 m1
前 m2
前 m3
hello world
后 m3
后 m2
后 m1
[GIN] 2023/12/25 - 00:06:11 | 200 | 35.726&micro;s | 127.0.0.1 | GET "/ping"

接口返回pong

2.Next()和Abort()

Next()之前的代码会在执行HandlerFunc之前执行,之后的代码会在执行HandlerFunc之后执行

如果将middleware2中的c.Next()修改成c.Abort()

1
2
3
4
5
6
7
8
9
// MiddleWare2 定义中间件2
func MiddleWare2() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("前 m2")
c.Abort()
fmt.Println("后 m2")
}
}

则输出将会如下

1
2
3
4
5
6
前 m1
前 m2
后 m2
后 m1
[GIN] 2023/12/25 - 00:11:05 | 200 | 16.262&micro;s | 127.0.0.1 | GET "/ping"

接口无返回

可以Abort()不会阻止当前middleware的执行,但是会中止下游middleware以及HandlerFunc的执行

参考:[Go Package] gin 中间件流程控制:c.Next() / c.Abort()

3.全局中间件和局部中间件

1.白名单

上面给出的例子中,middleware将对全部的请求生效,这里可以通过指定白名单的方法来使其只对部分请求生效

2.局部中间件

参考:Golang Gin 局部、全局 中间件使用

7.在Gin中使用http handler中间件

除了使用gin的middleware来实现中间件之外,也可以使用Go的标准库的net/http包来初始http请求

参考:GO中间件(Middleware)

在gin中使用的话,需要将http.Handler转换成gin.HanlderFunc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// MiddleWare4 定义中间件4
func MiddleWare4(next http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
fmt.Println("前 m4")
next.ServeHTTP(writer, request)
fmt.Println("后 m4")
})
}

// 将 http.Handler 包装成 Gin 中间件的函数
func WrapHandler(handler http.Handler) gin.HandlerFunc {
return func(c *gin.Context) {
handler.ServeHTTP(c.Writer, c.Request)
c.Next()
}
}

在router中添加middleware,注意需要将middleware写在局部路由的前面才能生效

1
2
3
4
5
6
7
8
9
10
wrappedMiddleware := middleware.WrapHandler(middleware.MiddleWare4(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 空的 http.HandlerFunc,只为了包装中间件
})))
r.Use(wrappedMiddleware, middleware.MiddleWare1(), middleware.MiddleWare2())

// Ping test
r.GET("/ping", func(c *gin.Context) {
c.String(http.StatusOK, "pong")
})

参考:https://github.com/go-kratos/examples/blob/main/http/middlewares/middlewares.go

全文 >>