first commit

main
NoahLan 11 months ago
commit ee760242e2

23
.gitignore vendored

@ -0,0 +1,23 @@
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
.idea/
.vscode/
*/logs/
logs/
*.log
nkatago/katago/

@ -0,0 +1,129 @@
package captcha
import (
"context"
"git.noahlan.cn/noahlan/ntool-biz/core/config"
"git.noahlan.cn/noahlan/ntool/nlog"
"github.com/mojocn/base64Captcha"
"github.com/redis/go-redis/v9"
"image/color"
"time"
)
type RedisStore struct {
Expiration time.Duration
Prefix string
Context context.Context
Redis redis.Cmdable
}
// NewRedisStore returns a redis store for captcha.
func NewRedisStore(conf config.RedisConf) *RedisStore {
rdb := redis.NewClient(&redis.Options{
Network: conf.Network,
Addr: conf.Addr,
Username: conf.Username,
Password: conf.Password,
DB: conf.DB,
})
return &RedisStore{
Expiration: time.Minute * 5,
Prefix: "CAPTCHA_",
Redis: rdb,
Context: context.Background(),
}
}
// UseWithCtx add context for captcha.
func (r *RedisStore) UseWithCtx(ctx context.Context) base64Captcha.Store {
r.Context = ctx
return r
}
// Set sets the captcha KV to redis.
func (r *RedisStore) Set(id string, value string) error {
_, err := r.Redis.SetEx(r.Context, r.Prefix+id, value, r.Expiration).Result()
if err != nil {
nlog.Errorw("error occurs when captcha key sets to redis", nlog.Field("detail", err))
return err
}
return nil
}
// Get gets the captcha KV from redis.
func (r *RedisStore) Get(key string, clear bool) string {
val, err := r.Redis.Get(r.Context, key).Result()
if err != nil {
nlog.Errorw("error occurs when captcha key gets from redis", nlog.Field("detail", err))
return ""
}
if clear {
_, err := r.Redis.Del(r.Context, key).Result()
if err != nil {
nlog.Errorw("error occurs when captcha key deletes from redis", nlog.Field("detail", err))
return ""
}
}
return val
}
// Verify verifies the captcha whether it is correct.
func (r *RedisStore) Verify(id, answer string, clear bool) bool {
key := r.Prefix + id
v := r.Get(key, clear)
return v == answer
}
// MustNewRedisCaptcha returns the captcha using redis, it will panic when error occur
func MustNewRedisCaptcha(c Config, redisConf config.RedisConf) *base64Captcha.Captcha {
driver := NewDriver(c)
store := NewRedisStore(redisConf)
return base64Captcha.NewCaptcha(driver, store)
}
func NewDriver(c Config) base64Captcha.Driver {
var driver base64Captcha.Driver
bgColor := &color.RGBA{
R: 254,
G: 254,
B: 254,
A: 254,
}
fonts := []string{
"3Dumb.ttf",
"ApothecaryFont.ttf",
"Comismsh.ttf",
"DENNEthree-dee.ttf",
"DeborahFancyDress.ttf",
"Flim-Flam.ttf",
"RitaSmith.ttf",
"actionj.ttf",
"chromohv.ttf",
}
switch c.Driver {
case "digit":
driver = base64Captcha.NewDriverDigit(c.ImgHeight, c.ImgWidth,
c.KeyLen, 0.7, 80)
case "string":
driver = base64Captcha.NewDriverString(c.ImgHeight, c.ImgWidth, 0, 0, c.KeyLen,
"qwertyupasdfghjkzxcvbnm23456789",
bgColor, nil, fonts)
case "math":
driver = base64Captcha.NewDriverMath(c.ImgHeight, c.ImgWidth, 0, 0, bgColor,
nil, fonts)
case "chinese":
// TODO 读取中文列表
driver = base64Captcha.NewDriverChinese(c.ImgHeight, c.ImgWidth, 0, 0, c.KeyLen,
"埃里克森等级分类三级独立开发叫我阿胶发凯撒就看来附件为投靠子女的咖啡",
bgColor, nil, fonts)
default:
driver = base64Captcha.NewDriverDigit(c.ImgHeight, c.ImgWidth,
c.KeyLen, 0.7, 80)
}
return driver
}

@ -0,0 +1,8 @@
package captcha
type Config struct {
KeyLen int `json:",optional,default=4,env=CAPTCHA_KEY_LEN"` // captcha length
ImgWidth int `json:",optional,default=240,env=CAPTCHA_IMG_WIDTH"` // captcha width
ImgHeight int `json:"optional,default=80,env=CAPTCHA_IMG_HEIGHT"` // captcha height
Driver string `json:",optional,default=string,options=[digit,string,math,chinese],env=CAPTCHA_DRIVER"` // captcha type
}

@ -0,0 +1,104 @@
package casbin
import (
"git.noahlan.cn/noahlan/ntool-biz/core/config"
"git.noahlan.cn/noahlan/ntool/nlog"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
"github.com/casbin/casbin/v2/persist"
entadapter "github.com/casbin/ent-adapter"
rediswatcher "github.com/casbin/redis-watcher/v2"
redis2 "github.com/redis/go-redis/v9"
)
type CasbinConf struct {
ModelText string `json:"ModelText,optional"`
}
func (l CasbinConf) NewCasbin(dbType, dsn string) (*casbin.Enforcer, error) {
adapter, err := entadapter.NewAdapter(dbType, dsn)
if err != nil {
nlog.Errorf("NCasbin new ent-adapter err: %v", err)
return nil, err
}
text := l.ModelText
if l.ModelText == "" {
text = `
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && keyMatch2(r.obj,p.obj) && r.act == p.act
`
}
m, err := model.NewModelFromString(text)
if err != nil {
nlog.Errorf("NCasbin new Model err: %v", err)
return nil, err
}
enforcer, err := casbin.NewEnforcer(m, adapter)
if err != nil {
nlog.Errorf("NCasbin NewEnforcer err: %v", err)
return nil, err
}
err = enforcer.LoadPolicy()
if err != nil {
nlog.Errorf("NCasbin LoadPolicy err: %v", err)
return nil, err
}
return enforcer, nil
}
func (l CasbinConf) MustNewCasbin(dbType, dsn string) *casbin.Enforcer {
csb, err := l.NewCasbin(dbType, dsn)
nlog.Must(err)
return csb
}
// MustNewRedisWatcher returns redis watcher. If there are errors, it will exist.
// f function will be called if the policies are updated.
func (l CasbinConf) MustNewRedisWatcher(c config.RedisConf, f func(string2 string)) persist.Watcher {
w, err := rediswatcher.NewWatcher(c.Addr, rediswatcher.WatcherOptions{
Options: redis2.Options{
Network: c.Network,
Password: c.Password,
},
Channel: "/casbin",
IgnoreSelf: false,
})
nlog.Must(err)
err = w.SetUpdateCallback(f)
nlog.Must(err)
return w
}
// MustNewCasbinWithRedisWatcher returns Casbin Enforcer with Redis watcher.
func (l CasbinConf) MustNewCasbinWithRedisWatcher(dbType, dsn string, c config.RedisConf) *casbin.Enforcer {
cbn := l.MustNewCasbin(dbType, dsn)
w := l.MustNewRedisWatcher(c, func(data string) {
rediswatcher.DefaultUpdateCallback(cbn)(data)
})
err := cbn.SetWatcher(w)
nlog.Must(err)
err = cbn.SavePolicy()
nlog.Must(err)
return cbn
}

@ -0,0 +1,7 @@
package config
// AuthConfig 登录认证配置
type AuthConfig struct {
AccessSecret string `json:",default=northlan,env=ACCESS_SECRET"` // Secret
AccessExpire int64 `json:",default=604800,env=ACCESS_EXPIRE"` // Expire in seconds
}

@ -0,0 +1,26 @@
package config
// RedisConf redis连接配置
type RedisConf struct {
// The network type, either tcp or unix.
// Default is tcp.
Network string `json:",default=tcp,options=tcp|unix,env=REDIS_NETWORK"`
// host:port address.
Addr string `json:",env=REDIS_ADDR"`
// Use the specified Username to authenticate the current connection
// with one of the connections defined in the ACL list when connecting
// to a Redis 6.0 instance, or greater, that is using the Redis ACL system.
Username string `json:",optional,env=REDIS_USERNAME"`
// Optional password. Must match the password specified in the
// requirepass server configuration option (if connecting to a Redis 5.0 instance, or lower),
// or the User Password when connecting to a Redis 6.0 instance, or greater,
// that is using the Redis ACL system.
Password string `json:",optional,env=REDIS_PASSWORD"`
Type string `json:",default=node,options=node|cluster"`
// Database to be selected after connecting to the server.
DB int `json:",optional,env=REDIS_DB"`
Tls bool `json:",optional"`
Key string `json:",optional"`
}

@ -0,0 +1,13 @@
package i18n
// Config i18n configuration
type Config struct {
// Default default language
Default string `json:",optional,default=zh"`
// RootPath root path for search and watching
RootPath string `json:",optional,default=locale"`
// SortedParameterPrefix prefix of sorted parameters.
SortedParameterPrefix string `json:",optional,default=p"`
}

@ -0,0 +1,54 @@
package i18n
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
// UnaryServerInterceptor grpc i18n 服务端拦截器
// 客户端将通过metadata传递其lang
// 将语言Key (lang) 从 grpc-metadata 中取出并注入到context中
// see github.com\zeromicro\go-zero\zrpc\server.go#AddUnaryInterceptors
// see google.golang.org\grpc\interceptor.go
func UnaryServerInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
var md metadata.MD
requestMetadata, ok := metadata.FromIncomingContext(ctx)
if ok {
md = requestMetadata.Copy()
} else {
md = metadata.MD{}
}
lngs := md.Get(KeyLang)
if len(lngs) > 0 {
lang := lngs[0]
if len(lang) > 0 {
ctx = context.WithValue(ctx, KeyLang, lang)
}
}
resp, err = handler(ctx, req)
return
}
}
// UnaryClientInterceptor i18n grpc 客户端拦截器
// 将context中的lang注入到metadata中传递给服务端
// see google.golang.org\grpc\interceptor.go
func UnaryClientInterceptor() grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
var md metadata.MD
requestMetadata, ok := metadata.FromOutgoingContext(ctx)
if ok {
md = requestMetadata.Copy()
} else {
md = metadata.MD{}
}
if lang, ok := ctx.Value(KeyLang).(string); ok && len(lang) > 0 {
md.Set(KeyLang, lang)
}
ctx = metadata.NewOutgoingContext(ctx, md)
return invoker(ctx, method, req, reply, cc, opts...)
}
}

@ -0,0 +1,63 @@
package i18n
import (
"context"
"net/http"
)
type HttpKeyConfig struct {
ParamKey string `json:",optional,default=lang"` // 参数中的语言Key
HeaderKey string `json:",optional,default=X-LANG"` // Header中的语言Key
}
var (
paramKey = "lang"
headerKey = "X-LANG"
)
// WithHttpConfig 基于配置文件设置Keys
func WithHttpConfig(config HttpKeyConfig) {
WithHttpParamKey(config.ParamKey)
WithHttpHeaderKey(config.HeaderKey)
}
// WithHttpParamKey 设置参数key
func WithHttpParamKey(key string) {
paramKey = key
}
// WithHttpHeaderKey 设置头Key
func WithHttpHeaderKey(key string) {
headerKey = key
}
// WithRequest 包装 http.Request, 接受客户端传递的lang并存入其 context 中
func WithRequest(r *http.Request) *http.Request {
paramLang := r.FormValue(paramKey)
headerLang := r.Header.Get(headerKey)
acceptLanguage := r.Header.Get(KeyAcceptLanguage)
ConfigLanguages(acceptLanguage, paramLang, headerLang)
// 参数中的lang优先级大于header中
lang := headerLang
if len(paramLang) != 0 {
lang = paramLang
}
ctx := r.Context()
if len(lang) > 0 {
ctx = context.WithValue(ctx, KeyLang, lang)
}
return r.WithContext(ctx)
}
// WithI18nMiddleware i18n中间件
func WithI18nMiddleware() func(next http.HandlerFunc) http.HandlerFunc {
return func(next http.HandlerFunc) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
req := WithRequest(request)
next.ServeHTTP(writer, req)
}
}
}

@ -0,0 +1,131 @@
package i18n
import (
"context"
"git.noahlan.cn/noahlan/ntool/nlog"
"golang.org/x/text/language"
)
var atTranslator *Translator
func getTranslator() *Translator {
if atTranslator == nil {
atTranslator = NewTranslator()
}
return atTranslator
}
// InitWithConfig 通过配置文件初始化I18N
func InitWithConfig(c Config) {
Init(
WithDefaultLanguage(c.Default),
WithRootPath(c.RootPath),
WithSortedParameterPrefix(c.SortedParameterPrefix),
)
}
// Init 初始化I18N
func Init(opts ...Option) {
atTranslator = NewTranslator(opts...)
}
// ResetOptions 重置Option
func ResetOptions(opts ...Option) {
for _, opt := range opts {
opt(getTranslator())
}
}
// WithDefaultLanguage 设置默认语言
func WithDefaultLanguage(lng string) Option {
return func(translator *Translator) {
tag, err := language.Parse(lng)
if err != nil {
nlog.Errorf("parse lang err: %v", err)
return
}
translator.DefaultLanguage = tag
}
}
// WithRootPath 设置监听根目录
func WithRootPath(rootPath string) Option {
return func(translator *Translator) {
translator.RootPath = rootPath
}
}
// WithFileLoader 设置文件加载器,一般使用默认值即可
func WithFileLoader(loader Loader) Option {
return func(translator *Translator) {
translator.FileLoader = loader
}
}
// WithTagGetter 设置动态语言Tag获取器
func WithTagGetter(getter TagGetter) Option {
return func(translator *Translator) {
translator.TagGetter = getter
}
}
// WithLocalizeConfigGetter 设置TransAny的LocalizeConfig获取器默认会处理string和LocalizeConfig类型
func WithLocalizeConfigGetter(getter LocalizeConfigGetter) Option {
return func(translator *Translator) {
translator.LocalizeConfigGetter = getter
}
}
// WithSortedParameterPrefix 设置自排序参数前缀,默认为'p'
func WithSortedParameterPrefix(prefix string) Option {
return func(translator *Translator) {
translator.SortedParameterPrefix = prefix
}
}
// ConfigLanguages 动态配置acceptLanguages
func ConfigLanguages(acceptLanguages ...string) {
getTranslator().configLocalizer(acceptLanguages...)
}
// Trans by message id
func Trans(ctx context.Context, messageID string) string {
return getTranslator().Trans(ctx, messageID)
}
// TransWithParam by sorted string parameters
// params can be string or map[string]any
// all params must be same.
func TransWithParam(ctx context.Context, messageID string, params ...any) string {
if len(params) == 0 {
return messageID
}
p := &MessageWithParameter{MessageID: messageID}
switch t := params[0].(type) {
case map[string]any:
p.ParamsWithName = t
default:
p.Params = params
}
msg, err := getTranslator().TransAny(ctx, p)
if err != nil || msg == "" {
return messageID
}
return msg
}
// TransAny by any parameter
/**
param is one of these type: messageID, *i18n.LocalizeConfig
Example:
MustGetMessage("hello") // messageID is hello
MustGetMessage(&i18n.LocalizeConfig{
MessageID: "welcomeWithName",
TemplateData: map[string]string{
"name": context.Param("name"),
},
})
*/
func TransAny(ctx context.Context, param any) (string, error) {
return getTranslator().TransAny(ctx, param)
}

@ -0,0 +1,26 @@
package i18n_test
import (
"context"
"git.noahlan.cn/noahlan/ntool-biz/core/i18n"
"git.noahlan.cn/noahlan/ntool/ntest/assert"
"testing"
)
func TestTrans(t *testing.T) {
ctx := context.Background()
result1 := i18n.Trans(ctx, "success")
result2 := i18n.Trans(context.WithValue(ctx, i18n.KeyLang, "en"), "success")
assert.Eq(t, "成功", result1)
assert.Eq(t, "Successfully", result2)
}
func TestTransWithParam(t *testing.T) {
ctx := context.Background()
result1 := i18n.TransWithParam(ctx, "hello", "王二", "麻子")
assert.Eq(t, "王二麻子", result1)
}

@ -0,0 +1,3 @@
{
"success": "Successfully"
}

@ -0,0 +1,4 @@
{
"success": "成功",
"hello": "{{.p0}}{{.p1}}"
}

@ -0,0 +1,40 @@
package i18n
import (
"context"
"github.com/nicksnyder/go-i18n/v2/i18n"
"golang.org/x/text/language"
)
type (
BundleConfig struct {
DefaultLanguage language.Tag // 默认语言
RootPath string // 语言文件根路径
FileLoader Loader // 文件读取器
SortedParameterPrefix string // 自排序参数前缀
TagGetter TagGetter
LocalizeConfigGetter LocalizeConfigGetter
}
// MessageWithParameter 带参消息
MessageWithParameter struct {
MessageID string // 消息ID
Params []any // 自排序参数
ParamsWithName map[string]any // 带名参数
}
// TagGetter 当前language获取器
TagGetter func(context context.Context) string
LocalizeConfigGetter func(config BundleConfig, param any) *i18n.LocalizeConfig
Loader interface {
LoadMessage(path string) ([]byte, error)
}
LoaderFunc func(path string) ([]byte, error)
)
func (f LoaderFunc) LoadMessage(path string) ([]byte, error) {
return f(path)
}

