Go 常见 linter

静态分析是一种编译时的编译器检查,它可以检查代码的语法,语义,结构,等等,但是不能检查代码的运行时的错误。 静态分析一般会集成在项目上线的 Ci 流程中,如果过程中出现了错误,那么项目就不能上线,避免了有问题的代码被部署上线。 常见的 go linter 在 go 语言社区中,已经有了很多的 linter,这些 linter 可以帮助我们检查代码的语法,语义,结构等等,接下来我来介绍一些常见的 linter。 go lint go lint 是 go 官方推出的最早的 linter, 它可以检查导出函数是否有注释,变量、函数等名称是否符合官方规范。随着近年来的发展,各种 linter 已经层出不穷了,go lint 已经被 deprecated and frozen 了,官方推荐使用 Staticcheck 和 go vet 等 linter。 go vet go vet 也是官方提供的静态分析工具,其内置了锁拷贝检查、循环变量捕获问题、printf 参数不匹配等工具。 比如下面的例子: package main import "sync" func main() { var wg sync.WaitGroup for _, v := range []int{0,1,2,3} { wg.Add(1) go func() { print(v) wg.Done() }() } wg.Wait() } 执行 go vet 命令会提示相应错误,如下图所示: 正确应该使用如下代码: ...

十一月 28, 2021 · 1 分钟 · overstarry

WebAssembly In Go

WebAssembly 介绍 WebAssembly 是一种新的编码方式,可以在现代的网络浏览器中运行 - 它是一种低级的类汇编语言,具有紧凑的二进制格式,可以接近原生的性能运行,并为诸如 C / C ++等语言提供一个编译目标,以便它们可以在 Web 上运行。它也被设计为可以与 JavaScript 共存,允许两者一起工作。 Go 1.11 向 WebAssembly 添加了一个实验性端口。Go 1.12 对其某些部分进行了改进,预计 Go 1.13 会进一步改进。 简单入门 1 编写 main.go package main import "fmt" func main() { fmt.Println("Hello, WebAssembly!") } 2 使用命令编译 GOOS=js GOARCH=wasm go build -o main.wasm 这将生成一个可以在浏览器中运行的 WebAssembly 文件。 要在浏览器中执行 main.wasm,我们还需要一个 JavaScript 支持文件和一个 HTML 页面来将所有内容连接在一起。 3 复制 js 支持文件 cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" . 4 创建 HTML 页面 <html> <head> <meta charset="utf-8"/> <script src="wasm_exec.js"></script> <script> const go = new Go(); WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then((result) => { go.run(result.instance); }); </script> </head> <body></body> </html> 5 启动 web 服务器 ...

十一月 27, 2021 · 1 分钟 · overstarry

kratos 自定义 handler func 没有请求日志的问题及解决

出现的问题 最近在使用 kratos 开发 api 的时候,由于通过 proto 生成的 server handler 不符合业务的需求,需要通过自定义 handlerFunc 来定义接口。在开发中为了程序的可观测性,我使用了 kratos 提供的 logging 中间件。 在使用的过程中,我发现自定义的 HandlerFunc 的请求日志没有显示,而 proto 生成的请求正确显示了。 问题的原因 为了找到出现这种情况的原因,我在官方的 Github 仓库提了个 issue(#1566), 得到了维护人员的解答。 出现这种情况的原因是 自己定义的 handlerFunc 不走 middleware 中间件,需要自定义 http filter 才能解决。 解决 经过对 http filter 例子的简单研究,参考了官方的 logging 中间件,我自己实现了 logging filter. 代码: package middleware import ( "fmt" "net/http" "time" "github.com/go-kratos/kratos/v2/errors" "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/transport" ) func Server(logger log.Logger) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var ( code int32 reason string kind string operation string ) ctx := r.Context() startTime := time.Now() if info, ok := transport.FromServerContext(ctx); ok { kind = info.Kind().String() operation = info.Operation() } next.ServeHTTP(w, r) if se := errors.FromError(nil); se != nil { code = se.Code reason = se.Reason } level, stack := extractError(nil) _ = log.WithContext(ctx, logger).Log(level, "kind", "server", "component", kind, "operation", operation, "args", extractArgs(r), "code", code, "reason", reason, "stack", stack, "latency", time.Since(startTime).Seconds(), ) }) } } // extractArgs returns the string of the req func extractArgs(req interface{}) string { if stringer, ok := req.(fmt.Stringer); ok { return stringer.String() } return fmt.Sprintf("%+v", req) } // extractError returns the string of the error func extractError(err error) (log.Level, string) { if err != nil { return log.LevelError, fmt.Sprintf("%+v", err) } return log.LevelInfo, "" } 顺利的显示了请求的日志: ...

十月 22, 2021 · 2 分钟 · overstarry

golang 随机 time.Sleep 出现的问题

问题 最近需要使用 rand 包随机 time.Sleep() 的时间,我的代码是这样的: package main import ( "math/rand" "time" ) func main() { rand.Seed(time.Now().UTC().UnixNano()) n := rand.Intn(10) time.Sleep(n * time.Second) } 会遇到下面的问题: Cannot use 'n * time.Second' (type int) as the type Duration Invalid operation: n * time.Second (mismatched types int and Duration) 解决的方法 使用 time.Duration 转换类型,代码如下: package main import ( "math/rand" "time" ) func main() { rand.Seed(time.Now().UTC().UnixNano()) n := rand.Intn(10) time.Sleep(time.Duration(n) * time.Second) } 问题的原因 time.Sleep 方法接收的类型是 Duration 类型。 func Sleep(d Duration) 当我们要 sleep 5s 时,参数是 5 * time.Second, Second 又是 1000 * Millisecond, Millisecond 是 1000 * Microsecond,Microsecond 是 1000 * Nanosecond,Nanosecond 是 1 纳秒,它的类型就是 Duration。最后 5s 就变成 5000000000 数值。 ...

十月 15, 2021 · 1 分钟 · overstarry

如何在 gin 中查看 Prometheus 指标

在我们的 go 应用程序中,我们如果想要查看应用程序的相关指标,该如何操作呢? 解决 Prometheus 的官方 pkg 提供了 promhttp.Handler() 方法,但是该方法返回了一个 http.Handler 接口, 不满足 gin 所使用的类型,我们采用其他的方法进行。 具体的代码: promhttp.InstrumentMetricHandler(prometheus.DefaultRegisterer, promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{ DisableCompression: true, })).ServeHTTP(c.Writer, c.Request) 通过在路由处理函数中添加此代码,就能顺利的展示与应用程序相关的指标。 如果想要自定义指标,你只需将自定义的指标进行相应的注册。 参考链接 https://github.com/prometheus/client_golang

