Create & Init Project...

This commit is contained in:
2019-04-22 18:49:16 +08:00
commit fc4fa37393
25440 changed files with 4054998 additions and 0 deletions

45
app/tool/cache/BUILD vendored Normal file
View 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
View 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
View File

@ -0,0 +1,8 @@
# Owner
zhapuyu
# Author
wangxu01
# Reviewer
zhapuyu

12
app/tool/cache/OWNERS vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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)

View 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
View 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 ""
}

View 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
}
`

View 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
}
`

View 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
View 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"],
)

View 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"
}

View 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()
}
}

View 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
}

View 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,
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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")
}
}