@ -0,0 +1,316 @@
package i18n
import (
"context"
"encoding/json"
"errors"
"fmt"
"git.noahlan.cn/noahlan/ntool/nlog"
"github.com/BurntSushi/toml"
"github.com/nicksnyder/go-i18n/v2/i18n"
"github.com/radovskyb/watcher"
"golang.org/x/text/language"
"gopkg.in/yaml.v3"
"os"
"strings"
"sync"
"time"
)
// Translator is a struct storing translating data.
type Translator struct {
BundleConfig
bundle *i18n.Bundle
localizerMap map[string]*i18n.Localizer
supportedFormat []string
mu sync.RWMutex
}
type Option func(*Translator)
func NewTranslator(opts ...Option) *Translator {
ret := &Translator{
localizerMap: make(map[string]*i18n.Localizer),
BundleConfig: defaultBundleConfig,
}
for _, opt := range opts {
opt(ret)
}
ret.bundle = i18n.NewBundle(ret.DefaultLanguage)
ret.RegisterUnmarshalFunc("json", json.Unmarshal)
ret.RegisterUnmarshalFunc("toml", toml.Unmarshal)
ret.RegisterUnmarshalFunc("yaml", yaml.Unmarshal)
// init and watcher
files := ret.watcher()
lngs := make([]string, len(files))
for i, file := range files {
lngs[i] = filenameWithoutExt(file.Name)
if err := ret.loadFileByPath(file.Path); err != nil {
nlog.Debugf("load locale file err: %v\n", err)
}
}
ret.configLocalizer(lngs...)
return ret
}
func (l *Translator) RegisterUnmarshalFunc(format string, fn i18n.UnmarshalFunc) {
if l.supportedFormat == nil {
l.supportedFormat = make([]string, 0)
}
l.supportedFormat = append(l.supportedFormat, format)
l.bundle.RegisterUnmarshalFunc(format, fn)
}
// Trans translates the message of message's ID
func (l *Translator) Trans(ctx context.Context, messageID string) string {
s, err := l.TransAny(ctx, messageID)
if err != nil || s == "" {
return messageID
}
return s
}
// TransAny translates the message of any param
func (l *Translator) TransAny(ctx context.Context, param any) (string, error) {
lngTag := l.TagGetter(ctx)
if lngTag == "" {
lngTag = l.DefaultLanguage.String()
}
localizer := l.getLocalizerByTag(lngTag)
config, err := l.getLocalizeConfig(param)
if err != nil {
return "", err
}
message, err := localizer.Localize(config)
if err != nil {
return "", err
}
return message, nil
}
func (l *Translator) loadFileByPath(path string) error {
buf, err := l.FileLoader.LoadMessage(path)
if err != nil {
return err
}
if _, err = l.bundle.ParseMessageFileBytes(buf, path); err != nil {
return err
}
return nil
}
// watcher 监控给定Path的文件数据变化实时更改localizer以实现语言文件热更新
func (l *Translator) watcher() []struct {
Path string
Name string
} {
w := watcher.New()
w.FilterOps(watcher.Create, watcher.Remove, watcher.Write, watcher.Rename)
w.AddFilterHook(func(info os.FileInfo, fullPath string) error {
if info.IsDir() {
return watcher.ErrSkip
}
if info.Size() <= 0 {
return watcher.ErrSkip
}
name := info.Name()
for _, format := range l.supportedFormat {
if strings.HasSuffix(name, format) {
return nil
}
}
// 文件后缀不支持,直接跳过
return watcher.ErrSkip
})
go func() {
for {
select {
case event := <-w.Event:
if event.IsDir() {
continue
}
filePrefix := filenameWithoutExt(event.Name())
lang, err := language.Parse(filePrefix)
if err != nil {
nlog.Debugf("cannot parse filename:%s to language.Tag, process will continue.", filePrefix)
continue
}
path := event.Path
switch event.Op {
case watcher.Remove:
l.removeLocalizer(lang.String())
case watcher.Create, watcher.Write:
if err := l.loadFileByPath(path); err != nil {
nlog.Debugf("load locale file err [Create] or [Write], %+v", err)
continue
}
l.configLocalizer(lang.String())
case watcher.Rename:
l.removeLocalizer(lang.String())
newLangStr := filenameWithoutExt(path)
newLang, err := language.Parse(newLangStr)
if err != nil {
continue
}
if err := l.loadFileByPath(path); err != nil {
nlog.Debugf("load locale file err [Rename], %+v", err)
continue
}
l.configLocalizer(newLang.String())
}
case err := <-w.Error:
nlog.Errorf("watch locale file err: %+v", err)
case <-w.Closed:
nlog.Debugf("i18n watcher closed...")
return
}
}
}()
if err := w.Add(l.RootPath); err != nil {
nlog.Errorf("[i18n] watching %s err:%v", l.RootPath, err)
w.Closed <- struct{}{}
return nil
}
go func() {
_ = w.Start(time.Second * 5)
}()
watchedFiles := w.WatchedFiles()
ret := make([]struct {
Path string
Name string
}, 0, len(watchedFiles))
for path, info := range watchedFiles {
if info.IsDir() {
continue
}
if info.Size() <= 0 {
continue
}
name := info.Name()
for _, format := range l.supportedFormat {
if strings.HasSuffix(name, format) {
ret = append(ret, struct {
Path string
Name string
}{Path: path, Name: info.Name()})
}
}
}
return ret
}
// getLocalizerByTag get localizer by language
func (l *Translator) getLocalizerByTag(tag string) *i18n.Localizer {
l.mu.RLock()
defer l.mu.RUnlock()
if localizer, ok := l.localizerMap[tag]; ok {
return localizer
}
return l.localizerMap[l.DefaultLanguage.String()]
}
// getLocalizerByTag get localizer by language
func (l *Translator) getLocalizeConfig(param any) (*i18n.LocalizeConfig, error) {
switch t := param.(type) {
case string:
return &i18n.LocalizeConfig{
DefaultMessage: &i18n.Message{ID: t},
}, nil
case *MessageWithParameter:
// 转化 template data
return &i18n.LocalizeConfig{
TemplateData: l.buildTemplateData(t.Params, t.ParamsWithName),
DefaultMessage: &i18n.Message{ID: t.MessageID},
}, nil
case *i18n.LocalizeConfig:
return t, nil
}
// extends localizerConfig getter
if ret := l.LocalizeConfigGetter(l.BundleConfig, param); ret != nil {
return ret, nil
}
return nil, errors.New(fmt.Sprintf("un supported localize param: %v", param))
}
// buildTemplateData 构建TemplateData
func (l *Translator) buildTemplateData(params []any, paramsWithName map[string]any) map[string]any {
if len(params) == 0 {
return paramsWithName
}
data := make(map[string]any)
for i, param := range params {
data[fmt.Sprintf("%s%d", l.SortedParameterPrefix, i)] = param
}
return data
}
// configLocalizer set localizer by language
func (l *Translator) configLocalizer(acceptLanguage ...string) {
l.mu.Lock()
defer l.mu.Unlock()
for _, lng := range acceptLanguage {
if lng == "" {
continue
}
if _, ok := l.localizerMap[lng]; !ok {
l.localizerMap[lng] = l.newLocalizer(lng)
}
}
// configure default
defaultLng := l.DefaultLanguage.String()
if _, ok := l.localizerMap[defaultLng]; !ok {
l.localizerMap[defaultLng] = l.newLocalizer(defaultLng)
}
}
// removeLocalizer remove localizer by tag string
func (l *Translator) removeLocalizer(lang ...string) {
l.mu.Lock()
defer l.mu.Unlock()
for _, lng := range lang {
delete(l.localizerMap, lng)
}
}
// newLocalizer create a localizer by language
func (l *Translator) newLocalizer(lng string) *i18n.Localizer {
lngDefault := l.DefaultLanguage.String()
lngs := []string{lng}
if lng != lngDefault {
lngs = append(lngs, lngDefault)
}
localizer := i18n.NewLocalizer(
l.bundle,
lngs...,
)
return localizer
}

@ -0,0 +1,10 @@
package i18n
import (
"git.noahlan.cn/noahlan/ntool/nfs"
"strings"
)
func filenameWithoutExt(path string) string {
return strings.ReplaceAll(nfs.Name(path), nfs.Ext(path), "")
}

@ -0,0 +1,37 @@
package i18n
import (
"context"
"github.com/nicksnyder/go-i18n/v2/i18n"
"golang.org/x/text/language"
"os"
)
const (
KeyLang = "lang"
KeyAcceptLanguage = "Accept-Language"
defaultRootPath = "locale"
)
var (
defaultLanguage = language.Chinese
defaultBundleConfig = BundleConfig{
DefaultLanguage: defaultLanguage,
RootPath: defaultRootPath,
FileLoader: LoaderFunc(os.ReadFile),
//
SortedParameterPrefix: "p",
//
TagGetter: func(context context.Context) string {
if v, ok := context.Value(KeyLang).(string); ok {
return v
}
return defaultLanguage.String()
},
LocalizeConfigGetter: func(config BundleConfig, param any) *i18n.LocalizeConfig {
return nil
},
}
)

@ -0,0 +1,69 @@
package jwt
import (
"git.noahlan.cn/noahlan/ntool/nrandom"
"github.com/golang-jwt/jwt/v5"
"strconv"
"time"
)
type (
ClaimStrings = jwt.ClaimStrings
Claims = jwt.Claims
SigningMethod = jwt.SigningMethod
)
type (
Option struct {
Key string
Val any
}
Token struct {
AccessToken string
ExpiresAt int64
}
)
func WithOption(key string, val any) Option {
return Option{key, val}
}
func WithID(id string) Option {
return WithOption("jti", id)
}
func WithRandID() Option {
return WithID(nrandom.SnowflakeIdStr())
}
func WithAudience(val jwt.ClaimStrings) Option {
return WithOption("aud", val)
}
func NewJwtToken(secretKey string, uid, expiresIn int64, opts ...Option) (Token, error) {
iat := time.Now().Unix()
claims := make(jwt.MapClaims)
claims["iat"] = iat // 签发时间
claims["exp"] = iat + expiresIn // 过期时间
claims["iss"] = "lan6995@gmail.com" // 签发者
claims["sub"] = strconv.FormatInt(uid, 10) // subject 面向用户
//claims["aud"] = "" // 接收jwt的
claims["nbf"] = iat // 在xx时间之前该jwt不可用
claims[KeyUserId] = uid // 自定义字段 uid
for _, opt := range opts {
claims[opt.Key] = opt.Val
}
token := jwt.New(jwt.SigningMethodHS256)
token.Claims = claims
jwtStr, err := token.SignedString([]byte(secretKey))
result := Token{
AccessToken: jwtStr,
ExpiresAt: iat + expiresIn,
}
if err != nil {
return result, err
}
return result, nil
}

@ -0,0 +1,22 @@
package jwt
import (
"context"
"encoding/json"
"git.noahlan.cn/noahlan/ntool/nlog"
)
// GetCurrentUserId 获取当前用户ID
func GetCurrentUserId(ctx context.Context) int64 {
var (
uid int64
err error
)
if jsonUid, ok := ctx.Value(KeyUserId).(json.Number); ok {
uid, err = jsonUid.Int64()
if err != nil {
nlog.WithContext(ctx).Errorw("get current userId err", nlog.Field("details", err))
}
}
return uid
}

@ -0,0 +1,7 @@
package jwt
const (
KeyUserId = "uid"
KeyRoleIds = "roles"
KeyDeptIds = "depts"
)

@ -0,0 +1,37 @@
package kafka
import (
"github.com/Shopify/sarama"
)
type Consumer struct {
client sarama.Client
topic string
consumer sarama.Consumer
partitions []int32
}
func NewKafkaConsumer(addr []string, topic string) (*Consumer, error) {
p := Consumer{}
p.topic = topic
config := sarama.NewConfig()
config.Version = sarama.V3_1_0_0
config.Consumer.Offsets.Initial = sarama.OffsetNewest
var err error
p.client, err = sarama.NewClient(addr, config)
if err != nil {
return nil, err
}
p.consumer, err = sarama.NewConsumerFromClient(p.client)
if err != nil {
return nil, err
}
p.partitions, err = p.consumer.Partitions(topic)
if err != nil {
return nil, err
}
return &p, nil
}

@ -0,0 +1,53 @@
package kafka
import (
"context"
"git.noahlan.cn/noahlan/ntool/ndef"
"github.com/Shopify/sarama"
)
type ConsumerGroup struct {
sarama.ConsumerGroup
groupId string
topics []string
unMarshaler ndef.Unmarshaler
}
type ConsumerGroupConfig struct {
KafkaVersion sarama.KafkaVersion
OffsetsInitial int64
IsReturnErr bool
UnMarshaler ndef.Unmarshaler
}
func NewConsumerGroup(config *ConsumerGroupConfig, addr, topics []string, groupId string) (*ConsumerGroup, error) {
c := sarama.NewConfig()
c.Version = config.KafkaVersion
c.Consumer.Offsets.Initial = config.OffsetsInitial
c.Consumer.Return.Errors = config.IsReturnErr
client, err := sarama.NewClient(addr, c)
if err != nil {
return nil, err
}
consumerGroup, err := sarama.NewConsumerGroupFromClient(groupId, client)
if err != nil {
return nil, err
}
return &ConsumerGroup{
ConsumerGroup: consumerGroup,
groupId: groupId,
topics: topics,
unMarshaler: config.UnMarshaler,
}, nil
}
func (cg *ConsumerGroup) RegisterHandlerAndConsumer(handler sarama.ConsumerGroupHandler) {
ctx := context.Background()
for {
err := cg.ConsumerGroup.Consume(ctx, cg.topics, handler)
if err != nil {
}
}
}

@ -0,0 +1,70 @@
package kafka
import (
"git.noahlan.cn/noahlan/ntool/ndef"
"github.com/Shopify/sarama"
)
type Producer struct {
topic string
client sarama.Client
producer sarama.AsyncProducer
marshaler ndef.Marshaler
}
type ProducerConfig struct {
RequiredAcks sarama.RequiredAcks
Partitioner sarama.PartitionerConstructor
IsReturnSuccess bool
Marshaler ndef.Marshaler
}
func NewKafkaProducer(cfg *ProducerConfig, addr []string, topic string) *Producer {
p := Producer{}
config := sarama.NewConfig() // Instantiate a sarama Config
config.Producer.Return.Successes = cfg.IsReturnSuccess // Whether to enable the successes channel to be notified after the message is sent successfully
config.Producer.RequiredAcks = cfg.RequiredAcks // Set producer Message Reply level 0 1 all
config.Producer.Partitioner = cfg.Partitioner // Set the hash-key automatic hash partition. When sending a message, you must specify the key value of the message. If there is no key, the partition will be selected randomly
p.marshaler = cfg.Marshaler
p.topic = topic
var err error
p.client, err = sarama.NewClient(addr, config)
if err != nil {
return &p
}
p.producer, err = sarama.NewAsyncProducerFromClient(p.client)
if err != nil {
return &p
}
go func() {
for range p.producer.Successes() {
}
}()
return &p
}
func (p *Producer) SendMessageAsync(v interface{}, key ...string) error {
kMsg := &sarama.ProducerMessage{}
kMsg.Topic = p.topic
if len(key) > 0 {
kMsg.Key = sarama.StringEncoder(key[0])
}
if str, ok := v.(string); ok {
kMsg.Value = sarama.StringEncoder(str)
} else if p.marshaler != nil {
marshal, err := p.marshaler.Marshal(v)
if err != nil {
return err
}
kMsg.Value = sarama.ByteEncoder(marshal)
}
select {
case p.producer.Input() <- kMsg:
}
return nil
}

@ -0,0 +1,35 @@
package nstatus
import "git.noahlan.cn/noahlan/ntool-biz/core/nstatus/code"
func NewApiErr(code int, message string) error {
return NewResult(code, message)
}
func NewApiErrWithCode(code int) error {
return NewApiErr(code, "")
}
func NewApiInternalErr(msg string) error {
return NewApiErr(code.StatusInternalServerError, msg)
}
func NewApiBadRequestErr(msg string) error {
return NewApiErr(code.StatusBadRequest, msg)
}
func NewApiUnauthorizedErr(msg string) error {
return NewApiErr(code.StatusUnauthorized, msg)
}
func NewApiForbiddenErr(msg string) error {
return NewApiErr(code.StatusForbidden, msg)
}
func NewApiNotFoundErr(msg string) error {
return NewApiErr(code.StatusNotFound, msg)
}
func NewApiBadGatewayErr(msg string) error {
return NewApiErr(code.StatusBadGateway, msg)
}

@ -0,0 +1,23 @@
package code
import "net/http"
const (
StatusUnknown int = -1
StatusOK = http.StatusOK
StatusFailed = 0
StatusBadRequest = http.StatusBadRequest
StatusUnauthorized = http.StatusUnauthorized
StatusForbidden = http.StatusForbidden
StatusNotFound = http.StatusNotFound
StatusRequestTimeout = http.StatusRequestTimeout
StatusConflict = http.StatusConflict
StatusTooManyRequests = http.StatusTooManyRequests
StatusInternalServerError = http.StatusInternalServerError
StatusNotImplemented = http.StatusNotImplemented
StatusBadGateway = http.StatusBadGateway
StatusServiceUnavailable = http.StatusServiceUnavailable
StatusGatewayTimeout = http.StatusGatewayTimeout
)

