From 0884384e912fe0e862b3a4f7afe43661e2c9f7dc Mon Sep 17 00:00:00 2001 From: "yan.y" Date: Wed, 27 Mar 2024 23:25:08 +0800 Subject: [PATCH] Initial commit --- .gitignore | 24 + business/api/auth.go | 110 +++ business/api/common.go | 119 +++ business/api/control.go | 28 + business/api/goods.go | 25 + business/api/init.go | 77 ++ business/api/order.go | 25 + business/api/pet.go | 159 ++++ business/api/service.go | 25 + business/build_linux.bat | 6 + business/build_windows.bat | 6 + business/config/mysql.json | 11 + business/config/redis.json | 6 + business/config/web.json | 32 + business/config/wxconfig.json | 7 + business/config/zap.json | 11 + business/doc/def.sql | 162 ++++ business/go.mod | 95 ++ business/go.sum | 312 +++++++ business/main.go | 21 + business/models/dataModel.go | 135 +++ business/utils/wechat.go | 91 ++ core/g/constant.go | 14 + core/g/constant_test.go | 26 + core/g/var.go | 14 + core/go.mod | 146 +++ core/go.sum | 861 ++++++++++++++++++ core/helper/arr/array.go | 62 ++ core/helper/arr/array_test.go | 90 ++ core/helper/arr/unit_join.go | 17 + core/helper/arr/unit_join_test.go | 47 + core/helper/dir/dir.go | 286 ++++++ core/helper/global/ip.go | 85 ++ core/helper/global/ip_test.go | 62 ++ core/helper/global/ssh.go | 214 +++++ core/helper/global/ssh_test.go | 111 +++ core/helper/global/time.go | 121 +++ core/helper/http/http.go | 296 ++++++ core/helper/http/http_test.go | 167 ++++ core/helper/http/main_test.go | 12 + core/helper/ping/example/main.go | 15 + core/helper/ping/ping.go | 36 + core/helper/ping/ping_test.go | 17 + core/helper/str/string.go | 63 ++ core/helper/str/struct.go | 30 + core/helper/str/struct_test.go | 26 + core/helper/sys/cmd_darwin.go | 76 ++ core/helper/sys/cmd_linux.go | 76 ++ core/helper/sys/cmd_windows.go | 80 ++ core/helper/sys/kill.go | 24 + core/helper/sys/net.go | 134 +++ core/helper/sys/proc.go | 55 ++ core/migration/migrate.go | 118 +++ core/migration/migrate_test.go | 133 +++ core/seed/seed.go | 19 + core/seed/seed_test.go | 18 + core/server/cache/cmd.go | 75 ++ core/server/cache/cmd_test.go | 27 + core/server/cache/config.go | 70 ++ core/server/cache/config_test.go | 16 + core/server/cache/index.go | 72 ++ core/server/cache/index_test.go | 87 ++ core/server/casbin/config.go | 49 + core/server/casbin/config_test.go | 29 + core/server/casbin/index.go | 75 ++ core/server/casbin/index_test.go | 37 + core/server/casbin/main_test.go | 41 + core/server/cron_server/index.go | 34 + core/server/cron_server/index_test.go | 16 + core/server/database/cmd.go | 102 +++ core/server/database/config.go | 102 +++ core/server/database/config/mysql.json | 11 + core/server/database/config/zap.json | 11 + core/server/database/index.go | 129 +++ core/server/database/index_test.go | 35 + core/server/database/internal.go | 187 ++++ core/server/database/main_test.go | 38 + core/server/database/orm/struct.go | 113 +++ core/server/database/scope/find.go | 43 + core/server/database/scope/paginate.go | 55 ++ core/server/mongodb/config.go | 78 ++ core/server/mongodb/config_test.go | 27 + core/server/mongodb/index.go | 111 +++ core/server/mongodb/index_test.go | 280 ++++++ core/server/operation/config.go | 129 +++ core/server/operation/config_test.go | 71 ++ core/server/operation/index.go | 41 + core/server/operation/index_test.go | 17 + core/server/operation/main_test.go | 40 + core/server/operation/migratin.go | 17 + core/server/viper_server/index.go | 124 +++ core/server/viper_server/index_test.go | 156 ++++ core/server/web/cmd.go | 76 ++ core/server/web/common/index.go | 153 ++++ core/server/web/config.go | 206 +++++ core/server/web/config/mysql.json | 11 + core/server/web/config/web.json | 36 + core/server/web/config/zap.json | 11 + core/server/web/config_test.go | 55 ++ core/server/web/index.go | 46 + core/server/web/web_gin/index.go | 151 +++ core/server/web/web_gin/index_test.go | 39 + core/server/web/web_gin/middleware/cors.go | 24 + core/server/web/web_gin/middleware/loadtls.go | 24 + core/server/web/web_gin/request/index.go | 20 + core/server/web/web_gin/response/index.go | 76 ++ core/server/web/web_gin/router.go | 90 ++ core/server/web/web_gin/run.go | 5 + core/server/web/web_gin/run_darwin.go | 22 + core/server/web/web_gin/run_linux.go | 16 + core/server/web/web_gin/run_windows.go | 18 + core/server/web/web_gin/validate.go | 19 + core/server/web/web_iris/config/mysql.json | 11 + core/server/web/web_iris/config/web.json | 36 + core/server/web/web_iris/config/zap.json | 11 + core/server/web/web_iris/index.go | 123 +++ core/server/web/web_iris/index_test.go | 39 + core/server/web/web_iris/middleware/crs.go | 17 + .../server/web/web_iris/middleware/loadtls.go | 15 + core/server/web/web_iris/router.go | 102 +++ core/server/web/web_iris/validate/index.go | 23 + core/server/zap_server/config.go | 88 ++ core/server/zap_server/index.go | 122 +++ core/server/zap_server/index_test.go | 15 + core/server/zap_server/rotatelogs_darwin.go | 24 + core/server/zap_server/rotatelogs_linux.go | 23 + core/server/zap_server/rotatelogs_windows.go | 23 + 127 files changed, 9353 insertions(+) create mode 100644 .gitignore create mode 100644 business/api/auth.go create mode 100644 business/api/common.go create mode 100644 business/api/control.go create mode 100644 business/api/goods.go create mode 100644 business/api/init.go create mode 100644 business/api/order.go create mode 100644 business/api/pet.go create mode 100644 business/api/service.go create mode 100644 business/build_linux.bat create mode 100644 business/build_windows.bat create mode 100644 business/config/mysql.json create mode 100644 business/config/redis.json create mode 100644 business/config/web.json create mode 100644 business/config/wxconfig.json create mode 100644 business/config/zap.json create mode 100644 business/doc/def.sql create mode 100644 business/go.mod create mode 100644 business/go.sum create mode 100644 business/main.go create mode 100644 business/models/dataModel.go create mode 100644 business/utils/wechat.go create mode 100644 core/g/constant.go create mode 100644 core/g/constant_test.go create mode 100644 core/g/var.go create mode 100644 core/go.mod create mode 100644 core/go.sum create mode 100644 core/helper/arr/array.go create mode 100644 core/helper/arr/array_test.go create mode 100644 core/helper/arr/unit_join.go create mode 100644 core/helper/arr/unit_join_test.go create mode 100644 core/helper/dir/dir.go create mode 100644 core/helper/global/ip.go create mode 100644 core/helper/global/ip_test.go create mode 100644 core/helper/global/ssh.go create mode 100644 core/helper/global/ssh_test.go create mode 100644 core/helper/global/time.go create mode 100644 core/helper/http/http.go create mode 100644 core/helper/http/http_test.go create mode 100644 core/helper/http/main_test.go create mode 100644 core/helper/ping/example/main.go create mode 100644 core/helper/ping/ping.go create mode 100644 core/helper/ping/ping_test.go create mode 100644 core/helper/str/string.go create mode 100644 core/helper/str/struct.go create mode 100644 core/helper/str/struct_test.go create mode 100644 core/helper/sys/cmd_darwin.go create mode 100644 core/helper/sys/cmd_linux.go create mode 100644 core/helper/sys/cmd_windows.go create mode 100644 core/helper/sys/kill.go create mode 100644 core/helper/sys/net.go create mode 100644 core/helper/sys/proc.go create mode 100644 core/migration/migrate.go create mode 100644 core/migration/migrate_test.go create mode 100644 core/seed/seed.go create mode 100644 core/seed/seed_test.go create mode 100644 core/server/cache/cmd.go create mode 100644 core/server/cache/cmd_test.go create mode 100644 core/server/cache/config.go create mode 100644 core/server/cache/config_test.go create mode 100644 core/server/cache/index.go create mode 100644 core/server/cache/index_test.go create mode 100644 core/server/casbin/config.go create mode 100644 core/server/casbin/config_test.go create mode 100644 core/server/casbin/index.go create mode 100644 core/server/casbin/index_test.go create mode 100644 core/server/casbin/main_test.go create mode 100644 core/server/cron_server/index.go create mode 100644 core/server/cron_server/index_test.go create mode 100644 core/server/database/cmd.go create mode 100644 core/server/database/config.go create mode 100644 core/server/database/config/mysql.json create mode 100644 core/server/database/config/zap.json create mode 100644 core/server/database/index.go create mode 100644 core/server/database/index_test.go create mode 100644 core/server/database/internal.go create mode 100644 core/server/database/main_test.go create mode 100644 core/server/database/orm/struct.go create mode 100644 core/server/database/scope/find.go create mode 100644 core/server/database/scope/paginate.go create mode 100644 core/server/mongodb/config.go create mode 100644 core/server/mongodb/config_test.go create mode 100644 core/server/mongodb/index.go create mode 100644 core/server/mongodb/index_test.go create mode 100644 core/server/operation/config.go create mode 100644 core/server/operation/config_test.go create mode 100644 core/server/operation/index.go create mode 100644 core/server/operation/index_test.go create mode 100644 core/server/operation/main_test.go create mode 100644 core/server/operation/migratin.go create mode 100644 core/server/viper_server/index.go create mode 100644 core/server/viper_server/index_test.go create mode 100644 core/server/web/cmd.go create mode 100644 core/server/web/common/index.go create mode 100644 core/server/web/config.go create mode 100644 core/server/web/config/mysql.json create mode 100644 core/server/web/config/web.json create mode 100644 core/server/web/config/zap.json create mode 100644 core/server/web/config_test.go create mode 100644 core/server/web/index.go create mode 100644 core/server/web/web_gin/index.go create mode 100644 core/server/web/web_gin/index_test.go create mode 100644 core/server/web/web_gin/middleware/cors.go create mode 100644 core/server/web/web_gin/middleware/loadtls.go create mode 100644 core/server/web/web_gin/request/index.go create mode 100644 core/server/web/web_gin/response/index.go create mode 100644 core/server/web/web_gin/router.go create mode 100644 core/server/web/web_gin/run.go create mode 100644 core/server/web/web_gin/run_darwin.go create mode 100644 core/server/web/web_gin/run_linux.go create mode 100644 core/server/web/web_gin/run_windows.go create mode 100644 core/server/web/web_gin/validate.go create mode 100644 core/server/web/web_iris/config/mysql.json create mode 100644 core/server/web/web_iris/config/web.json create mode 100644 core/server/web/web_iris/config/zap.json create mode 100644 core/server/web/web_iris/index.go create mode 100644 core/server/web/web_iris/index_test.go create mode 100644 core/server/web/web_iris/middleware/crs.go create mode 100644 core/server/web/web_iris/middleware/loadtls.go create mode 100644 core/server/web/web_iris/router.go create mode 100644 core/server/web/web_iris/validate/index.go create mode 100644 core/server/zap_server/config.go create mode 100644 core/server/zap_server/index.go create mode 100644 core/server/zap_server/index_test.go create mode 100644 core/server/zap_server/rotatelogs_darwin.go create mode 100644 core/server/zap_server/rotatelogs_linux.go create mode 100644 core/server/zap_server/rotatelogs_windows.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..28f870d --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# ext +*.dll +*.db +*.exe +*.log +*.out +*.txt +*.yaml +.air.* +*.jpg + +# file +latest_log +rbac_model.conf + + + +#dir +example/iris/tmp +example/iris/www +example/iris/config +.idea/ +.DS_Store + diff --git a/business/api/auth.go b/business/api/auth.go new file mode 100644 index 0000000..89957e7 --- /dev/null +++ b/business/api/auth.go @@ -0,0 +1,110 @@ +package api + +import ( + "encoding/json" + "github.com/kataras/iris/v12" + "github.com/kataras/iris/v12/context" + "io" + "pet-house.com/business/models" + "pet-house.com/core/server/database" + "pet-house.com/core/server/web" + "pet-house.com/core/server/web/web_iris" +) + +type LoginRequest struct { + Code string +} + +type LoginResponse struct { + Token string `json:"token"` + Uid int64 `json:"uid"` + NickName string `json:"nickName"` + HeadImgUrl string `json:"headImgUrl"` + Amount int `json:"amount"` + Role int `json:"role"` + UserPets []UserPetInfo `json:"userPets"` +} + +var defaultNickName = "微信用户" +var defaultHeadImgUrl = "http://" + web.CONFIG.System.Addr + "/static/img/20240327152925.jpg" + +// 登录 +func (p DefParty) login() web_iris.Party { + return web_iris.Party{Perfix: p.Perfix, PartyFunc: func(index iris.Party) { + index.Post(AuthBase+"/login", func(ctx *context.Context) { + body, _ := io.ReadAll(ctx.Request().Body) + var loginRequest LoginRequest + json.Unmarshal(body, &loginRequest) + //获取code + /*info, err := utils.GetWxUserInfo(loginRequest.Code) + if err != nil { + utils.LoginError.DefFail(ctx, loginRequest, err.Error()) + return + } + if info.ErrCode != 0 { + utils.LoginError.DefFail(ctx, loginRequest, info.ErrMsg) + return + }*/ + var userInfo models.User + database.Instance().Model(&models.User{}).Where("open_id = ? or union_id = ?", "1", "2").Find(&userInfo) + if userInfo.Id == 0 { + newUser := models.User{ + NickName: defaultNickName, + HeadImgUrl: defaultHeadImgUrl, + Amount: 0, + OpenId: NextId.Generate().String(), + UnionId: NextId.Generate().String(), + UserType: 0, + Mobile: "", + Role: 0, + } + database.Instance().Model(&models.User{}).Create(&newUser) + userInfo = newUser + } + token := genToken(userInfo.Id) + response := LoginResponse{ + Token: token, + Uid: userInfo.Id, + NickName: userInfo.NickName, + HeadImgUrl: userInfo.HeadImgUrl, + Amount: userInfo.Amount, + Role: userInfo.Role, + } + response.UserPets = GetUserPets(userInfo.Id) + Success(ctx, loginRequest, response) + }) + }} +} + +type GetUserInfoResponse struct { + Uid int64 `json:"uid"` + NickName string `json:"nickName"` + HeadImgUrl string `json:"headImgUrl"` + Amount int `json:"amount"` + Role int `json:"role"` + UserPets []UserPetInfo `json:"userPets"` +} + +// 获取用户信息 +func (p DefParty) getUserInfo() web_iris.Party { + return web_iris.Party{Perfix: p.Perfix, PartyFunc: func(index iris.Party) { + index.Post(AuthBase+"/getUserInfo", func(ctx *context.Context) { + headerInfo := GetHeaderBaseInfo(ctx) + var userInfo *models.User + database.Instance().Model(&models.User{}).Where("id = ?", headerInfo.Uid).Find(&userInfo) + if userInfo == nil || userInfo.Id == 0 { + UserNotExistError.Fail(ctx, nil) + return + } + getUserInfoResponse := GetUserInfoResponse{ + Uid: userInfo.Id, + NickName: userInfo.NickName, + HeadImgUrl: userInfo.HeadImgUrl, + Amount: userInfo.Amount, + Role: userInfo.Role, + UserPets: GetUserPets(userInfo.Id), + } + Success(ctx, headerInfo, getUserInfoResponse) + }) + }} +} diff --git a/business/api/common.go b/business/api/common.go new file mode 100644 index 0000000..8e16b95 --- /dev/null +++ b/business/api/common.go @@ -0,0 +1,119 @@ +package api + +import ( + "crypto/md5" + "encoding/hex" + "github.com/bwmarrin/snowflake" + "github.com/kataras/iris/v12/context" + "go.uber.org/zap" + "pet-house.com/business/models" + "pet-house.com/core/server/cache" + "pet-house.com/core/server/database" + "pet-house.com/core/server/zap_server" + "strconv" + "time" +) + +type Response struct { + Code int `json:"code"` //code + Msg string `json:"msg"` //msg + Data any `json:"data"` //数据 +} + +type ResponseData struct { + Data any `json:"data"` //数据 +} + +type Error struct { + Code int `json:"code"` + Msg string `json:"msg"` +} + +type DefParty struct { + Perfix string +} + +var ( + ParamError = Error{Code: 201, Msg: "参数错误"} + IllegalError = Error{Code: 202, Msg: "非法请求"} + UserError = Error{Code: 203, Msg: "用户错误"} + TokenError = Error{Code: 204, Msg: "Token失效,请重新登录"} + UserNotExistError = Error{Code: 205, Msg: "用户不存在"} + PetNotExistError = Error{Code: 206, Msg: "用户宠物不存在"} + PetBaseNotExistError = Error{Code: 207, Msg: "宠物基础信息不存在"} +) + +func Success(ctx *context.Context, request any, data any) { + response := Response{200, "success", data} + zap_server.ZAPLOG.Info(ctx.Path(), zap.Any("request", request), zap.Any("response", response)) + write(ctx, response) +} + +func (error *Error) Fail(ctx *context.Context, request any) { + zap_server.ZAPLOG.Info(ctx.Path(), zap.Any("request", request), zap.Any("error", error), zap.Any("err", error.Msg)) + write(ctx, Error{error.Code, error.Msg}) +} + +func (error *Error) DefFail(ctx *context.Context, request any, err any) { + zap_server.ZAPLOG.Info(ctx.Path(), zap.Any("request", request), zap.Any("error", error), zap.Any("err", err)) + write(ctx, Error{error.Code, err.(string)}) +} + +func write(ctx *context.Context, resp any) { + ctx.JSON(resp) +} + +var NextId, _ = snowflake.NewNode(1) + +func genToken(uid int64) string { + uToken, err := cache.GetCacheString("u_uid_token:" + strconv.FormatInt(uid, 10)) + if err == nil && len(uToken) > 0 { + return uToken + } + sId := NextId.Generate() + hash := md5.Sum(sId.Bytes()) + token := hex.EncodeToString(hash[:]) + cache.SetCache("u_token:"+token, uid, time.Hour*24*30) + cache.SetCache("u_uid_token:"+strconv.FormatInt(uid, 10), token, time.Hour*24*30) + return token +} + +func GetTokenInfo(token string) string { + uId, _ := cache.GetCacheString("u_token:" + token) + return uId +} + +func checkToken(uid string, token string) bool { + uToken, _ := cache.GetCacheString("u_uid_token:" + uid) + return uToken == token +} + +type HeaderBaseInfo struct { + Token string + Uid int64 +} + +func GetHeaderBaseInfo(ctx *context.Context) HeaderBaseInfo { + token := ctx.GetHeader("X-Token") + uid := ctx.GetHeader("X-U-Id") + _uid, _ := strconv.ParseInt(uid, 10, 64) + return HeaderBaseInfo{ + token, _uid, + } +} + +type FrontExclude struct { + path string +} + +var PetBaseInfoMap map[int]models.PetBaseInfo + +func DataInit() { + var petBastInfoList []models.PetBaseInfo + database.Instance().Model(&models.PetBaseInfo{}).Find(&petBastInfoList) + PetBaseInfoMap = make(map[int]models.PetBaseInfo) + for _, value := range petBastInfoList { + PetBaseInfoMap[value.Id] = value + } + zap_server.ZAPLOG.Info("dataInit : ", zap.Any("petBastInfoMap", PetBaseInfoMap)) +} diff --git a/business/api/control.go b/business/api/control.go new file mode 100644 index 0000000..53dfdf6 --- /dev/null +++ b/business/api/control.go @@ -0,0 +1,28 @@ +package api + +import "pet-house.com/core/server/web/web_iris" + +var AuthBase = "/auth" +var GoodsBase = "/goods" +var OrderBase = "/order" +var ServiceBase = "/service" +var PetBase = "/pet" + +func (p DefParty) RegisterList() []web_iris.Party { + ps := []web_iris.Party{ + p.login(), + p.getUserInfo(), + p.goodsList(), + p.goodsDetail(), + p.orderCreate(), + p.orderPay(), + p.petList(), + p.petInfo(), + p.petTypeList(), + p.petAddOrEdit(), + p.serviceAddrList(), + p.serviceAddOrEdit(), + p.index(), + } + return ps +} diff --git a/business/api/goods.go b/business/api/goods.go new file mode 100644 index 0000000..68badc9 --- /dev/null +++ b/business/api/goods.go @@ -0,0 +1,25 @@ +package api + +import ( + "github.com/kataras/iris/v12" + "github.com/kataras/iris/v12/context" + "pet-house.com/core/server/web/web_iris" +) + +// 商品列表 +func (p DefParty) goodsList() web_iris.Party { + return web_iris.Party{Perfix: p.Perfix, PartyFunc: func(index iris.Party) { + index.Post(GoodsBase+"/goodsList", func(ctx *context.Context) { + + }) + }} +} + +// 商品详情 +func (p DefParty) goodsDetail() web_iris.Party { + return web_iris.Party{Perfix: p.Perfix, PartyFunc: func(index iris.Party) { + index.Post(GoodsBase+"/goodsDetail", func(ctx *context.Context) { + + }) + }} +} diff --git a/business/api/init.go b/business/api/init.go new file mode 100644 index 0000000..6b87780 --- /dev/null +++ b/business/api/init.go @@ -0,0 +1,77 @@ +package api + +import ( + "github.com/kataras/iris/v12" + "github.com/kataras/iris/v12/context" + "go.uber.org/zap" + "pet-house.com/business/models" + "pet-house.com/business/utils" + "pet-house.com/core/server/database" + "pet-house.com/core/server/web/web_iris" + "pet-house.com/core/server/zap_server" + "strings" +) + +var Root = "/pet-house" +var ExcludeBase = "/static" +var ExcludeBase1 = "/debug" + +var frontExcludes = [...]string{ + Root + AuthBase + "/login", +} + +func ModuleInit() { + utils.WechatInit() + _ = database.Instance().AutoMigrate( + &models.User{}, + &models.Pet{}, + &models.PetBaseInfo{}, + &models.ServiceAddr{}, + &models.UserServiceAddr{}, + &models.Goods{}, + &models.SystemConfig{}, + &models.OrderMain{}, + &models.OrderSub{}, + &models.OrderDetail{}) +} + +func FrontAuth(ctx *context.Context) { + if strings.Contains(ctx.Path(), ExcludeBase) { + ctx.Next() + return + } + if strings.Contains(ctx.Path(), ExcludeBase1) { + ctx.Next() + return + } + frontExcludesStr := strings.Join(frontExcludes[:], ",") + if strings.Contains(frontExcludesStr, ctx.Path()) { + ctx.Next() + return + } + token := ctx.GetHeader("X-Token") + uid := ctx.GetHeader("X-U-Id") + zap_server.ZAPLOG.Info("frontAuth", zap.Any("path", ctx.Path()), zap.Any("token", token), zap.Any("uid", uid)) + if len(token) == 0 || len(uid) == 0 { + IllegalError.Fail(ctx, nil) + return + } + tokenToUid := GetTokenInfo(token) + if len(tokenToUid) == 0 { + TokenError.Fail(ctx, nil) + return + } + if uid != tokenToUid { + UserError.Fail(ctx, nil) + return + } + ctx.Next() +} + +func (p DefParty) index() web_iris.Party { + return web_iris.Party{Perfix: p.Perfix, PartyFunc: func(index iris.Party) { + index.Get("/", func(c *context.Context) { + c.WriteString("successful") + }) + }} +} diff --git a/business/api/order.go b/business/api/order.go new file mode 100644 index 0000000..75e7537 --- /dev/null +++ b/business/api/order.go @@ -0,0 +1,25 @@ +package api + +import ( + "github.com/kataras/iris/v12" + "github.com/kataras/iris/v12/context" + "pet-house.com/core/server/web/web_iris" +) + +// 创建 +func (p DefParty) orderCreate() web_iris.Party { + return web_iris.Party{Perfix: p.Perfix, PartyFunc: func(index iris.Party) { + index.Post(OrderBase+"/orderCreate", func(ctx *context.Context) { + + }) + }} +} + +// 支付 +func (p DefParty) orderPay() web_iris.Party { + return web_iris.Party{Perfix: p.Perfix, PartyFunc: func(index iris.Party) { + index.Post(OrderBase+"/orderPay", func(ctx *context.Context) { + + }) + }} +} diff --git a/business/api/pet.go b/business/api/pet.go new file mode 100644 index 0000000..3f195ce --- /dev/null +++ b/business/api/pet.go @@ -0,0 +1,159 @@ +package api + +import ( + "encoding/json" + "github.com/kataras/iris/v12" + "github.com/kataras/iris/v12/context" + "io" + "pet-house.com/business/models" + "pet-house.com/core/server/database" + "pet-house.com/core/server/web/web_iris" +) + +type UserPetInfo struct { + PetInfo models.Pet `json:"petInfo"` + PetBaseInfo models.PetBaseInfo `json:"petBaseInfo"` +} + +func GetUserPets(uId int64) []UserPetInfo { + var userPetList []models.Pet + database.Instance().Model(&models.Pet{}).Where("uid = ?", uId).Find(&userPetList) + var userPets []UserPetInfo + if len(userPetList) > 0 { + for _, pet := range userPetList { + userPets = append(userPets, UserPetInfo{pet, PetBaseInfoMap[pet.PetId]}) + } + } + return userPets +} + +func GetUserPet(uId int64, pId int64) UserPetInfo { + var userPet models.Pet + database.Instance().Model(&models.Pet{}).Where("uid = ? and id = ?", uId, pId).Find(&userPet) + return UserPetInfo{userPet, PetBaseInfoMap[userPet.PetId]} +} + +type PetListResponse struct { + UserPets []UserPetInfo `json:"userPets"` +} + +// 宠物列表 +func (p DefParty) petList() web_iris.Party { + return web_iris.Party{Perfix: p.Perfix, PartyFunc: func(index iris.Party) { + index.Post(PetBase+"/petList", func(ctx *context.Context) { + headerBaseInfo := GetHeaderBaseInfo(ctx) + userPets := GetUserPets(headerBaseInfo.Uid) + Success(ctx, headerBaseInfo, PetListResponse{ + userPets, + }) + }) + }} +} + +type PetInfoRequest struct { + Id int64 +} + +type PetInfoResponse struct { + UserPet UserPetInfo `json:"userPet"` +} + +func (p DefParty) petInfo() web_iris.Party { + return web_iris.Party{Perfix: p.Perfix, PartyFunc: func(index iris.Party) { + index.Post(PetBase+"/petInfo", func(ctx *context.Context) { + headerBaseInfo := GetHeaderBaseInfo(ctx) + body, _ := io.ReadAll(ctx.Request().Body) + var petInfoRequest PetInfoRequest + json.Unmarshal(body, &petInfoRequest) + userPet := GetUserPet(headerBaseInfo.Uid, petInfoRequest.Id) + Success(ctx, petInfoRequest, PetInfoResponse{userPet}) + }) + }} +} + +type PetAddOrEditRequest struct { + Id int64 //Id 不为0表示修改 + NickName string //宠物昵称 + HeadImgType int //头像类型 0远程头像 1本地头像 + HeadImgUrl string //宠物头像 + Desc string //宠物描述 + Precaution string //注意事项 + Gender int //性别 0男 1女 + Birthday string //生日 + PetId int //宠物类型 + Eunuch int //是否绝育 0否 1是 2未知 +} + +type PetAddOrEditResponse struct { + UserPets []UserPetInfo `json:"userPets"` +} + +// 宠物添加或编辑 +func (p DefParty) petAddOrEdit() web_iris.Party { + return web_iris.Party{Perfix: p.Perfix, PartyFunc: func(index iris.Party) { + index.Post(PetBase+"/petAddOrEdit", func(ctx *context.Context) { + headerBaseInfo := GetHeaderBaseInfo(ctx) + body, _ := io.ReadAll(ctx.Request().Body) + var petAddOrEditRequest PetAddOrEditRequest + json.Unmarshal(body, &petAddOrEditRequest) + if PetBaseInfoMap[petAddOrEditRequest.PetId].Id == 0 { + PetBaseNotExistError.Fail(ctx, petAddOrEditRequest) + return + } + pet := models.Pet{ + Uid: headerBaseInfo.Uid, + NickName: petAddOrEditRequest.NickName, + HeadImgType: petAddOrEditRequest.HeadImgType, + HeadImgUrl: petAddOrEditRequest.HeadImgUrl, + Desc: petAddOrEditRequest.Desc, + Precaution: petAddOrEditRequest.Precaution, + Gender: petAddOrEditRequest.Gender, + Birthday: petAddOrEditRequest.Birthday, + PetId: petAddOrEditRequest.PetId, + Eunuch: petAddOrEditRequest.Eunuch, + } + if petAddOrEditRequest.Id == 0 { + database.Instance().Model(&models.Pet{}).Create(&pet) + } else { + pet.Id = petAddOrEditRequest.Id + var userPetInfo models.Pet + database.Instance().Model(&models.Pet{}).Where("id = ? and uid = ?", pet.Id, headerBaseInfo.Uid).Find(&userPetInfo) + if userPetInfo.Id == 0 { + PetNotExistError.Fail(ctx, petAddOrEditRequest) + return + } + updateValues := map[string]interface{}{ + "NickName": pet.NickName, + "HeadImgType": pet.HeadImgType, + "HeadImgUrl": pet.HeadImgUrl, + "Desc": pet.Desc, + "Precaution": pet.Precaution, + "Gender": pet.Gender, + "Birthday": pet.Birthday, + "PetId": pet.PetId, + "Eunuch": pet.Eunuch, + } + database.Instance().Model(&pet).Updates(&updateValues) + } + userPets := GetUserPets(headerBaseInfo.Uid) + Success(ctx, petAddOrEditRequest, PetAddOrEditResponse{userPets}) + }) + }} +} + +type PetTypeListResponse struct { + PetBaseInfoList []models.PetBaseInfo `json:"petBaseInfoList"` +} + +// 宠物基础信息列表 +func (p DefParty) petTypeList() web_iris.Party { + return web_iris.Party{Perfix: p.Perfix, PartyFunc: func(index iris.Party) { + index.Post(PetBase+"/petTypeList", func(ctx *context.Context) { + var petBaseInfoList []models.PetBaseInfo + for _, petBaseInfo := range PetBaseInfoMap { + petBaseInfoList = append(petBaseInfoList, petBaseInfo) + } + Success(ctx, nil, PetTypeListResponse{petBaseInfoList}) + }) + }} +} diff --git a/business/api/service.go b/business/api/service.go new file mode 100644 index 0000000..beb11f8 --- /dev/null +++ b/business/api/service.go @@ -0,0 +1,25 @@ +package api + +import ( + "github.com/kataras/iris/v12" + "github.com/kataras/iris/v12/context" + "pet-house.com/core/server/web/web_iris" +) + +// 服务地址列表 +func (p DefParty) serviceAddrList() web_iris.Party { + return web_iris.Party{Perfix: p.Perfix, PartyFunc: func(index iris.Party) { + index.Post(ServiceBase+"/serviceAddrList", func(ctx *context.Context) { + + }) + }} +} + +// 服务地址添加或编辑 +func (p DefParty) serviceAddOrEdit() web_iris.Party { + return web_iris.Party{Perfix: p.Perfix, PartyFunc: func(index iris.Party) { + index.Post(ServiceBase+"/serviceAddOrEdit", func(ctx *context.Context) { + + }) + }} +} diff --git a/business/build_linux.bat b/business/build_linux.bat new file mode 100644 index 0000000..372b0d3 --- /dev/null +++ b/business/build_linux.bat @@ -0,0 +1,6 @@ +@echo off +set GOOS=linux +set GOARCH=amd64 +echo build +go build -o petHouseApp main.go +pause diff --git a/business/build_windows.bat b/business/build_windows.bat new file mode 100644 index 0000000..26a76e2 --- /dev/null +++ b/business/build_windows.bat @@ -0,0 +1,6 @@ +@echo off +set GOOS=windows +set GOARCH=amd64 +echo build +go build -o petHouseApp.exe main.go +pause diff --git a/business/config/mysql.json b/business/config/mysql.json new file mode 100644 index 0000000..bd65dd6 --- /dev/null +++ b/business/config/mysql.json @@ -0,0 +1,11 @@ +{ + "config": "charset=utf8mb4\u0026parseTime=True\u0026loc=Local", + "db-name": "pet_house", + "log-mode": false, + "log-zap": "error", + "max-idle-conns": 20, + "max-open-conns": 5, + "password": "ifish7mysql", + "path": "139.196.24.156:3306", + "username": "root" +} \ No newline at end of file diff --git a/business/config/redis.json b/business/config/redis.json new file mode 100644 index 0000000..bc919f9 --- /dev/null +++ b/business/config/redis.json @@ -0,0 +1,6 @@ +{ + "db": 7, + "addr": "172.31.64.5:6379", + "password": "z6r0tcpllx3wLceM", + "pool-size": 10 +} \ No newline at end of file diff --git a/business/config/web.json b/business/config/web.json new file mode 100644 index 0000000..6c107aa --- /dev/null +++ b/business/config/web.json @@ -0,0 +1,32 @@ +{ + "captcha": { + "img-height": 80, + "img-width": 240, + "key-long": 4 + }, + "cors": { + "access-credentials": "true", + "access-expose-headers": "Content-Length,Access-Control-Allow-Origin,Access-Control-Allow-Headers,Content-Type", + "access-headers": "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id", + "access-methods": "POST,GET", + "access-origin": "*" + }, + "except": { + "method": "", + "uri": "" + }, + "file-max-size": 1024, + "limit": { + "burst": 5, + "disable": true, + "limit": 0 + }, + "session-timeout": 60, + "system": { + "addr": "127.0.0.1:18005", + "db-type": "mysql", + "level": "debug", + "time-format": "2006-01-02 15:04:05", + "tls": false + } +} \ No newline at end of file diff --git a/business/config/wxconfig.json b/business/config/wxconfig.json new file mode 100644 index 0000000..f7b0cbd --- /dev/null +++ b/business/config/wxconfig.json @@ -0,0 +1,7 @@ +{ + "appId": "1", + "secret": "2", + "token": "3", + "aesKey": "4", + "msgDataFormat": "5" +} \ No newline at end of file diff --git a/business/config/zap.json b/business/config/zap.json new file mode 100644 index 0000000..fcf38aa --- /dev/null +++ b/business/config/zap.json @@ -0,0 +1,11 @@ +{ + "director": "logs", + "encode-level": "LowercaseColorLevelEncoder", + "format": "console", + "level": "info", + "link-name": "latest_log", + "log-in-console": true, + "prefix": "[per-house]\t", + "show-line": true, + "stacktrace-key": "stacktrace" +} \ No newline at end of file diff --git a/business/doc/def.sql b/business/doc/def.sql new file mode 100644 index 0000000..884dd3d --- /dev/null +++ b/business/doc/def.sql @@ -0,0 +1,162 @@ +create table pet_house.goods +( + id bigint unsigned auto_increment + primary key, + name varchar(191) not null, + goods_type bigint not null, + time varchar(191) null, + price int not null, + parent_id bigint not null, + buy_many bigint not null, + goods_detail varchar(191) not null, + create_time timestamp default CURRENT_TIMESTAMP null +); + +create table pet_house.order_details +( + id bigint unsigned auto_increment + primary key, + sub_order_id bigint not null, + goods_id bigint not null, + amount bigint not null +); + +create index idx_order_details_goods_id + on pet_house.order_details (goods_id); + +create index idx_order_details_sub_order_id + on pet_house.order_details (sub_order_id); + +create table pet_house.order_mains +( + id bigint unsigned auto_increment + primary key, + order_id varchar(191) not null, + uid bigint not null, + status bigint null, + create_time timestamp default CURRENT_TIMESTAMP null, + constraint uni_order_mains_order_id + unique (order_id) +); + +create index idx_order_mains_order_id + on pet_house.order_mains (order_id); + +create index idx_order_mains_uid + on pet_house.order_mains (uid); + +create table pet_house.order_subs +( + id bigint unsigned auto_increment + primary key, + main_order_id bigint null, + status bigint not null, + pet_id bigint null, + pay_type bigint not null, + discount bigint null, + total_amount int not null, + pay_amount int null, + pay_time varchar(191) null, + reserve_time varchar(191) not null, + service_time varchar(191) null, + service_addr_id bigint null, + service_remark varchar(191) null, + create_time timestamp default CURRENT_TIMESTAMP null, + update_time timestamp default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP +); + +create index idx_order_subs_main_order_id + on pet_house.order_subs (main_order_id); + +create index idx_order_subs_pet_id + on pet_house.order_subs (pet_id); + +create table pet_house.pet_base_infos +( + id bigint unsigned auto_increment + primary key, + pet_type bigint not null, + assortment varchar(191) not null, + size bigint not null, + create_time timestamp default CURRENT_TIMESTAMP null +); + +create table pet_house.pets +( + id bigint unsigned auto_increment + primary key, + uid bigint not null, + nick_name varchar(191) not null, + head_img_type bigint null, + head_img_url varchar(191) null, + `desc` varchar(191) null, + precaution varchar(191) null, + gender bigint null, + birthday varchar(191) null, + pet_id bigint null, + eunuch bigint null, + last_service_proj varchar(191) default '' - '' null, + last_service_date varchar(191) default '' - '' null, + create_time timestamp default CURRENT_TIMESTAMP not null +); + +create index idx_pets_uid + on pet_house.pets (uid); + +create table pet_house.service_addrs +( + id bigint unsigned auto_increment + primary key, + longitude varchar(191) null, + latitude varchar(191) null, + addr varchar(191) not null +); + +create table pet_house.system_configs +( + name varchar(191) null, + `desc` varchar(191) null, + config_type varchar(191) null, + desc_type bigint null +); + +create table pet_house.user_service_addrs +( + id bigint unsigned auto_increment + primary key, + uid bigint not null, + name varchar(191) not null, + mobile varchar(191) not null, + area varchar(191) not null, + addr varchar(191) not null, + create_time timestamp default CURRENT_TIMESTAMP not null +); + +create index idx_user_service_addrs_uid + on pet_house.user_service_addrs (uid); + +create table pet_house.users +( + id bigint unsigned auto_increment + primary key, + nick_name varchar(191) null, + head_img_url varchar(191) null, + amount bigint null, + open_id varchar(191) not null, + union_id varchar(191) not null, + user_type bigint null, + mobile varchar(191) null, + role bigint null, + create_time timestamp default CURRENT_TIMESTAMP not null, + constraint uni_users_open_id + unique (open_id), + constraint uni_users_union_id + unique (union_id) +); + +create index idx_users_open_id + on pet_house.users (open_id); + +create index idx_users_union_id + on pet_house.users (union_id); + diff --git a/business/go.mod b/business/go.mod new file mode 100644 index 0000000..ba64078 --- /dev/null +++ b/business/go.mod @@ -0,0 +1,95 @@ +module pet-house.com/business + +go 1.22.0 + +require ( + github.com/bwmarrin/snowflake v0.3.0 + github.com/dgb8901/go-wechat-miniapp-sdk v1.0.1-release + github.com/kataras/iris/v12 v12.2.10 + github.com/spf13/viper v1.18.2 + go.uber.org/zap v1.27.0 + pet-house.com/core v0.0.0 +) + +require ( + github.com/BurntSushi/toml v1.3.2 // indirect + github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect + github.com/CloudyKit/jet/v6 v6.2.0 // indirect + github.com/Joker/jade v1.1.3 // indirect + github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 // indirect + github.com/andybalholm/brotli v1.1.0 // indirect + github.com/astaxie/beego v1.12.0 // indirect + github.com/aymerick/douceur v0.2.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/fatih/structs v1.1.0 // indirect + github.com/flosch/pongo2/v4 v4.0.2 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.19.0 // indirect + github.com/go-sql-driver/mysql v1.7.0 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47 // indirect + github.com/gomodule/redigo v2.0.0+incompatible // indirect + github.com/google/uuid v1.5.0 // indirect + github.com/gorilla/css v1.0.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/iris-contrib/middleware/cors v0.0.0-20240111010557-e34016a4d6ee // indirect + github.com/iris-contrib/middleware/secure v0.0.0-20240111010557-e34016a4d6ee // indirect + github.com/iris-contrib/schema v0.0.6 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/kataras/blocks v0.0.8 // indirect + github.com/kataras/golog v0.1.11 // indirect + github.com/kataras/pio v0.0.13 // indirect + github.com/kataras/sitemap v0.0.6 // indirect + github.com/kataras/tunnel v0.0.4 // indirect + github.com/klauspost/compress v1.17.4 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible // indirect + github.com/lestrrat-go/strftime v1.0.6 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mailgun/raymond/v2 v2.0.48 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/microcosm-cc/bluemonday v1.0.26 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/redis/go-redis/v9 v9.4.0 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/schollz/closestmatch v2.1.0+incompatible // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/tdewolff/minify/v2 v2.20.14 // indirect + github.com/tdewolff/parse/v2 v2.7.8 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/yosssi/ace v0.0.5 // indirect + go.uber.org/multierr v1.10.0 // indirect + golang.org/x/crypto v0.19.0 // indirect + golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/driver/mysql v1.5.6 // indirect + gorm.io/gorm v1.25.8 // indirect +) + +replace pet-house.com/core => ../core diff --git a/business/go.sum b/business/go.sum new file mode 100644 index 0000000..0f805cd --- /dev/null +++ b/business/go.sum @@ -0,0 +1,312 @@ +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c= +github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= +github.com/CloudyKit/jet/v6 v6.2.0 h1:EpcZ6SR9n28BUGtNJSvlBqf90IpjeFr36Tizxhn/oME= +github.com/CloudyKit/jet/v6 v6.2.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4= +github.com/Joker/hpp v1.0.0 h1:65+iuJYdRXv/XyN62C1uEmmOx3432rNG/rKlX6V7Kkc= +github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= +github.com/Joker/jade v1.1.3 h1:Qbeh12Vq6BxURXT1qZBRHsDxeURB8ztcL6f3EXSGeHk= +github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM= +github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/OwnLocal/goes v1.0.0/go.mod h1:8rIFjBGTue3lCU0wplczcUgt9Gxgrkkrw7etMIcn8TM= +github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 h1:KkH3I3sJuOLP3TjA/dfr4NAY8bghDwnXiU7cTKxQqo0= +github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06/go.mod h1:7erjKLwalezA0k99cWs5L11HWOAPNjdUZ6RxH1BXbbM= +github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/astaxie/beego v1.12.0 h1:MRhVoeeye5N+Flul5PoVfD9CslfdoH+xqC/xvSQ5u2Y= +github.com/astaxie/beego v1.12.0/go.mod h1:fysx+LZNZKnvh4GED/xND7jWtjCR6HzydR2Hh2Im57o= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ= +github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU= +github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= +github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= +github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE= +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/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= +github.com/couchbase/go-couchbase v0.0.0-20181122212707-3e9b6e1258bb/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U= +github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c= +github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs= +github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgb8901/go-wechat-miniapp-sdk v1.0.1-release h1:7JiueZblXctQxQERwNYGHiGLt3bLnPPv3nMjxI2Ogto= +github.com/dgb8901/go-wechat-miniapp-sdk v1.0.1-release/go.mod h1:sB/jifE9Y68uRKmtubiQXE9EZKqoe2WbgeC3FOdIjNY= +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/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw= +github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= +github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47 h1:k4Tw0nt6lwro3Uin8eqoET7MDA4JnT8YgbCjc/g5E3k= +github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= +github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= +github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= +github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= +github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= +github.com/iris-contrib/httpexpect/v2 v2.15.2 h1:T9THsdP1woyAqKHwjkEsbCnMefsAFvk8iJJKokcJ3Go= +github.com/iris-contrib/httpexpect/v2 v2.15.2/go.mod h1:JLDgIqnFy5loDSUv1OA2j0mb6p/rDhiCqigP22Uq9xE= +github.com/iris-contrib/middleware/cors v0.0.0-20240111010557-e34016a4d6ee h1:srr0HUvI9IAximtqxhF0+WSy9cv9Dd5YlH8RMEkHtsw= +github.com/iris-contrib/middleware/cors v0.0.0-20240111010557-e34016a4d6ee/go.mod h1:hAsE+pfCAz1M2cCfsEolTmaBwHCyzchRFyCk5I1+kDo= +github.com/iris-contrib/middleware/secure v0.0.0-20240111010557-e34016a4d6ee h1:SpFT+8y4KVdrxoaB/jIjfNGT56dwcIPO/J16+pcLRs8= +github.com/iris-contrib/middleware/secure v0.0.0-20240111010557-e34016a4d6ee/go.mod h1:4TzL/OnRB+/kIiSTQb9iXwkNVc6bCn9l5hYfhYnDu88= +github.com/iris-contrib/schema v0.0.6 h1:CPSBLyx2e91H2yJzPuhGuifVRnZBBJ3pCOMbOvPZaTw= +github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA= +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.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= +github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/kataras/blocks v0.0.8 h1:MrpVhoFTCR2v1iOOfGng5VJSILKeZZI+7NGfxEh3SUM= +github.com/kataras/blocks v0.0.8/go.mod h1:9Jm5zx6BB+06NwA+OhTbHW1xkMOYxahnqTN5DveZ2Yg= +github.com/kataras/golog v0.1.11 h1:dGkcCVsIpqiAMWTlebn/ZULHxFvfG4K43LF1cNWSh20= +github.com/kataras/golog v0.1.11/go.mod h1:mAkt1vbPowFUuUGvexyQ5NFW6djEgGyxQBIARJ0AH4A= +github.com/kataras/iris/v12 v12.2.10 h1:rEJVM7qMoyhv8wpgkA1yGxibFcONE0jkJ70LFLibTAA= +github.com/kataras/iris/v12 v12.2.10/go.mod h1:z4+E+kLMqZ7U4WtDsYfFnG7BjMTXLkdzMAXLVMLnMNs= +github.com/kataras/pio v0.0.13 h1:x0rXVX0fviDTXOOLOmr4MUxOabu1InVSTu5itF8CXCM= +github.com/kataras/pio v0.0.13/go.mod h1:k3HNuSw+eJ8Pm2lA4lRhg3DiCjVgHlP8hmXApSej3oM= +github.com/kataras/sitemap v0.0.6 h1:w71CRMMKYMJh6LR2wTgnk5hSgjVNB9KL60n5e2KHvLY= +github.com/kataras/sitemap v0.0.6/go.mod h1:dW4dOCNs896OR1HmG+dMLdT7JjDk7mYBzoIRwuj5jA4= +github.com/kataras/tunnel v0.0.4 h1:sCAqWuJV7nPzGrlb0os3j49lk2JhILT0rID38NHNLpA= +github.com/kataras/tunnel v0.0.4/go.mod h1:9FkU4LaeifdMWqZu7o20ojmW4B7hdhv2CMLwfnHGpYw= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +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/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8= +github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is= +github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible h1:Y6sqxHMyB1D2YSzWkLibYKgg+SwmyFU9dF2hn6MdTj4= +github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible/go.mod h1:ZQnN8lSECaebrkQytbHj4xNgtg8CR7RYXnPok8e0EHA= +github.com/lestrrat-go/strftime v1.0.6 h1:CFGsDEt1pOpFNU+TJB0nhz9jl+K0hZSLE205AhTIGQQ= +github.com/lestrrat-go/strftime v1.0.6/go.mod h1:f7jQKgV5nnJpYgdEasS+/y7EsTb8ykN2z68n3TtcTaw= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw= +github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +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.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= +github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= +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/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pkg/errors v0.8.0/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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis/v9 v9.4.0 h1:Yzoz33UZw9I/mFhx4MNrB6Fk+XHO1VukNcCa1+lwyKk= +github.com/redis/go-redis/v9 v9.4.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= +github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= +github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk= +github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= +github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw= +github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg= +github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +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.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.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0= +github.com/tdewolff/minify/v2 v2.20.14 h1:sktSuVixRwk0ryQjqvKBu/uYS+MWmkwEFMEWtFZ+TdE= +github.com/tdewolff/minify/v2 v2.20.14/go.mod h1:qnIJbnG2dSzk7LIa/UUwgN2OjS8ir6RRlqc0T/1q2xY= +github.com/tdewolff/parse/v2 v2.7.8 h1:1cnVqa8L63xFkc2vfRsZTM6Qy35nJpTvQ2Uvdv3vbvs= +github.com/tdewolff/parse/v2 v2.7.8/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA= +github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= +github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo= +github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= +github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA= +github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0= +github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw= +golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +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-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.1/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/driver/mysql v1.5.6 h1:Ld4mkIickM+EliaQZQx3uOJDJHtrd70MxAUqWqlx3Y8= +gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.8 h1:WAGEZ/aEcznN4D03laj8DKnehe1e9gYQAjW8xyPRdeo= +gorm.io/gorm v1.25.8/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs= +moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE= diff --git a/business/main.go b/business/main.go new file mode 100644 index 0000000..895fe30 --- /dev/null +++ b/business/main.go @@ -0,0 +1,21 @@ +package main + +import ( + "go.uber.org/zap" + "os" + "pet-house.com/business/api" + "pet-house.com/core/server/web" + "pet-house.com/core/server/web/web_iris" + "pet-house.com/core/server/zap_server" +) + +func main() { + wi := web_iris.Init() + wi.AddModule(api.DefParty{Perfix: api.Root}.RegisterList()...) + wi.AddFrontFunc(api.FrontAuth) + wi.AddWebStatic("./static", "/static") + api.ModuleInit() + api.DataInit() + zap_server.ZAPLOG.Info("main start success", zap.Any("pid", os.Getegid())) + web.Start(wi) +} diff --git a/business/models/dataModel.go b/business/models/dataModel.go new file mode 100644 index 0000000..e0fbe43 --- /dev/null +++ b/business/models/dataModel.go @@ -0,0 +1,135 @@ +package models + +import "time" + +/** +数据模型 +*/ + +// User 用户 +type User struct { + Id int64 `gorm:"primaryKey;autoIncrement" json:"id"` //用户ID + NickName string `json:"nickName"` //昵称 + HeadImgUrl string `json:"headImgUrl"` //用户头像URL + Amount int `json:"amount"` //用户余额 + OpenId string `gorm:"index;unique;not null" json:"openId"` //三方openid + UnionId string `gorm:"index;unique;not null" json:"unionId"` //三方unionid + UserType int `json:"userType"` //用户类型 0微信 1手机号 + Mobile string `json:"mobile"` //手机号 + Role int `json:"role"` //用户角色 0普通用户 1护理人员 + CreateTime time.Time `gorm:"type:timestamp;default:CURRENT_TIMESTAMP;not null" json:"createTime"` //创建时间 +} + +// Pet 宠物信息 +type Pet struct { + Id int64 `gorm:"primaryKey;autoIncrement" json:"id"` //id + Uid int64 `gorm:"index;not null" json:"uid"` //用户id + NickName string `gorm:"not null" json:"nickName"` //宠物昵称 + HeadImgType int `json:"headImgType"` //用户头像类型 0远程头像 1本地头像 + HeadImgUrl string `json:"headImgUrl"` //宠物头像 + Desc string `json:"desc"` //宠物描述 + Precaution string `json:"precaution"` //注意事项 + Gender int `json:"gender"` //性别 0男 1女 + Birthday string `json:"birthday"` //生日 + PetId int `json:"petId"` //宠物类型 + Eunuch int `json:"eunuch"` //是否绝育 0否 1是 2未知 + LastServiceProj string `gorm:"default:'-'" json:"lastServiceProj"` //上次服务项目 + LastServiceDate string `gorm:"default:'-'" json:"lastServiceDate"` //上次服务时间 + CreateTime time.Time `gorm:"type:timestamp;default:CURRENT_TIMESTAMP;not null" json:"createTime"` //创建时间 +} + +// PetBaseInfo 宠物基础信息 +type PetBaseInfo struct { + Id int `gorm:"primaryKey;autoIncrement" json:"id"` //id + PetType int `gorm:"not null" json:"petType"` //类型 1猫 2狗 + Assortment string `gorm:"not null" json:"assortment"` //品种 + Size int `gorm:"not null" json:"size"` //大小 1大 2中 3小 + CreateTime time.Time `gorm:"type:timestamp;default:CURRENT_TIMESTAMP" json:"-"` //创建时间 +} + +// ServiceAddr 服务地址 +type ServiceAddr struct { + Id int64 `gorm:"primaryKey;autoIncrement" json:"id"` //ID + Longitude string `json:"longitude"` //经度 + Latitude string `json:"latitude"` //纬度 + Addr string `gorm:"not null" json:"addr"` //详细地址 +} + +// UserServiceAddr 用户服务地址 +type UserServiceAddr struct { + Id int64 `gorm:"primaryKey;autoIncrement" json:"id"` //id + Uid int64 `gorm:"index;not null" json:"uid"` //用户ID + Name string `gorm:"not null" json:"name"` //名称 + Mobile string `gorm:"not null" json:"mobile"` //手机号 + Area string `gorm:"not null" json:"area"` //服务区域 精确到区县级 + Addr string `gorm:"not null" json:"addr"` //详细地址 + CreateTime time.Time `gorm:"type:timestamp;default:CURRENT_TIMESTAMP;not null" json:"-"` //创建时间 +} + +// Goods 商品信息 +type Goods struct { + Id int64 `gorm:"primaryKey;autoIncrement" json:"id"` //商品ID + Name string `gorm:"not null" json:"name"` //商品名称 + GoodsType int `gorm:"not null" json:"goodsType"` //商品类型 1基础服务 2附加产品 3附加服务 + Time string `json:"time"` //时间 + Price int32 `gorm:"not null" json:"price"` //价格 + ParentId int64 `gorm:"not null" json:"parentId"` //商品父ID + BuyMany int `gorm:"not null" json:"buyMany"` //同类型是否可购买多个 0否 1是 + GoodsDetail string `gorm:"not null" json:"goodsDetail"` //商品详情 图片URL + CreateTime time.Time `gorm:"type:timestamp;default:CURRENT_TIMESTAMP" json:"-"` //创建时间 +} + +// PetGoods 宠物商品 +type PetGoods struct { + Id int64 `gorm:"primaryKey;autoIncrement" json:"id"` //id + Assortment int `gorm:"not null" json:"assortment"` //品种 优先用品种查询 如不存在 则用类型及大小查询 + PetType int `gorm:"not null" json:"petType"` //类型 1猫 2狗 + Size int `gorm:"not null" json:"size"` //大小 1大 2中 3小 + GoodsId int64 `gorm:"not null" json:"goodsId"` //商品ID + CreateTime time.Time `gorm:"type:timestamp;default:CURRENT_TIMESTAMP" json:"-"` //创建时间 +} + +// SystemConfig 系统配置 +type SystemConfig struct { + id int `gorm:"primaryKey;autoIncrement" json:"id"` //id + Name string `json:"name"` //名称 + Desc string `json:"desc"` //描述 + ConfigType string `json:"configType"` //类型 1关于我们 2在线客服 3拨打电话 + DescType int `json:"descType"` //1文本 2链接 +} + +// OrderMain 主订单 +type OrderMain struct { + Id int64 `gorm:"primaryKey;autoIncrement" json:"id"` //id + OrderId string `gorm:"index;unique;not null" json:"orderId"` //主订单号 + Uid int64 `gorm:"index;not null" json:"uid"` //用户id + Status int `json:"status"` //主订单状态 1待服务 2服务中 3已完成 + CreateTime time.Time `gorm:"type:timestamp;default:CURRENT_TIMESTAMP" json:"createTime"` //创建时间 +} + +// OrderSub 子订单 +type OrderSub struct { + Id int64 `gorm:"primaryKey;autoIncrement" json:"id"` //id + MainOrderId int64 `gorm:"index" json:"mainOrderId"` //主订单ID + Status int `gorm:"not null" json:"status"` //子订单状态 1待服务 2服务中 3已完成 4已取消 + PetId int64 `gorm:"index" json:"petId"` //宠物ID + PayType int `gorm:"not null" json:"payType"` //支付方式 1线下 2线上 + Discount int `json:"discount"` //折扣 + TotalAmount int32 `gorm:"not null" json:"totalAmount"` //总金额 + PayAmount int32 `json:"payAmount"` //实际支付金额 + PayTime string `json:"payTime"` //支付时间 + ReserveTime string `gorm:"not null" json:"reserveTime"` //预约时间 + ServiceTime string `json:"serviceTime"` //服务时间 + ServiceAddrId int64 `json:"serviceAddrId"` //服务地址信息 + ServiceRemark string `json:"serviceRemark"` //服务备注 + CreateTime time.Time `gorm:"type:timestamp;default:CURRENT_TIMESTAMP" json:"createTime"` //创建时间 + UpdateTime time.Time `gorm:"type:timestamp;default:CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP" json:"-"` //更新时间 +} + +// OrderDetail 订单明细 +type OrderDetail struct { + Id int64 `gorm:"primaryKey;autoIncrement" json:"id"` //id + SubOrderId int64 `gorm:"index;not null" json:"subOrderId"` //子订单ID + GoodsId int64 `gorm:"index;not null" json:"goodsId"` //商品ID + Amount int `gorm:"not null" json:"amount"` //价格 +} diff --git a/business/utils/wechat.go b/business/utils/wechat.go new file mode 100644 index 0000000..59d13c0 --- /dev/null +++ b/business/utils/wechat.go @@ -0,0 +1,91 @@ +package utils + +import ( + "fmt" + "github.com/dgb8901/go-wechat-miniapp-sdk/config" + "github.com/dgb8901/go-wechat-miniapp-sdk/models/response" + "github.com/dgb8901/go-wechat-miniapp-sdk/service" + "github.com/spf13/viper" + "pet-house.com/core/g" + "pet-house.com/core/server/viper_server" +) + +var CONFIG = WxConfig{ + AppId: "", + Secret: "", + Token: "", + AesKey: "", + MsgDataFormat: "", +} + +type WxConfig struct { + AppId string `mapstructure:"appId" json:"appId" yaml:"appId"` + Secret string `mapstructure:"secret" json:"secret" yaml:"secret"` + Token string `mapstructure:"token" json:"token" yaml:"token"` + AesKey string `mapstructure:"aesKey" json:"aesKey" yaml:"aesKey"` + MsgDataFormat string `mapstructure:"msgDataFormat" json:"msgDataFormat" yaml:"msgDataFormat"` +} + +// getViperConfig get viper config +func getViperConfig() viper_server.ViperConfig { + configName := "wxconfig" + return viper_server.ViperConfig{ + Debug: true, + Directory: g.ConfigDir, + Name: configName, + Type: g.ConfigType, + Watch: func(vi *viper.Viper) error { + if err := vi.Unmarshal(&CONFIG); err != nil { + return fmt.Errorf("get Unarshal error: %v", err) + } + // watch config file change + vi.SetConfigName(configName) + return nil + }, + Default: []byte(` +{ + "appId": ` + CONFIG.AppId + `, + "secret": "` + CONFIG.Secret + `", + "token": "` + CONFIG.Token + `", + "aesKey": "` + CONFIG.AesKey + `", + "msgDataFormat": ` + CONFIG.MsgDataFormat + ` +}`), + } +} + +type WechatHelper struct { + wechatService *service.WxaService +} + +var wxHelper = &WechatHelper{} + +func WechatInit() { + viper_server.Init(getViperConfig()) + cfg := &config.Cfg{ + AppId: CONFIG.AppId, + Secret: CONFIG.Secret, + Token: CONFIG.Token, + AesKey: CONFIG.AesKey, + MsgDataFormat: CONFIG.MsgDataFormat, + } + wxaConfig := config.NewInMemory(cfg) + wxaService := service.NewService(wxaConfig) + wxHelper.wechatService = wxaService +} + +func getWxaService() *service.WxaService { + return wxHelper.wechatService +} + +type WxUserInfo struct { + OpenId string + UnionId string +} + +func GetWxUserInfo(code string) (*response.JsCode2SessionResult, error) { + info, err := wxHelper.wechatService.GetUserService().Jscode2Session(code) + if err != nil { + return nil, err + } + return info, nil +} diff --git a/core/g/constant.go b/core/g/constant.go new file mode 100644 index 0000000..945f335 --- /dev/null +++ b/core/g/constant.go @@ -0,0 +1,14 @@ +package g + +const ( + ConfigType = "json" // config's type + ConfigDir = "config" // config's dir + CasbinFileName = "rbac_model.conf" // casbin rule file's name +) + +// Status 0:unknown,1:true,2:false +const ( + StatusUnknown int = iota + StatusTrue + StatusFalse +) diff --git a/core/g/constant_test.go b/core/g/constant_test.go new file mode 100644 index 0000000..b1679f4 --- /dev/null +++ b/core/g/constant_test.go @@ -0,0 +1,26 @@ +package g + +import "testing" + +func TestCons(t *testing.T) { + t.Run("test constant param", func(t *testing.T) { + if ConfigType != "json" { + t.Errorf("ConfigType want %s but get %s", "json", ConfigType) + } + if ConfigDir != "config" { + t.Errorf("ConfigDir want %s but get %s", "config", ConfigDir) + } + if CasbinFileName != "rbac_model.conf" { + t.Errorf("CasbinFileName want %s but get %s", "rbac_model.conf", CasbinFileName) + } + if StatusUnknown != 0 { + t.Errorf("StatusUnknown want %d but get %d", 0, StatusUnknown) + } + if StatusTrue != 1 { + t.Errorf("StatusTrue want %d but get %d", 1, StatusTrue) + } + if StatusFalse != 2 { + t.Errorf("StatusFalse want %d but get %d", 2, StatusFalse) + } + }) +} diff --git a/core/g/var.go b/core/g/var.go new file mode 100644 index 0000000..2a5b577 --- /dev/null +++ b/core/g/var.go @@ -0,0 +1,14 @@ +package g + +import ( + "os" + "strings" +) + +var ( + TestRedisPwd = strings.TrimSpace(os.Getenv("redisProPwd")) + TestMysqlAddr = strings.TrimSpace(os.Getenv("mysqlAddr")) + TestMysqlPwd = strings.TrimSpace(os.Getenv("mysqlPwd")) + TestMongoAddr = strings.TrimSpace(os.Getenv("mongoAddr")) + TestDbType = strings.TrimSpace(os.Getenv("dbType")) +) diff --git a/core/go.mod b/core/go.mod new file mode 100644 index 0000000..79556b4 --- /dev/null +++ b/core/go.mod @@ -0,0 +1,146 @@ +module pet-house.com/core + +go 1.22.0 + +require ( + github.com/aviddiviner/gin-limit v0.0.0-20170918012823-43b5f79762c1 + github.com/bwmarrin/snowflake v0.3.0 + github.com/casbin/casbin/v2 v2.77.1 + github.com/casbin/gorm-adapter/v3 v3.21.0 + github.com/fsnotify/fsnotify v1.7.0 + github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6 + github.com/gin-contrib/pprof v1.4.0 + github.com/gin-gonic/gin v1.9.1 + github.com/go-gormigrate/gormigrate/v2 v2.1.2 + github.com/go-ping/ping v1.1.0 + github.com/go-playground/validator/v10 v10.19.0 + github.com/iris-contrib/middleware/cors v0.0.0-20240111010557-e34016a4d6ee + github.com/iris-contrib/middleware/secure v0.0.0-20240111010557-e34016a4d6ee + github.com/kataras/iris/v12 v12.2.10 + github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible + github.com/mattn/go-colorable v0.1.13 + github.com/redis/go-redis/v9 v9.4.0 + github.com/shopspring/decimal v1.2.0 + github.com/spf13/viper v1.18.2 + github.com/unrolled/secure v1.14.0 + go.mongodb.org/mongo-driver v1.14.0 + go.uber.org/zap v1.27.0 + golang.org/x/crypto v0.19.0 + gopkg.in/yaml.v2 v2.4.0 + gorm.io/driver/mysql v1.5.6 + gorm.io/gorm v1.25.8 +) + +require ( + github.com/BurntSushi/toml v1.3.2 // indirect + github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect + github.com/CloudyKit/jet/v6 v6.2.0 // indirect + github.com/Joker/jade v1.1.3 // indirect + github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect + github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 // indirect + github.com/andybalholm/brotli v1.1.0 // indirect + github.com/aymerick/douceur v0.2.0 // indirect + github.com/bytedance/sonic v1.9.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/fatih/color v1.15.0 // indirect + github.com/fatih/structs v1.1.0 // indirect + github.com/flosch/pongo2/v4 v4.0.2 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/glebarez/go-sqlite v1.20.3 // indirect + github.com/glebarez/sqlite v1.7.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-sql-driver/mysql v1.7.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect + github.com/golang-sql/sqlexp v0.1.0 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/uuid v1.5.0 // indirect + github.com/gorilla/css v1.0.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/iris-contrib/schema v0.0.6 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.13.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.1 // indirect + github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect + github.com/jackc/pgtype v1.12.0 // indirect + github.com/jackc/pgx/v4 v4.17.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/jonboulle/clockwork v0.4.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kataras/blocks v0.0.8 // indirect + github.com/kataras/golog v0.1.11 // indirect + github.com/kataras/pio v0.0.13 // indirect + github.com/kataras/sitemap v0.0.6 // indirect + github.com/kataras/tunnel v0.0.4 // indirect + github.com/klauspost/compress v1.17.4 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/lestrrat-go/strftime v1.0.6 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mailgun/raymond/v2 v2.0.48 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/microcosm-cc/bluemonday v1.0.26 // indirect + github.com/microsoft/go-mssqldb v0.17.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/montanaflynn/stats v0.6.6 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230126093431-47fa9a501578 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/schollz/closestmatch v2.1.0+incompatible // indirect + github.com/sirupsen/logrus v1.8.1 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/tdewolff/minify/v2 v2.20.14 // indirect + github.com/tdewolff/parse/v2 v2.7.8 // indirect + github.com/tidwall/gjson v1.14.4 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.1.2 // indirect + github.com/xdg-go/stringprep v1.0.4 // indirect + github.com/yosssi/ace v0.0.5 // indirect + github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect + go.uber.org/multierr v1.10.0 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/sync v0.5.0 // indirect + golang.org/x/sys v0.17.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect + google.golang.org/protobuf v1.32.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/driver/postgres v1.4.4 // indirect + gorm.io/driver/sqlserver v1.4.1 // indirect + gorm.io/plugin/dbresolver v1.3.0 // indirect + modernc.org/libc v1.22.2 // indirect + modernc.org/mathutil v1.5.0 // indirect + modernc.org/memory v1.5.0 // indirect + modernc.org/sqlite v1.20.3 // indirect +) diff --git a/core/go.sum b/core/go.sum new file mode 100644 index 0000000..41f349e --- /dev/null +++ b/core/go.sum @@ -0,0 +1,861 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y= +cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic= +cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= +cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/firestore v1.14.0 h1:8aLcKnMPoldYU3YHgu4t2exrKhLQkqaXAGqT0ljrFVw= +cloud.google.com/go/firestore v1.14.0/go.mod h1:96MVaHLsEhbvkBEdZgfN+AS/GIkco1LRpH9Xp9YZfzQ= +cloud.google.com/go/longrunning v0.5.4 h1:w8xEcbZodnA2BbW6sVirkkoC+1gP8wS57EUUgGS0GVg= +cloud.google.com/go/longrunning v0.5.4/go.mod h1:zqNVncI0BOP8ST6XQD1+VcvuShMmq7+xFSzOL++V0dI= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.0.0/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.0.0/go.mod h1:+6sju8gk8FRmSajX3Oz4G5Gm7P+mbqE9FVaXXFYTkCM= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= +github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c= +github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= +github.com/CloudyKit/jet/v6 v6.2.0 h1:EpcZ6SR9n28BUGtNJSvlBqf90IpjeFr36Tizxhn/oME= +github.com/CloudyKit/jet/v6 v6.2.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/Joker/hpp v1.0.0 h1:65+iuJYdRXv/XyN62C1uEmmOx3432rNG/rKlX6V7Kkc= +github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= +github.com/Joker/jade v1.1.3 h1:Qbeh12Vq6BxURXT1qZBRHsDxeURB8ztcL6f3EXSGeHk= +github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM= +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/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 h1:KkH3I3sJuOLP3TjA/dfr4NAY8bghDwnXiU7cTKxQqo0= +github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06/go.mod h1:7erjKLwalezA0k99cWs5L11HWOAPNjdUZ6RxH1BXbbM= +github.com/agiledragon/gomonkey/v2 v2.2.0 h1:QJWqpdEhGV/JJy70sZ/LDnhbSlMrqHAWHcNOjz1kyuI= +github.com/agiledragon/gomonkey/v2 v2.2.0/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= +github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +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-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aviddiviner/gin-limit v0.0.0-20170918012823-43b5f79762c1 h1:OLrWlPirfG33eUv6tAZBb2SW2K+xBenfJIWJ+nORMTU= +github.com/aviddiviner/gin-limit v0.0.0-20170918012823-43b5f79762c1/go.mod h1:v4YSuwMq3CcRnBfKwKzvCATH1jq46sgSHJ8EEUx2ne0= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +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/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= +github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= +github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/casbin/casbin/v2 v2.77.1 h1:+H46VamJCTlmCPcb0N99Zaj4tSorfuvBh3v5lyGopeU= +github.com/casbin/casbin/v2 v2.77.1/go.mod h1:mzGx0hYW9/ksOSpw3wNjk3NRAroq5VMFYUQ6G43iGPk= +github.com/casbin/gorm-adapter/v3 v3.21.0 h1:1YOVpBvGc38H717WKSNCUuhsixFOjF8jmNukq792WBc= +github.com/casbin/gorm-adapter/v3 v3.21.0/go.mod h1:pvTTuyP2Es8VPHLyUssGtvOb3ETYD2tG7TfT5K8X2Sg= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +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/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +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/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= +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 h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +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/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw= +github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6 h1:6VSn3hB5U5GeA6kQw4TwWIWbOhtvR2hmbBJnTOtqTWc= +github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6/go.mod h1:YxOVT5+yHzKvwhsiSIWmbAYM3Dr9AEEbER2dVayfBkg= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/pprof v1.4.0 h1:XxiBSf5jWZ5i16lNOPbMTVdgHBdhfGRD5PZ1LWazzvg= +github.com/gin-contrib/pprof v1.4.0/go.mod h1:RrehPJasUVBPK6yTUwOl8/NP6i0vbUgmxtis+Z5KE90= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/glebarez/go-sqlite v1.20.3 h1:89BkqGOXR9oRmG58ZrzgoY/Fhy5x0M+/WV48U5zVrZ4= +github.com/glebarez/go-sqlite v1.20.3/go.mod h1:u3N6D/wftiAzIOJtZl6BmedqxmmkDfH3q+ihjqxC9u0= +github.com/glebarez/sqlite v1.7.0 h1:A7Xj/KN2Lvie4Z4rrgQHY8MsbebX3NyWsL3n2i82MVI= +github.com/glebarez/sqlite v1.7.0/go.mod h1:PkeevrRlF/1BhQBCnzcMWzgrIk7IOop+qS2jUYLfHhk= +github.com/go-gormigrate/gormigrate/v2 v2.1.2 h1:F/d1hpHbRAvKezziV2CC5KUE82cVe9zTgHSBoOOZ4CY= +github.com/go-gormigrate/gormigrate/v2 v2.1.2/go.mod h1:9nHVX6z3FCMCQPA7PThGcA55t22yKQfK/Dnsf5i7hUo= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.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-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw= +github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= +github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +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/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +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.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/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +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/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47 h1:k4Tw0nt6lwro3Uin8eqoET7MDA4JnT8YgbCjc/g5E3k= +github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= +github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +github.com/hashicorp/consul/api v1.25.1 h1:CqrdhYzc8XZuPnhIYZWH45toM0LB9ZeYr/gvpLVI3PE= +github.com/hashicorp/consul/api v1.25.1/go.mod h1:iiLVwR/htV7mas/sy0O+XSuEnrdBUUydemjxcUrAt4g= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/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.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +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/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= +github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= +github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= +github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= +github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= +github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= +github.com/iris-contrib/httpexpect/v2 v2.15.2 h1:T9THsdP1woyAqKHwjkEsbCnMefsAFvk8iJJKokcJ3Go= +github.com/iris-contrib/httpexpect/v2 v2.15.2/go.mod h1:JLDgIqnFy5loDSUv1OA2j0mb6p/rDhiCqigP22Uq9xE= +github.com/iris-contrib/middleware/cors v0.0.0-20240111010557-e34016a4d6ee h1:srr0HUvI9IAximtqxhF0+WSy9cv9Dd5YlH8RMEkHtsw= +github.com/iris-contrib/middleware/cors v0.0.0-20240111010557-e34016a4d6ee/go.mod h1:hAsE+pfCAz1M2cCfsEolTmaBwHCyzchRFyCk5I1+kDo= +github.com/iris-contrib/middleware/secure v0.0.0-20240111010557-e34016a4d6ee h1:SpFT+8y4KVdrxoaB/jIjfNGT56dwcIPO/J16+pcLRs8= +github.com/iris-contrib/middleware/secure v0.0.0-20240111010557-e34016a4d6ee/go.mod h1:4TzL/OnRB+/kIiSTQb9iXwkNVc6bCn9l5hYfhYnDu88= +github.com/iris-contrib/schema v0.0.6 h1:CPSBLyx2e91H2yJzPuhGuifVRnZBBJ3pCOMbOvPZaTw= +github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA= +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.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys= +github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI= +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.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y= +github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +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.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w= +github.com/jackc/pgtype v1.12.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.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E= +github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw= +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/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.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= +github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kataras/blocks v0.0.8 h1:MrpVhoFTCR2v1iOOfGng5VJSILKeZZI+7NGfxEh3SUM= +github.com/kataras/blocks v0.0.8/go.mod h1:9Jm5zx6BB+06NwA+OhTbHW1xkMOYxahnqTN5DveZ2Yg= +github.com/kataras/golog v0.1.11 h1:dGkcCVsIpqiAMWTlebn/ZULHxFvfG4K43LF1cNWSh20= +github.com/kataras/golog v0.1.11/go.mod h1:mAkt1vbPowFUuUGvexyQ5NFW6djEgGyxQBIARJ0AH4A= +github.com/kataras/iris/v12 v12.2.10 h1:rEJVM7qMoyhv8wpgkA1yGxibFcONE0jkJ70LFLibTAA= +github.com/kataras/iris/v12 v12.2.10/go.mod h1:z4+E+kLMqZ7U4WtDsYfFnG7BjMTXLkdzMAXLVMLnMNs= +github.com/kataras/pio v0.0.13 h1:x0rXVX0fviDTXOOLOmr4MUxOabu1InVSTu5itF8CXCM= +github.com/kataras/pio v0.0.13/go.mod h1:k3HNuSw+eJ8Pm2lA4lRhg3DiCjVgHlP8hmXApSej3oM= +github.com/kataras/sitemap v0.0.6 h1:w71CRMMKYMJh6LR2wTgnk5hSgjVNB9KL60n5e2KHvLY= +github.com/kataras/sitemap v0.0.6/go.mod h1:dW4dOCNs896OR1HmG+dMLdT7JjDk7mYBzoIRwuj5jA4= +github.com/kataras/tunnel v0.0.4 h1:sCAqWuJV7nPzGrlb0os3j49lk2JhILT0rID38NHNLpA= +github.com/kataras/tunnel v0.0.4/go.mod h1:9FkU4LaeifdMWqZu7o20ojmW4B7hdhv2CMLwfnHGpYw= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +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/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +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/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8= +github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is= +github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible h1:Y6sqxHMyB1D2YSzWkLibYKgg+SwmyFU9dF2hn6MdTj4= +github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible/go.mod h1:ZQnN8lSECaebrkQytbHj4xNgtg8CR7RYXnPok8e0EHA= +github.com/lestrrat-go/strftime v1.0.6 h1:CFGsDEt1pOpFNU+TJB0nhz9jl+K0hZSLE205AhTIGQQ= +github.com/lestrrat-go/strftime v1.0.6/go.mod h1:f7jQKgV5nnJpYgdEasS+/y7EsTb8ykN2z68n3TtcTaw= +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.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw= +github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +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.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +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.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +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/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= +github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= +github.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE= +github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +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/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/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 h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +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/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= +github.com/montanaflynn/stats v0.6.6 h1:Duep6KMIDpY4Yo11iFsvyqJDyfzLF9+sndUKT+v64GQ= +github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nats-io/nats.go v1.31.0 h1:/WFBHEc/dOKBF6qf1TZhrdEfTmOZ5JzdJ+Y3m6Y/p7E= +github.com/nats-io/nats.go v1.31.0/go.mod h1:di3Bm5MLsoB4Bx61CBTsxuarI36WbhAwOm8QrW39+i8= +github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= +github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +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/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/redis/go-redis/v9 v9.4.0 h1:Yzoz33UZw9I/mFhx4MNrB6Fk+XHO1VukNcCa1+lwyKk= +github.com/redis/go-redis/v9 v9.4.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/remyoudompheng/bigfft v0.0.0-20230126093431-47fa9a501578 h1:VstopitMQi3hZP0fzvnsLmzXZdQGc4bEcgu24cp+d4M= +github.com/remyoudompheng/bigfft v0.0.0-20230126093431-47fa9a501578/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +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.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sagikazarmark/crypt v0.17.0 h1:ZA/7pXyjkHoK4bW4mIdnCLvL8hd+Nrbiw7Dqk7D4qUk= +github.com/sagikazarmark/crypt v0.17.0/go.mod h1:SMtHTvdmsZMuY/bpZoqokSoChIrcJ/epOxZN58PbZDg= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= +github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk= +github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= +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/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +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/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/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +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.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +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.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/tdewolff/minify/v2 v2.20.14 h1:sktSuVixRwk0ryQjqvKBu/uYS+MWmkwEFMEWtFZ+TdE= +github.com/tdewolff/minify/v2 v2.20.14/go.mod h1:qnIJbnG2dSzk7LIa/UUwgN2OjS8ir6RRlqc0T/1q2xY= +github.com/tdewolff/parse/v2 v2.7.8 h1:1cnVqa8L63xFkc2vfRsZTM6Qy35nJpTvQ2Uvdv3vbvs= +github.com/tdewolff/parse/v2 v2.7.8/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA= +github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= +github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo= +github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= +github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= +github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/unrolled/secure v1.14.0 h1:u9vJTU/pR4Bny0ntLUMxdfLtmIRGvQf2sEFuA0TG9AE= +github.com/unrolled/secure v1.14.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= +github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA= +github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.etcd.io/etcd/api/v3 v3.5.10 h1:szRajuUUbLyppkhs9K6BRtjY37l66XQQmw7oZRANE4k= +go.etcd.io/etcd/api/v3 v3.5.10/go.mod h1:TidfmT4Uycad3NM/o25fG3J07odo4GBB9hoxaodFCtI= +go.etcd.io/etcd/client/pkg/v3 v3.5.10 h1:kfYIdQftBnbAq8pUWFXfpuuxFSKzlmM5cSn76JByiT0= +go.etcd.io/etcd/client/pkg/v3 v3.5.10/go.mod h1:DYivfIviIuQ8+/lCq4vcxuseg2P2XbHygkKwFo9fc8U= +go.etcd.io/etcd/client/v2 v2.305.10 h1:MrmRktzv/XF8CvtQt+P6wLUlURaNpSDJHFZhe//2QE4= +go.etcd.io/etcd/client/v2 v2.305.10/go.mod h1:m3CKZi69HzilhVqtPDcjhSGp+kA1OmbNn0qamH80xjA= +go.etcd.io/etcd/client/v3 v3.5.10 h1:W9TXNZ+oB3MCd/8UjxHTWK5J9Nquw9fQBLJd5ne5/Ao= +go.etcd.io/etcd/client/v3 v3.5.10/go.mod h1:RVeBnDz2PUEZqTpgqwAtUd8nAPf5kjyFyND7P1VkOKc= +go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= +go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +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/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +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/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +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= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/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-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +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.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o= +golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= +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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +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-20181114220301-adae6a3d119a/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-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/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-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +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-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +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-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= +golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= +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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/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-20210220032951-036812b2e83c/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.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +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-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-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-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/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-20200122134326-e047566fdf82/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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/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-20220704084225-05e143d24a9e/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-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +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.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/text v0.3.0/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.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-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-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/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-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.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +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= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +google.golang.org/api v0.153.0 h1:N1AwGhielyKFaUqH07/ZSIQR3uNPcV7NVw0vj+j4iR4= +google.golang.org/api v0.153.0/go.mod h1:3qNJX5eOmhiWYc67jRA/3GsDw97UFb5ivv7Y2PrriAY= +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.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= +google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= +google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo= +google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +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/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +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/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/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +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.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +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.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/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.0-20210107192922-496545a6307b/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/driver/mysql v1.3.2/go.mod h1:ChK6AHbHgDCFZyJp0F+BmVGb06PSIoh9uVYKAlRbb2U= +gorm.io/driver/mysql v1.5.6 h1:Ld4mkIickM+EliaQZQx3uOJDJHtrd70MxAUqWqlx3Y8= +gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= +gorm.io/driver/postgres v1.4.4 h1:zt1fxJ+C+ajparn0SteEnkoPg0BQ6wOWXEQ99bteAmw= +gorm.io/driver/postgres v1.4.4/go.mod h1:whNfh5WhhHs96honoLjBAMwJGYEuA3m1hvgUbNXhPCw= +gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0= +gorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig= +gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +gorm.io/gorm v1.23.7/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.8 h1:WAGEZ/aEcznN4D03laj8DKnehe1e9gYQAjW8xyPRdeo= +gorm.io/gorm v1.25.8/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/plugin/dbresolver v1.3.0 h1:uFDX3bIuH9Lhj5LY2oyqR/bU6pqWuDgas35NAPF4X3M= +gorm.io/plugin/dbresolver v1.3.0/go.mod h1:Pr7p5+JFlgDaiM6sOrli5olekJD16YRunMyA2S7ZfKk= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/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= +modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0= +modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug= +modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= +modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/sqlite v1.20.3 h1:SqGJMMxjj1PHusLxdYxeQSodg7Jxn9WWkaAQjKrntZs= +modernc.org/sqlite v1.20.3/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A= +moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs= +moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/core/helper/arr/array.go b/core/helper/arr/array.go new file mode 100644 index 0000000..f608e9c --- /dev/null +++ b/core/helper/arr/array.go @@ -0,0 +1,62 @@ +package arr + +import "sync" + +type ArrayType interface { + Add(value interface{}) + Check(value interface{}) bool + Len() int + Values() map[interface{}]bool +} + +// CheckType type for check array data +type CheckArrayType struct { + values map[interface{}]bool + sm sync.Mutex + len int +} + +// NewCheckArrayType +func NewCheckArrayType(len int) *CheckArrayType { + return &CheckArrayType{values: make(map[interface{}]bool, len)} +} + +// Add +func (ct *CheckArrayType) Add(value interface{}) { + defer ct.sm.Unlock() + ct.sm.Lock() + ct.values[value] = true + ct.len++ +} + +// AddMutil +func (ct *CheckArrayType) AddMutil(values ...interface{}) { + for _, v := range values { + v := v + ct.Add(v) + } +} + +// Check +func (ct *CheckArrayType) Check(value interface{}) bool { + defer ct.sm.Unlock() + ct.sm.Lock() + if b, ok := ct.values[value]; ok && b { + return true + } + return false +} + +// Len +func (ct *CheckArrayType) Len() int { + defer ct.sm.Unlock() + ct.sm.Lock() + return ct.len +} + +// Values +func (ct *CheckArrayType) Values() map[interface{}]bool { + defer ct.sm.Unlock() + ct.sm.Lock() + return ct.values +} diff --git a/core/helper/arr/array_test.go b/core/helper/arr/array_test.go new file mode 100644 index 0000000..6808fb3 --- /dev/null +++ b/core/helper/arr/array_test.go @@ -0,0 +1,90 @@ +package arr + +import ( + "log" + "testing" +) + +func TestCheckArrayType(t *testing.T) { + t.Run("check array type test with string", func(t *testing.T) { + arrayType := NewCheckArrayType(10) + arrayType.Add("1") + if !arrayType.Check("1") { + t.Errorf("1 should be in array type,but it is not in.") + } + if arrayType.Check("2") { + t.Errorf("2 should not be in array type,but it is in.") + } + if arrayType.Check(1) { + t.Errorf("int 1 should not be in array type,but it is in.") + } + if arrayType.Len() != 1 { + t.Errorf("array type len should be 1 ,but get array type len is %d.", arrayType.Len()) + } + arrayType.AddMutil("2", "3", "4") + if arrayType.Len() != 4 { + t.Errorf("array type len should be 4 ,but get array type len is %d.", arrayType.Len()) + } + for i, v := range arrayType.Values() { + log.Println(v) + if !arrayType.Check(i) { + t.Errorf("%v should be in array type,but it is not in", i) + } + } + }) + + t.Run("check array type test with uint", func(t *testing.T) { + arrayType := NewCheckArrayType(10) + var one uint = 1 + var two uint = 2 + var three uint = 3 + var four uint = 4 + arrayType.Add(one) + if !arrayType.Check(one) { + t.Errorf("1 should be in array type,but it is not in.") + } + if arrayType.Check(two) { + t.Errorf("2 should not be in array type,but it is in.") + } + if arrayType.Len() != 1 { + t.Errorf("array type len should be 1 ,but get array type len is %d.", arrayType.Len()) + } + arrayType.AddMutil(two, three, four) + if arrayType.Len() != 4 { + t.Errorf("array type len should be 4 ,but get array type len is %d.", arrayType.Len()) + } + for i, v := range arrayType.Values() { + log.Println(v) + if !arrayType.Check(i) { + t.Errorf("%v should be in array type,but it is not in", i) + } + } + }) + + t.Run("check array type test with int", func(t *testing.T) { + arrayType := NewCheckArrayType(10) + arrayType.Add(1) + if !arrayType.Check(1) { + t.Errorf("1 should be in array type,but it is not in.") + } + if arrayType.Check(2) { + t.Errorf("2 should not be in array type,but it is in.") + } + if arrayType.Check("1") { + t.Errorf("string 1 should not be in array type,but it is in.") + } + if arrayType.Len() != 1 { + t.Errorf("array type len should be 1 ,but get array type len is %d.", arrayType.Len()) + } + arrayType.AddMutil(2, 3, 4) + if arrayType.Len() != 4 { + t.Errorf("array type len should be 4 ,but get array type len is %d.", arrayType.Len()) + } + for i, v := range arrayType.Values() { + log.Println(v) + if !arrayType.Check(i) { + t.Errorf("%v should be in array type,but it is not in", i) + } + } + }) +} diff --git a/core/helper/arr/unit_join.go b/core/helper/arr/unit_join.go new file mode 100644 index 0000000..19ece37 --- /dev/null +++ b/core/helper/arr/unit_join.go @@ -0,0 +1,17 @@ +package arr + +import "strconv" + +// 连接 unit slice 为字符串 +func UnitJoin(ss []uint, sep string) string { + var rs string + for index, item := range ss { + itemS := strconv.FormatUint(uint64(item), 10) + if index < len(ss)-1 { + rs += itemS + sep + } else { + rs += itemS + } + } + return rs +} diff --git a/core/helper/arr/unit_join_test.go b/core/helper/arr/unit_join_test.go new file mode 100644 index 0000000..fb2c143 --- /dev/null +++ b/core/helper/arr/unit_join_test.go @@ -0,0 +1,47 @@ +package arr + +import "testing" + +func TestUnitJoin(t *testing.T) { + type args struct { + ss []uint + sep string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "success", + args: struct { + ss []uint + sep string + }{ss: []uint{1, 2, 3, 4}, sep: ","}, + want: "1,2,3,4", + }, + { + name: "success", + args: struct { + ss []uint + sep string + }{ss: []uint{1, 2, 3, 4}, sep: "||"}, + want: "1||2||3||4", + }, + { + name: "success", + args: struct { + ss []uint + sep string + }{ss: []uint{1, 2, 3, 4}, sep: "-"}, + want: "1-2-3-4", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := UnitJoin(tt.args.ss, tt.args.sep); got != tt.want { + t.Errorf("UnitJoin() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/core/helper/dir/dir.go b/core/helper/dir/dir.go new file mode 100644 index 0000000..5950fa0 --- /dev/null +++ b/core/helper/dir/dir.go @@ -0,0 +1,286 @@ +package dir + +import ( + "bufio" + "crypto/md5" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "path" + "path/filepath" + "runtime" + "strings" + + "gopkg.in/yaml.v2" +) + +// GetCurrentAbPath 项目根目录绝对路径 +func GetCurrentAbPath() string { + dir := GetCurrentAbPathByExecutable() + tmpDir, _ := filepath.EvalSymlinks(os.TempDir()) + if strings.Contains(dir, tmpDir) { + wd, _ := os.Getwd() + return wd + } + if strings.Contains(dir, "/tmp") { + return filepath.Dir(dir) + } + + return dir +} + +// GetCurrentAbPathByExecutable 当前执行文件目录 +func GetCurrentAbPathByExecutable() string { + exePath, err := os.Executable() + if err != nil { + log.Fatal(err) + } + res, _ := filepath.EvalSymlinks(filepath.Dir(exePath)) + return res +} + +// GetCurrentFuncNameByCaller 当前方法执行函数名 +func GetCurrentFuncNameByCaller() string { + pc := make([]uintptr, 1) + runtime.Callers(2, pc) + f := runtime.FuncForPC(pc[0]) + return f.Name() +} + +// RealPath 基于构件执行文件的绝对文件路径 +func RealPath(fp string) (string, error) { + if path.IsAbs(fp) { + return fp, nil + } + wd, err := os.Getwd() + return path.Join(wd, fp), err +} + +// SelfPath 完整的执行文件绝对路径 +func SelfPath() string { + path, _ := filepath.Abs(os.Args[0]) + return path +} + +// SelfDir 执行文件目录完整路径 +func SelfDir() string { + return filepath.Dir(SelfPath()) +} + +// Basename 从路径中提取文件名 +func Basename(fp string) string { + return path.Base(fp) +} + +// Dir 从路径中获取目录路径 +func Dir(fp string) string { + return path.Dir(fp) +} + +//InsureDir 新建不存在的文件夹 +func InsureDir(fp string) error { + if IsExist(fp) { + return nil + } + return os.MkdirAll(fp, os.ModePerm) +} + +// IsExist 检测文件或者目录是否存在 +// 不存在的时候将返回 fasle +func IsExist(fp string) bool { + _, err := os.Stat(fp) + return err == nil || os.IsExist(err) +} + +// IsFile checks whether the path is a file, +// it returns false when it's a directory or does not exist. +func IsFile(fp string) bool { + f, e := os.Stat(fp) + if e != nil { + return false + } + return !f.IsDir() +} + +func Remove(filename string) error { + if IsFile(filename) && IsExist(filename) { + return os.Remove(filename) + } + return nil +} + +func ReadBytes(cpath string) ([]byte, error) { + if !IsExist(cpath) { + return nil, fmt.Errorf("%s not exists", cpath) + } + + if !IsFile(cpath) { + return nil, fmt.Errorf("%s not file", cpath) + } + + return ioutil.ReadFile(cpath) +} + +func ReadString(cpath string) (string, error) { + bs, err := ReadBytes(cpath) + if err != nil { + return "", err + } + + return string(bs), nil +} + +func ReadStringTrim(cpath string) (string, error) { + out, err := ReadString(cpath) + if err != nil { + return "", err + } + + return strings.TrimSpace(out), nil +} + +func ReadYaml(cpath string, cptr interface{}) error { + bs, err := ReadBytes(cpath) + if err != nil { + return fmt.Errorf("cannot read %s: %s", cpath, err.Error()) + } + + err = yaml.Unmarshal(bs, cptr) + if err != nil { + return fmt.Errorf("cannot parse %s: %s", cpath, err.Error()) + } + + return nil +} + +func ReadJson(cpath string, cptr interface{}) error { + os.MkdirAll(path.Dir(cpath), os.ModePerm) + bs, err := ReadBytes(cpath) + if err != nil { + return fmt.Errorf("cannot read %s: %s", cpath, err.Error()) + } + + err = json.Unmarshal(bs, cptr) + if err != nil { + return fmt.Errorf("cannot parse %s: %s", cpath, err.Error()) + } + + return nil +} + +func WriteBytes(filePath string, b []byte) (int, error) { + os.MkdirAll(path.Dir(filePath), os.ModePerm) + fw, err := os.Create(filePath) + if err != nil { + return 0, err + } + defer fw.Close() + return fw.Write(b) +} + +func WriteString(filePath, s string) (int, error) { + return WriteBytes(filePath, []byte(s)) +} + +func MD5(file string) (string, error) { + f, err := os.Open(file) + if err != nil { + return "", err + } + defer f.Close() + + r := bufio.NewReader(f) + h := md5.New() + + _, err = io.Copy(h, r) + if err != nil { + return "", err + } + return hex.EncodeToString(h.Sum(nil)), nil +} + +func Md5Byte(p []byte) (string, error) { + h := md5.New() + _, err := h.Write(p) + if err != nil { + return "", err + } + return hex.EncodeToString(h.Sum(nil)), nil +} + +func OpenLogFile(fp string) (*os.File, error) { + os.MkdirAll(path.Dir(fp), os.ModePerm) + return os.OpenFile(fp, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) +} + +// 添加文本 +func AppendFile(filePath string, b []byte) error { + os.MkdirAll(path.Dir(filePath), os.ModePerm) + f, err := os.OpenFile(filePath, os.O_APPEND|os.O_RDWR|os.O_CREATE, os.ModePerm) + if err != nil { + return err + } + defer f.Close() + f.WriteString(string(b) + "\r\n\r\n") + + return nil +} + +// list dirs under dirPath +func DirsUnder(dirPath string) ([]string, error) { + if !IsExist(dirPath) { + return []string{}, nil + } + + fs, err := ioutil.ReadDir(dirPath) + if err != nil { + return []string{}, err + } + + sz := len(fs) + if sz == 0 { + return []string{}, nil + } + + ret := make([]string, 0, sz) + for i := 0; i < sz; i++ { + if fs[i].IsDir() { + name := fs[i].Name() + if name != "." && name != ".." { + ret = append(ret, name) + } + } + } + + return ret, nil +} + +// list files under dirPath +func FilesUnder(dirPath string) ([]string, error) { + if !IsExist(dirPath) { + return []string{}, nil + } + + fs, err := ioutil.ReadDir(dirPath) + if err != nil { + return []string{}, err + } + + sz := len(fs) + if sz == 0 { + return []string{}, nil + } + + ret := make([]string, 0, sz) + for i := 0; i < sz; i++ { + if !fs[i].IsDir() { + ret = append(ret, fs[i].Name()) + } + } + + return ret, nil +} diff --git a/core/helper/global/ip.go b/core/helper/global/ip.go new file mode 100644 index 0000000..580ffd3 --- /dev/null +++ b/core/helper/global/ip.go @@ -0,0 +1,85 @@ +package global + +import ( + "fmt" + "net" + "regexp" + "strings" + "time" + + "pet-house.com/core/helper/arr" +) + +func GetMacAddr() string { + if getMacAddrInterface() == nil { + return "" + } + addr := getMacAddrInterface().HardwareAddr.String() + if len(addr) > 0 { + addr = strings.ReplaceAll(addr, ":", "") + addr = strings.ToUpper(addr) + return addr + } + return "" +} + +func getMacAddrInterface() *net.Interface { + netInterfaces, err := net.Interfaces() + if err != nil { + return nil + } + re, err := regexp.Compile(`^(ens|eth|waln|以太网|Ethernet)[0-9]*`) + if err != nil { + return nil + } + nameCheck := arr.NewCheckArrayType(0) + nameCheck.AddMutil("eth0", "waln0", "以太网", "Ethernet", "ens0") + for _, netInterface := range netInterfaces { + if nameCheck.Check(netInterface.Name) { + return &netInterface + } + } + for _, netInterface := range netInterfaces { + if re.MatchString(netInterface.Name) { + return &netInterface + } + } + return nil +} + +func check(ip, network string) bool { + parseIp, subnet, err := net.ParseCIDR(network) + if err != nil { + return false + } + if ip == parseIp.String() { + return false + } + return subnet.Contains(net.ParseIP(ip)) +} + +func LocalIP(network string) string { + ip := "" + if addrs, err := net.InterfaceAddrs(); err == nil { + for i, addr := range addrs { + i += 1 + if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() && !ipnet.IP.IsMulticast() && !ipnet.IP.IsLinkLocalUnicast() && !ipnet.IP.IsLinkLocalMulticast() && ipnet.IP.To4() != nil { + ip = ipnet.IP.String() + if len(ip) > 0 && check(ip, network) { + return ip + } + } + } + } + return ip +} + +func IsPortInUse(host string, port int64) bool { + conn, err := net.DialTimeout("tcp", net.JoinHostPort(host, fmt.Sprintf("%d", port)), time.Second*1) + if err == nil { + conn.Close() + return true + } + + return false +} diff --git a/core/helper/global/ip_test.go b/core/helper/global/ip_test.go new file mode 100644 index 0000000..c992cc4 --- /dev/null +++ b/core/helper/global/ip_test.go @@ -0,0 +1,62 @@ +package global + +import ( + "strings" + "testing" +) + +var network = "10.0.0.1/22" + +func TestLocalIP(t *testing.T) { + want := "172.18.236.240" + t.Run("test get local ip", func(t *testing.T) { + ip := LocalIP(network) + if ip != want { + t.Errorf("LocalIP() want get %s but get %s", want, ip) + } + }) +} + +func TestGetMacAddrs(t *testing.T) { + want := "00155DDB2E65" + t.Run("test get mac addr", func(t *testing.T) { + mac := GetMacAddr() + if mac != strings.ToUpper(want) { + t.Errorf("GetMacAddr() want get %s but get %s", want, mac) + } + }) +} + +func TestGetMacAddrInterface(t *testing.T) { + want := "eth0" + t.Run("test get mac addr interface", func(t *testing.T) { + mai := getMacAddrInterface() + if mai == nil { + t.Error("mac addr interface is nil") + return + } + if mai.Name != want { + t.Errorf("getMacAddrInterface() want get %s but get %s", want, mai.Name) + } + }) +} +func TestCheck(t *testing.T) { + t.Run("test ip check", func(t *testing.T) { + if !check("10.0.1.1", network) { + t.Error("ip check is fail") + return + } + if check("192.168.0.1", network) { + t.Error("ip check is fail") + return + } + }) +} + +func TestIsPortInUse(t *testing.T) { + t.Run("test is port in use", func(t *testing.T) { + if !IsPortInUse("10.0.0.26", 9092) { + t.Errorf("IsPortInUse(9092) must be return ture but return false") + } + }) +} diff --git a/core/helper/global/ssh.go b/core/helper/global/ssh.go new file mode 100644 index 0000000..828e9bc --- /dev/null +++ b/core/helper/global/ssh.go @@ -0,0 +1,214 @@ +package global + +import ( + "errors" + "fmt" + "net" + "regexp" + "strconv" + "strings" + "time" + + "log" + + "github.com/shopspring/decimal" + "golang.org/x/crypto/ssh" +) + +var ( + ErrConnectFail = errors.New("SSH 连接失败") + ErrNewSessionFail = errors.New("SSH 新建会话失败") + ErrRunCommandFail = errors.New("SSH 执行命令失败") +) + +type Cli struct { + IP string //IP地址 + Username string //用户名 + Password string //密码 + Port int //端口号 + client *ssh.Client //ssh客户端 + LastResult string //最近一次Run的结果 + Debug bool +} + +// 创建命令行对象 +// @param ip IP地址 +// @param username 用户名 +// @param password 密码 +// @param debug 是否调试 +// @param port 端口号,默认22 +func NewSSH(ip string, username string, password string, debug bool, port ...int) *Cli { + cli := new(Cli) + cli.IP = ip + cli.Username = username + cli.Password = password + cli.Debug = debug + if len(port) <= 0 { + cli.Port = 22 + } else { + cli.Port = port[0] + } + return cli +} + +// 执行shell +// @param shell shell脚本命令 +func (c Cli) Run(shell string) (string, error) { + if c.client == nil { + if err := c.connect(); err != nil { + if c.Debug { + log.Println(err.Error()) + } + return "", ErrConnectFail + } + } + defer c.client.Close() + session, err := c.client.NewSession() + if err != nil { + if c.Debug { + log.Println(err.Error()) + } + return "", ErrNewSessionFail + } + defer session.Close() + buf, err := session.CombinedOutput(shell) + if err != nil { + if c.Debug { + log.Println(err.Error()) + } + return "", ErrRunCommandFail + } + + c.LastResult = string(buf) + return c.LastResult, nil +} + +// 连接 +func (c *Cli) connect() error { + config := ssh.ClientConfig{ + User: c.Username, + Auth: []ssh.AuthMethod{ssh.Password(c.Password)}, + HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { + return nil + }, + Timeout: 10 * time.Second, + } + addr := fmt.Sprintf("%s:%d", c.IP, c.Port) + sshClient, err := ssh.Dial("tcp", addr, &config) + if err != nil { + if c.Debug { + log.Println(err.Error()) + } + return err + } + c.client = sshClient + return nil +} + +type DeviceMem struct { + Free decimal.Decimal + Total decimal.Decimal + FreeRound decimal.Decimal +} + +// GetMem 内存信息 +func (c *Cli) GetMem() (DeviceMem, error) { + var deviceMen DeviceMem + totalDec, err := c.getMenInfo("cat /proc/meminfo | grep -w MemTotal", "MemTotal:") + if err != nil { + return deviceMen, fmt.Errorf("memTotal %w", err) + } + freeDec, err := c.getMenInfo("cat /proc/meminfo | grep -w MemFree", "MemFree:") + if err != nil { + return deviceMen, fmt.Errorf("memFree %w", err) + } + buffersDec, err := c.getMenInfo("cat /proc/meminfo | grep -w Buffers", "Buffers:") + if err != nil { + return deviceMen, fmt.Errorf("buffers %w", err) + } + cachedDec, err := c.getMenInfo("cat /proc/meminfo | grep -w Cached", "Cached:") + if err != nil { + return deviceMen, fmt.Errorf("cached %w", err) + } + + deviceMen.Total = totalDec + deviceMen.Free = freeDec.Add(buffersDec).Add(cachedDec) + deviceMen.FreeRound = deviceMen.Free.DivRound(deviceMen.Total, 2) + return deviceMen, nil +} + +// getMenInfo 内存信息 +func (c *Cli) getMenInfo(cmd, key string) (decimal.Decimal, error) { + info, err := c.Run(cmd) + if err != nil { + return decimal.Zero, fmt.Errorf("%s %w", cmd, err) + } + + dec, err := decimal.NewFromString(strings.TrimSpace(strings.ReplaceAll(strings.ReplaceAll(info, key, ""), "kB", ""))) + if err != nil { + return decimal.Zero, fmt.Errorf("NewFromString %w", err) + } + return dec, err +} + +// GetDf 硬盘率 +func (c *Cli) GetDf() (string, error) { + total, err := c.Run("df /sdcard -h") + if err != nil { + return "", fmt.Errorf("df /sdcard -h %w", err) + } + flysnowRegexp := regexp.MustCompile(`(100|[1-9]?\d(\.\d\d?\d?)?)%`) + params := flysnowRegexp.FindStringSubmatch(total) + + if len(params) > 0 { + return params[0], nil + } + return "", nil +} + +// GetSignal wifi信号 +func (c *Cli) GetSignal() (string, error) { + signal, err := c.Run("iw dev wlan0 link | grep -w signal:") + if err != nil { + return "", fmt.Errorf("iw dev wlan0 link | grep -w signal: %w", err) + } + + return strings.TrimSpace(strings.ReplaceAll(signal, "signal:", "")), nil +} + +// GetDatetime 当前时间 +func (c *Cli) GetDatetime() (string, error) { + datetime, err := c.Run("date +'%Y/%m/%d %T %Z'") + if err != nil { + return "", fmt.Errorf("date %w", err) + } + + return datetime, nil +} + +// GetCpuTemp CPU温度 +func (c *Cli) GetCpuTemp() (float64, error) { + var ct float64 + cmd := "acpi -t" + cpuTemp, err := c.Run(cmd) + if err != nil { + return 0, fmt.Errorf("%s : %w", cmd, err) + } + cpuTemps := strings.Split(cpuTemp, "\n") + + for _, v := range cpuTemps { + flysnowRegexp := regexp.MustCompile(`[1-9]\d*\.\d*|0\.\d*[1-9]\d*`) + params := flysnowRegexp.FindStringSubmatch(v) + if len(params) > 0 { + f, err := strconv.ParseFloat(strings.Trim(params[0], "\n"), 64) + if err != nil { + return 0, fmt.Errorf("ParseFloat %w", err) + } + current := f / 100 + if current > ct { + ct = current + } + } + } + return ct, nil +} diff --git a/core/helper/global/ssh_test.go b/core/helper/global/ssh_test.go new file mode 100644 index 0000000..8f82b92 --- /dev/null +++ b/core/helper/global/ssh_test.go @@ -0,0 +1,111 @@ +package global + +import ( + _ "embed" + "log" + "os" + "testing" +) + +func Test_NewSSH(t *testing.T) { + sshPwd := os.Getenv("sshPwd") + t.Run("测试新建ssh链接", func(t *testing.T) { + ip := "10.0.1.14" + name := "root" + sshClient := NewSSH(ip, name, sshPwd, true, 2022) + if sshClient == nil { + t.Errorf("客户端为空") + return + } + }) +} +func Test_GetMem(t *testing.T) { + sshPwd := os.Getenv("sshPwd") + t.Run("测试获取设备内存", func(t *testing.T) { + ip := "10.0.1.14" + name := "root" + sshClient := NewSSH(ip, name, sshPwd, true, 2022) + if sshClient == nil { + t.Errorf("客户端为空") + return + } + mem, err := sshClient.GetMem() + if err != nil { + t.Errorf(err.Error()) + return + } + log.Println(mem.Total.String()) + }) +} +func Test_GetDf(t *testing.T) { + sshPwd := os.Getenv("sshPwd") + t.Run("测试获取设备硬盘使用", func(t *testing.T) { + ip := "10.0.1.14" + name := "root" + sshClient := NewSSH(ip, name, sshPwd, true, 2022) + if sshClient == nil { + t.Errorf("客户端为空") + return + } + _, err := sshClient.GetDf() + if err != nil { + t.Errorf(err.Error()) + return + } + }) +} +func Test_GetSignal(t *testing.T) { + sshPwd := os.Getenv("sshPwd") + t.Run("测试获取设备信号使用", func(t *testing.T) { + ip := "10.0.1.14" + name := "root" + sshClient := NewSSH(ip, name, sshPwd, true, 2022) + if sshClient == nil { + t.Errorf("客户端为空") + return + } + _, err := sshClient.GetSignal() + if err != nil { + t.Errorf(err.Error()) + return + } + }) +} + +func Test_GetDatetime(t *testing.T) { + sshPwd := os.Getenv("sshPwd") + t.Run("测试获取设备时间使用", func(t *testing.T) { + ip := "10.0.1.14" + name := "root" + sshClient := NewSSH(ip, name, sshPwd, true, 2022) + if sshClient == nil { + t.Errorf("客户端为空") + return + } + _, err := sshClient.GetDatetime() + if err != nil { + t.Errorf(err.Error()) + return + } + }) +} +func Test_GetCpuTemp(t *testing.T) { + sshPwd := os.Getenv("sshPwd") + t.Run("测试获取设备时间使用", func(t *testing.T) { + ip := "10.0.1.14" + name := "root" + sshClient := NewSSH(ip, name, sshPwd, true, 2022) + if sshClient == nil { + t.Errorf("客户端为空") + return + } + cpuTemp, err := sshClient.GetCpuTemp() + if err != nil { + t.Errorf(err.Error()) + return + } + if cpuTemp <= 0 { + t.Errorf("cpu temp is 0") + } + }) +} diff --git a/core/helper/global/time.go b/core/helper/global/time.go new file mode 100644 index 0000000..52412fe --- /dev/null +++ b/core/helper/global/time.go @@ -0,0 +1,121 @@ +package global + +import ( + "database/sql/driver" + "fmt" + "math" + "math/rand" + "strings" + "time" +) + +// DateTime 自定义事件类型 +type DateTime time.Time + +// 日期格式 +const ( + DateLayout = "2006-01-02" + DateTimeLayout = "2006-01-02 15:04:05" + TimeLayout = "15:04:05" + BuildTimeLayout = "2006.0102.150405" + TimestampLayout = "20060102150405" +) + +var StartTime = time.Now() + +func (dt *DateTime) UnmarshalJSON(data []byte) (err error) { + value := strings.Trim(string(data), "\"") + now, err := time.ParseInLocation(DateTimeLayout, value, time.Local) + *dt = DateTime(now) + return +} + +func (dt DateTime) MarshalJSON() ([]byte, error) { + b := make([]byte, 0, len(DateTimeLayout)+2) + b = append(b, '"') + b = time.Time(dt).AppendFormat(b, DateTimeLayout) + b = append(b, '"') + return b, nil +} + +func (dt DateTime) Value() (driver.Value, error) { + var zeroTime time.Time + ti := time.Time(dt) + if ti.UnixNano() == zeroTime.UnixNano() { + return nil, nil + } + return ti, nil +} + +func (dt *DateTime) Scan(v interface{}) error { + if value, ok := v.(time.Time); ok { + *dt = DateTime(value) + return nil + } + return nil +} + +func (dt DateTime) String() string { + return time.Time(dt).Format(DateTimeLayout) +} + +func UpTime() time.Duration { + return time.Since(StartTime) +} + +func UpTimeString() string { + d := UpTime() + days := d / (time.Hour * 24) + d -= days * 24 * time.Hour + hours := d / time.Hour + d -= hours * time.Hour + minutes := d / time.Minute + d -= minutes * time.Minute + seconds := d / time.Second + return fmt.Sprintf("%d Days %d Hours %d Mins %d Secs", days, hours, minutes, seconds) +} + +// 获取时区 +func GetLocation() (*time.Location, error) { + location, err := time.LoadLocation("Local") + if err != nil { + return location, nil + } + return location, nil +} + +func GetDuration(t int64, v string) time.Duration { + switch v { + case "h": + return time.Hour * time.Duration(t) + case "m": + return time.Minute * time.Duration(t) + case "s": + return time.Second * time.Duration(t) + default: + return time.Minute * time.Duration(t) + } +} + +func SleepRandomDuration() { + ns := int64(5) * 1000000000 + // 以当前时间为随机数种子,如果所有 log-agent-updater 在同一时间启动,系统时间是相同的,那么随机种子就是一样的 + // 问题不大,批量ssh去启动 log-agent-updater 的话也是一个顺次的过程 + r := rand.New(rand.NewSource(time.Now().UnixNano())) + d := time.Duration(r.Int63n(ns)) * time.Nanosecond + time.Sleep(d) +} + +// OverTimeNow 超时比较 +func OverTimeNow(compareTime, subTime time.Time, sub int64) (bool, int64, error) { + location, err := GetLocation() + if err != nil { + return false, 0, err + } + subT := compareTime.In(location).Sub(subTime) + abs := int64(math.Abs(math.Ceil(subT.Minutes()))) + if abs > sub { + return true, abs, nil + } + return false, abs, nil +} diff --git a/core/helper/http/http.go b/core/helper/http/http.go new file mode 100644 index 0000000..8afc2a5 --- /dev/null +++ b/core/helper/http/http.go @@ -0,0 +1,296 @@ +package http + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "log" + "mime/multipart" + "net/http" + "os" + "strings" + "time" + + "pet-house.com/core/helper/str" +) + +var ErrInvalidDataStruct = errors.New("invalid response data struct") +var ErrBaseAuthConfig = errors.New("base auth config is error") +var ErrEmptyFileNameField = errors.New("must set field filename") + +type client struct { + config *Config + cookie *http.Cookie +} + +type Config struct { + TimeOver int64 + TimeOut int64 + Headers map[string]string // request headers + CookieName string + Host string +} + +// BaseAuth +type BaseAuth struct { + Enable bool + Account string + Pwd string +} + +// NewClient +func NewClient(configs ...*Config) *client { + var config *Config + if len(configs) == 0 || configs[0] == nil { + config = &Config{TimeOut: 30, TimeOver: 5, Headers: map[string]string{}} + } else { + config = configs[0] + } + if config.TimeOut == 0 { + config.TimeOut = 30 + } + if config.TimeOver == 0 { + config.TimeOver = 5 + } + if config.Host == "" { + config.Host = "http://127.0.0.1:7777" + } + return &client{config: config} +} + +// NewResponse +func NewResponse(path string) *ServerResponse { + return &ServerResponse{path: path} +} + +type ServerResponse struct { + path string + baseAuth *BaseAuth + Data interface{} `json:"data"` + body io.Reader + fields map[string]string +} + +// SetBaseAuth +func (sr *ServerResponse) SetBaseAuth(account, pwd string) { + sr.baseAuth = &BaseAuth{ + Enable: true, + Account: account, + Pwd: pwd, + } +} + +// BaseAuth +func (sr *ServerResponse) BaseAuth() *BaseAuth { + return sr.baseAuth +} + +// SetFields +func (sr *ServerResponse) SetFields(fields map[string]string) { + sr.fields = fields +} + +// GetFields +func (sr *ServerResponse) GetFields() map[string]string { + return sr.fields +} + +// SetUploadFile +func (sr *ServerResponse) SetUploadFile(name string) { + f, err := os.Open(name) + if err != nil { + return + } + sr.body = f +} + +// Close +func (sr *ServerResponse) Close() { + if f, ok := sr.body.(*os.File); ok && f != nil { + f.Close() + } +} + +func (n *client) GetCookie() *http.Cookie { + return n.cookie +} + +// Check +func (n *client) Check(sr *ServerResponse) error { + ba := sr.BaseAuth() + if ba == nil { + return nil + } + if ba.Enable && (ba.Account == "" || ba.Pwd == "") { + return ErrBaseAuthConfig + } + return nil +} + +// getFullPath +func (n *client) getFullPath(path string) string { + log.Println("fullpath:", path) + return str.Join(n.config.Host, path) +} + +// Post +func (n *client) Post(sr *ServerResponse, data string) ([]byte, error) { + err := n.Check(sr) + if err != nil { + return nil, err + } + result := n.request("POST", n.getFullPath(sr.path), sr.BaseAuth(), strings.NewReader(data)) + if len(result) == 0 { + return result, fmt.Errorf("Post %s 没有返回数据", n.getFullPath(sr.path)) + } + if !json.Valid(result) || sr.Data == nil { + sr.Data = string(result) + return result, nil + } + err = json.Unmarshal(result, sr.Data) + if err != nil { + return result, fmt.Errorf("执行解码失败: %s 错误:%w ,结果: %v", n.getFullPath(sr.path), err, string(result)) + } + return result, nil +} + +// GetFile +func (n *client) GetFile(sr *ServerResponse) error { + err := n.Check(sr) + if err != nil { + return err + } + _ = n.request("GET", n.getFullPath(sr.path), sr.BaseAuth(), nil) + return nil +} + +// Upload 上传文件 +func (n *client) Upload(sr *ServerResponse) ([]byte, error) { + err := n.Check(sr) + if err != nil { + return nil, err + } + + if sr.fields == nil { + return nil, ErrEmptyFileNameField + } + if filename, ok := sr.fields["filename"]; !ok || filename == "" { + return nil, ErrEmptyFileNameField + } + + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + fw, err := writer.CreateFormFile("file", sr.fields["filename"]) + if err != nil { + return nil, fmt.Errorf("create form file %v", err) + } + + _, err = io.Copy(fw, sr.body) + if err != nil { + return nil, fmt.Errorf("copying fileWriter %v", err) + } + + for k, v := range sr.fields { + _ = writer.WriteField(k, v) + } + + err = writer.Close() // close writer before POST request + if err != nil { + return nil, fmt.Errorf("writerClose: %v", err) + } + + n.config.Headers["Content-Type"] = writer.FormDataContentType() + + result := n.request("POST", n.getFullPath(sr.path), sr.BaseAuth(), body) + if len(result) == 0 { + return result, fmt.Errorf("Upload %s 没有返回数据", n.getFullPath(sr.path)) + } + + if !json.Valid(result) || sr.Data == nil { + sr.Data = string(result) + return result, nil + } + + err = json.Unmarshal(result, sr.Data) + if err != nil { + return result, fmt.Errorf("执行解码失败: %s 错误:%w ,结果: %v", n.getFullPath(sr.path), err, string(result)) + } + + return result, nil +} + +// Get 获取数据 +func (n *client) Get(sr *ServerResponse) ([]byte, error) { + err := n.Check(sr) + if err != nil { + return nil, err + } + result := n.request("GET", n.getFullPath(sr.path), sr.BaseAuth(), nil) + if len(result) == 0 { + return result, fmt.Errorf("Get %s 没有返回数据", n.getFullPath(sr.path)) + } + if !json.Valid(result) || sr.Data == nil { + sr.Data = string(result) + return result, nil + } + err = json.Unmarshal(result, sr.Data) + if err != nil { + return result, fmt.Errorf("执行解码失败: %s 获取服务解析返回内容报错 %w : ,结果:[%s]", n.getFullPath(sr.path), err, string(result)) + } + return result, nil +} + +func (n *client) request(method, fullpath string, ba *BaseAuth, body io.Reader) []byte { + result := make(chan []byte, 30) + T := time.NewTicker(time.Duration(n.config.TimeOver) * time.Second) + go func() { + t := time.Duration(n.config.TimeOut) * time.Second + Client := http.Client{Timeout: t} + req, err := http.NewRequest(method, fullpath, body) + if err != nil { + result <- nil + return + } + + if len(n.config.Headers) > 0 { + for key, value := range n.config.Headers { + req.Header.Set(key, value) + } + } else { + req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") + } + if ba != nil && ba.Enable { + req.SetBasicAuth(ba.Account, ba.Pwd) + } + var resp *http.Response + resp, err = Client.Do(req) + if err != nil { + result <- nil + return + } + if n.config.CookieName != "" && resp.Cookies() != nil { + for _, cookie := range resp.Cookies() { + if cookie.Name == n.config.CookieName { + n.cookie = cookie + } + } + } + defer resp.Body.Close() + + buf := bytes.NewBuffer(nil) + io.Copy(buf, resp.Body) + result <- buf.Bytes() + + }() + + for { + select { + case x := <-result: + return x + case <-T.C: + return []byte("请求超时") + } + } +} diff --git a/core/helper/http/http_test.go b/core/helper/http/http_test.go new file mode 100644 index 0000000..f8e4e5d --- /dev/null +++ b/core/helper/http/http_test.go @@ -0,0 +1,167 @@ +package http + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "reflect" + "testing" + + "github.com/gin-gonic/gin" +) + +func startGin() { + r := gin.Default() + gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) { + log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers) + } + + r.POST("/foo", func(c *gin.Context) { + c.JSON(http.StatusOK, "foo") + }) + + r.GET("/bar", func(c *gin.Context) { + c.JSON(http.StatusOK, "bar") + }) + + r.GET("/status", func(c *gin.Context) { + c.JSON(http.StatusOK, "ok") + }) + r.StaticFS("/txt", http.Dir("./txt")) + + r.MaxMultipartMemory = 8 << 20 // 8 MiB + r.POST("/upload", func(c *gin.Context) { + // single file + file, err := c.FormFile("file") + if err != nil { + c.JSON(http.StatusBadRequest, "upload failed!") + return + } + + // Upload the file to specific dst. + c.SaveUploadedFile(file, "./txt") + + c.JSON(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename)) + }) + + // Listen and Server in http://0.0.0.0:7777 + r.Run(":7777") +} + +func TestNewClient(t *testing.T) { + + client := NewClient() + t.Run("test new client", func(t *testing.T) { + if client == nil { + t.Error("client is nil") + return + } + if client.config.TimeOut != 30 { + t.Errorf("client default timeout want %d but get %d", 30, client.config.TimeOut) + return + } + if client.config.TimeOver != 5 { + t.Errorf("client default timeover want %d but get %d", 5, client.config.TimeOver) + return + } + if client.config.Host != "http://127.0.0.1:7777" { + t.Errorf("client default timeover want %s but get %s", "http://127.0.0.1:7777", client.config.Host) + return + } + }) + + response := NewResponse("/foo") + fullpath := client.getFullPath("/fullpath") + if client.getFullPath("/fullpath") != "http://127.0.0.1:7777/fullpath" { + t.Errorf("client default timeover want %s but get %s", "http://127.0.0.1:7777", fullpath) + return + } + t.Run("test new response", func(t *testing.T) { + if response == nil { + t.Error("response is nil") + return + } + if response.path != "/foo" { + t.Errorf("response default path want %s but get %s", "/foo", response.path) + } + if response.Data != nil { + t.Errorf("response default data is not nil") + } + ba := response.BaseAuth() + if ba != nil { + t.Errorf("response default baseauth is not nil") + } + response.SetBaseAuth("account", "pwd") + ba = response.BaseAuth() + if ba.Account != "account" { + t.Errorf("response baseauth default accout is not account") + } + if ba.Pwd != "pwd" { + t.Errorf("response baseauth default password is not pwd") + } + if !ba.Enable { + t.Errorf("response baseauth default enable is not true") + } + fields := response.GetFields() + if fields != nil { + t.Errorf("response default fields is not nil") + } + f := map[string]string{ + "a": "a", + "b": "b", + } + response.SetFields(f) + fields = response.GetFields() + if !reflect.DeepEqual(f, fields) { + t.Error("fields is set failed") + } + }) + + t.Run("test get file", func(t *testing.T) { + response := NewResponse("/txt/file.txt") + err := client.GetFile(response) + if err != nil { + t.Error(err.Error()) + return + } + }) + t.Run("test upload file", func(t *testing.T) { + response := NewResponse("/upload") + response.SetUploadFile("./upload.txt") + defer response.Close() + response.SetFields(map[string]string{"filename": "upload.txt"}) + _, err := client.Upload(response) + if err != nil { + t.Error(err.Error()) + return + } + }) + + t.Run("test get", func(t *testing.T) { + response := NewResponse("/bar") + _, err := client.Get(response) + if err != nil { + t.Error(err.Error()) + return + } + }) + + t.Run("test post", func(t *testing.T) { + response := NewResponse("/foo") + b, err := json.Marshal(map[string]interface{}{ + "a": "a", + "b": "b", + }) + if err != nil { + t.Error(err.Error()) + return + } + _, err = client.Post(response, string(b)) + if err != nil { + t.Error(err.Error()) + return + } + }) + +} diff --git a/core/helper/http/main_test.go b/core/helper/http/main_test.go new file mode 100644 index 0000000..17bb3d8 --- /dev/null +++ b/core/helper/http/main_test.go @@ -0,0 +1,12 @@ +package http + +import ( + "os" + "testing" +) + +func TestMain(m *testing.M) { + go startGin() + code := m.Run() + os.Exit(code) +} diff --git a/core/helper/ping/example/main.go b/core/helper/ping/example/main.go new file mode 100644 index 0000000..8f051e2 --- /dev/null +++ b/core/helper/ping/example/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "fmt" + + "pet-house.com/core/helper/ping" +) + +func main() { + ip := "10.0.0.113" + ok, msg := ping.GetPingMsg(ip) + if !ok { + fmt.Printf("%s ping is fault,get msg %s \n", ip, msg) + } +} diff --git a/core/helper/ping/ping.go b/core/helper/ping/ping.go new file mode 100644 index 0000000..76bf31e --- /dev/null +++ b/core/helper/ping/ping.go @@ -0,0 +1,36 @@ +package ping + +import ( + "fmt" + "time" + + "github.com/go-ping/ping" +) + +// GetPingMsg ping 检查网络情况 +// icmp 检查5次,每次300毫秒超时,一共1500毫秒超时 +// 只要有一个次响应就成功 +func GetPingMsg(devIp string) (bool, string) { + if devIp == "" { + return false, "设备ip为空,请检查设备是否绑定ip" + } + pinger, err := ping.NewPinger(devIp) + if err != nil { + return false, err.Error() + } + pinger.Count = 3 + // 修改相关问题https://githubmemory.com/repo/go-ping/ping/issues/168 + pinger.Size = 548 + pinger.Interval = time.Duration(500 * time.Millisecond) + pinger.Timeout = time.Duration(1500 * time.Millisecond) + pinger.SetPrivileged(true) + err = pinger.Run() + if err != nil { + return false, err.Error() + } + stats := pinger.Statistics() + if stats.PacketsRecv >= 1 { + return true, fmt.Sprintf("设备ip(%s)可以访问", devIp) + } + return false, fmt.Sprintf("设备(%s)可能已离线或者网络不稳定", devIp) +} diff --git a/core/helper/ping/ping_test.go b/core/helper/ping/ping_test.go new file mode 100644 index 0000000..ea63f27 --- /dev/null +++ b/core/helper/ping/ping_test.go @@ -0,0 +1,17 @@ +package ping + +import ( + "testing" +) + +func Test_GetPingMsg(t *testing.T) { + ips := []string{"www.weibo.com", "www.qq.com", "www.baidu.com"} + for _, ip := range ips { + t.Run("测试 ping 方法:"+ip, func(t *testing.T) { + ok, msg := GetPingMsg(ip) + if !ok { + t.Errorf("%s ping is fault,get msg %s", ip, msg) + } + }) + } +} diff --git a/core/helper/str/string.go b/core/helper/str/string.go new file mode 100644 index 0000000..07126e8 --- /dev/null +++ b/core/helper/str/string.go @@ -0,0 +1,63 @@ +package str + +import ( + "encoding/json" + "regexp" + "strings" + "unicode" +) + +type StringArray string + +func (r StringArray) MarshalJSON() ([]byte, error) { + items := []string{} + if string(r) != "" { + items = strings.Split(string(r), ",") + } + for _, item := range items { + item = strings.TrimSpace(item) + } + return json.Marshal(items) +} + +func Ellipsis(text string, length int) string { + r := []rune(text) + if len(r) > length { + return string(r[0:length]) + "..." + } + return text +} + +func HasChinese(str string) bool { + for _, r := range str { + if unicode.Is(unicode.Scripts["Han"], r) || (regexp.MustCompile("[\u3002\uff1b\uff0c\uff1a\u201c\u201d\uff08\uff09\u3001\uff1f\u300a\u300b]").MatchString(string(r))) { + return true + } + } + return false +} + +func IsGBK(data []byte) bool { + length := len(data) + var i int = 0 + for i < length { + if data[i] <= 0x7f { + //编码0~127,只有一个字节的编码,兼容ASCII码 + i++ + continue + } else { + //大于127的使用双字节编码,落在gbk编码范围内的字符 + if data[i] >= 0x81 && + data[i] <= 0xfe && + data[i+1] >= 0x40 && + data[i+1] <= 0xfe && + data[i+1] != 0xf7 { + i += 2 + continue + } else { + return false + } + } + } + return true +} diff --git a/core/helper/str/struct.go b/core/helper/str/struct.go new file mode 100644 index 0000000..12a3497 --- /dev/null +++ b/core/helper/str/struct.go @@ -0,0 +1,30 @@ +package str + +import ( + "reflect" + "strings" +) + +// StructToMap 利用反射将结构体转化为map +func StructToMap(obj interface{}) map[string]interface{} { + obj1 := reflect.TypeOf(obj) + obj2 := reflect.ValueOf(obj) + + var data = make(map[string]interface{}) + for i := 0; i < obj1.NumField(); i++ { + data[obj1.Field(i).Name] = obj2.Field(i).Interface() + } + return data +} + +// 连接字符串 +func Join(strs ...string) string { + var builder strings.Builder + if len(strs) == 0 { + return "" + } + for _, str := range strs { + builder.WriteString(str) + } + return builder.String() +} diff --git a/core/helper/str/struct_test.go b/core/helper/str/struct_test.go new file mode 100644 index 0000000..4ebdf3a --- /dev/null +++ b/core/helper/str/struct_test.go @@ -0,0 +1,26 @@ +package str + +import "testing" + +func TestJoin(t *testing.T) { + t.Run("字符串拼接", func(t *testing.T) { + if got := Join("abc", " ", "def"); got != "abc def" { + t.Errorf("Join() = %v, want %v", got, "abc def") + } + }) + t.Run("字符串拼接单个字符", func(t *testing.T) { + if got := Join("abc"); got != "abc" { + t.Errorf("Join() = %v, want %v", got, "abc") + } + }) + t.Run("中文字符串拼接", func(t *testing.T) { + if got := Join("中文字符串拼接", " ", "你好"); got != "中文字符串拼接 你好" { + t.Errorf("Join() = %v, want %v", got, "中文字符串拼接 你好") + } + }) + t.Run("中文字符串拼接单个字符", func(t *testing.T) { + if got := Join("中文字符串拼接"); got != "中文字符串拼接" { + t.Errorf("Join() = %v, want %v", got, "中文字符串拼接") + } + }) +} diff --git a/core/helper/sys/cmd_darwin.go b/core/helper/sys/cmd_darwin.go new file mode 100644 index 0000000..fcd472e --- /dev/null +++ b/core/helper/sys/cmd_darwin.go @@ -0,0 +1,76 @@ +package sys + +import ( + "bytes" + "errors" + "fmt" + "os/exec" + "strings" + "syscall" + "time" +) + +func CmdOutString(name string, arg ...string) (string, error) { + bs, err := CmdOutBytes(name, arg...) + if err != nil { + return "", errors.New(fmt.Sprintf("CmdOutBytes get error : %v", err)) + } + + return string(bs), nil +} + +func CmdOutBytes(name string, arg ...string) ([]byte, error) { + cmd := exec.Command(name, arg...) + return cmd.CombinedOutput() +} + +func CmdOutTrim(name string, arg ...string) (string, error) { + out, err := CmdOutString(name, arg...) + if err != nil { + return "", err + } + + return strings.TrimSpace(string(out)), nil +} + +func CmdRun(name string, arg ...string) error { + cmd := exec.Command(name, arg...) + return cmd.Run() +} + +// CmdRunT Command run with timeout +func CmdRunT(timeout time.Duration, name string, arg ...string) (output string, err error, istimeout bool) { + cmd := exec.Command(name, arg...) + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + var b bytes.Buffer + cmd.Stdout = &b + cmd.Stderr = &b + + cmd.Start() + err, istimeout = WrapTimeout(cmd, timeout) + output = b.String() + + return +} + +func WrapTimeout(cmd *exec.Cmd, timeout time.Duration) (error, bool) { + var err error + done := make(chan error) + go func() { + done <- cmd.Wait() + }() + + select { + case <-time.After(timeout): + go func() { + <-done // allow goroutine to exit + }() + + // IMPORTANT: cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} is necessary before cmd.Start() + // err = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL) + err = cmd.Process.Kill() + return fmt.Errorf("cmd kill process %w", err), true + case err = <-done: + return err, false + } +} diff --git a/core/helper/sys/cmd_linux.go b/core/helper/sys/cmd_linux.go new file mode 100644 index 0000000..fcd472e --- /dev/null +++ b/core/helper/sys/cmd_linux.go @@ -0,0 +1,76 @@ +package sys + +import ( + "bytes" + "errors" + "fmt" + "os/exec" + "strings" + "syscall" + "time" +) + +func CmdOutString(name string, arg ...string) (string, error) { + bs, err := CmdOutBytes(name, arg...) + if err != nil { + return "", errors.New(fmt.Sprintf("CmdOutBytes get error : %v", err)) + } + + return string(bs), nil +} + +func CmdOutBytes(name string, arg ...string) ([]byte, error) { + cmd := exec.Command(name, arg...) + return cmd.CombinedOutput() +} + +func CmdOutTrim(name string, arg ...string) (string, error) { + out, err := CmdOutString(name, arg...) + if err != nil { + return "", err + } + + return strings.TrimSpace(string(out)), nil +} + +func CmdRun(name string, arg ...string) error { + cmd := exec.Command(name, arg...) + return cmd.Run() +} + +// CmdRunT Command run with timeout +func CmdRunT(timeout time.Duration, name string, arg ...string) (output string, err error, istimeout bool) { + cmd := exec.Command(name, arg...) + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + var b bytes.Buffer + cmd.Stdout = &b + cmd.Stderr = &b + + cmd.Start() + err, istimeout = WrapTimeout(cmd, timeout) + output = b.String() + + return +} + +func WrapTimeout(cmd *exec.Cmd, timeout time.Duration) (error, bool) { + var err error + done := make(chan error) + go func() { + done <- cmd.Wait() + }() + + select { + case <-time.After(timeout): + go func() { + <-done // allow goroutine to exit + }() + + // IMPORTANT: cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} is necessary before cmd.Start() + // err = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL) + err = cmd.Process.Kill() + return fmt.Errorf("cmd kill process %w", err), true + case err = <-done: + return err, false + } +} diff --git a/core/helper/sys/cmd_windows.go b/core/helper/sys/cmd_windows.go new file mode 100644 index 0000000..789abf3 --- /dev/null +++ b/core/helper/sys/cmd_windows.go @@ -0,0 +1,80 @@ +package sys + +import ( + "bytes" + "errors" + "fmt" + "os/exec" + "strings" + "syscall" + "time" +) + +func CmdOutString(name string, arg ...string) (string, error) { + bs, err := CmdOutBytes(name, arg...) + if err != nil { + return "", errors.New(fmt.Sprintf("CmdOutBytes get error : %v", err)) + } + + return string(bs), nil +} + +func CmdOutBytes(name string, arg ...string) ([]byte, error) { + cmd := exec.Command(name, arg...) + return cmd.CombinedOutput() +} + +func CmdOutTrim(name string, arg ...string) (string, error) { + out, err := CmdOutString(name, arg...) + if err != nil { + return "", err + } + + return strings.TrimSpace(string(out)), nil +} + +func CmdRun(name string, arg ...string) error { + cmd := exec.Command(name, arg...) + return cmd.Run() +} + +// CmdRunT Command run with timeout +func CmdRunT(timeout time.Duration, name string, arg ...string) (output string, err error, istimeout bool) { + cmd := exec.Command(name, arg...) + // cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + cmd.SysProcAttr = &syscall.SysProcAttr{ + HideWindow: true, + CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP, + } + var b bytes.Buffer + cmd.Stdout = &b + cmd.Stderr = &b + + cmd.Start() + err, istimeout = WrapTimeout(cmd, timeout) + output = b.String() + + return +} + +func WrapTimeout(cmd *exec.Cmd, timeout time.Duration) (error, bool) { + var err error + done := make(chan error) + go func() { + done <- cmd.Wait() + }() + + select { + case <-time.After(timeout): + go func() { + <-done // allow goroutine to exit + }() + + // IMPORTANT: cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} is necessary before cmd.Start() + // err = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL) + err = cmd.Process.Kill() + return fmt.Errorf("cmd kill process %w", err), true + case err = <-done: + return err, false + } +} diff --git a/core/helper/sys/kill.go b/core/helper/sys/kill.go new file mode 100644 index 0000000..0ba74aa --- /dev/null +++ b/core/helper/sys/kill.go @@ -0,0 +1,24 @@ +package sys + +import ( + "fmt" + "strconv" + "strings" +) + +func KillProcessByCmdline(cmdline string) error { + cmdline = strings.TrimSpace(cmdline) + if cmdline == "" { + return fmt.Errorf("cmdline is blank") + } + + pids := PidsByCmdline(cmdline) + for i := 0; i < len(pids); i++ { + out, err := CmdOutTrim("kill", "-9", strconv.Itoa(pids[i])) + if err != nil { + return fmt.Errorf("kill -9 %d fail: %v, output: %s", pids[i], err, out) + } + } + + return nil +} diff --git a/core/helper/sys/net.go b/core/helper/sys/net.go new file mode 100644 index 0000000..806acc1 --- /dev/null +++ b/core/helper/sys/net.go @@ -0,0 +1,134 @@ +package sys + +import ( + "fmt" + "net" + "strconv" + "strings" +) + +func IntranetIP() (ips []string, err error) { + ips = make([]string, 0) + + ifaces, e := net.Interfaces() + if e != nil { + return ips, e + } + + for _, iface := range ifaces { + if iface.Flags&net.FlagUp == 0 { + continue // interface down + } + + if iface.Flags&net.FlagLoopback != 0 { + continue // loopback interface + } + + // ignore docker and warden bridge + if strings.HasPrefix(iface.Name, "docker") || strings.HasPrefix(iface.Name, "w-") { + continue + } + + addrs, e := iface.Addrs() + if e != nil { + return ips, e + } + + for _, addr := range addrs { + var ip net.IP + switch v := addr.(type) { + case *net.IPNet: + ip = v.IP + case *net.IPAddr: + ip = v.IP + } + + if ip == nil || ip.IsLoopback() { + continue + } + + ip = ip.To4() + if ip == nil { + continue // not an ipv4 address + } + + ipStr := ip.String() + if IsIntranet(ipStr) { + ips = append(ips, ipStr) + } + } + } + + return ips, nil +} + +func IsIntranet(ipStr string) bool { + if strings.HasPrefix(ipStr, "10.") { + return true + } + + // for didi + if strings.HasPrefix(ipStr, "100.") { + return true + } + + if strings.HasPrefix(ipStr, "192.168.") { + return true + } + + if strings.HasPrefix(ipStr, "172.") { + // 172.16.0.0-172.31.255.255 + arr := strings.Split(ipStr, ".") + if len(arr) != 4 { + return false + } + + second, err := strconv.ParseInt(arr[1], 10, 64) + if err != nil { + return false + } + + if second >= 16 && second <= 31 { + return true + } + } + + return false +} + +// ${sn}-${hostname}-${ip} +func LocalHostIdent() string { + sn, _ := CmdOutTrim("/bin/bash", "-c", "dmidecode -s system-serial-number") + if sn != "" { + arr := strings.Fields(sn) + sn = arr[len(arr)-1] + } else { + sn = "nil" + } + + name, _ := CmdOutTrim("hostname") + + ips, _ := IntranetIP() + ip := "" + if ips != nil && len(ips) > 0 { + ip = ips[0] + } + + return fmt.Sprintf("%s-%s-%s", sn, name, ip) +} + +func GetOutboundIpaddr() string { + conn, err := net.Dial("udp4", "1.2.3.4:56") + if err != nil { + return "" + } + defer conn.Close() + + localAddr := conn.LocalAddr().String() + + if ip, _, err := net.SplitHostPort(localAddr); err != nil { + return "" + } else { + return ip + } +} diff --git a/core/helper/sys/proc.go b/core/helper/sys/proc.go new file mode 100644 index 0000000..6405891 --- /dev/null +++ b/core/helper/sys/proc.go @@ -0,0 +1,55 @@ +package sys + +import ( + "fmt" + "strconv" + "strings" + + "pet-house.com/core/helper/dir" +) + +func PidsByCmdline(cmdline string) []int { + ret := []int{} + + var dirs []string + dirs, err := dir.DirsUnder("/proc") + if err != nil { + return ret + } + + count := len(dirs) + for i := 0; i < count; i++ { + pid, err := strconv.Atoi(dirs[i]) + if err != nil { + continue + } + + cmdlineFile := fmt.Sprintf("/proc/%d/cmdline", pid) + if !dir.IsExist(cmdlineFile) { + continue + } + + cmdlineBytes, err := dir.ReadBytes(cmdlineFile) + if err != nil { + continue + } + + cmdlineBytesLen := len(cmdlineBytes) + if cmdlineBytesLen == 0 { + continue + } + + noNut := make([]byte, 0, cmdlineBytesLen) + for j := 0; j < cmdlineBytesLen; j++ { + if cmdlineBytes[j] != 0 { + noNut = append(noNut, cmdlineBytes[j]) + } + } + + if strings.Contains(string(noNut), cmdline) { + ret = append(ret, pid) + } + } + + return ret +} diff --git a/core/migration/migrate.go b/core/migration/migrate.go new file mode 100644 index 0000000..17a23c1 --- /dev/null +++ b/core/migration/migrate.go @@ -0,0 +1,118 @@ +package migration + +import ( + "errors" + "github.com/go-gormigrate/gormigrate/v2" + "pet-house.com/core/seed" + "pet-house.com/core/server/database" +) + +// MigrationCmd migration cmd +// MigrationCollection migration collections +// SeedCollection data seed collection +type MigrationCmd struct { + MigrationCollection []*gormigrate.Migration + SeedCollection []seed.SeedFunc +} + +// New MigrationCmd +func New() *MigrationCmd { + mc := &MigrationCmd{ + MigrationCollection: nil, + SeedCollection: nil, + } + + return mc +} + +// AddMigration add *gormigrate.Migration +func (mc *MigrationCmd) AddMigration(m ...*gormigrate.Migration) { + mc.MigrationCollection = append(mc.MigrationCollection, m...) +} + +// MigrationLen length of MigrationCollection +func (mc *MigrationCmd) MigrationLen() int { + return len(mc.MigrationCollection) +} + +// AddSeed add SeedFunc +func (mc *MigrationCmd) AddSeed(sf ...seed.SeedFunc) { + mc.SeedCollection = append(mc.SeedCollection, sf...) +} + +// SeedlLen length of SeedCollection +func (mc *MigrationCmd) SeedlLen() int { + return len(mc.SeedCollection) +} + +// Refresh refresh migration +func (mc *MigrationCmd) Refresh() error { + if mc.getFirstMigration() == "" { + return nil + } + err := mc.rollbackTo(mc.getFirstMigration()) + if !errors.Is(gormigrate.ErrMigrationIDDoesNotExist, err) && err != nil { + return err + } + return mc.Migrate() +} + +// rollbackTo roolback migration to migrationId +func (mc *MigrationCmd) rollbackTo(migrationId string) error { + return mc.gormigrate().RollbackTo(migrationId) +} + +// Rollback roolback migrations +func (mc *MigrationCmd) Rollback(migrationId string) error { + if mc.MigrationLen() == 0 { + return nil + } + if migrationId == "" { + err := mc.rollbackLast() + if !errors.Is(gormigrate.ErrMigrationIDDoesNotExist, err) && err != nil { + return err + } + return nil + } + err := mc.rollbackTo(migrationId) + if !errors.Is(gormigrate.ErrMigrationIDDoesNotExist, err) && err != nil { + return err + } + return nil +} + +// rollbackLast roolback the lasted migration +func (mc *MigrationCmd) rollbackLast() error { + return mc.gormigrate().RollbackLast() +} + +// Migrate exec migration cmd +func (mc *MigrationCmd) Migrate() error { + m := mc.gormigrate() + err := m.Migrate() + if err != nil { + return err + } + return nil +} + +// Seed seed data into database +func (mc *MigrationCmd) Seed() error { + if mc.SeedCollection == nil { + return nil + } + return seed.Seed(mc.SeedCollection...) +} + +// getFirstMigration get first migration's id +func (mc *MigrationCmd) getFirstMigration() string { + if mc.MigrationLen() == 0 { + return "" + } + return mc.MigrationCollection[0].ID +} + +// gormigrate create *gormigrate.Gormigrate +func (mc *MigrationCmd) gormigrate() *gormigrate.Gormigrate { + return gormigrate.New(database.Instance(), gormigrate.DefaultOptions, mc.MigrationCollection) +} diff --git a/core/migration/migrate_test.go b/core/migration/migrate_test.go new file mode 100644 index 0000000..5e62890 --- /dev/null +++ b/core/migration/migrate_test.go @@ -0,0 +1,133 @@ +package migration + +import ( + "github.com/go-gormigrate/gormigrate/v2" + "pet-house.com/core/g" + "pet-house.com/core/server/database" + "pet-house.com/core/server/zap_server" + "testing" + + "gorm.io/gorm" +) + +type Test struct { + gorm.Model +} + +var id = "20211214120700_create_tests_table" +var m = &gormigrate.Migration{ + ID: id, + Migrate: func(tx *gorm.DB) error { + return tx.AutoMigrate(&Test{}) + }, + Rollback: func(tx *gorm.DB) error { + return tx.Migrator().DropTable("tests") + }, +} + +func TestAddMigration(t *testing.T) { + migrate := New() + t.Run("migrate add migration", func(t *testing.T) { + migrate.AddMigration(m) + l := migrate.MigrationLen() + if l != 1 { + t.Errorf("MigrationLen want %d but get %d", 1, l) + } + }) +} + +type testSeed struct{} + +func (ts *testSeed) Init() error { + return nil +} +func TestAddSeed(t *testing.T) { + migrate := New() + t.Run("migrate add seed", func(t *testing.T) { + migrate.AddSeed(&testSeed{}) + l := migrate.SeedlLen() + if l != 1 { + t.Errorf("SeedlLen want %d but get %d", 1, l) + } + }) +} +func TestMigrate(t *testing.T) { + defer zap_server.Remove() + defer database.Remove() + database.CONFIG.Path = g.TestMysqlAddr + database.CONFIG.Password = g.TestMysqlPwd + migrate := New() + migrate.AddMigration(m) + t.Run("migrate migrate", func(t *testing.T) { + err := migrate.Migrate() + if err != nil { + t.Error(err.Error()) + } + }) +} +func TestRollback(t *testing.T) { + defer zap_server.Remove() + defer database.Remove() + database.CONFIG.Path = g.TestMysqlAddr + database.CONFIG.Password = g.TestMysqlPwd + migrate := New() + t.Run("migrate rollback no migrate with id", func(t *testing.T) { + err := migrate.Rollback(id) + if err != nil { + t.Error(err.Error()) + } + }) + t.Run("migrate rollback no migrate without id", func(t *testing.T) { + err := migrate.Rollback("") + if err != nil { + t.Error(err.Error()) + } + }) + migrate.AddMigration(m) + err := migrate.Migrate() + if err != nil { + t.Error(err.Error()) + } + + t.Run("migrate rollback after migrate with id", func(t *testing.T) { + err := migrate.Rollback(id) + if err != nil { + t.Error(err.Error()) + } + }) + t.Run("migrate rollback after migrate without id", func(t *testing.T) { + err := migrate.Rollback("") + if err != nil { + t.Error(err.Error()) + } + }) +} + +func TestRefresh(t *testing.T) { + defer zap_server.Remove() + defer database.Remove() + database.CONFIG.Path = g.TestMysqlAddr + database.CONFIG.Password = g.TestMysqlPwd + migrate := New() + + t.Run("migrate refresh no migrate", func(t *testing.T) { + err := migrate.Refresh() + if err != nil { + t.Error(err.Error()) + } + }) + + migrate.AddMigration(m) + err := migrate.Migrate() + if err != nil { + t.Error(err.Error()) + } + + t.Run("migrate refresh after migrate", func(t *testing.T) { + err := migrate.Refresh() + if err != nil { + t.Error(err.Error()) + } + }) + +} diff --git a/core/seed/seed.go b/core/seed/seed.go new file mode 100644 index 0000000..85b4432 --- /dev/null +++ b/core/seed/seed.go @@ -0,0 +1,19 @@ +package seed + +type SeedFunc interface { + Init() (err error) +} + +// Seed exec seed funcs +func Seed(SeedFunctions ...SeedFunc) error { + if len(SeedFunctions) == 0 { + return nil + } + for _, v := range SeedFunctions { + err := v.Init() + if err != nil { + return err + } + } + return nil +} diff --git a/core/seed/seed_test.go b/core/seed/seed_test.go new file mode 100644 index 0000000..51de931 --- /dev/null +++ b/core/seed/seed_test.go @@ -0,0 +1,18 @@ +package seed + +import "testing" + +type testSeed struct{} + +func (ts *testSeed) Init() error { + return nil +} + +func TestSeed(t *testing.T) { + t.Run("test seed data", func(t *testing.T) { + err := Seed(&testSeed{}) + if err != nil { + t.Error(err) + } + }) +} diff --git a/core/server/cache/cmd.go b/core/server/cache/cmd.go new file mode 100644 index 0000000..6f5c7a0 --- /dev/null +++ b/core/server/cache/cmd.go @@ -0,0 +1,75 @@ +package cache + +import ( + "fmt" + "strings" + + "pet-house.com/core/server/viper_server" +) + +// InitConfig initialize redis's config file +func InitConfig() error { + var cover string + if IsExist() { + fmt.Println("Your redis config is initialized , reinitialized redis will cover your redis config.") + fmt.Println("Did you want to do it ? [Y/N]") + fmt.Scanln(&cover) + switch strings.ToUpper(cover) { + case "Y": + case "N": + return nil + default: + } + } else { + fmt.Println("Redis config file is not exist!") + } + + err := Remove() + if err != nil { + return err + } + + err = initConfig() + if err != nil { + return err + } + + fmt.Println("redis initialized finished!") + return nil +} + +func initConfig() error { + var addr, dbPwd string + var db, poolSize int + fmt.Println("Please input your redis addr: ") + fmt.Printf("Redis addr default is '%s'\n", CONFIG.Addr) + fmt.Scanln(&addr) + if addr != "" { + CONFIG.Addr = addr + } + + fmt.Println("Please input your redis db: ") + fmt.Printf("Redis db default is '%d'\n", CONFIG.DB) + fmt.Scanln(&db) + if db > 0 { + CONFIG.DB = db + } + + fmt.Println("Please input your redis password: ") + fmt.Printf("Redis password default is '%s'\n", CONFIG.Password) + fmt.Scanln(&dbPwd) + if dbPwd != "" { + CONFIG.Password = dbPwd + } + + fmt.Println("Please input your redis pool size: ") + fmt.Scanln(&poolSize) + if poolSize > 0 { + CONFIG.PoolSize = poolSize + } + viper_server.Init(getViperConfig()) + if Instance() == nil { + return ErrRedisInit + } + return nil +} diff --git a/core/server/cache/cmd_test.go b/core/server/cache/cmd_test.go new file mode 100644 index 0000000..47bb06c --- /dev/null +++ b/core/server/cache/cmd_test.go @@ -0,0 +1,27 @@ +package cache + +import ( + "testing" + + "pet-house.com/core/g" + "pet-house.com/core/server/zap_server" +) + +func TestInitConfig(t *testing.T) { + defer zap_server.Remove() + t.Run("test redis's file initialize", func(t *testing.T) { + CONFIG.Password = g.TestRedisPwd + err := InitConfig() + if err != nil { + t.Error(err) + return + } + if !IsExist() { + t.Errorf("config's files is not exist.") + return + } + if err := Remove(); err != nil { + t.Error(err) + } + }) +} diff --git a/core/server/cache/config.go b/core/server/cache/config.go new file mode 100644 index 0000000..b73c5ba --- /dev/null +++ b/core/server/cache/config.go @@ -0,0 +1,70 @@ +package cache + +import ( + "encoding/json" + "fmt" + "strconv" + + "github.com/spf13/viper" + "pet-house.com/core/g" + "pet-house.com/core/server/viper_server" +) + +var CONFIG = Redis{ + DB: 0, + Addr: "127.0.0.1:6379", + Password: "", + PoolSize: 0, +} + +type Redis struct { + DB int `mapstructure:"db" json:"db" yaml:"db"` + Addr string `mapstructure:"addr" json:"addr" yaml:"addr"` + Password string `mapstructure:"password" json:"password" yaml:"password"` + PoolSize int `mapstructure:"pool-size" json:"pool-size" yaml:"pool-size"` +} + +// IsExist config file is exist +func IsExist() bool { + return getViperConfig().IsFileExist() +} + +// Remove remove config file +func Remove() error { + return getViperConfig().Remove() +} + +// Recover +func Recover() error { + b, err := json.Marshal(CONFIG) + if err != nil { + return err + } + return getViperConfig().Recover(b) +} + +// getViperConfig get viper config +func getViperConfig() viper_server.ViperConfig { + configName := "redis" + return viper_server.ViperConfig{ + Debug: true, + Directory: g.ConfigDir, + Name: configName, + Type: g.ConfigType, + Watch: func(vi *viper.Viper) error { + if err := vi.Unmarshal(&CONFIG); err != nil { + return fmt.Errorf("get Unarshal error: %v", err) + } + // watch config file change + vi.SetConfigName(configName) + return nil + }, + Default: []byte(` +{ + "db": ` + strconv.FormatInt(int64(CONFIG.DB), 10) + `, + "addr": "` + CONFIG.Addr + `", + "password": "` + CONFIG.Password + `", + "pool-size": ` + strconv.FormatInt(int64(CONFIG.PoolSize), 10) + ` +}`), + } +} diff --git a/core/server/cache/config_test.go b/core/server/cache/config_test.go new file mode 100644 index 0000000..03e8279 --- /dev/null +++ b/core/server/cache/config_test.go @@ -0,0 +1,16 @@ +package cache + +import ( + "testing" +) + +func TestIsExist(t *testing.T) { + t.Run("Test Remove function", func(t *testing.T) { + if err := Remove(); err != nil { + t.Error(err) + } + if IsExist() { + t.Errorf("config's files remove is fail.") + } + }) +} diff --git a/core/server/cache/index.go b/core/server/cache/index.go new file mode 100644 index 0000000..95d72a7 --- /dev/null +++ b/core/server/cache/index.go @@ -0,0 +1,72 @@ +package cache + +import ( + "context" + "errors" + "github.com/redis/go-redis/v9" + "strings" + "sync" + "time" + + "pet-house.com/core/server/viper_server" +) + +var ErrRedisInit = errors.New("缓存初始化失败") +var ( + once sync.Once + cacheClient redis.UniversalClient +) + +// init initialize +func init() { + viper_server.Init(getViperConfig()) +} + +// Instance get instance +func Instance() redis.UniversalClient { + once.Do(func() { + universalOptions := &redis.UniversalOptions{ + Addrs: strings.Split(CONFIG.Addr, ","), + Password: CONFIG.Password, + PoolSize: CONFIG.PoolSize, + DialTimeout: 300 * time.Second, + DB: CONFIG.DB, + } + cacheClient = redis.NewUniversalClient(universalOptions) + }) + + return cacheClient +} + +// SetCache +func SetCache(key string, value interface{}, expiration time.Duration) error { + err := Instance().Set(context.Background(), key, value, expiration).Err() + if err != nil { + return err + } + return nil +} + +// DeleteCache +func DeleteCache(key string) (int64, error) { + return Instance().Del(context.Background(), key).Result() +} + +// GetCacheString +func GetCacheString(key string) (string, error) { + value, err := GetCacheBytes(key) + if err != nil { + return "", err + } + return string(value), nil +} + +// GetCacheBytes +func GetCacheBytes(key string) ([]byte, error) { + return Instance().Get(context.Background(), key).Bytes() +} + +// GetCacheUint +func GetCacheUint(key string) (uint64, error) { + return Instance().Get(context.Background(), key).Uint64() +} diff --git a/core/server/cache/index_test.go b/core/server/cache/index_test.go new file mode 100644 index 0000000..6c3e5ad --- /dev/null +++ b/core/server/cache/index_test.go @@ -0,0 +1,87 @@ +package cache + +import ( + "reflect" + "testing" + "time" + + "pet-house.com/core/g" + "pet-house.com/core/server/zap_server" +) + +func TestSetCacheString(t *testing.T) { + defer Remove() + defer zap_server.Remove() + CONFIG.Password = g.TestRedisPwd + t.Run("test set cache string", func(t *testing.T) { + key := "test_set_cache" + want := "test_set_cache_value" + err := SetCache(key, want, time.Duration(time.Second*3)) + if err != nil { + t.Errorf("set cache get error %v\n", err) + } + get, err := GetCacheString(key) + if err != nil { + t.Errorf("set cache get error %v\n", err) + } + if get != want { + t.Errorf("set cache want [%s] but get [%s]\n", want, get) + } + time.Sleep(time.Second * 5) + _, err = GetCacheString(key) + if err == nil { + t.Error("set cache want error but get nil\n") + } + }) +} + +func TestSetCacheUint(t *testing.T) { + defer Remove() + defer zap_server.Remove() + CONFIG.Password = g.TestRedisPwd + t.Run("test set cache uint", func(t *testing.T) { + key := "test_set_cache" + var want uint64 = 123 + err := SetCache(key, want, time.Duration(time.Second*3)) + if err != nil { + t.Errorf("set cache get error %v\n", err) + } + get, err := GetCacheUint(key) + if err != nil { + t.Errorf("set cache get error %v\n", err) + } + if get != want { + t.Errorf("set cache want [%d] but get [%d]\n", want, get) + } + time.Sleep(time.Second * 5) + data, err := GetCacheUint(key) + if err == nil && data > 0 { + t.Error("set cache want error but get nil\n") + } + }) +} +func TestSetCacheBytes(t *testing.T) { + defer Remove() + defer zap_server.Remove() + CONFIG.Password = g.TestRedisPwd + t.Run("test set cache bytes", func(t *testing.T) { + key := "test_set_cache" + want := []byte("test_set_cache_value") + err := SetCache(key, want, time.Duration(time.Second*3)) + if err != nil { + t.Errorf("set cache get error %v\n", err) + } + get, err := GetCacheBytes(key) + if err != nil { + t.Errorf("set cache get error %v\n", err) + } + if !reflect.DeepEqual(get, want) { + t.Errorf("set cache want [%s] but get [%s]\n", want, get) + } + time.Sleep(time.Second * 10) + _, err = GetCacheBytes(key) + if err == nil { + t.Error("set cache want error but get nil\n") + } + }) +} diff --git a/core/server/casbin/config.go b/core/server/casbin/config.go new file mode 100644 index 0000000..1db10cb --- /dev/null +++ b/core/server/casbin/config.go @@ -0,0 +1,49 @@ +package casbin + +import ( + "fmt" + "path/filepath" + + "pet-house.com/core/g" + "pet-house.com/core/helper/dir" +) + +// Remove del config file +func Remove() error { + casbinPath := getCasbinPath() + if dir.IsExist(casbinPath) && dir.IsFile(casbinPath) { + return dir.Remove(casbinPath) + } + return nil +} + +func getCasbinPath() string { + return filepath.Join(dir.GetCurrentAbPath(), g.CasbinFileName) +} + +// init initialize config file +// - initialize casbin's config file as rbac_model.conf name +func init() { + casbinPath := getCasbinPath() + fmt.Printf("casbin rbac_model.conf's path: %s\n\n", casbinPath) + if !dir.IsExist(casbinPath) { // casbin rbac_model.conf file + var rbacModelConf = []byte(`[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 = g(r.sub, p.sub) && keyMatch2(r.obj, p.obj) && (r.act == p.act || p.act == "*")`) + _, err := dir.WriteBytes(casbinPath, rbacModelConf) + if err != nil { + panic(fmt.Errorf("initialize casbin rbac_model.conf file return error: %w ", err)) + } + } +} diff --git a/core/server/casbin/config_test.go b/core/server/casbin/config_test.go new file mode 100644 index 0000000..d65044b --- /dev/null +++ b/core/server/casbin/config_test.go @@ -0,0 +1,29 @@ +package casbin + +import ( + "testing" + + "pet-house.com/core/helper/dir" +) + +func TestRemove(t *testing.T) { + t.Run("Test casbin instance", func(t *testing.T) { + casbin := Instance() + if casbin == nil { + t.Error("casbin instance is nil") + } + }) + casbinPath := getCasbinPath() + if !dir.IsExist(casbinPath) || !dir.IsFile(casbinPath) { + t.Error("casbin file is not exist") + } + t.Run("Test casbin config remove", func(t *testing.T) { + err := Remove() + if err != nil { + t.Error(err) + } + }) + if dir.IsExist(casbinPath) && dir.IsFile(casbinPath) { + t.Error("casbin file is delete fail") + } +} diff --git a/core/server/casbin/index.go b/core/server/casbin/index.go new file mode 100644 index 0000000..69fb267 --- /dev/null +++ b/core/server/casbin/index.go @@ -0,0 +1,75 @@ +package casbin + +import ( + "path/filepath" + "strconv" + "sync" + + "github.com/casbin/casbin/v2" + gormadapter "github.com/casbin/gorm-adapter/v3" + "pet-house.com/core/g" + "pet-house.com/core/helper/dir" + "pet-house.com/core/server/database" + "pet-house.com/core/server/zap_server" +) + +var ( + once sync.Once + enforcer *casbin.Enforcer +) + +// Instance casbin instance +func Instance() *casbin.Enforcer { + once.Do(func() { + enforcer = getEnforcer() + }) + return enforcer +} + +// getEnforcer get casbin.Enforcer +func getEnforcer() *casbin.Enforcer { + if database.Instance() == nil { + zap_server.ZAPLOG.Error(database.ErrDatabaseInit.Error()) + return nil + } + c, err := gormadapter.NewAdapterByDBUseTableName(database.Instance(), "", "casbin_rule") // Your driver and data source. + if err != nil { + return nil + } + + enforcer, err := casbin.NewEnforcer(filepath.Join(dir.GetCurrentAbPath(), g.CasbinFileName), c) + if err != nil { + return nil + } + + if enforcer == nil { + zap_server.ZAPLOG.Error("Casbin init") + return nil + } + + err = enforcer.LoadPolicy() + if err != nil { + return nil + } + + return enforcer +} + +// GetRolesForUser get user's roles +func GetRolesForUser(uid uint) []string { + uids, err := Instance().GetRolesForUser(strconv.FormatUint(uint64(uid), 10)) + if err != nil { + return []string{} + } + + return uids +} + +// ClearCasbin clean rules +func ClearCasbin(v int, p ...string) error { + _, err := Instance().RemoveFilteredPolicy(v, p...) + if err != nil { + return err + } + return nil +} diff --git a/core/server/casbin/index_test.go b/core/server/casbin/index_test.go new file mode 100644 index 0000000..32ef77f --- /dev/null +++ b/core/server/casbin/index_test.go @@ -0,0 +1,37 @@ +package casbin + +import ( + "strconv" + "testing" +) + +func TestInstance(t *testing.T) { + t.Run("test casbin instance", func(t *testing.T) { + casbin := Instance() + if casbin == nil { + t.Error("casbin instance is nil") + } + }) +} + +func TestGetRolesForUser(t *testing.T) { + userId := "888" + roleId := "2" + _, err := Instance().AddRoleForUser(userId, roleId) + if err != nil { + t.Errorf("add role for user get %v", err.Error()) + } + userUid, err := strconv.ParseUint(userId, 10, 64) + if err != nil { + t.Errorf("parse uint err %v", err.Error()) + } + t.Run("test casbin get enforcer", func(t *testing.T) { + uids := GetRolesForUser(uint(userUid)) + if len(uids) != 1 { + t.Errorf("get role for user want %+v but get %+v", userId, uids) + } + if uids[0] != roleId { + t.Errorf("get role for user want %s but get %s", roleId, uids[0]) + } + }) +} diff --git a/core/server/casbin/main_test.go b/core/server/casbin/main_test.go new file mode 100644 index 0000000..c6aa7ee --- /dev/null +++ b/core/server/casbin/main_test.go @@ -0,0 +1,41 @@ +package casbin + +import ( + _ "embed" + "os" + "testing" + + "github.com/bwmarrin/snowflake" + "pet-house.com/core/g" + "pet-house.com/core/helper/str" + "pet-house.com/core/server/database" + "pet-house.com/core/server/zap_server" +) + +func TestMain(m *testing.M) { + + node, _ := snowflake.NewNode(1) + uuid := str.Join("casbin", "_", node.Generate().String()) + + database.CONFIG.DbName = uuid + database.CONFIG.Path = g.TestMysqlAddr + database.CONFIG.Password = g.TestMysqlPwd + + Instance() + + code := m.Run() + + err := database.DorpDB(database.CONFIG.BaseDsn(), "mysql", uuid) + if err != nil { + zap_server.ZAPLOG.Error(err.Error()) + } + + db, _ := database.Instance().DB() + if db != nil { + db.Close() + } + Remove() + zap_server.Remove() + database.Remove() + os.Exit(code) +} diff --git a/core/server/cron_server/index.go b/core/server/cron_server/index.go new file mode 100644 index 0000000..c78c925 --- /dev/null +++ b/core/server/cron_server/index.go @@ -0,0 +1,34 @@ +package cron_server + +import ( + "fmt" + "sync" + "time" +) + +var ( + once sync.Once + cc *cron.Cron +) + +// CronInstance cron single instance +func CronInstance() *cron.Cron { + once.Do(func() { + cc = cron.New(cron.WithSeconds()) + }) + return cc +} + +// DoOnce run job once time,this job will run after 2 second +func DoOnce(job cron.Job, t ...time.Duration) error { + once := time.Now().Add(2 * time.Second) + if len(t) == 1 { + once = time.Now().Add(t[0] * time.Second) + } + onceSpec := fmt.Sprintf("%d %d %d %d %d %d", once.Second(), once.Minute(), once.Hour(), once.Day(), once.Month(), once.Weekday()) + _, err := CronInstance().AddJob(onceSpec, job) + if err != nil { + return err + } + return nil +} diff --git a/core/server/cron_server/index_test.go b/core/server/cron_server/index_test.go new file mode 100644 index 0000000..c551c31 --- /dev/null +++ b/core/server/cron_server/index_test.go @@ -0,0 +1,16 @@ +package cron_server + +import "testing" + +func TestCronInstance(t *testing.T) { + t.Run("Test cron instance init", func(t *testing.T) { + instance := CronInstance() + if instance == nil { + t.Error("Cron Instance init fail.") + } + instance1 := CronInstance() + if instance != instance1 { + t.Error("Cron Instance is change.") + } + }) +} diff --git a/core/server/database/cmd.go b/core/server/database/cmd.go new file mode 100644 index 0000000..88ef05b --- /dev/null +++ b/core/server/database/cmd.go @@ -0,0 +1,102 @@ +package database + +import ( + "fmt" + "strings" +) + +// Init initialize mysql config file +func Init() error { + var cover string + if IsExist() { + fmt.Println("Your database config is initialized , reinitialized database will cover your database config.") + fmt.Println("Did you want to do it ? [Y/N]") + fmt.Scanln(&cover) + switch strings.ToUpper(cover) { + case "Y": + case "N": + return nil + default: + } + } + err := Remove() + if err != nil { + return err + } + + err = initConfig() + if err != nil { + return err + } + fmt.Println("mysql initialized finished!") + return nil +} + +func initConfig() error { + var dbPath, dbName, dbUsername, dbPwd, dbLogZap, dbLogMod string + var maxIdleConns, maxOpenConns int + fmt.Println("Please input your database path: ") + fmt.Printf("Database path default is '%s'\n", CONFIG.Path) + fmt.Scanln(&dbPath) + if dbPath != "" { + CONFIG.Path = dbPath + } + + fmt.Println("Please input your database db-name: ") + fmt.Printf("Database db-name default is '%s'\n", CONFIG.DbName) + fmt.Scanln(&dbName) + if dbName != "" { + CONFIG.DbName = dbName + } + + fmt.Println("Please input your database username: ") + fmt.Printf("Database username default is '%s'\n", CONFIG.Username) + fmt.Scanln(&dbUsername) + if dbUsername != "" { + CONFIG.Username = dbUsername + } + + fmt.Println("Please input your database password: ") + fmt.Printf("Database password default is '%s'\n", CONFIG.Password) + fmt.Scanln(&dbPwd) + if dbPwd != "" { + CONFIG.Password = dbPwd + } + + fmt.Println("Please input your database log zap: ") + fmt.Printf("Database log zap default is '%s'\n", CONFIG.LogZap) + fmt.Scanln(&dbLogZap) + if dbLogZap != "" { + CONFIG.LogZap = dbLogZap + } + + fmt.Println("Please input your database log mode: [Y/N]") + fmt.Println("Database log mode default is N") + fmt.Scanln(&dbLogMod) + switch strings.ToUpper(dbLogMod) { + case "Y": + CONFIG.LogMode = true + case "N": + CONFIG.LogMode = false + default: + CONFIG.LogMode = false + } + + fmt.Println("Please input your database max idle conns: ") + fmt.Scanln(&maxIdleConns) + if maxIdleConns > 0 { + CONFIG.MaxIdleConns = maxIdleConns + } + + fmt.Println("Please input your database max open conns: ") + fmt.Scanln(&maxOpenConns) + if maxOpenConns > 0 { + CONFIG.MaxOpenConns = maxOpenConns + } + + if Instance() == nil { + return ErrDatabaseInit + } + + return nil +} diff --git a/core/server/database/config.go b/core/server/database/config.go new file mode 100644 index 0000000..49ebfe2 --- /dev/null +++ b/core/server/database/config.go @@ -0,0 +1,102 @@ +package database + +import ( + "encoding/json" + "fmt" + + "github.com/fsnotify/fsnotify" + "github.com/spf13/viper" + "pet-house.com/core/g" + "pet-house.com/core/server/viper_server" +) + +var CONFIG = Mysql{ + Path: "127.0.0.1:3306", + Config: "charset=utf8mb4&parseTime=True&loc=Local", + DbName: "iris-admin", + Username: "root", + Password: "", + MaxIdleConns: 0, + MaxOpenConns: 0, + LogMode: false, + LogZap: "error", +} + +type Mysql struct { + Path string `mapstructure:"path" json:"path" yaml:"path"` + Config string `mapstructure:"config" json:"config" yaml:"config"` + DbName string `mapstructure:"db-name" json:"db-name" yaml:"db-name"` + Username string `mapstructure:"username" json:"username" yaml:"username"` + Password string `mapstructure:"password" json:"password" yaml:"password"` + MaxIdleConns int `mapstructure:"max-idle-conns" json:"max-idle-conns" yaml:"max-idle-conns"` + MaxOpenConns int `mapstructure:"max-open-conns" json:"max-open-conns" yaml:"max-open-conns"` + LogMode bool `mapstructure:"log-mode" json:"log-mode" yaml:"log-mode"` + LogZap string `mapstructure:"log-zap" json:"log-zap" yaml:"log-zap"` //silent,error,warn,info,zap +} + +// Dsn return mysql dsn +func (m *Mysql) Dsn() string { + return fmt.Sprintf("%s%s?%s", m.BaseDsn(), m.DbName, m.Config) +} + +// Dsn return +func (m *Mysql) BaseDsn() string { + return fmt.Sprintf("%s:%s@tcp(%s)/", m.Username, m.Password, m.Path) +} + +// IsExist config file is exist +func IsExist() bool { + return getViperConfig().IsFileExist() +} + +// Remove remove config file +func Remove() error { + return getViperConfig().Remove() +} + +// Recover +func Recover() error { + b, err := json.Marshal(CONFIG) + if err != nil { + return err + } + return getViperConfig().Recover(b) +} + +// getViperConfig get viper config +func getViperConfig() viper_server.ViperConfig { + configName := "mysql" + mxIdleConns := fmt.Sprintf("%d", CONFIG.MaxIdleConns) + mxOpenConns := fmt.Sprintf("%d", CONFIG.MaxOpenConns) + logMode := fmt.Sprintf("%t", CONFIG.LogMode) + return viper_server.ViperConfig{ + Debug: true, + Directory: g.ConfigDir, + Name: configName, + Type: g.ConfigType, + Watch: func(vi *viper.Viper) error { + if err := vi.Unmarshal(&CONFIG); err != nil { + return fmt.Errorf("get Unarshal error: %v", err) + } + // watch config file change + vi.OnConfigChange(func(e fsnotify.Event) { + fmt.Println("Config file changed:", e.Name) + }) + vi.WatchConfig() + return nil + }, + // + Default: []byte(` +{ + "path": "` + CONFIG.Path + `", + "config": "` + CONFIG.Config + `", + "db-name": "` + CONFIG.DbName + `", + "username": "` + CONFIG.Username + `", + "password": "` + CONFIG.Password + `", + "max-idle-conns": ` + mxIdleConns + `, + "max-open-conns": ` + mxOpenConns + `, + "log-mode": ` + logMode + `, + "log-zap": "` + CONFIG.LogZap + `" +}`), + } +} diff --git a/core/server/database/config/mysql.json b/core/server/database/config/mysql.json new file mode 100644 index 0000000..a2a7bff --- /dev/null +++ b/core/server/database/config/mysql.json @@ -0,0 +1,11 @@ +{ + "config": "charset=utf8mb4\u0026parseTime=True\u0026loc=Local", + "db-name": "iris-admin", + "log-mode": false, + "log-zap": "error", + "max-idle-conns": 0, + "max-open-conns": 0, + "password": "", + "path": "127.0.0.1:3306", + "username": "root" +} \ No newline at end of file diff --git a/core/server/database/config/zap.json b/core/server/database/config/zap.json new file mode 100644 index 0000000..54061fb --- /dev/null +++ b/core/server/database/config/zap.json @@ -0,0 +1,11 @@ +{ + "director": "logs", + "encode-level": "LowercaseColorLevelEncoder", + "format": "console", + "level": "debug", + "link-name": "latest_log", + "log-in-console": false, + "prefix": "[IRIS-ADMIN]", + "show-line": true, + "stacktrace-key": "stacktrace" +} \ No newline at end of file diff --git a/core/server/database/index.go b/core/server/database/index.go new file mode 100644 index 0000000..c849268 --- /dev/null +++ b/core/server/database/index.go @@ -0,0 +1,129 @@ +package database + +import ( + "database/sql" + "errors" + "fmt" + "sync" + + "pet-house.com/core/server/viper_server" + "pet-house.com/core/server/zap_server" + + "gorm.io/driver/mysql" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +var ErrDatabaseInit = errors.New("database initialize fail") + +var ( + once sync.Once + db *gorm.DB +) + +// init +func init() { + viper_server.Init(getViperConfig()) +} + +// Instance +func Instance() *gorm.DB { + once.Do(func() { + db = gormMysql() + }) + return db +} + +// gormMysql get *gorm.DB +func gormMysql() *gorm.DB { + if CONFIG.DbName == "" { + fmt.Println("config dbname is empty") + return nil + } + /*err := createTable(CONFIG.BaseDsn(), "mysql", CONFIG.DbName) + if err != nil { + fmt.Printf("create database %s is failed %v \n", CONFIG.DbName, err) + return nil + }*/ + mysqlConfig := mysql.Config{ + DSN: CONFIG.Dsn(), + DefaultStringSize: 191, + DisableDatetimePrecision: true, + DontSupportRenameIndex: true, + DontSupportRenameColumn: true, + SkipInitializeWithVersion: false, + } + if db, err := gorm.Open(mysql.New(mysqlConfig), gormConfig(CONFIG.LogMode)); err != nil { + fmt.Printf("open mysql is failed %v \n", err) + return nil + } else { + sqlDB, _ := db.DB() + sqlDB.SetMaxIdleConns(CONFIG.MaxIdleConns) + sqlDB.SetMaxOpenConns(CONFIG.MaxOpenConns) + return db + } +} + +// gormConfig get gorm config +func gormConfig(mod bool) *gorm.Config { + var config = &gorm.Config{DisableForeignKeyConstraintWhenMigrating: true} + switch CONFIG.LogZap { + case "silent", "Silent": + config.Logger = Default.LogMode(logger.Silent) + case "error", "Error": + config.Logger = Default.LogMode(logger.Error) + case "warn", "Warn": + config.Logger = Default.LogMode(logger.Warn) + case "info", "Info": + config.Logger = Default.LogMode(logger.Info) + case "zap", "Zap": + config.Logger = Default.LogMode(logger.Info) + default: + if mod { + config.Logger = Default.LogMode(logger.Info) + break + } + config.Logger = Default.LogMode(logger.Silent) + } + return config +} + +// createTable create database(mysql) +func createTable(dsn, driver, dbName string) error { + createSql := fmt.Sprintf("CREATE DATABASE IF NOT EXISTS `%s` DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci;", dbName) + db, err := sql.Open(driver, dsn) + if err != nil { + return err + } + defer func(db *sql.DB) { + _ = db.Close() + }(db) + if err = db.Ping(); err != nil { + return err + } + _, err = db.Exec(createSql) + return err +} + +func DorpDB(dsn, driver, dbName string) error { + execSql := fmt.Sprintf("DROP database if exists `%s`;", dbName) + db, err := sql.Open(driver, dsn) + if err != nil { + return err + } + if db == nil { + return errors.New("db is nil") + } + defer func(db *sql.DB) { + _ = db.Close() + }(db) + if err = db.Ping(); err != nil { + return err + } + _, err = db.Exec(execSql) + if err != nil { + return err + } + zap_server.ZAPLOG.Debug(execSql) + return nil +} diff --git a/core/server/database/index_test.go b/core/server/database/index_test.go new file mode 100644 index 0000000..f78a890 --- /dev/null +++ b/core/server/database/index_test.go @@ -0,0 +1,35 @@ +package database + +import ( + "reflect" + "testing" + + "gorm.io/gorm/logger" +) + +func TestInstanceMysql(t *testing.T) { + t.Run("test instance mysql", func(t *testing.T) { + mysql := Instance() + if mysql == nil { + t.Error("mysql instance is nil") + } + }) +} + +func TestGormMysql(t *testing.T) { + t.Run("test gorm mysql", func(t *testing.T) { + gormDb := gormMysql() + if gormDb == nil { + t.Error("gorm db is nil") + } + }) +} + +func TestGormConfig(t *testing.T) { + t.Run("test gorm config", func(t *testing.T) { + gormConfig := gormConfig(false) + if !reflect.DeepEqual(gormConfig.Logger, Default.LogMode(logger.Error)) { + t.Errorf("gorm config logger want %+v but get %+v", Default.LogMode(logger.Error), gormConfig.Logger) + } + }) +} diff --git a/core/server/database/internal.go b/core/server/database/internal.go new file mode 100644 index 0000000..770e931 --- /dev/null +++ b/core/server/database/internal.go @@ -0,0 +1,187 @@ +package database + +import ( + "context" + "fmt" + "io/ioutil" + "log" + "os" + "time" + + "go.uber.org/zap" + "gorm.io/gorm/logger" + "gorm.io/gorm/utils" + "pet-house.com/core/server/zap_server" +) + +// writer +type writer interface { + Printf(string, ...interface{}) +} + +// config +type config struct { + SlowThreshold time.Duration + Colorful bool + LogLevel logger.LogLevel +} + +var ( + Discard = New(log.New(ioutil.Discard, "", log.LstdFlags), config{}) + Default = New(log.New(os.Stdout, "\r\n", log.LstdFlags), config{ + SlowThreshold: 200 * time.Millisecond, + LogLevel: logger.Warn, + Colorful: true, + }) + Recorder = traceRecorder{Interface: Default, BeginAt: time.Now()} +) + +// New +func New(writer writer, config config) logger.Interface { + var ( + infoStr = "%s\n[info] " + warnStr = "%s\n[warn] " + errStr = "%s\n[error] " + traceStr = "%s\n[%.3fms] [rows:%v] %s" + traceWarnStr = "%s %s\n[%.3fms] [rows:%v] %s" + traceErrStr = "%s %s\n[%.3fms] [rows:%v] %s" + ) + + if config.Colorful { + infoStr = logger.Green + "%s\n" + logger.Reset + logger.Green + "[info] " + logger.Reset + warnStr = logger.BlueBold + "%s\n" + logger.Reset + logger.Magenta + "[warn] " + logger.Reset + errStr = logger.Magenta + "%s\n" + logger.Reset + logger.Red + "[error] " + logger.Reset + traceStr = logger.Green + "%s\n" + logger.Reset + logger.Yellow + "[%.3fms] " + logger.BlueBold + "[rows:%v]" + logger.Reset + " %s" + traceWarnStr = logger.Green + "%s " + logger.Yellow + "%s\n" + logger.Reset + logger.RedBold + "[%.3fms] " + logger.Yellow + "[rows:%v]" + logger.Magenta + " %s" + logger.Reset + traceErrStr = logger.RedBold + "%s " + logger.MagentaBold + "%s\n" + logger.Reset + logger.Yellow + "[%.3fms] " + logger.BlueBold + "[rows:%v]" + logger.Reset + " %s" + } + + return &customLogger{ + writer: writer, + config: config, + infoStr: infoStr, + warnStr: warnStr, + errStr: errStr, + traceStr: traceStr, + traceWarnStr: traceWarnStr, + traceErrStr: traceErrStr, + } +} + +// customLogger +type customLogger struct { + writer + config + infoStr, warnStr, errStr string + traceStr, traceErrStr, traceWarnStr string +} + +// LogMode +func (c *customLogger) LogMode(level logger.LogLevel) logger.Interface { + newLogger := *c + newLogger.LogLevel = level + return &newLogger +} + +// Info print info +func (c *customLogger) Info(ctx context.Context, message string, data ...interface{}) { + if c.LogLevel >= logger.Info { + c.Printf(c.infoStr+message, append([]interface{}{utils.FileWithLineNum()}, data...)...) + } +} + +// Warn print warn messages +func (c *customLogger) Warn(ctx context.Context, message string, data ...interface{}) { + if c.LogLevel >= logger.Warn { + c.Printf(c.warnStr+message, append([]interface{}{utils.FileWithLineNum()}, data...)...) + } +} + +// Error print error messages +func (c *customLogger) Error(ctx context.Context, message string, data ...interface{}) { + if c.LogLevel >= logger.Error { + c.Printf(c.errStr+message, append([]interface{}{utils.FileWithLineNum()}, data...)...) + } +} + +// Trace print sql message +func (c *customLogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) { + if c.LogLevel > 0 { + elapsed := time.Since(begin) + switch { + case err != nil && c.LogLevel >= logger.Error: + sql, rows := fc() + if rows == -1 { + c.Printf(c.traceErrStr, utils.FileWithLineNum(), err, float64(elapsed.Nanoseconds())/1e6, "-", sql) + } else { + c.Printf(c.traceErrStr, utils.FileWithLineNum(), err, float64(elapsed.Nanoseconds())/1e6, rows, sql) + } + case elapsed > c.SlowThreshold && c.SlowThreshold != 0 && c.LogLevel >= logger.Warn: + sql, rows := fc() + slowLog := fmt.Sprintf("SLOW SQL >= %v", c.SlowThreshold) + if rows == -1 { + c.Printf(c.traceWarnStr, utils.FileWithLineNum(), slowLog, float64(elapsed.Nanoseconds())/1e6, "-", sql) + } else { + c.Printf(c.traceWarnStr, utils.FileWithLineNum(), slowLog, float64(elapsed.Nanoseconds())/1e6, rows, sql) + } + case c.LogLevel >= logger.Info: + sql, rows := fc() + if rows == -1 { + c.Printf(c.traceStr, utils.FileWithLineNum(), float64(elapsed.Nanoseconds())/1e6, "-", sql) + } else { + c.Printf(c.traceStr, utils.FileWithLineNum(), float64(elapsed.Nanoseconds())/1e6, rows, sql) + } + } + } +} + +// Printf +func (c *customLogger) Printf(message string, data ...interface{}) { + if CONFIG.LogZap != "" { + switch len(data) { + case 0: + zap_server.ZAPLOG.Info(message) + case 1: + zap_server.ZAPLOG.Info("gorm", zap.Any("src", data[0])) + case 2: + zap_server.ZAPLOG.Info("gorm", zap.Any("src", data[0]), zap.Any("duration", data[1])) + case 3: + zap_server.ZAPLOG.Info("gorm", zap.Any("src", data[0]), zap.Any("duration", data[1]), zap.Any("rows", data[2])) + case 4: + zap_server.ZAPLOG.Info("gorm", zap.Any("src", data[0]), zap.Any("duration", data[1]), zap.Any("rows", data[2]), zap.Any("sql", data[3])) + } + return + } + switch len(data) { + case 0: + c.writer.Printf(message, "") + case 1: + c.writer.Printf(message, data[0]) + case 2: + c.writer.Printf(message, data[0], data[1]) + case 3: + c.writer.Printf(message, data[0], data[1], data[2]) + case 4: + c.writer.Printf(message, data[0], data[1], data[2], data[3]) + case 5: + c.writer.Printf(message, data[0], data[1], data[2], data[3], data[4]) + } +} + +type traceRecorder struct { + logger.Interface + BeginAt time.Time + SQL string + RowsAffected int64 + Err error +} + +func (t traceRecorder) New() *traceRecorder { + return &traceRecorder{Interface: t.Interface, BeginAt: time.Now()} +} + +func (t *traceRecorder) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) { + t.BeginAt = begin + t.SQL, t.RowsAffected = fc() + t.Err = err +} diff --git a/core/server/database/main_test.go b/core/server/database/main_test.go new file mode 100644 index 0000000..fff9a03 --- /dev/null +++ b/core/server/database/main_test.go @@ -0,0 +1,38 @@ +package database + +import ( + _ "embed" + "fmt" + "os" + "pet-house.com/core/server/database/scope" + "testing" + "time" +) + +type otheraccounts struct { + Id int64 + Accid string + Apptypeid string + Unionid string + Openid string + Usertype string +} + +func TestMain(m *testing.M) { + + CONFIG.DbName = "pikauser" + CONFIG.Path = "172.31.65.221:3306" + CONFIG.Password = "qC#5Lp%9AF=6BmR5Ny!6aaabbb" + CONFIG.Username = "pikauser_dev" + + code := m.Run() + + for i := 1; i <= 10; i++ { + var account otheraccounts + Instance().Table("otheraccounts").Model(&otheraccounts{}).Scopes(scope.PaginateScope(i, 1, "desc", "createtime")).Where(" apptypeid = ?", "100001").Find(&account) + fmt.Println(account.Openid, account.Id) + time.Sleep(time.Second) + } + + os.Exit(code) +} diff --git a/core/server/database/orm/struct.go b/core/server/database/orm/struct.go new file mode 100644 index 0000000..fc498b3 --- /dev/null +++ b/core/server/database/orm/struct.go @@ -0,0 +1,113 @@ +package orm + +import ( + "errors" + "strings" + + "github.com/gin-gonic/gin" + "gorm.io/gorm" + "pet-house.com/core/server/database/scope" + "pet-house.com/core/server/web/web_iris/validate" + "pet-house.com/core/server/zap_server" +) + +type ErrMsg struct { + Code int64 `json:"code"` + Msg string `json:"message"` +} + +var ( + ErrParamValidate = errors.New("参数验证失败") + ErrPaginateParam = errors.New("分页查询参数缺失") + ErrUnSupportFramework = errors.New("不支持的框架") +) + +// Model +type Model struct { + Id uint `json:"id" uri:"id" form:"id" param:"id"` + UpdatedAt string `json:"updatedAt" uri:"updatedAt" form:"updatedAt" param:"updatedAt"` + CreatedAt string `json:"createdAt" uri:"createdAt" form:"createdAt" param:"createdAt"` + DeletedAt string `json:"deletedAt" uri:"deletedAt" form:"deletedAt" param:"deletedAt"` +} + +// ReqId the struct has used to get id form the context of every query +type ReqId struct { + Id uint `json:"id" uri:"id" form:"id" param:"id"` +} + +// Request get id data form the context of every query +func (req *ReqId) Request(ctx interface{}) error { + if c, ok := ctx.(iris.Context); ok { + return req.irisReadParams(c) + } else if c, ok := ctx.(*gin.Context); ok { + return req.ginShouldBindUri(c) + } else { + return ErrUnSupportFramework + } +} + +func (req *ReqId) irisReadParams(ctx iris.Context) error { + if err := ctx.ReadParams(req); err != nil { + zap_server.ZAPLOG.Error(err.Error()) + return ErrParamValidate + } + return nil +} + +func (req *ReqId) ginShouldBindUri(ctx *gin.Context) error { + if err := ctx.ShouldBindUri(req); err != nil { + zap_server.ZAPLOG.Error(err.Error()) + return ErrParamValidate + } + return nil +} + +// Paginate param for paginate query +type Paginate struct { + Page int `json:"page" form:"page"` + PageSize int `json:"pageSize" form:"pageSize"` + OrderBy string `json:"orderBy" form:"orderBy"` + Sort string `json:"sort" form:"sort"` +} + +// Request +func (req *Paginate) Request(ctx interface{}) error { + if c, ok := ctx.(iris.Context); ok { + return req.irisReadQuerys(c) + } else if c, ok := ctx.(*gin.Context); ok { + return req.ginShouldBind(c) + } else { + return ErrUnSupportFramework + } +} + +func (req *Paginate) irisReadQuerys(ctx iris.Context) error { + if err := ctx.ReadQuery(req); err != nil { + errs := validate.ValidRequest(err) + if len(errs) > 0 { + zap_server.ZAPLOG.Error(strings.Join(errs, ";")) + return ErrParamValidate + } + } + return nil +} + +func (req *Paginate) ginShouldBind(ctx *gin.Context) error { + if err := ctx.ShouldBind(req); err != nil { + zap_server.ZAPLOG.Error(err.Error()) + return ErrParamValidate + } + return nil +} + +// PaginateScope paginate scope +func (req *Paginate) PaginateScope() func(db *gorm.DB) *gorm.DB { + return scope.PaginateScope(req.Page, req.PageSize, req.Sort, req.OrderBy) +} + +// Response +type Response struct { + Status int64 `json:"status"` + Msg string `json:"message"` + Data interface{} `json:"data"` +} diff --git a/core/server/database/scope/find.go b/core/server/database/scope/find.go new file mode 100644 index 0000000..a04e468 --- /dev/null +++ b/core/server/database/scope/find.go @@ -0,0 +1,43 @@ +package scope + +import "gorm.io/gorm" + +// IdScope +// - id uint +func IdScope(id uint) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + return db.Where("id = ?", id) + } +} + +// InIdsScope +// - ids []uint +func InIdsScope(ids []uint) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + return db.Where("id in ?", ids) + } +} + +// InNamesScope +// - names []string +func InNamesScope(names []string) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + return db.Where("name in ?", names) + } +} + +// InUuidsScope +// - uuids []string +func InUuidsScope(uuids []string) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + return db.Where("uuid in ?", uuids) + } +} + +// NeIdScope +// - id uint +func NeIdScope(id uint) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + return db.Where("id != ?", id) + } +} diff --git a/core/server/database/scope/paginate.go b/core/server/database/scope/paginate.go new file mode 100644 index 0000000..7a09ace --- /dev/null +++ b/core/server/database/scope/paginate.go @@ -0,0 +1,55 @@ +package scope + +import ( + "gorm.io/gorm" + "pet-house.com/core/helper/str" +) + +// PaginateScope return paginate scope for gorm +// - page int +// - pageSize int +// - sort string +// - orderBy string +func PaginateScope(page, pageSize int, sort, orderBy string) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + pageSize := getPageSize(pageSize) + offset := getOffset(page, pageSize) + return db.Order(getOrderBy(sort, orderBy)).Offset(offset).Limit(pageSize) + } +} + +// getOffset +func getOffset(page, pageSize int) int { + if page == 0 { + page = 1 + } + offset := (page - 1) * pageSize + if page < 0 { + offset = -1 + } + return offset +} + +// getPageSize +func getPageSize(pageSize int) int { + switch { + case pageSize > 100: + pageSize = 100 + case pageSize < 0: + pageSize = -1 + case pageSize == 0: + pageSize = 10 + } + return pageSize +} + +// getOrderBy +func getOrderBy(sort, orderBy string) string { + if sort == "" { + sort = "desc" + } + if orderBy == "" { + orderBy = "created_at" + } + return str.Join(orderBy, " ", sort) +} diff --git a/core/server/mongodb/config.go b/core/server/mongodb/config.go new file mode 100644 index 0000000..9968026 --- /dev/null +++ b/core/server/mongodb/config.go @@ -0,0 +1,78 @@ +package mongodb + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/spf13/viper" + "pet-house.com/core/g" + "pet-house.com/core/helper/str" + "pet-house.com/core/server/viper_server" +) + +// init initialize +func init() { + viper_server.Init(getViperConfig()) +} + +var CONFIG = MongoDB{ + DB: "mongo_test", + Timeout: 10, + Addr: "localhost:27017", +} + +type MongoDB struct { + Timeout time.Duration `mapstructure:"timeout" json:"timeout" yaml:"timeout"` + DB string `mapstructure:"db" json:"db" yaml:"db"` + Addr string `mapstructure:"addr" json:"addr" yaml:"addr"` +} + +func (md *MongoDB) GetApplyURI() string { + return str.Join("mongodb://", md.Addr) +} + +// IsExist config file is exist +func IsExist() bool { + return getViperConfig().IsFileExist() +} + +// Remove remove config file +func Remove() error { + return getViperConfig().Remove() +} + +// Recover +func Recover() error { + b, err := json.Marshal(CONFIG) + if err != nil { + return err + } + return getViperConfig().Recover(b) +} + +// getViperConfig get viper config +func getViperConfig() viper_server.ViperConfig { + configName := "mongo" + return viper_server.ViperConfig{ + Debug: true, + Directory: g.ConfigDir, + Name: configName, + Type: g.ConfigType, + Watch: func(vi *viper.Viper) error { + if err := vi.Unmarshal(&CONFIG); err != nil { + return fmt.Errorf("get Unarshal error: %v", err) + } + // watch config file change + vi.SetConfigName(configName) + return nil + }, + // + Default: []byte(` +{ + "timeout": "` + CONFIG.Timeout.String() + `", + "db": "` + CONFIG.DB + `", + "addr": "` + CONFIG.Addr + `" +}`), + } +} diff --git a/core/server/mongodb/config_test.go b/core/server/mongodb/config_test.go new file mode 100644 index 0000000..22e319e --- /dev/null +++ b/core/server/mongodb/config_test.go @@ -0,0 +1,27 @@ +package mongodb + +import ( + "testing" +) + +func TestIsExist(t *testing.T) { + t.Run("test mongodb config IsExist function", func(t *testing.T) { + if !IsExist() { + t.Errorf("config's files is not exist.") + } + }) + t.Run("Test GetApplyURI function", func(t *testing.T) { + want := "mongodb://localhost:27017" + if applyUrl := CONFIG.GetApplyURI(); applyUrl != want { + t.Errorf("applyURI want %s but get %s", want, applyUrl) + } + }) + t.Run("Test Remove function", func(t *testing.T) { + if err := Remove(); err != nil { + t.Error(err) + } + if IsExist() { + t.Errorf("config's files remove is fail.") + } + }) +} diff --git a/core/server/mongodb/index.go b/core/server/mongodb/index.go new file mode 100644 index 0000000..f6ecd42 --- /dev/null +++ b/core/server/mongodb/index.go @@ -0,0 +1,111 @@ +package mongodb + +import ( + "context" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/mongo/readpref" +) + +type Client struct { + mc *mongo.Client +} + +// GetClient +func GetClient() (*Client, error) { + mc, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(CONFIG.GetApplyURI())) + if err != nil { + return nil, err + } + client := &Client{mc: mc} + return client, nil +} + +// Ping +func (c *Client) Ping() error { + return c.mc.Ping(context.TODO(), readpref.Primary()) +} + +// getCollection +func (c *Client) getCollection(name string) *mongo.Collection { + return c.mc.Database(CONFIG.DB).Collection(name) +} + +// Aggregate +func (c *Client) Aggregate(name string, groupStage mongo.Pipeline) ([]bson.M, error) { + // pass the stage into a pipeline + // pass the pipeline as the second paramter in the Aggregate() method + cursor, err := c.getCollection(name).Aggregate(context.TODO(), groupStage) + if err != nil { + return nil, err + } + // display the results + results := []bson.M{} + if err = cursor.All(context.TODO(), &results); err != nil { + return nil, err + } + defer cursor.Close(context.TODO()) + return results, nil +} + +// Find +func (c *Client) Find(name string, filters ...interface{}) ([]bson.M, error) { + var cursor *mongo.Cursor + var err error + if len(filters) == 0 { + cursor, err = c.getCollection(name).Find(context.TODO(), bson.D{}) + if err != nil { + return nil, err + } + } else { + cursor, err = c.getCollection(name).Find(context.TODO(), filters[0]) + if err != nil { + return nil, err + } + } + var results []bson.M + for cursor.Next(context.TODO()) { + b := bson.M{} + err := cursor.Decode(b) + if err != nil { + return nil, err + } + results = append(results, b) + } + return results, nil +} + +// FindOne +func (c *Client) FindOne(name string, filter interface{}) *mongo.SingleResult { + return c.getCollection(name).FindOne(context.TODO(), filter) +} + +// Disconnect +func (c *Client) Disconnect() error { + return c.mc.Disconnect(context.TODO()) +} + +// InsertOne +func (c *Client) InsertOne(name string, filter interface{}) (interface{}, error) { + cur, err := c.getCollection(name).InsertOne(context.TODO(), filter) + if err != nil { + return nil, err + } + return cur.InsertedID, nil +} + +// DeleteOne +func (c *Client) DeleteOne(name string, filter interface{}) error { + _, err := c.getCollection(name).DeleteOne(context.TODO(), filter) + if err != nil { + return err + } + return nil +} + +// UpdateByID +func (c *Client) UpdateOne(name string, filter, update interface{}) (*mongo.UpdateResult, error) { + return c.getCollection(name).UpdateOne(context.TODO(), filter, update) +} diff --git a/core/server/mongodb/index_test.go b/core/server/mongodb/index_test.go new file mode 100644 index 0000000..63ce735 --- /dev/null +++ b/core/server/mongodb/index_test.go @@ -0,0 +1,280 @@ +package mongodb + +import ( + "testing" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "pet-house.com/core/g" + "pet-house.com/core/server/cache" + _ "pet-house.com/core/server/cache" +) + +func TestGetClient(t *testing.T) { + CONFIG.Addr = g.TestMongoAddr + defer Remove() + defer cache.Remove() + t.Run("test mongodb getClient", func(t *testing.T) { + client, err := GetClient() + if err != nil { + t.Error(err.Error()) + return + } + if client == nil { + t.Error("mongodb clinet is nil") + } + defer func() { + if err = client.Disconnect(); err != nil { + t.Error(err) + } + }() + }) + +} +func TestPing(t *testing.T) { + CONFIG.Addr = g.TestMongoAddr + defer Remove() + defer cache.Remove() + t.Run("test mongodb ping", func(t *testing.T) { + client, err := GetClient() + if err != nil { + t.Error(err.Error()) + return + } + if client == nil { + t.Error("mongodb clinet is nil") + } + defer func() { + if err = client.Disconnect(); err != nil { + t.Error(err) + } + }() + err = client.Ping() + if err != nil { + t.Error(err.Error()) + return + } + }) +} +func TestInsertOne(t *testing.T) { + CONFIG.Addr = g.TestMongoAddr + defer Remove() + defer cache.Remove() + t.Run("test mongodb InsertOne", func(t *testing.T) { + client, err := GetClient() + if err != nil { + t.Error(err.Error()) + return + } + if client == nil { + t.Error("mongodb clinet is nil") + } + defer func() { + if err = client.Disconnect(); err != nil { + t.Error(err) + } + }() + res, err := client.InsertOne("testing", bson.D{ + {Key: "name", Value: "pi"}, {Key: "value", Value: 3.14159}, + }) + if err != nil { + t.Error(err.Error()) + return + } + if res == "" { + t.Error("inserted id is empty") + } + }) +} +func TestGetCollection(t *testing.T) { + CONFIG.Addr = g.TestMongoAddr + defer Remove() + defer cache.Remove() + t.Run("test mongodb GetCollection", func(t *testing.T) { + client, err := GetClient() + if err != nil { + t.Error(err.Error()) + return + } + if client == nil { + t.Error("mongodb clinet is nil") + } + defer func() { + if err = client.Disconnect(); err != nil { + t.Error(err) + } + }() + res := client.getCollection("testing") + if res == nil { + t.Error("Collection return empty") + } + }) +} + +func TestGetAggregate(t *testing.T) { + CONFIG.Addr = g.TestMongoAddr + defer Remove() + defer cache.Remove() + t.Run("test mongodb Aggregate", func(t *testing.T) { + client, err := GetClient() + if err != nil { + t.Error(err.Error()) + return + } + if client == nil { + t.Error("mongodb clinet is nil") + return + } + defer func() { + if err = client.Disconnect(); err != nil { + t.Error(err) + } + }() + pipeline := mongo.Pipeline{ + { + {"$match", bson.D{ + {"items.fruit", "banana"}, + }}, + }, + { + {"$sort", bson.D{ + {"date", 1}, + }}, + }, + } + res, err := client.Aggregate("testing", pipeline) + if err != nil { + t.Error(err.Error()) + return + } + if res == nil { + t.Error("Collection return empty") + } + }) +} + +func TestFind(t *testing.T) { + CONFIG.Addr = g.TestMongoAddr + defer Remove() + defer cache.Remove() + t.Run("test mongodb Find", func(t *testing.T) { + client, err := GetClient() + if err != nil { + t.Error(err.Error()) + return + } + if client == nil { + t.Error("mongodb clinet is nil") + return + } + defer func() { + if err = client.Disconnect(); err != nil { + t.Error(err) + } + }() + res, err := client.Find("testing", bson.D{{"end", nil}}) + if err != nil { + t.Error(err.Error()) + return + } + if res == nil { + t.Error("Collection return empty") + } + }) +} + +func TestFindOne(t *testing.T) { + CONFIG.Addr = g.TestMongoAddr + defer Remove() + defer cache.Remove() + t.Run("test mongodb FindOne", func(t *testing.T) { + client, err := GetClient() + if err != nil { + t.Error(err.Error()) + return + } + if client == nil { + t.Error("mongodb clinet is nil") + return + } + defer func() { + if err = client.Disconnect(); err != nil { + t.Error(err) + } + }() + res := client.FindOne("testing", bson.D{{"end", nil}}) + if res == nil { + t.Error("Collection return empty") + } + }) +} + +func TestDeleteOne(t *testing.T) { + CONFIG.Addr = g.TestMongoAddr + defer Remove() + defer cache.Remove() + t.Run("test mongodb DeleteOne", func(t *testing.T) { + client, err := GetClient() + if err != nil { + t.Error(err.Error()) + return + } + if client == nil { + t.Error("mongodb clinet is nil") + return + } + defer func() { + if err = client.Disconnect(); err != nil { + t.Error(err) + } + }() + err = client.DeleteOne("testing", bson.D{{"end", nil}}) + if err != nil { + t.Error(err.Error()) + return + } + }) +} + +func TestUpdateOne(t *testing.T) { + CONFIG.Addr = g.TestMongoAddr + defer Remove() + defer cache.Remove() + t.Run("test mongodb UpdateOne", func(t *testing.T) { + client, err := GetClient() + if err != nil { + t.Error(err.Error()) + return + } + if client == nil { + t.Error("mongodb clinet is nil") + return + } + defer func() { + if err = client.Disconnect(); err != nil { + t.Error(err) + } + }() + id, err := client.InsertOne("testing", bson.D{ + {Key: "name", Value: "pi"}, {Key: "value", Value: 3.14159}, + }) + if err != nil { + t.Error(err.Error()) + return + } + b := bson.D{ + {Key: "$set", Value: bson.D{ + {Key: "name", Value: "pi"}, + {Key: "value", Value: 3.1415926}, + }}, + } + res, err := client.UpdateOne("testing", bson.D{{"_id", id}}, b) + if err != nil { + t.Error(err.Error()) + return + } + if res == nil { + t.Error("Collection return empty") + } + }) +} diff --git a/core/server/operation/config.go b/core/server/operation/config.go new file mode 100644 index 0000000..628e788 --- /dev/null +++ b/core/server/operation/config.go @@ -0,0 +1,129 @@ +package operation + +import ( + "encoding/json" + "fmt" + "github.com/spf13/viper" + "pet-house.com/core/g" + "pet-house.com/core/server/viper_server" + "strings" +) + +var CONFIG = Operation{ + Except: Route{ + Uri: "api/v1/upload;api/v1/upload", + Method: "post;put", + }, + Include: Route{ + Uri: "api/v1/menus", + Method: "get", + }, +} + +// IsExist config file is exist +func IsExist() bool { + return getViperConfig().IsFileExist() +} + +// Remove remove config file +func Remove() error { + return getViperConfig().Remove() +} + +// Recover +func Recover() error { + b, err := json.Marshal(CONFIG) + if err != nil { + return err + } + return getViperConfig().Recover(b) +} + +// Operation +// Except set which routers don't generate system log, use ';' to separate. +// Include set which routers need to generate system log, use ';' to separate. +type Operation struct { + Except Route `mapstructure:"except" json:"except" yaml:"except"` + Include Route `mapstructure:"include" json:"include" yaml:"include"` +} + +type Route struct { + Uri string `mapstructure:"uri" json:"uri" yaml:"uri"` + Method string `mapstructure:"method" json:"method" yaml:"method"` +} + +// GetExcept return routers which need to excepted +func (op Operation) GetExcept() ([]string, []string) { + uri := strings.Split(op.Except.Uri, ";") + method := strings.Split(op.Except.Method, ";") + return uri, method +} + +// GetInclude return routers which need to included +func (op Operation) GetInclude() ([]string, []string) { + uri := strings.Split(op.Include.Uri, ";") + method := strings.Split(op.Include.Method, ";") + return uri, method +} + +// IsInclude check whether the current route needs to belong to the included data +func (op Operation) IsInclude(uri, method string) bool { + incUri, incMethod := op.GetInclude() + if len(incUri) != len(incMethod) { + return false + } + + for i := 0; i < len(incUri); i++ { + if uri == incUri[i] && method == incMethod[i] { + return true + } + } + return false +} + +// IsExcept check whether the current route needs to belong to the excepted data +func (op Operation) IsExcept(uri, method string) bool { + excUri, excMethod := op.GetExcept() + if len(excUri) != len(excMethod) { + return false + } + + for i := 0; i < len(excUri); i++ { + if uri == excUri[i] && method == excMethod[i] { + return true + } + } + return false +} + +// getViperConfig get viper config +func getViperConfig() viper_server.ViperConfig { + configName := "operation" + return viper_server.ViperConfig{ + Debug: true, + Directory: g.ConfigDir, + Name: configName, + Type: g.ConfigType, + Watch: func(vi *viper.Viper) error { + if err := vi.Unmarshal(&CONFIG); err != nil { + return fmt.Errorf("get Unarshal error: %v", err) + } + // watch config file change + vi.SetConfigName(configName) + return nil + }, + // + Default: []byte(` +{ + "except":{ + "uri": "` + CONFIG.Except.Uri + `", + "method": "` + CONFIG.Except.Method + `" + }, + "include": + { + "uri": "` + CONFIG.Include.Uri + `", + "method": "` + CONFIG.Include.Method + `" + } + }`), + } +} diff --git a/core/server/operation/config_test.go b/core/server/operation/config_test.go new file mode 100644 index 0000000..fccdba4 --- /dev/null +++ b/core/server/operation/config_test.go @@ -0,0 +1,71 @@ +package operation + +import ( + "reflect" + "testing" +) + +func TestGetExcept(t *testing.T) { + t.Run("test get except", func(t *testing.T) { + wantUri := []string{"api/v1/upload", "api/v1/upload"} + wantMethod := []string{"post", "put"} + uri, method := CONFIG.GetExcept() + if !reflect.DeepEqual(uri, wantUri) { + t.Errorf("get except want %+v but get %+v", wantUri, uri) + } + if !reflect.DeepEqual(method, wantMethod) { + t.Errorf("get except want %+v but get %+v", wantMethod, method) + } + }) +} +func TestGetInclude(t *testing.T) { + t.Run("test get include", func(t *testing.T) { + wantUri := []string{"api/v1/menus"} + wantMethod := []string{"get"} + uri, method := CONFIG.GetInclude() + if !reflect.DeepEqual(uri, wantUri) { + t.Errorf("get include want %+v but get %+v", wantUri, uri) + } + if !reflect.DeepEqual(method, wantMethod) { + t.Errorf("get include want %+v but get %+v", wantMethod, method) + } + }) +} +func TestGetIsInclude(t *testing.T) { + t.Run("test get include", func(t *testing.T) { + wantUri := "api/v1/menus" + wantMethod := "get" + if !CONFIG.IsInclude(wantUri, wantMethod) { + t.Errorf("[%s](%s) want include true but get false", wantMethod, wantUri) + } + wantUri = "api/v1/menus" + wantMethod = "post" + if CONFIG.IsInclude(wantUri, wantMethod) { + t.Errorf("[%s](%s) want include false but get true", wantMethod, wantUri) + } + wantUri = "api/v1/menu" + wantMethod = "get" + if CONFIG.IsInclude(wantUri, wantMethod) { + t.Errorf("[%s](%s) want include false but get true", wantMethod, wantUri) + } + }) +} +func TestGetIsExcept(t *testing.T) { + t.Run("test get except", func(t *testing.T) { + wantUri := "api/v1/upload" + wantMethod := "post" + if !CONFIG.IsExcept(wantUri, wantMethod) { + t.Errorf("[%s](%s) want except true but get false", wantMethod, wantUri) + } + wantUri = "api/v1/upload" + wantMethod = "get" + if CONFIG.IsExcept(wantUri, wantMethod) { + t.Errorf("[%s](%s) want except false but get true", wantMethod, wantUri) + } + wantUri = "api/v1/menu" + wantMethod = "post" + if CONFIG.IsExcept(wantUri, wantMethod) { + t.Errorf("[%s](%s) want except false but get true", wantMethod, wantUri) + } + }) +} diff --git a/core/server/operation/index.go b/core/server/operation/index.go new file mode 100644 index 0000000..76e7d22 --- /dev/null +++ b/core/server/operation/index.go @@ -0,0 +1,41 @@ +package operation + +import ( + "time" + + "gorm.io/gorm" + "pet-house.com/core/server/database" + "pet-house.com/core/server/viper_server" +) + +func init() { + err := viper_server.Init(getViperConfig()) + if err != nil { + panic(err) + } +} + +// CreateOplog +func CreateOplog(ol *Oplog) error { + err := database.Instance().Model(&Oplog{}).Create(ol).Error + if err != nil { + return err + } + return nil +} + +// Oplog middleware model +type Oplog struct { + gorm.Model + Ip string `json:"ip" form:"ip" gorm:"column:ip;comment:ip"` + Method string `json:"method" form:"method" gorm:"column:method;comment:method" validate:"required"` + Path string `json:"path" form:"path" gorm:"column:path;comment:path" validate:"required"` + Status int `json:"status" form:"status" gorm:"column:status;comment:status" validate:"required"` + Latency time.Duration `json:"latency" form:"latency" gorm:"column:latency;comment:latency"` + Agent string `json:"agent" form:"agent" gorm:"column:agent;comment:agent"` + ErrorMessage string `json:"errorMessage" form:"errorMessage" gorm:"column:error_message;comment:error_message"` + Body string `json:"body" form:"body" gorm:"type:longtext;column:body;comment:body"` + Resp string `json:"resp" form:"resp" gorm:"type:longtext;column:resp;comment:resp"` + UserID uint `json:"userId" form:"userId" gorm:"column:user_id;comment:user_id"` + TenancyId uint `json:"tenancyId" form:"tenancyId" gorm:"column:tenancy_id;comment:tenancyId"` +} diff --git a/core/server/operation/index_test.go b/core/server/operation/index_test.go new file mode 100644 index 0000000..2978dd5 --- /dev/null +++ b/core/server/operation/index_test.go @@ -0,0 +1,17 @@ +package operation + +import ( + "testing" + + "pet-house.com/core/server/database" +) + +func TestCreateOplog(t *testing.T) { + database.Instance().AutoMigrate(&Oplog{}) + t.Run("test create op log", func(t *testing.T) { + record := &Oplog{} + if err := CreateOplog(record); err != nil { + t.Errorf("test create op log get %s", err.Error()) + } + }) +} diff --git a/core/server/operation/main_test.go b/core/server/operation/main_test.go new file mode 100644 index 0000000..e7e404c --- /dev/null +++ b/core/server/operation/main_test.go @@ -0,0 +1,40 @@ +package operation + +import ( + _ "embed" + "os" + "testing" + + "github.com/bwmarrin/snowflake" + "pet-house.com/core/g" + "pet-house.com/core/helper/str" + "pet-house.com/core/server/database" + "pet-house.com/core/server/zap_server" +) + +func TestMain(m *testing.M) { + + node, _ := snowflake.NewNode(1) + uuid := str.Join("operation", "_", node.Generate().String()) + + database.CONFIG.DbName = uuid + database.CONFIG.Path = g.TestMysqlAddr + database.CONFIG.Password = g.TestMysqlPwd + database.Instance() + + code := m.Run() + + err := database.DorpDB(database.CONFIG.BaseDsn(), "mysql", uuid) + if err != nil { + zap_server.ZAPLOG.Error(err.Error()) + } + + db, _ := database.Instance().DB() + if db != nil { + db.Close() + } + database.Remove() + Remove() + zap_server.Remove() + os.Exit(code) +} diff --git a/core/server/operation/migratin.go b/core/server/operation/migratin.go new file mode 100644 index 0000000..4d5bbba --- /dev/null +++ b/core/server/operation/migratin.go @@ -0,0 +1,17 @@ +package operation + +import ( + "gorm.io/gorm" +) + +func GetMigration() *gormigrate.Migration { + return &gormigrate.Migration{ + ID: "20211214120700_create_oplogs_table", + Migrate: func(tx *gorm.DB) error { + return tx.AutoMigrate(&Oplog{}) + }, + Rollback: func(tx *gorm.DB) error { + return tx.Migrator().DropTable("oplogs") + }, + } +} diff --git a/core/server/viper_server/index.go b/core/server/viper_server/index.go new file mode 100644 index 0000000..03c6357 --- /dev/null +++ b/core/server/viper_server/index.go @@ -0,0 +1,124 @@ +package viper_server + +import ( + "bytes" + "errors" + "fmt" + "path/filepath" + + "github.com/spf13/viper" + "pet-house.com/core/helper/dir" + "pet-house.com/core/helper/str" +) + +var ( + ErrEmptyName = errors.New("config'name can't be empty value") +) + +type ViperConfig struct { + Debug bool + Directory string + Name string + Type string + Default []byte + Watch func(*viper.Viper) error +} + +// getConfigFilePath +func (vc ViperConfig) getConfigFilePath() string { + return filepath.Join(dir.GetCurrentAbPath(), vc.Directory, str.Join(vc.Name, ".", vc.Type)) +} + +// getConfigFileDir +func (vc ViperConfig) getConfigFileDir() string { + if vc.Directory == "" { + return "config" + } + return vc.Directory +} + +// IsFileExist +func (vc ViperConfig) IsFileExist() bool { + return dir.IsExist(vc.getConfigFilePath()) +} + +// Remove remove config file +func (vc ViperConfig) Remove() error { + return dir.Remove(vc.getConfigFilePath()) +} + +// Recover recover config file content +func (vc ViperConfig) Recover(b []byte) error { + _, err := dir.WriteBytes(vc.getConfigFilePath(), b) + return err +} + +// Init +func Init(vc ViperConfig) error { + + if vc.Name == "" { + return ErrEmptyName + } + + if vc.Type == "" { + vc.Type = "yaml" + } + + vc.Directory = vc.getConfigFileDir() + + filePath := vc.getConfigFilePath() + if vc.Debug { + fmt.Printf("\nthis config file's path is [%s]\n", filePath) + } + + vi := viper.New() + if vc.Debug { + fmt.Printf("this config file's type is [%s]\n", vc.Type) + } + vi.SetConfigName(vc.Name) + vi.SetConfigType(vc.Type) + vi.AddConfigPath(vc.Directory) + + isExist := dir.IsExist(filePath) + if !isExist { + if vc.Debug { + fmt.Printf("this config [%s] is not exist\n", filePath) + } + if vc.Directory != "./" { + err := dir.InsureDir(filepath.Dir(filePath)) + if err != nil { + return fmt.Errorf("create dir %s fail : %v", filePath, err) + } + } + + // ReadConfig + if err := vi.ReadConfig(bytes.NewBuffer(vc.Default)); err != nil { + if vc.Debug { + fmt.Println(string(vc.Default)) + } + return fmt.Errorf("read default config fail : %w ", err) + } + + // WriteConfigAs + if err := vi.WriteConfigAs(filePath); err != nil { + return fmt.Errorf("write config to path fail: %w ", err) + } + + } else { + if vc.Debug { + fmt.Printf("this config file [%s] is existed\n", filePath) + } + vi.SetConfigFile(filePath) + err := vi.ReadInConfig() + if err != nil { + return fmt.Errorf("read config fail: %w ", err) + } + } + + err := vc.Watch(vi) + if err != nil { + return err + } + + return nil +} diff --git a/core/server/viper_server/index_test.go b/core/server/viper_server/index_test.go new file mode 100644 index 0000000..07f603c --- /dev/null +++ b/core/server/viper_server/index_test.go @@ -0,0 +1,156 @@ +package viper_server + +import ( + "fmt" + "path/filepath" + "testing" + "time" + + "github.com/spf13/viper" + "pet-house.com/core/g" + "pet-house.com/core/helper/dir" + "pet-house.com/core/helper/str" +) + +type Zap struct { + Level string `mapstructure:"level" json:"level" yaml:"level"` //debug ,info,warn,error,panic,fatal + Format string `mapstructure:"format" json:"format" yaml:"format"` + Prefix string `mapstructure:"prefix" json:"prefix" yaml:"prefix"` + Director string `mapstructure:"director" json:"director" yaml:"director"` + LinkName string `mapstructure:"link-name" json:"link-name" yaml:"link-name"` + ShowLine bool `mapstructure:"show-line" json:"show-line" yaml:"show-line"` + EncodeLevel string `mapstructure:"encode-level" json:"encode-level" yaml:"encode-level"` + StacktraceKey string `mapstructure:"stacktrace-key" json:"stacktrace-key" yaml:"stacktrace-key"` + LogInConsole bool `mapstructure:"log-in-console" json:"log-in-console" yaml:"log-in-console"` +} + +func TestViperInit(t *testing.T) { + tc := &Zap{} + config := ViperConfig{ + Directory: g.ConfigDir, + Name: "zap", // zap => type Zap struct + Type: g.ConfigType, + Watch: func(vi *viper.Viper) error { + if err := vi.Unmarshal(tc); err != nil { + return fmt.Errorf("get Unarshal error: %v", err) + } + vi.SetConfigName("zap") + return nil + }, + // + Default: []byte(`{ +"level": "info", +"format": "console", +"prefix": "[OP-ONLINE]", +"director": "log", +"link-name": "latest_log", +"show-line": true, +"encode-level": "LowercaseColorLevelEncoder", +"stacktrace-key": "stacktrace", +"log-in-console": true}`), + } + + defer config.Remove() + + want := Zap{ + Level: "info", + Format: "console", + Prefix: "[OP-ONLINE]", + Director: "log", + LinkName: "latest_log", + ShowLine: true, + EncodeLevel: "LowercaseColorLevelEncoder", + StacktraceKey: "stacktrace", + LogInConsole: true, + } + + err := Init(config) + if err != nil { + t.Errorf("init %s's config get error: %v", str.Join(config.Name, ".", config.Type), err) + } + if want.Level != tc.Level { + t.Errorf("want %+v but get %+v", want.Level, tc.Level) + } + if want.Format != tc.Format { + t.Errorf("want %+v but get %+v", want.Format, tc.Format) + } + if want.Prefix != tc.Prefix { + t.Errorf("want %+v but get %+v", want.Prefix, tc.Prefix) + } + if want.Director != tc.Director { + t.Errorf("want %+v but get %+v", want.Director, tc.Director) + } + if want.LinkName != tc.LinkName { + t.Errorf("want %+v but get %+v", want.LinkName, tc.LinkName) + } + if want.ShowLine != tc.ShowLine { + t.Errorf("want %+v but get %+v", want.ShowLine, tc.ShowLine) + } + if want.EncodeLevel != tc.EncodeLevel { + t.Errorf("want %+v but get %+v", want.EncodeLevel, tc.EncodeLevel) + } + if want.StacktraceKey != tc.StacktraceKey { + t.Errorf("want %+v but get %+v", want.StacktraceKey, tc.StacktraceKey) + } + if want.LogInConsole != tc.LogInConsole { + t.Errorf("want %+v but get %+v", want.LogInConsole, tc.LogInConsole) + } + + dir.WriteBytes(filepath.Join(config.getConfigFilePath()), []byte(`{ +"level": "info1", +"format": "console1", +"prefix": "[OP-ONLINE]1", +"director": "log1", +"link-name": "latest_log1", +"show-line": false, +"encode-level": "LowercaseColorLevelEncoder1", +"stacktrace-key": "stacktrace1", +"log-in-console": false}`)) + + want1 := Zap{ + Level: "info1", + Format: "console1", + Prefix: "[OP-ONLINE]1", + Director: "log1", + LinkName: "latest_log1", + ShowLine: false, + EncodeLevel: "LowercaseColorLevelEncoder1", + StacktraceKey: "stacktrace1", + LogInConsole: false, + } + + err = Init(config) + if err != nil { + t.Errorf("init %s's config get error: %v", str.Join(config.Name, ".", config.Type), err) + } + + time.Sleep(5 * time.Second) + + if want1.Level != tc.Level { + t.Errorf("want1 %+v but get %+v", want1.Level, tc.Level) + } + if want1.Format != tc.Format { + t.Errorf("want1 %+v but get %+v", want1.Format, tc.Format) + } + if want1.Prefix != tc.Prefix { + t.Errorf("want1 %+v but get %+v", want1.Prefix, tc.Prefix) + } + if want1.Director != tc.Director { + t.Errorf("want1 %+v but get %+v", want1.Director, tc.Director) + } + if want1.LinkName != tc.LinkName { + t.Errorf("want1 %+v but get %+v", want1.LinkName, tc.LinkName) + } + if want1.ShowLine != tc.ShowLine { + t.Errorf("want1 %+v but get %+v", want1.ShowLine, tc.ShowLine) + } + if want1.EncodeLevel != tc.EncodeLevel { + t.Errorf("want1 %+v but get %+v", want1.EncodeLevel, tc.EncodeLevel) + } + if want1.StacktraceKey != tc.StacktraceKey { + t.Errorf("want1 %+v but get %+v", want1.StacktraceKey, tc.StacktraceKey) + } + if want1.LogInConsole != tc.LogInConsole { + t.Errorf("want1 %+v but get %+v", want1.LogInConsole, tc.LogInConsole) + } +} diff --git a/core/server/web/cmd.go b/core/server/web/cmd.go new file mode 100644 index 0000000..3d7ea74 --- /dev/null +++ b/core/server/web/cmd.go @@ -0,0 +1,76 @@ +package web + +import ( + "fmt" + "strings" + + "pet-house.com/core/server/database" + "pet-house.com/core/server/viper_server" +) + +// Initialize initialize +func Initialize() error { + var cover string + if IsExist() { + fmt.Println("Your web config is initialized , reinitialized web will cover your web config.") + fmt.Println("Did you want to do it ? [Y/N]") + fmt.Scanln(&cover) + switch strings.ToUpper(cover) { + case "Y": + case "N": + return nil + default: + } + } + + err := Remove() + if err != nil { + return err + } + + err = initConfig() + if err != nil { + return err + } + fmt.Println("web iris-admin initialized finished!") + return nil +} + +func initConfig() error { + var dbType string + fmt.Println("Please choose your database type: ") + fmt.Println("1. mysql (only support mysql now)") + fmt.Scanln(&dbType) + switch dbType { + case "1": + CONFIG.System.DbType = "mysql" + if err := database.Init(); err != nil { + return err + } + default: + CONFIG.System.DbType = "mysql" + if err := database.Init(); err != nil { + return err + } + } + + var systemTimeFormat, systemAddr string + fmt.Println("Please input your system timeformat: ") + fmt.Printf("System timeformat is '%s'\n", CONFIG.System.TimeFormat) + fmt.Scanln(&systemTimeFormat) + if systemTimeFormat != "" { + CONFIG.System.TimeFormat = systemTimeFormat + } + + fmt.Println("Please input your system addr: ") + fmt.Printf("System addr is '%s'\n", CONFIG.System.Addr) + fmt.Scanln(&systemAddr) + if systemAddr != "" { + CONFIG.System.Addr = systemAddr + } + err := viper_server.Init(getViperConfig()) + if err != nil { + return err + } + return nil +} diff --git a/core/server/web/common/index.go b/core/server/web/common/index.go new file mode 100644 index 0000000..8698d2c --- /dev/null +++ b/core/server/web/common/index.go @@ -0,0 +1,153 @@ +package common + +import ( + "fmt" + + "github.com/bwmarrin/snowflake" + "go.uber.org/zap" + "pet-house.com/core/g" + "pet-house.com/core/helper/str" + "pet-house.com/core/migration" + "pet-house.com/core/server/casbin" + "pet-house.com/core/server/database" + "pet-house.com/core/server/operation" + "pet-house.com/core/server/web" + "pet-house.com/core/server/web/web_gin" + "pet-house.com/core/server/web/web_iris" + "pet-house.com/core/server/zap_server" +) + +// BeforeTestMainGin +func BeforeTestMainGin(party func(wi *web_gin.WebServer), seed func(wi *web_gin.WebServer, mc *migration.MigrationCmd)) (string, *web_gin.WebServer) { + fmt.Println("+++++ TEST BEGAIN +++++") + zap_server.CONFIG.LogInConsole = true + zap_server.Recover() + + dbType := g.TestDbType + if dbType != "" { + web.CONFIG.System.DbType = dbType + } + web.Recover() + + node, _ := snowflake.NewNode(1) + uuid := str.Join("gin", "_", node.Generate().String()) + + database.CONFIG.DbName = uuid + mysqlPwd := g.TestMysqlPwd + if mysqlPwd != "" { + database.CONFIG.Password = mysqlPwd + } + mysqlAddr := g.TestMysqlAddr + if mysqlAddr != "" { + database.CONFIG.Path = mysqlAddr + } + database.CONFIG.LogMode = true + + database.Recover() + + if database.Instance() == nil { + fmt.Println("database instance is nil") + return uuid, nil + } + + wi := web_gin.Init() + party(wi) + web.StartTest(wi) + + mc := migration.New() + fmt.Println("++++++ add data to database ++++++") + seed(wi, mc) + err := mc.Migrate() + if err != nil { + fmt.Printf("migrate fail: [%s]", err.Error()) + return uuid, nil + } + err = mc.Seed() + if err != nil { + fmt.Printf("seed fail: [%s]", err.Error()) + return uuid, nil + } + + return uuid, wi +} + +// BeforeTestMainIris +func BeforeTestMainIris(party func(wi *web_iris.WebServer), seed func(wi *web_iris.WebServer, mc *migration.MigrationCmd)) (string, *web_iris.WebServer) { + fmt.Println("+++++ TEST BEGAIN +++++") + zap_server.CONFIG.LogInConsole = true + zap_server.Recover() + + dbType := g.TestDbType + if dbType != "" { + web.CONFIG.System.DbType = dbType + } + web.Recover() + + node, _ := snowflake.NewNode(1) + uuid := str.Join("iris", "_", node.Generate().String()) + + database.CONFIG.DbName = uuid + mysqlPwd := g.TestMysqlPwd + if mysqlPwd != "" { + database.CONFIG.Password = mysqlPwd + } + mysqlAddr := g.TestMysqlAddr + if mysqlAddr != "" { + database.CONFIG.Path = mysqlAddr + } + database.CONFIG.LogMode = true + + database.Recover() + + if database.Instance() == nil { + fmt.Println("database instance is nil") + return uuid, nil + } + + wi := web_iris.Init() + party(wi) + web.StartTest(wi) + + mc := migration.New() + + fmt.Println("++++++ add datas to database ++++++") + + seed(wi, mc) + err := mc.Migrate() + if err != nil { + fmt.Printf("migrate fail: [%s]", err.Error()) + return uuid, nil + } + err = mc.Seed() + if err != nil { + fmt.Printf("seed fail: [%s]", err.Error()) + return uuid, nil + } + + return uuid, wi +} + +func AfterTestMain(uuid string, isDelDb bool) { + defer fmt.Println("++++++++ AFTER END ++++++++") + if isDelDb { + err := database.DorpDB(database.CONFIG.BaseDsn(), "mysql", uuid) + if err != nil { + zap_server.ZAPLOG.Error("delete database fail", zap.String("uuid", uuid), zap.String("err", err.Error())) + } + } + fmt.Println("++++++++ DELETE DATABASE ++++++++") + + db, err := database.Instance().DB() + if err != nil { + zap_server.ZAPLOG.Error(str.Join("get database instance fail:", err.Error())) + } + if db != nil { + db.Close() + } + + defer zap_server.Remove() + defer operation.Remove() + defer casbin.Remove() + defer web.Remove() + defer database.Remove() +} diff --git a/core/server/web/config.go b/core/server/web/config.go new file mode 100644 index 0000000..e156082 --- /dev/null +++ b/core/server/web/config.go @@ -0,0 +1,206 @@ +package web + +import ( + "encoding/json" + "fmt" + "path/filepath" + "strconv" + + "github.com/spf13/viper" + "pet-house.com/core/g" + "pet-house.com/core/helper/str" + "pet-house.com/core/server/viper_server" +) + +var CONFIG = Web{ + FileMaxSize: 1024, // upload file size limit 1024M + SessionTimeout: 60, // session timeout after 60 Minute + Cors: Cors{ + AccessOrigin: "*", + AccessHeaders: "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-U-Id", + AccessMethods: "POST,GET", + AccessExposeHeaders: "Content-Length,Access-Control-Allow-Origin,Access-Control-Allow-Headers,Content-Type", + AccessCredentials: "true", + }, + Except: Route{ + Uri: "", + Method: "", + }, + Menu: Route{ + Uri: "", + Method: "", + }, + System: System{ + Tls: false, + Level: "debug", + Addr: "127.0.0.1:8085", + DbType: "mysql", + TimeFormat: "2006-01-02 15:04:05", + }, + Limit: Limit{ + Disable: true, + Limit: 0, + Burst: 5, + }, + Captcha: Captcha{ + KeyLong: 4, + ImgWidth: 240, + ImgHeight: 80, + }, +} + +type Web struct { + FileMaxSize int64 `mapstructure:"file-max-size" json:"file-max-size" yaml:"file-max-siz"` + SessionTimeout int64 `mapstructure:"session-timeout" json:"session-timeout" yaml:"session-timeout"` + Except Route `mapstructure:"except" json:"except" yaml:"except"` + Menu Route `mapstructure:"menu" json:"menu" yaml:"menu"` + System System `mapstructure:"system" json:"system" yaml:"system"` + Limit Limit `mapstructure:"limit" json:"limit" yaml:"limit"` + Captcha Captcha `mapstructure:"captcha" json:"captcha" yaml:"captcha"` + Cors Cors `mapstructure:"cors" json:"cors" yaml:"cors"` +} + +type Cors struct { + AccessOrigin string `mapstructure:"access-origin" json:"burst" access-origin:"access-origin"` + AccessHeaders string `mapstructure:"access-headers" json:"access-headers" yaml:"access-headers"` + AccessMethods string `mapstructure:"access-methods" json:"access-methods" yaml:"access-methods"` + AccessExposeHeaders string `mapstructure:"access-expose-headers" json:"access-expose-headers" yaml:"access-expose-headers"` + AccessCredentials string `mapstructure:"access-credentials" json:"access-credentials" yaml:"access-credentials"` +} +type Route struct { + Uri string `mapstructure:"uri" json:"uri" yaml:"uri"` + Method string `mapstructure:"method" json:"method" yaml:"method"` +} + +type Captcha struct { + KeyLong int `mapstructure:"key-long" json:"key-long" yaml:"key-long"` + ImgWidth int `mapstructure:"img-width" json:"img-width" yaml:"img-width"` + ImgHeight int `mapstructure:"img-height" json:"img-height" yaml:"img-height"` +} + +type Limit struct { + Disable bool `mapstructure:"disable" json:"disable" yaml:"disable"` + Limit float64 `mapstructure:"limit" json:"limit" yaml:"limit"` + Burst int `mapstructure:"burst" json:"burst" yaml:"burst"` +} + +type System struct { + Tls bool `mapstructure:"tls" json:"tls" yaml:"tls"` // debug,release,test + Level string `mapstructure:"level" json:"level" yaml:"level"` // debug,release,test + Addr string `mapstructure:"addr" json:"addr" yaml:"addr"` + StaticPrefix string `mapstructure:"static-prefix" json:"static-prefix" yaml:"static-prefix"` + WebPrefix string `mapstructure:"web-prefix" json:"web-prefix" yaml:"web-prefix"` + DbType string `mapstructure:"db-type" json:"db-type" yaml:"db-type"` + TimeFormat string `mapstructure:"time-format" json:"time-format" yaml:"time-format"` +} + +// SetDefaultAddrAndTimeFormat +func SetDefaultAddrAndTimeFormat() { + if CONFIG.System.Addr == "" { + CONFIG.System.Addr = "127.0.0.1:8085" + } + + if CONFIG.System.TimeFormat == "" { + CONFIG.System.TimeFormat = "2006-01-02 15:04:05" + } +} + +// ToStaticUrl +func ToStaticUrl(uri string) string { + path := filepath.Join(CONFIG.System.Addr, CONFIG.System.StaticPrefix, uri) + if CONFIG.System.Tls { + return filepath.ToSlash(str.Join("https://", path)) + } + return filepath.ToSlash(str.Join("http://", path)) +} + +// IsExist config file is exist +func IsExist() bool { + return getViperConfig().IsFileExist() +} + +// Remove remove config file +func Remove() error { + return getViperConfig().Remove() +} + +// Recover +func Recover() error { + b, err := json.Marshal(CONFIG) + if err != nil { + return err + } + return getViperConfig().Recover(b) +} + +// getViperConfig get viper config +func getViperConfig() viper_server.ViperConfig { + maxSize := strconv.FormatInt(CONFIG.FileMaxSize, 10) + sessionTimeout := strconv.FormatInt(CONFIG.SessionTimeout, 10) + keyLong := strconv.FormatInt(int64(CONFIG.Captcha.KeyLong), 10) + imgWidth := strconv.FormatInt(int64(CONFIG.Captcha.ImgWidth), 10) + imgHeight := strconv.FormatInt(int64(CONFIG.Captcha.ImgHeight), 10) + limit := strconv.FormatInt(int64(CONFIG.Limit.Limit), 10) + burst := strconv.FormatInt(int64(CONFIG.Limit.Burst), 10) + disable := strconv.FormatBool(CONFIG.Limit.Disable) + tls := strconv.FormatBool(CONFIG.System.Tls) + configName := "web" + return viper_server.ViperConfig{ + Debug: true, + Directory: g.ConfigDir, + Name: configName, + Type: g.ConfigType, + Watch: func(vi *viper.Viper) error { + if err := vi.Unmarshal(&CONFIG); err != nil { + return fmt.Errorf("get Unarshal error: %v", err) + } + // watch config file change + vi.SetConfigName(configName) + return nil + }, + // + Default: []byte(` +{ + "file-max-size": ` + maxSize + `, + "session-timeout": ` + sessionTimeout + `, + "except": + { + "uri": "` + CONFIG.Except.Uri + `", + "method": "` + CONFIG.Except.Method + `" + }, + "menu": + { + "uri": "` + CONFIG.Menu.Uri + `", + "method": "` + CONFIG.Menu.Method + `" + }, + "cors": + { + "access-origin": "` + CONFIG.Cors.AccessOrigin + `", + "access-headers": "` + CONFIG.Cors.AccessHeaders + `", + "access-methods": "` + CONFIG.Cors.AccessMethods + `", + "access-expose-headers": "` + CONFIG.Cors.AccessExposeHeaders + `", + "access-credentials": "` + CONFIG.Cors.AccessCredentials + `" + }, + "captcha": + { + "key-long": ` + keyLong + `, + "img-width": ` + imgWidth + `, + "img-height": ` + imgHeight + ` + }, + "limit": + { + "limit": ` + limit + `, + "disable": ` + disable + `, + "burst": ` + burst + ` + }, + "system": + { + "tls": ` + tls + `, + "level": "` + CONFIG.System.Level + `", + "addr": "` + CONFIG.System.Addr + `", + "db-type": "` + CONFIG.System.DbType + `", + "time-format": "` + CONFIG.System.TimeFormat + `" + } + }`), + } +} diff --git a/core/server/web/config/mysql.json b/core/server/web/config/mysql.json new file mode 100644 index 0000000..a2a7bff --- /dev/null +++ b/core/server/web/config/mysql.json @@ -0,0 +1,11 @@ +{ + "config": "charset=utf8mb4\u0026parseTime=True\u0026loc=Local", + "db-name": "iris-admin", + "log-mode": false, + "log-zap": "error", + "max-idle-conns": 0, + "max-open-conns": 0, + "password": "", + "path": "127.0.0.1:3306", + "username": "root" +} \ No newline at end of file diff --git a/core/server/web/config/web.json b/core/server/web/config/web.json new file mode 100644 index 0000000..7803abb --- /dev/null +++ b/core/server/web/config/web.json @@ -0,0 +1,36 @@ +{ + "captcha": { + "img-height": 80, + "img-width": 240, + "key-long": 4 + }, + "cors": { + "access-credentials": "true", + "access-expose-headers": "Content-Length,Access-Control-Allow-Origin,Access-Control-Allow-Headers,Content-Type", + "access-headers": "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-U-Id", + "access-methods": "POST,GET", + "access-origin": "*" + }, + "except": { + "method": "", + "uri": "" + }, + "file-max-size": 1024, + "limit": { + "burst": 5, + "disable": true, + "limit": 0 + }, + "menu": { + "method": "", + "uri": "" + }, + "session-timeout": 60, + "system": { + "addr": "127.0.0.1:8085", + "db-type": "mysql", + "level": "debug", + "time-format": "2006-01-02 15:04:05", + "tls": false + } +} \ No newline at end of file diff --git a/core/server/web/config/zap.json b/core/server/web/config/zap.json new file mode 100644 index 0000000..54061fb --- /dev/null +++ b/core/server/web/config/zap.json @@ -0,0 +1,11 @@ +{ + "director": "logs", + "encode-level": "LowercaseColorLevelEncoder", + "format": "console", + "level": "debug", + "link-name": "latest_log", + "log-in-console": false, + "prefix": "[IRIS-ADMIN]", + "show-line": true, + "stacktrace-key": "stacktrace" +} \ No newline at end of file diff --git a/core/server/web/config_test.go b/core/server/web/config_test.go new file mode 100644 index 0000000..8f2d846 --- /dev/null +++ b/core/server/web/config_test.go @@ -0,0 +1,55 @@ +package web + +import ( + "testing" +) + +func TestIsExist(t *testing.T) { + t.Run("test web config IsExist function", func(t *testing.T) { + if !IsExist() { + t.Errorf("config's files is not exist.") + } + }) + + t.Run("Test Remove function", func(t *testing.T) { + if err := Remove(); err != nil { + t.Error(err) + } + if IsExist() { + t.Errorf("config's files remove is fail.") + } + }) +} + +func TestSetDefaultAddrAndTimeFormat(t *testing.T) { + CONFIG.System.Addr = "" + CONFIG.System.TimeFormat = "" + t.Run("test set defualt addr and time format", func(t *testing.T) { + SetDefaultAddrAndTimeFormat() + if CONFIG.System.Addr != "127.0.0.1:8085" { + t.Errorf("applyURI want %s but get %s", "127.0.0.1:8085", CONFIG.System.Addr) + } + if CONFIG.System.TimeFormat != "2006-01-02 15:04:05" { + t.Errorf("applyURI want %s but get %s", "2006-01-02 15:04:05", CONFIG.System.TimeFormat) + } + }) +} + +func TestToStaticUrl(t *testing.T) { + SetDefaultAddrAndTimeFormat() + CONFIG.System.StaticPrefix = "/admin" + t.Run("test to static url with tls", func(t *testing.T) { + CONFIG.System.Tls = true + staticPath := ToStaticUrl("/uploads/123.png") + if staticPath != "https://127.0.0.1:8085/admin/uploads/123.png" { + t.Errorf("applyURI want %s but get %s", "https://127.0.0.1:8085/admin/uploads/123.png", staticPath) + } + }) + t.Run("test to static url with tls", func(t *testing.T) { + CONFIG.System.Tls = false + staticPath := ToStaticUrl("/uploads/123.png") + if staticPath != "http://127.0.0.1:8085/admin/uploads/123.png" { + t.Errorf("applyURI want %s but get %s", "https://127.0.0.1:8085/admin/uploads/123.png", staticPath) + } + }) +} diff --git a/core/server/web/index.go b/core/server/web/index.go new file mode 100644 index 0000000..f6bf4b8 --- /dev/null +++ b/core/server/web/index.go @@ -0,0 +1,46 @@ +package web + +import ( + "pet-house.com/core/server/viper_server" + "pet-house.com/core/server/zap_server" +) + +// init +func init() { + viper_server.Init(getViperConfig()) +} + +type WebBaseFunc interface { + AddWebStatic(staticAbsPath, webPrefix string, paths ...string) + AddUploadStatic(staticAbsPath, webPrefix string) + InitRouter() error + Run() +} + +// WebFunc +// - GetTestClient +// - GetTestLogin +// - AddWebStatic +// - AddUploadStatic +// - Run +type WebFunc interface { + WebBaseFunc +} + +// Start +func Start(wf WebFunc) { + err := wf.InitRouter() + if err != nil { + zap_server.ZAPLOG.Error(err.Error()) + return + } + wf.Run() +} + +// StartTest +func StartTest(wf WebFunc) { + err := wf.InitRouter() + if err != nil { + zap_server.ZAPLOG.Error(err.Error()) + } +} diff --git a/core/server/web/web_gin/index.go b/core/server/web/web_gin/index.go new file mode 100644 index 0000000..b314c6d --- /dev/null +++ b/core/server/web/web_gin/index.go @@ -0,0 +1,151 @@ +package web_gin + +import ( + "errors" + "fmt" + "github.com/mattn/go-colorable" + "net/http" + "path/filepath" + "regexp" + "strings" + "time" + + "github.com/gin-gonic/gin" + "pet-house.com/core/helper/arr" + "pet-house.com/core/helper/dir" + "pet-house.com/core/helper/str" + "pet-house.com/core/server/web" + "pet-house.com/core/server/web/web_gin/middleware" +) + +var ErrAuthDriverEmpty = errors.New("auth driver initialize fail") + +// WebServer +// - app gin.Engine +// - idleConnsClosed +// - addr +// - timeFormat +// - staticPrefix +type WebServer struct { + app *gin.Engine + server + addr string + timeFormat string + webStatics []WebStatic +} + +type WebStatic struct { + Prefix string + IndexFile []byte +} + +// Init +func Init() *WebServer { + gin.SetMode(web.CONFIG.System.Level) + app := gin.Default() + if web.CONFIG.System.Tls { + app.Use(middleware.LoadTls()) + } + app.Use(middleware.Cors()) + registerValidation() + + gin.DefaultWriter = colorable.NewColorableStdout() + + web.SetDefaultAddrAndTimeFormat() + + return &WebServer{ + app: app, + addr: web.CONFIG.System.Addr, + timeFormat: web.CONFIG.System.TimeFormat, + } +} + +// NoRoute for 404 http status +func (ws *WebServer) NoRoute() { + if len(ws.webStatics) == 0 { + return + } + + ws.app.NoRoute(func(ctx *gin.Context) { + // excepte for /v0 /v1 and so on + reg := `^/v[0-9]+$|^(/v[0-9]+)/` + ok, _ := regexp.MatchString(reg, ctx.Request.RequestURI) + if ok { + ctx.Writer.WriteHeader(http.StatusNotFound) + ctx.Writer.Flush() + return + } + + var indexFile []byte + for _, wp := range ws.webStatics { + // match /admin or /admin/*** + reg := str.Join("^", wp.Prefix, "$|^(", wp.Prefix, ")/") + ok, err := regexp.MatchString(reg, ctx.Request.RequestURI) + if err != nil || !ok { + continue + } + indexFile = wp.IndexFile + } + + ctx.Writer.WriteHeader(http.StatusOK) + ctx.Writer.Write(indexFile) + + ctx.Writer.Header().Add("Accept", "text/html") + ctx.Writer.Flush() + }) +} + +// GetEngine return *gin.Engine +func (ws *WebServer) GetEngine() *gin.Engine { + return ws.app +} + +// AddWebStatic +func (ws *WebServer) AddWebStatic(staticAbsPath, webPrefix string, paths ...string) { + webPrefixs := strings.Split(web.CONFIG.System.WebPrefix, ",") + wp := arr.NewCheckArrayType(2) + for _, webPrefix := range webPrefixs { + wp.Add(webPrefix) + } + if wp.Check(webPrefix) { + return + } + + favicon := filepath.Join(staticAbsPath, "favicon.ico") + index := filepath.Join(staticAbsPath, "index.html") + + ws.app.Static(str.Join(webPrefix, "/favicon.ico"), favicon) + ws.app.StaticFile(webPrefix, index) + + if len(paths) > 0 { + for _, path := range paths { + static := filepath.Join(staticAbsPath, path) + ws.app.Static(path, static) + } + } + + web.CONFIG.System.WebPrefix = str.Join(web.CONFIG.System.WebPrefix, ",", webPrefix) + file, _ := dir.ReadBytes(index) + webStatic := WebStatic{ + Prefix: webPrefix, + IndexFile: file, + } + ws.webStatics = append(ws.webStatics, webStatic) + +} + +// AddUploadStatic +func (ws *WebServer) AddUploadStatic(webPrefix, staticAbsPath string) { + ws.app.StaticFS(webPrefix, http.Dir(staticAbsPath)) + web.CONFIG.System.StaticPrefix = webPrefix +} + +// Run +func (ws *WebServer) Run() { + ws.NoRoute() + s := initServer(web.CONFIG.System.Addr, ws.app) + time.Sleep(10 * time.Microsecond) + fmt.Printf("默认监听地址: http://%s\n", web.CONFIG.System.Addr) + s.ListenAndServe() + +} diff --git a/core/server/web/web_gin/index_test.go b/core/server/web/web_gin/index_test.go new file mode 100644 index 0000000..e78c01f --- /dev/null +++ b/core/server/web/web_gin/index_test.go @@ -0,0 +1,39 @@ +package web_gin + +import ( + "io/ioutil" + "net/http" + "testing" + "time" + + "pet-house.com/core/server/database" + "pet-house.com/core/server/web" + "pet-house.com/core/server/zap_server" +) + +func TestStart(t *testing.T) { + defer zap_server.Remove() + defer web.Remove() + defer database.Remove() + web.CONFIG.System.Addr = "127.0.0.1:18088" + go func() { + web.Start(Init()) + }() + + time.Sleep(3 * time.Second) + + t.Run("test web start", func(t *testing.T) { + resp, err := http.Get("http://127.0.0.1:18088") + if err != nil { + t.Errorf("test web start get %v", err) + } + defer resp.Body.Close() + _, err = ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("test web start get %v", err) + } + if resp.StatusCode != http.StatusNotFound { + t.Errorf("test web start want [%d] but get [%d]", http.StatusNotFound, resp.StatusCode) + } + }) +} diff --git a/core/server/web/web_gin/middleware/cors.go b/core/server/web/web_gin/middleware/cors.go new file mode 100644 index 0000000..c66b71a --- /dev/null +++ b/core/server/web/web_gin/middleware/cors.go @@ -0,0 +1,24 @@ +package middleware + +import ( + "github.com/gin-gonic/gin" + "net/http" + "pet-house.com/core/server/web" +) + +// Cors +func Cors() gin.HandlerFunc { + return func(c *gin.Context) { + method := c.Request.Method + c.Header("Access-Control-Allow-Origin", web.CONFIG.Cors.AccessOrigin) + c.Header("Access-Control-Allow-Headers", web.CONFIG.Cors.AccessHeaders) + c.Header("Access-Control-Allow-Methods", web.CONFIG.Cors.AccessMethods) + c.Header("Access-Control-Expose-Headers", web.CONFIG.Cors.AccessExposeHeaders) + c.Header("Access-Control-Allow-Credentials", web.CONFIG.Cors.AccessCredentials) + + if method == "OPTIONS" { + c.AbortWithStatus(http.StatusNoContent) + } + c.Next() + } +} diff --git a/core/server/web/web_gin/middleware/loadtls.go b/core/server/web/web_gin/middleware/loadtls.go new file mode 100644 index 0000000..b8d0ebf --- /dev/null +++ b/core/server/web/web_gin/middleware/loadtls.go @@ -0,0 +1,24 @@ +package middleware + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/unrolled/secure" +) + +// LoadTls +func LoadTls() gin.HandlerFunc { + return func(c *gin.Context) { + middleware := secure.New(secure.Options{ + SSLRedirect: true, + SSLHost: "127.0.0.1:443", + }) + err := middleware.Process(c.Writer, c.Request) + if err != nil { + fmt.Println(err) + return + } + c.Next() + } +} diff --git a/core/server/web/web_gin/request/index.go b/core/server/web/web_gin/request/index.go new file mode 100644 index 0000000..af27092 --- /dev/null +++ b/core/server/web/web_gin/request/index.go @@ -0,0 +1,20 @@ +package request + +// Paging common input parameter structure +type PageInfo struct { + Page int `json:"page" form:"page" binding:"required"` + PageSize int `json:"pageSize" form:"pageSize" binding:"required"` + OrderBy string `json:"orderBy" form:"orderBy"` + SortBy string `json:"sortBy" form:"sortBy"` +} + +// Find by id structure +type IdBinding struct { + Id uint `json:"id" uri:"id" form:"id" binding:"required"` +} + +type IdsBinding struct { + Ids []uint `json:"ids" form:"ids" binding:"required,dive,required"` +} + +type Empty struct{} diff --git a/core/server/web/web_gin/response/index.go b/core/server/web/web_gin/response/index.go new file mode 100644 index 0000000..0f897c6 --- /dev/null +++ b/core/server/web/web_gin/response/index.go @@ -0,0 +1,76 @@ +package response + +import ( + "net/http" + "time" + + "github.com/gin-gonic/gin" +) + +const ( + ResponseOkMessage = "OK" + ResponseErrorMessage = "FAIL" +) + +type Response struct { + Code int `json:"status"` + Data interface{} `json:"data,omitempty"` + Msg string `json:"message"` +} + +func Result(code int, data interface{}, msg string, ctx *gin.Context) { + ctx.JSON(http.StatusOK, Response{code, data, msg}) +} + +func Ok(ctx *gin.Context) { + Result(http.StatusOK, map[string]interface{}{}, ResponseOkMessage, ctx) +} + +func OkWithMessage(message string, ctx *gin.Context) { + Result(http.StatusOK, map[string]interface{}{}, message, ctx) +} + +func OkWithData(data interface{}, ctx *gin.Context) { + Result(http.StatusOK, data, ResponseOkMessage, ctx) +} + +func OkWithDetailed(data interface{}, message string, ctx *gin.Context) { + Result(http.StatusOK, data, message, ctx) +} + +func Fail(ctx *gin.Context) { + Result(http.StatusBadRequest, map[string]interface{}{}, ResponseErrorMessage, ctx) +} + +func UnauthorizedFailWithMessage(message string, ctx *gin.Context) { + Result(http.StatusUnauthorized, map[string]interface{}{}, message, ctx) +} + +func UnauthorizedFailWithDetailed(data interface{}, message string, ctx *gin.Context) { + Result(http.StatusUnauthorized, data, message, ctx) +} + +func ForbiddenFailWithMessage(message string, ctx *gin.Context) { + Result(http.StatusForbidden, map[string]interface{}{}, message, ctx) +} + +func FailWithMessage(message string, ctx *gin.Context) { + Result(http.StatusBadRequest, map[string]interface{}{}, message, ctx) +} + +func FailWithDetailed(data interface{}, message string, ctx *gin.Context) { + Result(http.StatusBadRequest, data, message, ctx) +} + +type PageResult struct { + List interface{} `json:"list,omitempty"` + Total int64 `json:"total"` + Page int `json:"page"` + PageSize int `json:"pageSize"` +} + +type BaseResponse struct { + Id uint `json:"id"` + CreatedAt *time.Time `json:"createdAt"` + UpdatedAt *time.Time `json:"updatedAt"` +} diff --git a/core/server/web/web_gin/router.go b/core/server/web/web_gin/router.go new file mode 100644 index 0000000..e8032d7 --- /dev/null +++ b/core/server/web/web_gin/router.go @@ -0,0 +1,90 @@ +package web_gin + +import ( + "net/http" + "path/filepath" + "strings" + + limit "github.com/aviddiviner/gin-limit" + "github.com/gin-contrib/pprof" + "github.com/gin-gonic/gin" + "pet-house.com/core/helper/arr" + "pet-house.com/core/server/web" + "pet-house.com/core/server/web/web_gin/middleware" +) + +func (ws *WebServer) GetRouterGroup(relativePath string) *gin.RouterGroup { + return ws.app.Group(relativePath) +} + +// InitRouter +func (ws *WebServer) InitRouter() error { + ws.app.Use(limit.MaxAllowed(50)) + if web.CONFIG.System.Level == "debug" { + pprof.Register(ws.app) + } + router := ws.app.Group("/") + { + router.Use(middleware.Cors()) + + router.GET("/v0/version", func(ctx *gin.Context) { + ctx.String(http.StatusOK, "IRIS-ADMIN is running!!!") + }) + } + return nil +} + +// GetSources +// - PermRoutes +// - NoPermRoutes +func (ws *WebServer) GetSources() ([]map[string]string, []map[string]string) { + + methodExcepts := strings.Split(web.CONFIG.Except.Method, ";") + uriExcepts := strings.Split(web.CONFIG.Except.Uri, ";") + methodMenus := strings.Split(web.CONFIG.Menu.Method, ";") + uriMenus := strings.Split(web.CONFIG.Menu.Uri, ";") + + routeLen := len(ws.app.Routes()) + permRoutes := make([]map[string]string, 0, routeLen) + noPermRoutes := make([]map[string]string, 0, routeLen) + + for _, r := range ws.app.Routes() { + bases := strings.Split(filepath.Base(r.Handler), ".") + if len(bases) != 2 { + continue + } + path := filepath.ToSlash(filepath.Clean(r.Path)) + route := map[string]string{ + "path": path, + "desc": bases[1], + "group": bases[0], + "method": r.Method, + "is_menu": "0", + } + if len(methodMenus) > 0 && len(uriMenus) > 0 && len(methodMenus) == len(uriMenus) { + for i := 0; i < len(methodMenus); i++ { + if strings.EqualFold(r.Method, strings.ToLower(methodMenus[i])) && strings.EqualFold(path, strings.ToLower(uriMenus[i])) { + route["is_menu"] = "1" + } + } + } + + httpStatusType := arr.NewCheckArrayType(4) + httpStatusType.AddMutil(http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete) + if !httpStatusType.Check(r.Method) { + noPermRoutes = append(noPermRoutes, route) + continue + } + + if len(methodExcepts) > 0 && len(uriExcepts) > 0 && len(methodExcepts) == len(uriExcepts) { + for i := 0; i < len(methodExcepts); i++ { + if strings.EqualFold(r.Method, strings.ToLower(methodExcepts[i])) && strings.EqualFold(path, strings.ToLower(uriExcepts[i])) { + noPermRoutes = append(noPermRoutes, route) + continue + } + } + } + permRoutes = append(permRoutes, route) + } + return permRoutes, noPermRoutes +} diff --git a/core/server/web/web_gin/run.go b/core/server/web/web_gin/run.go new file mode 100644 index 0000000..1062078 --- /dev/null +++ b/core/server/web/web_gin/run.go @@ -0,0 +1,5 @@ +package web_gin + +type server interface { + ListenAndServe() error +} diff --git a/core/server/web/web_gin/run_darwin.go b/core/server/web/web_gin/run_darwin.go new file mode 100644 index 0000000..1e7e44a --- /dev/null +++ b/core/server/web/web_gin/run_darwin.go @@ -0,0 +1,22 @@ +package web_gin + +import ( + "fmt" + "syscall" + "time" + + "github.com/fvbock/endless" + "github.com/gin-gonic/gin" +) + +func initServer(address string, router *gin.Engine) server { + s := endless.NewServer(address, router) + s.BeforeBegin = func(add string) { + fmt.Printf("Actual pid is %d\n", syscall.Getpid()) + // save it somehow + } + s.ReadHeaderTimeout = 10 * time.Millisecond + s.WriteTimeout = 10 * time.Second + s.MaxHeaderBytes = 1 << 20 + return s +} diff --git a/core/server/web/web_gin/run_linux.go b/core/server/web/web_gin/run_linux.go new file mode 100644 index 0000000..5c54fbc --- /dev/null +++ b/core/server/web/web_gin/run_linux.go @@ -0,0 +1,16 @@ +package web_gin + +import ( + "time" + + "github.com/fvbock/endless" + "github.com/gin-gonic/gin" +) + +func initServer(address string, router *gin.Engine) server { + s := endless.NewServer(address, router) + s.ReadHeaderTimeout = 10 * time.Millisecond + s.WriteTimeout = 10 * time.Second + s.MaxHeaderBytes = 1 << 20 + return s +} diff --git a/core/server/web/web_gin/run_windows.go b/core/server/web/web_gin/run_windows.go new file mode 100644 index 0000000..b702785 --- /dev/null +++ b/core/server/web/web_gin/run_windows.go @@ -0,0 +1,18 @@ +package web_gin + +import ( + "net/http" + "time" + + "github.com/gin-gonic/gin" +) + +func initServer(address string, router *gin.Engine) server { + return &http.Server{ + Addr: address, + Handler: router, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + MaxHeaderBytes: 1 << 20, + } +} diff --git a/core/server/web/web_gin/validate.go b/core/server/web/web_gin/validate.go new file mode 100644 index 0000000..641bcb1 --- /dev/null +++ b/core/server/web/web_gin/validate.go @@ -0,0 +1,19 @@ +package web_gin + +import ( + "github.com/gin-gonic/gin/binding" + "pet-house.com/core/server/web" +) + +func registerValidation() { + if v, ok := binding.Validator.Engine().(*validator.Validate); ok { + v.RegisterValidation("dev-required", validateDevRequired) + } +} + +var validateDevRequired validator.Func = func(fl validator.FieldLevel) bool { + if web.CONFIG.System.Level == "release" { + return fl.Field().String() != "" + } + return true +} diff --git a/core/server/web/web_iris/config/mysql.json b/core/server/web/web_iris/config/mysql.json new file mode 100644 index 0000000..a2a7bff --- /dev/null +++ b/core/server/web/web_iris/config/mysql.json @@ -0,0 +1,11 @@ +{ + "config": "charset=utf8mb4\u0026parseTime=True\u0026loc=Local", + "db-name": "iris-admin", + "log-mode": false, + "log-zap": "error", + "max-idle-conns": 0, + "max-open-conns": 0, + "password": "", + "path": "127.0.0.1:3306", + "username": "root" +} \ No newline at end of file diff --git a/core/server/web/web_iris/config/web.json b/core/server/web/web_iris/config/web.json new file mode 100644 index 0000000..7803abb --- /dev/null +++ b/core/server/web/web_iris/config/web.json @@ -0,0 +1,36 @@ +{ + "captcha": { + "img-height": 80, + "img-width": 240, + "key-long": 4 + }, + "cors": { + "access-credentials": "true", + "access-expose-headers": "Content-Length,Access-Control-Allow-Origin,Access-Control-Allow-Headers,Content-Type", + "access-headers": "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-U-Id", + "access-methods": "POST,GET", + "access-origin": "*" + }, + "except": { + "method": "", + "uri": "" + }, + "file-max-size": 1024, + "limit": { + "burst": 5, + "disable": true, + "limit": 0 + }, + "menu": { + "method": "", + "uri": "" + }, + "session-timeout": 60, + "system": { + "addr": "127.0.0.1:8085", + "db-type": "mysql", + "level": "debug", + "time-format": "2006-01-02 15:04:05", + "tls": false + } +} \ No newline at end of file diff --git a/core/server/web/web_iris/config/zap.json b/core/server/web/web_iris/config/zap.json new file mode 100644 index 0000000..54061fb --- /dev/null +++ b/core/server/web/web_iris/config/zap.json @@ -0,0 +1,11 @@ +{ + "director": "logs", + "encode-level": "LowercaseColorLevelEncoder", + "format": "console", + "level": "debug", + "link-name": "latest_log", + "log-in-console": false, + "prefix": "[IRIS-ADMIN]", + "show-line": true, + "stacktrace-key": "stacktrace" +} \ No newline at end of file diff --git a/core/server/web/web_iris/index.go b/core/server/web/web_iris/index.go new file mode 100644 index 0000000..8fe9bba --- /dev/null +++ b/core/server/web/web_iris/index.go @@ -0,0 +1,123 @@ +package web_iris + +import ( + stdContext "context" + "errors" + "github.com/go-playground/validator/v10" + "github.com/kataras/iris/v12" + "github.com/kataras/iris/v12/context" + "strings" + "time" + + "github.com/kataras/iris/v12/middleware/recover" + "pet-house.com/core/helper/arr" + "pet-house.com/core/helper/str" + "pet-house.com/core/server/web" + "pet-house.com/core/server/web/web_iris/middleware" +) + +var ErrAuthDriverEmpty = errors.New("auth driver initialize fail") + +// WebServer +// - app iris application +// - idleConnsClosed +// - addr +// - timeFormat +// - staticPrefix + +type WebServer struct { + app *iris.Application + idleConnsClosed chan struct{} + parties []Party + addr string + timeFormat string +} + +// Party +// - perfix +// - partyFunc +type Party struct { + Perfix string + PartyFunc func(index iris.Party) +} + +func Init() *WebServer { + app := iris.New() + if web.CONFIG.System.Tls { + app.Use(middleware.LoadTls()) + } + app.Use(recover.New()) + app.Validator = validator.New() + app.Logger().SetLevel(web.CONFIG.System.Level) + idleConnsClosed := make(chan struct{}) + iris.RegisterOnInterrupt(func() { + timeout := 10 * time.Second + ctx, cancel := stdContext.WithTimeout(stdContext.Background(), timeout) + defer cancel() + app.Shutdown(ctx) // close all hosts + close(idleConnsClosed) + }) + // 自定义应用程序配置 + _ = iris.Configuration{ + DisablePathCorrection: true, + DisableBodyConsumptionOnUnmarshal: true, + Charset: "UTF-8", + } + app.Configure(iris.WithCharset("UTF-8"), iris.WithOptimizations) + web.SetDefaultAddrAndTimeFormat() + + return &WebServer{ + app: app, + addr: web.CONFIG.System.Addr, + timeFormat: web.CONFIG.System.TimeFormat, + idleConnsClosed: idleConnsClosed, + } +} + +func (ws *WebServer) GetEngine() *iris.Application { + return ws.app +} + +func (ws *WebServer) AddModule(parties ...Party) { + ws.parties = append(ws.parties, parties...) +} + +func (ws *WebServer) AddFrontFunc(f func(ctx *context.Context)) { + ws.app.Use(f) +} + +func (ws *WebServer) AddWebStatic(staticAbsPath, webPrefix string, paths ...string) { + webPrefixs := strings.Split(web.CONFIG.System.WebPrefix, ",") + wp := arr.NewCheckArrayType(2) + for _, webPrefix := range webPrefixs { + wp.Add(webPrefix) + } + if wp.Check(webPrefix) { + return + } + + fsOrDir := iris.Dir(staticAbsPath) + opt := iris.DirOptions{ + IndexName: "index.html", + SPA: true, + } + ws.app.HandleDir(webPrefix, fsOrDir, opt) + web.CONFIG.System.WebPrefix = str.Join(web.CONFIG.System.WebPrefix, ",", webPrefix) +} + +func (ws *WebServer) AddUploadStatic(webPrefix, staticAbsPath string) { + fsOrDir := iris.Dir(staticAbsPath) + ws.app.HandleDir(webPrefix, fsOrDir) + web.CONFIG.System.StaticPrefix = webPrefix +} + +func (ws *WebServer) Run() { + ws.app.Listen( + ws.addr, + iris.WithoutInterruptHandler, + iris.WithoutServerError(iris.ErrServerClosed), + iris.WithOptimizations, + iris.WithTimeFormat(ws.timeFormat), + ) + <-ws.idleConnsClosed +} diff --git a/core/server/web/web_iris/index_test.go b/core/server/web/web_iris/index_test.go new file mode 100644 index 0000000..333f6b0 --- /dev/null +++ b/core/server/web/web_iris/index_test.go @@ -0,0 +1,39 @@ +package web_iris + +import ( + "io/ioutil" + "net/http" + "testing" + "time" + + "pet-house.com/core/server/database" + "pet-house.com/core/server/web" + "pet-house.com/core/server/zap_server" +) + +func TestRun(t *testing.T) { + defer zap_server.Remove() + defer web.Remove() + defer database.Remove() + web.CONFIG.System.Addr = "127.0.0.1:18085" + go func() { + web.Start(Init()) + }() + + time.Sleep(3 * time.Second) + + t.Run("test web run", func(t *testing.T) { + resp, err := http.Get("http://127.0.0.1:18085/v0/version") + if err != nil { + t.Errorf("test web start get %v", err) + } + defer resp.Body.Close() + s, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("test web start get %v", err) + } + if string(s) != "IRIS-ADMIN is running!!!" { + t.Errorf("test web start want %s but get %s", "Not Found", string(s)) + } + }) +} diff --git a/core/server/web/web_iris/middleware/crs.go b/core/server/web/web_iris/middleware/crs.go new file mode 100644 index 0000000..40a1e42 --- /dev/null +++ b/core/server/web/web_iris/middleware/crs.go @@ -0,0 +1,17 @@ +package middleware + +import ( + "github.com/iris-contrib/middleware/cors" + "github.com/kataras/iris/v12/context" +) + +// CrsAuth +func CrsAuth() context.Handler { + return cors.New(cors.Options{ + AllowedOrigins: []string{"*"}, // allows everything, use that to change the hosts. + AllowedMethods: []string{"POST", "GET"}, + AllowedHeaders: []string{"*"}, + ExposedHeaders: []string{"Accept", "Content-Type", "Content-Length", "Accept-Encoding", "X-CSRF-Token", "Authorization"}, + AllowCredentials: true, + }) +} diff --git a/core/server/web/web_iris/middleware/loadtls.go b/core/server/web/web_iris/middleware/loadtls.go new file mode 100644 index 0000000..97850ea --- /dev/null +++ b/core/server/web/web_iris/middleware/loadtls.go @@ -0,0 +1,15 @@ +package middleware + +import ( + "github.com/iris-contrib/middleware/secure" + "github.com/kataras/iris/v12" +) + +// LoadTls +func LoadTls() iris.Handler { + middleware := secure.New(secure.Options{ + SSLRedirect: true, + SSLHost: "127.0.0.1:443", + }) + return middleware.Handler +} diff --git a/core/server/web/web_iris/router.go b/core/server/web/web_iris/router.go new file mode 100644 index 0000000..e49375b --- /dev/null +++ b/core/server/web/web_iris/router.go @@ -0,0 +1,102 @@ +package web_iris + +import ( + "github.com/kataras/iris/v12" + "net/http" + "strings" + "time" + + "github.com/kataras/iris/v12/middleware/pprof" + "github.com/kataras/iris/v12/middleware/rate" + "github.com/kataras/iris/v12/middleware/recover" + "pet-house.com/core/helper/arr" + "pet-house.com/core/server/web" + "pet-house.com/core/server/web/web_iris/middleware" +) + +// InitRouter +func (ws *WebServer) InitRouter() error { + app := ws.app.Party("/").AllowMethods(iris.MethodOptions) + { + app.Get("/v0/version", func(ctx iris.Context) { + ctx.WriteString("IRIS-ADMIN is running!!!") + }) + + app.UseRouter(middleware.CrsAuth()) + app.UseRouter(recover.New()) + if !web.CONFIG.Limit.Disable { + limitV1 := rate.Limit(web.CONFIG.Limit.Limit, web.CONFIG.Limit.Burst, rate.PurgeEvery(time.Minute, 5*time.Minute)) + app.Use(limitV1) + } + if web.CONFIG.System.Level == "debug" { + debug := func(index iris.Party) { + index.Get("/", func(ctx iris.Context) { + ctx.HTML("

