diff --git a/api/err/wechat.proto b/api/err/wechat.proto index 0190269..4497fd4 100644 --- a/api/err/wechat.proto +++ b/api/err/wechat.proto @@ -35,4 +35,5 @@ enum WechatErr{ WechatNaturalPersonRuleBlocked = 25 [(errors.code) = 500]; WechatResourceNotExists = 26 [(errors.code) = 500]; WechatFrequencyLimited = 27 [(errors.code) = 500]; + WechatAccountFail = 28 [(errors.code) = 500]; } diff --git a/api/v1/cmb_cpn.proto b/api/v1/cmb_cpn.proto index b92b7a5..ae03540 100644 --- a/api/v1/cmb_cpn.proto +++ b/api/v1/cmb_cpn.proto @@ -29,6 +29,12 @@ service Cmb { }; } + rpc OrderRetry (OrderRetryRequest) returns (Empty) { + option (google.api.http) = { + post: "/voucher/cmb/v1/orderRetry", + body: "*" + }; + } rpc OrderMock (CmbOrderRequest) returns (CmbRequest) { option (google.api.http) = { @@ -56,12 +62,10 @@ service Cmb { }; } - rpc Test (Empty) returns (Empty) { - option (google.api.http) = { - post: "/voucher/cmb/v1/test", - body: "*" - }; - } +} + +message OrderRetryRequest { + repeated string transactionIds = 1 [json_name = "transactionIds"]; } message CmbRequest { diff --git a/cmd/server/main.go b/cmd/server/main.go index b43a77a..c0f3141 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -60,6 +60,7 @@ func newApp( consumerServer *server.Consumer, cronServer *server.CronServer, wechatNotifyConsumer *server.WechatNotifyConsumer, + rdbConsumer *server.RdbConsumer, ) *kratos.App { return kratos.New( kratos.ID(id), @@ -71,6 +72,7 @@ func newApp( consumerServer, cronServer, wechatNotifyConsumer, + rdbConsumer, ), ) } diff --git a/configs/config.yaml b/configs/config.yaml index 19b20af..95b9bef 100644 --- a/configs/config.yaml +++ b/configs/config.yaml @@ -84,6 +84,20 @@ cron: isOpen: true #是否启动 true/false command: "0 0 1 * * ?" # 每天凌晨1点执行一次 +rdsMQ: + wechatQuery: #发放结算 + name: "wechatQuery" + retryNum: 1 #重试次数 + numWorkers: 1 #协程数量,不配置默认为10 + waitTime: 1s #处理完成后等待时间 + isOpen: true #是否启动消费 true/false + orderRetry: #发放结算 + name: "orderRetry" + retryNum: 1 #重试次数 + numWorkers: 1 #协程数量,不配置默认为10 + waitTime: 1s #处理完成后等待时间 + isOpen: true #是否启动消费 true/false + #配置日志 logs: business: business.log #业务日志路径:如果不写日志,则不配置或配置为空 diff --git a/configs/config_test.yaml b/configs/config_test.yaml index 19b20af..9718289 100644 --- a/configs/config_test.yaml +++ b/configs/config_test.yaml @@ -84,6 +84,14 @@ cron: isOpen: true #是否启动 true/false command: "0 0 1 * * ?" # 每天凌晨1点执行一次 +rdsMQ: + wechatQuery: #发放结算 + name: "wechatQuery" + retryNum: 1 #重试次数 + numWorkers: 1 #协程数量,不配置默认为10 + waitTime: 1s #处理完成后等待时间 + isOpen: true #是否启动消费 true/false + #配置日志 logs: business: business.log #业务日志路径:如果不写日志,则不配置或配置为空 diff --git a/internal/biz/alarm.go b/internal/biz/alarm.go new file mode 100644 index 0000000..09945e2 --- /dev/null +++ b/internal/biz/alarm.go @@ -0,0 +1,69 @@ +package biz + +import ( + "context" + "fmt" + "github.com/redis/go-redis/v9" + "voucher/internal/biz/bo" + "voucher/internal/biz/vo" + "voucher/internal/pkg/lock" +) + +func (v *VoucherBiz) alarm(ctx context.Context, order *bo.OrderBo, errMsg string) error { + + // 1小时 内 指定的批次号 发放 发生错误 预警 + c := vo.OrderConsumeFailAlarmKey.BuildCache([]string{order.ProductNo}) + + _, err := v.rdb.Rdb.Get(ctx, c.Key).Result() + + if err == nil { + // 缓存存在,直接返回 + return nil + } + + if err != redis.Nil { + return fmt.Errorf(fmt.Sprintf("alarm 获取redis缓存%s异常:%v", c.Key, err)) + } + + cl := vo.OrderConsumeFailAlarmLockKey.BuildCache([]string{order.ProductNo}) + + return lock.NewMutex(v.rdb.Rdb, cl.TTL).Lock(ctx, cl.Key, func(ctx context.Context) error { + // 二次获取,判定处理,以免获取锁后又执行了一次 + + cacheValue, err3 := v.rdb.Rdb.Get(ctx, c.Key).Result() + + if err3 != nil && err3 != redis.Nil { + return fmt.Errorf(fmt.Sprintf("alarm 二次获取redis缓存%s异常:%v", c.Key, err)) + } + + if len(cacheValue) > 0 { + return nil // 有直接返回 + } + + // 通知 + if err = v.DingMixRepo.SendMarkdownMessage(ctx, "异常通知", v.alarmText(ctx, order, errMsg)); err != nil { + return err + } + + if err = v.rdb.Rdb.Set(ctx, c.Key, order.ProductNo, c.TTL).Err(); err != nil { + return fmt.Errorf(fmt.Sprintf("设置redis缓存%s异常:%v", c.Key, err)) + } + + return nil + }) +} + +func (v *VoucherBiz) alarmText(_ context.Context, order *bo.OrderBo, errMsg string) string { + + remarks := fmt.Sprintf("订单号:%s,商品编号:%s,原因:%s", order.OrderNo, order.ProductNo, errMsg) + + msg := "# " + + "

立减金发放平台报警通知

" + + "
\n" + + "" + + "不好了,订单发放发生异常了" + + "[%s]请尽快处理@相关人员。" + + "" + + return fmt.Sprintf(msg, remarks) +} diff --git a/internal/biz/bo/wechat_notify_register_tag_bo.go b/internal/biz/bo/wechat_notify_register_tag_bo.go index 68a58e2..22531d6 100644 --- a/internal/biz/bo/wechat_notify_register_tag_bo.go +++ b/internal/biz/bo/wechat_notify_register_tag_bo.go @@ -1,6 +1,9 @@ package bo -import "time" +import ( + "time" + "voucher/internal/biz/vo" +) // WechatNotifyRegisterTagBo 领域实体Bo结构,字段和模型字段保持一致 type WechatNotifyRegisterTagBo struct { @@ -8,7 +11,7 @@ type WechatNotifyRegisterTagBo struct { StockID string StockCreatorMchID string Tag string - Status uint8 + Status vo.WechatNotifyRegisterTagStatus Remark string CreateTime *time.Time UpdateTime *time.Time diff --git a/internal/biz/cron_notice.go b/internal/biz/cron_notice.go index fc6f45f..276eb7a 100644 --- a/internal/biz/cron_notice.go +++ b/internal/biz/cron_notice.go @@ -85,10 +85,10 @@ func (v *VoucherBiz) isCanNotice(ctx context.Context) error { // 二次获取,判定处理,以免获取锁后又执行了一次 - cacheValue, err := v.rdb.Rdb.Get(ctx, cache.Key).Result() + cacheValue, err2 := v.rdb.Rdb.Get(ctx, cache.Key).Result() - if err != nil && err != redis.Nil { - return fmt.Errorf(fmt.Sprintf("notice 二次获取redis缓存%s异常:%v", cache.Key, err)) + if err2 != nil && err2 != redis.Nil { + return fmt.Errorf(fmt.Sprintf("notice 二次获取redis缓存%s异常:%v", cache.Key, err2)) } if len(cacheValue) > 0 { @@ -113,7 +113,7 @@ func (v *VoucherBiz) notice(ctx context.Context, order *bo.OrderBo) error { } if order.Status == status { - log.Warnf("notice 券状态未改变:%s,忽略不处理,orderNo:%s", order.Status.GetText(), order.OrderNo) + //log.Warnf("notice 券状态未改变:%s,忽略不处理,orderNo:%s", order.Status.GetText(), order.OrderNo) return nil } @@ -140,7 +140,7 @@ func (v *VoucherBiz) notice(ctx context.Context, order *bo.OrderBo) error { func (v *VoucherBiz) cmbNotice(ctx context.Context, order *bo.OrderBo, orderNotify *bo.OrderNotifyBo) error { if !orderNotify.Event.CanNotify() { - log.Warnf("notice 券状态:%s,忽略不通知,orderNo:%s", orderNotify.Event.GetText(), order.OrderNo) + //log.Warnf("notice 券状态:%s,忽略不通知,orderNo:%s", orderNotify.Event.GetText(), order.OrderNo) return nil } diff --git a/internal/biz/notify_retry_consume.go b/internal/biz/notify_retry.go similarity index 100% rename from internal/biz/notify_retry_consume.go rename to internal/biz/notify_retry.go diff --git a/internal/biz/order.go b/internal/biz/order.go index d9de653..96df93d 100644 --- a/internal/biz/order.go +++ b/internal/biz/order.go @@ -3,14 +3,43 @@ package biz import ( "context" "fmt" - "github.com/go-kratos/kratos/v2/log" - "github.com/redis/go-redis/v9" err2 "voucher/api/err" "voucher/internal/biz/bo" "voucher/internal/biz/vo" - "voucher/internal/pkg/lock" ) +func (v *VoucherBiz) CmbOrder(ctx context.Context, req *bo.OrderCreateReqBo) (orderNo string, err error) { + + order, err3 := v.GetByOutBizNo(ctx, req) + if err3 != nil { + return "", err3 + } + + if order != nil { + + if order.Status.IsFail() || order.Status.IsIng() { + + if err4 := v.orderRetry(ctx, order); err4 != nil { + return "", err4 + } + } + + return order.OrderNo, err + } + + product, err3 := v.ProductRepo.GetByProductNo(ctx, req.ProductNo) + if err3 != nil { + return "", err3 + } + + order, err3 = v.order(ctx, req, product) + if err3 != nil { + return "", err3 + } + + return order.OrderNo, nil +} + func (v *VoucherBiz) order(ctx context.Context, req *bo.OrderCreateReqBo, product *bo.ProductBo) (*bo.OrderBo, error) { order, err := v.create(ctx, req, product) @@ -18,21 +47,19 @@ func (v *VoucherBiz) order(ctx context.Context, req *bo.OrderCreateReqBo, produc return nil, err } - // 注册通知标签 order.MerchantNo 批次创建商户, order.BatchNo 商品批次号 - if err = v.registerNotifyTag(ctx, order.MerchantNo, order.BatchNo); err != nil { - return nil, err - } - - // 真实发放 voucherNo, err := v.WechatCpnRepo.Order(ctx, order) if err != nil { - if err3 := v.fail(ctx, order, err.Error()); err3 != nil { + if err3 := v.fail(ctx, order, err); err3 != nil { return nil, err3 } return nil, err } - return order, v.success(ctx, order, voucherNo) + if err = v.success(ctx, order, voucherNo); err != nil { + return nil, err + } + + return order, nil } func (v *VoucherBiz) orderRetry(ctx context.Context, order *bo.OrderBo) error { @@ -40,7 +67,7 @@ func (v *VoucherBiz) orderRetry(ctx context.Context, order *bo.OrderBo) error { voucherNo, err := v.WechatCpnRepo.Order(ctx, order) if err != nil { - if err3 := v.fail(ctx, order, err.Error()); err3 != nil { + if err3 := v.fail(ctx, order, err); err3 != nil { return err3 } return err @@ -70,85 +97,6 @@ func (v *VoucherBiz) create(ctx context.Context, req *bo.OrderCreateReqBo, produ return v.OrderRepo.Create(ctx, o) } -func (v *VoucherBiz) registerNotifyTag(ctx context.Context, stockCreatorMchID, stockID string) error { - - c := vo.WechatNotifyRegisterTagCacheKey.BuildCache([]string{v.bc.WechatNotifyMQ.Tag, stockCreatorMchID, stockID}) - - _, err := v.rdb.Rdb.Get(ctx, c.Key).Result() - - if err == nil { - // 缓存存在,直接返回 - return nil - } - - if err != redis.Nil { - return fmt.Errorf(fmt.Sprintf("获取redis缓存%s异常:%v", c.Key, err)) - } - - cl := vo.WechatNotifyRegisterTagCacheLockKey.BuildCache([]string{v.bc.WechatNotifyMQ.Tag, stockCreatorMchID, stockID}) - - return lock.NewMutex(v.rdb.Rdb, cl.TTL).Lock(ctx, cl.Key, func(ctx context.Context) error { - // 二次获取,判定处理,以免获取锁后又执行了一次 - - cacheValue, err3 := v.rdb.Rdb.Get(ctx, c.Key).Result() - - if err3 != nil && err3 != redis.Nil { - return fmt.Errorf(fmt.Sprintf("二次获取redis缓存%s异常:%v", c.Key, err)) - } - - if cacheValue != "" { - return nil // 有直接返回 - } - - wechatNotifyTag, err3 := v.WechatNotifyRegisterTagRepo.GetByStockIdAndMchId(ctx, stockCreatorMchID, stockID) - if err3 != nil && !err2.IsDbNotFound(err3) { - return err3 - } - - if wechatNotifyTag != nil { - - if wechatNotifyTag.Tag != v.bc.WechatNotifyMQ.Tag { - return fmt.Errorf("tag不一致,请检查tag配置:%s", wechatNotifyTag.Tag) - } - - return v.setCache(ctx, c, wechatNotifyTag) - } - - wechatNotifyTag, err3 = v.createWechatNotifyRegisterTag(ctx, stockCreatorMchID, stockID) - if err3 != nil { - return err3 - } - - if err = v.WechatCpnRepo.RegisterNotifyTag(ctx, stockID); err != nil { - - return v.WechatNotifyRegisterTagRepo.Fail(ctx, wechatNotifyTag.ID, err.Error()) - } - - if err = v.WechatNotifyRegisterTagRepo.Success(ctx, wechatNotifyTag.ID); err != nil { - return err - } - - return v.setCache(ctx, c, wechatNotifyTag) - }) -} - -func (v *VoucherBiz) createWechatNotifyRegisterTag(ctx context.Context, stockCreatorMchID, stockID string) (*bo.WechatNotifyRegisterTagBo, error) { - return v.WechatNotifyRegisterTagRepo.Create(ctx, &bo.WechatNotifyRegisterTagBo{ - StockID: stockID, - StockCreatorMchID: stockCreatorMchID, - Tag: v.bc.WechatNotifyMQ.Tag, - }) -} - -func (v *VoucherBiz) setCache(ctx context.Context, c *vo.Cache, wechatNotifyTag *bo.WechatNotifyRegisterTagBo) error { - - if err := v.rdb.Rdb.Set(ctx, c.Key, wechatNotifyTag.Tag, c.TTL).Err(); err != nil { - return fmt.Errorf(fmt.Sprintf("设置redis缓存%s异常:%v", c.Key, err)) - } - - return nil -} - func (v *VoucherBiz) ing(ctx context.Context, id uint64) error { return v.OrderRepo.Ing(ctx, id) @@ -159,93 +107,28 @@ func (v *VoucherBiz) success(ctx context.Context, order *bo.OrderBo, voucherNo s return v.OrderRepo.Success(ctx, order.ID, voucherNo) } -func (v *VoucherBiz) fail(ctx context.Context, order *bo.OrderBo, remark string) error { +func (v *VoucherBiz) fail(ctx context.Context, order *bo.OrderBo, errReq error) error { - if err := v.OrderRepo.Fail(ctx, order.ID, remark); err != nil { + if err := v.OrderRepo.Fail(ctx, order.ID, errReq.Error()); err != nil { return err } - return v.alarm(ctx, order, remark) + if err2.IsWechatAccountFail(errReq) || err2.IsWechatUserMaxCoupons(errReq) { + return nil // 过滤调该类型错误通知 + } + + return v.alarm(ctx, order, errReq.Error()) } -func (v *VoucherBiz) alarm(ctx context.Context, order *bo.OrderBo, errMsg string) error { +func (v *VoucherBiz) GetByOutBizNo(ctx context.Context, req *bo.OrderCreateReqBo) (*bo.OrderBo, error) { - // 1小时 内 指定的批次号 发放 发生错误 预警 - c := vo.OrderConsumeFailAlarmKey.BuildCache([]string{order.ProductNo}) + order, err := v.OrderRepo.GetByOutBizNo(ctx, vo.OrderTypeCmb, req.OutBizNo) - _, err := v.rdb.Rdb.Get(ctx, c.Key).Result() - - if err == nil { - // 缓存存在,直接返回 - return nil + if err != nil && !err2.IsDbNotFound(err) { + return nil, err } - if err != redis.Nil { - return fmt.Errorf(fmt.Sprintf("alarm 获取redis缓存%s异常:%v", c.Key, err)) - } - - cl := vo.OrderConsumeFailAlarmLockKey.BuildCache([]string{order.ProductNo}) - - return lock.NewMutex(v.rdb.Rdb, cl.TTL).Lock(ctx, cl.Key, func(ctx context.Context) error { - // 二次获取,判定处理,以免获取锁后又执行了一次 - - cacheValue, err3 := v.rdb.Rdb.Get(ctx, c.Key).Result() - - if err3 != nil && err3 != redis.Nil { - return fmt.Errorf(fmt.Sprintf("alarm 二次获取redis缓存%s异常:%v", c.Key, err)) - } - - if len(cacheValue) > 0 { - return nil // 有直接返回 - } - - // 通知 - if err = v.DingMixRepo.SendMarkdownMessage(ctx, "异常通知", v.alarmText(ctx, order, errMsg)); err != nil { - return err - } - - if err = v.rdb.Rdb.Set(ctx, c.Key, order.ProductNo, c.TTL).Err(); err != nil { - return fmt.Errorf(fmt.Sprintf("设置redis缓存%s异常:%v", c.Key, err)) - } - - return nil - }) -} - -func (v *VoucherBiz) alarmText(_ context.Context, order *bo.OrderBo, errMsg string) string { - - remarks := fmt.Sprintf("订单号:%s,商品编号:%s,原因:%s", order.OrderNo, order.ProductNo, errMsg) - - msg := "# " + - "

立减金发放平台报警通知

" + - "
\n" + - "" + - "不好了,订单发放发生异常了" + - "[%s]请尽快处理@相关人员。" + - "" - - return fmt.Sprintf(msg, remarks) -} - -func (v *VoucherBiz) Query(ctx context.Context, order *bo.OrderBo) error { - - status, err := v.WechatCpnRepo.Query(ctx, order) - if err != nil { - return err - } - - if order.Status == status { - log.Warnf("券状态未改变:%s,忽略不处理,orderNo:%s", order.Status.GetText(), order.OrderNo) - return nil - } - - if err = v.UpdateOrderStatus(ctx, order.ID, status); err != nil { - return err - } - - order.Status = status - - return nil + return order, nil } func (v *VoucherBiz) UpdateOrderStatus(ctx context.Context, orderId uint64, status vo.OrderStatus) error { @@ -265,32 +148,3 @@ func (v *VoucherBiz) UpdateOrderStatus(ctx context.Context, orderId uint64, stat return fmt.Errorf("notice 未知券状态,orderId:%d,statuText:%s", orderId, status.GetText()) } - -func (v *VoucherBiz) OrderQuery(ctx context.Context, orderNo string) (*bo.OrderBo, error) { - - order, err3 := v.OrderRepo.GetByOrderNo(ctx, orderNo) - if err3 != nil { - return nil, err3 - } - - if order == nil || order.ID == 0 { - return nil, fmt.Errorf("订单不存在:%s", orderNo) - } - - return order, nil -} - -func (v *VoucherBiz) QueryOrder(ctx context.Context, orderNo string) (string, error) { - - order, err3 := v.OrderQuery(ctx, orderNo) - if err3 != nil { - return "", err3 - } - - status, err := v.WechatCpnRepo.Query(ctx, order) - if err != nil { - return "", err - } - - return fmt.Sprintf("orderNo:%s,订单状态:%s,微信查询返回状态:%s", orderNo, order.Status.GetText(), status.GetText()), nil -} diff --git a/internal/biz/cmb.go b/internal/biz/product.go similarity index 57% rename from internal/biz/cmb.go rename to internal/biz/product.go index c394d85..9d62b66 100644 --- a/internal/biz/cmb.go +++ b/internal/biz/product.go @@ -4,93 +4,11 @@ import ( "context" "fmt" "time" - err2 "voucher/api/err" v1 "voucher/api/v1" - "voucher/internal/biz/bo" "voucher/internal/biz/vo" "voucher/internal/pkg/lock" ) -func (v *VoucherBiz) GetByOutBizNo(ctx context.Context, req *bo.OrderCreateReqBo) (*bo.OrderBo, error) { - - order, err := v.OrderRepo.GetByOutBizNo(ctx, vo.OrderTypeCmb, req.OutBizNo) - - if err != nil && !err2.IsDbNotFound(err) { - return nil, err - } - - return order, nil -} - -func (v *VoucherBiz) CmbOrder(ctx context.Context, req *bo.OrderCreateReqBo) (orderNo string, err error) { - - order, err3 := v.GetByOutBizNo(ctx, req) - if err3 != nil { - return orderNo, err3 - } - - if order != nil { - - if order.Status.IsFail() { - - if err4 := v.orderRetry(ctx, order); err4 != nil { - return orderNo, err4 - } - } - - orderNo = order.OrderNo - return orderNo, err - } - - product, err3 := v.ProductRepo.GetByProductNo(ctx, req.ProductNo) - if err3 != nil { - return orderNo, err3 - } - - order, err3 = v.order(ctx, req, product) - if err3 != nil { - return orderNo, err3 - } - - orderNo = order.OrderNo - - return orderNo, nil -} - -func (v *VoucherBiz) CmbQuery(ctx context.Context, orderNo string) (resp *v1.CmbQueryReply, err error) { - - c := vo.CmbQueryLockKey.BuildCache([]string{orderNo}) - - err = lock.NewMutex(v.rdb.Rdb, c.TTL).Lock(ctx, c.Key, func(ctx context.Context) error { - - order, err3 := v.OrderRepo.GetByOrderNo(ctx, orderNo) - if err3 != nil { - return err3 - } - - if err = v.Query(ctx, order); err != nil { - return err - } - - status, err3 := order.Status.GetCmbStatusText() - if err3 != nil { - return err3 - } - - resp = &v1.CmbQueryReply{ - Ticket: order.OrderNo, - Status: status.GetValue(), - TransDate: time.Now().Format("20060102150405"), - OrgNo: v.bc.Cmb.OrgNo, - Ext: "", - } - - return nil - }) - - return -} - func (v *VoucherBiz) CmbProductQuery(ctx context.Context, productNo string) (reps *v1.CmbQueryProductReply, err error) { c := vo.CmbProductQueryLockKey.BuildCache([]string{productNo}) diff --git a/internal/biz/query.go b/internal/biz/query.go new file mode 100644 index 0000000..5d8515c --- /dev/null +++ b/internal/biz/query.go @@ -0,0 +1,82 @@ +package biz + +import ( + "context" + "fmt" + "github.com/go-kratos/kratos/v2/log" + "time" + v1 "voucher/api/v1" + "voucher/internal/biz/bo" + "voucher/internal/biz/vo" + "voucher/internal/pkg/lock" +) + +func (v *VoucherBiz) CmbQuery(ctx context.Context, orderNo string) (resp *v1.CmbQueryReply, err error) { + + c := vo.CmbQueryLockKey.BuildCache([]string{orderNo}) + + err = lock.NewMutex(v.rdb.Rdb, c.TTL).Lock(ctx, c.Key, func(ctx context.Context) error { + + order, err3 := v.OrderRepo.GetByOrderNo(ctx, orderNo) + if err3 != nil { + return err3 + } + + if err = v.Query(ctx, order); err != nil { + return err + } + + status, err3 := order.Status.GetCmbStatusText() + if err3 != nil { + return err3 + } + + resp = &v1.CmbQueryReply{ + Ticket: order.OrderNo, + Status: status.GetValue(), + TransDate: time.Now().Format("20060102150405"), + OrgNo: v.bc.Cmb.OrgNo, + Ext: "", + } + + return nil + }) + + return +} + +func (v *VoucherBiz) Query(ctx context.Context, order *bo.OrderBo) error { + + status, err := v.WechatCpnRepo.Query(ctx, order) + if err != nil { + return err + } + + if order.Status == status { + log.Warnf("券状态未改变:%s,忽略不处理,orderNo:%s", order.Status.GetText(), order.OrderNo) + return nil + } + + if err = v.UpdateOrderStatus(ctx, order.ID, status); err != nil { + return err + } + + order.Status = status + + return nil +} + +func (v *VoucherBiz) QueryOrder(ctx context.Context, orderNo string) (string, error) { + + order, err3 := v.OrderRepo.GetByOrderNo(ctx, orderNo) + if err3 != nil { + return "", err3 + } + + status, err := v.WechatCpnRepo.Query(ctx, order) + if err != nil { + return "", err + } + + return fmt.Sprintf("orderNo:%s,订单状态:%s,微信查询返回状态:%s", orderNo, order.Status.GetText(), status.GetText()), nil +} diff --git a/internal/biz/register_tag.go b/internal/biz/register_tag.go new file mode 100644 index 0000000..cb8314d --- /dev/null +++ b/internal/biz/register_tag.go @@ -0,0 +1,102 @@ +package biz + +import ( + "context" + "fmt" + "github.com/redis/go-redis/v9" + err2 "voucher/api/err" + "voucher/internal/biz/bo" + "voucher/internal/biz/vo" + "voucher/internal/pkg/lock" +) + +// RegisterTag 注册通知标签 stock.MchId 批次创建商户, stock.BatchNo 商品批次号 +func (this *VoucherBiz) RegisterTag(ctx context.Context, productNo string) error { + + stock, err := this.ProductRepo.GetByProductNo(ctx, productNo) + if err != nil { + return err + } + + return this.registerNotifyTag(ctx, stock.MchId, stock.BatchNo) +} + +func (v *VoucherBiz) registerNotifyTag(ctx context.Context, stockCreatorMchID, stockID string) error { + + c := vo.WechatNotifyRegisterTagCacheKey.BuildCache([]string{v.bc.WechatNotifyMQ.Tag, stockCreatorMchID, stockID}) + + _, err := v.rdb.Rdb.Get(ctx, c.Key).Result() + + if err == nil { + // 缓存存在,直接返回 + return nil + } + + if err != redis.Nil { + return fmt.Errorf(fmt.Sprintf("获取redis缓存%s异常:%v", c.Key, err)) + } + + cl := vo.WechatNotifyRegisterTagCacheLockKey.BuildCache([]string{v.bc.WechatNotifyMQ.Tag, stockCreatorMchID, stockID}) + + return lock.NewMutex(v.rdb.Rdb, cl.TTL).Lock(ctx, cl.Key, func(ctx context.Context) error { + // 二次获取,判定处理,以免获取锁后又执行了一次 + + cacheValue, err3 := v.rdb.Rdb.Get(ctx, c.Key).Result() + + if err3 != nil && err3 != redis.Nil { + return fmt.Errorf(fmt.Sprintf("二次获取redis缓存%s异常:%v", c.Key, err)) + } + + if cacheValue != "" { + return nil // 有直接返回 + } + + wechatNotifyTag, err3 := v.WechatNotifyRegisterTagRepo.GetByStockIdAndMchId(ctx, stockCreatorMchID, stockID) + if err3 != nil && !err2.IsDbNotFound(err3) { + return err3 + } + + if wechatNotifyTag != nil { + if wechatNotifyTag.Tag != v.bc.WechatNotifyMQ.Tag { + return fmt.Errorf("tag不一致,请检查tag配置:%s", wechatNotifyTag.Tag) + } + + if wechatNotifyTag.Status.IsSuccess() { + return v.setCache(ctx, c, wechatNotifyTag) + } + } else { + wechatNotifyTag, err3 = v.createWechatNotifyRegisterTag(ctx, stockCreatorMchID, stockID) + if err3 != nil { + return err3 + } + } + + if err = v.WechatCpnRepo.RegisterNotifyTag(ctx, stockID); err != nil { + + return v.WechatNotifyRegisterTagRepo.Fail(ctx, wechatNotifyTag.ID, err.Error()) + } + + if err = v.WechatNotifyRegisterTagRepo.Success(ctx, wechatNotifyTag.ID); err != nil { + return err + } + + return v.setCache(ctx, c, wechatNotifyTag) + }) +} + +func (v *VoucherBiz) createWechatNotifyRegisterTag(ctx context.Context, stockCreatorMchID, stockID string) (*bo.WechatNotifyRegisterTagBo, error) { + return v.WechatNotifyRegisterTagRepo.Create(ctx, &bo.WechatNotifyRegisterTagBo{ + StockID: stockID, + StockCreatorMchID: stockCreatorMchID, + Tag: v.bc.WechatNotifyMQ.Tag, + }) +} + +func (v *VoucherBiz) setCache(ctx context.Context, c *vo.Cache, wechatNotifyTag *bo.WechatNotifyRegisterTagBo) error { + + if err := v.rdb.Rdb.Set(ctx, c.Key, wechatNotifyTag.Tag, c.TTL).Err(); err != nil { + return fmt.Errorf(fmt.Sprintf("设置redis缓存%s异常:%v", c.Key, err)) + } + + return nil +} diff --git a/internal/biz/repo/order.go b/internal/biz/repo/order.go index b2c72ea..e3520d8 100644 --- a/internal/biz/repo/order.go +++ b/internal/biz/repo/order.go @@ -7,6 +7,9 @@ import ( ) type OrderRepo interface { + FinSucByStockIdInBatches(ctx context.Context, batchNo string, fun func(ctx context.Context, rows []*bo.OrderBo) error) error + FinFailByStockIdInBatches(ctx context.Context, batchNo string, fun func(ctx context.Context, rows []*bo.OrderBo) error) error + FindIngInBatches(ctx context.Context, fun func(ctx context.Context, rows []*bo.OrderBo) error) error FindInBatches(ctx context.Context, w *bo.FindInBatchesUseBo, fun func(ctx context.Context, rows []*bo.OrderBo) error) error GetByOutBizNo(ctx context.Context, t vo.OrderType, outBizNo string) (*bo.OrderBo, error) GetByOrderNo(ctx context.Context, orderNo string) (*bo.OrderBo, error) diff --git a/internal/biz/retry.go b/internal/biz/retry.go new file mode 100644 index 0000000..2816fdf --- /dev/null +++ b/internal/biz/retry.go @@ -0,0 +1,45 @@ +package biz + +import ( + "context" + "fmt" + "voucher/internal/biz/bo" + "voucher/internal/biz/vo" +) + +func (v *VoucherBiz) OrderRetry(ctx context.Context, outBizNos []string) error { + + if len(outBizNos) > 0 { + + for _, outBizNo := range outBizNos { + + order, err := v.OrderRepo.GetByOutBizNo(ctx, vo.OrderTypeCmb, outBizNo) + if err != nil { + return fmt.Errorf(fmt.Sprintf("获取订单%s异常:%v", outBizNo, err)) + } + + if !order.Status.IsIng() { + return fmt.Errorf(fmt.Sprintf("订单%s状态异常:%s", order.OrderNo, order.Status)) + } + + if err4 := v.orderRetry(ctx, order); err4 != nil { + return err4 + } + } + + return nil + } + + return v.OrderRepo.FindIngInBatches(ctx, func(ctx context.Context, rows []*bo.OrderBo) error { + + for _, order := range rows { + + if err4 := v.orderRetry(ctx, order); err4 != nil { + return err4 + } + } + + return nil + }) + +} diff --git a/internal/biz/vo/cache.go b/internal/biz/vo/cache.go index 2537d25..3081ab0 100644 --- a/internal/biz/vo/cache.go +++ b/internal/biz/vo/cache.go @@ -36,7 +36,7 @@ var CacheKeyMap = map[CacheKey]time.Duration{ CmbProductQueryLockKey: 30 * time.Second, CmbBatchNoticeCacheKey: 21600 * time.Second, // 6小时 CmbBatchNoticeLockKey: 300 * time.Second, - OrderConsumeFailAlarmKey: 5 * time.Hour, // 5小时 + OrderConsumeFailAlarmKey: 3 * time.Hour, // 3小时 OrderConsumeFailAlarmLockKey: 60 * time.Second, NotifyRetryConsume: 60 * time.Second, WechatNotifyRegisterTagCacheKey: 30 * 86400 * time.Second, // 30天 diff --git a/internal/biz/wechat_notify_consume.go b/internal/biz/wechat_notify.go similarity index 97% rename from internal/biz/wechat_notify_consume.go rename to internal/biz/wechat_notify.go index 280e9e7..0661fa0 100644 --- a/internal/biz/wechat_notify_consume.go +++ b/internal/biz/wechat_notify.go @@ -78,7 +78,8 @@ func (v *VoucherBiz) expired(ctx context.Context, order *bo.OrderBo) error { return err } - return v.notify(ctx, order) + //return v.notify(ctx, order) + return nil // 过期不做通知 } func (v *VoucherBiz) notify(ctx context.Context, order *bo.OrderBo) error { diff --git a/internal/biz/wechat_query.go b/internal/biz/wechat_query.go new file mode 100644 index 0000000..b725138 --- /dev/null +++ b/internal/biz/wechat_query.go @@ -0,0 +1,75 @@ +package biz + +import ( + "context" + "fmt" + "github.com/go-kratos/kratos/v2/log" + "time" + "voucher/internal/biz/bo" +) + +func (v *VoucherBiz) PushWechatQuery(ctx context.Context, productNo string) error { + + product, err := v.ProductRepo.GetByProductNo(ctx, productNo) + if err != nil { + return err + } + + queue := v.bc.RdsMQ.GetWechatQuery() + if queue == nil { + return fmt.Errorf("队列不存在") + } + + _, err = v.rdb.Rdb.RPush(ctx, queue.Name, product.BatchNo).Result() + if err != nil { + return fmt.Errorf("添加到队列失败:%v", err) + } + + return nil +} + +func (v *VoucherBiz) WechatQuery(ctx context.Context, batchNo string) error { + + start := time.Now() + log.Warnf("微信券查询处理开始:%s,batchNo:%s", start.String(), batchNo) + fmt.Printf("微信券查询处理开始:%s,batchNo:%s", start.String(), batchNo) + + num := 0 + err := v.OrderRepo.FinSucByStockIdInBatches(ctx, batchNo, func(ctx context.Context, rows []*bo.OrderBo) error { + + for _, order := range rows { + + num += 1 + if err := v.wechatQuery(ctx, order); err != nil { + log.Errorf("微信查询券订单状态发生错误,batchNo:%s,orderNo:%s,couponId:%s,appId:%s,openId:%s,err:%v", + batchNo, order.OrderNo, order.VoucherNo, order.AppID, order.Account, err) + } + + } + + time.Sleep(1 * time.Second) + + return nil + }) + + log.Warnf("微信券查询处理耗时:%s,batchNo:%s,处理%d单", time.Now().Sub(start).String(), batchNo, num) + fmt.Printf("微信券查询处理耗时:%s,batchNo:%s,处理%d单", time.Now().Sub(start).String(), batchNo, num) + + return err +} + +func (v *VoucherBiz) wechatQuery(ctx context.Context, order *bo.OrderBo) error { + + status, err := v.WechatCpnRepo.Query(ctx, order) + if err != nil { + return err + } + + if status.IsUse() { + return v.used(ctx, order) + } else if status.IsExpired() { + return v.expired(ctx, order) + } + + return nil +} diff --git a/internal/biz/wechat_retry.go b/internal/biz/wechat_retry.go new file mode 100644 index 0000000..c5fe0c0 --- /dev/null +++ b/internal/biz/wechat_retry.go @@ -0,0 +1,64 @@ +package biz + +import ( + "context" + "fmt" + "github.com/go-kratos/kratos/v2/log" + "time" + "voucher/internal/biz/bo" +) + +func (v *VoucherBiz) PushWechatRetry(ctx context.Context, productNo string) error { + + product, err := v.ProductRepo.GetByProductNo(ctx, productNo) + if err != nil { + return err + } + + queue := v.bc.RdsMQ.GetWechatRetry() + if queue == nil { + return fmt.Errorf("队列不存在") + } + + _, err = v.rdb.Rdb.RPush(ctx, queue.Name, product.BatchNo).Result() + if err != nil { + return fmt.Errorf("添加到队列失败:%v", err) + } + + return nil +} + +func (v *VoucherBiz) WechatRetry(ctx context.Context, batchNo string) error { + + start := time.Now() + log.Warnf("失败订单重试开始:%s,batchNo:%s", start.String(), batchNo) + fmt.Printf("失败订单重试开始:%s,batchNo:%s", start.String(), batchNo) + + num := 0 + err := v.OrderRepo.FinFailByStockIdInBatches(ctx, batchNo, func(ctx context.Context, rows []*bo.OrderBo) error { + + if len(rows) == 0 { + log.Infof("微信查询券订单状态,batchNo[%s],已处理[%d]单,无订单,结束执行", batchNo, num) + return nil + } + + for _, order := range rows { + + num += 1 + if err := v.orderRetry(ctx, order); err != nil { + log.Errorf("失败订单重试发生错误,batchNo:%s,orderNo:%s,appId:%s,openId:%s,err:%v", + batchNo, order.OrderNo, order.AppID, order.Account, err) + } + + } + + time.Sleep(1 * time.Second) + + return nil + }) + + log.Warnf("微信券查询处理耗时:%s,batchNo:%s,处理%d单", time.Now().Sub(start).String(), batchNo, num) + fmt.Printf("微信券查询处理耗时:%s,batchNo:%s,处理%d单", time.Now().Sub(start).String(), batchNo, num) + + return err +} diff --git a/internal/conf/conf.pb.go b/internal/conf/conf.pb.go index f400113..dc691b8 100644 --- a/internal/conf/conf.pb.go +++ b/internal/conf/conf.pb.go @@ -35,6 +35,7 @@ type Bootstrap struct { WechatNotifyMQ *WechatNotifyMQ `protobuf:"bytes,7,opt,name=wechatNotifyMQ,proto3" json:"wechatNotifyMQ,omitempty"` Alarm *Alarm `protobuf:"bytes,8,opt,name=alarm,proto3" json:"alarm,omitempty"` Cron *Cron `protobuf:"bytes,9,opt,name=cron,proto3" json:"cron,omitempty"` + RdsMQ *RdsMQ `protobuf:"bytes,10,opt,name=rdsMQ,proto3" json:"rdsMQ,omitempty"` } func (x *Bootstrap) Reset() { @@ -132,6 +133,13 @@ func (x *Bootstrap) GetCron() *Cron { return nil } +func (x *Bootstrap) GetRdsMQ() *RdsMQ { + if x != nil { + return x.RdsMQ + } + return nil +} + type Server struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -827,6 +835,61 @@ func (x *Cron) GetCommandMap() map[string]*Cron_CommandMap { return nil } +type RdsMQ struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + WechatQuery *RdsMQ_Queue `protobuf:"bytes,1,opt,name=wechatQuery,proto3" json:"wechatQuery,omitempty"` + WechatRetry *RdsMQ_Queue `protobuf:"bytes,2,opt,name=wechatRetry,proto3" json:"wechatRetry,omitempty"` +} + +func (x *RdsMQ) Reset() { + *x = RdsMQ{} + if protoimpl.UnsafeEnabled { + mi := &file_conf_conf_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RdsMQ) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RdsMQ) ProtoMessage() {} + +func (x *RdsMQ) ProtoReflect() protoreflect.Message { + mi := &file_conf_conf_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RdsMQ.ProtoReflect.Descriptor instead. +func (*RdsMQ) Descriptor() ([]byte, []int) { + return file_conf_conf_proto_rawDescGZIP(), []int{10} +} + +func (x *RdsMQ) GetWechatQuery() *RdsMQ_Queue { + if x != nil { + return x.WechatQuery + } + return nil +} + +func (x *RdsMQ) GetWechatRetry() *RdsMQ_Queue { + if x != nil { + return x.WechatRetry + } + return nil +} + type Logs struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -839,7 +902,7 @@ type Logs struct { func (x *Logs) Reset() { *x = Logs{} if protoimpl.UnsafeEnabled { - mi := &file_conf_conf_proto_msgTypes[10] + mi := &file_conf_conf_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -852,7 +915,7 @@ func (x *Logs) String() string { func (*Logs) ProtoMessage() {} func (x *Logs) ProtoReflect() protoreflect.Message { - mi := &file_conf_conf_proto_msgTypes[10] + mi := &file_conf_conf_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -865,7 +928,7 @@ func (x *Logs) ProtoReflect() protoreflect.Message { // Deprecated: Use Logs.ProtoReflect.Descriptor instead. func (*Logs) Descriptor() ([]byte, []int) { - return file_conf_conf_proto_rawDescGZIP(), []int{10} + return file_conf_conf_proto_rawDescGZIP(), []int{11} } func (x *Logs) GetBusiness() string { @@ -897,7 +960,7 @@ type Server_HTTP struct { func (x *Server_HTTP) Reset() { *x = Server_HTTP{} if protoimpl.UnsafeEnabled { - mi := &file_conf_conf_proto_msgTypes[11] + mi := &file_conf_conf_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -910,7 +973,7 @@ func (x *Server_HTTP) String() string { func (*Server_HTTP) ProtoMessage() {} func (x *Server_HTTP) ProtoReflect() protoreflect.Message { - mi := &file_conf_conf_proto_msgTypes[11] + mi := &file_conf_conf_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -977,7 +1040,7 @@ type Data_Database struct { func (x *Data_Database) Reset() { *x = Data_Database{} if protoimpl.UnsafeEnabled { - mi := &file_conf_conf_proto_msgTypes[12] + mi := &file_conf_conf_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -990,7 +1053,7 @@ func (x *Data_Database) String() string { func (*Data_Database) ProtoMessage() {} func (x *Data_Database) ProtoReflect() protoreflect.Message { - mi := &file_conf_conf_proto_msgTypes[12] + mi := &file_conf_conf_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1067,7 +1130,7 @@ type Data_Redis struct { func (x *Data_Redis) Reset() { *x = Data_Redis{} if protoimpl.UnsafeEnabled { - mi := &file_conf_conf_proto_msgTypes[13] + mi := &file_conf_conf_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1080,7 +1143,7 @@ func (x *Data_Redis) String() string { func (*Data_Redis) ProtoMessage() {} func (x *Data_Redis) ProtoReflect() protoreflect.Message { - mi := &file_conf_conf_proto_msgTypes[13] + mi := &file_conf_conf_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1171,7 +1234,7 @@ type Cron_CommandMap struct { func (x *Cron_CommandMap) Reset() { *x = Cron_CommandMap{} if protoimpl.UnsafeEnabled { - mi := &file_conf_conf_proto_msgTypes[15] + mi := &file_conf_conf_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1184,7 +1247,7 @@ func (x *Cron_CommandMap) String() string { func (*Cron_CommandMap) ProtoMessage() {} func (x *Cron_CommandMap) ProtoReflect() protoreflect.Message { - mi := &file_conf_conf_proto_msgTypes[15] + mi := &file_conf_conf_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1214,6 +1277,85 @@ func (x *Cron_CommandMap) GetCommand() string { return "" } +type RdsMQ_Queue struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + IsOpen bool `protobuf:"varint,2,opt,name=isOpen,proto3" json:"isOpen,omitempty"` + RetryNum uint32 `protobuf:"varint,3,opt,name=retryNum,proto3" json:"retryNum,omitempty"` + NumWorkers uint32 `protobuf:"varint,4,opt,name=numWorkers,proto3" json:"numWorkers,omitempty"` + WaitTime *durationpb.Duration `protobuf:"bytes,5,opt,name=waitTime,proto3" json:"waitTime,omitempty"` +} + +func (x *RdsMQ_Queue) Reset() { + *x = RdsMQ_Queue{} + if protoimpl.UnsafeEnabled { + mi := &file_conf_conf_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RdsMQ_Queue) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RdsMQ_Queue) ProtoMessage() {} + +func (x *RdsMQ_Queue) ProtoReflect() protoreflect.Message { + mi := &file_conf_conf_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RdsMQ_Queue.ProtoReflect.Descriptor instead. +func (*RdsMQ_Queue) Descriptor() ([]byte, []int) { + return file_conf_conf_proto_rawDescGZIP(), []int{10, 0} +} + +func (x *RdsMQ_Queue) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *RdsMQ_Queue) GetIsOpen() bool { + if x != nil { + return x.IsOpen + } + return false +} + +func (x *RdsMQ_Queue) GetRetryNum() uint32 { + if x != nil { + return x.RetryNum + } + return 0 +} + +func (x *RdsMQ_Queue) GetNumWorkers() uint32 { + if x != nil { + return x.NumWorkers + } + return 0 +} + +func (x *RdsMQ_Queue) GetWaitTime() *durationpb.Duration { + if x != nil { + return x.WaitTime + } + return nil +} + var File_conf_conf_proto protoreflect.FileDescriptor var file_conf_conf_proto_rawDesc = []byte{ @@ -1221,7 +1363,7 @@ var file_conf_conf_proto_rawDesc = []byte{ 0x6f, 0x12, 0x0e, 0x76, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x22, 0xbb, 0x03, 0x0a, 0x09, 0x42, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x12, + 0x6f, 0x22, 0xe8, 0x03, 0x0a, 0x09, 0x42, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x12, 0x2e, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x76, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, @@ -1248,172 +1390,194 @@ var file_conf_conf_proto_rawDesc = []byte{ 0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x41, 0x6c, 0x61, 0x72, 0x6d, 0x52, 0x05, 0x61, 0x6c, 0x61, 0x72, 0x6d, 0x12, 0x28, 0x0a, 0x04, 0x63, 0x72, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x76, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x52, 0x04, 0x63, 0x72, 0x6f, 0x6e, 0x22, - 0xff, 0x01, 0x0a, 0x06, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x04, 0x68, 0x74, - 0x74, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x76, 0x6f, 0x75, 0x63, 0x68, - 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x2e, 0x48, 0x54, 0x54, 0x50, 0x52, 0x04, 0x68, 0x74, 0x74, 0x70, 0x1a, 0xc3, 0x01, 0x0a, 0x04, - 0x48, 0x54, 0x54, 0x50, 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x12, - 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, - 0x64, 0x72, 0x12, 0x33, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, - 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x69, 0x73, 0x4f, 0x70, 0x65, - 0x6e, 0x53, 0x77, 0x61, 0x67, 0x67, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, - 0x69, 0x73, 0x4f, 0x70, 0x65, 0x6e, 0x53, 0x77, 0x61, 0x67, 0x67, 0x65, 0x72, 0x12, 0x32, 0x0a, - 0x14, 0x69, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x65, 0x71, 0x48, 0x65, - 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x69, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x65, 0x71, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, - 0x73, 0x22, 0x94, 0x05, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x2d, 0x0a, 0x02, 0x64, 0x62, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x76, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, - 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x2e, 0x44, 0x61, 0x74, - 0x61, 0x62, 0x61, 0x73, 0x65, 0x52, 0x02, 0x64, 0x62, 0x12, 0x30, 0x0a, 0x05, 0x72, 0x65, 0x64, - 0x69, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x76, 0x6f, 0x75, 0x63, 0x68, - 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x2e, 0x52, - 0x65, 0x64, 0x69, 0x73, 0x52, 0x05, 0x72, 0x65, 0x64, 0x69, 0x73, 0x1a, 0xc5, 0x01, 0x0a, 0x08, - 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x72, 0x69, 0x76, - 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x72, 0x69, 0x76, 0x65, 0x72, - 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x61, 0x78, 0x49, - 0x64, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x6d, 0x61, 0x78, 0x49, 0x64, - 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x61, 0x78, 0x4f, 0x70, 0x65, 0x6e, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x07, 0x6d, 0x61, 0x78, 0x4f, 0x70, 0x65, 0x6e, 0x12, 0x3b, 0x0a, 0x0b, - 0x6d, 0x61, 0x78, 0x4c, 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x6d, 0x61, - 0x78, 0x4c, 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x69, 0x73, 0x44, - 0x65, 0x62, 0x75, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x44, 0x65, - 0x62, 0x75, 0x67, 0x1a, 0xe2, 0x02, 0x0a, 0x05, 0x52, 0x65, 0x64, 0x69, 0x73, 0x12, 0x18, 0x0a, - 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x12, 0x3b, 0x0a, 0x0b, 0x72, - 0x65, 0x61, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x52, 0x04, 0x63, 0x72, 0x6f, 0x6e, 0x12, + 0x2b, 0x0a, 0x05, 0x72, 0x64, 0x73, 0x4d, 0x51, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, + 0x2e, 0x76, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, + 0x52, 0x64, 0x73, 0x4d, 0x51, 0x52, 0x05, 0x72, 0x64, 0x73, 0x4d, 0x51, 0x22, 0xff, 0x01, 0x0a, + 0x06, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x04, 0x68, 0x74, 0x74, 0x70, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x76, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x2e, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x48, 0x54, + 0x54, 0x50, 0x52, 0x04, 0x68, 0x74, 0x74, 0x70, 0x1a, 0xc3, 0x01, 0x0a, 0x04, 0x48, 0x54, 0x54, + 0x50, 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x61, + 0x64, 0x64, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x12, + 0x33, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x72, 0x65, 0x61, - 0x64, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x3d, 0x0a, 0x0c, 0x77, 0x72, 0x69, 0x74, - 0x65, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, + 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x74, 0x69, 0x6d, + 0x65, 0x6f, 0x75, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x69, 0x73, 0x4f, 0x70, 0x65, 0x6e, 0x53, 0x77, + 0x61, 0x67, 0x67, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x69, 0x73, 0x4f, + 0x70, 0x65, 0x6e, 0x53, 0x77, 0x61, 0x67, 0x67, 0x65, 0x72, 0x12, 0x32, 0x0a, 0x14, 0x69, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x65, 0x71, 0x48, 0x65, 0x61, 0x64, 0x65, + 0x72, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x69, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x65, 0x71, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x22, 0x94, + 0x05, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x2d, 0x0a, 0x02, 0x64, 0x62, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x76, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, + 0x73, 0x65, 0x52, 0x02, 0x64, 0x62, 0x12, 0x30, 0x0a, 0x05, 0x72, 0x65, 0x64, 0x69, 0x73, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x76, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x2e, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x2e, 0x52, 0x65, 0x64, 0x69, + 0x73, 0x52, 0x05, 0x72, 0x65, 0x64, 0x69, 0x73, 0x1a, 0xc5, 0x01, 0x0a, 0x08, 0x44, 0x61, 0x74, + 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x72, 0x69, 0x76, 0x65, 0x72, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x72, 0x69, 0x76, 0x65, 0x72, 0x12, 0x16, 0x0a, + 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x61, 0x78, 0x49, 0x64, 0x6c, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x6d, 0x61, 0x78, 0x49, 0x64, 0x6c, 0x65, 0x12, + 0x18, 0x0a, 0x07, 0x6d, 0x61, 0x78, 0x4f, 0x70, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x07, 0x6d, 0x61, 0x78, 0x4f, 0x70, 0x65, 0x6e, 0x12, 0x3b, 0x0a, 0x0b, 0x6d, 0x61, 0x78, + 0x4c, 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x77, 0x72, 0x69, 0x74, 0x65, - 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, - 0x6f, 0x72, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, - 0x6f, 0x72, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6f, 0x6f, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x6f, 0x6f, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x12, - 0x22, 0x0a, 0x0c, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x73, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x6c, 0x65, 0x43, 0x6f, - 0x6e, 0x6e, 0x73, 0x12, 0x43, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x4d, 0x61, 0x78, 0x49, 0x64, - 0x6c, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, - 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x4d, 0x61, 0x78, - 0x49, 0x64, 0x6c, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x64, 0x62, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x64, 0x62, 0x22, 0x97, 0x02, 0x0a, 0x08, 0x52, 0x6f, 0x63, - 0x6b, 0x65, 0x74, 0x4d, 0x51, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x63, 0x63, - 0x65, 0x73, 0x73, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, - 0x63, 0x65, 0x73, 0x73, 0x4b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x63, 0x72, 0x65, - 0x74, 0x4b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x63, 0x72, - 0x65, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x63, 0x72, - 0x65, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x42, 0x0a, 0x08, 0x65, 0x76, 0x65, 0x6e, 0x74, - 0x4d, 0x61, 0x70, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x6f, 0x75, 0x63, - 0x68, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x52, 0x6f, 0x63, 0x6b, 0x65, - 0x74, 0x4d, 0x51, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x52, 0x08, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4d, 0x61, 0x70, 0x1a, 0x55, 0x0a, 0x0d, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2e, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, - 0x76, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x4d, 0x61, 0x70, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, - 0x38, 0x01, 0x22, 0x88, 0x01, 0x0a, 0x08, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x4d, 0x61, 0x70, 0x12, - 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x12, 0x28, 0x0a, 0x0f, 0x70, - 0x65, 0x72, 0x43, 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x43, 0x6e, 0x74, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x70, 0x65, 0x72, 0x43, 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, - 0x6e, 0x65, 0x43, 0x6e, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x69, 0x73, 0x4f, 0x70, 0x65, 0x6e, 0x43, - 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x69, - 0x73, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, 0x22, 0x92, 0x01, - 0x0a, 0x06, 0x57, 0x65, 0x63, 0x68, 0x61, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x63, 0x68, 0x49, - 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x63, 0x68, 0x49, 0x44, 0x12, 0x3e, - 0x0a, 0x1a, 0x6d, 0x63, 0x68, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, - 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x1a, 0x6d, 0x63, 0x68, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x65, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x32, - 0x0a, 0x14, 0x77, 0x65, 0x63, 0x68, 0x61, 0x74, 0x50, 0x61, 0x79, 0x50, 0x75, 0x62, 0x6c, 0x69, - 0x63, 0x4b, 0x65, 0x79, 0x49, 0x44, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x77, 0x65, - 0x63, 0x68, 0x61, 0x74, 0x50, 0x61, 0x79, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, - 0x49, 0x44, 0x22, 0xd7, 0x02, 0x0a, 0x03, 0x43, 0x6d, 0x62, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, - 0x61, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x61, 0x69, 0x64, 0x12, 0x16, - 0x0a, 0x06, 0x73, 0x6d, 0x32, 0x50, 0x72, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x73, 0x6d, 0x32, 0x50, 0x72, 0x6b, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6d, 0x32, 0x50, 0x75, 0x6b, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6d, 0x32, 0x50, 0x75, 0x6b, 0x12, 0x1c, - 0x0a, 0x09, 0x63, 0x6d, 0x62, 0x53, 0x6d, 0x32, 0x50, 0x69, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x63, 0x6d, 0x62, 0x53, 0x6d, 0x32, 0x50, 0x69, 0x6b, 0x12, 0x1c, 0x0a, 0x09, - 0x63, 0x6d, 0x62, 0x53, 0x6d, 0x32, 0x50, 0x75, 0x6b, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x63, 0x6d, 0x62, 0x53, 0x6d, 0x32, 0x50, 0x75, 0x6b, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, - 0x79, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, - 0x79, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6d, 0x62, 0x4b, 0x65, 0x79, - 0x41, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6d, 0x62, - 0x4b, 0x65, 0x79, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x72, 0x67, 0x4e, - 0x6f, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x4e, 0x6f, 0x12, 0x1c, - 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x55, 0x72, 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x55, 0x72, 0x6c, 0x12, 0x28, 0x0a, 0x0f, - 0x6e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x44, 0x61, 0x79, 0x73, 0x18, - 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, - 0x72, 0x74, 0x44, 0x61, 0x79, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x6f, 0x74, 0x69, 0x63, 0x65, - 0x45, 0x6e, 0x64, 0x44, 0x61, 0x79, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6e, - 0x6f, 0x74, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x64, 0x44, 0x61, 0x79, 0x73, 0x22, 0xc6, 0x02, 0x0a, - 0x0e, 0x57, 0x65, 0x63, 0x68, 0x61, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x4d, 0x51, 0x12, - 0x20, 0x0a, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4b, 0x65, 0x79, 0x49, - 0x64, 0x12, 0x28, 0x0a, 0x0f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4b, 0x65, 0x79, 0x53, 0x65, - 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x61, 0x63, 0x63, 0x65, - 0x73, 0x73, 0x4b, 0x65, 0x79, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, - 0x6e, 0x64, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, - 0x6e, 0x64, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, - 0x6e, 0x49, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, - 0x6e, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, - 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, - 0x65, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x67, - 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x67, 0x72, - 0x6f, 0x75, 0x70, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, - 0x72, 0x54, 0x61, 0x67, 0x55, 0x72, 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x54, 0x61, 0x67, 0x55, 0x72, 0x6c, 0x12, 0x26, 0x0a, - 0x0e, 0x69, 0x73, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, 0x18, - 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x69, 0x73, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x6f, 0x6e, - 0x73, 0x75, 0x6d, 0x65, 0x72, 0x22, 0x73, 0x0a, 0x05, 0x41, 0x6c, 0x61, 0x72, 0x6d, 0x12, 0x1e, - 0x0a, 0x0a, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x55, 0x52, 0x4c, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0a, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x55, 0x52, 0x4c, 0x12, 0x16, - 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x74, 0x41, 0x6c, 0x6c, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x61, 0x74, 0x41, 0x6c, 0x6c, 0x12, 0x1c, 0x0a, 0x09, - 0x61, 0x74, 0x4d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x09, 0x61, 0x74, 0x4d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x73, 0x22, 0x84, 0x02, 0x0a, 0x04, 0x43, - 0x72, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x73, 0x4f, 0x70, 0x65, 0x6e, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, 0x73, 0x4f, 0x70, 0x65, 0x6e, 0x12, 0x44, 0x0a, 0x0a, 0x63, - 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x4d, 0x61, 0x70, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x24, 0x2e, 0x76, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x4d, 0x61, 0x70, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x4d, 0x61, - 0x70, 0x1a, 0x3e, 0x0a, 0x0a, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x4d, 0x61, 0x70, 0x12, - 0x16, 0x0a, 0x06, 0x69, 0x73, 0x4f, 0x70, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x06, 0x69, 0x73, 0x4f, 0x70, 0x65, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, - 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, - 0x64, 0x1a, 0x5e, 0x0a, 0x0f, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x4d, 0x61, 0x70, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x35, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x76, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x2e, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x4d, 0x61, 0x70, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x22, 0x3a, 0x0a, 0x04, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x62, 0x75, 0x73, - 0x69, 0x6e, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x62, 0x75, 0x73, - 0x69, 0x6e, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x42, 0x17, 0x5a, - 0x15, 0x76, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x2f, 0x63, 0x70, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, - 0x66, 0x3b, 0x63, 0x6f, 0x6e, 0x66, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x4c, 0x69, + 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x69, 0x73, 0x44, 0x65, 0x62, 0x75, + 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x44, 0x65, 0x62, 0x75, 0x67, + 0x1a, 0xe2, 0x02, 0x0a, 0x05, 0x52, 0x65, 0x64, 0x69, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65, + 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x74, + 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x12, 0x3b, 0x0a, 0x0b, 0x72, 0x65, 0x61, 0x64, + 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x72, 0x65, 0x61, 0x64, 0x54, 0x69, + 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x3d, 0x0a, 0x0c, 0x77, 0x72, 0x69, 0x74, 0x65, 0x54, 0x69, + 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x77, 0x72, 0x69, 0x74, 0x65, 0x54, 0x69, 0x6d, + 0x65, 0x6f, 0x75, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, + 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6f, 0x6f, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x08, 0x70, 0x6f, 0x6f, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x22, 0x0a, 0x0c, + 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x73, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x0c, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x73, + 0x12, 0x43, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x4d, 0x61, 0x78, 0x49, 0x64, 0x6c, 0x65, 0x54, + 0x69, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x4d, 0x61, 0x78, 0x49, 0x64, 0x6c, + 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x64, 0x62, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x02, 0x64, 0x62, 0x22, 0x97, 0x02, 0x0a, 0x08, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, + 0x4d, 0x51, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x63, 0x65, 0x73, + 0x73, 0x4b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4b, 0x65, + 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4b, + 0x65, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x42, 0x0a, 0x08, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4d, 0x61, 0x70, + 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, + 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x4d, 0x51, + 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4d, 0x61, 0x70, 0x1a, 0x55, 0x0a, 0x0d, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2e, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x76, 0x6f, 0x75, + 0x63, 0x68, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x4d, 0x61, 0x70, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, + 0x88, 0x01, 0x0a, 0x08, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x4d, 0x61, 0x70, 0x12, 0x14, 0x0a, 0x05, + 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x67, 0x72, 0x6f, + 0x75, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x12, 0x28, 0x0a, 0x0f, 0x70, 0x65, 0x72, 0x43, + 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x43, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x0f, 0x70, 0x65, 0x72, 0x43, 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x43, + 0x6e, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x69, 0x73, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x6f, 0x6e, 0x73, + 0x75, 0x6d, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x69, 0x73, 0x4f, 0x70, + 0x65, 0x6e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, 0x22, 0x92, 0x01, 0x0a, 0x06, 0x57, + 0x65, 0x63, 0x68, 0x61, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x63, 0x68, 0x49, 0x44, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x63, 0x68, 0x49, 0x44, 0x12, 0x3e, 0x0a, 0x1a, 0x6d, + 0x63, 0x68, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, + 0x69, 0x61, 0x6c, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x1a, 0x6d, 0x63, 0x68, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x53, + 0x65, 0x72, 0x69, 0x61, 0x6c, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x32, 0x0a, 0x14, 0x77, + 0x65, 0x63, 0x68, 0x61, 0x74, 0x50, 0x61, 0x79, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, + 0x79, 0x49, 0x44, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x77, 0x65, 0x63, 0x68, 0x61, + 0x74, 0x50, 0x61, 0x79, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x49, 0x44, 0x22, + 0xd7, 0x02, 0x0a, 0x03, 0x43, 0x6d, 0x62, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x61, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, + 0x6d, 0x32, 0x50, 0x72, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6d, 0x32, + 0x50, 0x72, 0x6b, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6d, 0x32, 0x50, 0x75, 0x6b, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6d, 0x32, 0x50, 0x75, 0x6b, 0x12, 0x1c, 0x0a, 0x09, 0x63, + 0x6d, 0x62, 0x53, 0x6d, 0x32, 0x50, 0x69, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x63, 0x6d, 0x62, 0x53, 0x6d, 0x32, 0x50, 0x69, 0x6b, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6d, 0x62, + 0x53, 0x6d, 0x32, 0x50, 0x75, 0x6b, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6d, + 0x62, 0x53, 0x6d, 0x32, 0x50, 0x75, 0x6b, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x41, 0x6c, + 0x69, 0x61, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x41, 0x6c, + 0x69, 0x61, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6d, 0x62, 0x4b, 0x65, 0x79, 0x41, 0x6c, 0x69, + 0x61, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6d, 0x62, 0x4b, 0x65, 0x79, + 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x72, 0x67, 0x4e, 0x6f, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x4e, 0x6f, 0x12, 0x1c, 0x0a, 0x09, 0x6e, + 0x6f, 0x74, 0x69, 0x66, 0x79, 0x55, 0x72, 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x55, 0x72, 0x6c, 0x12, 0x28, 0x0a, 0x0f, 0x6e, 0x6f, 0x74, + 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x44, 0x61, 0x79, 0x73, 0x18, 0x0b, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0f, 0x6e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x44, + 0x61, 0x79, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x64, + 0x44, 0x61, 0x79, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6e, 0x6f, 0x74, 0x69, + 0x63, 0x65, 0x45, 0x6e, 0x64, 0x44, 0x61, 0x79, 0x73, 0x22, 0xc6, 0x02, 0x0a, 0x0e, 0x57, 0x65, + 0x63, 0x68, 0x61, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x4d, 0x51, 0x12, 0x20, 0x0a, 0x0b, + 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x12, 0x28, + 0x0a, 0x0f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4b, 0x65, 0x79, 0x53, 0x65, 0x63, 0x72, 0x65, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4b, + 0x65, 0x79, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x50, + 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x50, + 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, + 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, + 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x67, 0x72, 0x6f, 0x75, + 0x70, 0x49, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, + 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x54, 0x61, + 0x67, 0x55, 0x72, 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x65, 0x72, 0x54, 0x61, 0x67, 0x55, 0x72, 0x6c, 0x12, 0x26, 0x0a, 0x0e, 0x69, 0x73, + 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, 0x18, 0x0b, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0e, 0x69, 0x73, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, + 0x65, 0x72, 0x22, 0x73, 0x0a, 0x05, 0x41, 0x6c, 0x61, 0x72, 0x6d, 0x12, 0x1e, 0x0a, 0x0a, 0x77, + 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x55, 0x52, 0x4c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x55, 0x52, 0x4c, 0x12, 0x16, 0x0a, 0x06, 0x73, + 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x63, + 0x72, 0x65, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x74, 0x41, 0x6c, 0x6c, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x05, 0x61, 0x74, 0x41, 0x6c, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x74, 0x4d, + 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x61, 0x74, + 0x4d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x73, 0x22, 0x84, 0x02, 0x0a, 0x04, 0x43, 0x72, 0x6f, 0x6e, + 0x12, 0x16, 0x0a, 0x06, 0x69, 0x73, 0x4f, 0x70, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x06, 0x69, 0x73, 0x4f, 0x70, 0x65, 0x6e, 0x12, 0x44, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, + 0x61, 0x6e, 0x64, 0x4d, 0x61, 0x70, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x76, + 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x43, 0x72, + 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x4d, 0x61, 0x70, 0x1a, 0x3e, + 0x0a, 0x0a, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x4d, 0x61, 0x70, 0x12, 0x16, 0x0a, 0x06, + 0x69, 0x73, 0x4f, 0x70, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, 0x73, + 0x4f, 0x70, 0x65, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x1a, 0x5e, + 0x0a, 0x0f, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x35, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x76, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x2e, 0x43, 0x72, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, + 0x4d, 0x61, 0x70, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xae, + 0x02, 0x0a, 0x05, 0x52, 0x64, 0x73, 0x4d, 0x51, 0x12, 0x3d, 0x0a, 0x0b, 0x77, 0x65, 0x63, 0x68, + 0x61, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, + 0x76, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x52, + 0x64, 0x73, 0x4d, 0x51, 0x2e, 0x51, 0x75, 0x65, 0x75, 0x65, 0x52, 0x0b, 0x77, 0x65, 0x63, 0x68, + 0x61, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x3d, 0x0a, 0x0b, 0x77, 0x65, 0x63, 0x68, 0x61, + 0x74, 0x52, 0x65, 0x74, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x76, + 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x52, 0x64, + 0x73, 0x4d, 0x51, 0x2e, 0x51, 0x75, 0x65, 0x75, 0x65, 0x52, 0x0b, 0x77, 0x65, 0x63, 0x68, 0x61, + 0x74, 0x52, 0x65, 0x74, 0x72, 0x79, 0x1a, 0xa6, 0x01, 0x0a, 0x05, 0x51, 0x75, 0x65, 0x75, 0x65, + 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x73, 0x4f, 0x70, 0x65, 0x6e, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, 0x73, 0x4f, 0x70, 0x65, 0x6e, 0x12, 0x1a, 0x0a, 0x08, + 0x72, 0x65, 0x74, 0x72, 0x79, 0x4e, 0x75, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, + 0x72, 0x65, 0x74, 0x72, 0x79, 0x4e, 0x75, 0x6d, 0x12, 0x1e, 0x0a, 0x0a, 0x6e, 0x75, 0x6d, 0x57, + 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6e, 0x75, + 0x6d, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x12, 0x35, 0x0a, 0x08, 0x77, 0x61, 0x69, 0x74, + 0x54, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x77, 0x61, 0x69, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x22, + 0x3a, 0x0a, 0x04, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x62, 0x75, 0x73, 0x69, 0x6e, + 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x62, 0x75, 0x73, 0x69, 0x6e, + 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x42, 0x17, 0x5a, 0x15, 0x76, + 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x2f, 0x63, 0x70, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x3b, + 0x63, 0x6f, 0x6e, 0x66, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1428,7 +1592,7 @@ func file_conf_conf_proto_rawDescGZIP() []byte { return file_conf_conf_proto_rawDescData } -var file_conf_conf_proto_msgTypes = make([]protoimpl.MessageInfo, 17) +var file_conf_conf_proto_msgTypes = make([]protoimpl.MessageInfo, 19) var file_conf_conf_proto_goTypes = []any{ (*Bootstrap)(nil), // 0: voucher.config.Bootstrap (*Server)(nil), // 1: voucher.config.Server @@ -1440,18 +1604,20 @@ var file_conf_conf_proto_goTypes = []any{ (*WechatNotifyMQ)(nil), // 7: voucher.config.WechatNotifyMQ (*Alarm)(nil), // 8: voucher.config.Alarm (*Cron)(nil), // 9: voucher.config.Cron - (*Logs)(nil), // 10: voucher.config.Logs - (*Server_HTTP)(nil), // 11: voucher.config.Server.HTTP - (*Data_Database)(nil), // 12: voucher.config.Data.Database - (*Data_Redis)(nil), // 13: voucher.config.Data.Redis - nil, // 14: voucher.config.RocketMQ.EventMapEntry - (*Cron_CommandMap)(nil), // 15: voucher.config.Cron.CommandMap - nil, // 16: voucher.config.Cron.CommandMapEntry - (*durationpb.Duration)(nil), // 17: google.protobuf.Duration + (*RdsMQ)(nil), // 10: voucher.config.RdsMQ + (*Logs)(nil), // 11: voucher.config.Logs + (*Server_HTTP)(nil), // 12: voucher.config.Server.HTTP + (*Data_Database)(nil), // 13: voucher.config.Data.Database + (*Data_Redis)(nil), // 14: voucher.config.Data.Redis + nil, // 15: voucher.config.RocketMQ.EventMapEntry + (*Cron_CommandMap)(nil), // 16: voucher.config.Cron.CommandMap + nil, // 17: voucher.config.Cron.CommandMapEntry + (*RdsMQ_Queue)(nil), // 18: voucher.config.RdsMQ.Queue + (*durationpb.Duration)(nil), // 19: google.protobuf.Duration } var file_conf_conf_proto_depIdxs = []int32{ 1, // 0: voucher.config.Bootstrap.server:type_name -> voucher.config.Server - 10, // 1: voucher.config.Bootstrap.logs:type_name -> voucher.config.Logs + 11, // 1: voucher.config.Bootstrap.logs:type_name -> voucher.config.Logs 2, // 2: voucher.config.Bootstrap.data:type_name -> voucher.config.Data 3, // 3: voucher.config.Bootstrap.rocketMQ:type_name -> voucher.config.RocketMQ 5, // 4: voucher.config.Bootstrap.wechat:type_name -> voucher.config.Wechat @@ -1459,23 +1625,27 @@ var file_conf_conf_proto_depIdxs = []int32{ 7, // 6: voucher.config.Bootstrap.wechatNotifyMQ:type_name -> voucher.config.WechatNotifyMQ 8, // 7: voucher.config.Bootstrap.alarm:type_name -> voucher.config.Alarm 9, // 8: voucher.config.Bootstrap.cron:type_name -> voucher.config.Cron - 11, // 9: voucher.config.Server.http:type_name -> voucher.config.Server.HTTP - 12, // 10: voucher.config.Data.db:type_name -> voucher.config.Data.Database - 13, // 11: voucher.config.Data.redis:type_name -> voucher.config.Data.Redis - 14, // 12: voucher.config.RocketMQ.eventMap:type_name -> voucher.config.RocketMQ.EventMapEntry - 16, // 13: voucher.config.Cron.commandMap:type_name -> voucher.config.Cron.CommandMapEntry - 17, // 14: voucher.config.Server.HTTP.timeout:type_name -> google.protobuf.Duration - 17, // 15: voucher.config.Data.Database.maxLifetime:type_name -> google.protobuf.Duration - 17, // 16: voucher.config.Data.Redis.readTimeout:type_name -> google.protobuf.Duration - 17, // 17: voucher.config.Data.Redis.writeTimeout:type_name -> google.protobuf.Duration - 17, // 18: voucher.config.Data.Redis.connMaxIdleTime:type_name -> google.protobuf.Duration - 4, // 19: voucher.config.RocketMQ.EventMapEntry.value:type_name -> voucher.config.EventMap - 15, // 20: voucher.config.Cron.CommandMapEntry.value:type_name -> voucher.config.Cron.CommandMap - 21, // [21:21] is the sub-list for method output_type - 21, // [21:21] is the sub-list for method input_type - 21, // [21:21] is the sub-list for extension type_name - 21, // [21:21] is the sub-list for extension extendee - 0, // [0:21] is the sub-list for field type_name + 10, // 9: voucher.config.Bootstrap.rdsMQ:type_name -> voucher.config.RdsMQ + 12, // 10: voucher.config.Server.http:type_name -> voucher.config.Server.HTTP + 13, // 11: voucher.config.Data.db:type_name -> voucher.config.Data.Database + 14, // 12: voucher.config.Data.redis:type_name -> voucher.config.Data.Redis + 15, // 13: voucher.config.RocketMQ.eventMap:type_name -> voucher.config.RocketMQ.EventMapEntry + 17, // 14: voucher.config.Cron.commandMap:type_name -> voucher.config.Cron.CommandMapEntry + 18, // 15: voucher.config.RdsMQ.wechatQuery:type_name -> voucher.config.RdsMQ.Queue + 18, // 16: voucher.config.RdsMQ.wechatRetry:type_name -> voucher.config.RdsMQ.Queue + 19, // 17: voucher.config.Server.HTTP.timeout:type_name -> google.protobuf.Duration + 19, // 18: voucher.config.Data.Database.maxLifetime:type_name -> google.protobuf.Duration + 19, // 19: voucher.config.Data.Redis.readTimeout:type_name -> google.protobuf.Duration + 19, // 20: voucher.config.Data.Redis.writeTimeout:type_name -> google.protobuf.Duration + 19, // 21: voucher.config.Data.Redis.connMaxIdleTime:type_name -> google.protobuf.Duration + 4, // 22: voucher.config.RocketMQ.EventMapEntry.value:type_name -> voucher.config.EventMap + 16, // 23: voucher.config.Cron.CommandMapEntry.value:type_name -> voucher.config.Cron.CommandMap + 19, // 24: voucher.config.RdsMQ.Queue.waitTime:type_name -> google.protobuf.Duration + 25, // [25:25] is the sub-list for method output_type + 25, // [25:25] is the sub-list for method input_type + 25, // [25:25] is the sub-list for extension type_name + 25, // [25:25] is the sub-list for extension extendee + 0, // [0:25] is the sub-list for field type_name } func init() { file_conf_conf_proto_init() } @@ -1605,7 +1775,7 @@ func file_conf_conf_proto_init() { } } file_conf_conf_proto_msgTypes[10].Exporter = func(v any, i int) any { - switch v := v.(*Logs); i { + switch v := v.(*RdsMQ); i { case 0: return &v.state case 1: @@ -1617,7 +1787,7 @@ func file_conf_conf_proto_init() { } } file_conf_conf_proto_msgTypes[11].Exporter = func(v any, i int) any { - switch v := v.(*Server_HTTP); i { + switch v := v.(*Logs); i { case 0: return &v.state case 1: @@ -1629,7 +1799,7 @@ func file_conf_conf_proto_init() { } } file_conf_conf_proto_msgTypes[12].Exporter = func(v any, i int) any { - switch v := v.(*Data_Database); i { + switch v := v.(*Server_HTTP); i { case 0: return &v.state case 1: @@ -1641,6 +1811,18 @@ func file_conf_conf_proto_init() { } } file_conf_conf_proto_msgTypes[13].Exporter = func(v any, i int) any { + switch v := v.(*Data_Database); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_conf_conf_proto_msgTypes[14].Exporter = func(v any, i int) any { switch v := v.(*Data_Redis); i { case 0: return &v.state @@ -1652,7 +1834,7 @@ func file_conf_conf_proto_init() { return nil } } - file_conf_conf_proto_msgTypes[15].Exporter = func(v any, i int) any { + file_conf_conf_proto_msgTypes[16].Exporter = func(v any, i int) any { switch v := v.(*Cron_CommandMap); i { case 0: return &v.state @@ -1664,6 +1846,18 @@ func file_conf_conf_proto_init() { return nil } } + file_conf_conf_proto_msgTypes[18].Exporter = func(v any, i int) any { + switch v := v.(*RdsMQ_Queue); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -1671,7 +1865,7 @@ func file_conf_conf_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_conf_conf_proto_rawDesc, NumEnums: 0, - NumMessages: 17, + NumMessages: 19, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/conf/conf.proto b/internal/conf/conf.proto index 707713b..cb15acf 100644 --- a/internal/conf/conf.proto +++ b/internal/conf/conf.proto @@ -15,6 +15,7 @@ message Bootstrap { WechatNotifyMQ wechatNotifyMQ = 7; Alarm alarm = 8; Cron cron = 9; + RdsMQ rdsMQ = 10; } message Server { @@ -118,6 +119,18 @@ message Cron { map commandMap = 2; } +message RdsMQ { + message Queue { + string name = 1; + bool isOpen = 2; + uint32 retryNum = 3; + uint32 numWorkers = 4; + google.protobuf.Duration waitTime = 5; + } + Queue wechatQuery = 1; + Queue wechatRetry = 2; +} + message Logs { string business = 1; string access = 2; diff --git a/internal/data/repoimpl/order.go b/internal/data/repoimpl/order.go index a8efa66..d00826f 100644 --- a/internal/data/repoimpl/order.go +++ b/internal/data/repoimpl/order.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "github.com/go-kratos/kratos/v2/log" "gorm.io/gorm" "time" "unicode/utf8" @@ -31,6 +30,61 @@ func (p *OrderRepoImpl) DB(ctx context.Context) *gorm.DB { return p.db.DB(ctx).Model(model.Order{}) } +func (p *OrderRepoImpl) FinSucByStockIdInBatches(ctx context.Context, batchNo string, fun func(ctx context.Context, rows []*bo.OrderBo) error) error { + + var results = make([]*model.Order, 0) + + result := p.DB(ctx). + Where("batch_no = ?", batchNo). + Where("status = ?", vo.OrderStatusSuccess.GetValue()). + FindInBatches(&results, 50, func(tx *gorm.DB, batch int) error { + return fun(ctx, p.ToBos(results)) + }) + + if result.Error != nil { + return result.Error + } + + return nil +} + +func (p *OrderRepoImpl) FinFailByStockIdInBatches(ctx context.Context, batchNo string, fun func(ctx context.Context, rows []*bo.OrderBo) error) error { + + var results = make([]*model.Order, 0) + + result := p.DB(ctx). + Where("batch_no = ?", batchNo). + Where("status = ?", vo.OrderStatusFail.GetValue()). + Where("remark <> ?", "error: code = 500 reason = WechatFAIL message = 微信返回错误:该用户账号异常,无法领券。商家可联系微信支付或让用户联系微信支付客服处理。 metadat"). + FindInBatches(&results, 50, func(tx *gorm.DB, batch int) error { + return fun(ctx, p.ToBos(results)) + }) + + if result.Error != nil { + return result.Error + } + + return nil +} + +func (p *OrderRepoImpl) FindIngInBatches(ctx context.Context, fun func(ctx context.Context, rows []*bo.OrderBo) error) error { + + var results = make([]*model.Order, 0) + + result := p.DB(ctx). + Where("status = ?", vo.OrderStatusIng.GetValue()). + Limit(100). + FindInBatches(&results, 20, func(tx *gorm.DB, batch int) error { + return fun(ctx, p.ToBos(results)) + }) + + if result.Error != nil { + return result.Error + } + + return nil +} + func (p *OrderRepoImpl) FindInBatches(ctx context.Context, w *bo.FindInBatchesUseBo, fun func(ctx context.Context, rows []*bo.OrderBo) error) error { var results = make([]*model.Order, 0) @@ -75,12 +129,9 @@ func (p *OrderRepoImpl) Create(ctx context.Context, req *bo.OrderBo) (*bo.OrderB UpdateTime: &now, } - db := p.DB(ctx) - tx := db.Create(info) + tx := p.DB(ctx).Create(info) if tx.Error != nil { - sqlDB, _ := db.DB() - log.Warnf("order create 当前打开连接数:%d,空闲连接数:%d", sqlDB.Stats().OpenConnections, sqlDB.Stats().Idle) return nil, fmt.Errorf("create db fail %w", tx.Error) } @@ -91,8 +142,7 @@ func (p *OrderRepoImpl) GetByOutBizNo(ctx context.Context, t vo.OrderType, outBi info := &model.Order{} - db := p.DB(ctx) - tx := db.Where(model.Order{Type: t.GetValue(), OutBizNo: outBizNo}).First(&info) + tx := p.DB(ctx).Where(model.Order{Type: t.GetValue(), OutBizNo: outBizNo}).First(&info) if tx.Error != nil { @@ -100,11 +150,6 @@ func (p *OrderRepoImpl) GetByOutBizNo(ctx context.Context, t vo.OrderType, outBi return nil, err2.ErrorDbNotFound("订单数据不存在") } - if errors.Is(tx.Error, context.DeadlineExceeded) { - sqlDB, _ := db.DB() - log.Warnf("order当前打开连接数:%d,空闲连接数:%d", sqlDB.Stats().OpenConnections, sqlDB.Stats().Idle) - } - return nil, fmt.Errorf("order db fail %w", tx.Error) } @@ -199,8 +244,7 @@ func (p *OrderRepoImpl) Success(ctx context.Context, id uint64, voucherNo string tx := p.DB(ctx). Where(model.Order{ - ID: id, - Status: vo.OrderStatusIng.GetValue(), + ID: id, }). Updates(model.Order{ Status: vo.OrderStatusSuccess.GetValue(), diff --git a/internal/data/repoimpl/wechat_notify_register_tag.go b/internal/data/repoimpl/wechat_notify_register_tag.go index 10a5feb..d26e6f7 100644 --- a/internal/data/repoimpl/wechat_notify_register_tag.go +++ b/internal/data/repoimpl/wechat_notify_register_tag.go @@ -71,8 +71,7 @@ func (p *WechatNotifyRegisterTagRepoImpl) Success(ctx context.Context, id int32) res := p.DB(ctx). Where(model.WechatNotifyRegisterTag{ - ID: id, - Status: vo.WechatNotifyRegisterTagStatusWait.GetValue(), + ID: id, }). Updates(model.WechatNotifyRegisterTag{ Status: vo.WechatNotifyRegisterTagStatusSuccess.GetValue(), @@ -99,8 +98,7 @@ func (p *WechatNotifyRegisterTagRepoImpl) Fail(ctx context.Context, id int32, re res := p.DB(ctx). Where(model.WechatNotifyRegisterTag{ - ID: id, - Status: vo.WechatNotifyRegisterTagStatusWait.GetValue(), + ID: id, }). Updates(model.WechatNotifyRegisterTag{ Status: vo.WechatNotifyRegisterTagStatusFail.GetValue(), diff --git a/internal/data/wechatrepoimpl/cpn.go b/internal/data/wechatrepoimpl/cpn.go index c1ab450..0bc9ea8 100644 --- a/internal/data/wechatrepoimpl/cpn.go +++ b/internal/data/wechatrepoimpl/cpn.go @@ -88,7 +88,11 @@ func (c *CpnRepoImpl) Order(ctx context.Context, order *bo.OrderBo) (string, err if err != nil { - return "", c.bodyErr(ctx, result) + if result.Response != nil && result.Response.Body != nil { + return "", c.bodyErr(ctx, result) + } + + return "", err } return *resp.CouponId, nil @@ -112,7 +116,11 @@ func (c *CpnRepoImpl) Query(ctx context.Context, orderWechat *bo.OrderBo) (vo.Or resp, result, err := svc.QueryCoupon(ctx, req) if err != nil { - return 0, c.bodyErr(ctx, result) + if result.Response != nil && result.Response.Body != nil { + return 0, c.bodyErr(ctx, result) + } + + return 0, err } return CpnStatus(*resp.Status).GetStatus() @@ -139,7 +147,11 @@ func (c *CpnRepoImpl) QueryProduct(ctx context.Context, stockCreatorMchId, stock if err != nil { - return nil, c.bodyErr(ctx, result) + if result.Response != nil && result.Response.Body != nil { + return nil, c.bodyErr(ctx, result) + } + + return nil, err } return response, nil @@ -160,7 +172,11 @@ func (c *CpnRepoImpl) QueryCallback(ctx context.Context) (*cashcoupons.Callback, if err != nil { - return nil, c.bodyErr(ctx, result) + if result.Response != nil && result.Response.Body != nil { + return nil, c.bodyErr(ctx, result) + } + + return nil, err } return response, nil @@ -183,7 +199,11 @@ func (c *CpnRepoImpl) SetCallback(ctx context.Context, url string) (*cashcoupons if err != nil { - return nil, c.bodyErr(ctx, result) + if result.Response != nil && result.Response.Body != nil { + return nil, c.bodyErr(ctx, result) + } + + return nil, err } return response, nil diff --git a/internal/data/wechatrepoimpl/cpn_code.go b/internal/data/wechatrepoimpl/cpn_code.go index 4b2c281..4dfe297 100644 --- a/internal/data/wechatrepoimpl/cpn_code.go +++ b/internal/data/wechatrepoimpl/cpn_code.go @@ -249,6 +249,7 @@ const ( ErrorWechatNaturalPersonRuleBlocked = "被自然人规则拦截" ErrorWechatResourceNotExists = "批次不存在" ErrorWechatFrequencyLimited = "当前请求人数过多,请稍后重试" + ErrorWechatAccountFail = "该用户账号异常,无法领券。商家可联系微信支付或让用户联系微信支付客服处理。" ) // WechatError 映射错误描述到具体的错误处理 @@ -280,6 +281,7 @@ var WechatError = map[string]*errors.Error{ ErrorWechatNaturalPersonRuleBlocked: err2.ErrorWechatNaturalPersonRuleBlocked(ErrorWechatNaturalPersonRuleBlocked), ErrorWechatResourceNotExists: err2.ErrorWechatResourceNotExists(ErrorWechatResourceNotExists), ErrorWechatFrequencyLimited: err2.ErrorWechatFrequencyLimited(ErrorWechatFrequencyLimited), + ErrorWechatAccountFail: err2.ErrorWechatAccountFail(ErrorWechatAccountFail), } type ErrBody struct { diff --git a/internal/pkg/helper/utils_test.go b/internal/pkg/helper/utils_test.go index 5ab23c1..64996c6 100644 --- a/internal/pkg/helper/utils_test.go +++ b/internal/pkg/helper/utils_test.go @@ -3,9 +3,25 @@ package helper import ( "fmt" "testing" + "time" ) func TestHashMod(t *testing.T) { serverId := HashMod("1dfsfdsfsddf12dddd5451212iodewnsanf2") fmt.Println(serverId) } + +func TestNoticeTime(t *testing.T) { + now := time.Now() + + // 获取七天前的日期 + noticeStartDay := now.AddDate(0, 0, -29) + // 获取七天前 00:00:00 的时间 + startTime := time.Date(noticeStartDay.Year(), noticeStartDay.Month(), noticeStartDay.Day(), 0, 0, 0, 0, noticeStartDay.Location()) + + noticeEndDay := now.AddDate(0, 0, -28) + // 获取昨天 23:59:59 的时间 + endTime := time.Date(noticeEndDay.Year(), noticeEndDay.Month(), noticeEndDay.Day(), 23, 59, 59, 0, noticeEndDay.Location()) + + t.Logf("startTime:%s,endTime:%s", startTime, endTime) +} diff --git a/internal/pkg/mq_http/mq_http_test.go b/internal/pkg/mq_http/mq_http_test.go index 81a903c..47e5758 100644 --- a/internal/pkg/mq_http/mq_http_test.go +++ b/internal/pkg/mq_http/mq_http_test.go @@ -37,7 +37,7 @@ func Test_WechatNotifyProducer(t *testing.T) { func Test_WechatNotifyProducer2(t *testing.T) { - tag := "voucher_notify_dev" + tag := "voucher_notify_pro" bodyStr := `{"id":"5465699d-de6a-5414-a8df-283167b577ca", "create_time":"2025-03-07T15:57:24+08:00", @@ -48,15 +48,15 @@ func Test_WechatNotifyProducer2(t *testing.T) { "associated_data":"coupon", "plain_text":{ "stock_creator_mchid":"1652465541", -"stock_id":"20259610", -"coupon_id":"97225743207", +"stock_id":"20393435", +"coupon_id":"101423873113", "coupon_name":"test", "description":"","status":"USED", "create_time":"2025-03-07T15:49:31+08:00", "coupon_type":"NORMAL", "no_cash":false, "singleitem":false, -"consume_information":{"consume_time":"2025-03-07T15:57:24+08:00","consume_mchid":"1800002761","transaction_id":"4200002544202503077103159055"}}}` +"consume_information":{"consume_time":"2025-05-13T15:57:24+08:00","consume_mchid":"1800002761","transaction_id":"4200002544202503077103159055"}}}` if err := wechatNotifyProducer(tag, bodyStr); err != nil { t.Errorf("入队失败 error = %v", err) diff --git a/internal/pkg/rdsmq/logger.go b/internal/pkg/rdsmq/logger.go new file mode 100644 index 0000000..f50ba65 --- /dev/null +++ b/internal/pkg/rdsmq/logger.go @@ -0,0 +1,15 @@ +package rdsmq + +type Logger interface { + // Debugf logs a formatted debugging message. + Debugf(format string, args ...interface{}) + + // Infof logs a formatted informational message. + Infof(format string, args ...interface{}) + + // Warnf logs a formatted warning message. + Warnf(format string, args ...interface{}) + + // Errorf logs a formatted error message. + Errorf(format string, args ...interface{}) +} diff --git a/internal/pkg/rdsmq/manager.go b/internal/pkg/rdsmq/manager.go new file mode 100644 index 0000000..517a571 --- /dev/null +++ b/internal/pkg/rdsmq/manager.go @@ -0,0 +1,47 @@ +package rdsmq + +import ( + "context" + "fmt" + "sync" +) + +// ConsumerManager 消费者管理器 +type ConsumerManager struct { + ConsumerConfigs []*ConsumeConfig +} + +func NewConsumerManager() *ConsumerManager { + return &ConsumerManager{} +} + +func (r *ConsumerManager) Add(config *ConsumeConfig) { + r.ConsumerConfigs = append(r.ConsumerConfigs, config) +} + +func (r *ConsumerManager) Start(ctx context.Context) { + var wg sync.WaitGroup + for _, c := range r.ConsumerConfigs { + if _, err := c.Rdb.Ping(ctx).Result(); err != nil { + panic(fmt.Sprintf("Redis connection failed: %v", err)) + } + + wg.Add(1) + go func(co *ConsumeConfig) { + defer func() { + wg.Done() + if err := recover(); err != nil { + fmt.Printf("panic", err) + } + }() + co.Start(ctx) + }(c) + } + wg.Wait() +} + +func (r *ConsumerManager) Stop(ctx context.Context) { + for _, c := range r.ConsumerConfigs { + c.Stop(ctx) + } +} diff --git a/internal/pkg/rdsmq/rdsmq.go b/internal/pkg/rdsmq/rdsmq.go new file mode 100644 index 0000000..e1ff23f --- /dev/null +++ b/internal/pkg/rdsmq/rdsmq.go @@ -0,0 +1,156 @@ +package rdsmq + +import ( + "context" + "fmt" + "github.com/redis/go-redis/v9" + "sync" + "time" +) + +type ConsumeConfig struct { + // Redis + Rdb *redis.Client + // 队列名称 + QueueName string + // 限制启用的协程数量 + NumWorkers uint32 + // 处理完成后等待时间 + WaitTime time.Duration + // 重试次数 + RetryNum uint32 + // 消费函数 + Fn func(context.Context, string) error + // 日志 + Logger Logger + // 协程控制 + numWorkersChan chan struct{} +} + +func (r *ConsumeConfig) init(_ context.Context) { + if r.RetryNum > 5 { + panic("RetryNum must be less than 5") + } + + if r.NumWorkers == 0 { + r.NumWorkers = 10 + } + + r.numWorkersChan = make(chan struct{}, r.NumWorkers) +} + +func (r *ConsumeConfig) Start(ctx context.Context) { + fmt.Printf("RdsMQ Starting to dequeue from [%s]", r.QueueName) + + r.init(ctx) + defer r.close(ctx) + + var wg sync.WaitGroup + for { + // 使用 BLPop 获取消息 + results, err := r.Rdb.BLPop(ctx, 0, r.QueueName).Result() + if err != nil { + r.Logger.Errorf("BLPop on %s Failed to get message: %v", r.QueueName, err) + time.Sleep(time.Second * 10) // 等待一段时间 + continue + } + + // 处理消息 + if len(results) < 2 { + panic(fmt.Sprintf("Invalid result length from BLPop on [%s]: %v", r.QueueName, results)) + } + + key := results[0] // 队列名称 + value := results[1] // 消息内容 + + wg.Add(1) + go func(key, value string) { + + defer func() { + wg.Done() + if err := recover(); err != nil { + r.Logger.Errorf("rds panic [%v]", err) + } + }() + + r.numWorkersChan <- struct{}{} // 获取信号量 + defer func() { <-r.numWorkersChan }() // 释放信号量 + + // 处理消息的业务逻辑 + r.consumer(ctx, key, value) + }(key, value) + + if r.WaitTime > 0 { + // 可选:合理设置等待时间 + time.Sleep(r.WaitTime) // 等待一段时间 + } + } + wg.Wait() +} + +func (r *ConsumeConfig) consumer(ctx context.Context, queueName string, value string) { + if err := r.Fn(ctx, value); err == nil { + r.Logger.Errorf("BLPop on %s Failed to process message [%s] after retry %d times, err[%v]\n", queueName, value, r.RetryNum, err) + return + } + + if r.RetryNum > 0 { + r.retry(ctx, queueName, value) + } +} + +func (r *ConsumeConfig) retry(ctx context.Context, queueName string, value string) { + retryNum := uint32(0) + retryInterval := 5 * time.Second // 初始重试间隔 + + // 创建一个定时器,定期尝试处理消息 + lockTicker := time.NewTicker(retryInterval) + defer lockTicker.Stop() + + for { + select { + case <-lockTicker.C: + if retryNum < r.RetryNum { + + err := r.Fn(ctx, value) + if err == nil { + return // 成功处理消息,退出 + } + + // 错误分类处理 + if isRecoverableError(err) { + r.Logger.Errorf("BLPop on %s Failed to process message [%s] after retry %d times, err[%v]\n", queueName, value, retryNum, err) + retryNum++ + // 动态调整重试间隔 + retryInterval = time.Duration(float64(retryInterval) * 1.5) // 每次增加 50% + lockTicker.Reset(retryInterval) // 重置定时器 + } else { + r.Logger.Errorf("BLPop on %s Non-recoverable error for message [%s]: %v\n", queueName, value, err) + return // 立即退出 + } + } else { + r.Logger.Warnf("BLPop on %s Max retries reached for message [%s]\n", queueName, value) + return // 达到最大重试次数,退出 + } + case <-ctx.Done(): + r.Logger.Warnf("BLPop on %s push consumer close tick. [%s]\n", queueName, value) + return + } + } +} + +// 示例函数,判断错误是否可恢复 +func isRecoverableError(_ error) bool { + // 根据具体情况实现错误分类逻辑 + return true // 假设所有错误都可恢复,实际情况中应进行详细判断 +} + +func (r *ConsumeConfig) close(_ context.Context) { + if r.numWorkersChan != nil { + close(r.numWorkersChan) + } +} + +func (r *ConsumeConfig) Stop(ctx context.Context) { + r.close(ctx) +} diff --git a/internal/server/http.go b/internal/server/http.go index 3e0023e..c89501c 100644 --- a/internal/server/http.go +++ b/internal/server/http.go @@ -38,6 +38,9 @@ func NewHTTPServer( srv.Route("/voucher/").GET("notifyRetry/{id}", cmb.NotifyRetry) srv.Route("/voucher/").GET("queryOrder/{order_no}", cmb.QueryOrder) + srv.Route("/voucher/").GET("registerTag/{product_no}", cmb.RegisterTag) + srv.Route("/voucher/").GET("pushWechatQuery/{product_no}", cmb.PushWechatQuery) + srv.Route("/voucher/").GET("pushWechatRetry/{product_no}", cmb.PushWechatRetry) v1.RegisterCmbHTTPServer(srv, cmb) diff --git a/internal/server/provider_set.go b/internal/server/provider_set.go index 13460f0..00d6438 100644 --- a/internal/server/provider_set.go +++ b/internal/server/provider_set.go @@ -10,4 +10,5 @@ var ProviderSetServer = wire.NewSet( NewConsumer, NewWechatNotifyConsumer, NewCronServer, + NewRdbConsumer, ) diff --git a/internal/server/rds_consume.go b/internal/server/rds_consume.go new file mode 100644 index 0000000..9eed6ef --- /dev/null +++ b/internal/server/rds_consume.go @@ -0,0 +1,50 @@ +package server + +import ( + "context" + "fmt" + "github.com/go-kratos/kratos/v2/log" + "github.com/go-kratos/kratos/v2/transport" + "voucher/internal/conf" + "voucher/internal/pkg/rdsmq" + "voucher/internal/service" +) + +var _ transport.Server = (*RdbConsumer)(nil) + +type RdbConsumer struct { + hLog *log.Helper + conf *conf.Bootstrap + manager *rdsmq.ConsumerManager + voucherService *service.VoucherService +} + +func NewRdbConsumer( + hLog *log.Helper, + conf *conf.Bootstrap, + voucherService *service.VoucherService, +) *RdbConsumer { + manager := rdsmq.NewConsumerManager() + + if cf := voucherService.GetConfig(); cf != nil { + manager.Add(cf) + } + + if cf2 := voucherService.GetWechatConfig(); cf2 != nil { + manager.Add(cf2) + } + + return &RdbConsumer{hLog: hLog, conf: conf, manager: manager} +} + +func (c *RdbConsumer) Start(ctx context.Context) error { + c.manager.Start(ctx) + return nil +} + +func (c *RdbConsumer) Stop(ctx context.Context) error { + fmt.Println("关闭 RdbConsumer 中...") + c.manager.Stop(ctx) + fmt.Println("关闭 RdbConsumer 完成...") + return nil +} diff --git a/internal/server/wechat_notify_consumer.go b/internal/server/wechat_consumer.go similarity index 76% rename from internal/server/wechat_notify_consumer.go rename to internal/server/wechat_consumer.go index d80ad2c..3017144 100644 --- a/internal/server/wechat_notify_consumer.go +++ b/internal/server/wechat_consumer.go @@ -8,6 +8,7 @@ import ( "github.com/go-kratos/kratos/v2/transport" "github.com/gogap/errors" "strings" + "sync/atomic" "time" "voucher/internal/conf" "voucher/internal/service" @@ -18,6 +19,9 @@ var _ transport.Server = (*WechatNotifyConsumer)(nil) type WechatNotifyConsumer struct { conf *conf.Bootstrap voucherService *service.VoucherService + + activeCnt atomic.Int32 //当前正在处理业务的个数,用于退出时业务平滑的退出 + shutdownFlag atomic.Bool // 关闭标记 } func NewWechatNotifyConsumer( @@ -128,16 +132,67 @@ func (w *WechatNotifyConsumer) consumeMessages(mqConsumer mq_http_sdk.MQConsumer // 业务逻辑处理 func (w *WechatNotifyConsumer) processMessage(msg mq_http_sdk.ConsumeMessageEntry) { - log.Warnf("微信回调消费接收消息成功 messageId:%s, messageTag:%s, message: %s", msg.MessageId, msg.MessageTag, msg.MessageBody) + // 收到消息 + if w.shutdownFlag.Load() { + fmt.Println("wechat consumer 正在退出中,延期处理") + // 卡住,不再继续消费,等待退出 + time.Sleep(24 * time.Hour) + return + } + + // 标记活跃状态 + w.activeCnt.Add(1) + defer func() { + w.activeCnt.Add(-1) + if v := recover(); v != nil { + log.Errorf("wechat consumer 处理消息panic, ,%+v", v) + return + } + }() ctx := context.Background() if err := w.voucherService.WechatUseNotifyConsumer(ctx, msg.MessageTag, msg.MessageBody); err != nil { - log.Errorf("微信回调消费处理失败:%+v", err) + log.Errorf("微信回调消费接收消息成功,处理失败 messageId:%s, messageTag:%s, message:%s, err:%+v", msg.MessageId, msg.MessageTag, msg.MessageBody, err) } } // Stop 停止消息消费 func (w *WechatNotifyConsumer) Stop(_ context.Context) error { - fmt.Println("关闭 wechat consumer 中...") + + if !w.conf.WechatNotifyMQ.IsOpenConsumer { + fmt.Println("wechat consumer 关闭完成!") + return nil + } + + fmt.Println("wechat consumer 关闭中...") + + w.shutdownFlag.Store(true) + + //shutdown之间,保证正在处理的消费先提交 + _ = w.blockWaitFinish() + + fmt.Println("wechat consumer 关闭完成") + + return nil +} + +// blockWaitFinish 阻塞等待业务完成 +func (c *WechatNotifyConsumer) blockWaitFinish() error { + // 每1s检查下业务是否都处理完成 + + for { + cnt := c.activeCnt.Load() + if cnt == 0 { + fmt.Println("wechat consumer 无业务处理,正常退") + break + } else { + fmt.Printf("wechat consumer 等待消费者退出,%d 个正在运行\n", cnt) + } + time.Sleep(1 * time.Second) + } + + //防止极端情况下commit未完成 + // nolint + time.Sleep(1 * time.Second) return nil } diff --git a/internal/service/cmb.go b/internal/service/cmb.go index 4a76c18..bcbdc12 100644 --- a/internal/service/cmb.go +++ b/internal/service/cmb.go @@ -2,8 +2,12 @@ package service import ( "context" + "fmt" "github.com/go-kratos/kratos/v2/log" + "github.com/go-kratos/kratos/v2/transport/http" "github.com/robfig/cron" + http2 "net/http" + "strconv" v1 "voucher/api/v1" "voucher/internal/biz" "voucher/internal/biz/bo" @@ -55,3 +59,90 @@ func (c *CmbService) GetResponse(ctx context.Context, replyBizContent []byte) (* return reply, nil } + +func (this *CmbService) NotifyRetry(ctx http.Context) error { + id := ctx.Vars().Get("id") + if id == "" { + return fmt.Errorf("id is empty") + } + + orderNotifyId, err := strconv.ParseUint(id, 10, 64) + if err != nil { + return err + } + + return this.VoucherBiz.PushNotifyRetryDelayMQ(ctx, 1, orderNotifyId) +} + +func (this *CmbService) QueryOrder(ctx http.Context) error { + + orderNo := ctx.Vars().Get("order_no") + if orderNo == "" { + return fmt.Errorf("orderNo is empty") + } + + str, err := this.VoucherBiz.QueryOrder(ctx, orderNo) + if err != nil { + return err + } + + return ctx.JSON(http2.StatusOK, map[string]interface{}{ + "data": str, + }) +} + +func (c *CmbService) OrderRetry(ctx context.Context, request *v1.OrderRetryRequest) (*v1.Empty, error) { + + return nil, c.VoucherBiz.OrderRetry(ctx, request.GetTransactionIds()) +} + +func (this *CmbService) RegisterTag(ctx http.Context) error { + + productNo := ctx.Vars().Get("product_no") + if productNo == "" { + return fmt.Errorf("product_no is empty") + } + + err := this.VoucherBiz.RegisterTag(ctx, productNo) + if err != nil { + return err + } + + return ctx.JSON(http2.StatusOK, map[string]interface{}{ + "data": productNo, + }) +} + +func (this *CmbService) PushWechatQuery(ctx http.Context) error { + + productNo := ctx.Vars().Get("product_no") + if productNo == "" { + return fmt.Errorf("product_no is empty") + } + + err := this.VoucherBiz.PushWechatQuery(ctx, productNo) + if err != nil { + return err + } + + return ctx.JSON(http2.StatusOK, map[string]interface{}{ + "data": productNo, + }) +} + +func (this *CmbService) PushWechatRetry(ctx http.Context) error { + + productNo := ctx.Vars().Get("product_no") + if productNo == "" { + return fmt.Errorf("product_no is empty") + } + + err := this.VoucherBiz.PushWechatRetry(ctx, productNo) + if err != nil { + return err + } + + return ctx.JSON(http2.StatusOK, map[string]interface{}{ + "data": productNo, + }) +} diff --git a/internal/service/cmb_mock.go b/internal/service/cmb_mock.go deleted file mode 100644 index a66bb9c..0000000 --- a/internal/service/cmb_mock.go +++ /dev/null @@ -1,148 +0,0 @@ -package service - -import ( - "context" - "encoding/json" - "fmt" - "github.com/go-kratos/kratos/v2/transport/http" - http2 "net/http" - "strconv" - "strings" - v1 "voucher/api/v1" - "voucher/internal/biz/bo" -) - -func (s *CmbService) OrderMock(ctx context.Context, request *v1.CmbOrderRequest) (*v1.CmbRequest, error) { - - bizJsonBytes, err := json.Marshal(request) - if err != nil { - return nil, err - } - - reply, err := s.CmbMixRepo.GetMockRequest(ctx, string(bizJsonBytes)) - if err != nil { - return nil, err - } - - return reply, nil -} - -func (s *CmbService) QueryMock(ctx context.Context, request *v1.CmbQueryRequest) (*v1.CmbRequest, error) { - - bizJsonBytes, err := json.Marshal(request) - if err != nil { - return nil, err - } - - reply, err := s.CmbMixRepo.GetMockRequest(ctx, string(bizJsonBytes)) - if err != nil { - return nil, err - } - - return reply, nil -} - -func (s *CmbService) QueryProductMock(ctx context.Context, request *v1.CmbQueryProductRequest) (*v1.CmbRequest, error) { - - bizJsonBytes, err := json.Marshal(request) - if err != nil { - return nil, err - } - - reply, err := s.CmbMixRepo.GetMockRequest(ctx, string(bizJsonBytes)) - if err != nil { - return nil, err - } - - return reply, nil -} - -func (s *CmbService) DecryptBody(ctx context.Context, request *v1.EncryptBodyRequest) (*v1.DecryptBodyReply, error) { - - decryptBody, err := s.CmbMixRepo.Decrypt(ctx, request.EncryptBody) - if err != nil { - return nil, err - } - - return &v1.DecryptBodyReply{ - DecryptBody: decryptBody, - }, nil -} - -func (s *CmbService) Test(ctx context.Context, _ *v1.Empty) (*v1.Empty, error) { - - if err := s.VoucherBiz.ExecuteNotice(ctx); err != nil { - return nil, err - } - - return nil, nil -} - -func (this *CmbService) NotifyRetry(ctx http.Context) error { - - id := ctx.Vars().Get("id") - if id == "" { - return fmt.Errorf("id is empty") - } - - orderNotifyId, err := strconv.ParseUint(id, 10, 64) - if err != nil { - return err - } - - return this.VoucherBiz.PushNotifyRetryDelayMQ(ctx, 1, orderNotifyId) -} - -func (this *CmbService) QueryOrder(ctx http.Context) error { - - orderNo := ctx.Vars().Get("order_no") - if orderNo == "" { - return fmt.Errorf("orderNo is empty") - } - - str, err := this.VoucherBiz.QueryOrder(ctx, orderNo) - if err != nil { - return err - } - - return ctx.JSON(http2.StatusOK, map[string]string{"data": str}) -} - -func (this *CmbService) queryOrder(r *http.Request) (*bo.OrderBo, error) { - - orderNo, err := this.queryByOrderNo(r) - if err != nil { - return nil, err - } - - order, err := this.VoucherBiz.OrderQuery(r.Context(), orderNo) - if err != nil { - return nil, err - } - - return order, nil -} - -func (this *CmbService) queryByOrderNo(r *http.Request) (string, error) { - - ip := r.Header.Get("X-Forwarded-For") - if len(ip) == 0 { - ip = r.RemoteAddr - } - if ip != "117.175.169.61" && ip != "127.0.0.1" { - return "", fmt.Errorf("ip check fail,IP:%s", ip) - } - - path := r.URL.Path - parts := strings.Split(path, "/") - if len(parts) == 0 { - return "", fmt.Errorf("发起请求格式有误:%s", path) - } - - orderNo := parts[len(parts)-1] - if len(orderNo) == 0 { - return "", fmt.Errorf("order is nil") - } - - return orderNo, nil -} diff --git a/internal/service/mock.go b/internal/service/mock.go new file mode 100644 index 0000000..4b71eaa --- /dev/null +++ b/internal/service/mock.go @@ -0,0 +1,64 @@ +package service + +import ( + "context" + "encoding/json" + v1 "voucher/api/v1" +) + +func (s *CmbService) OrderMock(ctx context.Context, request *v1.CmbOrderRequest) (*v1.CmbRequest, error) { + + bizJsonBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + + reply, err := s.CmbMixRepo.GetMockRequest(ctx, string(bizJsonBytes)) + if err != nil { + return nil, err + } + + return reply, nil +} + +func (s *CmbService) QueryMock(ctx context.Context, request *v1.CmbQueryRequest) (*v1.CmbRequest, error) { + + bizJsonBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + + reply, err := s.CmbMixRepo.GetMockRequest(ctx, string(bizJsonBytes)) + if err != nil { + return nil, err + } + + return reply, nil +} + +func (s *CmbService) QueryProductMock(ctx context.Context, request *v1.CmbQueryProductRequest) (*v1.CmbRequest, error) { + + bizJsonBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + + reply, err := s.CmbMixRepo.GetMockRequest(ctx, string(bizJsonBytes)) + if err != nil { + return nil, err + } + + return reply, nil +} + +func (s *CmbService) DecryptBody(ctx context.Context, request *v1.EncryptBodyRequest) (*v1.DecryptBodyReply, error) { + + decryptBody, err := s.CmbMixRepo.Decrypt(ctx, request.EncryptBody) + if err != nil { + return nil, err + } + + return &v1.DecryptBodyReply{ + DecryptBody: decryptBody, + }, nil +} diff --git a/internal/service/cmb_order.go b/internal/service/order.go similarity index 91% rename from internal/service/cmb_order.go rename to internal/service/order.go index 3351c1f..398076b 100644 --- a/internal/service/cmb_order.go +++ b/internal/service/order.go @@ -4,13 +4,49 @@ import ( "context" "encoding/json" "github.com/go-kratos/kratos/v2/errors" - "github.com/go-kratos/kratos/v2/log" err2 "voucher/api/err" v1 "voucher/api/v1" "voucher/internal/biz/bo" "voucher/internal/biz/vo" ) +func (c *CmbService) Order(ctx context.Context, request *v1.CmbRequest) (*v1.CmbReply, error) { + + orderNo, err := c.order(ctx, request) + + if err != nil { + return c.OrderFail(ctx, err) + } + + return c.OrderSuccess(ctx, orderNo) +} + +func (c *CmbService) order(ctx context.Context, request *v1.CmbRequest) (string, error) { + + bizContent, err := c.CmbMixRepo.OrderVerify(ctx, request) + if err != nil { + return "", err + } + + boReq := &bo.OrderCreateReqBo{ + OutBizNo: bizContent.TransactionId, + ProductNo: bizContent.ActivityId, + Account: bizContent.CmbUid, + AppID: bizContent.AppId, + Attach: bizContent.Attach, + AccountType: vo.OrderAccountTypeOpenId, + Type: vo.OrderTypeCmb, + NotifyUrl: c.bc.Cmb.NotifyUrl, + } + + orderNo, err := c.VoucherBiz.CmbOrder(ctx, boReq) + if err != nil { + return "", err + } + + return orderNo, nil +} + func (c *CmbService) OrderSuccess(ctx context.Context, orderNo string) (*v1.CmbReply, error) { bizReply := &v1.CmbOrderReply{ @@ -32,8 +68,6 @@ func (c *CmbService) OrderFail(ctx context.Context, err error) (*v1.CmbReply, er se.Reason = err2.CmbErr_CMB_UNKNOWN.String() } - log.Errorf("order fail: %v", se) - bizReply := &v1.CmbOrderReply{ RespCode: vo.CmbResponseStatusFail.GetValue(), RespMsg: se.Message, @@ -45,42 +79,3 @@ func (c *CmbService) OrderFail(ctx context.Context, err error) (*v1.CmbReply, er return c.GetResponse(ctx, replyBizContent) } - -func (c *CmbService) Order(ctx context.Context, request *v1.CmbRequest) (*v1.CmbReply, error) { - - orderNo, err := c.order(ctx, request) - - if err != nil { - return c.OrderFail(ctx, err) - } - - return c.OrderSuccess(ctx, orderNo) -} - -func (c *CmbService) order(ctx context.Context, request *v1.CmbRequest) (string, error) { - - bizContent, err := c.CmbMixRepo.OrderVerify(ctx, request) - if err != nil { - return "", err - } - - ctx2 := context.Background() - - boReq := &bo.OrderCreateReqBo{ - OutBizNo: bizContent.TransactionId, - ProductNo: bizContent.ActivityId, - Account: bizContent.CmbUid, - AppID: bizContent.AppId, - Attach: bizContent.Attach, - AccountType: vo.OrderAccountTypeOpenId, - Type: vo.OrderTypeCmb, - NotifyUrl: c.bc.Cmb.NotifyUrl, - } - - orderNo, err := c.VoucherBiz.CmbOrder(ctx2, boReq) - if err != nil { - return "", err - } - - return orderNo, nil -} diff --git a/internal/service/cmb_product.go b/internal/service/product.go similarity index 100% rename from internal/service/cmb_product.go rename to internal/service/product.go index 3595509..46632a4 100644 --- a/internal/service/cmb_product.go +++ b/internal/service/product.go @@ -10,6 +10,26 @@ import ( "voucher/internal/biz/vo" ) +func (c *CmbService) QueryProduct(ctx context.Context, request *v1.CmbRequest) (*v1.CmbReply, error) { + + bizReply, err := c.queryProduct(ctx, request) + if err != nil { + return c.QueryProductFail(ctx, err) + } + + return c.QueryProductSuccess(ctx, bizReply) +} + +func (c *CmbService) queryProduct(ctx context.Context, request *v1.CmbRequest) (*v1.CmbQueryProductReply, error) { + + bizContent, err := c.CmbMixRepo.ProductQueryVerify(ctx, request) + if err != nil { + return nil, err + } + + return c.VoucherBiz.CmbProductQuery(ctx, bizContent.ActivityId) +} + func (c *CmbService) QueryProductSuccess(ctx context.Context, bizReply *v1.CmbQueryProductReply) (*v1.CmbReply, error) { replyBizContent, _ := json.Marshal(bizReply) @@ -37,23 +57,3 @@ func (c *CmbService) QueryProductFail(ctx context.Context, err error) (*v1.CmbRe return c.GetResponse(ctx, replyBizContent) } - -func (c *CmbService) QueryProduct(ctx context.Context, request *v1.CmbRequest) (*v1.CmbReply, error) { - - bizReply, err := c.queryProduct(ctx, request) - if err != nil { - return c.QueryProductFail(ctx, err) - } - - return c.QueryProductSuccess(ctx, bizReply) -} - -func (c *CmbService) queryProduct(ctx context.Context, request *v1.CmbRequest) (*v1.CmbQueryProductReply, error) { - - bizContent, err := c.CmbMixRepo.ProductQueryVerify(ctx, request) - if err != nil { - return nil, err - } - - return c.VoucherBiz.CmbProductQuery(ctx, bizContent.ActivityId) -} diff --git a/internal/service/cmb_query.go b/internal/service/query.go similarity index 100% rename from internal/service/cmb_query.go rename to internal/service/query.go index 88577a9..433d7db 100644 --- a/internal/service/cmb_query.go +++ b/internal/service/query.go @@ -8,6 +8,27 @@ import ( v1 "voucher/api/v1" ) +func (c *CmbService) Query(ctx context.Context, request *v1.CmbRequest) (*v1.CmbReply, error) { + + orderNo, err := c.query(ctx, request) + + if err != nil { + return c.QueryFail(ctx, err) + } + + return c.QuerySuccess(ctx, orderNo) +} + +func (c *CmbService) query(ctx context.Context, request *v1.CmbRequest) (*v1.CmbQueryReply, error) { + + bizContent, err := c.CmbMixRepo.QueryVerify(ctx, request) + if err != nil { + return nil, err + } + + return c.VoucherBiz.CmbQuery(ctx, bizContent.CodeNo) +} + func (c *CmbService) QuerySuccess(ctx context.Context, bizReply *v1.CmbQueryReply) (*v1.CmbReply, error) { replyBizContent, _ := json.Marshal(bizReply) @@ -29,24 +50,3 @@ func (c *CmbService) QueryFail(ctx context.Context, err error) (*v1.CmbReply, er return c.GetResponse(ctx, replyBizContent) } - -func (c *CmbService) Query(ctx context.Context, request *v1.CmbRequest) (*v1.CmbReply, error) { - - orderNo, err := c.query(ctx, request) - - if err != nil { - return c.QueryFail(ctx, err) - } - - return c.QuerySuccess(ctx, orderNo) -} - -func (c *CmbService) query(ctx context.Context, request *v1.CmbRequest) (*v1.CmbQueryReply, error) { - - bizContent, err := c.CmbMixRepo.QueryVerify(ctx, request) - if err != nil { - return nil, err - } - - return c.VoucherBiz.CmbQuery(ctx, bizContent.CodeNo) -} diff --git a/internal/service/voucher.go b/internal/service/voucher.go index 82dbbbb..c663d55 100644 --- a/internal/service/voucher.go +++ b/internal/service/voucher.go @@ -11,6 +11,7 @@ import ( "voucher/internal/biz" "voucher/internal/biz/bo" "voucher/internal/conf" + "voucher/internal/data" "voucher/internal/pkg/mq" ) @@ -18,17 +19,23 @@ type VoucherService struct { bc *conf.Bootstrap cron *cron.Cron VoucherBiz *biz.VoucherBiz + rdb *data.Rdb + logHelper *log.Helper } func NewVoucherService( bc *conf.Bootstrap, cron *cron.Cron, VoucherBiz *biz.VoucherBiz, + rdb *data.Rdb, + logHelper *log.Helper, ) *VoucherService { return &VoucherService{ bc: bc, cron: cron, VoucherBiz: VoucherBiz, + rdb: rdb, + logHelper: logHelper, } } diff --git a/internal/service/wechat_query.go b/internal/service/wechat_query.go new file mode 100644 index 0000000..35a2a9f --- /dev/null +++ b/internal/service/wechat_query.go @@ -0,0 +1,45 @@ +package service + +import ( + "context" + "fmt" + "github.com/go-kratos/kratos/v2/log" + "voucher/internal/pkg/rdsmq" +) + +func (s *VoucherService) GetConfig() *rdsmq.ConsumeConfig { + + queue := s.bc.RdsMQ.GetWechatQuery() + if queue == nil { + return nil + } + + if !queue.GetIsOpen() { + log.Warn(fmt.Sprintf("[%s]RdsMQ is not open", queue.Name)) + return nil + } + + return &rdsmq.ConsumeConfig{ + Rdb: s.rdb.Rdb, + QueueName: queue.Name, + NumWorkers: queue.NumWorkers, + WaitTime: queue.GetWaitTime().AsDuration(), + RetryNum: queue.RetryNum, + Fn: s.Handle, + Logger: s.logHelper, + } +} + +func (s *VoucherService) Handle(ctx context.Context, batchNo string) error { + + if batchNo == "" { + s.logHelper.Errorf("wechat query error: batchNo is empty") + return nil + } + + if err := s.VoucherBiz.WechatQuery(ctx, batchNo); err != nil { + s.logHelper.Error(err) + } + + return nil +} diff --git a/internal/service/wechat_retry.go b/internal/service/wechat_retry.go new file mode 100644 index 0000000..34cc754 --- /dev/null +++ b/internal/service/wechat_retry.go @@ -0,0 +1,45 @@ +package service + +import ( + "context" + "fmt" + "github.com/go-kratos/kratos/v2/log" + "voucher/internal/pkg/rdsmq" +) + +func (s *VoucherService) GetWechatConfig() *rdsmq.ConsumeConfig { + + queue := s.bc.RdsMQ.GetWechatRetry() + if queue == nil { + return nil + } + + if !queue.GetIsOpen() { + log.Warn(fmt.Sprintf("[%s]RdsMQ is not open", queue.Name)) + return nil + } + + return &rdsmq.ConsumeConfig{ + Rdb: s.rdb.Rdb, + QueueName: queue.Name, + NumWorkers: queue.NumWorkers, + WaitTime: queue.GetWaitTime().AsDuration(), + RetryNum: queue.RetryNum, + Fn: s.HandleWechat, + Logger: s.logHelper, + } +} + +func (s *VoucherService) HandleWechat(ctx context.Context, batchNo string) error { + + if batchNo == "" { + s.logHelper.Errorf("RdsMQ keySend error: batchNo is empty") + return nil + } + + if err := s.VoucherBiz.WechatRetry(ctx, batchNo); err != nil { + s.logHelper.Error(err) + } + + return nil +} diff --git a/third_party/swagger_ui/openapi.yaml b/third_party/swagger_ui/openapi.yaml index eb8440b..368aa0c 100644 --- a/third_party/swagger_ui/openapi.yaml +++ b/third_party/swagger_ui/openapi.yaml @@ -60,6 +60,24 @@ paths: application/json: schema: $ref: '#/components/schemas/api.v1.CmbRequest' + /voucher/cmb/v1/orderRetry: + post: + tags: + - Cmb + operationId: Cmb_OrderRetry + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/api.v1.OrderRetryRequest' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/api.v1.Empty' /voucher/cmb/v1/product/query: post: tags: @@ -132,24 +150,6 @@ paths: application/json: schema: $ref: '#/components/schemas/api.v1.CmbRequest' - /voucher/cmb/v1/test: - post: - tags: - - Cmb - operationId: Cmb_Test - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/api.v1.Empty' - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/api.v1.Empty' components: schemas: api.v1.CmbOrderRequest: @@ -263,5 +263,12 @@ components: properties: encryptBody: type: string + api.v1.OrderRetryRequest: + type: object + properties: + transactionIds: + type: array + items: + type: string tags: - name: Cmb