@ -0,0 +1,86 @@
package nstatus
import (
"git.noahlan.cn/noahlan/ntool-biz/core/nstatus/code"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
var table = map[codes.Code]int{
codes.OK: code.StatusOK,
codes.Canceled: code.StatusRequestTimeout,
codes.Unknown: code.StatusInternalServerError,
codes.InvalidArgument: code.StatusBadRequest,
codes.DeadlineExceeded: code.StatusGatewayTimeout,
codes.NotFound: code.StatusNotFound,
codes.AlreadyExists: code.StatusConflict,
codes.PermissionDenied: code.StatusForbidden,
codes.ResourceExhausted: code.StatusTooManyRequests,
codes.FailedPrecondition: code.StatusBadRequest,
codes.Aborted: code.StatusConflict,
codes.OutOfRange: code.StatusBadRequest,
codes.Unimplemented: code.StatusNotImplemented,
codes.Internal: code.StatusInternalServerError,
codes.Unavailable: code.StatusServiceUnavailable,
codes.DataLoss: code.StatusInternalServerError,
codes.Unauthenticated: code.StatusUnauthorized,
}
// WrapGrpcErr 将grpc-error转换为 Result
func WrapGrpcErr(err error) *Result {
gErr := status.Convert(err)
if gErr == nil {
return nil
}
if c, ok := table[gErr.Code()]; ok {
return NewResult(c, gErr.Message())
}
return NewResult(int(gErr.Code()), gErr.Message())
}
// IsGrpcErr return an error if grpc status
func IsGrpcErr(err error) bool {
if err == nil {
return false
}
_, ok := err.(interface {
GRPCStatus() *status.Status
})
return ok
}
func NewGrpcErr(code codes.Code, msg string) error {
return NewResult(int(code), msg)
}
// NewGrpcInternalErr returns status error with Internal error code.
func NewGrpcInternalErr(msg string) error {
return NewGrpcErr(codes.Internal, msg)
}
// NewGrpcInvalidArgumentErr returns status error with InvalidArgument error code.
func NewGrpcInvalidArgumentErr(msg string) error {
return NewGrpcErr(codes.InvalidArgument, msg)
}
// NewGrpcNotFoundErr returns status error with NotFound error code.
func NewGrpcNotFoundErr(msg string) error {
return NewGrpcErr(codes.NotFound, msg)
}
// NewGrpcAlreadyExistsErr returns status error with AlreadyExists error code.
func NewGrpcAlreadyExistsErr(msg string) error {
return NewGrpcErr(codes.AlreadyExists, msg)
}
// NewGrpcUnauthenticatedErr returns status error with Unauthenticated error code.
func NewGrpcUnauthenticatedErr(msg string) error {
return NewGrpcErr(codes.Unauthenticated, msg)
}
// NewGrpcPermissionDeniedErr returns status error with PermissionDenied error code.
func NewGrpcPermissionDeniedErr(msg string) error {
return NewGrpcErr(codes.PermissionDenied, msg)
}

@ -0,0 +1,39 @@
package nstatus
import (
"context"
"github.com/pkg/errors"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// UnaryServerInterceptor grpc 错误拦截 服务端拦截器
// 将服务端传递出的任何错误拦截并转换为标准grpc-status保持code与msg不进行翻译
func UnaryServerInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
resp, err = handler(ctx, req)
if err != nil {
cause := errors.Cause(err)
e := ConvertErr(cause)
if e != nil {
// 转换为grpc-err
err = status.Error(codes.Code(e.Code), e.Msg)
}
}
return
}
}
// UnaryClientInterceptor 将客户端调用后产生的grpc-err转换为 *Result
// 客户端直接强转为*Result 或 再次调用 ConvertErr 即可使用
func UnaryClientInterceptor() grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
err := invoker(ctx, method, req, reply, cc, opts...)
if err == nil {
return nil
}
return ConvertErr(err)
}
}

@ -0,0 +1,23 @@
{
"common": {
"success": "Successfully",
"failed": "Failed",
"updateSuccess": "Update successfully",
"updateFailed": "Update failed",
"createSuccess": "Create successfully",
"createFailed": "Create failed",
"deleteSuccess": "Delete successfully",
"deleteFailed": "Delete failed",
"objectNotFound": "Object not found",
"databaseError": "Database error",
"cacheError": "Cache error",
"constraintError": "Operation failed: Data conflict",
"validationError": "Operation failed: Validation failed",
"notSingularError": "Operation failed: Data not unique",
"permissionDeny": "User does not have permission to access this interface"
},
"init": {
"alreadyInit": "Already initialized",
"initializeIsRunning": "Initialize is running"
}
}

@ -0,0 +1,23 @@
{
"common": {
"success": "成功",
"failed": "失败",
"updateSuccess": "更新成功",
"updateFailed": "更新失败",
"createSuccess": "创建成功",
"createFailed": "创建失败",
"deleteSuccess": "删除成功",
"deleteFailed": "删除失败",
"objectNotFound": "对象未找到",
"databaseError": "数据库错误",
"cacheError": "缓存错误",
"constraintError": "操作失败: 数据冲突",
"validationError": "操作失败: 校验失败",
"notSingularError": "操作失败: 数据不唯一",
"permissionDeny": "用户无权限访问此接口"
},
"init": {
"alreadyInit": "已初始化,请勿重复进行此操作",
"initializeIsRunning": "正在初始化,请稍等"
}
}

@ -0,0 +1,23 @@
package msg
// message keys
const (
Success = "common.success"
Failed = "common.failed"
UpdateSuccess = "common.updateSuccess"
UpdateFailed = "common.updateFailed"
CreateSuccess = "common.createSuccess"
CreateFailed = "common.createFailed"
DeleteSuccess = "common.deleteSuccess"
DeleteFailed = "common.deleteFailed"
ObjectNotFound = "common.objectNotFound"
DatabaseError = "common.databaseError"
CacheError = "common.cacheError"
ConstraintError = "common.constraintError"
ValidationError = "common.validationError"
NotSingularError = "common.notSingularError"
PermissionDeny = "common.permissionDeny"
AlreadyInit = "init.alreadyInit"
InitRunning = "init.initializeIsRunning"
)

@ -0,0 +1,45 @@
package nstatus
import (
"encoding/json"
"fmt"
)
// Result 统一返回结果 结构体
// {"code":200,"msg":"OK","data":{}}
// API RPC 共用
type Result struct {
Code int `json:"code"` // 返回编码(业务+错误)
Msg string `json:"msg"` // 消息
Data any `json:"data,omitempty,optional"` // 数据
}
func NewResult(code int, message string) *Result {
return NewResultWithData(code, message, nil)
}
func NewResultWithData(code int, message string, data any) *Result {
return &Result{
Code: code,
Msg: message,
Data: data,
}
}
// Error 错误输出,同时可作为错误
func (r *Result) Error() string {
return r.String()
}
func (r *Result) String() string {
return fmt.Sprintf("Code:%d Message:%s Data:%v", r.Code, r.Msg, r.Data)
}
// JsonString 返回JsonString格式
func (r *Result) JsonString() string {
bytes, err := json.Marshal(r)
if err != nil {
return ""
}
return string(bytes)
}

@ -0,0 +1,12 @@
package nstatus
import (
"fmt"
"testing"
)
func TestResult_String(t *testing.T) {
ret := NewResult(200, "ok")
ret = nil
fmt.Println(ret.Error())
}

@ -0,0 +1,34 @@
package nstatus
import (
"git.noahlan.cn/noahlan/ntool-biz/core/nstatus/code"
)
// ConvertErr is a convenience function which removes the need to handle the
// boolean return value from FromError.
func ConvertErr(err error) *Result {
s, ok := FromError(err)
if !ok {
// 尝试grpc
return WrapGrpcErr(err)
}
return s
}
// FromError returns a Status representation of err.
//
// - If err was produced by this package *Result`, the appropriate Result is returned.
// - If err is nil, a nil is returned.
//
// - Otherwise, err is an error not compatible with this package. In this
// case, a *Result is returned with code.StatusUnknown and err's Error() message,
// and ok is false.
func FromError(err error) (s *Result, ok bool) {
if err == nil {
return nil, false
}
if se, ok := err.(*Result); ok {
return se, true
}
return NewResult(code.StatusUnknown, err.Error()), false
}

@ -0,0 +1,19 @@
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
.idea/
.vscode/
test/

@ -0,0 +1,89 @@
package config
import (
"ariga.io/entcache"
"database/sql"
entsql "entgo.io/ent/dialect/sql"
"fmt"
"git.noahlan.cn/noahlan/ntool-biz/core/config"
"git.noahlan.cn/noahlan/ntool/nlog"
"github.com/go-redis/redis/v8"
_ "github.com/go-sql-driver/mysql"
_ "github.com/lib/pq"
"time"
)
type Database struct {
Host string `json:",env=DB_HOST"` // Host
Port int `json:",env=DB_PORT"` // Port
Username string `json:",optional,env=DB_USERNAME"` // 用户名
Password string `json:",optional,env=DB_PASSWORD"` // 密码
DBName string `json:",optional,env=DB_DBNAME"` // 数据库名
Timezone string `json:",optional,default=Local"` // 时区 Local本地 Asia/Shanghai上海
SSLMode string `json:",optional"` // Postgresql的SSL模式
Type string `json:",default=mysql,options=[mysql,postgres]"` // 数据库类型
MaxOpenConns *int `json:",optional,default=200"` // 最大打开连接数
Debug bool `json:",optional,default=false"` // 调试模式
CacheTime int `json:",optional,default=10"` // 缓存时间,单位秒
}
// NewCacheDriver returns an ent driver with cache.
func (c Database) NewCacheDriver(redisConf config.RedisConf) *entcache.Driver {
db, err := sql.Open(c.Type, c.GetDSN())
nlog.Must(err)
db.SetMaxOpenConns(*c.MaxOpenConns)
driver := entsql.OpenDB(c.Type, db)
rdb := redis.NewClient(&redis.Options{
Network: redisConf.Network,
Addr: redisConf.Addr,
Username: redisConf.Username,
Password: redisConf.Password,
DB: redisConf.DB,
})
cacheDrv := entcache.NewDriver(
driver,
entcache.TTL(time.Duration(c.CacheTime)*time.Second),
entcache.Levels(
entcache.NewLRU(256),
entcache.NewRedis(rdb),
),
)
return cacheDrv
}
// NewNoCacheDriver returns a ent driver without cache.
func (c Database) NewNoCacheDriver() *entsql.Driver {
db, err := sql.Open(c.Type, c.GetDSN())
nlog.Must(err)
db.SetMaxOpenConns(*c.MaxOpenConns)
driver := entsql.OpenDB(c.Type, db)
return driver
}
// MysqlDSN returns mysql DSN.
func (c Database) MysqlDSN() string {
return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=True&loc=%s", c.Username, c.Password, c.Host, c.Port, c.DBName, c.Timezone)
}
// PostgresDSN returns Postgres DSN.
func (c Database) PostgresDSN() string {
return fmt.Sprintf("postgresql://%s:%s@%s:%d/%s?sslmode=%s", c.Username, c.Password, c.Host, c.Port, c.DBName, c.SSLMode)
}
// GetDSN returns DSN according to the database type.
func (c Database) GetDSN() string {
switch c.Type {
case "mysql":
return c.MysqlDSN()
case "postgres":
return c.PostgresDSN()
default:
return "mysql"
}
}

@ -0,0 +1,45 @@
package mixins
import (
"entgo.io/ent"
"entgo.io/ent/dialect/entsql"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/mixin"
"git.noahlan.cn/noahlan/ntool/nstd/tea"
)
// DistributedIDMixin implements the ent.Mixin for sharing
// Distributed ID field with package schemas.
type DistributedIDMixin struct {
mixin.Schema
idFn func() int64
}
func NewDistributedIDMixin(defaultIDFunc func() int64) DistributedIDMixin {
return DistributedIDMixin{
idFn: defaultIDFunc,
}
}
func (d DistributedIDMixin) Fields() []ent.Field {
return []ent.Field{
field.Int64("id").
Annotations(entsql.Annotation{Incremental: tea.Bool(false)}).
DefaultFunc(d.idFn).
Comment("Primary Key | 主键"),
}
}
// DefaultIDMixin implements the ent.Mixin for sharing
// ID field with package schemas.
type DefaultIDMixin struct {
mixin.Schema
}
func (DefaultIDMixin) Fields() []ent.Field {
return []ent.Field{
field.Int64("id").
Default(1).
Comment("Primary Key | 主键"),
}
}

@ -0,0 +1,67 @@
package mixins
import (
"context"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/mixin"
"fmt"
"github.com/casbin/ent-adapter/ent/hook"
)
// OptimisticLockMixin implements the ent.Mixin for sharing
// OptimisticLock fields with package schemas.
type OptimisticLockMixin struct {
mixin.Schema
}
func (OptimisticLockMixin) Fields() []ent.Field {
return []ent.Field{
field.Int64("version").
Default(1).
Comment("optimistic lock | 乐观锁"),
}
}
type optimisticLockKey struct{}
// WithVersion returns a new context that contains pre-version value for interceptor/mutators.
func WithVersion(parent context.Context, version int64) context.Context {
return context.WithValue(parent, optimisticLockKey{}, version)
}
// Hooks of the OptimisticLockMixin.
func (d OptimisticLockMixin) Hooks() []ent.Hook {
return []ent.Hook{
hook.On(
func(next ent.Mutator) ent.Mutator {
return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) {
mx, ok := m.(interface {
AddVersion(i int64)
WhereP(...func(*sql.Selector))
})
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
// get pre-version for update condition | where version={pre-version}
if preVersion, preVersionOk := ctx.Value(optimisticLockKey{}).(int64); preVersionOk {
d.P(mx, preVersion)
}
// set version=version+1
mx.AddVersion(1)
return next.Mutate(ctx, m)
})
},
ent.OpUpdate|ent.OpUpdateOne,
),
}
}
// P adds a storage-level predicate to the queries and mutations.
func (d OptimisticLockMixin) P(w interface{ WhereP(...func(*sql.Selector)) }, preVersion int64) {
w.WhereP(
sql.FieldEQ(d.Fields()[0].Descriptor().Name, preVersion),
)
}

@ -0,0 +1,89 @@
package mixins
import (
"context"
"entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/mixin"
)
// SoftDeleteMixin implements the ent.Mixin for sharing
// soft_delete field with package schemas.
// see also: source code for more information
type SoftDeleteMixin struct {
// We embed the `mixin.Schema` to avoid
// implementing the rest of the methods.
mixin.Schema
}
func (SoftDeleteMixin) Fields() []ent.Field {
return []ent.Field{
field.Time("deleted_at").
Optional().
SchemaType(map[string]string{
dialect.MySQL: "datetime",
}).
Comment("Soft delete | 软删除(逻辑删除)"),
}
}
type SoftDeleteKey struct{}
// SkipSoftDelete returns a new context that skips the soft-delete interceptor/mutators.
func SkipSoftDelete(parent context.Context) context.Context {
return context.WithValue(parent, SoftDeleteKey{}, true)
}
//// Interceptors of the SoftDeleteMixin.
//func (d SoftDeleteMixin) Interceptors() []ent.Interceptor {
// return []ent.Interceptor{
// intercept.TraverseFunc(func(ctx context.Context, q intercept.Query) error {
// // Skip soft-delete, means include soft-deleted entities.
// if skip, _ := ctx.Value(SoftDeleteKey{}).(bool); skip {
// return nil
// }
// d.SoftDeleteWhere(q)
// return nil
// }),
// }
//}
//// Hooks of the SoftDeleteMixin.
//func (d SoftDeleteMixin) Hooks() []ent.Hook {
// return []ent.Hook{
// hook.On(
// func(next ent.Mutator) ent.Mutator {
// return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) {
// // Skip soft-delete, means delete the entity permanently.
// if skip, _ := ctx.Value(SoftDeleteKey{}).(bool); skip {
// return next.Mutate(ctx, m)
// }
// mx, ok := m.(interface {
// SetOp(ent.Op)
// SetDeletedAt(time.Time)
// WhereP(...func(*sql.Selector))
// })
// if !ok {
// return nil, fmt.Errorf("unexpected mutation type %T", m)
// }
// d.SoftDeleteWhere(mx)
// mx.SetOp(ent.OpUpdate)
// mx.SetDeletedAt(time.Now())
//
// return next.Mutate(ctx, m)
// //return mx.Client().Mutate(ctx, m)
// })
// },
// ent.OpDeleteOne|ent.OpDelete,
// ),
// }
//}
// SoftDeleteWhere adds a storage-level predicate to the queries and mutations.
func (d SoftDeleteMixin) SoftDeleteWhere(w interface{ WhereP(...func(*sql.Selector)) }) {
w.WhereP(
sql.FieldIsNull(d.Fields()[0].Descriptor().Name),
)
}

@ -0,0 +1,23 @@
package mixins
import (
"entgo.io/ent"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/mixin"
)
// SortMixin implements the ent.Mixin for sharing
// sort fields with package schemas.
type SortMixin struct {
// We embed the `mixin.Schema` to avoid
// implementing the rest of the methods.
mixin.Schema
}
func (SortMixin) Fields() []ent.Field {
return []ent.Field{
field.Uint32("sort").
Default(1).
Comment("Sort number | 排序号"),
}
}

@ -0,0 +1,29 @@
package mixins
import (
"entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/mixin"
"git.noahlan.cn/noahlan/ntool-biz/core/orm/nent/types"
)
// StatusMixin implements the ent.Mixin for sharing
// status fields with package schemas.
type StatusMixin struct {
// We embed the `mixin.Schema` to avoid
// implementing the rest of the methods.
mixin.Schema
}
func (StatusMixin) Fields() []ent.Field {
return []ent.Field{
field.Enum("status").
Default(string(types.StatusNormal)).
SchemaType(map[string]string{
dialect.MySQL: "varchar(32)",
}).
GoType(types.Status("")).
Comment("Status | 状态"),
}
}

@ -0,0 +1,34 @@
package mixins
import (
"entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/mixin"
"time"
)
// TimeMixin implements the ent.Mixin for sharing
// time fields with package schemas.
type TimeMixin struct {
mixin.Schema
}
func (TimeMixin) Fields() []ent.Field {
return []ent.Field{
field.Time("created_at").
Immutable().
Default(time.Now).
SchemaType(map[string]string{
dialect.MySQL: "datetime",
}).
Comment("创建时间"),
field.Time("updated_at").
Default(time.Now).
UpdateDefault(time.Now).
SchemaType(map[string]string{
dialect.MySQL: "datetime",
}).
Comment("更新时间"),
}
}

