This commit is contained in:
新亮
2021-05-22 16:05:51 +08:00
parent a6947b59ac
commit 6d9a07f5dd
21 changed files with 246 additions and 145 deletions

View File

@@ -79,9 +79,9 @@
</header>
<div class="card-body">
<p class="h6">
<span class="badge badge-pill badge-warning">v1.26</span>
<span class="badge badge-pill badge-warning">v1.2.6</span>
->
<span class="badge badge-pill badge-warning">v1.27</span>
<span class="badge badge-pill badge-warning">v1.2.7</span>
</p>
<p>......</p>

View File

@@ -17,13 +17,13 @@ const (
ProjectInstallMark = "INSTALL.lock"
// 登录验证 TokenHeader 中传递的参数
LoginToken = "Token"
HeaderLoginToken = "Token"
// 签名验证 TokenHeader 中传递的参数
SignToken = "Authorization"
HeaderSignToken = "Authorization"
// 签名验证 DateHeader 中传递的参数
SignTokenDate = "Authorization-Date"
HeaderSignTokenDate = "Authorization-Date"
// Redis Key 前缀 - 防止重复提交
RedisKeyPrefixRequestID = ProjectName + ":request-id:"

View File

@@ -60,7 +60,15 @@ func (h *handler) Detail() core.HandlerFunc {
}
var menuData []admin_service.ListMyMenuData
_ = json.Unmarshal([]byte(menuCacheData), &menuData)
err = json.Unmarshal([]byte(menuCacheData), &menuData)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminDetailError,
code.Text(code.AdminDetailError)).WithErr(err),
)
return
}
res.Username = info.Username
res.Nickname = info.Nickname

View File