九月 30, 2021 · 1 分钟 · overstarry

遍历 map 列表的 Golang 模板

遍历 map 列表的 Golang 模板遇到的问题及解决方式 出现了什么问题? 今天在使用 go template 渲染文本的时候,在运行的时候,遇到了一个问题导致渲染不成功,具体的问题是: 2021/09/10 09:47:05 template: test:2:3: executing "test" at <.Attributes>: can't evaluate field Attributes in type interface {} 具体的代码如下: package main import ( "log" "os" "strings" "text/template" ) type TestData struct { Name string `json:"name"` Attributes map[string]interface{} } const testTemplate = `{{ range $key, $value := .Attributes }} {{.Name}} {{ $key }}: {{ $value }}{{- end }}` func main() { tmpl, err := template.New("test").Parse(strings.TrimSpace(testTemplate)) if err != nil { log.Println(err) return } data := TestData{ "hello", map[string]interface{}{ "key": "hello", "value": "json", }, } err = tmpl.Execute(os.Stdout, data) if err != nil { log.Println(err) return } } 问题出现的原因? 模板在 range 循环中会把。设置为当前 Attributes 变量,在 Attributes 变量中没有 name 这个成员结构,这就导致模板没有渲染成功。 ...

九月 10, 2021 · 2 分钟 · overstarry

Golang Templates

Golang Template 今天来讲讲 golang 中的标准库 template, Go 标准库提供了几个 package 可以产生输出结果,主要有 2 个:text/template 和 html/template, text/template 提供根据模板输出内容,html/template 产生安全的 HTML 的输出,这两个库的使用方式很相似,文中的例子大部分是基于 html/template 展示的。 解析和创建模板 模板命名 template 所使用的库没有限定扩展名,但最经常使用的后缀是 .tmpl, 编辑器对.tmpl 的支持最好,官方的例子也是使用 .tmpl, .tpl 也经常使用。 创建模板 通过 Parse 方法可以创建文件名为名字的模板。 package main import ( "fmt" "html/template" "log" ) func main() { tpl, err := template.New("index").Parse("index.tmpl") if err != nil { log.Fatalln(err) } } 解析多个模板 通过 template.ParseFiles(filenames …string) 方法可以解析一组模板,使用各个文件名作为模板名称。 template.ParseGlob(pattern) 方法会根据 pattern 解析所有匹配的模板并保存。 package main import "html/template" func main() { template.ParseFiles("index.tmpl", "index2.tmpl") } 解析字符串模板 除了可以解析文件,还可以解析字符串模板。 ...

九月 3, 2021 · 2 分钟 · overstarry

Wire 入门