@ -0,0 +1,25 @@
package mixins
import (
"entgo.io/ent"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/mixin"
"git.noahlan.cn/noahlan/ntool/nrandom"
"github.com/gofrs/uuid/v5"
)
// UUIDMixin implements the ent.Mixin for sharing
// UUID fields with package schemas.
type UUIDMixin struct {
// We embed the `mixin.Schema` to avoid
// implementing the rest of the methods.
mixin.Schema
}
func (UUIDMixin) Fields() []ent.Field {
return []ent.Field{
field.UUID("id", uuid.UUID{}).
Default(nrandom.NewUUIDV7).
Comment("唯一IDUUID"),
}
}

@ -0,0 +1,16 @@
package types
type Gender string
const (
GenderSecure Gender = "Secure" // 保密
GenderMale Gender = "Male" // 男
GenderFemale Gender = "Female" // 女
)
func (Gender) Values() (kinds []string) {
for _, s := range []Gender{GenderSecure, GenderMale, GenderFemale} {
kinds = append(kinds, string(s))
}
return
}

@ -0,0 +1,17 @@
package types
type Status string
const (
StatusNormal Status = "Normal" // 正常状态
StatusPending Status = "Pending" // 等待状态
StatusDisabled Status = "Disabled" // 禁用
StatusLocked Status = "Locked" // 锁定
)
func (Status) Values() (kinds []string) {
for _, s := range []Status{StatusNormal, StatusPending, StatusDisabled, StatusLocked} {
kinds = append(kinds, string(s))
}
return
}

@ -0,0 +1,13 @@
package ngorm
import (
"gorm.io/gen"
"gorm.io/gen/field"
"gorm.io/plugin/optimisticlock"
)
func ScopeVersionWhere(versionField field.Field, version optimisticlock.Version) func(gen.Dao) gen.Dao {
return func(dao gen.Dao) gen.Dao {
return dao.Where(versionField.Eq(version))
}
}

@ -0,0 +1,79 @@
package page
import "fmt"
const (
DefaultPageSize = 10
DefaultTotal = 0
DefaultCurrent = 1
)
// Pagination 物理分页模型
type Pagination struct {
Size int64 `json:"size"` // 每页条目个数
Total int64 `json:"total"` // 数据条目总数
Current int64 `json:"current"` // 当前页码
}
// NewPagination 构建分页器
func NewPagination(current, size, total int64) *Pagination {
return &Pagination{
Size: size,
Total: total,
Current: current,
}
}
// DefaultPagination 构建默认分页器
func DefaultPagination() *Pagination {
return NewPagination(DefaultCurrent, DefaultPageSize, DefaultTotal)
}
// Clone 克隆 deep-clone
func (p *Pagination) Clone() *Pagination {
return NewPagination(p.Current, p.Size, p.Total)
}
// WithSize 设置每页个数
func (p *Pagination) WithSize(size int64) *Pagination {
p.Size = size
return p
}
// WithTotal 设置数据总数
func (p *Pagination) WithTotal(total int64) *Pagination {
p.Total = total
return p
}
// WithCurrent 设置当前页码
func (p *Pagination) WithCurrent(current int64) *Pagination {
p.Current = current
return p
}
// NextPage 下一页
func (p *Pagination) NextPage() *Pagination {
p.Current++
return p
}
// PrevPage 上一页
func (p *Pagination) PrevPage() *Pagination {
p.Current--
return p
}
// Offset 返回数据库需要的 Offset
func (p *Pagination) Offset() int64 {
return (p.Current - 1) * p.Size
}
// Limit 返回数据库需要的 Limit
func (p *Pagination) Limit() int64 {
return p.Size
}
func (p *Pagination) String() string {
return fmt.Sprintf("[Pagination current: %d, size: %d, total: %d]", p.Current, p.Size, p.Total)
}

@ -0,0 +1,67 @@
package page
import "fmt"
// PaginationMemory 内存分页
type PaginationMemory[T any] struct {
*Pagination // 分页参数
Data []T // 数据
}
// NewMemoryPagination 构建内存分页器
func NewMemoryPagination[T any](size int, data []T) *PaginationMemory[T] {
tmp := &PaginationMemory[T]{
Pagination: &Pagination{
Size: int64(size),
Total: int64(len(data)),
Current: 0,
},
Data: data,
}
return tmp
}
// DefaultMemoryPagination 默认内存分页器
func DefaultMemoryPagination[T any]() *PaginationMemory[T] {
t := make([]T, 0)
return NewMemoryPagination(DefaultPageSize, t)
}
// WithData 设置Data
func (p *PaginationMemory[T]) WithData(data []T) *PaginationMemory[T] {
p.Data = data
p.Total = int64(len(p.Data))
return p
}
// NextPage 返回下一页数据
func (p *PaginationMemory[T]) NextPage() []T {
p.Pagination.NextPage()
return p.currentData()
}
// currentData 返回当前页数据
func (p *PaginationMemory[T]) currentData() []T {
start := (p.Current - 1) * p.Size
end := p.Current * p.Size
dataLen := len(p.Data)
if dataLen < int(end) {
end = int64(dataLen)
}
return p.Data[start:end]
}
// PrevPage 返回上一页数据
func (p *PaginationMemory[T]) PrevPage() []T {
// 减一页
p.Pagination.PrevPage()
return p.currentData()
}
func (p *PaginationMemory[T]) String() string {
return fmt.Sprintf("%s\nData: %v", p.Pagination.String(), p.Data)
}

@ -0,0 +1,99 @@
module git.noahlan.cn/noahlan/ntool-biz
go 1.20
require (
git.noahlan.cn/noahlan/ntool v1.1.0
github.com/BurntSushi/toml v1.3.2
github.com/casbin/casbin/v2 v2.71.0
github.com/casbin/ent-adapter v0.3.0
github.com/casbin/redis-watcher/v2 v2.5.0
github.com/mojocn/base64Captcha v1.3.5
github.com/nicksnyder/go-i18n/v2 v2.2.1
github.com/radovskyb/watcher v1.0.7
github.com/redis/go-redis/v9 v9.0.5
golang.org/x/text v0.10.0
google.golang.org/grpc v1.55.0
gopkg.in/yaml.v3 v3.0.1
)
require (
ariga.io/atlas v0.12.0 // indirect
ariga.io/entcache v0.1.0 // indirect
entgo.io/ent v0.12.3 // indirect
git.noahlan.cn/noahlan/nnet v1.0.0 // indirect
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect
github.com/Shopify/sarama v1.38.1 // indirect
github.com/agext/levenshtein v1.2.3 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/eapache/go-resiliency v1.3.0 // indirect
github.com/eapache/go-xerial-snappy v0.0.0-20230111030713-bf00bc1b83b6 // indirect
github.com/eapache/queue v1.1.0 // indirect
github.com/go-openapi/inflect v0.19.0 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/go-sql-driver/mysql v1.7.1 // indirect
github.com/goburrow/serial v0.1.0 // indirect
github.com/gofrs/uuid/v5 v5.0.0 // indirect
github.com/golang-jwt/jwt/v5 v5.0.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gookit/color v1.5.3 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/hcl/v2 v2.17.0 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.14.0 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.3.2 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgtype v1.14.0 // indirect
github.com/jackc/pgx/v4 v4.18.1 // indirect
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
github.com/jcmturner/gofork v1.7.6 // indirect
github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/klauspost/compress v1.16.6 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/hashstructure v1.1.0 // indirect
github.com/panjf2000/ants/v2 v2.7.5 // indirect
github.com/pierrec/lz4/v4 v4.1.18 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/zclconf/go-cty v1.13.2 // indirect
github.com/zeromicro/go-zero v1.5.3 // indirect
go.opentelemetry.io/otel v1.16.0 // indirect
go.opentelemetry.io/otel/trace v1.16.0 // indirect
golang.org/x/crypto v0.10.0 // indirect
golang.org/x/image v0.8.0 // indirect
golang.org/x/mod v0.11.0 // indirect
golang.org/x/net v0.11.0 // indirect
golang.org/x/sys v0.9.0 // indirect
golang.org/x/term v0.9.0 // indirect
golang.org/x/tools v0.10.0 // indirect
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gorm.io/datatypes v1.2.0 // indirect
gorm.io/driver/mysql v1.5.1 // indirect
gorm.io/gen v0.3.22 // indirect
gorm.io/gorm v1.25.1 // indirect
gorm.io/hints v1.1.2 // indirect
gorm.io/plugin/dbresolver v1.4.1 // indirect
gorm.io/plugin/optimisticlock v1.1.1 // indirect
)

724
go.sum

@ -0,0 +1,724 @@
ariga.io/atlas v0.12.0 h1:jDfjxT3ppKhzqLS26lZv9ni7p9TVNrhy7SQquaF7bPs=
ariga.io/atlas v0.12.0/go.mod h1:+TR129FJZ5Lvzms6dvCeGWh1yR6hMvmXBhug4hrNIGk=
ariga.io/entcache v0.1.0 h1:nfJXzjB5CEvAK6SmjupHREMJrKLakeqU5tG3s4TO6JA=
ariga.io/entcache v0.1.0/go.mod h1:3Z1Sql5bcqPA1YV/jvMlZyh9T+ntSFOclaASAm1TiKQ=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
entgo.io/ent v0.8.0/go.mod h1:KNjsukat/NJi6zJh1utwRadsbGOZsBbAZNDxkW7tMCc=
entgo.io/ent v0.12.3 h1:N5lO2EOrHpCH5HYfiMOCHYbo+oh5M8GjT0/cx5x6xkk=
entgo.io/ent v0.12.3/go.mod h1:AigGGx+tbrBBYHAzGOg8ND661E5cxx1Uiu5o/otJ6Yg=
git.noahlan.cn/noahlan/nnet v0.7.1 h1:KHE/1Ae6A4Kt64o8c0hiBLzdxz6YPVrgo0Orz5JqrJI=
git.noahlan.cn/noahlan/nnet v0.7.1/go.mod h1:SwaY4X+7+WQmy7h71+fEbWm2cIw5p+y7zMlvKvAEXUU=
git.noahlan.cn/noahlan/nnet v1.0.0 h1:y8FxE5WV1SlOKoBxzUTIMsyLVhWOOZnle+tzQ6skA3Y=
git.noahlan.cn/noahlan/nnet v1.0.0/go.mod h1:mJSMpTwpAY+nHPkiLke7y4OLtbLf8Ijdc2O47Y7noXw=
git.noahlan.cn/noahlan/ntool v1.0.0 h1:T7reElDh8Y4HaXWv3WAtIpSjpdscfvm73meo5GZT04c=
git.noahlan.cn/noahlan/ntool v1.0.0/go.mod h1:mYDpbKJBJ/kMhBviJ2msi8QPRj2ldhtXbT/r6OuytCk=
git.noahlan.cn/noahlan/ntool v1.0.1 h1:c2sw65BmyqQHKn3+s2Bjdkz9AtSKI4e1WAkRi/CG1oc=
git.noahlan.cn/noahlan/ntool v1.0.1/go.mod h1:nLp9j4lz203kNpVFrNbT25A2QHbx235gXv+Ut1bX8E8=
git.noahlan.cn/noahlan/ntool v1.1.0 h1:f3s/xaj2kCtFozSXxoGELXiYfnmhOxC8D8RAx3MeSW4=
git.noahlan.cn/noahlan/ntool v1.1.0/go.mod h1:nLp9j4lz203kNpVFrNbT25A2QHbx235gXv+Ut1bX8E8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/Shopify/sarama v1.38.1 h1:lqqPUPQZ7zPqYlWpTh+LQ9bhYNu2xJL6k1SJN4WVe2A=
github.com/Shopify/sarama v1.38.1/go.mod h1:iwv9a67Ha8VNa+TifujYoWGxWnu2kNVAQdSdZ4X2o5g=
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
github.com/casbin/casbin/v2 v2.29.2/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
github.com/casbin/casbin/v2 v2.71.0 h1:pVzHKXkGgOXIjksEwnrOjNu5CE4xy6aAVzdR8td2gSc=
github.com/casbin/casbin/v2 v2.71.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
github.com/casbin/ent-adapter v0.3.0 h1:4lkhB/BwXAhj84iSrZG/gQ4avkO4uPwz4kdDP1VEnww=
github.com/casbin/ent-adapter v0.3.0/go.mod h1:U6saAFuVDEOWLCtrgx35d95M12FC0uh5GtJL82QunRM=
github.com/casbin/redis-watcher/v2 v2.5.0 h1:a0922GOKYDSSiD7hEQxmLh/psea2eLZtf1V12XzLI5w=
github.com/casbin/redis-watcher/v2 v2.5.0/go.mod h1:lgtjnQrfbo+xZIwMPtLu9is/XpnCfAT94SLgMzY7HGk=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/eapache/go-resiliency v1.3.0 h1:RRL0nge+cWGlxXbUzJ7yMcq6w2XBEr19dCN6HECGaT0=
github.com/eapache/go-resiliency v1.3.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho=
github.com/eapache/go-xerial-snappy v0.0.0-20230111030713-bf00bc1b83b6 h1:8yY/I9ndfrgrXUbOGObLHKBR4Fl3nZXwM2c7OYTT8hM=
github.com/eapache/go-xerial-snappy v0.0.0-20230111030713-bf00bc1b83b6/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0=
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-bindata/go-bindata v1.0.1-0.20190711162640-ee3c2418e368/go.mod h1:7xCgX1lzlrXPHkfvn3EhumqHkmSlzt8at9q7v0ax19c=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4=
github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-sql-driver/mysql v1.5.1-0.20200311113236-681ffa848bae/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/goburrow/serial v0.1.0 h1:v2T1SQa/dlUqQiYIT8+Cu7YolfqAi3K96UmhwYyuSrA=
github.com/goburrow/serial v0.1.0/go.mod h1:sAiqG0nRVswsm1C97xsttiYCzSLBmUZ/VSlVLZJ8haA=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gookit/color v1.5.3 h1:twfIhZs4QLCtimkP7MOxlF3A0U/5cDPseRT9M/+2SCE=
github.com/gookit/color v1.5.3/go.mod h1:NUzwzeehUfl7GIb36pqId+UGmRfQcU/WiiyTTeNjHtE=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/hcl/v2 v2.17.0 h1:z1XvSUyXd1HP10U4lrLg5e0JMVz6CPaJvAgxM0KNZVY=
github.com/hashicorp/hcl/v2 v2.17.0/go.mod h1:gJyW2PTShkJqQBKpAmPO3yxMxIuoXkOF2TpqXzrQyx4=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgconn v1.14.0 h1:vrbA9Ud87g6JdFWkHTJXppVce58qPIdP7N8y0Ml/A7Q=
github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E=
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0=
github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw=
github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0=
github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.16.6 h1:91SKEy4K37vkp255cJ8QesJhjyRO0hn9i9G0GoUwLsk=
github.com/klauspost/compress v1.16.6/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0=
github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mojocn/base64Captcha v1.3.5 h1:Qeilr7Ta6eDtG4S+tQuZ5+hO+QHbiGAJdi4PfoagaA0=
github.com/mojocn/base64Captcha v1.3.5/go.mod h1:/tTTXn4WTpX9CfrmipqRytCpJ27Uw3G6I7NcP2WwcmY=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nicksnyder/go-i18n/v2 v2.2.1 h1:aOzRCdwsJuoExfZhoiXHy4bjruwCMdt5otbYojM/PaA=
github.com/nicksnyder/go-i18n/v2 v2.2.1/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/panjf2000/ants/v2 v2.7.5 h1:/vhh0Hza9G1vP1PdCj9hl6MUzCRbmtcTJL0OsnmytuU=
github.com/panjf2000/ants/v2 v2.7.5/go.mod h1:KIBmYG9QQX5U2qzFP/yQJaq/nSb6rahS9iEHkrCMgM8=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ=
github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE=
github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/redis/go-redis/v9 v9.0.5 h1:CuQcn5HIEeK7BgElubPP8CGtE0KakrnbBSTLjathl5o=
github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zclconf/go-cty v1.13.2 h1:4GvrUxe/QUDYuJKAav4EYqdM47/kZa672LwmXFmEKT0=
github.com/zclconf/go-cty v1.13.2/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
github.com/zeromicro/go-zero v1.5.3 h1:9poyd+raeL7gSMUu6P19N7bssTppieR2j7Oos2j1yFQ=
github.com/zeromicro/go-zero v1.5.3/go.mod h1:dmoBpgJTxt9KWmgrNGpv06XxZRPXMakrxUVgROFAR3g=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190501045829-6d32002ffd75/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.8.0 h1:agUcRXV/+w6L9ryntYYsF2x9fQTMd4T8fiiYXAVW6Jg=
golang.org/x/image v0.8.0/go.mod h1:PwLxp3opCYg4WR2WO9P0L6ESnsD6bLTWcw8zanLMVFM=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2 h1:EQyQC3sa8M+p6Ulc8yy9SWSS2GVwyRc83gAbG8lrl4o=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/datatypes v1.2.0 h1:5YT+eokWdIxhJgWHdrb2zYUimyk0+TaFth+7a0ybzco=
gorm.io/datatypes v1.2.0/go.mod h1:o1dh0ZvjIjhH/bngTpypG6lVRJ5chTBxE09FH/71k04=
gorm.io/driver/mysql v1.4.3/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c=
gorm.io/driver/mysql v1.5.1 h1:WUEH5VF9obL/lTtzjmML/5e6VfFR/788coz2uaVCAZw=
gorm.io/driver/mysql v1.5.1/go.mod h1:Jo3Xu7mMhCyj8dlrb3WoCaRd1FhsVh+yMXb1jUInf5o=
gorm.io/driver/sqlite v1.5.0/go.mod h1:kDMDfntV9u/vuMmz8APHtHF0b4nyBB7sfCieC6G8k8I=
gorm.io/gen v0.3.22 h1:K7u5tCyaZfe1cbQFD8N2xrTqUuqximNFSRl7zOFPq+M=
gorm.io/gen v0.3.22/go.mod h1:dQcELeF/7Kf82M6AQF+O/rKT5r1sjv49TlGz0cerPn4=
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
gorm.io/gorm v1.24.3/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/gorm v1.25.1 h1:nsSALe5Pr+cM3V1qwwQ7rOkw+6UeLrX5O4v3llhHa64=
gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/hints v1.1.2 h1:b5j0kwk5p4+3BtDtYqqfY+ATSxjj+6ptPgVveuynn9o=
gorm.io/hints v1.1.2/go.mod h1:/ARdpUHAtyEMCh5NNi3tI7FsGh+Cj/MIUlvNxCNCFWg=
gorm.io/plugin/dbresolver v1.4.1 h1:Ug4LcoPhrvqq71UhxtF346f+skTYoCa/nEsdjvHwEzk=
gorm.io/plugin/dbresolver v1.4.1/go.mod h1:CTbCtMWhsjXSiJqiW2R8POvJ2cq18RVOl4WGyT5nhNc=
gorm.io/plugin/optimisticlock v1.1.1 h1:REWF26BNTIcLpgzp34EW1Mi9bPZpthBcwjBkOYINn5Q=
gorm.io/plugin/optimisticlock v1.1.1/go.mod h1:wFWgM/KsGEg+IoxgZAAVBP4OmaPfj337L/+T4AR6/hI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=

