Create & Init Project...
This commit is contained in:
45
app/tool/cache/BUILD
vendored
Normal file
45
app/tool/cache/BUILD
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_binary",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"header_template.go",
|
||||
"main.go",
|
||||
"multi_template.go",
|
||||
"none_template.go",
|
||||
"single_template.go",
|
||||
],
|
||||
importpath = "go-common/app/tool/cache",
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
deps = ["//app/tool/cache/common:go_default_library"],
|
||||
)
|
||||
|
||||
go_binary(
|
||||
name = "cache",
|
||||
embed = [":go_default_library"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//app/tool/cache/common:all-srcs",
|
||||
"//app/tool/cache/memcached:all-srcs",
|
||||
"//app/tool/cache/testdata:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
56
app/tool/cache/CHANGELOG.md
vendored
Normal file
56
app/tool/cache/CHANGELOG.md
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
### tools/cache
|
||||
##### Version 1.6.4
|
||||
1. 修复某些参数下多make一次的问题
|
||||
|
||||
##### Version 1.6.3
|
||||
1. 使用fanout替换cache包
|
||||
|
||||
##### Version 1.6.2
|
||||
1. 改为使用errgroup提供的GOMAXPROCS方法 替换channel
|
||||
|
||||
##### Version 1.6.1
|
||||
1. 弃用errgroup改用channel进行批量操作 防止线程饥饿
|
||||
##### Version 1.6.0
|
||||
1. 增加对metadata.WithContext的支持
|
||||
|
||||
##### Version 1.5.3
|
||||
1. 优化gofmt提示
|
||||
|
||||
##### Version 1.5.2
|
||||
1. 补充返回部分数据时的测试
|
||||
2. 增加两种空缓存错误参数的检测
|
||||
3. 支持// cache: 这样语法
|
||||
|
||||
##### Version 1.5.1
|
||||
1. 批量模板中分批回源失败时候 返回部分数据
|
||||
|
||||
##### Version 1.5.0
|
||||
1. 批量模板中改增加对数字类型0值返回的支持
|
||||
|
||||
##### Version 1.4.2
|
||||
1. 修复回源失败 缓存数据未返回的问题
|
||||
|
||||
##### Version 1.4.1
|
||||
1. 修复Hit计算问题
|
||||
2. 由于mc已经有pkg/errors了 因此不再warp
|
||||
3. 修复变量类型省略解析失败的问题
|
||||
|
||||
##### Version 1.4.0
|
||||
1. 增加自定义注释和忽略参数的支持
|
||||
|
||||
##### Version 1.3.0
|
||||
1. 增加batch_err选项 用于在分批发生错误的时候是否降级
|
||||
|
||||
##### Version 1.2.1
|
||||
1. 回源错误的时候返回部分数据
|
||||
|
||||
##### Version 1.2.0
|
||||
1. 解决saga提示无用代码的问题
|
||||
|
||||
##### Version 1.1.0
|
||||
1. 去掉生成代码中的Cp前缀
|
||||
|
||||
##### Version 1.0.0
|
||||
|
||||
1. 添加基础模块与测试:
|
||||
- 代码生成组件
|
8
app/tool/cache/CONTRIBUTORS.md
vendored
Normal file
8
app/tool/cache/CONTRIBUTORS.md
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# Owner
|
||||
zhapuyu
|
||||
|
||||
# Author
|
||||
wangxu01
|
||||
|
||||
# Reviewer
|
||||
zhapuyu
|
12
app/tool/cache/OWNERS
vendored
Normal file
12
app/tool/cache/OWNERS
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
approvers:
|
||||
- wangxu01
|
||||
- zhapuyu
|
||||
labels:
|
||||
- tool
|
||||
options:
|
||||
no_parent_owners: true
|
||||
reviewers:
|
||||
- wangxu01
|
||||
- zhapuyu
|
23
app/tool/cache/README.md
vendored
Normal file
23
app/tool/cache/README.md
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
#### tools/cache
|
||||
|
||||
> 缓存代码生成
|
||||
|
||||
##### 项目简介
|
||||
|
||||
从缓存中获取数据 如果miss则调用回源函数从数据源获取 然后塞入缓存
|
||||
|
||||
支持以下功能:
|
||||
|
||||
- 单飞限制回源并发 防止打爆数据源
|
||||
- 空缓存 防止缓存穿透
|
||||
- 分批获取数据 降低延时
|
||||
- 默认异步加缓存 可选同步加缓存
|
||||
- prometheus回源比监控
|
||||
- 多行注释生成代码
|
||||
- 支持分页(限单key模板)
|
||||
- 自定义注释
|
||||
- 支持忽略参数
|
||||
|
||||
##### 使用方式:
|
||||
|
||||
代码生成: 使用go generate方式生成 具体参数见[文档](http://info.bilibili.co/pages/viewpage.action?pageId=8462061)
|
28
app/tool/cache/common/BUILD
vendored
Normal file
28
app/tool/cache/common/BUILD
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["common.go"],
|
||||
importpath = "go-common/app/tool/cache/common",
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
149
app/tool/cache/common/common.go
vendored
Normal file
149
app/tool/cache/common/common.go
vendored
Normal file
@ -0,0 +1,149 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Source source
|
||||
type Source struct {
|
||||
Fset *token.FileSet
|
||||
Src string
|
||||
F *ast.File
|
||||
}
|
||||
|
||||
// NewSource new source
|
||||
func NewSource(src string) *Source {
|
||||
s := &Source{
|
||||
Fset: token.NewFileSet(),
|
||||
Src: src,
|
||||
}
|
||||
f, err := parser.ParseFile(s.Fset, "", src, 0)
|
||||
if err != nil {
|
||||
log.Fatal("无法解析源文件")
|
||||
}
|
||||
s.F = f
|
||||
return s
|
||||
}
|
||||
|
||||
// ExprString expr string
|
||||
func (s *Source) ExprString(typ ast.Expr) string {
|
||||
fset := s.Fset
|
||||
s1 := fset.Position(typ.Pos()).Offset
|
||||
s2 := fset.Position(typ.End()).Offset
|
||||
return s.Src[s1:s2]
|
||||
}
|
||||
|
||||
// pkgPath package path
|
||||
func (s *Source) pkgPath(name string) (res string) {
|
||||
for _, im := range s.F.Imports {
|
||||
if im.Name != nil && im.Name.Name == name {
|
||||
return im.Path.Value
|
||||
}
|
||||
}
|
||||
for _, im := range s.F.Imports {
|
||||
if strings.HasSuffix(im.Path.Value, name+"\"") {
|
||||
return im.Path.Value
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetDef get define code
|
||||
func (s *Source) GetDef(name string) string {
|
||||
c := s.F.Scope.Lookup(name).Decl.(*ast.TypeSpec).Type.(*ast.InterfaceType)
|
||||
s1 := s.Fset.Position(c.Pos()).Offset
|
||||
s2 := s.Fset.Position(c.End()).Offset
|
||||
line := s.Fset.Position(c.Pos()).Line
|
||||
lines := []string{strings.Split(s.Src, "\n")[line-1]}
|
||||
for _, l := range strings.Split(s.Src[s1:s2], "\n")[1:] {
|
||||
lines = append(lines, "\t"+l)
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
// RegexpReplace replace regexp
|
||||
func RegexpReplace(reg, src, temp string) string {
|
||||
result := []byte{}
|
||||
pattern := regexp.MustCompile(reg)
|
||||
for _, submatches := range pattern.FindAllStringSubmatchIndex(src, -1) {
|
||||
result = pattern.ExpandString(result, temp, src, submatches)
|
||||
}
|
||||
return string(result)
|
||||
}
|
||||
|
||||
// formatPackage format package
|
||||
func formatPackage(name, path string) (res string) {
|
||||
if path != "" {
|
||||
if strings.HasSuffix(path, name+"\"") {
|
||||
res = path
|
||||
return
|
||||
}
|
||||
res = fmt.Sprintf("%s %s", name, path)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SourceText get source file text
|
||||
func SourceText() string {
|
||||
file := os.Getenv("GOFILE")
|
||||
data, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
log.Fatal("can't open file", file)
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
// FormatCode format code
|
||||
func FormatCode(source string) string {
|
||||
src, err := format.Source([]byte(source))
|
||||
if err != nil {
|
||||
// Should never happen, but can arise when developing this code.
|
||||
// The user can compile the output to see the error.
|
||||
log.Printf("warning: 输出文件不合法: %s", err)
|
||||
log.Printf("warning: 详细错误请编译查看")
|
||||
return source
|
||||
}
|
||||
return string(src)
|
||||
}
|
||||
|
||||
// Packages get import packages
|
||||
func (s *Source) Packages(f *ast.Field) (res []string) {
|
||||
fs := f.Type.(*ast.FuncType).Params.List
|
||||
fs = append(fs, f.Type.(*ast.FuncType).Results.List...)
|
||||
var types []string
|
||||
resMap := make(map[string]bool)
|
||||
for _, field := range fs {
|
||||
if p, ok := field.Type.(*ast.MapType); ok {
|
||||
types = append(types, s.ExprString(p.Key))
|
||||
types = append(types, s.ExprString(p.Value))
|
||||
} else if p, ok := field.Type.(*ast.ArrayType); ok {
|
||||
types = append(types, s.ExprString(p.Elt))
|
||||
} else {
|
||||
types = append(types, s.ExprString(field.Type))
|
||||
}
|
||||
}
|
||||
|
||||
for _, t := range types {
|
||||
name := RegexpReplace(`(?P<pkg>\w+)\.\w+`, t, "$pkg")
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
pkg := formatPackage(name, s.pkgPath(name))
|
||||
if !resMap[pkg] {
|
||||
resMap[pkg] = true
|
||||
}
|
||||
}
|
||||
for pkg := range resMap {
|
||||
res = append(res, pkg)
|
||||
}
|
||||
return
|
||||
}
|
3
app/tool/cache/gen
vendored
Normal file
3
app/tool/cache/gen
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
#! /bin/sh
|
||||
DIR=$(dirname "$0")
|
||||
go run $DIR/main.go $DIR/single_template.go $DIR/multi_template.go $DIR/none_template.go $DIR/header_template.go $@
|
31
app/tool/cache/header_template.go
vendored
Normal file
31
app/tool/cache/header_template.go
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
package main
|
||||
|
||||
var _headerTemplate = `
|
||||
// Code generated by $GOPATH/src/go-common/app/tool/cache/gen. DO NOT EDIT.
|
||||
|
||||
NEWLINE
|
||||
/*
|
||||
Package {{.PkgName}} is a generated cache proxy package.
|
||||
It is generated from:
|
||||
ARGS
|
||||
*/
|
||||
NEWLINE
|
||||
|
||||
package {{.PkgName}}
|
||||
|
||||
import (
|
||||
"context"
|
||||
{{if .EnableBatch }}"sync"{{end}}
|
||||
NEWLINE
|
||||
"go-common/library/stat/prom"
|
||||
{{if .EnableBatch }}"go-common/library/sync/errgroup"{{end}}
|
||||
{{.ImportPackage}}
|
||||
NEWLINE
|
||||
{{if .EnableSingleFlight}} "golang.org/x/sync/singleflight" {{end}}
|
||||
)
|
||||
|
||||
var _ _cache
|
||||
{{if .EnableSingleFlight}}
|
||||
var cacheSingleFlights = [SFCOUNT]*singleflight.Group{SFINIT}
|
||||
{{end }}
|
||||
`
|
451
app/tool/cache/main.go
vendored
Normal file
451
app/tool/cache/main.go
vendored
Normal file
@ -0,0 +1,451 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"go-common/app/tool/cache/common"
|
||||
)
|
||||
|
||||
var (
|
||||
// arguments
|
||||
singleFlight = flag.Bool("singleflight", false, "enable singleflight")
|
||||
nullCache = flag.String("nullcache", "", "null cache")
|
||||
checkNullCode = flag.String("check_null_code", "", "check null code")
|
||||
batchSize = flag.Int("batch", 0, "batch size")
|
||||
batchErr = flag.String("batch_err", "break", "batch err to contine or break")
|
||||
maxGroup = flag.Int("max_group", 0, "max group size")
|
||||
sync = flag.Bool("sync", false, "add cache in sync way.")
|
||||
paging = flag.Bool("paging", false, "use paging in single template")
|
||||
ignores = flag.String("ignores", "", "ignore params")
|
||||
|
||||
numberTypes = []string{"int", "int8", "int16", "int32", "int64", "float32", "float64", "uint", "uint8", "uint16", "uint32", "uint64"}
|
||||
simpleTypes = []string{"int", "int8", "int16", "int32", "int64", "float32", "float64", "uint", "uint8", "uint16", "uint32", "uint64", "bool", "string", "[]byte"}
|
||||
optionNames = []string{"singleflight", "nullcache", "check_null_code", "batch", "max_group", "sync", "paging", "ignores", "batch_err"}
|
||||
optionNamesMap = map[string]bool{}
|
||||
)
|
||||
|
||||
const (
|
||||
_interfaceName = "_cache"
|
||||
_multiTpl = 1
|
||||
_singleTpl = 2
|
||||
_noneTpl = 3
|
||||
)
|
||||
|
||||
func resetFlag() {
|
||||
*singleFlight = false
|
||||
*nullCache = ""
|
||||
*checkNullCode = ""
|
||||
*batchSize = 0
|
||||
*maxGroup = 0
|
||||
*sync = false
|
||||
*paging = false
|
||||
*batchErr = "break"
|
||||
*ignores = ""
|
||||
}
|
||||
|
||||
// options options
|
||||
type options struct {
|
||||
name string
|
||||
keyType string
|
||||
valueType string
|
||||
cacheFunc string
|
||||
rawFunc string
|
||||
addCacheFunc string
|
||||
template int
|
||||
SimpleValue bool
|
||||
NumberValue bool
|
||||
GoValue bool
|
||||
ZeroValue string
|
||||
ImportPackage string
|
||||
importPackages []string
|
||||
Args string
|
||||
PkgName string
|
||||
EnableSingleFlight bool
|
||||
NullCache string
|
||||
EnableNullCache bool
|
||||
GroupSize int
|
||||
MaxGroup int
|
||||
EnableBatch bool
|
||||
BatchErrBreak bool
|
||||
Sync bool
|
||||
CheckNullCode string
|
||||
ExtraArgsType string
|
||||
ExtraArgs string
|
||||
ExtraCacheArgs string
|
||||
ExtraRawArgs string
|
||||
ExtraAddCacheArgs string
|
||||
EnablePaging bool
|
||||
Comment string
|
||||
}
|
||||
|
||||
// parse parse options
|
||||
func parse(s *common.Source) (opts []*options) {
|
||||
f := s.F
|
||||
fset := s.Fset
|
||||
src := s.Src
|
||||
c := f.Scope.Lookup(_interfaceName)
|
||||
if (c == nil) || (c.Kind != ast.Typ) {
|
||||
log.Fatalln("无法找到缓存声明")
|
||||
}
|
||||
lines := strings.Split(src, "\n")
|
||||
lists := c.Decl.(*ast.TypeSpec).Type.(*ast.InterfaceType).Methods.List
|
||||
for _, list := range lists {
|
||||
opt := options{Args: s.GetDef(_interfaceName), importPackages: s.Packages(list)}
|
||||
// get comment
|
||||
line := fset.Position(list.Pos()).Line - 3
|
||||
if len(lines)-1 >= line {
|
||||
comment := lines[line]
|
||||
opt.Comment = common.RegexpReplace(`\s+//(?P<name>.+)`, comment, "$name")
|
||||
opt.Comment = strings.TrimSpace(opt.Comment)
|
||||
}
|
||||
// get options
|
||||
line = fset.Position(list.Pos()).Line - 2
|
||||
comment := lines[line]
|
||||
os.Args = []string{os.Args[0]}
|
||||
if regexp.MustCompile(`\s+//\s*cache:.+`).Match([]byte(comment)) {
|
||||
args := strings.Split(common.RegexpReplace(`//\s*cache:(?P<arg>.+)`, comment, "$arg"), " ")
|
||||
for _, arg := range args {
|
||||
arg = strings.TrimSpace(arg)
|
||||
if arg != "" {
|
||||
// validate option name
|
||||
argName := common.RegexpReplace(`-(?P<name>[\w_-]+)=.+`, arg, "$name")
|
||||
if !optionNamesMap[argName] {
|
||||
log.Fatalf("选项:%s 不存在 请检查拼写\n", argName)
|
||||
}
|
||||
os.Args = append(os.Args, arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
resetFlag()
|
||||
flag.Parse()
|
||||
opt.EnableSingleFlight = *singleFlight
|
||||
opt.NullCache = *nullCache
|
||||
opt.EnablePaging = *paging
|
||||
opt.EnableNullCache = *nullCache != ""
|
||||
opt.EnableBatch = (*batchSize != 0) && (*maxGroup != 0)
|
||||
opt.BatchErrBreak = *batchErr == "break"
|
||||
opt.Sync = *sync
|
||||
opt.CheckNullCode = *checkNullCode
|
||||
opt.GroupSize = *batchSize
|
||||
opt.MaxGroup = *maxGroup
|
||||
// get func
|
||||
opt.name = list.Names[0].Name
|
||||
params := list.Type.(*ast.FuncType).Params.List
|
||||
if len(params) == 0 {
|
||||
log.Fatalln(opt.name + "参数不足")
|
||||
}
|
||||
if s.ExprString(params[0].Type) != "context.Context" {
|
||||
log.Fatalln("第一个参数必须为context")
|
||||
}
|
||||
if len(params) == 1 {
|
||||
opt.template = _noneTpl
|
||||
} else {
|
||||
if _, ok := params[1].Type.(*ast.ArrayType); ok {
|
||||
opt.template = _multiTpl
|
||||
} else {
|
||||
opt.template = _singleTpl
|
||||
// get key
|
||||
opt.keyType = s.ExprString(params[1].Type)
|
||||
}
|
||||
}
|
||||
if len(params) > 2 {
|
||||
var args []string
|
||||
var allArgs []string
|
||||
for _, pa := range params[2:] {
|
||||
paType := s.ExprString(pa.Type)
|
||||
if len(pa.Names) == 0 {
|
||||
args = append(args, paType)
|
||||
allArgs = append(allArgs, paType)
|
||||
continue
|
||||
}
|
||||
var names []string
|
||||
for _, name := range pa.Names {
|
||||
names = append(names, name.Name)
|
||||
}
|
||||
allArgs = append(allArgs, strings.Join(names, ",")+" "+paType)
|
||||
args = append(args, names...)
|
||||
}
|
||||
opt.ExtraArgs = strings.Join(args, ",")
|
||||
opt.ExtraArgsType = strings.Join(allArgs, ",")
|
||||
argsMap := make(map[string]bool)
|
||||
for _, arg := range args {
|
||||
argsMap[arg] = true
|
||||
}
|
||||
ignoreCache := make(map[string]bool)
|
||||
ignoreRaw := make(map[string]bool)
|
||||
ignoreAddCache := make(map[string]bool)
|
||||
ignoreArray := [3]map[string]bool{ignoreCache, ignoreRaw, ignoreAddCache}
|
||||
if *ignores != "" {
|
||||
is := strings.Split(*ignores, "|")
|
||||
if len(is) > 3 {
|
||||
log.Fatalln("ignores参数错误")
|
||||
}
|
||||
for i := range is {
|
||||
if len(is) > i {
|
||||
for _, s := range strings.Split(is[i], ",") {
|
||||
ignoreArray[i][s] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var as []string
|
||||
for _, arg := range args {
|
||||
if !ignoreCache[arg] {
|
||||
as = append(as, arg)
|
||||
}
|
||||
}
|
||||
opt.ExtraCacheArgs = strings.Join(as, ",")
|
||||
as = []string{}
|
||||
for _, arg := range args {
|
||||
if !ignoreRaw[arg] {
|
||||
as = append(as, arg)
|
||||
}
|
||||
}
|
||||
opt.ExtraRawArgs = strings.Join(as, ",")
|
||||
as = []string{}
|
||||
for _, arg := range args {
|
||||
if !ignoreAddCache[arg] {
|
||||
as = append(as, arg)
|
||||
}
|
||||
}
|
||||
opt.ExtraAddCacheArgs = strings.Join(as, ",")
|
||||
if opt.ExtraAddCacheArgs != "" {
|
||||
opt.ExtraAddCacheArgs = "," + opt.ExtraAddCacheArgs
|
||||
}
|
||||
if opt.ExtraRawArgs != "" {
|
||||
opt.ExtraRawArgs = "," + opt.ExtraRawArgs
|
||||
}
|
||||
if opt.ExtraCacheArgs != "" {
|
||||
opt.ExtraCacheArgs = "," + opt.ExtraCacheArgs
|
||||
}
|
||||
if opt.ExtraArgs != "" {
|
||||
opt.ExtraArgs = "," + opt.ExtraArgs
|
||||
}
|
||||
if opt.ExtraArgsType != "" {
|
||||
opt.ExtraArgsType = "," + opt.ExtraArgsType
|
||||
}
|
||||
}
|
||||
// get k v from results
|
||||
results := list.Type.(*ast.FuncType).Results.List
|
||||
if len(results) != 2 {
|
||||
log.Fatalln(opt.name + ": 参数个数不对")
|
||||
}
|
||||
if s.ExprString(results[1].Type) != "error" {
|
||||
log.Fatalln(opt.name + ": 最后返回值参数需为error")
|
||||
}
|
||||
if opt.template == _multiTpl {
|
||||
p, ok := results[0].Type.(*ast.MapType)
|
||||
if !ok {
|
||||
log.Fatalln(opt.name + ": 批量获取方法 返回值类型需为map类型")
|
||||
}
|
||||
opt.keyType = s.ExprString(p.Key)
|
||||
opt.valueType = s.ExprString(p.Value)
|
||||
} else {
|
||||
opt.valueType = s.ExprString(results[0].Type)
|
||||
}
|
||||
for _, t := range numberTypes {
|
||||
if t == opt.valueType {
|
||||
opt.NumberValue = true
|
||||
break
|
||||
}
|
||||
}
|
||||
opt.ZeroValue = "nil"
|
||||
for _, t := range simpleTypes {
|
||||
if t == opt.valueType {
|
||||
opt.SimpleValue = true
|
||||
opt.ZeroValue = zeroValue(t)
|
||||
break
|
||||
}
|
||||
}
|
||||
if !opt.SimpleValue {
|
||||
for _, t := range []string{"[]", "map"} {
|
||||
if strings.HasPrefix(opt.valueType, t) {
|
||||
opt.GoValue = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
upperName := strings.ToUpper(opt.name[0:1]) + opt.name[1:]
|
||||
opt.cacheFunc = fmt.Sprintf("d.Cache%s", upperName)
|
||||
opt.rawFunc = fmt.Sprintf("d.Raw%s", upperName)
|
||||
opt.addCacheFunc = fmt.Sprintf("d.AddCache%s", upperName)
|
||||
opt.Check()
|
||||
opts = append(opts, &opt)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (option *options) Check() {
|
||||
if !option.SimpleValue && !strings.Contains(option.valueType, "*") && !strings.Contains(option.valueType, "[]") && !strings.Contains(option.valueType, "map") {
|
||||
log.Fatalf("%s: 值类型只能为基本类型/slice/map/指针类型\n", option.name)
|
||||
}
|
||||
if option.EnableSingleFlight && option.EnableBatch {
|
||||
log.Fatalf("%s: 单飞和批量获取不能同时开启\n", option.name)
|
||||
}
|
||||
if option.template != _singleTpl && option.EnablePaging {
|
||||
log.Fatalf("%s: 分页只能用在单key模板中\n", option.name)
|
||||
}
|
||||
if option.SimpleValue && !option.EnableNullCache {
|
||||
if !((option.template == _multiTpl) && option.NumberValue) {
|
||||
log.Fatalf("%s: 值为基本类型时需开启空缓存 防止缓存零值穿透\n", option.name)
|
||||
}
|
||||
}
|
||||
if option.EnableNullCache {
|
||||
if !option.SimpleValue && option.CheckNullCode == "" {
|
||||
log.Fatalf("%s: 缺少-check_null_code参数\n", option.name)
|
||||
}
|
||||
if option.SimpleValue && option.NullCache == option.ZeroValue {
|
||||
log.Fatalf("%s: %s 不能作为空缓存值 \n", option.name, option.NullCache)
|
||||
}
|
||||
if strings.Contains(option.NullCache, "{}") {
|
||||
// -nullcache=[]*model.OrderMain{} 这种无效
|
||||
log.Fatalf("%s: %s 不能作为空缓存值 会导致空缓存无效 \n", option.name, option.NullCache)
|
||||
}
|
||||
if strings.Contains(option.CheckNullCode, "len") && strings.Contains(strings.Replace(option.CheckNullCode, " ", "", -1), "==0") {
|
||||
// -check_null_code=len($)==0 这种无效
|
||||
log.Fatalf("%s: -check_null_code=%s 错误 会有无意义的赋值\n", option.name, option.CheckNullCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func genHeader(opts []*options) (src string) {
|
||||
option := options{PkgName: os.Getenv("GOPACKAGE")}
|
||||
var sfCount int
|
||||
var packages, sfInit []string
|
||||
packagesMap := map[string]bool{`"context"`: true}
|
||||
for _, opt := range opts {
|
||||
if opt.EnableSingleFlight {
|
||||
option.EnableSingleFlight = true
|
||||
sfCount++
|
||||
}
|
||||
if opt.EnableBatch {
|
||||
option.EnableBatch = true
|
||||
}
|
||||
if len(opt.importPackages) > 0 {
|
||||
for _, pkg := range opt.importPackages {
|
||||
if !packagesMap[pkg] {
|
||||
packages = append(packages, pkg)
|
||||
packagesMap[pkg] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if opt.Args != "" {
|
||||
option.Args = opt.Args
|
||||
}
|
||||
}
|
||||
option.ImportPackage = strings.Join(packages, "\n")
|
||||
for i := 0; i < sfCount; i++ {
|
||||
sfInit = append(sfInit, "{}")
|
||||
}
|
||||
src = _headerTemplate
|
||||
src = strings.Replace(src, "SFCOUNT", strconv.Itoa(sfCount), -1)
|
||||
t := template.Must(template.New("header").Parse(src))
|
||||
var buffer bytes.Buffer
|
||||
err := t.Execute(&buffer, option)
|
||||
if err != nil {
|
||||
log.Fatalf("execute template: %s", err)
|
||||
}
|
||||
// Format the output.
|
||||
src = strings.Replace(buffer.String(), "\t", "", -1)
|
||||
src = regexp.MustCompile("\n+").ReplaceAllString(src, "\n")
|
||||
src = strings.Replace(src, "NEWLINE", "", -1)
|
||||
src = strings.Replace(src, "ARGS", option.Args, -1)
|
||||
src = strings.Replace(src, "SFINIT", strings.Join(sfInit, ","), -1)
|
||||
return
|
||||
}
|
||||
|
||||
func genBody(opts []*options) (res string) {
|
||||
sfnum := -1
|
||||
for _, option := range opts {
|
||||
var nullCodeVar, src string
|
||||
if option.template == _multiTpl {
|
||||
src = _multiTemplate
|
||||
nullCodeVar = "v"
|
||||
} else if option.template == _singleTpl {
|
||||
src = _singleTemplate
|
||||
nullCodeVar = "res"
|
||||
} else {
|
||||
src = _noneTemplate
|
||||
nullCodeVar = "res"
|
||||
}
|
||||
if option.template != _noneTpl {
|
||||
src = strings.Replace(src, "KEY", option.keyType, -1)
|
||||
}
|
||||
if option.CheckNullCode != "" {
|
||||
option.CheckNullCode = strings.Replace(option.CheckNullCode, "$", nullCodeVar, -1)
|
||||
}
|
||||
if option.EnableSingleFlight {
|
||||
sfnum++
|
||||
}
|
||||
src = strings.Replace(src, "NAME", option.name, -1)
|
||||
src = strings.Replace(src, "VALUE", option.valueType, -1)
|
||||
src = strings.Replace(src, "ADDCACHEFUNC", option.addCacheFunc, -1)
|
||||
src = strings.Replace(src, "CACHEFUNC", option.cacheFunc, -1)
|
||||
src = strings.Replace(src, "RAWFUNC", option.rawFunc, -1)
|
||||
src = strings.Replace(src, "GROUPSIZE", strconv.Itoa(option.GroupSize), -1)
|
||||
src = strings.Replace(src, "MAXGROUP", strconv.Itoa(option.MaxGroup), -1)
|
||||
src = strings.Replace(src, "SFNUM", strconv.Itoa(sfnum), -1)
|
||||
t := template.Must(template.New("cache").Parse(src))
|
||||
var buffer bytes.Buffer
|
||||
err := t.Execute(&buffer, option)
|
||||
if err != nil {
|
||||
log.Fatalf("execute template: %s", err)
|
||||
}
|
||||
// Format the output.
|
||||
src = strings.Replace(buffer.String(), "\t", "", -1)
|
||||
src = regexp.MustCompile("\n+").ReplaceAllString(src, "\n")
|
||||
res = res + "\n" + src
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func zeroValue(t string) string {
|
||||
switch t {
|
||||
case "bool":
|
||||
return "false"
|
||||
case "string":
|
||||
return "\"\""
|
||||
case "[]byte":
|
||||
return "nil"
|
||||
default:
|
||||
return "0"
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
for _, name := range optionNames {
|
||||
optionNamesMap[name] = true
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Fatalf("程序解析失败, err: %+v 请企业微信联系 @wangxu01", err)
|
||||
}
|
||||
}()
|
||||
options := parse(common.NewSource(common.SourceText()))
|
||||
header := genHeader(options)
|
||||
body := genBody(options)
|
||||
code := common.FormatCode(header + "\n" + body)
|
||||
// Write to file.
|
||||
dir := filepath.Dir(".")
|
||||
outputName := filepath.Join(dir, "dao.cache.go")
|
||||
err := ioutil.WriteFile(outputName, []byte(code), 0644)
|
||||
if err != nil {
|
||||
log.Fatalf("写入文件失败: %s", err)
|
||||
}
|
||||
log.Println("dao.cache.go: 生成成功")
|
||||
}
|
4
app/tool/cache/mc
vendored
Normal file
4
app/tool/cache/mc
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
#! /bin/sh
|
||||
DIR=$(dirname "$0")
|
||||
DIR="$DIR/memcached"
|
||||
go run $DIR/main.go $DIR/header_template.go $DIR/none_template.go $DIR/single_template.go $DIR/multi_template.go $@
|
45
app/tool/cache/memcached/BUILD
vendored
Normal file
45
app/tool/cache/memcached/BUILD
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_binary(
|
||||
name = "memcached",
|
||||
embed = [":go_default_library"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"header_template.go",
|
||||
"main.go",
|
||||
"multi_template.go",
|
||||
"none_template.go",
|
||||
"single_template.go",
|
||||
],
|
||||
importpath = "go-common/app/tool/cache/memcached",
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = ["//app/tool/cache/common:go_default_library"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//app/tool/cache/memcached/testdata:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
11
app/tool/cache/memcached/CHANGELOG.md
vendored
Normal file
11
app/tool/cache/memcached/CHANGELOG.md
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
### tools/cache/memcached
|
||||
|
||||
##### Version 1.1.1
|
||||
1. 修复返回值为string []byte无法正常生成的问题
|
||||
|
||||
##### Version 1.1.0
|
||||
1. 支持分批获取
|
||||
|
||||
##### Version 1.0.0
|
||||
|
||||
1. 添加基础模块与测试
|
24
app/tool/cache/memcached/README.md
vendored
Normal file
24
app/tool/cache/memcached/README.md
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
|
||||
#### tools/cache/mc
|
||||
|
||||
> mc缓存代码生成
|
||||
|
||||
##### 项目简介
|
||||
|
||||
自动生成memcached缓存代码 和缓存回源工具配合使用 体验更佳
|
||||
|
||||
支持以下功能:
|
||||
|
||||
- 常用mc命令(get/set/add/replace/delete)
|
||||
- 多种数据存储格式(json/pb/raw/gob/gzip)
|
||||
- 常用值类型自动转换(int/bool/float...)
|
||||
- 自定义缓存名称和过期时间
|
||||
- 记录pkg/error错误栈
|
||||
- 记录日志trace id
|
||||
- prometheus错误监控
|
||||
- 自定义参数个数
|
||||
- 自定义注释
|
||||
|
||||
##### 使用方式:
|
||||
|
||||
代码生成: 使用go generate方式生成 具体参数见[文档](http://info.bilibili.co/pages/viewpage.action?pageId=8471941)
|
30
app/tool/cache/memcached/header_template.go
vendored
Normal file
30
app/tool/cache/memcached/header_template.go
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
package main
|
||||
|
||||
var _headerTemplate = `
|
||||
// Code generated by $GOPATH/src/go-common/app/tool/cache/mc. DO NOT EDIT.
|
||||
|
||||
NEWLINE
|
||||
/*
|
||||
Package {{.PkgName}} is a generated mc cache package.
|
||||
It is generated from:
|
||||
ARGS
|
||||
*/
|
||||
NEWLINE
|
||||
|
||||
package {{.PkgName}}
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
{{if .UseStrConv}}"strconv"{{end}}
|
||||
{{if .EnableBatch }}"sync"{{end}}
|
||||
NEWLINE
|
||||
"go-common/library/stat/prom"
|
||||
{{if .UseMemcached }}"go-common/library/cache/memcache"{{end}}
|
||||
{{if .EnableBatch }}"go-common/library/sync/errgroup"{{end}}
|
||||
"go-common/library/log"
|
||||
{{.ImportPackage}}
|
||||
)
|
||||
|
||||
var _ _mc
|
||||
`
|
517
app/tool/cache/memcached/main.go
vendored
Normal file
517
app/tool/cache/memcached/main.go
vendored
Normal file
@ -0,0 +1,517 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"go/ast"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"go-common/app/tool/cache/common"
|
||||
)
|
||||
|
||||
var (
|
||||
encode = flag.String("encode", "", "encode type: json/pb/raw/gob/gzip")
|
||||
mcType = flag.String("type", "", "type: get/set/del/replace/only_add")
|
||||
key = flag.String("key", "", "key name method")
|
||||
expire = flag.String("expire", "", "expire time code")
|
||||
batchSize = flag.Int("batch", 0, "batch size")
|
||||
batchErr = flag.String("batch_err", "break", "batch err to contine or break")
|
||||
maxGroup = flag.Int("max_group", 0, "max group size")
|
||||
|
||||
mcValidTypes = []string{"set", "replace", "del", "get", "only_add"}
|
||||
mcValidPrefix = []string{"set", "replace", "del", "get", "cache", "add"}
|
||||
optionNamesMap = map[string]bool{"batch": true, "max_group": true, "encode": true, "type": true, "key": true, "expire": true, "batch_err": true}
|
||||
simpleTypes = []string{"int", "int8", "int16", "int32", "int64", "float32", "float64", "uint", "uint8", "uint16", "uint32", "uint64", "bool", "string", "[]byte"}
|
||||
lenTypes = []string{"[]", "map"}
|
||||
)
|
||||
|
||||
const (
|
||||
_interfaceName = "_mc"
|
||||
_multiTpl = 1
|
||||
_singleTpl = 2
|
||||
_noneTpl = 3
|
||||
_typeGet = "get"
|
||||
_typeSet = "set"
|
||||
_typeDel = "del"
|
||||
_typeReplace = "replace"
|
||||
_typeAdd = "only_add"
|
||||
)
|
||||
|
||||
func resetFlag() {
|
||||
*encode = ""
|
||||
*mcType = ""
|
||||
*batchSize = 0
|
||||
*maxGroup = 0
|
||||
*batchErr = "break"
|
||||
}
|
||||
|
||||
// options options
|
||||
type options struct {
|
||||
name string
|
||||
keyType string
|
||||
ValueType string
|
||||
template int
|
||||
SimpleValue bool
|
||||
// int float 类型
|
||||
GetSimpleValue bool
|
||||
// string, []byte类型
|
||||
GetDirectValue bool
|
||||
ConvertValue2Bytes string
|
||||
ConvertBytes2Value string
|
||||
GoValue bool
|
||||
ImportPackage string
|
||||
importPackages []string
|
||||
Args string
|
||||
PkgName string
|
||||
ExtraArgsType string
|
||||
ExtraArgs string
|
||||
MCType string
|
||||
KeyMethod string
|
||||
ExpireCode string
|
||||
Encode string
|
||||
UseMemcached bool
|
||||
InitValue bool
|
||||
OriginValueType string
|
||||
UseStrConv bool
|
||||
Comment string
|
||||
GroupSize int
|
||||
MaxGroup int
|
||||
EnableBatch bool
|
||||
BatchErrBreak bool
|
||||
LenType bool
|
||||
PointType bool
|
||||
}
|
||||
|
||||
func parse(s *common.Source) (opts []*options) {
|
||||
f := s.F
|
||||
fset := s.Fset
|
||||
src := s.Src
|
||||
c := f.Scope.Lookup(_interfaceName)
|
||||
if (c == nil) || (c.Kind != ast.Typ) {
|
||||
log.Fatalln("无法找到缓存声明")
|
||||
}
|
||||
lines := strings.Split(src, "\n")
|
||||
lists := c.Decl.(*ast.TypeSpec).Type.(*ast.InterfaceType).Methods.List
|
||||
for _, list := range lists {
|
||||
opt := options{Args: s.GetDef(_interfaceName), UseMemcached: true, importPackages: s.Packages(list)}
|
||||
opt.name = list.Names[0].Name
|
||||
opt.KeyMethod = "key" + opt.name
|
||||
opt.ExpireCode = "d.mc" + opt.name + "Expire"
|
||||
// get comment
|
||||
line := fset.Position(list.Pos()).Line - 3
|
||||
if len(lines)-1 >= line {
|
||||
comment := lines[line]
|
||||
opt.Comment = common.RegexpReplace(`\s+//(?P<name>.+)`, comment, "$name")
|
||||
opt.Comment = strings.TrimSpace(opt.Comment)
|
||||
}
|
||||
// get options
|
||||
line = fset.Position(list.Pos()).Line - 2
|
||||
comment := lines[line]
|
||||
os.Args = []string{os.Args[0]}
|
||||
if regexp.MustCompile(`\s+//\s*mc:.+`).Match([]byte(comment)) {
|
||||
args := strings.Split(common.RegexpReplace(`//\s*mc:(?P<arg>.+)`, comment, "$arg"), " ")
|
||||
for _, arg := range args {
|
||||
arg = strings.TrimSpace(arg)
|
||||
if arg != "" {
|
||||
// validate option name
|
||||
argName := common.RegexpReplace(`-(?P<name>[\w_-]+)=.+`, arg, "$name")
|
||||
if !optionNamesMap[argName] {
|
||||
log.Fatalf("选项:%s 不存在 请检查拼写\n", argName)
|
||||
}
|
||||
os.Args = append(os.Args, arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
resetFlag()
|
||||
flag.Parse()
|
||||
if *mcType != "" {
|
||||
opt.MCType = *mcType
|
||||
}
|
||||
if *key != "" {
|
||||
opt.KeyMethod = *key
|
||||
}
|
||||
if *expire != "" {
|
||||
opt.ExpireCode = *expire
|
||||
}
|
||||
opt.EnableBatch = (*batchSize != 0) && (*maxGroup != 0)
|
||||
opt.BatchErrBreak = *batchErr == "break"
|
||||
opt.GroupSize = *batchSize
|
||||
opt.MaxGroup = *maxGroup
|
||||
// get type from prefix
|
||||
if opt.MCType == "" {
|
||||
for _, t := range mcValidPrefix {
|
||||
if strings.HasPrefix(strings.ToLower(opt.name), t) {
|
||||
if t == "add" {
|
||||
t = _typeSet
|
||||
}
|
||||
opt.MCType = t
|
||||
break
|
||||
}
|
||||
}
|
||||
if opt.MCType == "" {
|
||||
log.Fatalln(opt.name + "请指定方法类型(type=get/set/del...)")
|
||||
}
|
||||
}
|
||||
if opt.MCType == "cache" {
|
||||
opt.MCType = _typeGet
|
||||
}
|
||||
params := list.Type.(*ast.FuncType).Params.List
|
||||
if len(params) == 0 {
|
||||
log.Fatalln(opt.name + "参数不足")
|
||||
}
|
||||
if s.ExprString(params[0].Type) != "context.Context" {
|
||||
log.Fatalln(opt.name + "第一个参数必须为context")
|
||||
}
|
||||
for _, param := range params {
|
||||
if len(param.Names) > 1 {
|
||||
log.Fatalln(opt.name + "不支持省略类型")
|
||||
}
|
||||
}
|
||||
// get template
|
||||
if len(params) == 1 {
|
||||
opt.template = _noneTpl
|
||||
} else if (len(params) == 2) && (opt.MCType == _typeSet || opt.MCType == _typeAdd || opt.MCType == _typeReplace) {
|
||||
if _, ok := params[1].Type.(*ast.MapType); ok {
|
||||
opt.template = _multiTpl
|
||||
} else {
|
||||
opt.template = _noneTpl
|
||||
}
|
||||
} else {
|
||||
if _, ok := params[1].Type.(*ast.ArrayType); ok {
|
||||
opt.template = _multiTpl
|
||||
} else {
|
||||
opt.template = _singleTpl
|
||||
}
|
||||
}
|
||||
// extra args
|
||||
if len(params) > 2 {
|
||||
args := []string{""}
|
||||
allArgs := []string{""}
|
||||
var pos = 2
|
||||
if (opt.MCType == _typeAdd) || (opt.MCType == _typeSet) || (opt.MCType == _typeReplace) {
|
||||
pos = 3
|
||||
}
|
||||
for _, pa := range params[pos:] {
|
||||
paType := s.ExprString(pa.Type)
|
||||
if len(pa.Names) == 0 {
|
||||
args = append(args, paType)
|
||||
allArgs = append(allArgs, paType)
|
||||
continue
|
||||
}
|
||||
var names []string
|
||||
for _, name := range pa.Names {
|
||||
names = append(names, name.Name)
|
||||
}
|
||||
allArgs = append(allArgs, strings.Join(names, ",")+" "+paType)
|
||||
args = append(args, strings.Join(names, ","))
|
||||
}
|
||||
if len(args) > 1 {
|
||||
opt.ExtraArgs = strings.Join(args, ",")
|
||||
opt.ExtraArgsType = strings.Join(allArgs, ",")
|
||||
}
|
||||
}
|
||||
// get k v from results
|
||||
results := list.Type.(*ast.FuncType).Results.List
|
||||
if s.ExprString(results[len(results)-1].Type) != "error" {
|
||||
log.Fatalln("最后返回值参数需为error")
|
||||
}
|
||||
for _, res := range results {
|
||||
if len(res.Names) > 1 {
|
||||
log.Fatalln(opt.name + "返回值不支持省略类型")
|
||||
}
|
||||
}
|
||||
if opt.MCType == _typeGet {
|
||||
if len(results) != 2 {
|
||||
log.Fatalln("参数个数不对")
|
||||
}
|
||||
}
|
||||
// get key type and value type
|
||||
if (opt.MCType == _typeAdd) || (opt.MCType == _typeSet) || (opt.MCType == _typeReplace) {
|
||||
if opt.template == _multiTpl {
|
||||
p, ok := params[1].Type.(*ast.MapType)
|
||||
if !ok {
|
||||
log.Fatalf("%s: 参数类型错误 批量设置数据时类型需为map类型\n", opt.name)
|
||||
}
|
||||
opt.keyType = s.ExprString(p.Key)
|
||||
opt.ValueType = s.ExprString(p.Value)
|
||||
} else if opt.template == _singleTpl {
|
||||
opt.keyType = s.ExprString(params[1].Type)
|
||||
opt.ValueType = s.ExprString(params[2].Type)
|
||||
} else {
|
||||
opt.ValueType = s.ExprString(params[1].Type)
|
||||
}
|
||||
}
|
||||
if opt.MCType == _typeGet {
|
||||
if opt.template == _multiTpl {
|
||||
if p, ok := results[0].Type.(*ast.MapType); ok {
|
||||
opt.keyType = s.ExprString(p.Key)
|
||||
opt.ValueType = s.ExprString(p.Value)
|
||||
} else {
|
||||
log.Fatalf("%s: 返回值类型错误 批量获取数据时返回值需为map类型\n", opt.name)
|
||||
}
|
||||
} else if opt.template == _singleTpl {
|
||||
opt.keyType = s.ExprString(params[1].Type)
|
||||
opt.ValueType = s.ExprString(results[0].Type)
|
||||
} else {
|
||||
opt.ValueType = s.ExprString(results[0].Type)
|
||||
}
|
||||
}
|
||||
if opt.MCType == _typeDel {
|
||||
if opt.template == _multiTpl {
|
||||
p, ok := params[1].Type.(*ast.ArrayType)
|
||||
if !ok {
|
||||
log.Fatalf("%s: 类型错误 参数需为[]类型\n", opt.name)
|
||||
}
|
||||
opt.keyType = s.ExprString(p.Elt)
|
||||
} else if opt.template == _singleTpl {
|
||||
opt.keyType = s.ExprString(params[1].Type)
|
||||
}
|
||||
}
|
||||
for _, t := range simpleTypes {
|
||||
if t == opt.ValueType {
|
||||
opt.SimpleValue = true
|
||||
opt.GetSimpleValue = true
|
||||
opt.ConvertValue2Bytes = convertValue2Bytes(t)
|
||||
opt.ConvertBytes2Value = convertBytes2Value(t)
|
||||
break
|
||||
}
|
||||
}
|
||||
if opt.ValueType == "string" {
|
||||
opt.LenType = true
|
||||
} else {
|
||||
for _, t := range lenTypes {
|
||||
if strings.HasPrefix(opt.ValueType, t) {
|
||||
opt.LenType = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if opt.SimpleValue && (opt.ValueType == "[]byte" || opt.ValueType == "string") {
|
||||
opt.GetSimpleValue = false
|
||||
opt.GetDirectValue = true
|
||||
}
|
||||
if opt.MCType == _typeGet && opt.template == _multiTpl {
|
||||
opt.UseMemcached = false
|
||||
}
|
||||
if strings.HasPrefix(opt.ValueType, "*") {
|
||||
opt.InitValue = true
|
||||
opt.PointType = true
|
||||
opt.OriginValueType = strings.Replace(opt.ValueType, "*", "", 1)
|
||||
} else {
|
||||
opt.OriginValueType = opt.ValueType
|
||||
}
|
||||
if *encode != "" {
|
||||
var flags []string
|
||||
for _, f := range strings.Split(*encode, "|") {
|
||||
switch f {
|
||||
case "gob":
|
||||
flags = append(flags, "memcache.FlagGOB")
|
||||
case "json":
|
||||
flags = append(flags, "memcache.FlagJSON")
|
||||
case "raw":
|
||||
flags = append(flags, "memcache.FlagRAW")
|
||||
case "pb":
|
||||
flags = append(flags, "memcache.FlagProtobuf")
|
||||
case "gzip":
|
||||
flags = append(flags, "memcache.FlagGzip")
|
||||
default:
|
||||
log.Fatalf("%s: encode类型无效\n", opt.name)
|
||||
}
|
||||
}
|
||||
opt.Encode = strings.Join(flags, " | ")
|
||||
} else {
|
||||
if opt.SimpleValue {
|
||||
opt.Encode = "memcache.FlagRAW"
|
||||
} else {
|
||||
opt.Encode = "memcache.FlagJSON"
|
||||
}
|
||||
}
|
||||
opt.Check()
|
||||
opts = append(opts, &opt)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (option *options) Check() {
|
||||
var valid bool
|
||||
for _, x := range mcValidTypes {
|
||||
if x == option.MCType {
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !valid {
|
||||
log.Fatalf("%s: 类型错误 不支持%s类型\n", option.name, option.MCType)
|
||||
}
|
||||
if (option.MCType != _typeDel) && !option.SimpleValue && !strings.Contains(option.ValueType, "*") && !strings.Contains(option.ValueType, "[]") && !strings.Contains(option.ValueType, "map") {
|
||||
log.Fatalf("%s: 值类型只能为基本类型/slice/map/指针类型\n", option.name)
|
||||
}
|
||||
}
|
||||
|
||||
func genHeader(opts []*options) (src string) {
|
||||
option := options{PkgName: os.Getenv("GOPACKAGE"), UseMemcached: false}
|
||||
var packages []string
|
||||
packagesMap := map[string]bool{`"context"`: true}
|
||||
for _, opt := range opts {
|
||||
if len(opt.importPackages) > 0 {
|
||||
for _, pkg := range opt.importPackages {
|
||||
if !packagesMap[pkg] {
|
||||
packages = append(packages, pkg)
|
||||
packagesMap[pkg] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if opt.Args != "" {
|
||||
option.Args = opt.Args
|
||||
}
|
||||
if opt.UseMemcached {
|
||||
option.UseMemcached = true
|
||||
}
|
||||
if opt.SimpleValue && !opt.GetDirectValue {
|
||||
option.UseStrConv = true
|
||||
}
|
||||
if opt.EnableBatch {
|
||||
option.EnableBatch = true
|
||||
}
|
||||
}
|
||||
option.ImportPackage = strings.Join(packages, "\n")
|
||||
src = _headerTemplate
|
||||
t := template.Must(template.New("header").Parse(src))
|
||||
var buffer bytes.Buffer
|
||||
err := t.Execute(&buffer, option)
|
||||
if err != nil {
|
||||
log.Fatalf("execute template: %s", err)
|
||||
}
|
||||
// Format the output.
|
||||
src = strings.Replace(buffer.String(), "\t", "", -1)
|
||||
src = regexp.MustCompile("\n+").ReplaceAllString(src, "\n")
|
||||
src = strings.Replace(src, "NEWLINE", "", -1)
|
||||
src = strings.Replace(src, "ARGS", option.Args, -1)
|
||||
return
|
||||
}
|
||||
|
||||
func genBody(opts []*options) (res string) {
|
||||
for _, option := range opts {
|
||||
var src string
|
||||
if option.template == _multiTpl {
|
||||
switch option.MCType {
|
||||
case _typeGet:
|
||||
src = _multiGetTemplate
|
||||
case _typeSet:
|
||||
src = _multiSetTemplate
|
||||
case _typeReplace:
|
||||
src = _multiReplaceTemplate
|
||||
case _typeDel:
|
||||
src = _multiDelTemplate
|
||||
case _typeAdd:
|
||||
src = _multiAddTemplate
|
||||
}
|
||||
} else if option.template == _singleTpl {
|
||||
switch option.MCType {
|
||||
case _typeGet:
|
||||
src = _singleGetTemplate
|
||||
case _typeSet:
|
||||
src = _singleSetTemplate
|
||||
case _typeReplace:
|
||||
src = _singleReplaceTemplate
|
||||
case _typeDel:
|
||||
src = _singleDelTemplate
|
||||
case _typeAdd:
|
||||
src = _singleAddTemplate
|
||||
}
|
||||
} else {
|
||||
switch option.MCType {
|
||||
case _typeGet:
|
||||
src = _noneGetTemplate
|
||||
case _typeSet:
|
||||
src = _noneSetTemplate
|
||||
case _typeReplace:
|
||||
src = _noneReplaceTemplate
|
||||
case _typeDel:
|
||||
src = _noneDelTemplate
|
||||
case _typeAdd:
|
||||
src = _noneAddTemplate
|
||||
}
|
||||
}
|
||||
src = strings.Replace(src, "KEY", option.keyType, -1)
|
||||
src = strings.Replace(src, "NAME", option.name, -1)
|
||||
src = strings.Replace(src, "VALUE", option.ValueType, -1)
|
||||
src = strings.Replace(src, "GROUPSIZE", strconv.Itoa(option.GroupSize), -1)
|
||||
src = strings.Replace(src, "MAXGROUP", strconv.Itoa(option.MaxGroup), -1)
|
||||
t := template.Must(template.New("cache").Parse(src))
|
||||
var buffer bytes.Buffer
|
||||
err := t.Execute(&buffer, option)
|
||||
if err != nil {
|
||||
log.Fatalf("execute template: %s", err)
|
||||
}
|
||||
// Format the output.
|
||||
src = strings.Replace(buffer.String(), "\t", "", -1)
|
||||
src = regexp.MustCompile("\n+").ReplaceAllString(src, "\n")
|
||||
res = res + "\n" + src
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Fatalf("程序解析失败, err: %+v 请企业微信联系 @wangxu01", err)
|
||||
}
|
||||
}()
|
||||
options := parse(common.NewSource(common.SourceText()))
|
||||
header := genHeader(options)
|
||||
body := genBody(options)
|
||||
code := common.FormatCode(header + "\n" + body)
|
||||
// Write to file.
|
||||
dir := filepath.Dir(".")
|
||||
outputName := filepath.Join(dir, "mc.cache.go")
|
||||
err := ioutil.WriteFile(outputName, []byte(code), 0644)
|
||||
if err != nil {
|
||||
log.Fatalf("写入文件失败: %s", err)
|
||||
}
|
||||
log.Println("mc.cache.go: 生成成功")
|
||||
}
|
||||
|
||||
func convertValue2Bytes(t string) string {
|
||||
switch t {
|
||||
case "int", "int8", "int16", "int32", "int64":
|
||||
return "[]byte(strconv.FormatInt(int64(val), 10))"
|
||||
case "uint", "uint8", "uint16", "uint32", "uint64":
|
||||
return "[]byte(strconv.FormatUInt(val, 10))"
|
||||
case "bool":
|
||||
return "[]byte(strconv.FormatBool(val))"
|
||||
case "float32":
|
||||
return "[]byte(strconv.FormatFloat(val, 'E', -1, 32))"
|
||||
case "float64":
|
||||
return "[]byte(strconv.FormatFloat(val, 'E', -1, 64))"
|
||||
case "string":
|
||||
return "[]byte(val)"
|
||||
case "[]byte":
|
||||
return "val"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func convertBytes2Value(t string) string {
|
||||
switch t {
|
||||
case "int", "int8", "int16", "int32", "int64":
|
||||
return "strconv.ParseInt(v, 10, 64)"
|
||||
case "uint", "uint8", "uint16", "uint32", "uint64":
|
||||
return "strconv.ParseUInt(v, 10, 64)"
|
||||
case "bool":
|
||||
return "strconv.ParseBool(v)"
|
||||
case "float32":
|
||||
return "float32(strconv.ParseFloat(v, 32))"
|
||||
case "float64":
|
||||
return "strconv.ParseFloat(v, 64)"
|
||||
}
|
||||
return ""
|
||||
}
|
217
app/tool/cache/memcached/multi_template.go
vendored
Normal file
217
app/tool/cache/memcached/multi_template.go
vendored
Normal file
@ -0,0 +1,217 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var _multiGetTemplate = `
|
||||
// NAME {{or .Comment "get data from mc"}}
|
||||
func (d *Dao) NAME(c context.Context, ids []KEY {{.ExtraArgsType}}) (res map[KEY]VALUE, err error) {
|
||||
l := len(ids)
|
||||
if l == 0 {
|
||||
return
|
||||
}
|
||||
{{if .EnableBatch}}
|
||||
mutex := sync.Mutex{}
|
||||
for i:=0;i < l; i+= GROUPSIZE * MAXGROUP {
|
||||
var subKeys []KEY
|
||||
{{if .BatchErrBreak}}
|
||||
group, ctx := errgroup.WithContext(c)
|
||||
{{else}}
|
||||
group := &errgroup.Group{}
|
||||
ctx := c
|
||||
{{end}}
|
||||
if (i + GROUPSIZE * MAXGROUP) > l {
|
||||
subKeys = ids[i:]
|
||||
} else {
|
||||
subKeys = ids[i : i+GROUPSIZE * MAXGROUP]
|
||||
}
|
||||
subLen := len(subKeys)
|
||||
for j:=0; j< subLen; j += GROUPSIZE {
|
||||
var ks []KEY
|
||||
if (j+GROUPSIZE) > subLen {
|
||||
ks = subKeys[j:]
|
||||
} else {
|
||||
ks = subKeys[j:j+GROUPSIZE]
|
||||
}
|
||||
group.Go(func() (err error) {
|
||||
keysMap := make(map[string]KEY, len(ks))
|
||||
keys := make([]string, 0, len(ks))
|
||||
for _, id := range ks {
|
||||
key := {{.KeyMethod}}(id{{.ExtraArgs}})
|
||||
keysMap[key] = id
|
||||
keys = append(keys, key)
|
||||
}
|
||||
conn := d.mc.Get(ctx)
|
||||
defer conn.Close()
|
||||
replies, err := conn.GetMulti(keys)
|
||||
if err != nil {
|
||||
prom.BusinessErrCount.Incr("mc:NAME")
|
||||
log.Errorv(ctx, log.KV("NAME", fmt.Sprintf("%+v", err)), log.KV("keys", keys))
|
||||
return
|
||||
}
|
||||
for key, reply := range replies {
|
||||
{{if .GetSimpleValue}}
|
||||
var v string
|
||||
err = conn.Scan(reply, &v)
|
||||
{{else}}
|
||||
var v VALUE
|
||||
{{if .GetDirectValue}}
|
||||
err = conn.Scan(reply, &v)
|
||||
{{else}}
|
||||
{{if .InitValue}}
|
||||
v = &{{.OriginValueType}}{}
|
||||
err = conn.Scan(reply, res)
|
||||
{{else}}
|
||||
v = {{.OriginValueType}}{}
|
||||
err = conn.Scan(reply, &res)
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
if err != nil {
|
||||
prom.BusinessErrCount.Incr("mc:NAME")
|
||||
log.Errorv(ctx, log.KV("NAME", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
{{if .GetSimpleValue}}
|
||||
r, err := {{.ConvertBytes2Value}}
|
||||
if err != nil {
|
||||
prom.BusinessErrCount.Incr("mc:NAME")
|
||||
log.Errorv(ctx, log.KV("NAME", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return res, err
|
||||
}
|
||||
mutex.Lock()
|
||||
if res == nil {
|
||||
res = make(map[KEY]VALUE, len(keys))
|
||||
}
|
||||
res[keysMap[key]] = {{.ValueType}}(r)
|
||||
mutex.Unlock()
|
||||
{{else}}
|
||||
mutex.Lock()
|
||||
if res == nil {
|
||||
res = make(map[KEY]VALUE, len(keys))
|
||||
}
|
||||
res[keysMap[key]] = v
|
||||
mutex.Unlock()
|
||||
{{end}}
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
err1 := group.Wait()
|
||||
if err1 != nil {
|
||||
err = err1
|
||||
{{if .BatchErrBreak}}
|
||||
break
|
||||
{{end}}
|
||||
}
|
||||
}
|
||||
{{else}}
|
||||
keysMap := make(map[string]KEY, l)
|
||||
keys := make([]string, 0, l)
|
||||
for _, id := range ids {
|
||||
key := {{.KeyMethod}}(id{{.ExtraArgs}})
|
||||
keysMap[key] = id
|
||||
keys = append(keys, key)
|
||||
}
|
||||
conn := d.mc.Get(c)
|
||||
defer conn.Close()
|
||||
replies, err := conn.GetMulti(keys)
|
||||
if err != nil {
|
||||
prom.BusinessErrCount.Incr("mc:NAME")
|
||||
log.Errorv(c, log.KV("NAME", fmt.Sprintf("%+v", err)), log.KV("keys", keys))
|
||||
return
|
||||
}
|
||||
for key, reply := range replies {
|
||||
{{if .GetSimpleValue}}
|
||||
var v string
|
||||
err = conn.Scan(reply, &v)
|
||||
{{else}}
|
||||
var v VALUE
|
||||
{{if .InitValue}}
|
||||
v = &{{.OriginValueType}}{}
|
||||
err = conn.Scan(reply, v)
|
||||
{{else}}
|
||||
v = {{.OriginValueType}}{}
|
||||
err = conn.Scan(reply, &v)
|
||||
{{end}}
|
||||
{{end}}
|
||||
if err != nil {
|
||||
prom.BusinessErrCount.Incr("mc:NAME")
|
||||
log.Errorv(c, log.KV("NAME", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
{{if .GetSimpleValue}}
|
||||
r, err := {{.ConvertBytes2Value}}
|
||||
if err != nil {
|
||||
prom.BusinessErrCount.Incr("mc:NAME")
|
||||
log.Errorv(c, log.KV("NAME", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return res, err
|
||||
}
|
||||
if res == nil {
|
||||
res = make(map[KEY]VALUE, len(keys))
|
||||
}
|
||||
res[keysMap[key]] = {{.ValueType}}(r)
|
||||
{{else}}
|
||||
if res == nil {
|
||||
res = make(map[KEY]VALUE, len(keys))
|
||||
}
|
||||
res[keysMap[key]] = v
|
||||
{{end}}
|
||||
}
|
||||
{{end}}
|
||||
return
|
||||
}
|
||||
`
|
||||
|
||||
var _multiSetTemplate = `
|
||||
// NAME {{or .Comment "Set data to mc"}}
|
||||
func (d *Dao) NAME(c context.Context, values map[KEY]VALUE {{.ExtraArgsType}}) (err error) {
|
||||
if len(values) == 0 {
|
||||
return
|
||||
}
|
||||
conn := d.mc.Get(c)
|
||||
defer conn.Close()
|
||||
for id, val := range values {
|
||||
key := {{.KeyMethod}}(id{{.ExtraArgs}})
|
||||
{{if .SimpleValue}}
|
||||
bs := {{.ConvertValue2Bytes}}
|
||||
item := &memcache.Item{Key: key, Value: bs, Expiration: {{.ExpireCode}}, Flags: {{.Encode}}}
|
||||
{{else}}
|
||||
item := &memcache.Item{Key: key, Object: val, Expiration: {{.ExpireCode}}, Flags: {{.Encode}}}
|
||||
{{end}}
|
||||
if err = conn.Set(item); err != nil {
|
||||
prom.BusinessErrCount.Incr("mc:NAME")
|
||||
log.Errorv(c, log.KV("NAME", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
`
|
||||
var _multiAddTemplate = strings.Replace(_multiSetTemplate, "Set", "Add", -1)
|
||||
var _multiReplaceTemplate = strings.Replace(_multiSetTemplate, "Set", "Replace", -1)
|
||||
|
||||
var _multiDelTemplate = `
|
||||
// NAME {{or .Comment "delete data from mc"}}
|
||||
func (d *Dao) NAME(c context.Context, ids []KEY {{.ExtraArgsType}}) (err error) {
|
||||
if len(ids) == 0 {
|
||||
return
|
||||
}
|
||||
conn := d.mc.Get(c)
|
||||
defer conn.Close()
|
||||
for _, id := range ids {
|
||||
key := {{.KeyMethod}}(id{{.ExtraArgs}})
|
||||
if err = conn.Delete(key); err != nil {
|
||||
if err == memcache.ErrNotFound {
|
||||
err = nil
|
||||
continue
|
||||
}
|
||||
prom.BusinessErrCount.Incr("mc:NAME")
|
||||
log.Errorv(c, log.KV("NAME", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
`
|
107
app/tool/cache/memcached/none_template.go
vendored
Normal file
107
app/tool/cache/memcached/none_template.go
vendored
Normal file
@ -0,0 +1,107 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var _noneGetTemplate = `
|
||||
// NAME {{or .Comment "get data from mc"}}
|
||||
func (d *Dao) NAME(c context.Context) (res VALUE, err error) {
|
||||
conn := d.mc.Get(c)
|
||||
defer conn.Close()
|
||||
key := {{.KeyMethod}}()
|
||||
reply, err := conn.Get(key)
|
||||
if err != nil {
|
||||
if err == memcache.ErrNotFound {
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
prom.BusinessErrCount.Incr("mc:NAME")
|
||||
log.Errorv(c, log.KV("NAME", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
{{if .GetSimpleValue}}
|
||||
var v string
|
||||
err = conn.Scan(reply, &v)
|
||||
{{else}}
|
||||
{{if .GetDirectValue}}
|
||||
err = conn.Scan(reply, &res)
|
||||
{{else}}
|
||||
{{if .InitValue}}
|
||||
res = &{{.OriginValueType}}{}
|
||||
err = conn.Scan(reply, res)
|
||||
{{else}}
|
||||
res = {{.OriginValueType}}{}
|
||||
err = conn.Scan(reply, &res)
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
if err != nil {
|
||||
prom.BusinessErrCount.Incr("mc:NAME")
|
||||
log.Errorv(c, log.KV("NAME", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
{{if .GetSimpleValue}}
|
||||
r, err := {{.ConvertBytes2Value}}
|
||||
if err != nil {
|
||||
prom.BusinessErrCount.Incr("mc:NAME")
|
||||
log.Errorv(c, log.KV("NAME", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
res = {{.ValueType}}(r)
|
||||
{{end}}
|
||||
return
|
||||
}
|
||||
`
|
||||
|
||||
var _noneSetTemplate = `
|
||||
// NAME {{or .Comment "Set data to mc"}}
|
||||
func (d *Dao) NAME(c context.Context, val VALUE) (err error) {
|
||||
{{if .PointType}}
|
||||
if val == nil {
|
||||
return
|
||||
}
|
||||
{{end}}
|
||||
{{if .LenType}}
|
||||
if len(val) == 0 {
|
||||
return
|
||||
}
|
||||
{{end}}
|
||||
conn := d.mc.Get(c)
|
||||
defer conn.Close()
|
||||
key := {{.KeyMethod}}()
|
||||
{{if .SimpleValue}}
|
||||
bs := {{.ConvertValue2Bytes}}
|
||||
item := &memcache.Item{Key: key, Value: bs, Expiration: {{.ExpireCode}}, Flags: {{.Encode}}}
|
||||
{{else}}
|
||||
item := &memcache.Item{Key: key, Object: val, Expiration: {{.ExpireCode}}, Flags: {{.Encode}}}
|
||||
{{end}}
|
||||
if err = conn.Set(item); err != nil {
|
||||
prom.BusinessErrCount.Incr("mc:NAME")
|
||||
log.Errorv(c, log.KV("NAME", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
`
|
||||
var _noneAddTemplate = strings.Replace(_noneSetTemplate, "Set", "Add", -1)
|
||||
var _noneReplaceTemplate = strings.Replace(_noneSetTemplate, "Set", "Replace", -1)
|
||||
|
||||
var _noneDelTemplate = `
|
||||
// NAME {{or .Comment "delete data from mc"}}
|
||||
func (d *Dao) NAME(c context.Context) (err error) {
|
||||
conn := d.mc.Get(c)
|
||||
defer conn.Close()
|
||||
key := {{.KeyMethod}}()
|
||||
if err = conn.Delete(key); err != nil {
|
||||
if err == memcache.ErrNotFound {
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
prom.BusinessErrCount.Incr("mc:NAME")
|
||||
log.Errorv(c, log.KV("NAME", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
`
|
107
app/tool/cache/memcached/single_template.go
vendored
Normal file
107
app/tool/cache/memcached/single_template.go
vendored
Normal file
@ -0,0 +1,107 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var _singleGetTemplate = `
|
||||
// NAME {{or .Comment "get data from mc"}}
|
||||
func (d *Dao) NAME(c context.Context, id KEY {{.ExtraArgsType}}) (res VALUE, err error) {
|
||||
conn := d.mc.Get(c)
|
||||
defer conn.Close()
|
||||
key := {{.KeyMethod}}(id{{.ExtraArgs}})
|
||||
reply, err := conn.Get(key)
|
||||
if err != nil {
|
||||
if err == memcache.ErrNotFound {
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
prom.BusinessErrCount.Incr("mc:NAME")
|
||||
log.Errorv(c, log.KV("NAME", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
{{if .GetSimpleValue}}
|
||||
var v string
|
||||
err = conn.Scan(reply, &v)
|
||||
{{else}}
|
||||
{{if .GetDirectValue}}
|
||||
err = conn.Scan(reply, &res)
|
||||
{{else}}
|
||||
{{if .InitValue}}
|
||||
res = &{{.OriginValueType}}{}
|
||||
err = conn.Scan(reply, res)
|
||||
{{else}}
|
||||
res = {{.OriginValueType}}{}
|
||||
err = conn.Scan(reply, &res)
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
if err != nil {
|
||||
prom.BusinessErrCount.Incr("mc:NAME")
|
||||
log.Errorv(c, log.KV("NAME", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
{{if .GetSimpleValue}}
|
||||
r, err := {{.ConvertBytes2Value}}
|
||||
if err != nil {
|
||||
prom.BusinessErrCount.Incr("mc:NAME")
|
||||
log.Errorv(c, log.KV("NAME", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
res = {{.ValueType}}(r)
|
||||
{{end}}
|
||||
return
|
||||
}
|
||||
`
|
||||
|
||||
var _singleSetTemplate = `
|
||||
// NAME {{or .Comment "Set data to mc"}}
|
||||
func (d *Dao) NAME(c context.Context, id KEY, val VALUE {{.ExtraArgsType}}) (err error) {
|
||||
{{if .PointType}}
|
||||
if val == nil {
|
||||
return
|
||||
}
|
||||
{{end}}
|
||||
{{if .LenType}}
|
||||
if len(val) == 0 {
|
||||
return
|
||||
}
|
||||
{{end}}
|
||||
conn := d.mc.Get(c)
|
||||
defer conn.Close()
|
||||
key := {{.KeyMethod}}(id{{.ExtraArgs}})
|
||||
{{if .SimpleValue}}
|
||||
bs := {{.ConvertValue2Bytes}}
|
||||
item := &memcache.Item{Key: key, Value: bs, Expiration: {{.ExpireCode}}, Flags: {{.Encode}}}
|
||||
{{else}}
|
||||
item := &memcache.Item{Key: key, Object: val, Expiration: {{.ExpireCode}}, Flags: {{.Encode}}}
|
||||
{{end}}
|
||||
if err = conn.Set(item); err != nil {
|
||||
prom.BusinessErrCount.Incr("mc:NAME")
|
||||
log.Errorv(c, log.KV("NAME", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
`
|
||||
var _singleAddTemplate = strings.Replace(_singleSetTemplate, "Set", "Add", -1)
|
||||
var _singleReplaceTemplate = strings.Replace(_singleSetTemplate, "Set", "Replace", -1)
|
||||
|
||||
var _singleDelTemplate = `
|
||||
// NAME {{or .Comment "delete data from mc"}}
|
||||
func (d *Dao) NAME(c context.Context, id KEY {{.ExtraArgsType}}) (err error) {
|
||||
conn := d.mc.Get(c)
|
||||
defer conn.Close()
|
||||
key := {{.KeyMethod}}(id{{.ExtraArgs}})
|
||||
if err = conn.Delete(key); err != nil {
|
||||
if err == memcache.ErrNotFound {
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
prom.BusinessErrCount.Incr("mc:NAME")
|
||||
log.Errorv(c, log.KV("NAME", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
`
|
71
app/tool/cache/memcached/testdata/BUILD
vendored
Normal file
71
app/tool/cache/memcached/testdata/BUILD
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
load(
|
||||
"@io_bazel_rules_go//proto:def.bzl",
|
||||
"go_proto_library",
|
||||
)
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_test",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
proto_library(
|
||||
name = "model_proto",
|
||||
srcs = ["model.proto"],
|
||||
tags = ["automanaged"],
|
||||
deps = ["@gogo_special_proto//github.com/gogo/protobuf/gogoproto"],
|
||||
)
|
||||
|
||||
go_proto_library(
|
||||
name = "model_go_proto",
|
||||
compilers = ["@io_bazel_rules_go//proto:gogofast_proto"],
|
||||
importpath = "go-common/app/tool/cache/memcached/testdata",
|
||||
proto = ":model_proto",
|
||||
tags = ["automanaged"],
|
||||
deps = ["@com_github_gogo_protobuf//gogoproto:go_default_library"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["dao_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
rundir = ".",
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"dao.go",
|
||||
"mc.cache.go",
|
||||
],
|
||||
embed = [":model_go_proto"],
|
||||
importpath = "go-common/app/tool/cache/memcached/testdata",
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//library/cache/memcache:go_default_library",
|
||||
"//library/container/pool:go_default_library",
|
||||
"//library/log:go_default_library",
|
||||
"//library/stat/prom:go_default_library",
|
||||
"//library/time:go_default_library",
|
||||
"@com_github_gogo_protobuf//gogoproto:go_default_library",
|
||||
"@com_github_golang_protobuf//proto:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
87
app/tool/cache/memcached/testdata/dao.go
vendored
Normal file
87
app/tool/cache/memcached/testdata/dao.go
vendored
Normal file
@ -0,0 +1,87 @@
|
||||
package testdata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go-common/library/cache/memcache"
|
||||
"go-common/library/container/pool"
|
||||
xtime "go-common/library/time"
|
||||
)
|
||||
|
||||
// Dao .
|
||||
type Dao struct {
|
||||
mc *memcache.Pool
|
||||
articleExpire int32
|
||||
}
|
||||
|
||||
// New new dao
|
||||
func New() (d *Dao) {
|
||||
cfg := &memcache.Config{
|
||||
Config: &pool.Config{
|
||||
Active: 10,
|
||||
Idle: 5,
|
||||
IdleTimeout: xtime.Duration(time.Second),
|
||||
},
|
||||
Name: "test",
|
||||
Proto: "tcp",
|
||||
// Addr: "172.16.33.54:11214",
|
||||
Addr: "127.0.0.1:11211",
|
||||
DialTimeout: xtime.Duration(time.Second),
|
||||
ReadTimeout: xtime.Duration(time.Second),
|
||||
WriteTimeout: xtime.Duration(time.Second),
|
||||
}
|
||||
d = &Dao{
|
||||
mc: memcache.NewPool(cfg),
|
||||
articleExpire: int32(5),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//go:generate $GOPATH/src/go-common/app/tool/cache/mc
|
||||
type _mc interface {
|
||||
// mc: -key=articleKey
|
||||
CacheArticles(c context.Context, keys []int64) (map[int64]*Article, error)
|
||||
// mc: -key=articleKey
|
||||
CacheArticle(c context.Context, key int64) (*Article, error)
|
||||
// mc: -key=keyMid
|
||||
CacheArticle1(c context.Context, key int64, mid int64) (*Article, error)
|
||||
// mc: -key=noneKey
|
||||
CacheNone(c context.Context) (*Article, error)
|
||||
// mc: -key=articleKey
|
||||
CacheString(c context.Context, key int64) (string, error)
|
||||
|
||||
// mc: -key=articleKey -expire=d.articleExpire -encode=json
|
||||
AddCacheArticles(c context.Context, values map[int64]*Article) error
|
||||
// 这里也支持自定义注释 会替换默认的注释
|
||||
// mc: -key=articleKey -expire=d.articleExpire -encode=json|gzip
|
||||
AddCacheArticle(c context.Context, key int64, value *Article) error
|
||||
// mc: -key=keyMid -expire=d.articleExpire -encode=gob
|
||||
AddCacheArticle1(c context.Context, key int64, value *Article, mid int64) error
|
||||
// mc: -key=noneKey
|
||||
AddCacheNone(c context.Context, value *Article) error
|
||||
// mc: -key=articleKey -expire=d.articleExpire
|
||||
AddCacheString(c context.Context, key int64, value string) error
|
||||
|
||||
// mc: -key=articleKey
|
||||
DelCacheArticles(c context.Context, keys []int64) error
|
||||
// mc: -key=articleKey
|
||||
DelCacheArticle(c context.Context, key int64) error
|
||||
// mc: -key=keyMid
|
||||
DelCacheArticle1(c context.Context, key int64, mid int64) error
|
||||
// mc: -key=noneKey
|
||||
DelCacheNone(c context.Context) error
|
||||
}
|
||||
|
||||
func articleKey(id int64) string {
|
||||
return fmt.Sprintf("art_%d", id)
|
||||
}
|
||||
|
||||
func keyMid(id, mid int64) string {
|
||||
return fmt.Sprintf("art_%d_%d", id, mid)
|
||||
}
|
||||
|
||||
func noneKey() string {
|
||||
return "none"
|
||||
}
|
116
app/tool/cache/memcached/testdata/dao_test.go
vendored
Normal file
116
app/tool/cache/memcached/testdata/dao_test.go
vendored
Normal file
@ -0,0 +1,116 @@
|
||||
package testdata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestArticle(t *testing.T) {
|
||||
d := New()
|
||||
c := context.TODO()
|
||||
art := &Article{ID: 1, Title: "title"}
|
||||
err := d.AddCacheArticle(c, art.ID, art)
|
||||
if err != nil {
|
||||
t.Errorf("err should be nil, get: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
art1, err := d.CacheArticle(c, art.ID)
|
||||
if err != nil {
|
||||
t.Errorf("err should be nil, get: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
if (art1.ID != art.ID) || (art.Title != art1.Title) {
|
||||
t.Error("art not equal")
|
||||
t.FailNow()
|
||||
}
|
||||
err = d.DelCacheArticle(c, art.ID)
|
||||
if err != nil {
|
||||
t.Errorf("err should be nil, get: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
art1, err = d.CacheArticle(c, art.ID)
|
||||
if (art1 != nil) || (err != nil) {
|
||||
t.Errorf("art %v, err: %v", art1, err)
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func TestNone(t *testing.T) {
|
||||
d := New()
|
||||
c := context.TODO()
|
||||
art := &Article{ID: 1, Title: "title"}
|
||||
err := d.AddCacheNone(c, art)
|
||||
if err != nil {
|
||||
t.Errorf("err should be nil, get: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
art1, err := d.CacheNone(c)
|
||||
if err != nil {
|
||||
t.Errorf("err should be nil, get: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
if (art1.ID != art.ID) || (art.Title != art1.Title) {
|
||||
t.Error("art not equal")
|
||||
t.FailNow()
|
||||
}
|
||||
err = d.DelCacheNone(c)
|
||||
if err != nil {
|
||||
t.Errorf("err should be nil, get: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
art1, err = d.CacheNone(c)
|
||||
if (art1 != nil) || (err != nil) {
|
||||
t.Errorf("art %v, err: %v", art1, err)
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func TestArticles(t *testing.T) {
|
||||
d := New()
|
||||
c := context.TODO()
|
||||
art1 := &Article{ID: 1, Title: "title"}
|
||||
art2 := &Article{ID: 2, Title: "title"}
|
||||
err := d.AddCacheArticles(c, map[int64]*Article{1: art1, 2: art2})
|
||||
if err != nil {
|
||||
t.Errorf("err should be nil, get: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
arts, err := d.CacheArticles(c, []int64{art1.ID, art2.ID})
|
||||
if err != nil {
|
||||
t.Errorf("err should be nil, get: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
if (arts[1].Title != art1.Title) || (arts[2].Title != art2.Title) {
|
||||
t.Error("art not equal")
|
||||
t.FailNow()
|
||||
}
|
||||
err = d.DelCacheArticles(c, []int64{art1.ID, art2.ID})
|
||||
if err != nil {
|
||||
t.Errorf("err should be nil, get: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
arts, err = d.CacheArticles(c, []int64{art1.ID, art2.ID})
|
||||
if (arts != nil) || (err != nil) {
|
||||
t.Errorf("art %v, err: %v", art1, err)
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func TestString(t *testing.T) {
|
||||
d := New()
|
||||
c := context.TODO()
|
||||
err := d.AddCacheString(c, 1, "abc")
|
||||
if err != nil {
|
||||
t.Errorf("err should be nil, get: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
res, err := d.CacheString(c, 1)
|
||||
if err != nil {
|
||||
t.Errorf("err should be nil, get: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
if res != "abc" {
|
||||
t.Error("res wrong")
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
350
app/tool/cache/memcached/testdata/mc.cache.go
vendored
Normal file
350
app/tool/cache/memcached/testdata/mc.cache.go
vendored
Normal file
@ -0,0 +1,350 @@
|
||||
// Code generated by $GOPATH/src/go-common/app/tool/cache/mc. DO NOT EDIT.
|
||||
|
||||
/*
|
||||
Package testdata is a generated mc cache package.
|
||||
It is generated from:
|
||||
type _mc interface {
|
||||
// mc: -key=articleKey
|
||||
CacheArticles(c context.Context, keys []int64) (map[int64]*Article, error)
|
||||
// mc: -key=articleKey
|
||||
CacheArticle(c context.Context, key int64) (*Article, error)
|
||||
// mc: -key=keyMid
|
||||
CacheArticle1(c context.Context, key int64, mid int64) (*Article, error)
|
||||
// mc: -key=noneKey
|
||||
CacheNone(c context.Context) (*Article, error)
|
||||
// mc: -key=articleKey
|
||||
CacheString(c context.Context, key int64) (string, error)
|
||||
|
||||
// mc: -key=articleKey -expire=d.articleExpire -encode=json
|
||||
AddCacheArticles(c context.Context, values map[int64]*Article) error
|
||||
// 这里也支持自定义注释 会替换默认的注释
|
||||
// mc: -key=articleKey -expire=d.articleExpire -encode=json|gzip
|
||||
AddCacheArticle(c context.Context, key int64, value *Article) error
|
||||
// mc: -key=keyMid -expire=d.articleExpire -encode=gob
|
||||
AddCacheArticle1(c context.Context, key int64, value *Article, mid int64) error
|
||||
// mc: -key=noneKey
|
||||
AddCacheNone(c context.Context, value *Article) error
|
||||
// mc: -key=articleKey -expire=d.articleExpire
|
||||
AddCacheString(c context.Context, key int64, value string) error
|
||||
|
||||
// mc: -key=articleKey
|
||||
DelCacheArticles(c context.Context, keys []int64) error
|
||||
// mc: -key=articleKey
|
||||
DelCacheArticle(c context.Context, key int64) error
|
||||
// mc: -key=keyMid
|
||||
DelCacheArticle1(c context.Context, key int64, mid int64) error
|
||||
// mc: -key=noneKey
|
||||
DelCacheNone(c context.Context) error
|
||||
}
|
||||
*/
|
||||
|
||||
package testdata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"go-common/library/cache/memcache"
|
||||
"go-common/library/log"
|
||||
"go-common/library/stat/prom"
|
||||
)
|
||||
|
||||
var _ _mc
|
||||
|
||||
// CacheArticles get data from mc
|
||||
func (d *Dao) CacheArticles(c context.Context, ids []int64) (res map[int64]*Article, err error) {
|
||||
l := len(ids)
|
||||
if l == 0 {
|
||||
return
|
||||
}
|
||||
keysMap := make(map[string]int64, l)
|
||||
keys := make([]string, 0, l)
|
||||
for _, id := range ids {
|
||||
key := articleKey(id)
|
||||
keysMap[key] = id
|
||||
keys = append(keys, key)
|
||||
}
|
||||
conn := d.mc.Get(c)
|
||||
defer conn.Close()
|
||||
replies, err := conn.GetMulti(keys)
|
||||
if err != nil {
|
||||
prom.BusinessErrCount.Incr("mc:CacheArticles")
|
||||
log.Errorv(c, log.KV("CacheArticles", fmt.Sprintf("%+v", err)), log.KV("keys", keys))
|
||||
return
|
||||
}
|
||||
for key, reply := range replies {
|
||||
var v *Article
|
||||
v = &Article{}
|
||||
err = conn.Scan(reply, v)
|
||||
if err != nil {
|
||||
prom.BusinessErrCount.Incr("mc:CacheArticles")
|
||||
log.Errorv(c, log.KV("CacheArticles", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
if res == nil {
|
||||
res = make(map[int64]*Article, len(keys))
|
||||
}
|
||||
res[keysMap[key]] = v
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// CacheArticle get data from mc
|
||||
func (d *Dao) CacheArticle(c context.Context, id int64) (res *Article, err error) {
|
||||
conn := d.mc.Get(c)
|
||||
defer conn.Close()
|
||||
key := articleKey(id)
|
||||
reply, err := conn.Get(key)
|
||||
if err != nil {
|
||||
if err == memcache.ErrNotFound {
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
prom.BusinessErrCount.Incr("mc:CacheArticle")
|
||||
log.Errorv(c, log.KV("CacheArticle", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
res = &Article{}
|
||||
err = conn.Scan(reply, res)
|
||||
if err != nil {
|
||||
prom.BusinessErrCount.Incr("mc:CacheArticle")
|
||||
log.Errorv(c, log.KV("CacheArticle", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// CacheArticle1 get data from mc
|
||||
func (d *Dao) CacheArticle1(c context.Context, id int64, mid int64) (res *Article, err error) {
|
||||
conn := d.mc.Get(c)
|
||||
defer conn.Close()
|
||||
key := keyMid(id, mid)
|
||||
reply, err := conn.Get(key)
|
||||
if err != nil {
|
||||
if err == memcache.ErrNotFound {
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
prom.BusinessErrCount.Incr("mc:CacheArticle1")
|
||||
log.Errorv(c, log.KV("CacheArticle1", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
res = &Article{}
|
||||
err = conn.Scan(reply, res)
|
||||
if err != nil {
|
||||
prom.BusinessErrCount.Incr("mc:CacheArticle1")
|
||||
log.Errorv(c, log.KV("CacheArticle1", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// CacheNone get data from mc
|
||||
func (d *Dao) CacheNone(c context.Context) (res *Article, err error) {
|
||||
conn := d.mc.Get(c)
|
||||
defer conn.Close()
|
||||
key := noneKey()
|
||||
reply, err := conn.Get(key)
|
||||
if err != nil {
|
||||
if err == memcache.ErrNotFound {
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
prom.BusinessErrCount.Incr("mc:CacheNone")
|
||||
log.Errorv(c, log.KV("CacheNone", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
res = &Article{}
|
||||
err = conn.Scan(reply, res)
|
||||
if err != nil {
|
||||
prom.BusinessErrCount.Incr("mc:CacheNone")
|
||||
log.Errorv(c, log.KV("CacheNone", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// CacheString get data from mc
|
||||
func (d *Dao) CacheString(c context.Context, id int64) (res string, err error) {
|
||||
conn := d.mc.Get(c)
|
||||
defer conn.Close()
|
||||
key := articleKey(id)
|
||||
reply, err := conn.Get(key)
|
||||
if err != nil {
|
||||
if err == memcache.ErrNotFound {
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
prom.BusinessErrCount.Incr("mc:CacheString")
|
||||
log.Errorv(c, log.KV("CacheString", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
err = conn.Scan(reply, &res)
|
||||
if err != nil {
|
||||
prom.BusinessErrCount.Incr("mc:CacheString")
|
||||
log.Errorv(c, log.KV("CacheString", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// AddCacheArticles Set data to mc
|
||||
func (d *Dao) AddCacheArticles(c context.Context, values map[int64]*Article) (err error) {
|
||||
if len(values) == 0 {
|
||||
return
|
||||
}
|
||||
conn := d.mc.Get(c)
|
||||
defer conn.Close()
|
||||
for id, val := range values {
|
||||
key := articleKey(id)
|
||||
item := &memcache.Item{Key: key, Object: val, Expiration: d.articleExpire, Flags: memcache.FlagJSON}
|
||||
if err = conn.Set(item); err != nil {
|
||||
prom.BusinessErrCount.Incr("mc:AddCacheArticles")
|
||||
log.Errorv(c, log.KV("AddCacheArticles", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// AddCacheArticle 这里也支持自定义注释 会替换默认的注释
|
||||
func (d *Dao) AddCacheArticle(c context.Context, id int64, val *Article) (err error) {
|
||||
if val == nil {
|
||||
return
|
||||
}
|
||||
conn := d.mc.Get(c)
|
||||
defer conn.Close()
|
||||
key := articleKey(id)
|
||||
item := &memcache.Item{Key: key, Object: val, Expiration: d.articleExpire, Flags: memcache.FlagJSON | memcache.FlagGzip}
|
||||
if err = conn.Set(item); err != nil {
|
||||
prom.BusinessErrCount.Incr("mc:AddCacheArticle")
|
||||
log.Errorv(c, log.KV("AddCacheArticle", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// AddCacheArticle1 Set data to mc
|
||||
func (d *Dao) AddCacheArticle1(c context.Context, id int64, val *Article, mid int64) (err error) {
|
||||
if val == nil {
|
||||
return
|
||||
}
|
||||
conn := d.mc.Get(c)
|
||||
defer conn.Close()
|
||||
key := keyMid(id, mid)
|
||||
item := &memcache.Item{Key: key, Object: val, Expiration: d.articleExpire, Flags: memcache.FlagGOB}
|
||||
if err = conn.Set(item); err != nil {
|
||||
prom.BusinessErrCount.Incr("mc:AddCacheArticle1")
|
||||
log.Errorv(c, log.KV("AddCacheArticle1", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// AddCacheNone Set data to mc
|
||||
func (d *Dao) AddCacheNone(c context.Context, val *Article) (err error) {
|
||||
if val == nil {
|
||||
return
|
||||
}
|
||||
conn := d.mc.Get(c)
|
||||
defer conn.Close()
|
||||
key := noneKey()
|
||||
item := &memcache.Item{Key: key, Object: val, Expiration: d.articleExpire, Flags: memcache.FlagJSON}
|
||||
if err = conn.Set(item); err != nil {
|
||||
prom.BusinessErrCount.Incr("mc:AddCacheNone")
|
||||
log.Errorv(c, log.KV("AddCacheNone", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// AddCacheString Set data to mc
|
||||
func (d *Dao) AddCacheString(c context.Context, id int64, val string) (err error) {
|
||||
if len(val) == 0 {
|
||||
return
|
||||
}
|
||||
conn := d.mc.Get(c)
|
||||
defer conn.Close()
|
||||
key := articleKey(id)
|
||||
bs := []byte(val)
|
||||
item := &memcache.Item{Key: key, Value: bs, Expiration: d.articleExpire, Flags: memcache.FlagRAW}
|
||||
if err = conn.Set(item); err != nil {
|
||||
prom.BusinessErrCount.Incr("mc:AddCacheString")
|
||||
log.Errorv(c, log.KV("AddCacheString", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DelCacheArticles delete data from mc
|
||||
func (d *Dao) DelCacheArticles(c context.Context, ids []int64) (err error) {
|
||||
if len(ids) == 0 {
|
||||
return
|
||||
}
|
||||
conn := d.mc.Get(c)
|
||||
defer conn.Close()
|
||||
for _, id := range ids {
|
||||
key := articleKey(id)
|
||||
if err = conn.Delete(key); err != nil {
|
||||
if err == memcache.ErrNotFound {
|
||||
err = nil
|
||||
continue
|
||||
}
|
||||
prom.BusinessErrCount.Incr("mc:DelCacheArticles")
|
||||
log.Errorv(c, log.KV("DelCacheArticles", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DelCacheArticle delete data from mc
|
||||
func (d *Dao) DelCacheArticle(c context.Context, id int64) (err error) {
|
||||
conn := d.mc.Get(c)
|
||||
defer conn.Close()
|
||||
key := articleKey(id)
|
||||
if err = conn.Delete(key); err != nil {
|
||||
if err == memcache.ErrNotFound {
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
prom.BusinessErrCount.Incr("mc:DelCacheArticle")
|
||||
log.Errorv(c, log.KV("DelCacheArticle", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DelCacheArticle1 delete data from mc
|
||||
func (d *Dao) DelCacheArticle1(c context.Context, id int64, mid int64) (err error) {
|
||||
conn := d.mc.Get(c)
|
||||
defer conn.Close()
|
||||
key := keyMid(id, mid)
|
||||
if err = conn.Delete(key); err != nil {
|
||||
if err == memcache.ErrNotFound {
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
prom.BusinessErrCount.Incr("mc:DelCacheArticle1")
|
||||
log.Errorv(c, log.KV("DelCacheArticle1", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DelCacheNone delete data from mc
|
||||
func (d *Dao) DelCacheNone(c context.Context) (err error) {
|
||||
conn := d.mc.Get(c)
|
||||
defer conn.Close()
|
||||
key := noneKey()
|
||||
if err = conn.Delete(key); err != nil {
|
||||
if err == memcache.ErrNotFound {
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
prom.BusinessErrCount.Incr("mc:DelCacheNone")
|
||||
log.Errorv(c, log.KV("DelCacheNone", fmt.Sprintf("%+v", err)), log.KV("key", key))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
328
app/tool/cache/memcached/testdata/model.pb.go
vendored
Normal file
328
app/tool/cache/memcached/testdata/model.pb.go
vendored
Normal file
@ -0,0 +1,328 @@
|
||||
// Code generated by protoc-gen-gogo. DO NOT EDIT.
|
||||
// source: model.proto
|
||||
|
||||
/*
|
||||
Package model is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
model.proto
|
||||
|
||||
It has these top-level messages:
|
||||
Article
|
||||
*/
|
||||
package testdata
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
import _ "github.com/gogo/protobuf/gogoproto"
|
||||
|
||||
import io "io"
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
type Article struct {
|
||||
ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"id"`
|
||||
Title string `protobuf:"bytes,3,opt,name=Title,proto3" json:"title"`
|
||||
}
|
||||
|
||||
func (m *Article) Reset() { *m = Article{} }
|
||||
func (m *Article) String() string { return proto.CompactTextString(m) }
|
||||
func (*Article) ProtoMessage() {}
|
||||
func (*Article) Descriptor() ([]byte, []int) { return fileDescriptorModel, []int{0} }
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Article)(nil), "model.Article")
|
||||
}
|
||||
func (m *Article) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalTo(dAtA)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *Article) MarshalTo(dAtA []byte) (int, error) {
|
||||
var i int
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if m.ID != 0 {
|
||||
dAtA[i] = 0x8
|
||||
i++
|
||||
i = encodeVarintModel(dAtA, i, uint64(m.ID))
|
||||
}
|
||||
if len(m.Title) > 0 {
|
||||
dAtA[i] = 0x1a
|
||||
i++
|
||||
i = encodeVarintModel(dAtA, i, uint64(len(m.Title)))
|
||||
i += copy(dAtA[i:], m.Title)
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func encodeVarintModel(dAtA []byte, offset int, v uint64) int {
|
||||
for v >= 1<<7 {
|
||||
dAtA[offset] = uint8(v&0x7f | 0x80)
|
||||
v >>= 7
|
||||
offset++
|
||||
}
|
||||
dAtA[offset] = uint8(v)
|
||||
return offset + 1
|
||||
}
|
||||
func (m *Article) Size() (n int) {
|
||||
var l int
|
||||
_ = l
|
||||
if m.ID != 0 {
|
||||
n += 1 + sovModel(uint64(m.ID))
|
||||
}
|
||||
l = len(m.Title)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovModel(uint64(l))
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func sovModel(x uint64) (n int) {
|
||||
for {
|
||||
n++
|
||||
x >>= 7
|
||||
if x == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
func sozModel(x uint64) (n int) {
|
||||
return sovModel(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||
}
|
||||
func (m *Article) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowModel
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: Article: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: Article: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field ID", wireType)
|
||||
}
|
||||
m.ID = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowModel
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.ID |= (int64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 3:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Title", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowModel
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthModel
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Title = string(dAtA[iNdEx:postIndex])
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipModel(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthModel
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func skipModel(dAtA []byte) (n int, err error) {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowModel
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
wireType := int(wire & 0x7)
|
||||
switch wireType {
|
||||
case 0:
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowModel
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx++
|
||||
if dAtA[iNdEx-1] < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 1:
|
||||
iNdEx += 8
|
||||
return iNdEx, nil
|
||||
case 2:
|
||||
var length int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowModel
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
length |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
iNdEx += length
|
||||
if length < 0 {
|
||||
return 0, ErrInvalidLengthModel
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 3:
|
||||
for {
|
||||
var innerWire uint64
|
||||
var start int = iNdEx
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowModel
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
innerWire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
innerWireType := int(innerWire & 0x7)
|
||||
if innerWireType == 4 {
|
||||
break
|
||||
}
|
||||
next, err := skipModel(dAtA[start:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
iNdEx = start + next
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 4:
|
||||
return iNdEx, nil
|
||||
case 5:
|
||||
iNdEx += 4
|
||||
return iNdEx, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
|
||||
}
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidLengthModel = fmt.Errorf("proto: negative length found during unmarshaling")
|
||||
ErrIntOverflowModel = fmt.Errorf("proto: integer overflow")
|
||||
)
|
||||
|
||||
func init() { proto.RegisterFile("model.proto", fileDescriptorModel) }
|
||||
|
||||
var fileDescriptorModel = []byte{
|
||||
// 166 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xce, 0xcd, 0x4f, 0x49,
|
||||
0xcd, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x73, 0xa4, 0x74, 0xd3, 0x33, 0x4b,
|
||||
0x32, 0x4a, 0x93, 0xf4, 0x92, 0xf3, 0x73, 0xf5, 0xd3, 0xf3, 0xd3, 0xf3, 0xf5, 0xc1, 0xb2, 0x49,
|
||||
0xa5, 0x69, 0x60, 0x1e, 0x98, 0x03, 0x66, 0x41, 0x74, 0x29, 0x39, 0x71, 0xb1, 0x3b, 0x16, 0x95,
|
||||
0x64, 0x26, 0xe7, 0xa4, 0x0a, 0x89, 0x71, 0x31, 0x79, 0xba, 0x48, 0x30, 0x2a, 0x30, 0x6a, 0x30,
|
||||
0x3b, 0xb1, 0xbd, 0xba, 0x27, 0xcf, 0x94, 0x99, 0x12, 0xc4, 0xe4, 0xe9, 0x22, 0x24, 0xcf, 0xc5,
|
||||
0x1a, 0x92, 0x59, 0x92, 0x93, 0x2a, 0xc1, 0xac, 0xc0, 0xa8, 0xc1, 0xe9, 0xc4, 0xf9, 0xea, 0x9e,
|
||||
0x3c, 0x6b, 0x09, 0x48, 0x20, 0x08, 0x22, 0xee, 0x24, 0x71, 0xe2, 0xa1, 0x1c, 0xc3, 0x85, 0x87,
|
||||
0x72, 0x0c, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, 0x1c, 0xe3, 0x8c,
|
||||
0xc7, 0x72, 0x0c, 0x49, 0x6c, 0x60, 0x4b, 0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x11, 0xa6,
|
||||
0xfa, 0x1c, 0xa9, 0x00, 0x00, 0x00,
|
||||
}
|
14
app/tool/cache/memcached/testdata/model.proto
vendored
Normal file
14
app/tool/cache/memcached/testdata/model.proto
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
syntax = "proto3";
|
||||
package model;
|
||||
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
|
||||
|
||||
option (gogoproto.goproto_enum_prefix_all) = false;
|
||||
option (gogoproto.goproto_getters_all) = false;
|
||||
option (gogoproto.unmarshaler_all) = true;
|
||||
option (gogoproto.marshaler_all) = true;
|
||||
option (gogoproto.sizer_all) = true;
|
||||
|
||||
message Article {
|
||||
int64 ID = 1 [(gogoproto.jsontag) = "id"];
|
||||
string Title = 3 [(gogoproto.jsontag) = "title"];
|
||||
}
|
127
app/tool/cache/multi_template.go
vendored
Normal file
127
app/tool/cache/multi_template.go
vendored
Normal file
@ -0,0 +1,127 @@
|
||||
package main
|
||||
|
||||
var _multiTemplate = `
|
||||
// NAME {{or .Comment "get data from cache if miss will call source method, then add to cache."}}
|
||||
func (d *Dao) NAME(c context.Context, keys []KEY{{.ExtraArgsType}}) (res map[KEY]VALUE, err error) {
|
||||
if len(keys) == 0 {
|
||||
return
|
||||
}
|
||||
addCache := true
|
||||
if res, err = CACHEFUNC(c, keys {{.ExtraCacheArgs}});err != nil {
|
||||
addCache = false
|
||||
res = nil
|
||||
err = nil
|
||||
}
|
||||
var miss []KEY
|
||||
for _, key := range keys {
|
||||
{{if .GoValue}}
|
||||
if (res == nil) || (len(res[key]) == 0) {
|
||||
{{else}}
|
||||
{{if .NumberValue}}
|
||||
if _, ok := res[key]; !ok {
|
||||
{{else}}
|
||||
if (res == nil) || (res[key] == {{.ZeroValue}}) {
|
||||
{{end}}
|
||||
{{end}}
|
||||
miss = append(miss, key)
|
||||
}
|
||||
}
|
||||
prom.CacheHit.Add("NAME", int64(len(keys) - len(miss)))
|
||||
{{if .EnableNullCache}}
|
||||
for k, v := range res {
|
||||
{{if .SimpleValue}} if v == {{.NullCache}} { {{else}} if {{.CheckNullCode}} { {{end}}
|
||||
delete(res, k)
|
||||
}
|
||||
}
|
||||
{{end}}
|
||||
missLen := len(miss)
|
||||
if missLen == 0 {
|
||||
return
|
||||
}
|
||||
{{if .EnableBatch}}
|
||||
missData := make(map[KEY]VALUE, missLen)
|
||||
{{else}}
|
||||
var missData map[KEY]VALUE
|
||||
{{end}}
|
||||
{{if .EnableSingleFlight}}
|
||||
var rr interface{}
|
||||
sf := d.cacheSFNAME(keys {{.ExtraArgs}})
|
||||
rr, err, _ = cacheSingleFlights[SFNUM].Do(sf, func() (r interface{}, e error) {
|
||||
prom.CacheMiss.Add("NAME", int64(len(miss)))
|
||||
r, e = RAWFUNC(c, miss {{.ExtraRawArgs}})
|
||||
return
|
||||
})
|
||||
missData = rr.(map[KEY]VALUE)
|
||||
{{else}}
|
||||
{{if .EnableBatch}}
|
||||
prom.CacheMiss.Add("NAME", int64(missLen))
|
||||
var mutex sync.Mutex
|
||||
{{if .BatchErrBreak}}
|
||||
group, ctx := errgroup.WithContext(c)
|
||||
{{else}}
|
||||
group := &errgroup.Group{}
|
||||
ctx := c
|
||||
{{end}}
|
||||
if missLen > MAXGROUP {
|
||||
group.GOMAXPROCS(MAXGROUP)
|
||||
}
|
||||
var run = func(ms []KEY) {
|
||||
group.Go(func() (err error) {
|
||||
data, err := RAWFUNC(ctx, ms {{.ExtraRawArgs}})
|
||||
mutex.Lock()
|
||||
for k, v := range data {
|
||||
missData[k] = v
|
||||
}
|
||||
mutex.Unlock()
|
||||
return
|
||||
})
|
||||
}
|
||||
var (
|
||||
i int
|
||||
n = missLen/GROUPSIZE
|
||||
)
|
||||
for i=0; i< n; i++{
|
||||
run(miss[i*n:(i+1)*n])
|
||||
}
|
||||
if len(miss[i*n:]) > 0 {
|
||||
run(miss[i*n:])
|
||||
}
|
||||
err = group.Wait()
|
||||
{{else}}
|
||||
prom.CacheMiss.Add("NAME", int64(len(miss)))
|
||||
missData, err = RAWFUNC(c, miss {{.ExtraRawArgs}})
|
||||
{{end}}
|
||||
{{end}}
|
||||
if res == nil {
|
||||
res = make(map[KEY]VALUE, len(keys))
|
||||
}
|
||||
for k, v := range missData {
|
||||
res[k] = v
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
{{if .EnableNullCache}}
|
||||
for _, key := range miss {
|
||||
{{if .GoValue}}
|
||||
if len(res[key]) == 0 {
|
||||
{{else}}
|
||||
if res[key] == {{.ZeroValue}} {
|
||||
{{end}}
|
||||
missData[key] = {{.NullCache}}
|
||||
}
|
||||
}
|
||||
{{end}}
|
||||
if !addCache {
|
||||
return
|
||||
}
|
||||
{{if .Sync}}
|
||||
ADDCACHEFUNC(c, missData {{.ExtraAddCacheArgs}})
|
||||
{{else}}
|
||||
d.cache.Do(c, func(c context.Context) {
|
||||
ADDCACHEFUNC(c, missData {{.ExtraAddCacheArgs}})
|
||||
})
|
||||
{{end}}
|
||||
return
|
||||
}
|
||||
`
|
65
app/tool/cache/none_template.go
vendored
Normal file
65
app/tool/cache/none_template.go
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
package main
|
||||
|
||||
var _noneTemplate = `
|
||||
// NAME {{or .Comment "get data from cache if miss will call source method, then add to cache."}}
|
||||
func (d *Dao) NAME(c context.Context) (res VALUE, err error) {
|
||||
addCache := true
|
||||
res, err = CACHEFUNC(c)
|
||||
if err != nil {
|
||||
addCache = false
|
||||
err = nil
|
||||
}
|
||||
{{if .EnableNullCache}}
|
||||
defer func() {
|
||||
{{if .SimpleValue}} if res == {{.NullCache}} { {{else}} if {{.CheckNullCode}} { {{end}}
|
||||
res = {{.ZeroValue}}
|
||||
}
|
||||
}()
|
||||
{{end}}
|
||||
{{if .GoValue}}
|
||||
if len(res) != 0 {
|
||||
{{else}}
|
||||
if res != {{.ZeroValue}} {
|
||||
{{end}}
|
||||
prom.CacheHit.Incr("NAME")
|
||||
return
|
||||
}
|
||||
{{if .EnableSingleFlight}}
|
||||
var rr interface{}
|
||||
sf := d.cacheSFNAME()
|
||||
rr, err, _ = cacheSingleFlights[SFNUM].Do(sf, func() (r interface{}, e error) {
|
||||
prom.CacheMiss.Incr("NAME")
|
||||
r, e = RAWFUNC(c)
|
||||
return
|
||||
})
|
||||
res = rr.(VALUE)
|
||||
{{else}}
|
||||
prom.CacheMiss.Incr("NAME")
|
||||
res, err = RAWFUNC(c)
|
||||
{{end}}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var miss = res
|
||||
{{if .EnableNullCache}}
|
||||
{{if .GoValue}}
|
||||
if len(miss) == 0 {
|
||||
{{else}}
|
||||
if miss == {{.ZeroValue}} {
|
||||
{{end}}
|
||||
miss = {{.NullCache}}
|
||||
}
|
||||
{{end}}
|
||||
if !addCache {
|
||||
return
|
||||
}
|
||||
{{if .Sync}}
|
||||
ADDCACHEFUNC(c, miss)
|
||||
{{else}}
|
||||
d.cache.Do(c, func(c context.Context) {
|
||||
ADDCACHEFUNC(c, miss)
|
||||
})
|
||||
{{end}}
|
||||
return
|
||||
}
|
||||
`
|
86
app/tool/cache/single_template.go
vendored
Normal file
86
app/tool/cache/single_template.go
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
package main
|
||||
|
||||
var _singleTemplate = `
|
||||
// NAME {{or .Comment "get data from cache if miss will call source method, then add to cache."}}
|
||||
func (d *Dao) NAME(c context.Context, id KEY{{.ExtraArgsType}}) (res VALUE, err error) {
|
||||
addCache := true
|
||||
res, err = CACHEFUNC(c, id {{.ExtraCacheArgs}})
|
||||
if err != nil {
|
||||
addCache = false
|
||||
err = nil
|
||||
}
|
||||
{{if .EnableNullCache}}
|
||||
defer func() {
|
||||
{{if .SimpleValue}} if res == {{.NullCache}} { {{else}} if {{.CheckNullCode}} { {{end}}
|
||||
res = {{.ZeroValue}}
|
||||
}
|
||||
}()
|
||||
{{end}}
|
||||
{{if .GoValue}}
|
||||
if len(res) != 0 {
|
||||
{{else}}
|
||||
if res != {{.ZeroValue}} {
|
||||
{{end}}
|
||||
prom.CacheHit.Incr("NAME")
|
||||
return
|
||||
}
|
||||
{{if .EnablePaging}}
|
||||
var miss VALUE
|
||||
{{end}}
|
||||
{{if .EnableSingleFlight}}
|
||||
var rr interface{}
|
||||
sf := d.cacheSFNAME(id {{.ExtraArgs}})
|
||||
rr, err, _ = cacheSingleFlights[SFNUM].Do(sf, func() (r interface{}, e error) {
|
||||
prom.CacheMiss.Incr("NAME")
|
||||
{{if .EnablePaging}}
|
||||
var rrs [2]interface{}
|
||||
rrs[0], rrs[1], e = RAWFUNC(c, id {{.ExtraRawArgs}})
|
||||
r = rrs
|
||||
{{else}}
|
||||
r, e = RAWFUNC(c, id {{.ExtraRawArgs}})
|
||||
{{end}}
|
||||
return
|
||||
})
|
||||
{{if .EnablePaging}}
|
||||
res = rr.([2]interface{})[0].(VALUE)
|
||||
miss = rr.([2]interface{})[1].(VALUE)
|
||||
{{else}}
|
||||
res = rr.(VALUE)
|
||||
{{end}}
|
||||
{{else}}
|
||||
prom.CacheMiss.Incr("NAME")
|
||||
{{if .EnablePaging}}
|
||||
res, miss, err = RAWFUNC(c, id {{.ExtraRawArgs}})
|
||||
{{else}}
|
||||
res, err = RAWFUNC(c, id {{.ExtraRawArgs}})
|
||||
{{end}}
|
||||
{{end}}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
{{if .EnablePaging}}
|
||||
{{else}}
|
||||
miss := res
|
||||
{{end}}
|
||||
{{if .EnableNullCache}}
|
||||
{{if .GoValue}}
|
||||
if len(miss) == 0 {
|
||||
{{else}}
|
||||
if miss == {{.ZeroValue}} {
|
||||
{{end}}
|
||||
miss = {{.NullCache}}
|
||||
}
|
||||
{{end}}
|
||||
if !addCache {
|
||||
return
|
||||
}
|
||||
{{if .Sync}}
|
||||
ADDCACHEFUNC(c, id, miss {{.ExtraAddCacheArgs}})
|
||||
{{else}}
|
||||
d.cache.Do(c, func(c context.Context) {
|
||||
ADDCACHEFUNC(c, id, miss {{.ExtraAddCacheArgs}})
|
||||
})
|
||||
{{end}}
|
||||
return
|
||||
}
|
||||
`
|
50
app/tool/cache/testdata/BUILD
vendored
Normal file
50
app/tool/cache/testdata/BUILD
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"dao.cache.go",
|
||||
"dao.go",
|
||||
"multi.go",
|
||||
"none.go",
|
||||
"single.go",
|
||||
],
|
||||
importpath = "go-common/app/tool/cache/testdata",
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//library/stat/prom:go_default_library",
|
||||
"//library/sync/errgroup:go_default_library",
|
||||
"//library/sync/pipeline/fanout:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"multi_test.go",
|
||||
"none_test.go",
|
||||
"single_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
rundir = ".",
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
202
app/tool/cache/testdata/dao.cache.go
vendored
Normal file
202
app/tool/cache/testdata/dao.cache.go
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
// Code generated by $GOPATH/src/go-common/app/tool/cache/gen. DO NOT EDIT.
|
||||
|
||||
/*
|
||||
Package testdata is a generated cache proxy package.
|
||||
It is generated from:
|
||||
type _cache interface {
|
||||
// cache: -batch=10 -max_group=10 -batch_err=break -nullcache=&Article{ID:-1} -check_null_code=$.ID==-1
|
||||
Articles(c context.Context, keys []int64) (map[int64]*Article, error)
|
||||
// cache: -sync=true -nullcache=&Article{ID:-1} -check_null_code=$.ID==-1
|
||||
Article(c context.Context, key int64) (*Article, error)
|
||||
// cache: -paging=true
|
||||
Article1(c context.Context, key int64, pn, ps int) (*Article, error)
|
||||
// cache: -nullcache=&Article{ID:-1} -check_null_code=$.ID==-1
|
||||
None(c context.Context) (*Article, error)
|
||||
}
|
||||
*/
|
||||
|
||||
package testdata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"go-common/library/stat/prom"
|
||||
"go-common/library/sync/errgroup"
|
||||
)
|
||||
|
||||
var _ _cache
|
||||
|
||||
// Articles get data from cache if miss will call source method, then add to cache.
|
||||
func (d *Dao) Articles(c context.Context, keys []int64) (res map[int64]*Article, err error) {
|
||||
if len(keys) == 0 {
|
||||
return
|
||||
}
|
||||
addCache := true
|
||||
if res, err = d.CacheArticles(c, keys); err != nil {
|
||||
addCache = false
|
||||
res = nil
|
||||
err = nil
|
||||
}
|
||||
var miss []int64
|
||||
for _, key := range keys {
|
||||
if (res == nil) || (res[key] == nil) {
|
||||
miss = append(miss, key)
|
||||
}
|
||||
}
|
||||
prom.CacheHit.Add("Articles", int64(len(keys)-len(miss)))
|
||||
for k, v := range res {
|
||||
if v.ID == -1 {
|
||||
delete(res, k)
|
||||
}
|
||||
}
|
||||
missLen := len(miss)
|
||||
if missLen == 0 {
|
||||
return
|
||||
}
|
||||
missData := make(map[int64]*Article, missLen)
|
||||
prom.CacheMiss.Add("Articles", int64(missLen))
|
||||
var mutex sync.Mutex
|
||||
group, ctx := errgroup.WithContext(c)
|
||||
if missLen > 10 {
|
||||
group.GOMAXPROCS(10)
|
||||
}
|
||||
var run = func(ms []int64) {
|
||||
group.Go(func() (err error) {
|
||||
data, err := d.RawArticles(ctx, ms)
|
||||
mutex.Lock()
|
||||
for k, v := range data {
|
||||
missData[k] = v
|
||||
}
|
||||
mutex.Unlock()
|
||||
return
|
||||
})
|
||||
}
|
||||
var (
|
||||
i int
|
||||
n = missLen / 10
|
||||
)
|
||||
for i = 0; i < n; i++ {
|
||||
run(miss[i*n : (i+1)*n])
|
||||
}
|
||||
if len(miss[i*n:]) > 0 {
|
||||
run(miss[i*n:])
|
||||
}
|
||||
err = group.Wait()
|
||||
if res == nil {
|
||||
res = make(map[int64]*Article, len(keys))
|
||||
}
|
||||
for k, v := range missData {
|
||||
res[k] = v
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, key := range miss {
|
||||
if res[key] == nil {
|
||||
missData[key] = &Article{ID: -1}
|
||||
}
|
||||
}
|
||||
if !addCache {
|
||||
return
|
||||
}
|
||||
d.cache.Do(c, func(c context.Context) {
|
||||
d.AddCacheArticles(c, missData)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Article get data from cache if miss will call source method, then add to cache.
|
||||
func (d *Dao) Article(c context.Context, id int64) (res *Article, err error) {
|
||||
addCache := true
|
||||
res, err = d.CacheArticle(c, id)
|
||||
if err != nil {
|
||||
addCache = false
|
||||
err = nil
|
||||
}
|
||||
defer func() {
|
||||
if res.ID == -1 {
|
||||
res = nil
|
||||
}
|
||||
}()
|
||||
if res != nil {
|
||||
prom.CacheHit.Incr("Article")
|
||||
return
|
||||
}
|
||||
prom.CacheMiss.Incr("Article")
|
||||
res, err = d.RawArticle(c, id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
miss := res
|
||||
if miss == nil {
|
||||
miss = &Article{ID: -1}
|
||||
}
|
||||
if !addCache {
|
||||
return
|
||||
}
|
||||
d.AddCacheArticle(c, id, miss)
|
||||
return
|
||||
}
|
||||
|
||||
// Article1 get data from cache if miss will call source method, then add to cache.
|
||||
func (d *Dao) Article1(c context.Context, id int64, pn, ps int) (res *Article, err error) {
|
||||
addCache := true
|
||||
res, err = d.CacheArticle1(c, id, pn, ps)
|
||||
if err != nil {
|
||||
addCache = false
|
||||
err = nil
|
||||
}
|
||||
if res != nil {
|
||||
prom.CacheHit.Incr("Article1")
|
||||
return
|
||||
}
|
||||
var miss *Article
|
||||
prom.CacheMiss.Incr("Article1")
|
||||
res, miss, err = d.RawArticle1(c, id, pn, ps)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !addCache {
|
||||
return
|
||||
}
|
||||
d.cache.Do(c, func(c context.Context) {
|
||||
d.AddCacheArticle1(c, id, miss, pn, ps)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// None get data from cache if miss will call source method, then add to cache.
|
||||
func (d *Dao) None(c context.Context) (res *Article, err error) {
|
||||
addCache := true
|
||||
res, err = d.CacheNone(c)
|
||||
if err != nil {
|
||||
addCache = false
|
||||
err = nil
|
||||
}
|
||||
defer func() {
|
||||
if res.ID == -1 {
|
||||
res = nil
|
||||
}
|
||||
}()
|
||||
if res != nil {
|
||||
prom.CacheHit.Incr("None")
|
||||
return
|
||||
}
|
||||
prom.CacheMiss.Incr("None")
|
||||
res, err = d.RawNone(c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var miss = res
|
||||
if miss == nil {
|
||||
miss = &Article{ID: -1}
|
||||
}
|
||||
if !addCache {
|
||||
return
|
||||
}
|
||||
d.cache.Do(c, func(c context.Context) {
|
||||
d.AddCacheNone(c, miss)
|
||||
})
|
||||
return
|
||||
}
|
35
app/tool/cache/testdata/dao.go
vendored
Normal file
35
app/tool/cache/testdata/dao.go
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
package testdata
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go-common/library/sync/pipeline/fanout"
|
||||
)
|
||||
|
||||
// Article test struct
|
||||
type Article struct {
|
||||
ID int64
|
||||
Title string
|
||||
}
|
||||
|
||||
// Dao .
|
||||
type Dao struct {
|
||||
cache *fanout.Fanout
|
||||
}
|
||||
|
||||
// New .
|
||||
func New() *Dao {
|
||||
return &Dao{cache: fanout.New("cache")}
|
||||
}
|
||||
|
||||
//go:generate $GOPATH/src/go-common/app/tool/cache/gen
|
||||
type _cache interface {
|
||||
// cache: -batch=10 -max_group=10 -batch_err=break -nullcache=&Article{ID:-1} -check_null_code=$.ID==-1
|
||||
Articles(c context.Context, keys []int64) (map[int64]*Article, error)
|
||||
// cache: -sync=true -nullcache=&Article{ID:-1} -check_null_code=$.ID==-1
|
||||
Article(c context.Context, key int64) (*Article, error)
|
||||
// cache: -paging=true
|
||||
Article1(c context.Context, key int64, pn, ps int) (*Article, error)
|
||||
// cache: -nullcache=&Article{ID:-1} -check_null_code=$.ID==-1
|
||||
None(c context.Context) (*Article, error)
|
||||
}
|
30
app/tool/cache/testdata/multi.go
vendored
Normal file
30
app/tool/cache/testdata/multi.go
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
package testdata
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// mock test
|
||||
var (
|
||||
_multiCacheFunc func(c context.Context, keys []int64) (map[int64]*Article, error)
|
||||
_multiRawFunc func(c context.Context, keys []int64) (map[int64]*Article, error)
|
||||
_multiAddCacheFunc func(c context.Context, values map[int64]*Article) error
|
||||
)
|
||||
|
||||
// CacheArticles .
|
||||
func (d *Dao) CacheArticles(c context.Context, keys []int64) (map[int64]*Article, error) {
|
||||
// get data from cache
|
||||
return _multiCacheFunc(c, keys)
|
||||
}
|
||||
|
||||
// RawArticles .
|
||||
func (d *Dao) RawArticles(c context.Context, keys []int64) (map[int64]*Article, error) {
|
||||
// get data from db
|
||||
return _multiRawFunc(c, keys)
|
||||
}
|
||||
|
||||
// AddCacheArticles .
|
||||
func (d *Dao) AddCacheArticles(c context.Context, values map[int64]*Article) error {
|
||||
// add to cache
|
||||
return _multiAddCacheFunc(c, values)
|
||||
}
|
67
app/tool/cache/testdata/multi_test.go
vendored
Normal file
67
app/tool/cache/testdata/multi_test.go
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
package testdata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMultiCache(t *testing.T) {
|
||||
id := int64(1)
|
||||
d := New()
|
||||
meta := map[int64]*Article{id: {ID: id}}
|
||||
getsFromCache := func(c context.Context, keys []int64) (map[int64]*Article, error) { return meta, nil }
|
||||
notGetsFromCache := func(c context.Context, keys []int64) (map[int64]*Article, error) { return nil, errors.New("err") }
|
||||
// 缓存返回了部分数据
|
||||
partFromCache := func(c context.Context, keys []int64) (map[int64]*Article, error) { return meta, errors.New("err") }
|
||||
getsFromSource := func(c context.Context, keys []int64) (map[int64]*Article, error) { return meta, nil }
|
||||
notGetsFromSource := func(c context.Context, keys []int64) (map[int64]*Article, error) {
|
||||
return meta, errors.New("err")
|
||||
}
|
||||
addToCache := func(c context.Context, values map[int64]*Article) error { return nil }
|
||||
// gets from cache
|
||||
_multiCacheFunc = getsFromCache
|
||||
_multiRawFunc = notGetsFromSource
|
||||
_multiAddCacheFunc = addToCache
|
||||
res, err := d.Articles(context.TODO(), []int64{id})
|
||||
if err != nil {
|
||||
t.Fatalf("err should be nil, get: %v", err)
|
||||
}
|
||||
if res[1].ID != 1 {
|
||||
t.Fatalf("id should be 1")
|
||||
}
|
||||
// get from source
|
||||
_multiCacheFunc = notGetsFromCache
|
||||
_multiRawFunc = getsFromSource
|
||||
res, err = d.Articles(context.TODO(), []int64{id})
|
||||
if err != nil {
|
||||
t.Fatalf("err should be nil, get: %v", err)
|
||||
}
|
||||
if res[1].ID != 1 {
|
||||
t.Fatalf("id should be 1")
|
||||
}
|
||||
// 缓存失败 返回部分数据 回源也失败的情况
|
||||
_multiCacheFunc = partFromCache
|
||||
_multiRawFunc = notGetsFromSource
|
||||
res, err = d.Articles(context.TODO(), []int64{id})
|
||||
if err == nil {
|
||||
t.Fatalf("err should be nil, get: %v", err)
|
||||
}
|
||||
if res[1].ID != 1 {
|
||||
t.Fatalf("id should be 1")
|
||||
}
|
||||
// with null cache
|
||||
nullCache := &Article{ID: -1}
|
||||
getNullFromCache := func(c context.Context, keys []int64) (map[int64]*Article, error) {
|
||||
return map[int64]*Article{id: nullCache}, nil
|
||||
}
|
||||
_multiCacheFunc = getNullFromCache
|
||||
_multiRawFunc = notGetsFromSource
|
||||
res, err = d.Articles(context.TODO(), []int64{id})
|
||||
if err != nil {
|
||||
t.Fatalf("err should be nil, get: %v", err)
|
||||
}
|
||||
if res[id] != nil {
|
||||
t.Fatalf("res should be nil")
|
||||
}
|
||||
}
|
30
app/tool/cache/testdata/none.go
vendored
Normal file
30
app/tool/cache/testdata/none.go
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
package testdata
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// mock test
|
||||
var (
|
||||
_noneCacheFunc func(c context.Context) (*Article, error)
|
||||
_noneRawFunc func(c context.Context) (*Article, error)
|
||||
_noneAddCacheFunc func(c context.Context, value *Article) error
|
||||
)
|
||||
|
||||
// CacheNone .
|
||||
func (d *Dao) CacheNone(c context.Context) (*Article, error) {
|
||||
// get data from cache
|
||||
return _noneCacheFunc(c)
|
||||
}
|
||||
|
||||
// RawNone .
|
||||
func (d *Dao) RawNone(c context.Context) (*Article, error) {
|
||||
// get data from db
|
||||
return _noneRawFunc(c)
|
||||
}
|
||||
|
||||
// AddCacheNone .
|
||||
func (d *Dao) AddCacheNone(c context.Context, value *Article) error {
|
||||
// add to cache
|
||||
return _noneAddCacheFunc(c, value)
|
||||
}
|
50
app/tool/cache/testdata/none_test.go
vendored
Normal file
50
app/tool/cache/testdata/none_test.go
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
package testdata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNoneCache(t *testing.T) {
|
||||
d := New()
|
||||
meta := &Article{ID: 1}
|
||||
getFromCache := func(c context.Context) (*Article, error) { return meta, nil }
|
||||
notGetFromCache := func(c context.Context) (*Article, error) { return nil, errors.New("err") }
|
||||
getFromSource := func(c context.Context) (*Article, error) { return meta, nil }
|
||||
notGetFromSource := func(c context.Context) (*Article, error) { return meta, errors.New("err") }
|
||||
addToCache := func(c context.Context, values *Article) error { return nil }
|
||||
// get from cache
|
||||
_noneCacheFunc = getFromCache
|
||||
_noneRawFunc = notGetFromSource
|
||||
_noneAddCacheFunc = addToCache
|
||||
res, err := d.None(context.TODO())
|
||||
if err != nil {
|
||||
t.Fatalf("err should be nil, get: %v", err)
|
||||
}
|
||||
if res.ID != 1 {
|
||||
t.Fatalf("id should be 1")
|
||||
}
|
||||
// get from source
|
||||
_noneCacheFunc = notGetFromCache
|
||||
_noneRawFunc = getFromSource
|
||||
res, err = d.None(context.TODO())
|
||||
if err != nil {
|
||||
t.Fatalf("err should be nil, get: %v", err)
|
||||
}
|
||||
if res.ID != 1 {
|
||||
t.Fatalf("id should be 1")
|
||||
}
|
||||
// with null cache
|
||||
nullCache := &Article{ID: -1}
|
||||
getNullFromCache := func(c context.Context) (*Article, error) { return nullCache, nil }
|
||||
_noneCacheFunc = getNullFromCache
|
||||
_noneRawFunc = notGetFromSource
|
||||
res, err = d.None(context.TODO())
|
||||
if err != nil {
|
||||
t.Fatalf("err should be nil, get: %v", err)
|
||||
}
|
||||
if res != nil {
|
||||
t.Fatalf("res should be nil")
|
||||
}
|
||||
}
|
48
app/tool/cache/testdata/single.go
vendored
Normal file
48
app/tool/cache/testdata/single.go
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
package testdata
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// mock test
|
||||
var (
|
||||
_singleCacheFunc func(c context.Context, key int64) (*Article, error)
|
||||
_singleRawFunc func(c context.Context, key int64) (*Article, error)
|
||||
_singleAddCacheFunc func(c context.Context, key int64, value *Article) error
|
||||
)
|
||||
|
||||
// CacheArticle .
|
||||
func (d *Dao) CacheArticle(c context.Context, key int64) (*Article, error) {
|
||||
// get data from cache
|
||||
return _singleCacheFunc(c, key)
|
||||
}
|
||||
|
||||
// RawArticle .
|
||||
func (d *Dao) RawArticle(c context.Context, key int64) (*Article, error) {
|
||||
// get data from db
|
||||
return _singleRawFunc(c, key)
|
||||
}
|
||||
|
||||
// AddCacheArticle .
|
||||
func (d *Dao) AddCacheArticle(c context.Context, key int64, value *Article) error {
|
||||
// add to cache
|
||||
return _singleAddCacheFunc(c, key, value)
|
||||
}
|
||||
|
||||
// CacheArticle1 .
|
||||
func (d *Dao) CacheArticle1(c context.Context, key int64, pn, ps int) (*Article, error) {
|
||||
// get data from cache
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// RawArticle1 .
|
||||
func (d *Dao) RawArticle1(c context.Context, key int64, pn, ps int) (*Article, *Article, error) {
|
||||
// get data from db
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// AddCacheArticle1 .
|
||||
func (d *Dao) AddCacheArticle1(c context.Context, key int64, value *Article, pn, ps int) error {
|
||||
// add to cache
|
||||
return nil
|
||||
}
|
50
app/tool/cache/testdata/single_test.go
vendored
Normal file
50
app/tool/cache/testdata/single_test.go
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
package testdata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSingleCache(t *testing.T) {
|
||||
d := New()
|
||||
meta := &Article{ID: 1}
|
||||
getFromCache := func(c context.Context, id int64) (*Article, error) { return meta, nil }
|
||||
notGetFromCache := func(c context.Context, id int64) (*Article, error) { return nil, errors.New("err") }
|
||||
getFromSource := func(c context.Context, id int64) (*Article, error) { return meta, nil }
|
||||
notGetFromSource := func(c context.Context, id int64) (*Article, error) { return meta, errors.New("err") }
|
||||
addToCache := func(c context.Context, id int64, values *Article) error { return nil }
|
||||
// get from cache
|
||||
_singleCacheFunc = getFromCache
|
||||
_singleRawFunc = notGetFromSource
|
||||
_singleAddCacheFunc = addToCache
|
||||
res, err := d.Article(context.TODO(), 1)
|
||||
if err != nil {
|
||||
t.Fatalf("err should be nil, get: %v", err)
|
||||
}
|
||||
if res.ID != 1 {
|
||||
t.Fatalf("id should be 1")
|
||||
}
|
||||
// get from source
|
||||
_singleCacheFunc = notGetFromCache
|
||||
_singleRawFunc = getFromSource
|
||||
res, err = d.Article(context.TODO(), 1)
|
||||
if err != nil {
|
||||
t.Fatalf("err should be nil, get: %v", err)
|
||||
}
|
||||
if res.ID != 1 {
|
||||
t.Fatalf("id should be 1")
|
||||
}
|
||||
// with null cache
|
||||
nullCache := &Article{ID: -1}
|
||||
getNullFromCache := func(c context.Context, id int64) (*Article, error) { return nullCache, nil }
|
||||
_singleCacheFunc = getNullFromCache
|
||||
_singleRawFunc = notGetFromSource
|
||||
res, err = d.Article(context.TODO(), 1)
|
||||
if err != nil {
|
||||
t.Fatalf("err should be nil, get: %v", err)
|
||||
}
|
||||
if res != nil {
|
||||
t.Fatalf("res should be nil")
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user