作者:matrix
发布时间:2024-07-06
分类:Golang Linux
在构建GO的docker镜像时,都需要安装很多开发环境和依赖包,如果正常打包完整环境为镜像完全没有必要。因为运行时只需要Golang打包的二进制文件,不需要完整 dev 环境。
dockerfile多阶段构建就可以完美解决,将构建和运行环境分开,可以最终镜像最小化。 😆 😆 爽~~
# 第一阶段:使用开发环境镜像进行构建,设置别名builder
FROM golang:1.22 AS builder
# 设置工作目录
WORKDIR /app
# 复制所有文件到工作目录
COPY . .
# 编译应用程序
RUN go build -o go-demo .
# 第二阶段:使用小体积的基础镜像 打包最终镜像
FROM alpine:latest
WORKDIR /app
# 从构建阶段复制编译好的可执行文件
COPY --from=builder /app/go-demo .
# 运行可执行文件
CMD ["./go-demo"]
这样就可以确保最终的镜像只包含运行应用所需的最小文件,镜像环境也只是基础的alpine镜像。
作者:matrix
发布时间:2023-12-31
分类:Golang
远程环境可能会有远程调试需求,比如白名单访问限制等情况
要让本地环境调试远程环境数据,本地代码和远程环境执行代码必须保持一致
安装dlv
远程服务器环境安装 dlv
$ go install github.com/go-delve/delve/cmd/dlv@latest
查看已安装dlv版本
$ dlv version
Delve Debugger
Version: 1.21.0
Build: $Id: fec0d226b2c2cce1567d5f59169660cf61dc1efe
启动dlv服务
方法a. 监听已启动进程
$ dlv attach 28122 --listen=:8669 --headless --api-version=2 --log
28122 为已启动进程id
8669 为dlv开启的监听端口
方法b. 通过dlv直接启动指定bin文件
$ dlv exec --listen=:8669 --headless --api-version=2 --log ./main-hhtjim
8669 为dlv开启的监听端口
main-hhtjim 为打包的bin文件
方法c. dlv直接启动并且监听
$ dlv debug --listen=:8669 --headless --api-version=2 --log
8669 为dlv开启的监听端口
本地vscode启动debug
本地配置 .vscode/launch.json
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch Remote",
"type": "go",
"request": "attach",
"mode": "remote",
"remotePath": "/home/work/demo-go/", //项目远程根路径
"port": 8669, //监听端口
"host": "www.hhtjim.com", //远程主机/IP
"cwd": "${workspaceFolder}",//vscode本地工作目录
"trace": "verbose" //输出详情
}
]
}
![](https://static.hhtjim.com/wp-content/uploads/2023/12/img_65918f8860f70.png?sign=bba37b42ed61b9547e4e17b7b0b87050&t=1722055639)
销毁dlv监听
调试环境不使用之后切记销毁,dlv目前无身份校验!
作者:matrix
发布时间:2023-11-30
分类:Golang
使用gorm时,可能需要处理虚拟字段(不在数据库中实际存在的字段)的情况。可以使用结构体tag标签来支持
User结构体模型
type User struct {
ID uint `gorm:"primaryKey;not null"` // 主键ID
// 虚拟字段
Isvip int `gorm:"-;default:0"` // 是否vip 1是 0否
}
说明:
IsVip字段被标记为 gorm:"-"
,表示虚拟字段。GORM在进行数据库操作(如查询、插入、更新等)时,将不会考虑此字段。同时,可以使用default
标签为其指定默认值。
自定义获取器
自定义一个Get方法 例如,下面的GetIsVip方法会基于用户的VIP状态来返回相应的值:
func (u *User) GetIsVip() int {
if u.Vip != nil && u.Vip.IsActive == 1 {
return 1
}
return 0
}
应用获取器
在查询User对象时,GORM提供了 AfterFind
方法来自动执行特定逻辑。这在处理虚拟字段时很有用:
// 查询数据时自动赋值字段
func (u *User) AfterFind(tx *gorm.DB) (err error) {
if u.Vip == nil {
//TIPS:Association方法手动触发模型关联。如果使用Preload会再次查询User主表,不推荐
// tx.Preload("Vip").First(&u, u.ID) //不推荐
tx.Model(u).Association("Vip").Find(&u.Vip)
}
u.Isvip = u.GetIsVip() // 手动触发虚拟字段计算
return
}
说明:
首先检查VIP信息是否已加载。如果未加载,则使用Association方法手动触发加载。之后,我们使用前面定义的GetIsVip方法来计算并设置Isvip字段的值。
注意
使用AfterFind可能会覆盖Isvip字段的默认值(如default:0
)
作者:matrix
发布时间:2023-10-31
分类:Golang
![图片5702-ShouldBindQuery读取GET参数默认值](//static.hhtjim.com/wp-content/uploads/2023/10/3111698728448_.pic_.jpg?sign=967d89dcd20b2907447bda35bc56813e&t=1722055639)
验证器结构体的form标签中设置default即可,仅测试ShouldBindQuery有效读取,其他绑定方法未知
验证器结构体
type UserListValidator struct {
Type string `form:"type,default=RECOMMEND" binding:"omitempty,oneof=NEAR RECOMMEND" label:"列表类型"` //列表类型 NEAR:附近(默认) RECOMMEND:推荐
}
控制器方法
func (u *UserController) List(ctx *gin.Context) {
validator := validators.UserListValidator{}
if err := ctx.ShouldBindQuery(&validator); err != nil {
u.JSONResponseError(ctx, err)
return
}
//validator.Type
参考:
https://github.com/gin-gonic/gin/issues/1052#issuecomment-1609678741
https://www.vksir.zone/posts/go_struct_default/
作者:matrix
发布时间:2023-10-18
分类:Golang
![图片5697-JSON Merge Patch 合并结构体字段数据](//static.hhtjim.com/wp-content/uploads/2023/10/86958e3b-ff80-4a19-afd5-f48ecc20a68c.jpeg?sign=67ba5fbee893c5e9f59b8557af2c0689&t=1722055639)
json Merge Patch,是一个Internet Engineering Task Force(IETF)标准。基本思想是,你有一个原始的JSON对象,然后根据提供的“补丁”JSON对象,最终生成原始JSON对象需要修改的结果。这种机制适用于部分更新(也称为PATCH更新)的场景。
例子
原始对象:
{
"Account": "old_account",
"Name": "old_name",
"Avatar": "old_avatar"
}
补丁对象(patch object):
{
"Account": "new_account",
"Name": null
}
应用补丁对象后的待更新数据(PATCH更新):
{
"Account": "new_account",
"Avatar": "old_avatar"
}
简单来说,补丁对象(patch object)描述了以下几种修改:
golang使用
使用实现IETF标准的JSON Merge Patch依赖库 json-patch
go get -u github.com/evanphx/json-patch
// JOSN PATCH
// dst 原始对象
// patch 补丁对象
// return 将补丁应用到原始对象
func MergePatch(dst, patch interface{}) error {
// 序列化目标(原始)结构体到JSON
dstJSON, err := json.Marshal(dst)
if err != nil {
return err
}
// 序列化补丁结构体到JSON,这个补丁描述了如何修改目标(原始)对象
patchJSON, err := json.Marshal(patch)
if err != nil {
return err
}
// 使用补丁合并目标(原始)对象
mergedJSON, err := jsonpatch.MergePatch(dstJSON, patchJSON)
if err != nil {
return err
}
// 反序列化合并后的JSON回到目标(原始)结构体
return json.Unmarshal(mergedJSON, dst)
}
调用:
if err := MergePatch(&originJSON, &patchJSON); err != nil {
u.JSONResponseError(ctx, err)
return
}
// originJSON 就是应用过补丁的最新原始结构数据
参考:
https://datatracker.ietf.org/doc/html/rfc7396
作者:matrix
发布时间:2023-07-07
分类:Golang
Golang中嵌入结构体类型有两种:值或指针
结论
创建 含内嵌指针struct实例时,必须手动声明嵌入的结构指针。
伪代码如下:
package main
type BaseDao struct{name string}
# 匿名结构体字段BaseDao
type OptDao1 struct{BaseDao} # 嵌入值
type OptDao2 struct{*BaseDao} # 嵌入指针
func main(){
opt := OptDao2{BaseDao:&BaseDao{}} //必须手动声明嵌入的结构指针
}
上面代码中OptDao1、OptDao2嵌入了BaseDao结构体,主要区别只有嵌入值的类型不同。值和指针区别
嵌入值
创建OptDao1对象
mOptDao1 := OptDao1{}
mOptDao1.name
代码调用会正常,属性name会获取到空字符串
其他例:
type Base struct {
value int
}
func (b *Base) Increase() {
b.value++
}
type Derived struct {
Base
}
func main() {
d := Derived{}
d.Increase()
fmt.Println(d.value) // 输出 1
}
嵌入指针
创建OptDao2对象
mOptDao2 := OptDao2{}
mOptDao2.name
上面代码调用会出现nil空指针异常,runtime error: invalid memory address or nil pointer dereference
,即nil指针解引用错误。
原因是访问一个nil对象的方法或属性,这就会panic。
怎么办?
mOptDao2 := OptDao2{BaseDao:&BaseDao{}}
mOptDao2.name
创建mOptDao2实例时必须声明嵌入的结构指针
其他例:
type Base struct {
value int
}
func (b *Base) Increase() {
b.value++
}
type Derived struct {
*Base
}
func main() {
d := Derived{Base: &Base{}}
d.Increase()
fmt.Println(d.value) // 输出 1
}
简而言之,选择值类型嵌入还是指针类型嵌入,主要取决于你是否需要多个实例共享同一个嵌入实例的状态。如果你需要共享状态,使用指针类型嵌入。如果你不需要共享状态,使用值类型嵌入。
一般情况下选择嵌入值即可,除非多个对象需要共享一个Base结构实例。
作者:matrix
发布时间:2023-06-24
分类:Golang
熟悉面向对象语言的话,Golang的struct结构体有点像面向对象编程中的类
。但这两者不是完全一样,只能说都有继承、封装、多态的特点。
结构体(struct)
结构体可以将零个或多个任意类型的值聚合在一起,能描述多个数据类型
type Person struct {
name string
age int
value string
address string
}
成员方法和接收者
func (p *Person) setName(name string) {
p.name = name
}
说明:
setName
为声明的方法
p *Person
为接收者(指针类型)
struc类型新增成员方法的语法很另类,像是单独给struct做绑定,绑定的时候会有接收者
来指定当前实例类型。
Golang其实可以给任何类型创建成员方法:
type MyInt int
func (i MyInt) IsZero() bool{
return i == 0
}
这里通过声明int的自定义类型MyInt,然后绑定一个成员方法。灵活~
值接收者 指针接收者
方法的接收者
可以是结构体的值
或者指针
。上面例子的接收者是一个Person类型的指针。指针接收者的一个优点是可以直接修改接收者的字段值,还避免值的拷贝(内部实际上是拷贝的指针)。
声明为值接收者也是可以:
func (p Person) getName() string {
return p.name
}
getName方法会在调用时复制接收者,就可能会导致性能问题。一般是建议使用指针作为接收者
小结
值接收者或者指针接收者 都能调用结构体或者内嵌结构体的方法或者属性。
给结构体绑定成员方法时,参数最好使用指针,防止值拷贝
func (this *Person) setName(name string)
// 申明结构体struct
type Person struct {
name string
age int
value string
address string
}
//结构绑定
// 给结构体绑定成员方法。 不推荐使用,内存利用低效(参数会使用值传递,会内存拷贝)
//p Person这里p表示值接收者
// func (p Person) getName() string {
// 这里的p变量指针和外部调用的mPerson不同,这里属于值拷贝!!!
// return p.name
// }
// 给Person结构体指针绑定成员方法,同上面效果。但是参数属于引用传递
//this为指针接收者
func (this *Person) getName() string {
return this.name
}
// 给Person结构体指针绑定方法
func (this *Person) setName(name string) {
this.name = name
}
func main() {
//创建实例
// var mPerson *Person = new(Person)//返回实例指针
// var mPerson Person = *new(Person)//返回实例
// var mPerson Person = Person{}//返回实例
var mPerson *Person = &Person{name: "Hi~"} //获取实例指针
mPerson.setName("")
fmt.Println(mPerson.getName())
}
作者:matrix
发布时间:2023-02-10
分类:Golang
Golang支持跨平台编译,比起python只能编译为当前平台的二进制文件要好太多。
go build help
$ go help build
usage: go build [-o output] [build flags] [packages]
...
默认编译(输出当前平台二进制文件)
$ go build -ldflags="-s -w" main.go && upx -9 ./main
说明:
upx 该工具用于压缩二进制文件 -9 表示最高压缩率(选择范围为1-9)
跨平台编译
Mac/Linux系统下:
输出Linux可执行文件:
$ CGO_ENABLE=0 GOOS=linux GOARCH=amd64 go build -ldflags='-s -w -extldflags "-static -fpic"' -o main-linux-amd64 main.go && upx -9 ./main-linux-amd64
输出Windows可执行文件:
$ CGO_ENABLE=0 GOOS=windows GOARCH=amd64 go build -ldflags='-s -w -extldflags "-static -fpic"' -o main-windows-amd64.exe main.go && upx -9 ./main-windows-amd64.exe
输出Mac可执行文件:
# Intel
$ CGO_ENABLE=0 GOOS=darwin GOARCH=amd64 go build -ldflags='-s -w -extldflags "-static -fpic"' -o main-darwin-amd64 main.go && upx -9 ./main-darwin-amd64
# Apple Silicon (M1)
$ CGO_ENABLE=0 GOOS=darwin GOARCH=arm64 go build -ldflags='-s -w -extldflags "-static -fpic"' -o main-darwin-arm64 main.go && upx -9 ./main-darwin-arm64
windows系统下:
SET CGO_ENABLE=0
SET GOOS=linux
SET GOARCH=amd64
go build -ldflags='-s -w -extldflags "-static -fpic"' main.go
参数说明
CGO_ENABLE 是否开启CGO。默认为1开启CGO(不支持交叉编译),0表示关闭CGO
GOARCH 表示CPU架构amd64(x86-64 普通家用电脑)、ARM
GOOS 表示操作系统Linux、Windows、Darwin(Mac)
-o 表示输出指定文件名(默认为main)
-a 表示强制重新构建
-ldflags参数
`-s -w` 表示减小体积,去除编译携带的符号表和调试信息
-s 忽略符号表和调试信息
-w 忽略DWARFv3调试信息,将无法使用gdb进行调试
-extldflags "-static -fpic" 表示完全静态编译(默认是静态链接 个别库可能会导致动态链接),这样编译生成的文件就可以任意放到指定平台下运行,不需要运行环境的基础依赖库()
二进制压缩工具upx(可选)
Mac下使用brew安装
$ brew install upx
$ upx --version
upx 3.96
其他平台:
https://github.com/upx/upx/releases
upx压缩工具能省掉40%-70%的大小
-rwxr-xr-x 1 hhtjim staff 7.9M Feb 9 00:13 main-default
-rwxr-xr-x 1 hhtjim staff 5.5M Feb 9 00:15 main-ldflags
-rwxr-xr-x 1 hhtjim staff 2.3M Feb 9 00:34 main-upx
参考:
https://mp.weixin.qq.com/s/hx9Vbdes6cmRNMiUNqOTCQ
https://mp.weixin.qq.com/s/s6QzFkT1YU3lwkuZSHNb-A
https://dev.to/tidalcloud/how-to-cross-compile-go-app-for-apple-silicon-m1-27l6
https://docs.studygolang.com/pkg/runtime/internal/sys/#pkg-constants
https://zhuanlan.zhihu.com/p/338891206
完全静态编译一个Go程序
https://colobu.com/2018/07/20/totally-static-Go-builds/
Go编译常用参数
https://juejin.cn/post/7096772208999006244
- 1
- 2