@ -0,0 +1,14 @@
# N-GTP
A golang package for handling GTP engine, (such as Katago. etc.), supporting analysis mode.
一个`Golang`编写的`GTP`工具包,支持通用`GTP 引擎`(如:`Katago`),支持分析模式。
## 安装
```bash
go get -u git.noahlan.cn/noahlan/ntools-go/ngtp
```
## 使用

@ -0,0 +1,44 @@
package codec
import (
"errors"
"fmt"
"git.noahlan.cn/noahlan/ntool-biz/nkatago/req"
"git.noahlan.cn/noahlan/ntool-biz/nkatago/resp"
"git.noahlan.cn/noahlan/ntool/ndef"
"strings"
)
type GtpSerializer struct {
}
func NewGtpSerializer() ndef.Serializer {
return &GtpSerializer{}
}
func (s *GtpSerializer) Marshal(v interface{}) ([]byte, error) {
ret, ok := v.(*req.GtpReq)
if !ok {
return nil, errors.New(fmt.Sprintf("参数类型必须为 %T", req.GtpReq{}))
}
// ret arg0 arg1 arg2 ...
// cmd arg0 arg1 arg2 ...
sb := strings.Builder{}
if ret.ID != "" {
sb.WriteString(ret.ID)
sb.WriteString(" ")
}
sb.WriteString(ret.Command)
sb.WriteString(" ")
sb.WriteString(strings.Join(ret.Args, " "))
return []byte(sb.String()), nil
}
func (s *GtpSerializer) Unmarshal(data []byte, v interface{}) error {
t, ok := v.(*resp.GtpResponse)
if !ok {
return errors.New(fmt.Sprintf("参数类型必须为 %T", resp.GtpResponse{}))
}
t.SetContent(string(data))
return nil
}

@ -0,0 +1,29 @@
package nkatago
import (
"errors"
"strings"
)
// KatagoConfig Katago配置
type KatagoConfig struct {
Exec string // Exec 可执行程序路径,可以是相对路径 必填
Mode EngineType `json:",default=Analysis"` // 类型 Analysis | Gtp
Dir string `json:",default=/"` // Katago 路径
Model string `json:",default=/"` // 模型文件路径,可以是相对路径
Config string `json:",default=analysis.cfg"` // 配置文件路径,可以是相对路径
}
func (c KatagoConfig) BuildGTP() (*GtpEngine, error) {
if c.Mode != EngineGTP {
return nil, errors.New("engine mode does not matched")
}
return NewGtpEngine(c.Exec, strings.ToLower(c.Mode), c.Dir, c.Model, c.Config)
}
func (c KatagoConfig) BuildAnalysis() (*AnalysisEngine, error) {
if c.Mode != EngineAnalysis {
return nil, errors.New("engine mode does not matched")
}
return NewAnalysisEngine(c.Exec, strings.ToLower(c.Mode), c.Dir, c.Model, c.Config)
}

@ -0,0 +1,17 @@
package nkatago
import "git.noahlan.cn/noahlan/ntool/nsys/cmdn"
type EngineType = string
const (
EngineGTP EngineType = "Gtp"
EngineAnalysis EngineType = "Analysis"
)
// KatagoEngine katago 引擎
type KatagoEngine struct {
Processor *cmdn.Processor
Name string
Mode EngineType
}

@ -0,0 +1,119 @@
package nkatago
import (
"git.noahlan.cn/noahlan/ntool-biz/nkatago/req"
"git.noahlan.cn/noahlan/ntool-biz/nkatago/resp"
"git.noahlan.cn/noahlan/ntool/ndef"
"git.noahlan.cn/noahlan/ntool/ngo/codec"
"git.noahlan.cn/noahlan/ntool/nrandom"
"git.noahlan.cn/noahlan/ntool/nstr"
"git.noahlan.cn/noahlan/ntool/nsys/cmdn"
"strings"
)
var (
analStartupDecidedFunc cmdn.LineFunc = func(sb *strings.Builder, line string) bool {
return strings.Contains(line, "Started, ready to begin handling requests")
}
analEndLineDecidedFunc cmdn.LineFunc = func(sb *strings.Builder, line string) bool {
return nstr.IsJSON(sb.String())
}
analReadIDFunc cmdn.ReadIDFunc = func(serializer ndef.Serializer, data string) (string, error) {
var s struct {
ID string `json:"id"`
}
err := serializer.Unmarshal([]byte(data), &s)
if err != nil {
return "", err
}
return s.ID, nil
}
)
type AnalysisEngine struct {
*KatagoEngine
}
// NewAnalysisEngine 创建Analysis引擎
func NewAnalysisEngine(name string, args ...string) (*AnalysisEngine, error) {
engine := &AnalysisEngine{KatagoEngine: &KatagoEngine{
Processor: cmdn.NewProcessor(
false,
cmdn.WithSerializer(codec.NewJsonSerializer()),
cmdn.WithStartupDecidedFunc(analStartupDecidedFunc),
cmdn.WithEndLineDecidedFunc(analEndLineDecidedFunc),
cmdn.WithReadIDFunc(analReadIDFunc),
),
Name: name,
Mode: EngineAnalysis,
}}
err := engine.Processor.Run(name, args...)
return engine, err
}
// Query 分析查询
func (eng *AnalysisEngine) Query(req *req.AnalQueryReq, callback func(queryResp *resp.AnalQueryResp)) error {
if len(req.ID) == 0 {
req.ID = nrandom.SnowflakeIdStr()
}
return eng.Processor.Exec(req, func(serializer ndef.Serializer, data string) {
var tmp resp.AnalQueryResp
tmp.Err = serializer.Unmarshal([]byte(data), &tmp)
callback(&tmp)
})
}
// QueryVersion 查询版本号
func (eng *AnalysisEngine) QueryVersion(callback func(versionResp *resp.AnalQueryVersionResp)) error {
return eng.Processor.Exec(&req.AnalQueryActionReq{
ID: nrandom.SnowflakeIdStr(),
Action: req.AnalActionQueryVersion,
}, func(serializer ndef.Serializer, data string) {
var tmp resp.AnalQueryVersionResp
tmp.Err = serializer.Unmarshal([]byte(data), &tmp)
callback(&tmp)
})
}
// ClearCache 清除缓存
func (eng *AnalysisEngine) ClearCache(callback func(cacheResp *resp.AnalClearCacheResp)) error {
return eng.Processor.Exec(&req.AnalQueryActionReq{
ID: nrandom.SnowflakeIdStr(),
Action: req.AnalActionClearCache,
}, func(serializer ndef.Serializer, data string) {
var tmp resp.AnalClearCacheResp
tmp.Err = serializer.Unmarshal([]byte(data), &tmp)
callback(&tmp)
})
}
// Terminate 终止某次分析
// terminateId: 分析轮次ID
// turnNumbers: 需要终止分析的回合数(手数),可空:所有回合
func (eng *AnalysisEngine) Terminate(terminateId string, turnNumbers []int32, callback func(terminateResp *resp.AnalTerminateResp)) error {
return eng.Processor.Exec(&req.AnalQueryActionReq{
ID: nrandom.SnowflakeIdStr(),
Action: req.AnalActionTerminate,
TerminateId: terminateId,
TurnNumbers: turnNumbers,
}, func(serializer ndef.Serializer, data string) {
var tmp resp.AnalTerminateResp
tmp.Err = serializer.Unmarshal([]byte(data), &tmp)
callback(&tmp)
})
}
// TerminateAll 终止所有分析
// turnNumbers: 需要终止所有分析轮次的回合数,可空:所有回合
func (eng *AnalysisEngine) TerminateAll(turnNumbers []int32, callback func(allResp *resp.AnalTerminateAllResp)) error {
return eng.Processor.Exec(&req.AnalQueryActionReq{
ID: nrandom.SnowflakeIdStr(),
Action: req.AnalActionTerminateAll,
TurnNumbers: turnNumbers,
}, func(serializer ndef.Serializer, data string) {
var tmp resp.AnalTerminateAllResp
tmp.Err = serializer.Unmarshal([]byte(data), &tmp)
callback(&tmp)
})
}

@ -0,0 +1,167 @@
package nkatago
import (
"fmt"
"git.noahlan.cn/noahlan/ntool-biz/nkatago/codec"
"git.noahlan.cn/noahlan/ntool-biz/nkatago/req"
"git.noahlan.cn/noahlan/ntool-biz/nkatago/resp"
"git.noahlan.cn/noahlan/ntool/ndef"
"git.noahlan.cn/noahlan/ntool/nsys/cmdn"
"strings"
)
var (
gtpStartupDecidedFunc cmdn.LineFunc = func(sb *strings.Builder, line string) bool {
return strings.Contains(line, "GTP ready")
}
gtpEndLineDecidedFunc cmdn.LineFunc = func(sb *strings.Builder, line string) bool {
return len(line) == 0
}
gtpReadIDFunc cmdn.ReadIDFunc = func(serializer ndef.Serializer, data string) (string, error) {
return "", nil
}
)
type GtpEngine struct {
*KatagoEngine
lastID string
}
// NewGtpEngine 创建GTP引擎
func NewGtpEngine(name string, args ...string) (*GtpEngine, error) {
engine := &GtpEngine{KatagoEngine: &KatagoEngine{
Processor: cmdn.NewProcessor(
true,
cmdn.WithSerializer(codec.NewGtpSerializer()),
cmdn.WithStartupDecidedFunc(gtpStartupDecidedFunc),
cmdn.WithEndLineDecidedFunc(gtpEndLineDecidedFunc),
cmdn.WithReadIDFunc(gtpReadIDFunc),
),
Name: name,
Mode: EngineGTP,
}}
err := engine.Processor.Run(name, args...)
return engine, err
}
func (eng *GtpEngine) ID(id string) {
eng.lastID = id
}
// Exec 同步方式自定义命令
func (eng *GtpEngine) Exec(id, cmd string) (*resp.GtpResponse, error) {
cmds := strings.Fields(cmd)
ret := resp.NewGtpResponse(cmds[0])
err := eng.Processor.ExecAsync(req.NewGtpReq(id, cmd, cmds[1:]...), func(serializer ndef.Serializer, data string) {
ret.Err = serializer.Unmarshal([]byte(data), ret)
})
return ret, err
}
// KnowCommand 判断命令是否支持
func (eng *GtpEngine) KnowCommand(cmd string) bool {
value, err := eng.Exec(eng.lastID, "known_command "+cmd)
if err != nil {
return false
}
if strings.ToLower(strings.TrimSpace(value.Content)) != "true" {
return false
}
return true
}
// Komi 设置贴目
func (eng *GtpEngine) Komi(komi float64) (*resp.GtpResponse, error) {
return eng.Exec(eng.lastID, fmt.Sprintf("komi %v", komi))
}
// BoardSize 设置棋盘大小
func (eng *GtpEngine) BoardSize(size int) (*resp.GtpResponse, error) {
return eng.Exec(eng.lastID, fmt.Sprintf("boardsize %v", size))
}
// GenMove AI落子
func (eng *GtpEngine) GenMove(color string) (*resp.GtpResponse, error) {
color = strings.ToUpper(color)
command := "genmove " + color
return eng.Exec(eng.lastID, command)
}
// Play 手工落子
func (eng *GtpEngine) Play(color, coor string) (*resp.GtpResponse, error) {
color = strings.ToUpper(color)
return eng.Exec(eng.lastID, fmt.Sprintf("play %s %s", color, coor))
}
// LoadSgf 加载SGF文件
func (eng *GtpEngine) LoadSgf(file string) (*resp.GtpResponse, error) {
command := fmt.Sprintf("loadsgf %s", file)
return eng.Exec(eng.lastID, command)
}
// FinalStatusList 获取当前盘面形势判断
func (eng *GtpEngine) FinalStatusList(cmd string) (*resp.GtpResponse, error) {
command := fmt.Sprintf("final_status_list %s", cmd)
return eng.Exec(eng.lastID, command)
}
// SetLevel 设置AI级别
func (eng *GtpEngine) SetLevel(seed int) (*resp.GtpResponse, error) {
command := fmt.Sprintf("level %d", seed)
return eng.Exec(eng.lastID, command)
}
// SetRandomSeed 设置AI随机数
func (eng *GtpEngine) SetRandomSeed(seed int) (*resp.GtpResponse, error) {
command := fmt.Sprintf("set_random_seed %d", seed)
return eng.Exec(eng.lastID, command)
}
// ShowBoard 显示棋盘
func (eng *GtpEngine) ShowBoard() (*resp.GtpResponse, error) {
return eng.Exec(eng.lastID, "showboard")
}
// ClearBoard 清空棋盘
func (eng *GtpEngine) ClearBoard() (*resp.GtpResponse, error) {
return eng.Exec(eng.lastID, "clear_board")
}
// PrintSgf 打印SGF
func (eng *GtpEngine) PrintSgf() (*resp.GtpResponse, error) {
return eng.Exec(eng.lastID, "printsgf")
}
// TimeSetting 设置时间规则
func (eng *GtpEngine) TimeSetting(baseTime, byoTime, byoStones int) (*resp.GtpResponse, error) {
return eng.Exec(eng.lastID, fmt.Sprintf("time_settings %d %d %d", baseTime, byoTime, byoStones))
}
// KGSTimeSetting 设置KGS time
func (eng *GtpEngine) KGSTimeSetting(mainTime, readTime, readLimit int) (*resp.GtpResponse, error) {
return eng.Exec(eng.lastID, fmt.Sprintf("kgs-time_settings byoyomi %d %d %d", mainTime, readTime, readLimit))
}
// FinalScore 获取结果
func (eng *GtpEngine) FinalScore() (*resp.GtpResponse, error) {
return eng.Exec(eng.lastID, "final_score")
}
// Undo 悔棋
func (eng *GtpEngine) Undo() (*resp.GtpResponse, error) {
return eng.Exec(eng.lastID, "undo")
}
// TimeLeft 设置时间
func (eng *GtpEngine) TimeLeft(color string, mainTime, stones int) (*resp.GtpResponse, error) {
return eng.Exec(eng.lastID, fmt.Sprintf("time_left %s %d %d", color, mainTime, stones))
}
// Quit 退出
func (eng *GtpEngine) Quit() (*resp.GtpResponse, error) {
r, err := eng.Exec(eng.lastID, "quit")
err = eng.Processor.Stop()
return r, err
}

@ -0,0 +1,66 @@
package req
import (
"encoding/json"
"git.noahlan.cn/noahlan/ntool/nsys/cmdn"
)
type Stone struct {
Player string // 玩家 "B" / "W"
Location string // "C4" GTP标准协议或 "CC" 行列协议
}
func (s Stone) MarshalJSON() ([]byte, error) {
ret := []string{s.Player, s.Location}
return json.Marshal(ret)
}
var _ cmdn.ICommand = (*AnalQueryReq)(nil)
type AnalQueryReq struct {
ID string `json:"id"` // ID
Moves []Stone `json:"moves"` // moves
InitialStones []Stone `json:"initialStones,omitempty,optional"` // 初始化棋子(让子棋,棋谱等等)
Rules string `json:"rules"` // 规则, chinese / japanese / tromp-taylor .etc...
Komi float32 `json:"komi"` // 贴目中国规则一般7.5目
BoardXSize int64 `json:"boardXSize"` // 棋盘X大小
BoardYSize int64 `json:"boardYSize"` // 棋盘Y大小
AnalyzeTurns []int32 `json:"analyzeTurns,omitempty,optional"` // 需要分析的回合数与moves对应若不指定则分析最后一回合
InitialPlayer string `json:"initialPlayer,omitempty,optional"` // 指定第一手的玩家 "B" / "W"当moves为空时很有用
/// .etc...
}
func (r *AnalQueryReq) MessageID() string {
return r.ID
}
func (r *AnalQueryReq) AddMove(player, location string) {
r.Moves = append(r.Moves, Stone{Player: player, Location: location})
}
func (r *AnalQueryReq) AddInitialStone(player, location string) {
r.InitialStones = append(r.InitialStones, Stone{Player: player, Location: location})
}
var _ cmdn.ICommand = (*AnalQueryActionReq)(nil)
const (
AnalActionQueryVersion = "query_version"
AnalActionClearCache = "clear_cache"
AnalActionTerminate = "terminate"
AnalActionTerminateAll = "terminate_all"
)
type AnalQueryActionReq struct {
ID string `json:"id"` // ID
Action string `json:"action"` // 查询动作
// terminate
TerminateId string `json:"terminateId,omitempty,optional"`
// terminate | terminate_all
TurnNumbers []int32 `json:"turnNumbers,omitempty,optional"`
}
func (r *AnalQueryActionReq) MessageID() string {
return r.ID
}

