chore: improve template gen (#192)

* chore: improve repo template gen
* chore: import cache template gen
* chore: import protoc-gen-go-gin template gen
* feat: add sonic json encoding

* docs: add feature

Co-authored-by: lvjiapeng <319161@myj.com.cn>
This commit is contained in:
vv06161
2025-07-19 09:30:48 +08:00
committed by GitHub
parent 5882db2acc
commit 1e0d1f1c4f
9 changed files with 113 additions and 57 deletions

View File

@@ -1,7 +1,8 @@
module github.com/go-eagle/eagle/cmd/eagle module github.com/go-eagle/eagle/cmd/eagle
go 1.22 go 1.23.0
toolchain go1.24.1
toolchain go1.24.3
require ( require (
github.com/AlecAivazis/survey/v2 v2.2.12 github.com/AlecAivazis/survey/v2 v2.2.12

View File

@@ -48,10 +48,10 @@ type {{.LcName}}Cache struct {
// New{{.Name}}Cache new a cache // New{{.Name}}Cache new a cache
func New{{.Name}}Cache(rdb *redis.Client) {{.Name}}Cache { func New{{.Name}}Cache(rdb *redis.Client) {{.Name}}Cache {
jsonEncoding := encoding.JSONEncoding{} sonicEncoding := encoding.SonicEncoding{}
cachePrefix := "" cachePrefix := ""
return &{{.LcName}}Cache{ return &{{.LcName}}Cache{
cache: cache.NewRedisCache(rdb, cachePrefix, jsonEncoding, func() interface{} { cache: cache.NewRedisCache(rdb, cachePrefix, sonicEncoding, func() interface{} {
return &model.{{.Name}}Model{} return &model.{{.Name}}Model{}
}), }),
} }

View File

@@ -16,6 +16,7 @@ import (
"time" "time"
localCache "github.com/go-eagle/eagle/pkg/cache" localCache "github.com/go-eagle/eagle/pkg/cache"
cacheBase "github.com/go-eagle/eagle/pkg/cache"
"github.com/go-eagle/eagle/pkg/encoding" "github.com/go-eagle/eagle/pkg/encoding"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cast" "github.com/spf13/cast"
@@ -57,7 +58,7 @@ func New{{.Name}}Repo(db *dal.DBClient, cache cache.{{.Name}}Cache) {{.Name}}Rep
db: db, db: db,
tracer: otel.Tracer("{{.LcName}}"), tracer: otel.Tracer("{{.LcName}}"),
cache: cache, cache: cache,
localCache: localCache.NewMemoryCache("local:{{.LcName}}:", encoding.JSONEncoding{}), localCache: localCache.NewMemoryCache("local:{{.LcName}}:", encoding.SonicEncoding{}),
sg: singleflight.Group{}, sg: singleflight.Group{},
} }
} }
@@ -124,48 +125,49 @@ func (r *{{.LcName}}Repo) Get{{.Name}}(ctx context.Context, id int64) (ret *mode
// read redis cache // read redis cache
ret, err = r.cache.Get{{.Name}}Cache(ctx, id) ret, err = r.cache.Get{{.Name}}Cache(ctx, id)
if err != nil { if errors.Is(err, cacheBase.ErrPlaceholder) {
return nil, err return nil, gorm.ErrRecordNotFound
} } else if errors.Is(err, redis.ErrRedisNotFound) {
if ret != nil && ret.ID > 0 { // get data from db
return ret, nil // 避免缓存击穿(瞬间有大量请求过来)
} val, err, _ := r.sg.Do("sg:{{.LcName}}:"+cast.ToString(id), func() (interface{}, error) {
// read db or rpc
// get data from db data, err := dao.{{.Name}}Model.WithContext(ctx).Where(dao.{{.Name}}Model.ID.Eq(id)).Last()
// 避免缓存击穿(瞬间有大量请求过来) if err != nil {
val, err, _ := r.sg.Do("sg:{{.LcName}}:"+cast.ToString(id), func() (interface{}, error) { // cache not found and set empty cache to avoid 缓存穿透
// read db or rpc // Note: 如果缓存空数据太多,会大大降低缓存命中率,可以改为使用布隆过滤器
data, err := dao.{{.Name}}Model.WithContext(ctx).Where(dao.{{.Name}}Model.ID.Eq(id)).Last() if errors.Is(err, gorm.ErrRecordNotFound) {
r.cache.SetCacheWithNotFound(ctx, id)
}
return nil, errors.Wrapf(err, "[repo] get {{.Name}} from db error, id: %d", id)
}
// write cache
if data != nil && data.ID > 0 {
// write redis
err = r.cache.Set{{.Name}}Cache(ctx, id, data, 5*time.Minute)
if err != nil {
return nil, errors.WithMessage(err, "[repo] Get{{.Name}} Set{{.Name}}Cache error")
}
// write local cache
err = r.localCache.Set(ctx, cast.ToString(id), data, 2*time.Minute)
if err != nil {
return nil, errors.WithMessage(err, "[repo] Get{{.Name}} localCache set error")
}
}
return data, nil
})
if err != nil { if err != nil {
// cache not found and set empty cache to avoid 缓存穿透 return nil, err
// Note: 如果缓存空数据太多,会大大降低缓存命中率,可以改为使用布隆过滤器
if errors.Is(err, gorm.ErrRecordNotFound) {
r.cache.SetCacheWithNotFound(ctx, id)
}
return nil, errors.Wrapf(err, "[repo] get {{.Name}} from db error, id: %d", id)
} }
// write cache return val.(*model.{{.Name}}Model), nil
if data != nil && data.ID > 0 { } else if err != nil {
// write redis
err = r.cache.Set{{.Name}}Cache(ctx, id, data, 5*time.Minute)
if err != nil {
return nil, errors.WithMessage(err, "[repo] Get{{.Name}} Set{{.Name}}Cache error")
}
// write local cache
err = r.localCache.Set(ctx, cast.ToString(id), data, 2*time.Minute)
if err != nil {
return nil, errors.WithMessage(err, "[repo] Get{{.Name}} localCache set error")
}
}
return data, nil
})
if err != nil {
return nil, err return nil, err
} }
return ret, nil
return val.(*model.{{.Name}}Model), nil
{{- else }} {{- else }}
// read db // read db
ret, err = dao.{{.Name}}Model.WithContext(ctx).Where(dao.{{.Name}}Model.ID.Eq(id)).Last() ret, err = dao.{{.Name}}Model.WithContext(ctx).Where(dao.{{.Name}}Model.ID.Eq(id)).Last()

View File

@@ -18,6 +18,7 @@ const (
metadataPackage = protogen.GoImportPath("google.golang.org/grpc/metadata") metadataPackage = protogen.GoImportPath("google.golang.org/grpc/metadata")
eagleAppPackage = protogen.GoImportPath("github.com/go-eagle/eagle/pkg/app") eagleAppPackage = protogen.GoImportPath("github.com/go-eagle/eagle/pkg/app")
errCodePackage = protogen.GoImportPath("github.com/go-eagle/eagle/pkg/errcode") errCodePackage = protogen.GoImportPath("github.com/go-eagle/eagle/pkg/errcode")
utilsPackage = protogen.GoImportPath("github.com/go-eagle/eagle/pkg/utils")
deprecationComment = "// Deprecated: Do not use." deprecationComment = "// Deprecated: Do not use."
) )
@@ -40,7 +41,7 @@ func generateFile(gen *protogen.Plugin, file *protogen.File) *protogen.Generated
g.P() g.P()
g.P("// ", contextPackage.Ident("")) g.P("// ", contextPackage.Ident(""))
g.P("// ", metadataPackage.Ident("")) g.P("// ", metadataPackage.Ident(""))
g.P("// ", ginPackage.Ident(""), eagleAppPackage.Ident(""), errCodePackage.Ident("")) g.P("// ", ginPackage.Ident(""), eagleAppPackage.Ident(""), errCodePackage.Ident(""), utilsPackage.Ident(""))
g.P() g.P()
for _, service := range file.Services { for _, service := range file.Services {

View File

@@ -31,14 +31,19 @@ type {{$.Name}} struct{
{{range .Methods}} {{range .Methods}}
func (s *{{$.Name}}) {{ .HandlerName }} (ctx *gin.Context) { func (s *{{$.Name}}) {{ .HandlerName }} (ctx *gin.Context) {
var in {{.Request}} var in {{.Request}}
var err error
{{if eq .Method "GET" }} {{if eq .Method "GET" }}
if err := ctx.ShouldBindQuery(&in); err != nil { if err = ctx.ShouldBindQuery(&in); err != nil {
app.Error(ctx, errcode.ErrInvalidParam.WithDetails(err.Error())) app.Error(ctx, errcode.ErrInvalidParam.WithDetails(err.Error()))
return return
} }
{{if .HasPathParams }} {{if .HasPathParams }}
// make sure the uri include :id // make sure the uri include :id
in.Id = ctx.Param("id") in.Id, err = utils.StringToInt64((ctx.Param("id")))
if err != nil {
app.Error(ctx, errcode.ErrInvalidParam.WithDetails(err.Error()))
return
}
{{end}} {{end}}
{{else if eq .Method "POST" "PUT" "PATCH" "DELETE"}} {{else if eq .Method "POST" "PUT" "PATCH" "DELETE"}}
if err := ctx.ShouldBindJSON(&in); err != nil { if err := ctx.ShouldBindJSON(&in); err != nil {
@@ -47,7 +52,11 @@ func (s *{{$.Name}}) {{ .HandlerName }} (ctx *gin.Context) {
} }
{{if .HasPathParams }} {{if .HasPathParams }}
// make sure the uri include :id // make sure the uri include :id
in.Id = ctx.Param("id") in.Id, err = utils.StringToInt64((ctx.Param("id")))
if err != nil {
app.Error(ctx, errcode.ErrInvalidParam.WithDetails(err.Error()))
return
}
{{end}} {{end}}
{{else}} {{else}}
if err := ctx.ShouldBind(&in); err != nil { if err := ctx.ShouldBind(&in); err != nil {

View File

@@ -78,7 +78,8 @@ func main() {
} }
// init service // init service
service.Svc = service.New(repository.New(model.GetDB())) db, _ := model.GetDB()
service.Svc = service.New(repository.New(db))
gin.SetMode(cfg.Mode) gin.SetMode(cfg.Mode)

View File

@@ -10,9 +10,9 @@ import (
) )
func getCacheClient(ctx context.Context) cache.Cache { func getCacheClient(ctx context.Context) cache.Cache {
jsonEncoding := encoding.JSONEncoding{} sonicEncoding := encoding.SonicEncoding{}
cachePrefix := "" cachePrefix := ""
client := cache.NewRedisCache(redis.RedisClient, cachePrefix, jsonEncoding, func() interface{} { client := cache.NewRedisCache(redis.RedisClient, cachePrefix, sonicEncoding, func() interface{} {
return &model.UserBaseModel{} return &model.UserBaseModel{}
}) })

View File

@@ -0,0 +1,45 @@
package encoding
import (
"github.com/bytedance/sonic"
"github.com/golang/snappy"
)
type SonicEncoding struct {
}
func (s SonicEncoding) Marshal(v interface{}) ([]byte, error) {
buf, err := sonic.Marshal(v)
return buf, err
}
func (s SonicEncoding) Unmarshal(data []byte, value interface{}) error {
err := sonic.Unmarshal(data, value)
if err != nil {
return err
}
return nil
}
// SonicSnappyEncoding Sonic json格式和snappy压缩
type SonicSnappyEncoding struct{}
// Marshal 序列化
func (s SonicSnappyEncoding) Marshal(v interface{}) (data []byte, err error) {
b, err := sonic.Marshal(v)
if err != nil {
return nil, err
}
d := snappy.Encode(nil, b)
return d, nil
}
// Unmarshal 反序列化
func (s SonicSnappyEncoding) Unmarshal(data []byte, value interface{}) error {
b, err := snappy.Decode(nil, data)
if err != nil {
return err
}
return sonic.Unmarshal(b, value)
}

View File

@@ -6,7 +6,7 @@ import (
"strings" "strings"
"unsafe" "unsafe"
jsoniter "github.com/json-iterator/go" "github.com/bytedance/sonic"
) )
// IsEmpty 是否是空字符串 // IsEmpty 是否是空字符串
@@ -97,13 +97,10 @@ func StringToBytes(s string) []byte {
return *(*[]byte)(unsafe.Pointer(&h)) return *(*[]byte)(unsafe.Pointer(&h))
} }
// 对于序列化和反序列化场景较多的服务可以使用性能更高的json-iterator var Json = sonic.Config{
// https://github.com/json-iterator/go EscapeHTML: true,
var Json = jsoniter.Config{ SortMapKeys: true,
EscapeHTML: true, UseNumber: true,
SortMapKeys: true,
ValidateJsonRawMessage: true,
UseNumber: true,
}.Froze() }.Froze()
func Stringify(obj interface{}) string { func Stringify(obj interface{}) string {