请点击这里打开调试页面") + }) + index.Any("/pprof", pprof.New()) + index.Any("/pprof/{action:path}", pprof.New()) + } + app.PartyFunc("/debug", debug) + } + + for _, party := range ws.parties { + app.PartyFunc(party.Perfix, party.PartyFunc) + } + } + + // http test must build + if err := ws.app.Build(); err != nil { + return err + } + + return nil +} + +// GetSources +// - PermRoutes +// - NoPermRoutes +func (ws *WebServer) GetSources() ([]map[string]string, []map[string]string) { + methodExcepts := strings.Split(web.CONFIG.Except.Method, ";") + uris := strings.Split(web.CONFIG.Except.Uri, ";") + + methodMenus := strings.Split(web.CONFIG.Menu.Method, ";") + uriMenus := strings.Split(web.CONFIG.Menu.Uri, ";") + + routeLen := len(ws.app.GetRoutes()) + permRoutes := make([]map[string]string, 0, routeLen) + noPermRoutes := make([]map[string]string, 0, routeLen) + + for _, r := range ws.app.GetRoutes() { + route := map[string]string{ + "path": r.Path, + "name": r.Name, + "act": r.Method, + } + if len(methodMenus) > 0 && len(uriMenus) > 0 && len(methodMenus) == len(uriMenus) { + for i := 0; i < len(methodMenus); i++ { + if strings.EqualFold(r.Method, strings.ToLower(methodMenus[i])) && strings.EqualFold(r.Path, strings.ToLower(uriMenus[i])) { + route["is_menu"] = "1" + } + } + } + + httpStatusType := arr.NewCheckArrayType(4) + httpStatusType.AddMutil(http.MethodGet, http.MethodPost) + if !httpStatusType.Check(r.Method) { + noPermRoutes = append(noPermRoutes, route) + continue + } + + if len(methodExcepts) > 0 && len(uris) > 0 && len(methodExcepts) == len(uris) { + for i := 0; i < len(methodExcepts); i++ { + if strings.EqualFold(r.Method, strings.ToLower(methodExcepts[i])) && strings.EqualFold(r.Path, strings.ToLower(uris[i])) { + noPermRoutes = append(noPermRoutes, route) + continue + } + } + } + + permRoutes = append(permRoutes, route) + } + return permRoutes, noPermRoutes +} diff --git a/core/server/web/web_iris/validate/index.go b/core/server/web/web_iris/validate/index.go new file mode 100644 index 0000000..6e6d67d --- /dev/null +++ b/core/server/web/web_iris/validate/index.go @@ -0,0 +1,23 @@ +package validate + +import ( + "fmt" + + "github.com/go-playground/validator/v10" +) + +// ValidRequest +func ValidRequest(err interface{}) []string { + var errs []string + if err == nil { + return errs + } + if validateErrs, ok := err.(validator.ValidationErrors); ok { + for _, e := range validateErrs { + e := e + sErr := fmt.Errorf("%s param error: %v", e.Namespace(), e.Value()) + errs = append(errs, sErr.Error()) + } + } + return errs +} diff --git a/core/server/zap_server/config.go b/core/server/zap_server/config.go new file mode 100644 index 0000000..0b99c12 --- /dev/null +++ b/core/server/zap_server/config.go @@ -0,0 +1,88 @@ +package zap_server + +import ( + "encoding/json" + "fmt" + "strconv" + + "github.com/spf13/viper" + "pet-house.com/core/g" + "pet-house.com/core/server/viper_server" +) + +var CONFIG = Zap{ + Level: "debug", + Format: "console", + Prefix: "[IRIS]", + Director: "logs", + LinkName: "latest_log", + ShowLine: true, + EncodeLevel: "LowercaseColorLevelEncoder", + StacktraceKey: "stacktrace", + LogInConsole: false, +} + +type Zap struct { + Level string `mapstructure:"level" json:"level" yaml:"level"` //debug ,info,warn,error,panic,fatal + Format string `mapstructure:"format" json:"format" yaml:"format"` + Prefix string `mapstructure:"prefix" json:"prefix" yaml:"prefix"` + Director string `mapstructure:"director" json:"director" yaml:"director"` + LinkName string `mapstructure:"link-name" json:"link-name" yaml:"link-name"` + ShowLine bool `mapstructure:"show-line" json:"show-line" yaml:"show-line"` + EncodeLevel string `mapstructure:"encode-level" json:"encode-level" yaml:"encode-level"` + StacktraceKey string `mapstructure:"stacktrace-key" json:"stacktrace-key" yaml:"stacktrace-key"` + LogInConsole bool `mapstructure:"log-in-console" json:"log-in-console" yaml:"log-in-console"` +} + +// IsExist config file is exist +func IsExist() bool { + return getViperConfig().IsFileExist() +} + +// Remove remove config file +func Remove() error { + return getViperConfig().Remove() +} + +// Recover +func Recover() error { + b, err := json.Marshal(CONFIG) + if err != nil { + return err + } + return getViperConfig().Recover(b) +} + +// getViperConfig get viper config +func getViperConfig() viper_server.ViperConfig { + configName := "zap" + showLine := strconv.FormatBool(CONFIG.ShowLine) + logInConsole := strconv.FormatBool(CONFIG.LogInConsole) + return viper_server.ViperConfig{ + Debug: true, + Directory: g.ConfigDir, + Name: configName, + Type: g.ConfigType, + Watch: func(vi *viper.Viper) error { + if err := vi.Unmarshal(&CONFIG); err != nil { + return fmt.Errorf("get Unarshal error: %v", err) + } + // watch config file change + vi.SetConfigName(configName) + return nil + }, + // + Default: []byte(` +{ + "level": "` + CONFIG.Level + `", + "format": "` + CONFIG.Format + `", + "prefix": "` + CONFIG.Prefix + `", + "director": "` + CONFIG.Director + `", + "link-name": "` + CONFIG.LinkName + `", + "show-line": ` + showLine + `, + "encode-level": "` + CONFIG.EncodeLevel + `", + "stacktrace-key": "` + CONFIG.StacktraceKey + `", + "log-in-console": ` + logInConsole + ` +}`), + } +} diff --git a/core/server/zap_server/index.go b/core/server/zap_server/index.go new file mode 100644 index 0000000..2e1aa06 --- /dev/null +++ b/core/server/zap_server/index.go @@ -0,0 +1,122 @@ +package zap_server + +import ( + "fmt" + "time" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "pet-house.com/core/server/viper_server" +) + +const ConfigFileName = "zap.yaml" + +// level log level +var ( + level zapcore.Level + ZAPLOG *zap.Logger +) + +// init +func init() { + var logger *zap.Logger + viper_server.Init(getViperConfig()) + + switch CONFIG.Level { + case "debug": + level = zap.DebugLevel + case "info": + level = zap.InfoLevel + case "warn": + level = zap.WarnLevel + case "error": + level = zap.ErrorLevel + case "dpanic": + level = zap.DPanicLevel + case "panic": + level = zap.PanicLevel + case "fatal": + level = zap.FatalLevel + default: + level = zap.InfoLevel + } + + if level == zap.DebugLevel || level == zap.ErrorLevel { + logger = zap.New(getEncoderCore(), zap.AddStacktrace(level)) + } else { + logger = zap.New(getEncoderCore()) + } + if CONFIG.ShowLine { + logger = logger.WithOptions(zap.AddCaller()) + } + ZAPLOG = logger +} + +// getEncoderConfig 获取zapcore.EncoderConfig +func getEncoderConfig() (conf zapcore.EncoderConfig) { + conf = zapcore.EncoderConfig{ + MessageKey: "message", + LevelKey: "level", + TimeKey: "time", + NameKey: "logger", + CallerKey: "caller", + StacktraceKey: CONFIG.StacktraceKey, + LineEnding: zapcore.DefaultLineEnding, + EncodeLevel: zapcore.LowercaseLevelEncoder, + EncodeTime: customTimeEncoder, + EncodeDuration: zapcore.SecondsDurationEncoder, + EncodeCaller: zapcore.FullCallerEncoder, + } + switch { + case CONFIG.EncodeLevel == "LowercaseLevelEncoder": + conf.EncodeLevel = zapcore.LowercaseLevelEncoder + case CONFIG.EncodeLevel == "LowercaseColorLevelEncoder": + conf.EncodeLevel = zapcore.LowercaseColorLevelEncoder + case CONFIG.EncodeLevel == "CapitalLevelEncoder": + conf.EncodeLevel = zapcore.CapitalLevelEncoder + case CONFIG.EncodeLevel == "CapitalColorLevelEncoder": + conf.EncodeLevel = zapcore.CapitalColorLevelEncoder + default: + conf.EncodeLevel = zapcore.LowercaseLevelEncoder + } + return conf +} + +// getEncoder +func getEncoder() zapcore.Encoder { + if CONFIG.Format == "json" { + return zapcore.NewJSONEncoder(getEncoderConfig()) + } + return zapcore.NewConsoleEncoder(getEncoderConfig()) +} + +// getEncoderCore +func getEncoderCore() (core zapcore.Core) { + writer, err := GetWriteSyncer() + if err != nil { + fmt.Printf("Get Write Syncer Failed err:%v", err.Error()) + return + } + return zapcore.NewCore(getEncoder(), writer, level) +} + +func customTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) { + enc.AppendString(t.Format(CONFIG.Prefix + "2006/01/02 - 15:04:05.000")) +} + +type StringsArray [][]string + +// MarshalLogArray +func (ss StringsArray) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range ss { + for ii := range ss[i] { + arr.AppendString(ss[i][ii]) + } + } + return nil +} + +// Strings constructs a field that carries a slice of strings. +func Strings(key string, ss [][]string) zap.Field { + return zap.Array(key, StringsArray(ss)) +} diff --git a/core/server/zap_server/index_test.go b/core/server/zap_server/index_test.go new file mode 100644 index 0000000..631889c --- /dev/null +++ b/core/server/zap_server/index_test.go @@ -0,0 +1,15 @@ +package zap_server + +import ( + "testing" +) + +func TestGetEncoderConfig(t *testing.T) { + defer Remove() + t.Run("test zap_server get encoder config", func(t *testing.T) { + config := getEncoderConfig() + if config.StacktraceKey != CONFIG.StacktraceKey { + t.Errorf("zaplog config stacktracekey want %s but get %s", CONFIG.StacktraceKey, config.StacktraceKey) + } + }) +} diff --git a/core/server/zap_server/rotatelogs_darwin.go b/core/server/zap_server/rotatelogs_darwin.go new file mode 100644 index 0000000..4e7a92b --- /dev/null +++ b/core/server/zap_server/rotatelogs_darwin.go @@ -0,0 +1,24 @@ +package zap_server + +import ( + "os" + "path" + "time" + + zaprotatelogs "github.com/lestrrat-go/file-rotatelogs" + "go.uber.org/zap/zapcore" +) + +// GetWriteSyncer +func GetWriteSyncer() (zapcore.WriteSyncer, error) { + fileWriter, err := zaprotatelogs.New( + path.Join(CONFIG.Director, "%Y-%m-%d.log"), + zaprotatelogs.WithLinkName(CONFIG.LinkName), + zaprotatelogs.WithMaxAge(7*24*time.Hour), + zaprotatelogs.WithRotationTime(24*time.Hour), + ) + if CONFIG.LogInConsole { + return zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(fileWriter)), err + } + return zapcore.AddSync(fileWriter), err +} diff --git a/core/server/zap_server/rotatelogs_linux.go b/core/server/zap_server/rotatelogs_linux.go new file mode 100644 index 0000000..f8ce30b --- /dev/null +++ b/core/server/zap_server/rotatelogs_linux.go @@ -0,0 +1,23 @@ +package zap_server + +import ( + zaprotatelogs "github.com/lestrrat-go/file-rotatelogs" + "go.uber.org/zap/zapcore" + "os" + "path" + "time" +) + +// GetWriteSyncer +func GetWriteSyncer() (zapcore.WriteSyncer, error) { + fileWriter, err := zaprotatelogs.New( + path.Join(CONFIG.Director, "%Y-%m-%d.log"), + zaprotatelogs.WithLinkName(CONFIG.LinkName), + zaprotatelogs.WithMaxAge(7*24*time.Hour), + zaprotatelogs.WithRotationTime(24*time.Hour), + ) + if CONFIG.LogInConsole { + return zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(fileWriter)), err + } + return zapcore.AddSync(fileWriter), err +} diff --git a/core/server/zap_server/rotatelogs_windows.go b/core/server/zap_server/rotatelogs_windows.go new file mode 100644 index 0000000..bc53fcc --- /dev/null +++ b/core/server/zap_server/rotatelogs_windows.go @@ -0,0 +1,23 @@ +package zap_server + +import ( + "os" + "path" + "time" + + zaprotatelogs "github.com/lestrrat-go/file-rotatelogs" + "go.uber.org/zap/zapcore" +) + +// GetWriteSyncer +func GetWriteSyncer() (zapcore.WriteSyncer, error) { + fileWriter, err := zaprotatelogs.New( + path.Join(CONFIG.Director, "%Y-%m-%d.log"), + zaprotatelogs.WithMaxAge(7*24*time.Hour), + zaprotatelogs.WithRotationTime(24*time.Hour), + ) + if CONFIG.LogInConsole { + return zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(fileWriter)), err + } + return zapcore.AddSync(fileWriter), err +}