@ -0,0 +1,25 @@
package req
import (
"git.noahlan.cn/noahlan/ntool/nsys/cmdn"
)
var _ cmdn.ICommand = (*GtpReq)(nil)
type GtpReq struct {
ID string
Command string
Args []string
}
func NewGtpReq(id, cmd string, args ...string) *GtpReq {
return &GtpReq{
ID: id,
Command: cmd,
Args: args,
}
}
func (r *GtpReq) MessageID() string {
return ""
}

@ -0,0 +1,69 @@
package resp
type AnalBaseResp struct {
ID string `json:"id"` // ID 必有
Action string `json:"action,omitempty,optional"` // Action 动作,不是必填值
// 其它东西,通过组合
Err error
}
type AnalQueryResp struct {
AnalBaseResp
IsDuringSearch bool `json:"isDuringSearch"` // 是否是搜索的过程结果,发送时指定 reportDuringSearchEvery则会在过程中进行回复
TurnNumber int32 `json:"turnNumber"` // 正在分析的回合数(手数)
MoveInfos []struct {
Move string `json:"move"` // 正在分析的移动,通常为棋盘坐标
Visits int32 `json:"visits"` // 访问次数(神经网络)
WinRate float64 `json:"winrate"` // 胜率 [0~1] [0%~100%] 越高越好
ScoreMean float64 `json:"scoreMean"` // 预计平均分
ScoreLead float64 `json:"scoreLead"` // 预计平均分 与 ScoreMean 一致
ScoreStdev float64 `json:"scoreStdev"` // 预计平均分标准差由于MCTS机制该分值可能偏高
ScoreSelfplay float64 `json:"scoreSelfplay"` // 自我对弈时的预计分数,该值目前偏高
Prior float64 `json:"prior"` // 策略优先级 [0~1],通常会选优先级高的下棋(直觉)
Utility float64 `json:"utility"` // 综合效用值结合winrate和score值域 [-C,C]
Lcb float64 `json:"lcb"` // LCB胜率但值可能有误 [0~1] 可能会越界
UtilityLcb float64 `json:"utilityLcb"` // LCB综合效用值
Weight float64 `json:"weight"` // Visits 的平均权重
Order int32 `json:"order"` // Katago神经网络中的直觉优先级值[0~max]越低,优先级越高
PV []string `json:"pv"` // 此次移动之后预测之后的移动列表
// PvVisits
// pvEdgeVisits
// ownership
// ownershipStdev
} `json:"moveInfos"` // 移动信息
RootInfo struct {
ThisHash string `json:"thisHash"` // 本次移动的唯一编码
SymHash string `json:"symHash"` //
CurrentPlayer string `json:"currentPlayer"` // "B" or "W"
// RawStWrError
// rawStScoreError
// rawVarTimeLeft
Visits int32 `json:"visits"` // 访问次数(神经网络)
WinRate float64 `json:"winrate"` // 胜率 [0~1] [0%~100%] 越高越好
ScoreLead float64 `json:"scoreLead"` // 预计平均分 与 ScoreMean 一致
ScoreSelfplay float64 `json:"scoreSelfplay"` // 自我对弈时的预计分数,该值目前偏高
Utility float64 `json:"utility"` // 综合效用值结合winrate和score值域 [-C,C]
} `json:"rootInfo"` // 根信息
}
type AnalQueryVersionResp struct {
AnalBaseResp
GitHash string `json:"git_hash"` // GitHash
Version string `json:"version"` // 版本号
}
type AnalClearCacheResp struct {
AnalBaseResp
}
type AnalTerminateResp struct {
AnalBaseResp
IsDuringSearch bool `json:"isDuringSearch"` // 是否是搜索的过程结果,发送时指定 reportDuringSearchEvery则会在过程中进行回复
TurnNumber int64 `json:"turnNumber"` // 本次停止的回合数
}
type AnalTerminateAllResp struct {
AnalBaseResp
TurnNumbers []int64 `json:"turnNumbers"` // 本次停止的回合数
}

@ -0,0 +1,45 @@
package resp
import (
"errors"
"fmt"
"regexp"
"strings"
)
type GtpResponse struct {
Command string
Content string
Err error
}
func NewGtpResponse(cmd string) *GtpResponse {
return &GtpResponse{
Command: cmd,
}
}
func (g *GtpResponse) SetContent(str string) {
reg, _ := regexp.Compile(`\t`)
result := strings.TrimSpace(reg.ReplaceAllString(str, " "))
if len(result) > 0 {
first := result[0]
switch first {
case '=':
g.Content = strings.TrimSpace(result[1:])
case '?':
g.Err = errors.New(strings.TrimSpace(result[1:]))
default:
g.Err = errors.New("未能识别GTP引擎返回值")
}
}
}
func (g *GtpResponse) String() string {
if g.Err != nil {
return fmt.Sprintf("Err: [%v]", g.Err)
}
return fmt.Sprintf("CMD:[%s] Response:[%s]", g.Command, g.Content)
}

@ -0,0 +1,2 @@
cd ./katago/katago-v1.12.3-cuda11.2-windows-x64
katago.exe gtp -model "../kata1-b40c256-s11840935168-d2898845681.bin.gz" -config "b40_gtp.cfg"

@ -0,0 +1,2 @@
cd ./katago/katago-v1.12.3-cuda11.2-windows-x64
katago.exe analysis -model "../kata1-b40c256-s11840935168-d2898845681.bin.gz" -config "analysis_example.cfg"

@ -0,0 +1,75 @@
package main
import (
"fmt"
"git.noahlan.cn/noahlan/ntool-biz/nkatago"
"git.noahlan.cn/noahlan/ntool-biz/nkatago/req"
"git.noahlan.cn/noahlan/ntool-biz/nkatago/resp"
"sync"
)
func testGTPEngine() {
engine, err := nkatago.NewGtpEngine(".\\run.bat")
if err != nil {
panic(err)
}
r, err := engine.BoardSize(1)
if err != nil {
panic(err)
}
fmt.Println(r)
}
func testAnalEngine() {
eng, err := nkatago.NewAnalysisEngine(".\\run_analysis.bat")
if err != nil {
panic(err)
}
var wg sync.WaitGroup
wg.Add(1)
err = eng.QueryVersion(func(versionResp *resp.AnalQueryVersionResp) {
fmt.Println(fmt.Sprintf("查询到版本git_hash:%s version:%s", versionResp.GitHash, versionResp.Version))
wg.Done()
})
if err != nil {
panic(err)
}
queryReq := &req.AnalQueryReq{
Rules: "chinese",
Komi: 6.5,
BoardXSize: 9,
BoardYSize: 9,
AnalyzeTurns: []int32{2},
}
queryReq.AddInitialStone("B", "A1")
queryReq.AddInitialStone("B", "A2")
queryReq.AddMove("W", "B3")
queryReq.AddMove("B", "C5")
wg.Add(1)
err = eng.Query(queryReq, func(queryResp *resp.AnalQueryResp) {
moveInfo := queryResp.MoveInfos[0]
fmt.Println(fmt.Sprintf("当前玩家:%s, 建议移动:%s, 此步移动的建议后续: %v, 胜率:%f, 直觉优先级:%d 预测平均分: %f",
queryResp.RootInfo.CurrentPlayer,
moveInfo.Move,
moveInfo.PV,
moveInfo.WinRate,
moveInfo.Order,
moveInfo.ScoreMean,
))
wg.Done()
})
wg.Wait()
}
func main() {
testGTPEngine()
//testAnalEngine()
}

@ -0,0 +1,12 @@
package nmodbus
import (
"encoding/binary"
"git.noahlan.cn/noahlan/nnet/config"
)
// ModbusTCPConf ModbusTCP配置
type ModbusTCPConf struct {
config.EngineConf // 引擎配置
ByteOrder binary.ByteOrder // 字节序
}

@ -0,0 +1,8 @@
package handler
type DataMgr struct {
Coils []byte // 线圈DO数字输出
DiscreteInputs []byte // 离散输入DI数字输入
HoldingRegisters []uint16 // 保持寄存器AO模拟输出
InputRegisters []uint16 // 输入寄存器AI模拟输入
}

@ -0,0 +1,145 @@
package handler
import (
"git.noahlan.cn/noahlan/ntool-biz/nmodbus/protocol"
"git.noahlan.cn/noahlan/ntool-biz/nmodbus/serialize"
"git.noahlan.cn/noahlan/ntool-biz/nmodbus/util"
)
// ReadCoils 读线圈寄存器DO 功能码0x01
func ReadCoils(s *Handler, packet protocol.Packet) ([]byte, *protocol.MError) {
register, numRegs, endRegister := serialize.RegisterAddressAndNumber(packet.GetBody())
if endRegister > 65535 {
return []byte{}, &protocol.IllegalDataAddress
}
// 一个寄存器2字节
dataSize := numRegs / 8
if (numRegs % 8) != 0 {
dataSize++
}
data := make([]byte, 1+dataSize)
data[0] = byte(dataSize)
for i, value := range s.Coils[register:endRegister] {
if value != 0 {
shift := uint(i) % 8
data[1+i/8] |= byte(1 << shift)
}
}
return data, &protocol.Success
}
// ReadDiscreteInputs 读离散输入寄存器(DI) 功能码0x02
func ReadDiscreteInputs(s *Handler, packet protocol.Packet) ([]byte, *protocol.MError) {
register, numRegs, endRegister := serialize.RegisterAddressAndNumber(packet.GetBody())
if endRegister > 65535 {
return []byte{}, &protocol.IllegalDataAddress
}
dataSize := numRegs / 8
if (numRegs % 8) != 0 {
dataSize++
}
data := make([]byte, 1+dataSize)
data[0] = byte(dataSize)
for i, value := range s.DiscreteInputs[register:endRegister] {
if value != 0 {
shift := uint(i) % 8
data[1+i/8] |= byte(1 << shift)
}
}
return data, &protocol.Success
}
// ReadHoldingRegisters 读保持寄存器AI 功能码0x03
func ReadHoldingRegisters(s *Handler, packet protocol.Packet) ([]byte, *protocol.MError) {
register, numRegs, endRegister := serialize.RegisterAddressAndNumber(packet.GetBody())
if endRegister > 65536 {
return []byte{}, &protocol.IllegalDataAddress
}
return append([]byte{byte(numRegs * 2)}, util.Uint16ToBytes(s.byteOrder, s.HoldingRegisters[register:endRegister])...), &protocol.Success
}
// ReadInputRegisters 读输入寄存器AO 功能码0x04
func ReadInputRegisters(s *Handler, packet protocol.Packet) ([]byte, *protocol.MError) {
register, numRegs, endRegister := serialize.RegisterAddressAndNumber(packet.GetBody())
if endRegister > 65536 {
return []byte{}, &protocol.IllegalDataAddress
}
return append([]byte{byte(numRegs * 2)}, util.Uint16ToBytes(s.byteOrder, s.InputRegisters[register:endRegister])...), &protocol.Success
}
// WriteSingleCoil 写单个线圈寄存器 功能码0x05
func WriteSingleCoil(s *Handler, packet protocol.Packet) ([]byte, *protocol.MError) {
data := packet.GetBody()
register, value := serialize.RegisterAddressAndValue(data)
// TODO Should we use 0 for off and 65,280 (0xFF00) for on?
if value != 0 {
value = 1
}
s.Coils[register] = byte(value)
return data[0:4], &protocol.Success
}
// WriteHoldingRegister 写单个保持寄存器 功能码0x06
func WriteHoldingRegister(s *Handler, packet protocol.Packet) ([]byte, *protocol.MError) {
data := packet.GetBody()
register, value := serialize.RegisterAddressAndValue(data)
s.HoldingRegisters[register] = value
return data[0:4], &protocol.Success
}
// WriteMultipleCoils 写多个线圈寄存器 功能码 0x0F=15
func WriteMultipleCoils(s *Handler, packet protocol.Packet) ([]byte, *protocol.MError) {
data := packet.GetBody()
register, numRegs, endRegister := serialize.RegisterAddressAndNumber(data)
valueBytes := data[5:]
if endRegister > 65536 {
return []byte{}, &protocol.IllegalDataAddress
}
// TODO This is not correct, bits and bytes do not always align
//if len(valueBytes)/2 != numRegs {
// return []byte{}, &IllegalDataAddress
//}
bitCount := 0
for i, value := range valueBytes {
for bitPos := uint(0); bitPos < 8; bitPos++ {
s.Coils[register+(i*8)+int(bitPos)] = util.BitAtPosition(value, bitPos)
bitCount++
if bitCount >= numRegs {
break
}
}
if bitCount >= numRegs {
break
}
}
return data[0:4], &protocol.Success
}
// WriteHoldingRegisters 写入 多个保持寄存器 功能码 0x10=16
func WriteHoldingRegisters(s *Handler, packet protocol.Packet) ([]byte, *protocol.MError) {
pkgData := packet.GetBody()
register, numRegs, _ := serialize.RegisterAddressAndNumber(packet.GetBody())
valueBytes := pkgData[5:]
var err *protocol.MError
var data []byte
if len(valueBytes)/2 != numRegs {
err = &protocol.IllegalDataAddress
}
// Copy data to memory
values := util.BytesToUint16(s.byteOrder, valueBytes)
valuesUpdated := copy(s.HoldingRegisters[register:], values)
if valuesUpdated == numRegs {
err = &protocol.Success
data = pkgData[0:4]
} else {
err = &protocol.IllegalDataAddress
}
return data, err
}

@ -0,0 +1,81 @@
package handler
import (
"encoding/binary"
"git.noahlan.cn/noahlan/nnet/connection"
"git.noahlan.cn/noahlan/nnet/packet"
rt "git.noahlan.cn/noahlan/nnet/router"
"git.noahlan.cn/noahlan/ntool-biz/nmodbus/protocol"
"git.noahlan.cn/noahlan/ntool/nlog"
)
var _ rt.Handler = (*Handler)(nil)
type (
Handler struct {
byteOrder binary.ByteOrder // 字节序
functions map[uint8]FunctionHandler
DiscreteInputs []byte // 离散输入
Coils []byte // 线圈
HoldingRegisters []uint16 // 保持寄存器
InputRegisters []uint16 // 输入寄存器
}
FunctionHandler func(handler *Handler, pkg protocol.Packet) ([]byte, *protocol.MError)
)
func NewHandler(byteOrder binary.ByteOrder) *Handler {
p := &Handler{
byteOrder: byteOrder,
functions: make(map[uint8]FunctionHandler),
DiscreteInputs: make([]byte, 65536),
Coils: make([]byte, 65536),
HoldingRegisters: make([]uint16, 65536),
InputRegisters: make([]uint16, 65536),
}
// 添加功能码对应功能
p.functions[0x01] = ReadCoils
p.functions[0x02] = ReadDiscreteInputs
p.functions[0x03] = ReadHoldingRegisters
p.functions[0x04] = ReadInputRegisters
p.functions[0x05] = WriteSingleCoil
p.functions[0x06] = WriteHoldingRegister
p.functions[0x0F] = WriteMultipleCoils
p.functions[0x10] = WriteHoldingRegisters
return p
}
func (p *Handler) RegisterFunction(fnCode uint8, fn FunctionHandler) {
p.functions[fnCode] = fn
}
func (p *Handler) Handle(conn *connection.Connection, pkg packet.IPacket) {
pp, ok := pkg.(protocol.Packet)
if !ok {
nlog.Error(packet.ErrWrongPacketType)
}
var (
err *protocol.MError
data []byte
)
resp := pp.Copy()
fnCode := pp.GetFunction()
fn, ok := p.functions[fnCode]
if ok {
data, err = fn(p, pp)
resp.SetBody(data)
} else {
err = &protocol.IllegalFunction
}
if err != &protocol.Success {
resp.SetError(err)
}
_ = conn.Send(resp.GetHeader(), resp.GetBody())
}

@ -0,0 +1,52 @@
package nmodbus
import (
"git.noahlan.cn/noahlan/nnet"
"git.noahlan.cn/noahlan/nnet/config"
"git.noahlan.cn/noahlan/nnet/packet"
"git.noahlan.cn/noahlan/ntool-biz/nmodbus/handler"
"git.noahlan.cn/noahlan/ntool-biz/nmodbus/protocol"
"git.noahlan.cn/noahlan/ntool-biz/nmodbus/serialize"
"git.noahlan.cn/noahlan/ntool/npool"
"math"
"time"
)
// NewModbusTCPEngine 新建modbus-tcp服务端引擎
// 默认行为:接收功能码为 0x01 0x02 0x03 0x04 0x05 0x06 0x15 0x16 的消息并自动按照Modbus协议进行处理
//
// Usage:
// ngin, handler := NewModbusTCPEngine(ModbusTCPConf{ByteOrder: binary.BigEndian, Mode: conf.DevMode, Name: "ModbusTCP"})
// defer ngin.Stop()
//
// handler.RegisterFunction(code, func()) // 自定义功能码对应的处理方法
// hr := handler.HoldingRegisters[0] // 获取地址为0x00的保持寄存器 2字节 uint16 (也可以设置,但非线程安全的)
//
// ngin.ListenTCP(config.TCPServerConf{Protocol: "tcp", Addr: "0.0.0.0:5502"})
func NewModbusTCPEngine(conf ModbusTCPConf) (*nnet.Engine, *handler.Handler) {
hd := handler.NewHandler(conf.ByteOrder)
opts := []nnet.RunOption{
nnet.WithPoolCfg(npool.Config{
PoolSize: math.MaxInt32,
ExpiryDuration: time.Second,
PreAlloc: false,
MaxBlockingTasks: 0,
Nonblocking: false,
DisablePurge: false,
}),
withPipeline(),
nnet.WithPackerBuilder(func() packet.Packer {
return protocol.NewTCPPacker(conf.ByteOrder)
}),
nnet.WithRouter(protocol.NewRouter(hd)),
}
// 设置字节序
serialize.SetByteOrder(conf.ByteOrder)
ngin := nnet.NewEngine(config.EngineConf{
Mode: conf.Mode,
Name: conf.Name,
}, opts...)
return ngin, hd
}