今天我们来讲一讲 Wire 的入门使用。 Wire 是什么 go 的依赖注入工具常见有 2 种,一类是通过反射在运行时进行依赖注入,典型代表是 uber 开源的 dig,另外一类是通过 generate 进行代码生成,典型代表就是我今天要将的 Google 开源的 wire。使用 dig 功能会强大一些,但是缺点就是错误只能在运行时才能发现,这样如果不小心的话可能会导致一些隐藏的 bug 出现。使用 wire 的缺点就是功能限制多一些,但是好处就是编译的时候就可以发现问题,并且生成的代码其实和我们自己手写相关代码差不太多,更符合直觉,心智负担更小。 Wire 使用 安装 Wire 的安装十分简单,只要执行 go get github.com/google/wire/cmd/wire , wire 命令行工具就会被安装到 $GOPATH/bin 目录下。 核心概念 在正式使用前,我来介绍一下 Wire 中的 2 个重要概念:Provider 和 Injector。 Provider Provider 是一个普通的函数,这个函数会返回构建依赖关系所需的组件。如下所示,就是一个 provider 函数,在实际使用的时候,往往是一些简单工厂函数。 func NewDb(opt *DbOpt)(*Db, error){...} 在使用中,不能存在 2 个 Provider 返回相同的类型。 Injector Injector 是由 wire 自动生成的函数。函数内部会按根据依赖顺序调用相关 Provider。 我们往往在 wire.go 文件中定义 Injector 函数签名。然后通过 wire 命令行工具生成完整函数。由于 wire.go 中的函数并没有真正返回值,为避免编译器报错,简单地用 panic 函数包装起来即可。不用担心执行时报错,因为它不会实际运行,只是用来生成真正的代码的依据。 ...

八月 27, 2021 · 2 分钟 · overstarry

Go 泛型初探

go 泛型初探 go1.17 于几天前正式发布,虽然 go 的泛型还没正式发布,但 go 的泛型代码已经并入 master 分支,所以我们可以在 go1.17 版本中提前试用泛型。 下载 go1.17 如果你目前没有 go 的任何版本,你只能去官网链接下载 1.17 版本,如果已有了 go 的其它版本,你可以通过以下方式下载使用 go1.17: go get golang.org/dl/go1.17 go1.17 download go1.17 version 这样就 1.17 就下好了。 例子 接下来我们来看看 2 个使用泛型的例子。 例子 1: package main import ("fmt") func print[T any](s []T) { for _, v := range s { fmt.Println(v) } } func main() { print([]string{"Hello, ", "overstarry\n"}) print([]int64{1, 2, 3}) print([]float64{1, 2, 3}) } go 使用[] 定义类型参数,与 java、c++ 等的<>有很大不同,any 关键字来表示任意类型约束。然后怎么运行呢?简单的 go run\build 是肯定不行的,要运行泛型代码,你需要在 run\build 后加-G 参数, 运行的命令是: ...

八月 20, 2021 · 1 分钟 · overstarry

Go_Modules 指南和常见问题

Go Modules 简明使用 Go Modules 是从 go1.11 开始初步支持,到现在的 1.16、1.17 默认开启,是目前 go 项目的推荐包管理方式。接下来让我们看一下 go modules 的简单使用方法。 使用 Go Modules 初始化 # go mod init [包路径] go mod init github.com/overstarry/go-mod-example 执行上述命令会生成 go.mod 文件 module github.com/overstarry/kratos-demo go 1.16 添加依赖 在当前项目的目录中执行 go get 命令,会添加相应的库到 mod 文件中 go get github.com/go-kratos/kratos/v2 这个命令会在 mod 文件里添加以下信息 require ( github.com/go-kratos/kratos/v2 v2.0.1 ) 在 go get 的时候如果不手动指定版本信息,会自动拉取最新的版本的包 如果想要拉取指定版本可以通过 go get github.com/go-kratos/kratos/v2 v2.0.1 的方式,支持 @版本号 例如 @v2.0.1 @分支名 例如 @master @commit tag 例如 @6cff360233dc4457f1536e4f3df4e4e740fd3410 // indirect 表示,我们在代码中没有直接应用这个包 执行完 go get 命令之后还会在目录下生成一个 go.sum 文件 github.com/go-kratos/kratos/v2 v2.0.0-rc7 h1:Qvpz07BefgMFQycSDb57NlWhtYGz4me3wh8E1naI9/k= github.com/go-kratos/kratos/v2 v2.0.0-rc7/go.mod h1:/2bGobqE+/F9kKOe4Re0OO5X2NWNGt+7n2e8Y5DHFRc= github.com/go-kratos/kratos/v2 v2.0.1 h1:iFteVlcLWnAQu5n4I5bTN63svW+0YylGwhNTYO2MkOQ= github.com/go-kratos/kratos/v2 v2.0.1/go.mod h1:Jz6fuJFF2SLczQ7Y8ocKieVGgstQKa+R9NX09bCHekU= 这个文件主要包含当前依赖的所有的包。 ...

七月 30, 2021 · 1 分钟 · overstarry