@@ -74,8 +74,20 @@ func (h *handler) Login() core.HandlerFunc {
token := password.GenerateLoginToken(info.Id)
adminCacheData := &struct {
Id int32 `json:"id"` // 主键ID
Username string `json:"username"` // 用户名
Nickname string `json:"nickname"` // 昵称
Mobile string `json:"mobile"` // 手机号
}{
Id: info.Id,
Username: info.Username,
Nickname: info.Nickname,
Mobile: info.Mobile,
}
// 用户信息
adminJsonInfo, _ := json.Marshal(info)
adminJsonInfo, _ := json.Marshal(adminCacheData)
// 将用户信息记录到 Redis 中
err = h.cache.Set(configs.RedisKeyPrefixLoginUser+token, string(adminJsonInfo), time.Hour*24, cache.WithTrace(c.Trace()))

View File

@@ -29,7 +29,7 @@ func (h *handler) Logout() core.HandlerFunc {
res := new(logoutResponse)
res.Username = c.UserName()
if !h.cache.Del(configs.RedisKeyPrefixLoginUser+c.GetHeader(configs.LoginToken), cache.WithTrace(c.Trace())) {
if !h.cache.Del(configs.RedisKeyPrefixLoginUser+c.GetHeader(configs.HeaderLoginToken), cache.WithTrace(c.Trace())) {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminLogOutError,

View File

@@ -2,7 +2,6 @@ package admin_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/admin_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/menu_action_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
@@ -26,7 +25,7 @@ type Service interface {
CreateMenu(ctx core.Context, menuData *CreateMenuData) (err error)
ListMenu(ctx core.Context, searchData *SearchListMenuData) (menuData []ListMenuData, err error)
MyMenu(ctx core.Context, searchData *SearchMyMenuData) (menuData []ListMyMenuData, err error)
MyAction(ctx core.Context, searchData *SearchMyActionData) (actionData []*menu_action_repo.MenuAction, err error)
MyAction(ctx core.Context, searchData *SearchMyActionData) (actionData []MyActionData, err error)
}
type service struct {

View File

@@ -11,7 +11,14 @@ type SearchMyActionData struct {
AdminId int32 `json:"admin_id"` // 管理员ID
}
func (s *service) MyAction(ctx core.Context, searchData *SearchMyActionData) (actionData []*menu_action_repo.MenuAction, err error) {
type MyActionData struct {
Id int32 // 主键
MenuId int32 // 菜单栏ID
Method string // 请求方式
Api string // 请求地址
}
func (s *service) MyAction(ctx core.Context, searchData *SearchMyActionData) (actionData []MyActionData, err error) {
adminMenuQb := admin_menu_repo.NewQueryBuilder()
if searchData.AdminId != 0 {
adminMenuQb.WhereAdminId(db_repo.EqualPredicate, searchData.AdminId)
@@ -36,10 +43,27 @@ func (s *service) MyAction(ctx core.Context, searchData *SearchMyActionData) (ac
actionQb := menu_action_repo.NewQueryBuilder()
actionQb.WhereIsDeleted(db_repo.EqualPredicate, -1)
actionQb.WhereMenuIdIn(menuIds)
actionData, err = actionQb.QueryAll(s.db.GetDbR().WithContext(ctx.RequestContext()))
actionListData, err := actionQb.QueryAll(s.db.GetDbR().WithContext(ctx.RequestContext()))
if err != nil {
return nil, err
}
if len(actionListData) <= 0 {
return
}
actionData = make([]MyActionData, len(actionListData))
for k, v := range actionListData {
data := MyActionData{
Id: v.Id,
MenuId: v.MenuId,
Method: v.Method,
Api: v.Api,
}
actionData[k] = data
}
return
}

View File

@@ -28,55 +28,65 @@ type cacheApiData struct {
func (s *service) DetailByKey(ctx core.Context, key string) (cacheData *CacheAuthorizedData, err error) {
// 查询缓存
cacheKey := configs.RedisKeyPrefixSignature + key
value, err := s.cache.Get(cacheKey, cache.WithTrace(ctx.RequestContext().Trace))
cacheData = new(CacheAuthorizedData)
if err == nil && json.Unmarshal([]byte(value), cacheData) == nil {
return
}
if !s.cache.Exists(cacheKey) {
// 查询调用方信息
authorizedInfo, err := authorized_repo.NewQueryBuilder().
WhereIsDeleted(db_repo.EqualPredicate, -1).
WhereBusinessKey(db_repo.EqualPredicate, key).
First(s.db.GetDbR().WithContext(ctx.RequestContext()))
// 查询调用方信息
authorizedInfo, err := authorized_repo.NewQueryBuilder().
WhereIsDeleted(db_repo.EqualPredicate, -1).
WhereBusinessKey(db_repo.EqualPredicate, key).
First(s.db.GetDbR().WithContext(ctx.RequestContext()))
if err != nil {
return nil, err
}
// 查询调用方授权 API 信息
authorizedApiInfo, err := authorized_api_repo.NewQueryBuilder().
WhereIsDeleted(db_repo.EqualPredicate, -1).
WhereBusinessKey(db_repo.EqualPredicate, key).
OrderById(false).
QueryAll(s.db.GetDbR().WithContext(ctx.RequestContext()))
if err != nil {
return nil, err
}
// 设置缓存 data
cacheData = new(CacheAuthorizedData)
cacheData.Key = key
cacheData.Secret = authorizedInfo.BusinessSecret
cacheData.IsUsed = authorizedInfo.IsUsed
cacheData.Apis = make([]cacheApiData, len(authorizedApiInfo))
for k, v := range authorizedApiInfo {
data := cacheApiData{
Method: v.Method,
Api: v.Api,
if err != nil {
return nil, err
}
cacheData.Apis[k] = data
// 查询调用方授权 API 信息
authorizedApiInfo, err := authorized_api_repo.NewQueryBuilder().
WhereIsDeleted(db_repo.EqualPredicate, -1).
WhereBusinessKey(db_repo.EqualPredicate, key).
OrderById(false).
QueryAll(s.db.GetDbR().WithContext(ctx.RequestContext()))
if err != nil {
return nil, err
}
// 设置缓存 data
cacheData = new(CacheAuthorizedData)
cacheData.Key = key
cacheData.Secret = authorizedInfo.BusinessSecret
cacheData.IsUsed = authorizedInfo.IsUsed
cacheData.Apis = make([]cacheApiData, len(authorizedApiInfo))
for k, v := range authorizedApiInfo {
data := cacheApiData{
Method: v.Method,
Api: v.Api,
}
cacheData.Apis[k] = data
}
cacheDataByte, _ := json.Marshal(cacheData)
err = s.cache.Set(cacheKey, string(cacheDataByte), time.Hour*24, cache.WithTrace(ctx.Trace()))
if err != nil {
return nil, err
}
return cacheData, nil
}
cacheDataByte, _ := json.Marshal(cacheData)
value, err := s.cache.Get(cacheKey, cache.WithTrace(ctx.RequestContext().Trace))
if err != nil {
return nil, err
}
err = s.cache.Set(cacheKey, string(cacheDataByte), time.Hour*24, cache.WithTrace(ctx.Trace()))
cacheData = new(CacheAuthorizedData)
err = json.Unmarshal([]byte(value), cacheData)
if err != nil {
return nil, err
}
return
}

View File

@@ -440,11 +440,20 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
}
decodedURL, _ := url.QueryUnescape(ctx.Request.URL.RequestURI())
// ctx.Request.Header精简 Header 参数
traceHeader := map[string]string{
"Content-Type": ctx.GetHeader("Content-Type"),
configs.HeaderLoginToken: ctx.GetHeader(configs.HeaderLoginToken),
configs.HeaderSignToken: ctx.GetHeader(configs.HeaderSignToken),
configs.HeaderSignTokenDate: ctx.GetHeader(configs.HeaderSignTokenDate),
}
t.WithRequest(&trace.Request{
TTL: "un-limit",
Method: ctx.Request.Method,
DecodedURL: decodedURL,
Header: ctx.Request.Header,
Header: traceHeader,
Body: string(context.RawData()),
})

View File

@@ -16,7 +16,7 @@ var metricsRequestsTotal = prometheus.NewCounterVec(
Namespace: namespace,
Subsystem: subsystem,
Name: "requests_total",
Help: "request(s) total",
Help: "request(ms) total",
},
[]string{"method", "path"},
)
@@ -27,9 +27,9 @@ var metricsRequestsCost = prometheus.NewHistogramVec(
Namespace: namespace,
Subsystem: subsystem,
Name: "requests_cost",
Help: "request(s) cost seconds",
Help: "request(ms) cost milliseconds",
},
[]string{"method", "path", "success", "http_code", "business_code", "cost_seconds", "trace_id"},
[]string{"method", "path", "success", "http_code", "business_code", "cost_milliseconds", "trace_id"},
)
func init() {
@@ -44,12 +44,12 @@ func RecordMetrics(method, uri string, success bool, httpCode, businessCode int,
}).Inc()
metricsRequestsCost.With(prometheus.Labels{
"method": method,
"path": uri,
"success": cast.ToString(success),
"http_code": cast.ToString(httpCode),
"business_code": cast.ToString(businessCode),
"cost_seconds": cast.ToString(costSeconds),
"trace_id": traceId,
"method": method,
"path": uri,
"success": cast.ToString(success),
"http_code": cast.ToString(httpCode),
"business_code": cast.ToString(businessCode),
"cost_milliseconds": cast.ToString(costSeconds * 1000),
"trace_id": traceId,
}).Observe(costSeconds)
}

View File

@@ -8,9 +8,10 @@ import (
"fmt"
)
const salt = "qkhPAGA13HocW3GAEWwb"
const defaultPassword = "123456"
const (
saltPassword = "qkhPAGA13HocW3GAEWwb"
defaultPassword = "123456"
)
func GeneratePassword(str string) (password string) {
// md5
@@ -19,7 +20,7 @@ func GeneratePassword(str string) (password string) {
mByte := m.Sum(nil)
// hmac
h := hmac.New(sha256.New, []byte(salt))
h := hmac.New(sha256.New, []byte(saltPassword))
h.Write(mByte)
password = hex.EncodeToString(h.Sum(nil))
@@ -38,7 +39,7 @@ func ResetPassword() (password string) {
func GenerateLoginToken(id int32) (token string) {
m := md5.New()
m.Write([]byte(fmt.Sprintf("%d%s", id, salt)))
m.Write([]byte(fmt.Sprintf("%d%s", id, saltPassword)))
token = hex.EncodeToString(m.Sum(nil))
return

View File

@@ -6,7 +6,7 @@ import (
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/menu_action_repo"
"github.com/xinliangnote/go-gin-api/internal/api/service/admin_service"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
@@ -54,8 +54,16 @@ func (m *middleware) RBAC() core.HandlerFunc {
return
}
var actions []menu_action_repo.MenuAction
_ = json.Unmarshal([]byte(actionData), &actions)
var actions []admin_service.MyActionData
err = json.Unmarshal([]byte(actionData), &actions)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusUnauthorized,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(err),
)
return
}
if len(actions) > 0 {
table := urltable.NewTable()

View File

@@ -14,6 +14,8 @@ import (
"github.com/pkg/errors"
)
const reSubmitMark = "1"
func (m *middleware) Resubmit() core.HandlerFunc {
return func(c core.Context) {
cfg := configs.Get().URLToken
@@ -30,7 +32,7 @@ func (m *middleware) Resubmit() core.HandlerFunc {
redisKey := configs.RedisKeyPrefixRequestID + tokenString
if !m.cache.Exists(redisKey) {
err = m.cache.Set(redisKey, "1", time.Minute*cfg.ExpireDuration)
err = m.cache.Set(redisKey, reSubmitMark, time.Minute*cfg.ExpireDuration)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
@@ -53,7 +55,7 @@ func (m *middleware) Resubmit() core.HandlerFunc {
return
}
if redisValue == "1" {
if redisValue == reSubmitMark {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ResubmitMsg,

View File

@@ -14,7 +14,12 @@ import (
"github.com/xinliangnote/go-gin-api/pkg/urltable"
)
const ttl = time.Minute * 2 // 签名超时时间 2 分钟
const (
ttl = time.Minute * 2 // 签名超时时间 2 分钟
minLength = 2 // split space 最小长度
notUsed = -1 // -1 表示被禁用
)
var whiteListPath = map[string]bool{
"/login/web": true,
@@ -23,7 +28,7 @@ var whiteListPath = map[string]bool{
func (m *middleware) Signature() core.HandlerFunc {
return func(c core.Context) {
// 签名信息
authorization := c.GetHeader(configs.SignToken)
authorization := c.GetHeader(configs.HeaderSignToken)
if authorization == "" {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
@@ -34,7 +39,7 @@ func (m *middleware) Signature() core.HandlerFunc {
}
// 时间信息
date := c.GetHeader(configs.SignTokenDate)
date := c.GetHeader(configs.HeaderSignTokenDate)
if date == "" {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
@@ -46,7 +51,7 @@ func (m *middleware) Signature() core.HandlerFunc {
// 通过签名信息获取 key
authorizationSplit := strings.Split(authorization, " ")
if len(authorizationSplit) < 2 {
if len(authorizationSplit) < minLength {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.SignatureError,
@@ -67,8 +72,7 @@ func (m *middleware) Signature() core.HandlerFunc {
return
}
// 验证 cache 是否被调用
if data.IsUsed == -1 {
if data.IsUsed == notUsed {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.SignatureError,

View File

@@ -13,7 +13,7 @@ import (
)
func (m *middleware) Token(ctx core.Context) (userId int64, userName string, err errno.Error) {
token := ctx.GetHeader(configs.LoginToken)
token := ctx.GetHeader(configs.HeaderLoginToken)
if token == "" {
err = errno.NewError(
http.StatusUnauthorized,
@@ -48,7 +48,16 @@ func (m *middleware) Token(ctx core.Context) (userId int64, userName string, err
}
var userData userInfo
_ = json.Unmarshal([]byte(cacheData), &userData)
jsonErr := json.Unmarshal([]byte(cacheData), &userData)
if jsonErr != nil {
errno.NewError(
http.StatusUnauthorized,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(jsonErr)
return
}
userId = userData.Id
userName = userData.Username

View File

@@ -22,51 +22,56 @@ type codeViewResponse struct {
BusinessCodes []codes
}
const (
codeFile = "./internal/api/code/code.go"
minBusinessCode = 20000
)
func (h *handler) CodeView() core.HandlerFunc {
return func(c core.Context) {
fs := token.NewFileSet()
filePath := "./internal/api/code/code.go"
parsedFile, err := decorator.ParseFile(fs, filePath, nil, 0)
if err != nil {
log.Fatalf("parsing package: %s: %s\n", filePath, err)
fs := token.NewFileSet()
parsedFile, err := decorator.ParseFile(fs, codeFile, nil, 0)
if err != nil {
log.Fatalf("parsing package: %s: %s\n", codeFile, err)
}
var (
systemCodes []codes
businessCodes []codes
)
dst.Inspect(parsedFile, func(n dst.Node) bool {
// GenDecl 代表除函数以外的所有声明,即 import、const、var 和 type
decl, ok := n.(*dst.GenDecl)
if !ok || decl.Tok != token.CONST {
return true
}
var (
systemCodes []codes
businessCodes []codes
)
dst.Inspect(parsedFile, func(n dst.Node) bool {
decl, ok := n.(*dst.GenDecl)
if !ok || decl.Tok != token.CONST {
return true
for _, spec := range decl.Specs {
valueSpec, _ok := spec.(*dst.ValueSpec)
if !_ok {
continue
}
for _, spec := range decl.Specs {
valueSpec, _ok := spec.(*dst.ValueSpec)
if !_ok {
continue
}
codeInt := cast.ToInt(valueSpec.Values[0].(*dst.BasicLit).Value)
if codeInt < 20000 {
systemCodes = append(systemCodes, codes{
Code: codeInt,
Message: code.Text(codeInt),
})
} else {
businessCodes = append(businessCodes, codes{
Code: codeInt,
Message: code.Text(codeInt),
})
}
codeInt := cast.ToInt(valueSpec.Values[0].(*dst.BasicLit).Value)
if codeInt < minBusinessCode {
systemCodes = append(systemCodes, codes{
Code: codeInt,
Message: code.Text(codeInt),
})
} else {
businessCodes = append(businessCodes, codes{
Code: codeInt,
Message: code.Text(codeInt),
})
}
return true
})
}
return true
})
return func(c core.Context) {
obj := new(codeViewResponse)
obj.BusinessCodes = businessCodes
obj.SystemCodes = systemCodes

View File

@@ -14,9 +14,10 @@ type gormExecuteRequest struct {
Tables string `form:"tables"`
}
const gormgenSh = "./scripts/gormgen.sh"
func (h *handler) GormExecute() core.HandlerFunc {
return func(c core.Context) {
req := new(gormExecuteRequest)
if err := c.ShouldBindPostForm(req); err != nil {
c.Payload("参数传递有误")
@@ -24,7 +25,7 @@ func (h *handler) GormExecute() core.HandlerFunc {
}
mysqlConf := configs.Get().MySQL.Read
shellPath := fmt.Sprintf("./scripts/gormgen.sh %s %s %s %s %s", mysqlConf.Addr, mysqlConf.User, mysqlConf.Pass, mysqlConf.Name, req.Tables)
shellPath := fmt.Sprintf("%s %s %s %s %s %s", gormgenSh, mysqlConf.Addr, mysqlConf.User, mysqlConf.Pass, mysqlConf.Name, req.Tables)
// runtime.GOOS = linux or darwin
command := exec.Command("/bin/bash", "-c", shellPath)

View File

@@ -5,6 +5,8 @@ import (
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"go.uber.org/zap"
)
func (h *handler) GormView() core.HandlerFunc {
@@ -21,9 +23,20 @@ func (h *handler) GormView() core.HandlerFunc {
sqlTables := fmt.Sprintf("SELECT `table_name`,`table_comment` FROM `information_schema`.`tables` WHERE `table_schema`= '%s'", mysqlConf.Name)
rows, err := h.db.GetDbR().Raw(sqlTables).Rows()
if err != nil {
h.logger.Error("rows err", zap.Error(err))
c.HTML("generator_gorm", tableCollect)
return
}
err = rows.Err()
if err != nil {
h.logger.Error("rows err", zap.Error(err))
c.HTML("generator_gorm", tableCollect)
return
}
defer rows.Close()
for rows.Next() {

View File

@@ -13,6 +13,8 @@ type handlerExecuteRequest struct {
Name string `form:"name"`
}
const handlergenSh = "./scripts/handlergen.sh"
func (h *handler) HandlerExecute() core.HandlerFunc {
return func(c core.Context) {
req := new(handlerExecuteRequest)
@@ -21,7 +23,7 @@ func (h *handler) HandlerExecute() core.HandlerFunc {
return
}
shellPath := fmt.Sprintf("./scripts/handlergen.sh %s", req.Name)
shellPath := fmt.Sprintf("%s %s", handlergenSh, req.Name)
// runtime.GOOS = linux or darwin
command := exec.Command("/bin/bash", "-c", shellPath)

View File

@@ -56,22 +56,25 @@ func (h *handler) LogsView() core.HandlerFunc {
for i := 0; i < logSize; i++ {
content, _ := readLineFromEnd.ReadLine()
var logParse logParseData
_ = json.Unmarshal(content, &logParse)
data := logData{
Content: string(content),
Level: logParse.Level,
Time: logParse.Time,
Path: logParse.Path,
Method: logParse.Method,
Msg: logParse.Msg,
HTTPCode: logParse.HTTPCode,
TraceID: logParse.TraceID,
CostSeconds: logParse.CostSeconds,
}
if string(content) != "" {
var logParse logParseData
err = json.Unmarshal(content, &logParse)
if err != nil {
h.logger.Error("NewReadLineFromEnd json Unmarshal err", zap.Error(err))
}
data := logData{
Content: string(content),
Level: logParse.Level,
Time: logParse.Time,
Path: logParse.Path,
Method: logParse.Method,
Msg: logParse.Msg,
HTTPCode: logParse.HTTPCode,
TraceID: logParse.TraceID,
CostSeconds: logParse.CostSeconds,
}
obj.Logs[i] = data
}
}

11
main.go
View File

@@ -31,6 +31,7 @@ import (
func main() {
// 初始化 logger
loggers, err := logger.NewJSONLogger(
logger.WithDisableConsole(),
logger.WithField("domain", fmt.Sprintf("%s[%s]", configs.ProjectName, env.Active().Value())),
logger.WithTimeLayout("2006-01-02 15:04:05"),
logger.WithFileP(configs.ProjectLogFile),
@@ -66,8 +67,6 @@ func main() {
if err := server.Shutdown(ctx); err != nil {
loggers.Error("server shutdown err", zap.Error(err))
} else {
loggers.Info("server shutdown success")
}
},
@@ -76,14 +75,10 @@ func main() {
if s.Db != nil {
if err := s.Db.DbWClose(); err != nil {
loggers.Error("dbw close err", zap.Error(err))
} else {
loggers.Info("dbw close success")
}
if err := s.Db.DbRClose(); err != nil {
loggers.Error("dbr close err", zap.Error(err))
} else {
loggers.Info("dbr close success")
}
}
},
@@ -93,8 +88,6 @@ func main() {
if s.Cache != nil {
if err := s.Cache.Close(); err != nil {
loggers.Error("cache close err", zap.Error(err))
} else {
loggers.Info("cache close success")
}
}
},
@@ -104,8 +97,6 @@ func main() {
if s.GrpClient != nil {
if err := s.GrpClient.Conn().Close(); err != nil {
loggers.Error("gRPC client close err", zap.Error(err))
} else {
loggers.Info("gRPC client close success")
}
}
},