@ -0,0 +1,36 @@
package nmodbus_test
import (
"encoding/binary"
"git.noahlan.cn/noahlan/nnet/config"
"git.noahlan.cn/noahlan/ntool-biz/nmodbus"
"git.noahlan.cn/noahlan/ntool-biz/nmodbus/util"
"testing"
)
func TestA(t *testing.T) {
ngin, handler := nmodbus.NewModbusTCPEngine(nmodbus.ModbusTCPConf{
EngineConf: config.EngineConf{
Mode: "dev",
Name: "ModbusTCP",
},
ByteOrder: binary.BigEndian,
})
defer ngin.Stop()
// read:0x03 write:0x06
handler.HoldingRegisters[0] = 0b00000011 // > 0000000000000011 = 0x03 = 3
// 1~2 浮点 [2]uint16
copy(handler.HoldingRegisters[1:2], util.Float32ToUint16(binary.BigEndian, 1.25))
handler.HoldingRegisters[3] = 21
// read:0x04 non-write
handler.InputRegisters[0] = 0b00000011
_ = ngin.ListenTCP(config.TCPServerConf{
Protocol: "tcp",
Addr: "0.0.0.0:5502",
})
}

@ -0,0 +1,15 @@
package nmodbus
import (
"git.noahlan.cn/noahlan/nnet"
"git.noahlan.cn/noahlan/nnet/connection"
)
func withPipeline() nnet.RunOption {
return func(ngin *nnet.Engine) {
ngin.Pipeline().Inbound().PushFront(func(conn *connection.Connection, v interface{}) error {
conn.SetStatus(connection.StatusWorking)
return nil
})
}
}

@ -0,0 +1,65 @@
package protocol
import "fmt"
type MError uint8
var (
// Success 成功
Success MError = 0
// IllegalFunction 接收到的功能码不可识别或服务端(从机)未实现其功能
IllegalFunction MError = 1
// IllegalDataAddress 数据地址不可接收
IllegalDataAddress MError = 2
// IllegalDataValue 数据不被识别
IllegalDataValue MError = 3
// SlaveDeviceFailure 不可恢复的错误
SlaveDeviceFailure MError = 4
// AcknowledgeSlave 已接受并正在处理的耗时请求防止发生超时错误主站可以发送一个poll(pgc)来确定消息是否处理完成
AcknowledgeSlave MError = 5
// SlaveDeviceBusy 从站正在处理耗时指令,从站忙
SlaveDeviceBusy MError = 6
// NegativeAcknowledge 从站无法处理此请求
NegativeAcknowledge MError = 7
// MemoryParityError 从站检测到奇偶校验错误,但仍然需要提供服务,故此返回此错误
MemoryParityError MError = 8
// GatewayPathUnavailable Modbus网关配置错误
GatewayPathUnavailable MError = 10
// GatewayTargetDeviceFailedToRespond Modbus网关从机无法响应
GatewayTargetDeviceFailedToRespond MError = 11
)
func (e MError) Error() string {
return fmt.Sprintf("%d", e)
}
func (e MError) String() string {
var str string
switch e {
case Success:
str = fmt.Sprintf("Success")
case IllegalFunction:
str = fmt.Sprintf("IllegalFunction")
case IllegalDataAddress:
str = fmt.Sprintf("IllegalDataAddress")
case IllegalDataValue:
str = fmt.Sprintf("IllegalDataValue")
case SlaveDeviceFailure:
str = fmt.Sprintf("SlaveDeviceFailure")
case AcknowledgeSlave:
str = fmt.Sprintf("AcknowledgeSlave")
case SlaveDeviceBusy:
str = fmt.Sprintf("SlaveDeviceBusy")
case NegativeAcknowledge:
str = fmt.Sprintf("NegativeAcknowledge")
case MemoryParityError:
str = fmt.Sprintf("MemoryParityError")
case GatewayPathUnavailable:
str = fmt.Sprintf("GatewayPathUnavailable")
case GatewayTargetDeviceFailedToRespond:
str = fmt.Sprintf("GatewayTargetDeviceFailedToRespond")
default:
str = fmt.Sprintf("unknown")
}
return str
}

@ -0,0 +1,80 @@
package protocol
import (
"bytes"
"encoding/binary"
"fmt"
"git.noahlan.cn/noahlan/nnet/packet"
"git.noahlan.cn/noahlan/ntool-biz/nmodbus/util"
"git.noahlan.cn/noahlan/ntool/nlog"
)
type RTUPacker struct {
buf *bytes.Buffer
byteOrder binary.ByteOrder
}
func NewRTUPacker(byteOrder binary.ByteOrder) *RTUPacker {
p := &RTUPacker{
buf: bytes.NewBuffer(nil),
byteOrder: byteOrder,
}
return p
}
func (d *RTUPacker) Pack(header interface{}, data []byte) ([]byte, error) {
modbusHeader, ok := header.(*ModbusHeader)
if !ok {
return nil, packet.ErrWrongPacketType
}
bs := make([]byte, 2)
bs[0] = modbusHeader.Address
bs[1] = modbusHeader.Function
bs = append(bs, data...)
// calc CRC
pLen := len(bs)
crc := util.Checksum(bs[0:pLen])
bs = append(bs, []byte{0, 0}...)
d.byteOrder.PutUint16(bs[pLen:pLen+2], crc)
return bs, nil
}
func (d *RTUPacker) Unpack(data []byte) ([]packet.IPacket, error) {
d.buf.Write(data)
nlog.Debugf("接收RTU数据: %x", data)
var (
packets []packet.IPacket
err error
)
pLen := d.buf.Len()
if pLen < 5 {
return packets, fmt.Errorf("RTU Frame error: packet less than 5 bytes")
}
// ModbusRTU 串口通讯 发送端在发送报文时一帧结束后必须有3.5个字符周期的间隔时间4.01ms
// 本质上不会发生粘包的情况,故此可以直接读取一个完整的包
// 否则无法处理粘包状况,这是由于协议帧没有描述具体包长度的字节
bs := d.buf.Next(d.buf.Len())
// check crc 取[0:len-2]进行计算,与最后一个字节对比最后一个字节为crc校验码
crcExpect := d.byteOrder.Uint16(bs[pLen-2 : pLen])
crcCalc := util.Checksum(bs[0 : pLen-2])
if crcCalc != crcExpect {
return nil, fmt.Errorf("RTU Frame error: CRC (expected 0x%x, got 0x%x)", crcExpect, crcCalc)
}
p := NewRTUPacket()
p.Address = bs[0]
p.Function = bs[1]
p.Data = bs[2 : pLen-2]
p.CRC = crcExpect
packets = append(packets, p)
d.buf.Reset()
return packets, err
}

@ -0,0 +1,117 @@
package protocol
import (
"bytes"
"encoding/binary"
"fmt"
"git.noahlan.cn/noahlan/nnet/packet"
"git.noahlan.cn/noahlan/ntool/nlog"
)
type TCPPackerOption func(*TCPPacker)
type TCPPacker struct {
buf *bytes.Buffer
header *TCPHeader
size int // 最近一次读取的 body size
byteOrder binary.ByteOrder
}
const (
tcpHeadLength = 2 + 2 + 2
tcpMaxPacketSize = 64 * 1024
)
func NewTCPPacker(byteOrder binary.ByteOrder) *TCPPacker {
p := &TCPPacker{
buf: bytes.NewBuffer(nil),
byteOrder: byteOrder,
}
p.resetFlags()
return p
}
func (d *TCPPacker) resetFlags() {
d.header = nil
d.size = -1
}
func (d *TCPPacker) Pack(header interface{}, data []byte) ([]byte, error) {
tcpHeader, ok := header.(*TCPHeader)
if !ok {
return nil, packet.ErrWrongPacketType
}
bs := make([]byte, 8)
d.byteOrder.PutUint16(bs[0:2], tcpHeader.TransactionIdentifier)
d.byteOrder.PutUint16(bs[2:4], tcpHeader.ProtocolIdentifier)
d.byteOrder.PutUint16(bs[4:6], uint16(2+len(data)))
bs[6] = tcpHeader.Address
bs[7] = tcpHeader.Function
bs = append(bs, data...)
return bs, nil
}
func (d *TCPPacker) Unpack(data []byte) ([]packet.IPacket, error) {
d.buf.Write(data)
nlog.Debugf("接收TCP数据: %x", data)
var (
packets []packet.IPacket
err error
)
if d.buf.Len() < 9 {
return packets, fmt.Errorf("TCP Frame error: packet less than 9 bytes")
}
if d.size < 0 {
if err = d.readHeader(); err != nil {
return nil, err
}
}
for (d.size - 2) <= d.buf.Len() {
p := NewTCPPacket()
p.TCPHeader = d.header
p.Data = d.buf.Next(d.size - 2)
packets = append(packets, p)
if d.buf.Len() < (tcpHeadLength + 2) {
d.resetFlags()
break
}
if err = d.readHeader(); err != nil {
return packets, err
}
}
if packets == nil || len(packets) == 0 {
d.resetFlags()
d.buf.Reset()
}
return packets, err
}
func (d *TCPPacker) readHeader() error {
header := d.buf.Next(tcpHeadLength + 2)
d.header = &TCPHeader{
TransactionIdentifier: d.byteOrder.Uint16(header[0:2]),
ProtocolIdentifier: d.byteOrder.Uint16(header[2:4]),
Length: d.byteOrder.Uint16(header[4:6]),
ModbusHeader: ModbusHeader{
Address: header[6],
Function: header[7],
},
}
d.size = int(d.header.Length)
// 最大包限定
if d.size > tcpMaxPacketSize {
return packet.ErrPacketSizeExceed
}
return nil
}

@ -0,0 +1,33 @@
package protocol
import (
"git.noahlan.cn/noahlan/nnet/packet"
)
type (
Packet interface {
packet.IPacket
Copy() Packet
Modbus
SetBody(data []byte)
SetError(err *MError)
}
Modbus interface {
GetFunction() uint8
GetAddress() uint8
}
ModbusHeader struct {
Address uint8
Function uint8
}
)
func GetError(pkg Packet) (err MError) {
function := pkg.GetFunction()
if (function & 0x80) != 0 {
err = MError(pkg.GetBody()[0])
}
return
}

@ -0,0 +1,49 @@
package protocol
var _ Packet = (*RTUPacket)(nil)
type (
RTUPacket struct {
*ModbusHeader
Data []byte // 数据
CRC uint16 // CRC校验码
}
)
func (p *RTUPacket) Copy() Packet {
c := *p
return &c
}
func NewRTUPacket() *RTUPacket {
return &RTUPacket{ModbusHeader: &ModbusHeader{}}
}
func (p *RTUPacket) GetHeader() interface{} {
return p.ModbusHeader
}
func (p *RTUPacket) GetLen() uint64 {
return uint64(len(p.Data))
}
func (p *RTUPacket) GetBody() []byte {
return p.Data
}
func (p *RTUPacket) GetFunction() uint8 {
return p.Function
}
func (p *RTUPacket) GetAddress() uint8 {
return p.Address
}
func (p *RTUPacket) SetBody(data []byte) {
p.Data = data
}
func (p *RTUPacket) SetError(err *MError) {
p.Function = p.Function | 0x80
p.Data = []byte{byte(*err)}
}

@ -0,0 +1,68 @@
package protocol
var _ Packet = (*TCPPacket)(nil)
type (
TCPHeader struct {
TransactionIdentifier uint16
ProtocolIdentifier uint16
Length uint16
ModbusHeader
}
// TCPPacket modbus-tcp 数据帧相对于rtu添加了协议头减少了crc
TCPPacket struct {
*TCPHeader
Data []byte
}
)
func NewTCPPacket() *TCPPacket {
return &TCPPacket{
TCPHeader: &TCPHeader{
TransactionIdentifier: 0,
ProtocolIdentifier: 0,
Length: 0,
ModbusHeader: ModbusHeader{},
},
}
}
func (p *TCPPacket) Copy() Packet {
c := *p
return &c
}
func (p *TCPPacket) GetHeader() interface{} {
return p.TCPHeader
}
func (p *TCPPacket) GetLen() uint64 {
return uint64(p.Length)
}
func (p *TCPPacket) GetBody() []byte {
return p.Data
}
func (p *TCPPacket) GetAddress() uint8 {
return p.Address
}
func (p *TCPPacket) GetFunction() uint8 {
return p.Function
}
func (p *TCPPacket) SetBody(data []byte) {
p.Data = data
p.calcLength()
}
func (p *TCPPacket) SetError(err *MError) {
p.Function = p.Function | 0x80
p.Data = []byte{byte(*err)}
p.calcLength()
}
func (p *TCPPacket) calcLength() {
p.Length = uint16(len(p.Data) + 2)
}

@ -0,0 +1,29 @@
package protocol
import (
"git.noahlan.cn/noahlan/nnet/connection"
"git.noahlan.cn/noahlan/nnet/packet"
rt "git.noahlan.cn/noahlan/nnet/router"
)
type Router struct {
handler rt.Handler
}
func NewRouter(handler rt.Handler) rt.Router {
return &Router{
handler: handler,
}
}
func (r *Router) Handle(c *connection.Connection, pkg packet.IPacket) {
r.handler.Handle(c, pkg)
}
func (r *Router) Register(_ interface{}, handler rt.Handler) error {
r.handler = handler
return nil
}
func (r *Router) SetNotFoundHandler(_ rt.Handler) {
}

@ -0,0 +1,43 @@
package serialize
import (
"encoding/binary"
)
var serializer = NewSerializer(binary.BigEndian)
func defaultSerializer() *Serializer {
return serializer
}
func SetByteOrder(order binary.ByteOrder) {
if order == nil {
return
}
defaultSerializer().byteOrder = order
}
// RegisterAddressAndNumber 返回数据帧中的 寄存器起始地址 数量 计算后的结束地址
func RegisterAddressAndNumber(data []byte) (register int, numRegs int, endRegister int) {
return defaultSerializer().RegisterAddressAndNumber(data)
}
// RegisterAddressAndValue 获取数据帧中的 寄存器起始地址 数据
func RegisterAddressAndValue(data []byte) (register int, value uint16) {
return defaultSerializer().RegisterAddressAndValue(data)
}
// WriteWithRegisterAndNumber 生成 寄存器起始地址 和 数量
func WriteWithRegisterAndNumber(register uint16, number uint16) []byte {
return defaultSerializer().WriteWithRegisterAndNumber(register, number)
}
// WriteWithRegisterAndNumberAndValue 写入 寄存器起始地址 数量 数据
func WriteWithRegisterAndNumberAndValue(register uint16, number uint16, values []uint16) []byte {
return defaultSerializer().WriteWithRegisterAndNumberAndValue(register, number, values)
}
// WriteWithRegisterAndNumberAndBytes 写入 寄存器起始地址 数量 数据
func WriteWithRegisterAndNumberAndBytes(register uint16, number uint16, bytes []byte) []byte {
return defaultSerializer().WriteWithRegisterAndNumberAndBytes(register, number, bytes)
}

@ -0,0 +1,62 @@
package serialize
import (
"encoding/binary"
"git.noahlan.cn/noahlan/ntool-biz/nmodbus/util"
)
type Serializer struct {
byteOrder binary.ByteOrder
}
// NewSerializer 新增序列化/反序列化器
func NewSerializer(byteOrder binary.ByteOrder) *Serializer {
tmp := &Serializer{byteOrder: byteOrder}
if tmp.byteOrder == nil {
tmp.byteOrder = binary.BigEndian
}
return tmp
}
// RegisterAddressAndNumber 返回数据帧中的 寄存器起始地址 数量 计算后的结束地址
func (s *Serializer) RegisterAddressAndNumber(data []byte) (register int, numRegs int, endRegister int) {
register = int(s.byteOrder.Uint16(data[0:2]))
numRegs = int(s.byteOrder.Uint16(data[2:4]))
endRegister = register + numRegs
return
}
// RegisterAddressAndValue 获取数据帧中的 寄存器起始地址 数据
func (s *Serializer) RegisterAddressAndValue(data []byte) (register int, value uint16) {
register = int(s.byteOrder.Uint16(data[0:2]))
value = s.byteOrder.Uint16(data[2:4])
return
}
// WriteWithRegisterAndNumber 生成 寄存器起始地址 和 数量
func (s *Serializer) WriteWithRegisterAndNumber(register uint16, number uint16) []byte {
data := make([]byte, 4)
s.byteOrder.PutUint16(data[0:2], register)
s.byteOrder.PutUint16(data[2:4], number)
return data
}
// WriteWithRegisterAndNumberAndValue 写入 寄存器起始地址 数量 数据
func (s *Serializer) WriteWithRegisterAndNumberAndValue(register uint16, number uint16, values []uint16) []byte {
data := make([]byte, 5+len(values)*2)
s.byteOrder.PutUint16(data[0:2], register)
s.byteOrder.PutUint16(data[2:4], number)
data[4] = uint8(len(values) * 2)
copy(data[5:], util.Uint16ToBytes(s.byteOrder, values))
return data
}
// WriteWithRegisterAndNumberAndBytes 写入 寄存器起始地址 数量 数据
func (s *Serializer) WriteWithRegisterAndNumberAndBytes(register uint16, number uint16, bytes []byte) []byte {
data := make([]byte, 5+len(bytes))
s.byteOrder.PutUint16(data[0:2], register)
s.byteOrder.PutUint16(data[2:4], number)
data[4] = byte(len(bytes))
copy(data[5:], bytes)
return data
}

