标签 go 下的文章

起因是这样的,我有一个 Go 写的 utils 库,但这个仓库目前是 private 的,于是在其他引用了这个库的项目构建时就需要解决鉴权访问的问题。

先来看看不使用私有库的 Go 项目构建的 Dockerfile:

# build api
FROM golang:1.23 AS builder

WORKDIR /src/app

COPY . ./

RUN --mount=type=cache,target=/go/pkg/mod \
    make

# build the final image
FROM ubuntu:24.04

COPY --from=builder /src/app/bin/api /app/bin/api

而使用了私有库的话,就需要设置 GOPRIVATE 环境变量和鉴权。

在本地开发的时候我是用的 ssh 来访问 git 仓库的,所以在 ~/.gitconfig 追加如下配置即可:

[url "[email protected]:"]
    insteadOf = https://github.com/

但是使用 Docker 构建的时候继续使用 ssh 就会略麻烦(使用 Deploy key)和不安全(使用个人账号 SSH key),那么细粒度的 Access Token 会是更好的选择。

因为 Access Token 是敏感数据,我们是不能直接写在 Dockerfile 里的,所以你可能会想到那就通过构建参数传进去:

+ARG GITHUB_TOKEN
+ENV TOKEN=$GITHUB_TOKEN

 WORKDIR /src/app

 COPY . ./

+RUN go env -w GOPRIVATE="github.com/YianAndCode/MY_PRIVATE_GO_PKG"

+RUN git config --global url."https://${TOKEN}:[email protected]/".insteadOf "https://github.com/"

 RUN --mount=type=cache,target=/go/pkg/mod \
    make

然后构建:

export GITHUB_TOKEN=xxx
docker build --build-arg GITHUB_TOKEN=${GITHUB_TOKEN} -t myapp:latest .

这时你会得到一个警告:

WARN: SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data

因为这样做别人在拿到镜像之后是可以看到你构建过程中的参数的,也不安全。其实在 Docker 官方文档有介绍 Build secrets,我们可以利用这个功能来实现更安全优雅地传入敏感数据。

首先来学习一下 build secrets 的基本知识:

- 阅读剩余部分 -

在 Go 里可以通过 CGO 调用 C 函数,最简单的写法就是直接在 import "C" 前一行写 C 函数:

package main

/*
int sum(int a, int b) { return a + b; }
*/
import "C"
import "fmt"

func main() {
    fmt.Println(C.sum(C.int(1), C.int(2)))
}

但,如果是那么简单的函数,也不需要用到 CGO,直接 Go 实现就行了,真要用到 CGO 了,大概率就是你需要调用一些比较复杂的 C 代码,可以用下面两种方式实现:

- 阅读剩余部分 -

最近在给一个 HTTP 服务写 SDK,这个 HTTP 服务的响应是下面这种经典格式:

{
    "cdoe": 0,
    "msg": "ok",
    "data": {}
}

那么在 Go 里定义结构体的时候就会长这样:

type Custom struct {
    // ...
}

type SomeResponse struct {
    Code int    `json:"code"`
    Msg  string `json:"msg"`
    Data Custom `json:"data"`
}

相应的方法会长这样:

SomeService(ctx context.Context, param any) (*Custom, error)

因为 Custom 的内部可能会有很多数据,所以是以指针作为返回值的,尽量避免数据拷贝;而 Code != 0 这种情况就通过 error 返回。

内部实现大概会是这样:

func (s SDK) SomeService(ctx context.Context, param any) (*Custom, error) {
    var resp SomeResponse
    err := s.request(ctx, "/someservice", param, &resp)
    if err != nil {
        return nil, err
    }

    if resp.Code != 0 {
        return nil, fmt.Errorf("code: %d, err: %s", resp.Code, resp.Msg)
    }

    return &resp.Data, nil
}

通常我们到这一步就算是完成任务了,但是这时候我想了下:return 之后,GC 是怎么回收 resp 的呢?是会把 resp.Coderesp.Msg 的内存回收了,只让 resp.Data 逃逸吗?

这个问题不算复杂,动手做个实验就知道了。先把上面的代码简化一下:

- 阅读剩余部分 -

最近在用 LemonSqueezy(lemonsqueezy.com) 做 side project 的收款服务,需要用到 webhook 来处理用户付款后的自动发货流程。

项目后端用的是 Go 语言,gin 框架,截止项目启动的时,没能在 GitHub 上找到 LemonSqueezy 的 webhook SDK,于是就徒手写了个,用了一段时间之后把代码整理了一下开源了。

因为 gin + lemon,于是就取名叫 gin and tonic 了😆

如果刚好你也在用 gin 框架,那么这个库你可以开箱即用:

Gin&Tonic

项目地址:https://github.com/YianAndCode/gintonic

最后祝大家财源滚滚,It's lemon squeezy to make money!