@ -0,0 +1,42 @@
package util
import (
"encoding/binary"
"math"
)
func Float32ToUint16(order binary.ByteOrder, val float32) []uint16 {
bs := Float32ToBytes(order, val)
return BytesToUint16(order, bs)
}
func Float32ToBytes(order binary.ByteOrder, val float32) []byte {
bits := math.Float32bits(val)
bs := make([]byte, 4)
order.PutUint32(bs, bits)
return bs
}
// BytesToUint16 转换byte数组到uint16数组自定义端序
func BytesToUint16(byteOrder binary.ByteOrder, bytes []byte) []uint16 {
values := make([]uint16, len(bytes)/2)
for i := range values {
values[i] = byteOrder.Uint16(bytes[i*2 : (i+1)*2])
}
return values
}
// Uint16ToBytes 转换uint16数组到byte数组大端序
func Uint16ToBytes(byteOrder binary.ByteOrder, values []uint16) []byte {
bytes := make([]byte, len(values)*2)
for i, value := range values {
byteOrder.PutUint16(bytes[i*2:(i+1)*2], value)
}
return bytes
}
// BitAtPosition 获取某字节指定 位 的值
func BitAtPosition(value uint8, pos uint) uint8 {
return (value >> pos) & 0x01
}

@ -0,0 +1,43 @@
package util
import "sync"
var crcTable []uint16
var mu sync.Mutex
// Checksum 计算modbus的crc
func Checksum(data []byte) (crc uint16) {
if crcTable == nil {
mu.Lock()
if crcTable == nil {
initCrcTable()
}
mu.Unlock()
}
crc = 0xffff
for _, v := range data {
crc = (crc >> 8) ^ crcTable[(crc^uint16(v))&0x00FF]
}
return crc
}
// initCrcTable 初始化crcTable
func initCrcTable() {
crc16IBM := uint16(0xA001)
crcTable = make([]uint16, 256)
for i := uint16(0); i < 256; i++ {
crc := uint16(0)
c := i
for j := uint16(0); j < 8; j++ {
if ((crc ^ c) & 0x0001) > 0 {
crc = (crc >> 1) ^ crc16IBM
} else {
crc = crc >> 1
}
c = c >> 1
}
crcTable[i] = crc
}
}

@ -0,0 +1,12 @@
package util
import (
"git.noahlan.cn/noahlan/ntool/ntest/assert"
"testing"
)
func TestCRC(t *testing.T) {
got := Checksum([]byte{0x01, 0x04, 0x02, 0xFF, 0xFF})
expect := uint16(0x80B8)
assert.Equal(t, expect, got)
}

@ -0,0 +1,58 @@
package logz
import (
"fmt"
"git.noahlan.cn/noahlan/ntool/nlog"
"github.com/zeromicro/go-zero/core/logx"
)
type NLogWriter struct {
}
func NewWriter() logx.Writer {
return &NLogWriter{}
}
func (w *NLogWriter) Alert(v any) {
nlog.Alert(fmt.Sprintf("%s", v))
}
func (w *NLogWriter) Close() error {
return nlog.GetWriter().Close()
}
func (w *NLogWriter) Debug(v any, fields ...logx.LogField) {
nlog.GetWriter().Debug(v, toNLogField(fields...)...)
}
func (w *NLogWriter) Error(v any, fields ...logx.LogField) {
nlog.GetWriter().Error(v, toNLogField(fields...)...)
}
func (w *NLogWriter) Info(v any, fields ...logx.LogField) {
nlog.GetWriter().Info(v, toNLogField(fields...)...)
}
func (w *NLogWriter) Severe(v any) {
nlog.GetWriter().Severe(v)
}
func (w *NLogWriter) Slow(v any, fields ...logx.LogField) {
nlog.GetWriter().Slow(v, toNLogField(fields...)...)
}
func (w *NLogWriter) Stack(v any) {
nlog.GetWriter().Stack(v)
}
func (w *NLogWriter) Stat(v any, fields ...logx.LogField) {
nlog.GetWriter().Stat(v, toNLogField(fields...)...)
}
func toNLogField(fields ...logx.LogField) []nlog.LogField {
zapFields := make([]nlog.LogField, 0, len(fields))
for _, f := range fields {
zapFields = append(zapFields, nlog.Field(f.Key, f.Value))
}
return zapFields
}

@ -0,0 +1,26 @@
package logz
import (
"git.noahlan.cn/noahlan/ntool/nlog"
"github.com/zeromicro/go-zero/core/logx"
)
// WrapConf 转换 logx.LogConf 到 nlog.LogConf
func WrapConf(conf logx.LogConf) nlog.LogConf {
return nlog.LogConf{
ServiceName: conf.ServiceName,
Mode: conf.Mode,
Encoding: conf.Encoding,
TimeFormat: conf.TimeFormat,
Path: conf.Path,
Level: conf.Level,
MaxContentLength: conf.MaxContentLength,
Compress: conf.Compress,
Stat: conf.Stat,
KeepDays: conf.KeepDays,
StackCooldownMillis: conf.StackCooldownMillis,
MaxBackups: conf.MaxBackups,
MaxSize: conf.MaxSize,
Rotation: conf.Rotation,
}
}

@ -0,0 +1,38 @@
package statusz
import (
"git.noahlan.cn/noahlan/ntool-biz/core/i18n"
"git.noahlan.cn/noahlan/ntool-biz/core/nstatus"
"git.noahlan.cn/noahlan/ntool-biz/core/nstatus/code"
"git.noahlan.cn/noahlan/ntool-biz/core/nstatus/msg"
"github.com/zeromicro/go-zero/rest/httpx"
"net/http"
)
// ResponseHandler API执行结果处理统一返回值类型与结构
func ResponseHandler(req *http.Request, w http.ResponseWriter, trans bool, resp any, err error) {
if err == nil {
r := nstatus.NewResultWithData(code.StatusOK, msg.Success, resp)
if trans {
r.Msg = i18n.Trans(req.Context(), r.Msg)
}
httpx.WriteJson(w, code.StatusOK, r)
return
}
result := nstatus.ConvertErr(err)
if result == nil {
// 不可能发生此情况,这里处理是以防万一
result = nstatus.NewResult(code.StatusFailed, msg.Failed)
}
result.Data = resp
if trans {
result.Msg = i18n.Trans(req.Context(), result.Msg)
}
c := result.Code
// 判断code如果不是http-status全部使用500
if result.Code > http.StatusNetworkAuthenticationRequired {
c = http.StatusInternalServerError
}
httpx.WriteJson(w, c, result)
}

@ -0,0 +1,72 @@
package statusz
import (
"encoding/json"
"fmt"
"git.noahlan.cn/noahlan/ntool-biz/core/nstatus"
"reflect"
"testing"
"unsafe"
)
type Dat struct {
Nickname string `json:"nickname"`
}
func TestA(t *testing.T) {
type A struct {
Code int64 `json:"code"` // 8
Msg string `json:"msg"` // 16
Data any `json:"data"`
}
type Response struct {
A
Data Dat
}
r := new(Response)
r.Code = 123
r.Msg = "222"
r.Data = Dat{Nickname: "2333"}
marshal, err := json.Marshal(r)
if err != nil {
return
}
fmt.Println(string(marshal))
fmt.Println(r)
fmt.Printf("r:%p code:%p data:%p\n", r, &r.Code, &r.Data)
a := (*nstatus.Result)(unsafe.Pointer(r))
trans(r)
fmt.Println(a)
fmt.Printf("a:%p code:%p data:%p\n", a, &a.Code, &a.Data)
}
func trans(resp any) {
fmt.Println()
type ApiResult struct {
Code int64 `json:"code"` // 8
Msg string `json:"msg"` // 16
Data any `json:"data"`
}
//ptr :=
//u := (*[2]uintptr)(unsafe.Pointer(&resp))[1]
u := (*ApiResult)(reflect.ValueOf(resp).UnsafePointer())
dataPtr := unsafe.Add(reflect.ValueOf(resp).UnsafePointer(), 40)
fmt.Println(dataPtr)
u.Data = unsafe.Alignof(dataPtr)
fmt.Println(u)
fmt.Printf("u:%p code:%p data:%p\n", u, &u.Code, &u.Data)
fmt.Println()
}

@ -0,0 +1,9 @@
package config
import {{.authImport}}
type Config struct {
rest.RestConf
{{.auth}}
{{.jwtTrans}}
}

@ -0,0 +1,17 @@
package svc
import (
{{.configImport}}
)
type ServiceContext struct {
Config {{.config}}
{{.middleware}}
}
func NewServiceContext(c {{.config}}) *ServiceContext {
return &ServiceContext{
Config: c,
{{.middlewareAssignment}}
}
}

@ -0,0 +1,3 @@
Name: {{.serviceName}}
Host: {{.host}}
Port: {{.port}}

@ -0,0 +1,23 @@
package {{.PkgName}}
import (
"net/http"
"git.noahlan.cn/noahlan/ntools-go/zero/respz"
"github.com/zeromicro/go-zero/rest/httpx"
{{.ImportPackages}}
)
func {{.HandlerName}}(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
{{if .HasRequest}}var req types.{{.RequestType}}
if err := httpx.Parse(r, &req); err != nil {
httpx.Error(w, err)
return
}
{{end}}l := {{.LogicName}}.New{{.LogicType}}(r.Context(), svcCtx)
{{if .HasResp}}resp, {{end}}err := l.{{.Call}}({{if .HasRequest}}&req{{end}})
{{if .HasResp}}respz.ResponseHandler(r, w, resp, err){{else}}respz.ResponseHandler(r, w, nil, err){{end}}
}
}

@ -0,0 +1,25 @@
package {{.pkgName}}
import (
{{.imports}}
)
type {{.logic}} struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func New{{.logic}}(ctx context.Context, svcCtx *svc.ServiceContext) *{{.logic}} {
return &{{.logic}}{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *{{.logic}}) {{.function}}({{.request}}) {{.responseType}} {
// todo: add your logic here and delete this line
{{.returnString}}
}

@ -0,0 +1,26 @@
package main
import (
"flag"
"fmt"
{{.importPackages}}
)
var configFile = flag.String("f", "etc/{{.serviceName}}.yaml", "the config file")
func main() {
flag.Parse()
var c config.Config
conf.MustLoad(*configFile, &c)
server := rest.MustNewServer(c.RestConf)
defer server.Stop()
ctx := svc.NewServiceContext(c)
handler.RegisterHandlers(server, ctx)
fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
server.Start()
}

@ -0,0 +1,19 @@
package middleware
import "net/http"
type {{.name}} struct {
}
func New{{.name}}() *{{.name}} {
return &{{.name}}{}
}
func (m *{{.name}})Handle(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// TODO generate middleware implement function, delete after code implementation
// Passthrough to next handler if need
next(w, r)
}
}

@ -0,0 +1,4 @@
server.AddRoutes(
{{.routes}} {{.jwt}}{{.signature}} {{.prefix}} {{.timeout}}
)

@ -0,0 +1,13 @@
// Code generated by goctl. DO NOT EDIT.
package handler
import (
"net/http"{{if .hasTimeout}}
"time"{{end}}
{{.importPackages}}
)
func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
{{.routesAdditions}}
}

@ -0,0 +1,24 @@
syntax = "v1"
info (
title: // TODO: add title
desc: // TODO: add description
author: "{{.gitUser}}"
email: "{{.gitEmail}}"
)
type request {
// TODO: add members here and delete this comment
}
type response {
// TODO: add members here and delete this comment
}
service {{.serviceName}} {
@handler GetUser // TODO: set handler name and delete this comment
get /users/id/:userId(request) returns(response)
@handler CreateUser // TODO: set handler name and delete this comment
post /users/create(request)
}

@ -0,0 +1,6 @@
// Code generated by goctl. DO NOT EDIT.
package types{{if .containsTime}}
import (
"time"
){{end}}
{{.types}}

@ -0,0 +1,33 @@
FROM golang:{{.Version}}alpine AS builder
LABEL stage=gobuilder
ENV CGO_ENABLED 0
{{if .Chinese}}ENV GOPROXY https://goproxy.cn,direct
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
{{end}}{{if .HasTimezone}}
RUN apk update --no-cache && apk add --no-cache tzdata
{{end}}
WORKDIR /build
ADD go.mod .
ADD go.sum .
RUN go mod download
COPY . .
{{if .Argument}}COPY {{.GoRelPath}}/etc /app/etc
{{end}}RUN go build -ldflags="-s -w" -o /app/{{.ExeFile}} {{.GoRelPath}}/{{.GoFile}}
FROM {{.BaseImage}}
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
{{if .HasTimezone}}COPY --from=builder /usr/share/zoneinfo/{{.Timezone}} /usr/share/zoneinfo/{{.Timezone}}
ENV TZ {{.Timezone}}
{{end}}
WORKDIR /app
COPY --from=builder /app/{{.ExeFile}} /app/{{.ExeFile}}{{if .Argument}}
COPY --from=builder /app/etc /app/etc{{end}}
{{if .HasPort}}
EXPOSE {{.Port}}
{{end}}
CMD ["./{{.ExeFile}}"{{.Argument}}]

@ -0,0 +1,115 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{.Name}}
namespace: {{.Namespace}}
labels:
app: {{.Name}}
spec:
replicas: {{.Replicas}}
revisionHistoryLimit: {{.Revisions}}
selector:
matchLabels:
app: {{.Name}}
template:
metadata:
labels:
app: {{.Name}}
spec:{{if .ServiceAccount}}
serviceAccountName: {{.ServiceAccount}}{{end}}
containers:
- name: {{.Name}}
image: {{.Image}}
lifecycle:
preStop:
exec:
command: ["sh","-c","sleep 5"]
ports:
- containerPort: {{.Port}}
readinessProbe:
tcpSocket:
port: {{.Port}}
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
tcpSocket:
port: {{.Port}}
initialDelaySeconds: 15
periodSeconds: 20
resources:
requests:
cpu: {{.RequestCpu}}m
memory: {{.RequestMem}}Mi
limits:
cpu: {{.LimitCpu}}m
memory: {{.LimitMem}}Mi
volumeMounts:
- name: timezone
mountPath: /etc/localtime
{{if .Secret}}imagePullSecrets:
- name: {{.Secret}}
{{end}}volumes:
- name: timezone
hostPath:
path: /usr/share/zoneinfo/Asia/Shanghai
---
apiVersion: v1
kind: Service
metadata:
name: {{.Name}}-svc
namespace: {{.Namespace}}
spec:
ports:
{{if .UseNodePort}}- nodePort: {{.NodePort}}
port: {{.Port}}
protocol: TCP
targetPort: {{.Port}}
type: NodePort{{else}}- port: {{.Port}}{{end}}
selector:
app: {{.Name}}
---
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
name: {{.Name}}-hpa-c
namespace: {{.Namespace}}
labels:
app: {{.Name}}-hpa-c
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{.Name}}
minReplicas: {{.MinReplicas}}
maxReplicas: {{.MaxReplicas}}
metrics:
- type: Resource
resource:
name: cpu
targetAverageUtilization: 80
---
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
name: {{.Name}}-hpa-m
namespace: {{.Namespace}}
labels:
app: {{.Name}}-hpa-m
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{.Name}}
minReplicas: {{.MinReplicas}}
maxReplicas: {{.MaxReplicas}}
metrics:
- type: Resource
resource:
name: memory
targetAverageUtilization: 80

@ -0,0 +1,37 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: {{.Name}}
namespace: {{.Namespace}}
spec:
successfulJobsHistoryLimit: {{.SuccessfulJobsHistoryLimit}}
schedule: "{{.Schedule}}"
jobTemplate:
spec:
template:
spec:{{if .ServiceAccount}}
serviceAccountName: {{.ServiceAccount}}{{end}}
{{end}}containers:
- name: {{.Name}}
image: # todo image url
resources:
requests:
cpu: {{.RequestCpu}}m
memory: {{.RequestMem}}Mi
limits:
cpu: {{.LimitCpu}}m
memory: {{.LimitMem}}Mi
command:
- ./{{.ServiceName}}
- -f
- ./{{.Name}}.yaml
volumeMounts:
- name: timezone
mountPath: /etc/localtime
imagePullSecrets:
- name: # registry secret, if no, remove this
restartPolicy: OnFailure
volumes:
- name: timezone
hostPath:
path: /usr/share/zoneinfo/Asia/Shanghai

@ -0,0 +1,14 @@
func (m *default{{.upperStartCamelObject}}Model) Delete(ctx context.Context, tx *gorm.DB, {{.lowerStartCamelPrimaryKey}} {{.dataType}}) error {
{{if .withCache}}{{if .containsIndexCache}}data, err:=m.FindOne(ctx, {{.lowerStartCamelPrimaryKey}})
if err != nil {
return err
}
{{end}} {{.keys}}
err {{if .containsIndexCache}}={{else}}:={{end}} m.ExecCtx(ctx, func(conn *gorm.DB) error {
return gormx.WithTx(ctx, conn, tx).Delete(&{{.upperStartCamelObject}}{}, {{.lowerStartCamelPrimaryKey}}).Error
}, {{.keyValues}}){{else}} err:= gormx.WithTx(ctx, m.DB, tx).Delete(&{{.upperStartCamelObject}}{}, {{.lowerStartCamelPrimaryKey}}).Error
{{end}}
return err
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save