From 0ddde21287c7931f53a61677a592d39a487a7599 Mon Sep 17 00:00:00 2001 From: ziming Date: Wed, 25 Mar 2026 14:08:29 +0800 Subject: [PATCH 01/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- configs/config.yaml | 7 + internal/biz/multi.go | 52 +- internal/biz/provider_set.go | 2 +- internal/biz/wechat.go | 26 + internal/biz/wechat_notify.go | 4 +- .../biz/wechatrepo/bank_multi_activity.go | 3 + internal/conf/conf.pb.go | 700 ++++++++++-------- internal/conf/conf.proto | 9 + .../wechatrepoimpl/bank_multi_activity.go | 22 + internal/data/wx.go | 36 + .../pkg/wechat/srv/marketing/marketing.go | 17 +- internal/pkg/wechat/utils/aes.go | 87 +++ internal/pkg/wechat/utils/wxpay_utility.go | 33 + .../pkg/wechat/utils/wxpay_utility_test.go | 68 ++ internal/server/http.go | 6 + internal/service/notify.go | 79 ++ internal/service/provider_set.go | 1 + internal/service/voucher.go | 2 +- test/bank_multi_activity.go | 33 + test/bank_multi_activity_test.go | 67 ++ test/coupon.go | 59 +- test/coupon_test.go | 18 +- 22 files changed, 980 insertions(+), 351 deletions(-) create mode 100644 internal/biz/wechat.go create mode 100644 internal/pkg/wechat/utils/aes.go create mode 100644 internal/pkg/wechat/utils/wxpay_utility_test.go create mode 100644 internal/service/notify.go diff --git a/configs/config.yaml b/configs/config.yaml index 6c00b25..cca15ea 100644 --- a/configs/config.yaml +++ b/configs/config.yaml @@ -53,6 +53,13 @@ wechat: mchCertificateSerialNumber: "6006B8208815DB5EAC5BF2E783CB9D34082C3772" #商户证书序列号 wechatPayPublicKeyID: "PUB_KEY_ID_0117109533612025031800326400002563" #微信支付公钥ID +wechatSubject: + - mchID: "1100040695" # 证书所属商户 + name: "福建峦峰网络科技有限公司" + mchApiV3Key: "d9af70585b18ae206d981548c766563f" + mchCertificateSerialNumber: "46712853869DB0EDAA9B4DF97DADEECD4CCDC85B" #商户证书序列号 + wechatPayPublicKeyID: "PUB_KEY_ID_0111000406952026032500382251001000" #微信支付公钥ID,有它则为新的“公私钥”模式 + cmb: sm2Prk: "8d39ff3d2559258c163f4510f082727f51531e1953ab203d5ab1ea4a6d94fd73" sm2Puk: "04d827a7dbaaa358ce45b8c7794a7f54819f5c175005a702370e47f135ef6f5f9732758b1474f218419fe9e87f90c28c3b05f08254c651db27df35fae67b77b2e4" # 公钥,给到招行密钥 diff --git a/internal/biz/multi.go b/internal/biz/multi.go index 21d7611..9869e38 100644 --- a/internal/biz/multi.go +++ b/internal/biz/multi.go @@ -7,8 +7,6 @@ import ( "fmt" "github.com/go-kratos/kratos/v2/log" "gorm.io/gorm" - "sync" - err2 "voucher/api/err" v1 "voucher/api/v1" "voucher/internal/biz/bo" "voucher/internal/biz/cmb" @@ -29,9 +27,6 @@ type MultiBiz struct { MultiNotifyDataRepo repo.MultiNotifyDataRepo MultiNotifyLogRepo repo.MultiNotifyLogRepo CmbMixRepo mixrepos.CmbMixRepo - - mu sync.RWMutex - NonExistentBatchData map[string]bool } func NewMultiBiz( @@ -53,34 +48,11 @@ func NewMultiBiz( MultiNotifyDataRepo: multiNotifyDataRepo, MultiNotifyLogRepo: multiNotifyLogRepo, CmbMixRepo: cmbMixRepo, - - NonExistentBatchData: make(map[string]bool), } } -func (this *MultiBiz) Get(uid string) bool { - - if _, ok := this.NonExistentBatchData[uid]; ok { - return ok - } - - return false -} - -func (this *MultiBiz) Add(uid string) { - - this.mu.Lock() - defer this.mu.Unlock() - - this.NonExistentBatchData[uid] = true -} - func (biz *MultiBiz) Notify(ctx context.Context, ip, source string, req *bo.WechatVoucherNotifyBo) error { - if biz.Get(req.PlainText.StockID) { - return nil - } - cl := vo.MultiNotifyLockKey.BuildCache([]string{ source, req.PlainText.StockID, @@ -89,26 +61,6 @@ func (biz *MultiBiz) Notify(ctx context.Context, ip, source string, req *bo.Wech return lock.NewMutex(biz.rdb.Rdb, cl.TTL).Lock(ctx, cl.Key, func(ctx context.Context) error { - product, err := biz.ProductRepo.GetByBatchNo(ctx, req.PlainText.StockID) - if err != nil { - // 数据库不存在该活动批次,过滤掉 - if errors.Is(err, gorm.ErrRecordNotFound) { - biz.Add(req.PlainText.StockID) - return nil - } - if err2.IsDbNotFound(err) { - biz.Add(req.PlainText.StockID) - return nil - } - return err - } - - // 不是多笔立减金,过滤掉 - if product.ActivityId == "" { - biz.Add(req.PlainText.StockID) - return nil - } - if req.PlainText.ConsumeInformation.ConsumeAmount == 0 { return fmt.Errorf("消费金额不能为0") } @@ -118,6 +70,10 @@ func (biz *MultiBiz) Notify(ctx context.Context, ip, source string, req *bo.Wech return err } + if order.ActivityId != "" { + return fmt.Errorf("批次活动ID不为空,不是多笔立减金,请检查") + } + if err = biz.Run(ctx, ip, source, req, order); err != nil { return err } diff --git a/internal/biz/provider_set.go b/internal/biz/provider_set.go index 1b2e794..293b190 100644 --- a/internal/biz/provider_set.go +++ b/internal/biz/provider_set.go @@ -5,4 +5,4 @@ import ( ) // ProviderSetBiz is biz providers. -var ProviderSetBiz = wire.NewSet(NewVoucherBiz, NewMultiBiz) +var ProviderSetBiz = wire.NewSet(NewVoucherBiz, NewMultiBiz, NewWechatBiz) diff --git a/internal/biz/wechat.go b/internal/biz/wechat.go new file mode 100644 index 0000000..e9761df --- /dev/null +++ b/internal/biz/wechat.go @@ -0,0 +1,26 @@ +package biz + +import ( + "context" + "net/http" + "voucher/internal/biz/bo" + "voucher/internal/biz/wechatrepo" +) + +type WechatBiz struct { + BankMultiActivityRepo wechatrepo.BankMultiActivityRepo +} + +func NewWechatBiz(bankMultiActivityRepo wechatrepo.BankMultiActivityRepo) *WechatBiz { + return &WechatBiz{BankMultiActivityRepo: bankMultiActivityRepo} +} + +func (biz *WechatBiz) CallBack(ctx context.Context, mchId string, headers *http.Header, respBody []byte) (*bo.WechatVoucherNotifyBo, error) { + + response, err := biz.BankMultiActivityRepo.Notify(ctx, mchId, headers, respBody) + if err != nil { + return nil, err + } + + return response, nil +} diff --git a/internal/biz/wechat_notify.go b/internal/biz/wechat_notify.go index be88e76..2ddc5bb 100644 --- a/internal/biz/wechat_notify.go +++ b/internal/biz/wechat_notify.go @@ -11,7 +11,7 @@ import ( "voucher/internal/pkg/lock" ) -func (this *VoucherBiz) WechatNotifyConsumer(ctx context.Context, tag string, req *bo.WechatVoucherNotifyBo) error { +func (this *VoucherBiz) WechatNotifyConsumer(ctx context.Context, ip, tag string, req *bo.WechatVoucherNotifyBo) error { c := vo.WechatNotifyConsumeLockKey.BuildCache([]string{tag, req.PlainText.StockID, req.PlainText.CouponID}) @@ -23,7 +23,7 @@ func (this *VoucherBiz) WechatNotifyConsumer(ctx context.Context, tag string, re } if order.ActivityId != "" { - return this.MultiBiz.Run(ctx, "127.0.0.1", "lsxd_"+req.PlainText.StockCreatorMchid, req, order) + return this.MultiBiz.Run(ctx, ip, "lsxd_"+req.PlainText.StockCreatorMchid, req, order) } if req.PlainText.Status.IsSended() { diff --git a/internal/biz/wechatrepo/bank_multi_activity.go b/internal/biz/wechatrepo/bank_multi_activity.go index 640efbf..626102e 100644 --- a/internal/biz/wechatrepo/bank_multi_activity.go +++ b/internal/biz/wechatrepo/bank_multi_activity.go @@ -1,9 +1,12 @@ package wechatrepo import ( + "context" + "net/http" "voucher/internal/biz/bo" ) type BankMultiActivityRepo interface { Order(order *bo.OrderBo) (couponId string, err error) + Notify(ctx context.Context, mchId string, headers *http.Header, respBody []byte) (response *bo.WechatVoucherNotifyBo, err error) } diff --git a/internal/conf/conf.pb.go b/internal/conf/conf.pb.go index 7f53e1e..298901e 100644 --- a/internal/conf/conf.pb.go +++ b/internal/conf/conf.pb.go @@ -26,18 +26,19 @@ type Bootstrap struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Server *Server `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"` - Logs *Logs `protobuf:"bytes,2,opt,name=logs,proto3" json:"logs,omitempty"` - Data *Data `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` - RocketMQ *RocketMQ `protobuf:"bytes,4,opt,name=rocketMQ,proto3" json:"rocketMQ,omitempty"` - Wechat *Wechat `protobuf:"bytes,5,opt,name=wechat,proto3" json:"wechat,omitempty"` - Cmb *Cmb `protobuf:"bytes,6,opt,name=cmb,proto3" json:"cmb,omitempty"` - 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"` - AliYunSms *AliYunSms `protobuf:"bytes,11,opt,name=aliYunSms,proto3" json:"aliYunSms,omitempty"` - Tripartite *Tripartite `protobuf:"bytes,12,opt,name=tripartite,proto3" json:"tripartite,omitempty"` + Server *Server `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"` + Logs *Logs `protobuf:"bytes,2,opt,name=logs,proto3" json:"logs,omitempty"` + Data *Data `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` + RocketMQ *RocketMQ `protobuf:"bytes,4,opt,name=rocketMQ,proto3" json:"rocketMQ,omitempty"` + Wechat *Wechat `protobuf:"bytes,5,opt,name=wechat,proto3" json:"wechat,omitempty"` + Cmb *Cmb `protobuf:"bytes,6,opt,name=cmb,proto3" json:"cmb,omitempty"` + 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"` + AliYunSms *AliYunSms `protobuf:"bytes,11,opt,name=aliYunSms,proto3" json:"aliYunSms,omitempty"` + Tripartite *Tripartite `protobuf:"bytes,12,opt,name=tripartite,proto3" json:"tripartite,omitempty"` + WechatSubject []*WechatSubject `protobuf:"bytes,13,rep,name=wechatSubject,proto3" json:"wechatSubject,omitempty"` } func (x *Bootstrap) Reset() { @@ -156,6 +157,13 @@ func (x *Bootstrap) GetTripartite() *Tripartite { return nil } +func (x *Bootstrap) GetWechatSubject() []*WechatSubject { + if x != nil { + return x.WechatSubject + } + return nil +} + type Server struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1143,6 +1151,85 @@ func (x *Logs) GetAccess() string { return "" } +type WechatSubject struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + MchID string `protobuf:"bytes,1,opt,name=mchID,proto3" json:"mchID,omitempty"` + Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` + MchCertificateSerialNumber string `protobuf:"bytes,2,opt,name=mchCertificateSerialNumber,proto3" json:"mchCertificateSerialNumber,omitempty"` + WechatPayPublicKeyID string `protobuf:"bytes,3,opt,name=wechatPayPublicKeyID,proto3" json:"wechatPayPublicKeyID,omitempty"` + MchApiV3Key string `protobuf:"bytes,6,opt,name=mchApiV3Key,proto3" json:"mchApiV3Key,omitempty"` +} + +func (x *WechatSubject) Reset() { + *x = WechatSubject{} + if protoimpl.UnsafeEnabled { + mi := &file_conf_conf_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WechatSubject) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WechatSubject) ProtoMessage() {} + +func (x *WechatSubject) ProtoReflect() protoreflect.Message { + mi := &file_conf_conf_proto_msgTypes[14] + 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 WechatSubject.ProtoReflect.Descriptor instead. +func (*WechatSubject) Descriptor() ([]byte, []int) { + return file_conf_conf_proto_rawDescGZIP(), []int{14} +} + +func (x *WechatSubject) GetMchID() string { + if x != nil { + return x.MchID + } + return "" +} + +func (x *WechatSubject) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *WechatSubject) GetMchCertificateSerialNumber() string { + if x != nil { + return x.MchCertificateSerialNumber + } + return "" +} + +func (x *WechatSubject) GetWechatPayPublicKeyID() string { + if x != nil { + return x.WechatPayPublicKeyID + } + return "" +} + +func (x *WechatSubject) GetMchApiV3Key() string { + if x != nil { + return x.MchApiV3Key + } + return "" +} + type Server_HTTP struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1158,7 +1245,7 @@ type Server_HTTP struct { func (x *Server_HTTP) Reset() { *x = Server_HTTP{} if protoimpl.UnsafeEnabled { - mi := &file_conf_conf_proto_msgTypes[14] + mi := &file_conf_conf_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1171,7 +1258,7 @@ func (x *Server_HTTP) String() string { func (*Server_HTTP) ProtoMessage() {} func (x *Server_HTTP) ProtoReflect() protoreflect.Message { - mi := &file_conf_conf_proto_msgTypes[14] + mi := &file_conf_conf_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1238,7 +1325,7 @@ type Data_Database struct { func (x *Data_Database) Reset() { *x = Data_Database{} 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) } @@ -1251,7 +1338,7 @@ func (x *Data_Database) String() string { func (*Data_Database) ProtoMessage() {} func (x *Data_Database) 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 { @@ -1328,7 +1415,7 @@ type Data_Redis struct { func (x *Data_Redis) Reset() { *x = Data_Redis{} if protoimpl.UnsafeEnabled { - mi := &file_conf_conf_proto_msgTypes[16] + mi := &file_conf_conf_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1341,7 +1428,7 @@ func (x *Data_Redis) String() string { func (*Data_Redis) ProtoMessage() {} func (x *Data_Redis) ProtoReflect() protoreflect.Message { - mi := &file_conf_conf_proto_msgTypes[16] + mi := &file_conf_conf_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1432,7 +1519,7 @@ type Cron_CommandMap struct { func (x *Cron_CommandMap) Reset() { *x = Cron_CommandMap{} if protoimpl.UnsafeEnabled { - mi := &file_conf_conf_proto_msgTypes[18] + mi := &file_conf_conf_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1445,7 +1532,7 @@ func (x *Cron_CommandMap) String() string { func (*Cron_CommandMap) ProtoMessage() {} func (x *Cron_CommandMap) ProtoReflect() protoreflect.Message { - mi := &file_conf_conf_proto_msgTypes[18] + mi := &file_conf_conf_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1490,7 +1577,7 @@ type RdsMQ_Queue struct { func (x *RdsMQ_Queue) Reset() { *x = RdsMQ_Queue{} if protoimpl.UnsafeEnabled { - mi := &file_conf_conf_proto_msgTypes[20] + mi := &file_conf_conf_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1503,7 +1590,7 @@ func (x *RdsMQ_Queue) String() string { func (*RdsMQ_Queue) ProtoMessage() {} func (x *RdsMQ_Queue) ProtoReflect() protoreflect.Message { - mi := &file_conf_conf_proto_msgTypes[20] + mi := &file_conf_conf_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1565,7 +1652,7 @@ type Tripartite_QiXing struct { func (x *Tripartite_QiXing) Reset() { *x = Tripartite_QiXing{} if protoimpl.UnsafeEnabled { - mi := &file_conf_conf_proto_msgTypes[21] + mi := &file_conf_conf_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1578,7 +1665,7 @@ func (x *Tripartite_QiXing) String() string { func (*Tripartite_QiXing) ProtoMessage() {} func (x *Tripartite_QiXing) ProtoReflect() protoreflect.Message { - mi := &file_conf_conf_proto_msgTypes[21] + mi := &file_conf_conf_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1608,7 +1695,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, 0xdd, 0x04, 0x0a, 0x09, 0x42, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x12, + 0x6f, 0x22, 0xa2, 0x05, 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, @@ -1646,233 +1733,250 @@ var file_conf_conf_proto_rawDesc = []byte{ 0x69, 0x74, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x76, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x54, 0x72, 0x69, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x65, 0x52, 0x0a, 0x74, 0x72, 0x69, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, - 0x65, 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, 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, - 0xa6, 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, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xff, 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, 0x26, 0x0a, 0x0e, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x4e, 0x6f, 0x74, 0x69, 0x66, - 0x79, 0x55, 0x72, 0x6c, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6d, 0x75, 0x6c, 0x74, - 0x69, 0x4e, 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, 0x9b, 0x01, 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, 0x12, 0x26, 0x0a, 0x0e, 0x77, 0x61, 0x72, - 0x6e, 0x69, 0x6e, 0x67, 0x4d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x0e, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 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, 0xc4, 0x04, 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, 0x4f, 0x0a, 0x14, 0x77, 0x65, 0x63, 0x68, 0x61, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x53, - 0x6c, 0x69, 0x63, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x65, 0x12, 0x43, 0x0a, 0x0d, 0x77, 0x65, 0x63, 0x68, 0x61, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x76, 0x6f, 0x75, 0x63, 0x68, + 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x57, 0x65, 0x63, 0x68, 0x61, 0x74, + 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x0d, 0x77, 0x65, 0x63, 0x68, 0x61, 0x74, 0x53, + 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 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, 0x52, 0x64, 0x73, 0x4d, 0x51, 0x2e, 0x51, 0x75, 0x65, 0x75, 0x65, 0x52, 0x14, 0x77, 0x65, - 0x63, 0x68, 0x61, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x51, 0x75, 0x65, - 0x72, 0x79, 0x12, 0x3d, 0x0a, 0x0b, 0x77, 0x65, 0x63, 0x68, 0x61, 0x74, 0x52, 0x65, 0x74, 0x72, - 0x79, 0x18, 0x03, 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, 0x12, 0x3d, 0x0a, 0x0b, 0x72, 0x65, 0x74, 0x72, 0x79, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, - 0x18, 0x04, 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, 0x72, 0x65, 0x74, 0x72, 0x79, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, - 0x12, 0x47, 0x0a, 0x10, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, - 0x65, 0x74, 0x72, 0x79, 0x18, 0x05, 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, 0x10, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x4e, 0x6f, - 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x74, 0x72, 0x79, 0x12, 0x3b, 0x0a, 0x0a, 0x75, 0x73, 0x65, - 0x64, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x18, 0x06, 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, 0x0a, 0x75, 0x73, 0x65, 0x64, - 0x4e, 0x6f, 0x74, 0x69, 0x66, 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, + 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, 0x08, 0x77, 0x61, 0x69, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x22, - 0xb9, 0x01, 0x0a, 0x09, 0x41, 0x6c, 0x69, 0x59, 0x75, 0x6e, 0x53, 0x6d, 0x73, 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, - 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x6e, 0x64, - 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x69, 0x67, 0x6e, 0x4e, 0x61, 0x6d, - 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x69, 0x67, 0x6e, 0x4e, 0x61, 0x6d, - 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x57, 0x61, 0x72, - 0x6e, 0x69, 0x6e, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x74, 0x65, 0x6d, 0x70, - 0x6c, 0x61, 0x74, 0x65, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x22, 0x69, 0x0a, 0x0a, 0x54, - 0x72, 0x69, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x71, 0x69, 0x58, - 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x76, 0x6f, 0x75, 0x63, - 0x68, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x54, 0x72, 0x69, 0x70, 0x61, - 0x72, 0x74, 0x69, 0x74, 0x65, 0x2e, 0x51, 0x69, 0x58, 0x69, 0x6e, 0x67, 0x52, 0x06, 0x71, 0x69, - 0x58, 0x69, 0x6e, 0x67, 0x1a, 0x20, 0x0a, 0x06, 0x51, 0x69, 0x58, 0x69, 0x6e, 0x67, 0x12, 0x16, - 0x0a, 0x06, 0x61, 0x70, 0x70, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x61, 0x70, 0x70, 0x4b, 0x65, 0x79, 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, + 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, 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, 0xa6, 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, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xff, 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, 0x26, 0x0a, 0x0e, 0x6d, 0x75, 0x6c, 0x74, 0x69, + 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x55, 0x72, 0x6c, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0e, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x4e, 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, 0x9b, 0x01, 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, 0x12, 0x26, + 0x0a, 0x0e, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x4d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x73, + 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 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, 0xc4, 0x04, + 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, 0x4f, 0x0a, 0x14, 0x77, 0x65, 0x63, 0x68, 0x61, 0x74, + 0x54, 0x69, 0x6d, 0x65, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x51, 0x75, 0x65, 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, 0x14, 0x77, 0x65, 0x63, 0x68, 0x61, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x6c, 0x69, + 0x63, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x3d, 0x0a, 0x0b, 0x77, 0x65, 0x63, 0x68, 0x61, + 0x74, 0x52, 0x65, 0x74, 0x72, 0x79, 0x18, 0x03, 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, 0x12, 0x3d, 0x0a, 0x0b, 0x72, 0x65, 0x74, 0x72, 0x79, 0x4e, + 0x6f, 0x74, 0x69, 0x66, 0x79, 0x18, 0x04, 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, 0x72, 0x65, 0x74, 0x72, 0x79, 0x4e, + 0x6f, 0x74, 0x69, 0x66, 0x79, 0x12, 0x47, 0x0a, 0x10, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x4e, 0x6f, + 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x74, 0x72, 0x79, 0x18, 0x05, 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, 0x10, 0x6f, 0x72, + 0x64, 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x74, 0x72, 0x79, 0x12, 0x3b, + 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x64, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x18, 0x06, 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, + 0x0a, 0x75, 0x73, 0x65, 0x64, 0x4e, 0x6f, 0x74, 0x69, 0x66, 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, 0xb9, 0x01, 0x0a, 0x09, 0x41, 0x6c, 0x69, 0x59, 0x75, 0x6e, 0x53, + 0x6d, 0x73, 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, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x69, + 0x67, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x69, + 0x67, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, + 0x22, 0x69, 0x0a, 0x0a, 0x54, 0x72, 0x69, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x65, 0x12, 0x39, + 0x0a, 0x06, 0x71, 0x69, 0x58, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, + 0x2e, 0x76, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, + 0x54, 0x72, 0x69, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x65, 0x2e, 0x51, 0x69, 0x58, 0x69, 0x6e, + 0x67, 0x52, 0x06, 0x71, 0x69, 0x58, 0x69, 0x6e, 0x67, 0x1a, 0x20, 0x0a, 0x06, 0x51, 0x69, 0x58, + 0x69, 0x6e, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x70, 0x70, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x70, 0x70, 0x4b, 0x65, 0x79, 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, 0x22, 0xcf, 0x01, 0x0a, 0x0d, 0x57, 0x65, 0x63, 0x68, + 0x61, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x63, 0x68, + 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x63, 0x68, 0x49, 0x44, 0x12, + 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 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, 0x12, 0x20, 0x0a, 0x0b, 0x6d, 0x63, 0x68, 0x41, 0x70, + 0x69, 0x56, 0x33, 0x4b, 0x65, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x63, + 0x68, 0x41, 0x70, 0x69, 0x56, 0x33, 0x4b, 0x65, 0x79, 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 ( @@ -1887,7 +1991,7 @@ func file_conf_conf_proto_rawDescGZIP() []byte { return file_conf_conf_proto_rawDescData } -var file_conf_conf_proto_msgTypes = make([]protoimpl.MessageInfo, 22) +var file_conf_conf_proto_msgTypes = make([]protoimpl.MessageInfo, 23) var file_conf_conf_proto_goTypes = []any{ (*Bootstrap)(nil), // 0: voucher.config.Bootstrap (*Server)(nil), // 1: voucher.config.Server @@ -1903,15 +2007,16 @@ var file_conf_conf_proto_goTypes = []any{ (*AliYunSms)(nil), // 11: voucher.config.AliYunSms (*Tripartite)(nil), // 12: voucher.config.Tripartite (*Logs)(nil), // 13: voucher.config.Logs - (*Server_HTTP)(nil), // 14: voucher.config.Server.HTTP - (*Data_Database)(nil), // 15: voucher.config.Data.Database - (*Data_Redis)(nil), // 16: voucher.config.Data.Redis - nil, // 17: voucher.config.RocketMQ.EventMapEntry - (*Cron_CommandMap)(nil), // 18: voucher.config.Cron.CommandMap - nil, // 19: voucher.config.Cron.CommandMapEntry - (*RdsMQ_Queue)(nil), // 20: voucher.config.RdsMQ.Queue - (*Tripartite_QiXing)(nil), // 21: voucher.config.Tripartite.QiXing - (*durationpb.Duration)(nil), // 22: google.protobuf.Duration + (*WechatSubject)(nil), // 14: voucher.config.WechatSubject + (*Server_HTTP)(nil), // 15: voucher.config.Server.HTTP + (*Data_Database)(nil), // 16: voucher.config.Data.Database + (*Data_Redis)(nil), // 17: voucher.config.Data.Redis + nil, // 18: voucher.config.RocketMQ.EventMapEntry + (*Cron_CommandMap)(nil), // 19: voucher.config.Cron.CommandMap + nil, // 20: voucher.config.Cron.CommandMapEntry + (*RdsMQ_Queue)(nil), // 21: voucher.config.RdsMQ.Queue + (*Tripartite_QiXing)(nil), // 22: voucher.config.Tripartite.QiXing + (*durationpb.Duration)(nil), // 23: google.protobuf.Duration } var file_conf_conf_proto_depIdxs = []int32{ 1, // 0: voucher.config.Bootstrap.server:type_name -> voucher.config.Server @@ -1926,31 +2031,32 @@ var file_conf_conf_proto_depIdxs = []int32{ 10, // 9: voucher.config.Bootstrap.rdsMQ:type_name -> voucher.config.RdsMQ 11, // 10: voucher.config.Bootstrap.aliYunSms:type_name -> voucher.config.AliYunSms 12, // 11: voucher.config.Bootstrap.tripartite:type_name -> voucher.config.Tripartite - 14, // 12: voucher.config.Server.http:type_name -> voucher.config.Server.HTTP - 15, // 13: voucher.config.Data.db:type_name -> voucher.config.Data.Database - 16, // 14: voucher.config.Data.redis:type_name -> voucher.config.Data.Redis - 17, // 15: voucher.config.RocketMQ.eventMap:type_name -> voucher.config.RocketMQ.EventMapEntry - 19, // 16: voucher.config.Cron.commandMap:type_name -> voucher.config.Cron.CommandMapEntry - 20, // 17: voucher.config.RdsMQ.wechatQuery:type_name -> voucher.config.RdsMQ.Queue - 20, // 18: voucher.config.RdsMQ.wechatTimeSliceQuery:type_name -> voucher.config.RdsMQ.Queue - 20, // 19: voucher.config.RdsMQ.wechatRetry:type_name -> voucher.config.RdsMQ.Queue - 20, // 20: voucher.config.RdsMQ.retryNotify:type_name -> voucher.config.RdsMQ.Queue - 20, // 21: voucher.config.RdsMQ.orderNotifyRetry:type_name -> voucher.config.RdsMQ.Queue - 20, // 22: voucher.config.RdsMQ.usedNotify:type_name -> voucher.config.RdsMQ.Queue - 21, // 23: voucher.config.Tripartite.qiXing:type_name -> voucher.config.Tripartite.QiXing - 22, // 24: voucher.config.Server.HTTP.timeout:type_name -> google.protobuf.Duration - 22, // 25: voucher.config.Data.Database.maxLifetime:type_name -> google.protobuf.Duration - 22, // 26: voucher.config.Data.Redis.readTimeout:type_name -> google.protobuf.Duration - 22, // 27: voucher.config.Data.Redis.writeTimeout:type_name -> google.protobuf.Duration - 22, // 28: voucher.config.Data.Redis.connMaxIdleTime:type_name -> google.protobuf.Duration - 4, // 29: voucher.config.RocketMQ.EventMapEntry.value:type_name -> voucher.config.EventMap - 18, // 30: voucher.config.Cron.CommandMapEntry.value:type_name -> voucher.config.Cron.CommandMap - 22, // 31: voucher.config.RdsMQ.Queue.waitTime:type_name -> google.protobuf.Duration - 32, // [32:32] is the sub-list for method output_type - 32, // [32:32] is the sub-list for method input_type - 32, // [32:32] is the sub-list for extension type_name - 32, // [32:32] is the sub-list for extension extendee - 0, // [0:32] is the sub-list for field type_name + 14, // 12: voucher.config.Bootstrap.wechatSubject:type_name -> voucher.config.WechatSubject + 15, // 13: voucher.config.Server.http:type_name -> voucher.config.Server.HTTP + 16, // 14: voucher.config.Data.db:type_name -> voucher.config.Data.Database + 17, // 15: voucher.config.Data.redis:type_name -> voucher.config.Data.Redis + 18, // 16: voucher.config.RocketMQ.eventMap:type_name -> voucher.config.RocketMQ.EventMapEntry + 20, // 17: voucher.config.Cron.commandMap:type_name -> voucher.config.Cron.CommandMapEntry + 21, // 18: voucher.config.RdsMQ.wechatQuery:type_name -> voucher.config.RdsMQ.Queue + 21, // 19: voucher.config.RdsMQ.wechatTimeSliceQuery:type_name -> voucher.config.RdsMQ.Queue + 21, // 20: voucher.config.RdsMQ.wechatRetry:type_name -> voucher.config.RdsMQ.Queue + 21, // 21: voucher.config.RdsMQ.retryNotify:type_name -> voucher.config.RdsMQ.Queue + 21, // 22: voucher.config.RdsMQ.orderNotifyRetry:type_name -> voucher.config.RdsMQ.Queue + 21, // 23: voucher.config.RdsMQ.usedNotify:type_name -> voucher.config.RdsMQ.Queue + 22, // 24: voucher.config.Tripartite.qiXing:type_name -> voucher.config.Tripartite.QiXing + 23, // 25: voucher.config.Server.HTTP.timeout:type_name -> google.protobuf.Duration + 23, // 26: voucher.config.Data.Database.maxLifetime:type_name -> google.protobuf.Duration + 23, // 27: voucher.config.Data.Redis.readTimeout:type_name -> google.protobuf.Duration + 23, // 28: voucher.config.Data.Redis.writeTimeout:type_name -> google.protobuf.Duration + 23, // 29: voucher.config.Data.Redis.connMaxIdleTime:type_name -> google.protobuf.Duration + 4, // 30: voucher.config.RocketMQ.EventMapEntry.value:type_name -> voucher.config.EventMap + 19, // 31: voucher.config.Cron.CommandMapEntry.value:type_name -> voucher.config.Cron.CommandMap + 23, // 32: voucher.config.RdsMQ.Queue.waitTime:type_name -> google.protobuf.Duration + 33, // [33:33] is the sub-list for method output_type + 33, // [33:33] is the sub-list for method input_type + 33, // [33:33] is the sub-list for extension type_name + 33, // [33:33] is the sub-list for extension extendee + 0, // [0:33] is the sub-list for field type_name } func init() { file_conf_conf_proto_init() } @@ -2128,7 +2234,7 @@ func file_conf_conf_proto_init() { } } file_conf_conf_proto_msgTypes[14].Exporter = func(v any, i int) any { - switch v := v.(*Server_HTTP); i { + switch v := v.(*WechatSubject); i { case 0: return &v.state case 1: @@ -2140,7 +2246,7 @@ func file_conf_conf_proto_init() { } } file_conf_conf_proto_msgTypes[15].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: @@ -2152,6 +2258,18 @@ func file_conf_conf_proto_init() { } } file_conf_conf_proto_msgTypes[16].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[17].Exporter = func(v any, i int) any { switch v := v.(*Data_Redis); i { case 0: return &v.state @@ -2163,7 +2281,7 @@ func file_conf_conf_proto_init() { return nil } } - file_conf_conf_proto_msgTypes[18].Exporter = func(v any, i int) any { + file_conf_conf_proto_msgTypes[19].Exporter = func(v any, i int) any { switch v := v.(*Cron_CommandMap); i { case 0: return &v.state @@ -2175,7 +2293,7 @@ func file_conf_conf_proto_init() { return nil } } - file_conf_conf_proto_msgTypes[20].Exporter = func(v any, i int) any { + file_conf_conf_proto_msgTypes[21].Exporter = func(v any, i int) any { switch v := v.(*RdsMQ_Queue); i { case 0: return &v.state @@ -2187,7 +2305,7 @@ func file_conf_conf_proto_init() { return nil } } - file_conf_conf_proto_msgTypes[21].Exporter = func(v any, i int) any { + file_conf_conf_proto_msgTypes[22].Exporter = func(v any, i int) any { switch v := v.(*Tripartite_QiXing); i { case 0: return &v.state @@ -2206,7 +2324,7 @@ func file_conf_conf_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_conf_conf_proto_rawDesc, NumEnums: 0, - NumMessages: 22, + NumMessages: 23, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/conf/conf.proto b/internal/conf/conf.proto index 1bc014f..aabe777 100644 --- a/internal/conf/conf.proto +++ b/internal/conf/conf.proto @@ -18,6 +18,7 @@ message Bootstrap { RdsMQ rdsMQ = 10; AliYunSms aliYunSms = 11; Tripartite tripartite = 12; + repeated WechatSubject wechatSubject = 13; } message Server { @@ -158,4 +159,12 @@ message Tripartite { message Logs { string business = 1; string access = 2; +} + +message WechatSubject { + string mchID = 1; + string name = 4; + string mchCertificateSerialNumber = 2; + string wechatPayPublicKeyID = 3; + string mchApiV3Key = 6; } \ No newline at end of file diff --git a/internal/data/wechatrepoimpl/bank_multi_activity.go b/internal/data/wechatrepoimpl/bank_multi_activity.go index 2e0fc0c..58bd146 100644 --- a/internal/data/wechatrepoimpl/bank_multi_activity.go +++ b/internal/data/wechatrepoimpl/bank_multi_activity.go @@ -1,9 +1,12 @@ package wechatrepoimpl import ( + "context" + "encoding/json" "errors" "fmt" "github.com/wechatpay-apiv3/wechatpay-go/core" + "net/http" err2 "voucher/api/err" "voucher/internal/biz/bo" "voucher/internal/biz/wechatrepo" @@ -55,3 +58,22 @@ func (w *BankMultiActivityImpl) Order(order *bo.OrderBo) (couponId string, err e return *resp.CouponId, nil } + +func (w *BankMultiActivityImpl) Notify(ctx context.Context, mchId string, headers *http.Header, respBody []byte) (response *bo.WechatVoucherNotifyBo, err error) { + + t, err := w.wx.Get(mchId) + if err != nil { + return + } + + decodeBodyStr, err := t.Notify(ctx, headers, respBody) + if err != nil { + return + } + + if err = json.Unmarshal([]byte(decodeBodyStr), &response); err != nil { + return nil, err + } + + return +} diff --git a/internal/data/wx.go b/internal/data/wx.go index fb9df64..6d268c0 100644 --- a/internal/data/wx.go +++ b/internal/data/wx.go @@ -24,6 +24,14 @@ func NewWx(c *conf.Bootstrap) (*Wx, error) { clients[c.Wechat.MchID] = client + for _, v := range c.WechatSubject { + cli, e := buildWechat(v) + if e != nil { + return nil, e + } + clients[v.MchID] = cli + } + return &Wx{Clients: clients}, nil } @@ -44,6 +52,33 @@ func (this *Wx) Get(mchId string) (*marketing.Marketing, error) { return nil, fmt.Errorf("微信调用client不存在[%s]", mchId) } +func buildWechat(wx *conf.WechatSubject) (*marketing.Marketing, error) { + + dir, err := os.Getwd() + if err != nil { + return nil, fmt.Errorf("商户ID[%s]商户名称[%s]获取目的地址有误[%v]", wx.MchID, wx.Name, err) + } + + filePath := fmt.Sprintf("%s/cert/wechat/%s", dir, wx.MchID) + if !helper.FileExists(filePath) { + panic(fmt.Sprintf("商户ID[%s]商户名称[%s]微信密钥证书信息不存在,请联系技术人员处理", wx.MchID, wx.Name)) + } + + cc, err := utils2.CreateMchConfig( + wx.MchID, // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756 + wx.MchCertificateSerialNumber, // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053 + fmt.Sprintf("%s/%s", filePath, "wechat_private_key.pem"), // 商户API证书私钥文件路径,本地文件路径 + wx.WechatPayPublicKeyID, // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816 + fmt.Sprintf("%s/%s", filePath, "pub_key.pem"), // 微信支付公钥文件路径,本地文件路径 + wx.MchApiV3Key, + ) + if err != nil { + return nil, err + } + + return &marketing.Marketing{MchConfig: cc}, nil +} + func buildWx(wx *conf.Wechat) (*marketing.Marketing, error) { dir, err := os.Getwd() @@ -62,6 +97,7 @@ func buildWx(wx *conf.Wechat) (*marketing.Marketing, error) { fmt.Sprintf("%s/%s", filePath, "wechat_private_key.pem"), // 商户API证书私钥文件路径,本地文件路径 wx.WechatPayPublicKeyID, // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816 fmt.Sprintf("%s/%s", filePath, "pub_key.pem"), // 微信支付公钥文件路径,本地文件路径 + "", ) if err != nil { return nil, err diff --git a/internal/pkg/wechat/srv/marketing/marketing.go b/internal/pkg/wechat/srv/marketing/marketing.go index 65203e0..71e0894 100644 --- a/internal/pkg/wechat/srv/marketing/marketing.go +++ b/internal/pkg/wechat/srv/marketing/marketing.go @@ -1,7 +1,9 @@ package marketing import ( + "context" "encoding/json" + "net/http" "net/url" "strings" "voucher/internal/pkg/wechat/srv" @@ -39,13 +41,13 @@ func (srv *Marketing) Send(openId string, req *SendReq) (response *SendResp, err } // Query @link https://pay.weixin.qq.com/doc/v3/merchant/4014569864 -func (srv *Marketing) Query(appid, openId, couponId string) (response *SendResp, err error) { +func (srv *Marketing) Query(appId, openId, couponId string) (response *SendResp, err error) { path := strings.Replace(queryPath, "{openid}", url.PathEscape(openId), -1) path = strings.Replace(path, "{coupon_id}", url.PathEscape(couponId), -1) var uv = url.Values{} - uv.Set("appid", appid) + uv.Set("appid", appId) path += "?" + uv.Encode() respBody, err := srv.Request2(utils.Host, utils.MethodGET, path, nil) @@ -59,3 +61,14 @@ func (srv *Marketing) Query(appid, openId, couponId string) (response *SendResp, return response, nil } + +// Notify . +func (srv *Marketing) Notify(_ context.Context, headers *http.Header, respBody []byte) (response string, err error) { + + bizStr, err := srv.GetDecodeBody(headers, respBody) + if err != nil { + return "", err + } + + return bizStr, nil +} diff --git a/internal/pkg/wechat/utils/aes.go b/internal/pkg/wechat/utils/aes.go new file mode 100644 index 0000000..d95fb4c --- /dev/null +++ b/internal/pkg/wechat/utils/aes.go @@ -0,0 +1,87 @@ +package utils + +import ( + "crypto/aes" + "crypto/cipher" + "encoding/base64" + "errors" + "fmt" +) + +const ( + keyLengthByte = 32 // AES-256 密钥长度(字节) + authTagLengthByte = 16 // GCM 认证标签长度(字节) +) + +// AesUtil 用于微信支付 AES-256-GCM 解密的工具类 +type AesUtil struct { + aesKey []byte // 32字节的AES密钥 +} + +// NewAesUtil 创建AesUtil实例,验证密钥长度 +func NewAesUtil(aesKey string) (*AesUtil, error) { + if len(aesKey) != keyLengthByte { + return nil, errors.New("无效的ApiV3Key,长度应为32个字节") + } + return &AesUtil{aesKey: []byte(aesKey)}, nil +} + +// DecryptToString 解密 AEAD_AES_256_GCM 加密的数据 +// associatedData: 附加认证数据 +// nonceStr: 随机数(12字节) +// ciphertext: 加密后的密文(Base64编码) +func (a *AesUtil) DecryptToString(associatedData, nonceStr, ciphertext string) (string, error) { + // 1. Base64解码密文 + cipherBytes, err := base64.StdEncoding.DecodeString(ciphertext) + if err != nil { + return "", fmt.Errorf("密文Base64解码失败: %w", err) + } + + // 2. 验证密文长度(需包含认证标签) + if len(cipherBytes) <= authTagLengthByte { + return "", errors.New("密文长度不足,无法解析认证标签") + } + + // 3. 分离密文和认证标签(GCM模式中,认证标签通常附加在密文末尾) + ctext := cipherBytes[:len(cipherBytes)-authTagLengthByte] + authTag := cipherBytes[len(cipherBytes)-authTagLengthByte:] + + // 4. 初始化AES-GCM加密器 + block, err := aes.NewCipher(a.aesKey) + if err != nil { + return "", fmt.Errorf("创建AES加密器失败: %w", err) + } + + gcm, err := cipher.NewGCM(block) + if err != nil { + return "", fmt.Errorf("创建GCM模式失败: %w", err) + } + + // 5. 构建附加数据(GCM的AAD) + additionalData := []byte(associatedData) + + // 6. 解密(GCM模式会自动验证认证标签) + plaintext, err := gcm.Open(nil, []byte(nonceStr), append(ctext, authTag...), additionalData) + if err != nil { + return "", fmt.Errorf("解密失败(可能是密钥错误或数据被篡改): %w", err) + } + + return string(plaintext), nil +} + +type Resource struct { + OriginalType string `json:"original_type"` + Algorithm string `json:"algorithm"` + Ciphertext string `json:"ciphertext"` // 如支付通知中的 encrypted_data + AssociatedData string `json:"associated_data"` // 微信支付回调的附加数据 通常为空字符串或回调相关信息 + Nonce string `json:"nonce"` // 12字节字符串 +} + +type WxNotifyBody struct { + Id string `json:"id"` + CreateTime string `json:"create_time"` + ResourceType string `json:"resource_type"` + EventType string `json:"event_type"` + Summary string `json:"summary"` + Resource Resource `json:"resource"` +} diff --git a/internal/pkg/wechat/utils/wxpay_utility.go b/internal/pkg/wechat/utils/wxpay_utility.go index f488688..15de6d0 100644 --- a/internal/pkg/wechat/utils/wxpay_utility.go +++ b/internal/pkg/wechat/utils/wxpay_utility.go @@ -42,6 +42,8 @@ type MchConfig struct { wechatPayPublicKeyFilePath string privateKey *rsa.PrivateKey wechatPayPublicKey *rsa.PublicKey + + aesKey string } // MchId 商户号 @@ -76,6 +78,7 @@ func CreateMchConfig( privateKeyFilePath string, wechatPayPublicKeyId string, wechatPayPublicKeyFilePath string, + aesKey string, ) (*MchConfig, error) { mchConfig := &MchConfig{ mchId: mchId, @@ -83,6 +86,7 @@ func CreateMchConfig( privateKeyFilePath: privateKeyFilePath, wechatPayPublicKeyId: wechatPayPublicKeyId, wechatPayPublicKeyFilePath: wechatPayPublicKeyFilePath, + aesKey: aesKey, } privateKey, err := LoadPrivateKeyWithPath(mchConfig.privateKeyFilePath) if err != nil { @@ -572,6 +576,35 @@ func (srv *MchConfig) Verify(request *http.Request) (string, error) { return EncryptOAEPWithPublicKey(string(respBody), srv.wechatPayPublicKey) } +func (srv *MchConfig) GetDecodeBody(headers *http.Header, respBody []byte) (string, error) { + + if respBody == nil { + return "", fmt.Errorf("request HttpBody is nil") + } + + err := ValidateResponse( + srv.WechatPayPublicKeyId(), + srv.WechatPayPublicKey(), + headers, + respBody, + ) + if err != nil { + return "", err + } + + var wxNotifyBody WxNotifyBody + if err = json.Unmarshal(respBody, &wxNotifyBody); err != nil { + return "", err + } + + aesUtil, err := NewAesUtil(srv.aesKey) + if err != nil { + return "", err + } + + return aesUtil.DecryptToString(wxNotifyBody.Resource.AssociatedData, wxNotifyBody.Resource.Nonce, wxNotifyBody.Resource.Ciphertext) +} + // BuildSortedQueryString 函数接受一个 map,返回按照字段名排序后的 URL 键值对格式字符串 func BuildSortedQueryString(params map[string]any) string { // 创建一个字符串切片,用于保存所有的键名 diff --git a/internal/pkg/wechat/utils/wxpay_utility_test.go b/internal/pkg/wechat/utils/wxpay_utility_test.go new file mode 100644 index 0000000..fc464bd --- /dev/null +++ b/internal/pkg/wechat/utils/wxpay_utility_test.go @@ -0,0 +1,68 @@ +package utils + +import ( + "crypto/rsa" + "reflect" + "testing" +) + +func TestMchConfig_Request(t *testing.T) { + type fields struct { + mchId string + certificateSerialNo string + privateKeyFilePath string + wechatPayPublicKeyId string + wechatPayPublicKeyFilePath string + privateKey *rsa.PrivateKey + wechatPayPublicKey *rsa.PublicKey + } + type args struct { + host string + method string + path string + reqBody []byte + } + tests := []struct { + name string + fields fields + args args + wantResponse []byte + wantErr bool + }{ + { + name: "test", + fields: fields{ + mchId: "1652322442", + certificateSerialNo: "certificateSerialNo", + privateKeyFilePath: "privateKeyFilePath", + wechatPayPublicKeyId: "wechatPayPublicKeyId", + wechatPayPublicKeyFilePath: "wechatPayPublicKeyFilePath", + }, + args: args{ + host: "https://api.mch.weixin.qq.com", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + srv := &MchConfig{ + mchId: tt.fields.mchId, + certificateSerialNo: tt.fields.certificateSerialNo, + privateKeyFilePath: tt.fields.privateKeyFilePath, + wechatPayPublicKeyId: tt.fields.wechatPayPublicKeyId, + wechatPayPublicKeyFilePath: tt.fields.wechatPayPublicKeyFilePath, + privateKey: tt.fields.privateKey, + wechatPayPublicKey: tt.fields.wechatPayPublicKey, + } + + gotResponse, err := srv.Request(tt.args.host, tt.args.method, tt.args.path, tt.args.reqBody) + if (err != nil) != tt.wantErr { + t.Errorf("Request() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotResponse, tt.wantResponse) { + t.Errorf("Request() gotResponse = %v, want %v", gotResponse, tt.wantResponse) + } + }) + } +} diff --git a/internal/server/http.go b/internal/server/http.go index 0941fe6..d6e9084 100644 --- a/internal/server/http.go +++ b/internal/server/http.go @@ -27,6 +27,7 @@ func NewHTTPServer( accessLogger *log2.AccessLogger, cmb *service.CmbService, tripartiteService *service.TripartiteService, + notifyService *service.NotifyService, ) *http.Server { //构建 server srv := buildHTTPServer(c, accessLogger, log) @@ -39,6 +40,11 @@ func NewHTTPServer( // 启星(启星-蓝色兄弟立减金代配) /voucher/qixing/v1/notify srv.Route("/voucher/").POST("qixing/v1/notify", tripartiteService.QiXingNotify) + // 通知url必须为公网可访问的URL,必须为HTTPS,不能携带参数 + // https://域名.cn/voucher/v1/notify/123456 123456为微信主体商户号 + // https://gateway.dev.cdlsxd.cn/voucher/v1/notify/123456 123456为微信主体商户号 测试环境 + // https://voucher.86698.cn/voucher/v1/notify/123456 123456为微信主体商户号 正式环境 + srv.Route("/voucher/").POST("/notify/v1/{mch_id}", notifyService.Notify) // 订单通知重试 -- 不健全 srv.Route("/voucher/").POST("orderNotifyRetry", cmb.OrderNotifyRetry) diff --git a/internal/service/notify.go b/internal/service/notify.go new file mode 100644 index 0000000..c34ac9f --- /dev/null +++ b/internal/service/notify.go @@ -0,0 +1,79 @@ +package service + +import ( + "fmt" + "github.com/go-kratos/kratos/v2/log" + "github.com/go-kratos/kratos/v2/transport/http" + "io" + http2 "net/http" + "voucher/internal/biz" + "voucher/internal/pkg/helper" +) + +type NotifyService struct { + WechatBiz *biz.WechatBiz + VoucherBiz *biz.VoucherBiz +} + +func NewNotifyService(wechatBiz *biz.WechatBiz, VoucherBiz *biz.VoucherBiz) *NotifyService { + return &NotifyService{WechatBiz: wechatBiz, VoucherBiz: VoucherBiz} +} + +// Notify https://pay.weixin.qq.com/doc/v3/merchant/4012285250 +func (srv *NotifyService) Notify(ctx http.Context) error { + + mchId := ctx.Vars().Get("mch_id") + if mchId == "" { + log.Errorf("微信回调通知,mchId参数不能为空") + return ctx.JSON(http2.StatusNetworkAuthenticationRequired, map[string]string{ + "code": "FAIL", + "message": "微信回调通知,mchId参数不能为空", + }) + } + + headers := ctx.Request().Header + if headers == nil { + log.Errorf("微信回调通知[%s],headers参数不能为空", mchId) + return ctx.JSON(http2.StatusNetworkAuthenticationRequired, map[string]string{ + "code": "FAIL", + "message": "微信回调通知,headers参数不能为空", + }) + } + + bodyBytes, err := io.ReadAll(ctx.Request().Body) + if err != nil { + log.Errorf("微信回调通知[%s],读取响应体失败: %v", mchId, err) + return fmt.Errorf("读取响应体失败: %w", err) + } + + if bodyBytes == nil { + log.Errorf("微信回调通知[%s],响应体不能为空", mchId) + return ctx.JSON(http2.StatusNetworkAuthenticationRequired, map[string]string{ + "code": "FAIL", + "message": "微信回调通知,响应体不能为空", + }) + } + + log.Warnf("微信回调通知[%s],headers:%+v,body:%s", mchId, headers, string(bodyBytes)) + + response, err := srv.WechatBiz.CallBack(ctx, mchId, &headers, bodyBytes) + if err != nil { + return ctx.JSON(http2.StatusNetworkAuthenticationRequired, map[string]string{ + "code": "FAIL", + "message": err.Error(), + }) + } + + ip := helper.GetClientIP(ctx) + + if err = srv.VoucherBiz.WechatNotifyConsumer(ctx, ip, "WECHAT_NOTIFY", response); err != nil { + + log.Errorf("微信回调通知[%s],headers:%+v,body:%s,err:%s", mchId, headers, string(bodyBytes), err.Error()) + return ctx.JSON(http2.StatusBadRequest, map[string]string{ + "code": "FAIL", + "message": err.Error(), + }) + } + + return ctx.JSON(http2.StatusOK, nil) +} diff --git a/internal/service/provider_set.go b/internal/service/provider_set.go index fd9e579..220ae5c 100644 --- a/internal/service/provider_set.go +++ b/internal/service/provider_set.go @@ -9,4 +9,5 @@ var ProviderSetService = wire.NewSet( NewVoucherService, NewCmbService, NewTripartiteService, + NewNotifyService, ) diff --git a/internal/service/voucher.go b/internal/service/voucher.go index b13c6d4..fddf7d1 100644 --- a/internal/service/voucher.go +++ b/internal/service/voucher.go @@ -133,5 +133,5 @@ func (v *VoucherService) WechatUseNotifyConsumer(ctx context.Context, tag, msg s return err } - return v.VoucherBiz.WechatNotifyConsumer(ctx, tag, req) + return v.VoucherBiz.WechatNotifyConsumer(ctx, "127.0.0.1", tag, req) } diff --git a/test/bank_multi_activity.go b/test/bank_multi_activity.go index 8edd220..84c0d2f 100644 --- a/test/bank_multi_activity.go +++ b/test/bank_multi_activity.go @@ -36,6 +36,7 @@ func marketing() *marketing2.Marketing { fmt.Sprintf("%s/%s", filePath, "wechat_private_key.pem"), // 商户API证书私钥文件路径,本地文件路径 wechatPayPublicKeyId, // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816 fmt.Sprintf("%s/%s", filePath, "pub_key.pem"), // 微信支付公钥文件路径,本地文件路径 + "", ) if err != nil { panic(err) @@ -67,6 +68,37 @@ func marketingFJxw() *marketing2.Marketing { fmt.Sprintf("%s/%s", filePath, "wechat_private_key.pem"), // 商户API证书私钥文件路径,本地文件路径 wechatPayPublicKeyId, // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816 fmt.Sprintf("%s/%s", filePath, "pub_key.pem"), // 微信支付公钥文件路径,本地文件路径 + "", + ) + if err != nil { + panic(err) + } + + return &marketing2.Marketing{MchConfig: c} +} + +func marketingFJLF() *marketing2.Marketing { + + dir, err := os.Getwd() + if err != nil { + panic(err) + } + + parentDir := filepath.Dir(dir) + + mchId := "1100040695" + wechatPayPublicKeyId := " PUB_KEY_ID_0111000406952026032500382251001000" + certificateSerialNo := "46712853869DB0EDAA9B4DF97DADEECD4CCDC85B" + + filePath := fmt.Sprintf("%s/cert/wechat/%s", parentDir, mchId) + + c, err := utils.CreateMchConfig( + mchId, // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756 + certificateSerialNo, // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053 + fmt.Sprintf("%s/%s", filePath, "wechat_private_key.pem"), // 商户API证书私钥文件路径,本地文件路径 + wechatPayPublicKeyId, // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816 + fmt.Sprintf("%s/%s", filePath, "pub_key.pem"), // 微信支付公钥文件路径,本地文件路径 + "", ) if err != nil { panic(err) @@ -112,4 +144,5 @@ func MarketingSend() { func MarketingQuery(appId, openId, couponId string) (response *marketing2.SendResp, err error) { return marketingFJxw().Query(appId, openId, couponId) + //return marketingFJLF().Query(appId, openId, couponId) } diff --git a/test/bank_multi_activity_test.go b/test/bank_multi_activity_test.go index 057fcca..453983b 100644 --- a/test/bank_multi_activity_test.go +++ b/test/bank_multi_activity_test.go @@ -1,8 +1,11 @@ package test import ( + "context" "encoding/base64" "encoding/json" + "fmt" + "net/http" "testing" "time" "voucher/internal/biz/bo" @@ -90,3 +93,67 @@ func Test_QixingNotifyData(t *testing.T) { // {"content":"base64(微信通知json对象数据)","timestamp":1765447477945,"ciphertext":"md5(base64(微信通知json对象数据)+key)"} //t.Logf(`{"content":"%s","timestamp":122345677890,"ciphertext":"%s"}`, content, ciphertext) } + +func Test_MarketingNotify(t *testing.T) { + + header := `{ + "Content-Type": [ + "application/json" + ], + "Wechatpay-Nonce": [ + "dF8R9izUJnPBjVLa2cAcCaa7j6QUgitl" + ], + "Wechatpay-Serial": [ + "PUB_KEY_ID_0116523224422025061800192371001800" + ], + "Wechatpay-Signature": [ + "SoXIiTRTr6jofXXxGlfO+wyf1IzXFXcsfvEU2EggQfRKFu+8h3TT6QMQ8zIf8dpkkTPexB/3igGiATrR3uZY4ZeOpRrhIFHSJj0Ala0Ri2Nt4zk+MuBQnhybSYJ4Cn3/sHC4i2HFoOSil7OqlSr79hjod3h0tjYVQLtZ4+Cjp0IeMNB4p5qmIuERuhtfRqcyqXik9/uYNYxw8/Wkf1mMnTsBxyXK3iHAoinXNrEiqCCrQHCfnORMYosr7l+Ox8v9u1c8FFt+rt09vKssVCqYaZ/XRala3mjslDRiluFKSuqb7/JO3AxQjBK6M0iSZOlnmiXSIAq+UxJg4cem6wHi+g==" + ], + "Wechatpay-Signature-Type": [ + "WECHATPAY2-SHA256-RSA2048" + ], + "Wechatpay-Timestamp": [ + "1753690220" + ], + "X-Forwarded-For": [ + "121.51.58.168" + ], + "X-Forwarded-Proto": [ + "https" + ], + "X-Real-Ip": [ + "121.51.58.168" + ] + }` + body := `{ + "id": "fd06376a-3e1b-5516-81f8-9b69cf1ba416", + "create_time": "2025-07-28T16:10:15+08:00", + "resource_type": "encrypt-resource", + "event_type": "MCHTRANSFER.BILL.FINISHED", + "summary": "商家转账单据终态通知", + "resource": { + "original_type": "mch_payment", + "algorithm": "AEAD_AES_256_GCM", + "ciphertext": "XJBIhrHgbe9NR5q/jLYmZKdT/3xuKm2x7EFu3T52Hj2hjPzarRSA2HCsGTxGojfD+CFyJHIULlL2adqLijAjpi3B6TaYKY4LqhtJ/RYSQtYNxYvBpWX1yLOWe8luJbWxmQvKZxIekFs8lGVgkPBUw0IfEAvJ6jHAGCcgxLIqxgOf6UtGUqxCCNp/V3xy8zCiHB0Mvlw8eXCTuG+ZESJIXvloVGNS79R6iNeqk4kNKRSaV86MNh1KQlmoBxZ4yEshD/vIlMulU3xEc+mM25y8vUS4Ot6pxEpUdUyjwcb9QTwTTnZzm6i+VWYymcItAVBQrvsKBMmqWnPtNXG8++13k3DeO1LyVKURmnWXXT1mImmGx/teN/1xPV5y6nChu/HTbcJGDQy2twuq6TPFbbYlTjZH047z/ZtozJNvGNeh", + "associated_data": "mch_payment", + "nonce": "YN3eW5H8mxLs" + } + }` + + fmt.Print(header) + fmt.Print(body) + + httpHeaders := make(http.Header) + if err := json.Unmarshal([]byte(header), &httpHeaders); err != nil { + fmt.Printf("headers Unmarshal err: %+v\n", err) + return + } + + bizContent, err := marketingFJLF().Notify(context.Background(), &httpHeaders, []byte(body)) + if err != nil { + fmt.Printf("notify err: %+v\n", err) + return + } + + fmt.Printf("bizContent: %+v\n", bizContent) +} diff --git a/test/coupon.go b/test/coupon.go index f63f57e..a258dda 100644 --- a/test/coupon.go +++ b/test/coupon.go @@ -24,6 +24,7 @@ var bc = &conf.Bootstrap{ }, } +// bc2 Callback{NotifyUrl:https://nsall.86698.cn/wechatPay/coupon_notify/fjxingwang, Mchid:1652465541} var bc2 = &conf.Bootstrap{ Wechat: &conf.Wechat{ MchID: "1652465541", // notifyUrl https://nsall.86698.cn/wechatPay/coupon_notify/fjxingwang @@ -33,6 +34,15 @@ var bc2 = &conf.Bootstrap{ }, } +var bcFJLF = &conf.Bootstrap{ + Wechat: &conf.Wechat{ + MchID: "1100040695", + MchCertificateSerialNumber: "46712853869DB0EDAA9B4DF97DADEECD4CCDC85B", + WechatPayPublicKeyID: "PUB_KEY_ID_0111000406952026032500382251001000", + Name: "福建峦峰", + }, +} + func SendCoupon() { ctx := context.Background() @@ -223,7 +233,7 @@ func WxResp(wxResp *cashcoupons.Stock) (reps *do.WxResp) { return req } -func QueryCallback() { +func QueryCallback(bc *conf.Bootstrap) { ctx := context.Background() @@ -235,9 +245,9 @@ func QueryCallback() { parentDir := filepath.Dir(dir) server := data.Server{ - MchID: bc2.Wechat.MchID, - MchCertificateSerialNumber: bc2.Wechat.MchCertificateSerialNumber, - WechatPayPublicKeyID: bc2.Wechat.WechatPayPublicKeyID, + MchID: bc.Wechat.MchID, + MchCertificateSerialNumber: bc.Wechat.MchCertificateSerialNumber, + WechatPayPublicKeyID: bc.Wechat.WechatPayPublicKeyID, Dir: parentDir, } client, err := data.GetClient(ctx, server) @@ -249,7 +259,46 @@ func QueryCallback() { svc := cashcoupons.CallBackUrlApiService{Client: client} response, _, err := svc.QueryCallback(ctx, cashcoupons.QueryCallbackRequest{ - Mchid: core.String("1652465541"), + Mchid: core.String(bc.Wechat.MchID), + }) + if err != nil { + fmt.Println(err) + return + } + + fmt.Printf("\nresp:%+v\n", response) + return +} + +func SetCallback(bc *conf.Bootstrap) { + + ctx := context.Background() + + dir, err := os.Getwd() + if err != nil { + fmt.Printf("os.Getwd() error = %v", err) + return + } + parentDir := filepath.Dir(dir) + + server := data.Server{ + MchID: bc.Wechat.MchID, + MchCertificateSerialNumber: bc.Wechat.MchCertificateSerialNumber, + WechatPayPublicKeyID: bc.Wechat.WechatPayPublicKeyID, + Dir: parentDir, + } + client, err := data.GetClient(ctx, server) + if err != nil { + fmt.Println(err) + return + } + + svc := cashcoupons.CallBackUrlApiService{Client: client} + + response, _, err := svc.SetCallback(ctx, cashcoupons.SetCallbackRequest{ + Mchid: core.String(bc.Wechat.MchID), + NotifyUrl: core.String("https://gateway.dev.cdlsxd.cn/voucher/v1/notify/" + bc.Wechat.MchID), + Switch: core.Bool(true), }) if err != nil { fmt.Println(err) diff --git a/test/coupon_test.go b/test/coupon_test.go index 61daa6f..2edf776 100644 --- a/test/coupon_test.go +++ b/test/coupon_test.go @@ -60,7 +60,23 @@ func Test_QueryCallback(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - QueryCallback() + QueryCallback(bcFJLF) + }) + } +} + +func Test_SetCallback(t *testing.T) { + tests := []struct { + name string + }{ + { + name: "设置券核销通知地址", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + SetCallback(bcFJLF) }) } } From 813dfe5ef0397eed71490f145c981f544517b57b Mon Sep 17 00:00:00 2001 From: ziming Date: Wed, 25 Mar 2026 14:13:38 +0800 Subject: [PATCH 02/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/biz/repo/order.go | 1 - internal/biz/wechat_notify.go | 27 +-------------------------- internal/data/repoimpl/order.go | 16 ---------------- 3 files changed, 1 insertion(+), 43 deletions(-) diff --git a/internal/biz/repo/order.go b/internal/biz/repo/order.go index 633ff9a..62a2a3d 100644 --- a/internal/biz/repo/order.go +++ b/internal/biz/repo/order.go @@ -19,7 +19,6 @@ type OrderRepo interface { GetByOutBizNo(ctx context.Context, t vo.OrderType, outBizNo string) (*bo.OrderBo, error) GetByOrderNo(ctx context.Context, orderNo string) (*bo.OrderBo, error) GetByCouponId(ctx context.Context, merchantNo, batchNo, voucherNo string) (*bo.OrderBo, error) - GetByTransactionId(ctx context.Context, stockCreatorMchId, stockID, transactionId string) (*bo.OrderBo, error) Create(ctx context.Context, req *bo.OrderBo) (*bo.OrderBo, error) GetByID(ctx context.Context, id uint64) (*bo.OrderBo, error) Ing(ctx context.Context, id uint64) error diff --git a/internal/biz/wechat_notify.go b/internal/biz/wechat_notify.go index 2ddc5bb..cf19609 100644 --- a/internal/biz/wechat_notify.go +++ b/internal/biz/wechat_notify.go @@ -2,9 +2,7 @@ package biz import ( "context" - "errors" "fmt" - "gorm.io/gorm" errPb "voucher/api/err" "voucher/internal/biz/bo" "voucher/internal/biz/vo" @@ -48,30 +46,7 @@ func (this *VoucherBiz) getOrder(ctx context.Context, req *bo.WechatVoucherNotif order, err := this.OrderRepo.GetByCouponId(ctx, req.PlainText.StockCreatorMchid, req.PlainText.StockID, req.PlainText.CouponID) if err != nil { - - if !errors.Is(err, gorm.ErrRecordNotFound) { - return nil, err - } - - order, err = this.OrderRepo.GetByTransactionId(ctx, req.PlainText.StockCreatorMchid, req.PlainText.StockID, req.PlainText.ConsumeInformation.TransactionID) - - if err != nil { - - if errors.Is(err, gorm.ErrRecordNotFound) { - - return nil, fmt.Errorf("微信回调消费,订单不存在,StockCreatorMchid:%s,StockID:%s,CouponID:%s,TransactionID:%s,CreateTime:%s", - req.PlainText.StockCreatorMchid, - req.PlainText.StockID, - req.PlainText.CouponID, - req.PlainText.ConsumeInformation.TransactionID, - req.PlainText.CreateTime, - ) - } - - return nil, err - } - - return order, nil + return nil, fmt.Errorf("订单查询错误 error: %v", err) } return order, nil diff --git a/internal/data/repoimpl/order.go b/internal/data/repoimpl/order.go index 8fa2c54..b42c4e1 100644 --- a/internal/data/repoimpl/order.go +++ b/internal/data/repoimpl/order.go @@ -372,22 +372,6 @@ func (p *OrderRepoImpl) GetByCouponId(ctx context.Context, merchantNo, batchNo, return p.ToBo(info), nil } -func (this *OrderRepoImpl) GetByTransactionId(ctx context.Context, stockCreatorMchId, stockID, transactionId string) (*bo.OrderBo, error) { - row := &model.Order{} - - tx := this.DB(ctx).Where(model.Order{MerchantNo: stockCreatorMchId, BatchNo: stockID, TransactionId: transactionId}).First(&row) - - if tx.Error != nil { - return nil, tx.Error - } - - if tx.RowsAffected == 0 { - return nil, gorm.ErrRecordNotFound - } - - return this.ToBo(row), nil -} - func (p *OrderRepoImpl) Ing(ctx context.Context, id uint64) error { now := time.Now() From ba4c95c7a6eb4952ae42cc00b1d5e0709a5a6163 Mon Sep 17 00:00:00 2001 From: ziming Date: Wed, 25 Mar 2026 14:22:35 +0800 Subject: [PATCH 03/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/server/http.go | 4 ++-- internal/service/notify.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/server/http.go b/internal/server/http.go index d6e9084..dbe207f 100644 --- a/internal/server/http.go +++ b/internal/server/http.go @@ -41,10 +41,10 @@ func NewHTTPServer( // 启星(启星-蓝色兄弟立减金代配) /voucher/qixing/v1/notify srv.Route("/voucher/").POST("qixing/v1/notify", tripartiteService.QiXingNotify) // 通知url必须为公网可访问的URL,必须为HTTPS,不能携带参数 - // https://域名.cn/voucher/v1/notify/123456 123456为微信主体商户号 + // https://域名.cn/voucher/v1/notify/123456 123456为微信主体商户号 /voucher/v1/notify/1100040695 // https://gateway.dev.cdlsxd.cn/voucher/v1/notify/123456 123456为微信主体商户号 测试环境 // https://voucher.86698.cn/voucher/v1/notify/123456 123456为微信主体商户号 正式环境 - srv.Route("/voucher/").POST("/notify/v1/{mch_id}", notifyService.Notify) + srv.Route("/voucher/").POST("/v1/notify/{mch_id}", notifyService.Notify) // 订单通知重试 -- 不健全 srv.Route("/voucher/").POST("orderNotifyRetry", cmb.OrderNotifyRetry) diff --git a/internal/service/notify.go b/internal/service/notify.go index c34ac9f..38610bc 100644 --- a/internal/service/notify.go +++ b/internal/service/notify.go @@ -46,7 +46,7 @@ func (srv *NotifyService) Notify(ctx http.Context) error { return fmt.Errorf("读取响应体失败: %w", err) } - if bodyBytes == nil { + if len(bodyBytes) == 0 { log.Errorf("微信回调通知[%s],响应体不能为空", mchId) return ctx.JSON(http2.StatusNetworkAuthenticationRequired, map[string]string{ "code": "FAIL", From d34403b012e19e12b184a509345ac4535c8f58e9 Mon Sep 17 00:00:00 2001 From: ziming Date: Wed, 25 Mar 2026 15:39:40 +0800 Subject: [PATCH 04/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/coupon.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/coupon.go b/test/coupon.go index a258dda..a2ca471 100644 --- a/test/coupon.go +++ b/test/coupon.go @@ -34,6 +34,8 @@ var bc2 = &conf.Bootstrap{ }, } +// bcFJLF Callback{NotifyUrl:https://gateway.dev.cdlsxd.cn/voucher/v1/notify/1100040695, Mchid:1100040695} +// /voucher/v1/notify/1100040695 var bcFJLF = &conf.Bootstrap{ Wechat: &conf.Wechat{ MchID: "1100040695", From 9c8f64a35a0408b1c7d5e6d9acbb43e823123b9b Mon Sep 17 00:00:00 2001 From: ziming Date: Wed, 25 Mar 2026 15:50:54 +0800 Subject: [PATCH 05/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/biz/register_tag.go | 15 +++++++++++++-- internal/server/http.go | 1 + internal/service/notify.go | 4 ++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/internal/biz/register_tag.go b/internal/biz/register_tag.go index aa27147..5a4c902 100644 --- a/internal/biz/register_tag.go +++ b/internal/biz/register_tag.go @@ -25,8 +25,10 @@ func (this *VoucherBiz) RegisterTag(ctx context.Context, id int32) error { return err } - if err = this.registerNotifyTag(ctx, stock.MchId, stock.BatchNo); err != nil { - return err + if this.IsNotifyRegisterTag(stock) { + if err = this.registerNotifyTag(ctx, stock.MchId, stock.BatchNo); err != nil { + return err + } } _, err = this.ProductRepo.GetByProductNo(ctx, stock.ProductNo) @@ -34,6 +36,15 @@ func (this *VoucherBiz) RegisterTag(ctx context.Context, id int32) error { return err } +func (this *VoucherBiz) IsNotifyRegisterTag(product *bo.ProductBo) bool { + for _, subject := range this.bc.WechatSubject { + if subject.MchID == product.MchId { + return false + } + } + return true +} + func (this *VoucherBiz) registerNotifyTag(ctx context.Context, stockCreatorMchID, stockID string) error { cl := vo.WechatNotifyRegisterTagCacheLockKey.BuildCache([]string{this.bc.WechatNotifyMQ.Tag, stockCreatorMchID, stockID}) diff --git a/internal/server/http.go b/internal/server/http.go index dbe207f..cf919ee 100644 --- a/internal/server/http.go +++ b/internal/server/http.go @@ -46,6 +46,7 @@ func NewHTTPServer( // https://voucher.86698.cn/voucher/v1/notify/123456 123456为微信主体商户号 正式环境 srv.Route("/voucher/").POST("/v1/notify/{mch_id}", notifyService.Notify) + // ---脚本-- // 订单通知重试 -- 不健全 srv.Route("/voucher/").POST("orderNotifyRetry", cmb.OrderNotifyRetry) // 重试订单通知,查询微信状态再通知下游招行 diff --git a/internal/service/notify.go b/internal/service/notify.go index 38610bc..a347b71 100644 --- a/internal/service/notify.go +++ b/internal/service/notify.go @@ -54,7 +54,7 @@ func (srv *NotifyService) Notify(ctx http.Context) error { }) } - log.Warnf("微信回调通知[%s],headers:%+v,body:%s", mchId, headers, string(bodyBytes)) + log.Warnf("微信回调通知[%s],数据,headers:%+v,body:%s", mchId, headers, string(bodyBytes)) response, err := srv.WechatBiz.CallBack(ctx, mchId, &headers, bodyBytes) if err != nil { @@ -68,7 +68,7 @@ func (srv *NotifyService) Notify(ctx http.Context) error { if err = srv.VoucherBiz.WechatNotifyConsumer(ctx, ip, "WECHAT_NOTIFY", response); err != nil { - log.Errorf("微信回调通知[%s],headers:%+v,body:%s,err:%s", mchId, headers, string(bodyBytes), err.Error()) + log.Errorf("微信回调通知[%s],处理失败,headers:%+v,body:%s,err:%s", mchId, headers, string(bodyBytes), err.Error()) return ctx.JSON(http2.StatusBadRequest, map[string]string{ "code": "FAIL", "message": err.Error(), From 5ba27d7f7b970bb06287df8160e5d0c62c893ce2 Mon Sep 17 00:00:00 2001 From: ziming Date: Wed, 25 Mar 2026 17:10:27 +0800 Subject: [PATCH 06/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/pkg/request/request_test.go | 40 ++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/internal/pkg/request/request_test.go b/internal/pkg/request/request_test.go index 4f13710..eef583f 100644 --- a/internal/pkg/request/request_test.go +++ b/internal/pkg/request/request_test.go @@ -2,6 +2,8 @@ package request import ( "context" + "encoding/json" + "fmt" "net/http" "net/url" "testing" @@ -48,6 +50,7 @@ func Test_RequestHeaders(t *testing.T) { } func Test_RequestStatusCode(t *testing.T) { + uri := "http://example.com/api/update" body := []byte("update data") @@ -64,3 +67,40 @@ func Test_RequestStatusCode(t *testing.T) { t.Logf("响应体:", string(respBody)) t.Logf("响应头:", respHeader) } + +func Test_WxNotifyRequest(t *testing.T) { + + uri := "https://gateway.dev.cdlsxd.cn/voucher/v1/notify/1100040695" + + body := []byte(`{"id":"fd06376a-3e1b-5516-81f8-9b69cf1ba416","create_time":"2025-07-28T16:10:15+08:00","resource_type":"encrypt-resource","event_type":"MCHTRANSFER.BILL.FINISHED","summary":"商家转账单据终态通知","resource":{"original_type":"mch_payment","algorithm":"AEAD_AES_256_GCM","ciphertext":"XJBIhrHgbe9NR5q/jLYmZKdT/3xuKm2x7EFu3T52Hj2hjPzarRSA2HCsGTxGojfD+CFyJHIULlL2adqLijAjpi3B6TaYKY4LqhtJ/RYSQtYNxYvBpWX1yLOWe8luJbWxmQvKZxIekFs8lGVgkPBUw0IfEAvJ6jHAGCcgxLIqxgOf6UtGUqxCCNp/V3xy8zCiHB0Mvlw8eXCTuG+ZESJIXvloVGNS79R6iNeqk4kNKRSaV86MNh1KQlmoBxZ4yEshD/vIlMulU3xEc+mM25y8vUS4Ot6pxEpUdUyjwcb9QTwTTnZzm6i+VWYymcItAVBQrvsKBMmqWnPtNXG8++13k3DeO1LyVKURmnWXXT1mImmGx/teN/1xPV5y6nChu/HTbcJGDQy2twuq6TPFbbYlTjZH047z/ZtozJNvGNeh","associated_data":"mch_payment","nonce":"YN3eW5H8mxLs"}}`) + + hds := `{"Accept":["*/*"],"Cache-Control":["no-cache"],"Connection":["close"],"Content-Length":["775"],"Content-Type":["application/json"],"Pragma":["no-cache"],"User-Agent":["Mozilla/4.0"],"Wechatpay-Nonce":["dF8R9izUJnPBjVLa2cAcCaa7j6QUgitl"],"Wechatpay-Serial":["PUB_KEY_ID_0116523224422025061800192371001800"],"Wechatpay-Signature":["SoXIiTRTr6jofXXxGlfO+wyf1IzXFXcsfvEU2EggQfRKFu+8h3TT6QMQ8zIf8dpkkTPexB/3igGiATrR3uZY4ZeOpRrhIFHSJj0Ala0Ri2Nt4zk+MuBQnhybSYJ4Cn3/sHC4i2HFoOSil7OqlSr79hjod3h0tjYVQLtZ4+Cjp0IeMNB4p5qmIuERuhtfRqcyqXik9/uYNYxw8/Wkf1mMnTsBxyXK3iHAoinXNrEiqCCrQHCfnORMYosr7l+Ox8v9u1c8FFt+rt09vKssVCqYaZ/XRala3mjslDRiluFKSuqb7/JO3AxQjBK6M0iSZOlnmiXSIAq+UxJg4cem6wHi+g=="],"Wechatpay-Signature-Type":["WECHATPAY2-SHA256-RSA2048"],"Wechatpay-Timestamp":["1753690220"],"X-Forwarded-For":["121.51.58.168"],"X-Forwarded-Proto":["https"],"X-Real-Ip":["121.51.58.168"]}` + + var headerMap http.Header + if err := json.Unmarshal([]byte(hds), &headerMap); err != nil { + t.Error(fmt.Sprintf("解析 headers 失败: %v", err)) + return + } + + hc := &http.Client{ + Timeout: 10 * time.Second, + Transport: &http.Transport{ + MaxIdleConns: 1, // 最大空闲连接数 + MaxIdleConnsPerHost: 1, // 每个主机的最大空闲连接数 + IdleConnTimeout: 10 * time.Second, // 空闲连接超时时间 + }, + } + + isSuccess := func(code int) bool { + return code == http.StatusOK || code == http.StatusCreated + } + + respHeader, respBody, err := Post(context.Background(), uri, body, WithHttpClient(hc), WithStatusCodeFunc(isSuccess)) + if err != nil { + t.Error(err) + return + } + + t.Logf("响应体:%s", string(respBody)) + t.Logf("响应头:%v", respHeader) +} From 7c851ddb1b4f7abf2a75565cc2dab3aeb6114d39 Mon Sep 17 00:00:00 2001 From: ziming Date: Wed, 25 Mar 2026 17:29:00 +0800 Subject: [PATCH 07/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/biz/wechat_notify.go | 6 +++--- internal/service/notify.go | 2 +- internal/service/voucher.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/biz/wechat_notify.go b/internal/biz/wechat_notify.go index cf19609..ac73bc1 100644 --- a/internal/biz/wechat_notify.go +++ b/internal/biz/wechat_notify.go @@ -9,9 +9,9 @@ import ( "voucher/internal/pkg/lock" ) -func (this *VoucherBiz) WechatNotifyConsumer(ctx context.Context, ip, tag string, req *bo.WechatVoucherNotifyBo) error { +func (this *VoucherBiz) WechatNotifyConsumer(ctx context.Context, ip string, req *bo.WechatVoucherNotifyBo) error { - c := vo.WechatNotifyConsumeLockKey.BuildCache([]string{tag, req.PlainText.StockID, req.PlainText.CouponID}) + c := vo.WechatNotifyConsumeLockKey.BuildCache([]string{req.PlainText.StockCreatorMchid, req.PlainText.StockID, req.PlainText.CouponID}) return lock.NewMutex(this.rdb.Rdb, c.TTL).Lock(ctx, c.Key, func(ctx context.Context) error { @@ -21,7 +21,7 @@ func (this *VoucherBiz) WechatNotifyConsumer(ctx context.Context, ip, tag string } if order.ActivityId != "" { - return this.MultiBiz.Run(ctx, ip, "lsxd_"+req.PlainText.StockCreatorMchid, req, order) + return this.MultiBiz.Run(ctx, ip, req.PlainText.StockCreatorMchid, req, order) } if req.PlainText.Status.IsSended() { diff --git a/internal/service/notify.go b/internal/service/notify.go index a347b71..01dfb5b 100644 --- a/internal/service/notify.go +++ b/internal/service/notify.go @@ -66,7 +66,7 @@ func (srv *NotifyService) Notify(ctx http.Context) error { ip := helper.GetClientIP(ctx) - if err = srv.VoucherBiz.WechatNotifyConsumer(ctx, ip, "WECHAT_NOTIFY", response); err != nil { + if err = srv.VoucherBiz.WechatNotifyConsumer(ctx, ip, response); err != nil { log.Errorf("微信回调通知[%s],处理失败,headers:%+v,body:%s,err:%s", mchId, headers, string(bodyBytes), err.Error()) return ctx.JSON(http2.StatusBadRequest, map[string]string{ diff --git a/internal/service/voucher.go b/internal/service/voucher.go index fddf7d1..07b3211 100644 --- a/internal/service/voucher.go +++ b/internal/service/voucher.go @@ -133,5 +133,5 @@ func (v *VoucherService) WechatUseNotifyConsumer(ctx context.Context, tag, msg s return err } - return v.VoucherBiz.WechatNotifyConsumer(ctx, "127.0.0.1", tag, req) + return v.VoucherBiz.WechatNotifyConsumer(ctx, "127.0.0.1", req) } From cf9ecc4486757041f54e69c17034c4217aa41792 Mon Sep 17 00:00:00 2001 From: ziming Date: Thu, 26 Mar 2026 10:14:59 +0800 Subject: [PATCH 08/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/biz/wechat_notify.go | 7 +++++++ internal/service/notify.go | 2 ++ test/coupon.go | 11 ++++++----- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/internal/biz/wechat_notify.go b/internal/biz/wechat_notify.go index ac73bc1..18c0f8f 100644 --- a/internal/biz/wechat_notify.go +++ b/internal/biz/wechat_notify.go @@ -21,6 +21,9 @@ func (this *VoucherBiz) WechatNotifyConsumer(ctx context.Context, ip string, req } if order.ActivityId != "" { + if err = req.Validate(); err != nil { + return fmt.Errorf("multi validate req error: %v", err) + } return this.MultiBiz.Run(ctx, ip, req.PlainText.StockCreatorMchid, req, order) } @@ -43,6 +46,10 @@ func (this *VoucherBiz) WechatNotifyConsumer(ctx context.Context, ip string, req func (this *VoucherBiz) getOrder(ctx context.Context, req *bo.WechatVoucherNotifyBo) (*bo.OrderBo, error) { + if req.PlainText.StockCreatorMchid == "" || req.PlainText.StockID == "" || req.PlainText.CouponID == "" { + return nil, fmt.Errorf("订单查询参数错误") + } + order, err := this.OrderRepo.GetByCouponId(ctx, req.PlainText.StockCreatorMchid, req.PlainText.StockID, req.PlainText.CouponID) if err != nil { diff --git a/internal/service/notify.go b/internal/service/notify.go index 01dfb5b..9c35c63 100644 --- a/internal/service/notify.go +++ b/internal/service/notify.go @@ -66,6 +66,8 @@ func (srv *NotifyService) Notify(ctx http.Context) error { ip := helper.GetClientIP(ctx) + log.Warnf("微信回调通知[%s],解析数据,response:%+v", mchId, response) + if err = srv.VoucherBiz.WechatNotifyConsumer(ctx, ip, response); err != nil { log.Errorf("微信回调通知[%s],处理失败,headers:%+v,body:%s,err:%s", mchId, headers, string(bodyBytes), err.Error()) diff --git a/test/coupon.go b/test/coupon.go index a2ca471..8cccda8 100644 --- a/test/coupon.go +++ b/test/coupon.go @@ -24,8 +24,8 @@ var bc = &conf.Bootstrap{ }, } -// bc2 Callback{NotifyUrl:https://nsall.86698.cn/wechatPay/coupon_notify/fjxingwang, Mchid:1652465541} -var bc2 = &conf.Bootstrap{ +// bcFJXW Callback{NotifyUrl:https://nsall.86698.cn/wechatPay/coupon_notify/fjxingwang, Mchid:1652465541} +var bcFJXW = &conf.Bootstrap{ Wechat: &conf.Wechat{ MchID: "1652465541", // notifyUrl https://nsall.86698.cn/wechatPay/coupon_notify/fjxingwang MchCertificateSerialNumber: "1E3F2CE013203BA9C3DEFC5782FCD3329C3DAC1C", @@ -169,8 +169,8 @@ func QueryProduct() { } req := cashcoupons.QueryStockRequest{ - StockId: core.String("21502886"), - StockCreatorMchid: core.String("1652465541"), + StockId: core.String("21923564"), + StockCreatorMchid: core.String("1100040695"), } svc := cashcoupons.StockApiService{Client: client} @@ -195,7 +195,8 @@ func QueryProduct() { fmt.Printf("\n剩余库存:%d", availableStock) fmt.Printf("\n剩余预算:%d", availableStock*couponAmount) - fmt.Printf("\nWxResp:%+v", WxResp(resp)) + str, _ := json.Marshal(WxResp(resp)) + fmt.Printf("\nWxResp:%+v", string(str)) return } From d5460e9415f2b44ef0cb8848e733ecb71fe9b492 Mon Sep 17 00:00:00 2001 From: ziming Date: Thu, 26 Mar 2026 10:44:29 +0800 Subject: [PATCH 09/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wechatrepoimpl/bank_multi_activity.go | 14 ++++++++++-- .../pkg/wechat/srv/marketing/marketing.go | 8 +++---- internal/pkg/wechat/utils/aes_test.go | 22 +++++++++++++++++++ internal/pkg/wechat/utils/wxpay_utility.go | 17 +++++++++----- 4 files changed, 49 insertions(+), 12 deletions(-) create mode 100644 internal/pkg/wechat/utils/aes_test.go diff --git a/internal/data/wechatrepoimpl/bank_multi_activity.go b/internal/data/wechatrepoimpl/bank_multi_activity.go index 58bd146..f0c9b00 100644 --- a/internal/data/wechatrepoimpl/bank_multi_activity.go +++ b/internal/data/wechatrepoimpl/bank_multi_activity.go @@ -66,14 +66,24 @@ func (w *BankMultiActivityImpl) Notify(ctx context.Context, mchId string, header return } - decodeBodyStr, err := t.Notify(ctx, headers, respBody) + body, decodeBodyStr, err := t.Notify(ctx, headers, respBody) if err != nil { return } + var plainText bo.PlainText if err = json.Unmarshal([]byte(decodeBodyStr), &response); err != nil { return nil, err } - return + return &bo.WechatVoucherNotifyBo{ + ID: body.Id, + CreateTime: body.CreateTime, + ResourceType: body.ResourceType, + EventType: body.EventType, + Summary: body.Summary, + //OriginalType: body.OriginalType, + //AssociatedData: body.AssociatedData, + PlainText: plainText, + }, nil } diff --git a/internal/pkg/wechat/srv/marketing/marketing.go b/internal/pkg/wechat/srv/marketing/marketing.go index 71e0894..362776a 100644 --- a/internal/pkg/wechat/srv/marketing/marketing.go +++ b/internal/pkg/wechat/srv/marketing/marketing.go @@ -63,12 +63,12 @@ func (srv *Marketing) Query(appId, openId, couponId string) (response *SendResp, } // Notify . -func (srv *Marketing) Notify(_ context.Context, headers *http.Header, respBody []byte) (response string, err error) { +func (srv *Marketing) Notify(_ context.Context, headers *http.Header, respBody []byte) (body *utils.WxNotifyBody, response string, err error) { - bizStr, err := srv.GetDecodeBody(headers, respBody) + wxNotifyBody, bizStr, err := srv.GetDecodeBody(headers, respBody) if err != nil { - return "", err + return nil, "", err } - return bizStr, nil + return wxNotifyBody, bizStr, nil } diff --git a/internal/pkg/wechat/utils/aes_test.go b/internal/pkg/wechat/utils/aes_test.go new file mode 100644 index 0000000..7a4e4de --- /dev/null +++ b/internal/pkg/wechat/utils/aes_test.go @@ -0,0 +1,22 @@ +package utils + +import ( + "testing" +) + +func TestAes(t *testing.T) { + + aesUtil, err := NewAesUtil("d9af70585b18ae206d981548c766563f") + if err != nil { + t.Errorf("NewAesUtil() error = %v", err) + return + } + + gotResponse, err := aesUtil.DecryptToString("coupon", "pV96KsdLrSAd", "IfJmKJnBYezruwmk+IA+ENQBn13oWiLR1YnYi3bhYaqYreaXwFcFWbnnQjWfh8RDF36S1v+H1eLj0OtiQVDveLJ9vOcCrtr0M8bkV6NpRN7amqcf02vg+xLcs6UiZDedkN353nMnVVY1SAyuJj0AjKTUJrMhPNtxzdPpL/A3bMVCHrw4sqUItAOsDWVyJ5Kb09BEbdNOesmFkCc+Pcqmr554UHXzjQxzYimMpdbZ8OCovEy6JY1jnbxyiduDI7XBiTvxLr5CzLGlxNk1tIHUSrrDnRzUCpFJzNETgcrF3JWvQVAPThelGnTLN/TT3/pyM7/Kz60YNGaSbJTNKOtxbLXopk72x5hlt45fgrueP+RsnVQWmjULJ0AVEVmPNtlHTYRw7WV0cGwwrF7CCSDifYPed9QZPU1+awVtnGb3If53l109HojJiJB7Y0C/ZPX/vH/KLmtUx+10YKIFOo3vYnxEBV5lv4D5ZqXn4gOOtufVH/URYqgRqnzNFqJV6sW2qP6K9AgBhe/BO4AoeDDHG00XP89XFbeHmR2zZ019jBapq92YkvDVtQc2oLr9MWPoHJUhkmApwB6AmOK7ldWJ14P/Fws3/zBzCxVSlQsgLoDyBKm461ZnA6k09c+hffhTutLt89HYv+pqA1nukdTGuCWqPqUgx0AsK5i1CngrBQZJwc7+ylpakpOrX8oulyuA3So65hgMuqgl5dgkR0e8nvjjv7dVl0aRkqTZt3VL7BB1xwGqbvO3aTEuqqPrFQKLpnaY5Gz64dgg6huQFOBnZww=") + if err != nil { + t.Errorf("DecryptToString() error = %v", err) + return + } + + t.Log(gotResponse) +} diff --git a/internal/pkg/wechat/utils/wxpay_utility.go b/internal/pkg/wechat/utils/wxpay_utility.go index 15de6d0..95ba98f 100644 --- a/internal/pkg/wechat/utils/wxpay_utility.go +++ b/internal/pkg/wechat/utils/wxpay_utility.go @@ -576,10 +576,10 @@ func (srv *MchConfig) Verify(request *http.Request) (string, error) { return EncryptOAEPWithPublicKey(string(respBody), srv.wechatPayPublicKey) } -func (srv *MchConfig) GetDecodeBody(headers *http.Header, respBody []byte) (string, error) { +func (srv *MchConfig) GetDecodeBody(headers *http.Header, respBody []byte) (*WxNotifyBody, string, error) { if respBody == nil { - return "", fmt.Errorf("request HttpBody is nil") + return nil, "", fmt.Errorf("request HttpBody is nil") } err := ValidateResponse( @@ -589,20 +589,25 @@ func (srv *MchConfig) GetDecodeBody(headers *http.Header, respBody []byte) (stri respBody, ) if err != nil { - return "", err + return nil, "", err } var wxNotifyBody WxNotifyBody if err = json.Unmarshal(respBody, &wxNotifyBody); err != nil { - return "", err + return nil, "", err } aesUtil, err := NewAesUtil(srv.aesKey) if err != nil { - return "", err + return nil, "", err } - return aesUtil.DecryptToString(wxNotifyBody.Resource.AssociatedData, wxNotifyBody.Resource.Nonce, wxNotifyBody.Resource.Ciphertext) + decryptedText, err := aesUtil.DecryptToString(wxNotifyBody.Resource.AssociatedData, wxNotifyBody.Resource.Nonce, wxNotifyBody.Resource.Ciphertext) + if err != nil { + return nil, "", err + } + + return &wxNotifyBody, decryptedText, nil } // BuildSortedQueryString 函数接受一个 map,返回按照字段名排序后的 URL 键值对格式字符串 From 2735d8d71dfda0cbd068e1fed84f9d98681f0ad4 Mon Sep 17 00:00:00 2001 From: ziming Date: Thu, 26 Mar 2026 10:52:22 +0800 Subject: [PATCH 10/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/pkg/wechat/utils/aes_test.go | 2 +- internal/service/notify.go | 6 ++++-- test/bank_multi_activity_test.go | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/internal/pkg/wechat/utils/aes_test.go b/internal/pkg/wechat/utils/aes_test.go index 7a4e4de..21f4d12 100644 --- a/internal/pkg/wechat/utils/aes_test.go +++ b/internal/pkg/wechat/utils/aes_test.go @@ -12,7 +12,7 @@ func TestAes(t *testing.T) { return } - gotResponse, err := aesUtil.DecryptToString("coupon", "pV96KsdLrSAd", "IfJmKJnBYezruwmk+IA+ENQBn13oWiLR1YnYi3bhYaqYreaXwFcFWbnnQjWfh8RDF36S1v+H1eLj0OtiQVDveLJ9vOcCrtr0M8bkV6NpRN7amqcf02vg+xLcs6UiZDedkN353nMnVVY1SAyuJj0AjKTUJrMhPNtxzdPpL/A3bMVCHrw4sqUItAOsDWVyJ5Kb09BEbdNOesmFkCc+Pcqmr554UHXzjQxzYimMpdbZ8OCovEy6JY1jnbxyiduDI7XBiTvxLr5CzLGlxNk1tIHUSrrDnRzUCpFJzNETgcrF3JWvQVAPThelGnTLN/TT3/pyM7/Kz60YNGaSbJTNKOtxbLXopk72x5hlt45fgrueP+RsnVQWmjULJ0AVEVmPNtlHTYRw7WV0cGwwrF7CCSDifYPed9QZPU1+awVtnGb3If53l109HojJiJB7Y0C/ZPX/vH/KLmtUx+10YKIFOo3vYnxEBV5lv4D5ZqXn4gOOtufVH/URYqgRqnzNFqJV6sW2qP6K9AgBhe/BO4AoeDDHG00XP89XFbeHmR2zZ019jBapq92YkvDVtQc2oLr9MWPoHJUhkmApwB6AmOK7ldWJ14P/Fws3/zBzCxVSlQsgLoDyBKm461ZnA6k09c+hffhTutLt89HYv+pqA1nukdTGuCWqPqUgx0AsK5i1CngrBQZJwc7+ylpakpOrX8oulyuA3So65hgMuqgl5dgkR0e8nvjjv7dVl0aRkqTZt3VL7BB1xwGqbvO3aTEuqqPrFQKLpnaY5Gz64dgg6huQFOBnZww=") + gotResponse, err := aesUtil.DecryptToString("coupon", "EGvd12Nf2Z1X", "+sUnGECvoHvsFE0ZnTT1ij/b8TKUxYS9jRh3gUSZbsO0WXEgXnwOvfOQjugnqx58lmYafa4BEoJ08c8La0HJpzZUthd6OEkcYlELcQOfqr72sikfFa5izbsNHh+hqd0fmUmpioyCre/BcrDneFqrOShrcLqUdb7FugI362Q1wgCgVJ1DqbGFvQuLHSHZ+UG0HY7MF7lt5r1w72tklHuvbxdfJZ2Vaj5muRUUpZIsZDZ76pqDlsTepxt5upWeVOBMLHjXCMEIJzcV0jF5tFgn7GTea/DORmRLEpY03uMiAyTYYaVbww1S0b96VuoTjVAAf0+oeiLtn+ZoWO1ZL5+Rbz1GTxivluoD+WVn/V5WUYNYnftiQ0LZcigxXIp+DYJOnuoLMDdWQCih4bElJGZnFuJIWpkS73Dn3HfbAx8lg0vq882PY/tk0bOXH6yrWGT9DO6ZHnPNABy4pb6xLRJtvmflSDhYc5Fa7qn0ySRAIK8e4C+z7aeKhzhWpNe2kGyjKKQqN92j9kMjsbfDqOSPRVB3FIxOvbzrJyR3erkN2WYRzTCZ6U7Qbgg/+tdWPdYNhAMrVbdmp2J1/daetCmmrd2HIIB6HqhsHYQMu0/NxQVPLGXuHzHY9fuNiYczST6KobLOFQ0fEO2dx6AFNImwHxHbdSHpchUqcE6sdjfoHNgSW7Jv4Fclm8GDqQmu4l6/ZhJrpn+I4ePijo7U/Ewh4KrCBHiw0JEttcZuBDqMM1Q9rmI4trcfekT2wy7HGgK9qetuOHMC5ENfO/U1gpa4wIU=") if err != nil { t.Errorf("DecryptToString() error = %v", err) return diff --git a/internal/service/notify.go b/internal/service/notify.go index 9c35c63..2dacb6d 100644 --- a/internal/service/notify.go +++ b/internal/service/notify.go @@ -1,6 +1,7 @@ package service import ( + "encoding/json" "fmt" "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/transport/http" @@ -54,7 +55,8 @@ func (srv *NotifyService) Notify(ctx http.Context) error { }) } - log.Warnf("微信回调通知[%s],数据,headers:%+v,body:%s", mchId, headers, string(bodyBytes)) + headerJson, _ := json.Marshal(headers) + log.Warnf("微信回调通知[%s],数据,headers:%s,body:%s", mchId, string(headerJson), string(bodyBytes)) response, err := srv.WechatBiz.CallBack(ctx, mchId, &headers, bodyBytes) if err != nil { @@ -70,7 +72,7 @@ func (srv *NotifyService) Notify(ctx http.Context) error { if err = srv.VoucherBiz.WechatNotifyConsumer(ctx, ip, response); err != nil { - log.Errorf("微信回调通知[%s],处理失败,headers:%+v,body:%s,err:%s", mchId, headers, string(bodyBytes), err.Error()) + log.Errorf("微信回调通知[%s],处理失败,headers:%s,body:%s,err:%s", mchId, headerJson, string(bodyBytes), err.Error()) return ctx.JSON(http2.StatusBadRequest, map[string]string{ "code": "FAIL", "message": err.Error(), diff --git a/test/bank_multi_activity_test.go b/test/bank_multi_activity_test.go index 453983b..8b39e4c 100644 --- a/test/bank_multi_activity_test.go +++ b/test/bank_multi_activity_test.go @@ -149,7 +149,7 @@ func Test_MarketingNotify(t *testing.T) { return } - bizContent, err := marketingFJLF().Notify(context.Background(), &httpHeaders, []byte(body)) + _, bizContent, err := marketingFJLF().Notify(context.Background(), &httpHeaders, []byte(body)) if err != nil { fmt.Printf("notify err: %+v\n", err) return From 6a06e1fed47362d65c0579e081922dabcf50d666 Mon Sep 17 00:00:00 2001 From: ziming Date: Thu, 26 Mar 2026 10:56:15 +0800 Subject: [PATCH 11/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/wechatrepoimpl/bank_multi_activity.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/data/wechatrepoimpl/bank_multi_activity.go b/internal/data/wechatrepoimpl/bank_multi_activity.go index f0c9b00..a18a6bc 100644 --- a/internal/data/wechatrepoimpl/bank_multi_activity.go +++ b/internal/data/wechatrepoimpl/bank_multi_activity.go @@ -77,13 +77,13 @@ func (w *BankMultiActivityImpl) Notify(ctx context.Context, mchId string, header } return &bo.WechatVoucherNotifyBo{ - ID: body.Id, - CreateTime: body.CreateTime, - ResourceType: body.ResourceType, - EventType: body.EventType, - Summary: body.Summary, - //OriginalType: body.OriginalType, - //AssociatedData: body.AssociatedData, - PlainText: plainText, + ID: body.Id, + CreateTime: body.CreateTime, + ResourceType: body.ResourceType, + EventType: body.EventType, + Summary: body.Summary, + OriginalType: body.Resource.OriginalType, + AssociatedData: body.Resource.AssociatedData, + PlainText: plainText, }, nil } From 5051e2e744712e8b4c7b8c7278e3e56f912eb4df Mon Sep 17 00:00:00 2001 From: ziming Date: Thu, 26 Mar 2026 10:57:16 +0800 Subject: [PATCH 12/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/service/notify.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/internal/service/notify.go b/internal/service/notify.go index 2dacb6d..4425565 100644 --- a/internal/service/notify.go +++ b/internal/service/notify.go @@ -55,11 +55,11 @@ func (srv *NotifyService) Notify(ctx http.Context) error { }) } - headerJson, _ := json.Marshal(headers) - log.Warnf("微信回调通知[%s],数据,headers:%s,body:%s", mchId, string(headerJson), string(bodyBytes)) - response, err := srv.WechatBiz.CallBack(ctx, mchId, &headers, bodyBytes) if err != nil { + headerJson, _ := json.Marshal(headers) + log.Errorf("微信回调通知[%s],CallBack处理失败,headers:%s,body:%s,err:%s", mchId, headerJson, string(bodyBytes), err.Error()) + return ctx.JSON(http2.StatusNetworkAuthenticationRequired, map[string]string{ "code": "FAIL", "message": err.Error(), @@ -72,7 +72,9 @@ func (srv *NotifyService) Notify(ctx http.Context) error { if err = srv.VoucherBiz.WechatNotifyConsumer(ctx, ip, response); err != nil { - log.Errorf("微信回调通知[%s],处理失败,headers:%s,body:%s,err:%s", mchId, headerJson, string(bodyBytes), err.Error()) + headerJson, _ := json.Marshal(headers) + log.Errorf("微信回调通知[%s],Consumer处理失败,headers:%s,body:%s,err:%s", mchId, headerJson, string(bodyBytes), err.Error()) + return ctx.JSON(http2.StatusBadRequest, map[string]string{ "code": "FAIL", "message": err.Error(), From 129b849b02564c9ddfbd913e665d8295e1aa25fe Mon Sep 17 00:00:00 2001 From: ziming Date: Thu, 26 Mar 2026 10:57:57 +0800 Subject: [PATCH 13/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/service/notify.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/internal/service/notify.go b/internal/service/notify.go index 4425565..e56c03c 100644 --- a/internal/service/notify.go +++ b/internal/service/notify.go @@ -68,12 +68,10 @@ func (srv *NotifyService) Notify(ctx http.Context) error { ip := helper.GetClientIP(ctx) - log.Warnf("微信回调通知[%s],解析数据,response:%+v", mchId, response) - if err = srv.VoucherBiz.WechatNotifyConsumer(ctx, ip, response); err != nil { headerJson, _ := json.Marshal(headers) - log.Errorf("微信回调通知[%s],Consumer处理失败,headers:%s,body:%s,err:%s", mchId, headerJson, string(bodyBytes), err.Error()) + log.Errorf("微信回调通知[%s],Consumer处理失败,headers:%s,body:%s,解析数据:%+v,err:%s", mchId, headerJson, string(bodyBytes), response, err.Error()) return ctx.JSON(http2.StatusBadRequest, map[string]string{ "code": "FAIL", From 6e57c2afe5ff37f8bb831b33661b1a3ca20034aa Mon Sep 17 00:00:00 2001 From: ziming Date: Thu, 26 Mar 2026 10:59:24 +0800 Subject: [PATCH 14/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/service/notify.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/notify.go b/internal/service/notify.go index e56c03c..2222ece 100644 --- a/internal/service/notify.go +++ b/internal/service/notify.go @@ -71,7 +71,7 @@ func (srv *NotifyService) Notify(ctx http.Context) error { if err = srv.VoucherBiz.WechatNotifyConsumer(ctx, ip, response); err != nil { headerJson, _ := json.Marshal(headers) - log.Errorf("微信回调通知[%s],Consumer处理失败,headers:%s,body:%s,解析数据:%+v,err:%s", mchId, headerJson, string(bodyBytes), response, err.Error()) + log.Errorf("微信回调通知[%s],Consumer处理失败,headers:%s,body:%s\n解析数据:%+v,err:%s", mchId, headerJson, string(bodyBytes), response, err.Error()) return ctx.JSON(http2.StatusBadRequest, map[string]string{ "code": "FAIL", From 00e637d3838ba1b3e495f159567e0d02f02f71f2 Mon Sep 17 00:00:00 2001 From: ziming Date: Thu, 26 Mar 2026 11:17:06 +0800 Subject: [PATCH 15/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/pkg/wechat/utils/wxpay_utility.go | 15 +++-- test/bank_multi_activity.go | 5 +- test/bank_multi_activity_test.go | 73 +++++++++------------- 3 files changed, 40 insertions(+), 53 deletions(-) diff --git a/internal/pkg/wechat/utils/wxpay_utility.go b/internal/pkg/wechat/utils/wxpay_utility.go index 95ba98f..1089bbe 100644 --- a/internal/pkg/wechat/utils/wxpay_utility.go +++ b/internal/pkg/wechat/utils/wxpay_utility.go @@ -19,7 +19,6 @@ import ( "net/url" "os" "sort" - "strconv" "strings" "time" ) @@ -297,13 +296,13 @@ func ValidateResponse( nonce := headers.Get(WechatPayNonce) // 拒绝过期请求 - timestamp, err := strconv.ParseInt(timestampStr, 10, 64) - if err != nil { - return fmt.Errorf("invalid timestamp: %v", err) - } - if time.Now().Sub(time.Unix(timestamp, 0)) > 5*time.Minute { - return errors.New("invalid timestamp") - } + //timestamp, err := strconv.ParseInt(timestampStr, 10, 64) + //if err != nil { + // return fmt.Errorf("invalid timestamp: %v", err) + //} + //if time.Now().Sub(time.Unix(timestamp, 0)) > 5*time.Minute { + // return errors.New("invalid timestamp") + //} if serialNo != wechatpayPublicKeyId { return fmt.Errorf( diff --git a/test/bank_multi_activity.go b/test/bank_multi_activity.go index 84c0d2f..1c137f4 100644 --- a/test/bank_multi_activity.go +++ b/test/bank_multi_activity.go @@ -87,10 +87,11 @@ func marketingFJLF() *marketing2.Marketing { parentDir := filepath.Dir(dir) mchId := "1100040695" - wechatPayPublicKeyId := " PUB_KEY_ID_0111000406952026032500382251001000" + wechatPayPublicKeyId := "PUB_KEY_ID_0111000406952026032500382251001000" certificateSerialNo := "46712853869DB0EDAA9B4DF97DADEECD4CCDC85B" filePath := fmt.Sprintf("%s/cert/wechat/%s", parentDir, mchId) + fmt.Printf("filePath: %s\n", filePath) c, err := utils.CreateMchConfig( mchId, // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756 @@ -98,7 +99,7 @@ func marketingFJLF() *marketing2.Marketing { fmt.Sprintf("%s/%s", filePath, "wechat_private_key.pem"), // 商户API证书私钥文件路径,本地文件路径 wechatPayPublicKeyId, // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816 fmt.Sprintf("%s/%s", filePath, "pub_key.pem"), // 微信支付公钥文件路径,本地文件路径 - "", + "d9af70585b18ae206d981548c766563f", ) if err != nil { panic(err) diff --git a/test/bank_multi_activity_test.go b/test/bank_multi_activity_test.go index 8b39e4c..4537df9 100644 --- a/test/bank_multi_activity_test.go +++ b/test/bank_multi_activity_test.go @@ -97,51 +97,38 @@ func Test_QixingNotifyData(t *testing.T) { func Test_MarketingNotify(t *testing.T) { header := `{ - "Content-Type": [ - "application/json" - ], - "Wechatpay-Nonce": [ - "dF8R9izUJnPBjVLa2cAcCaa7j6QUgitl" - ], - "Wechatpay-Serial": [ - "PUB_KEY_ID_0116523224422025061800192371001800" - ], - "Wechatpay-Signature": [ - "SoXIiTRTr6jofXXxGlfO+wyf1IzXFXcsfvEU2EggQfRKFu+8h3TT6QMQ8zIf8dpkkTPexB/3igGiATrR3uZY4ZeOpRrhIFHSJj0Ala0Ri2Nt4zk+MuBQnhybSYJ4Cn3/sHC4i2HFoOSil7OqlSr79hjod3h0tjYVQLtZ4+Cjp0IeMNB4p5qmIuERuhtfRqcyqXik9/uYNYxw8/Wkf1mMnTsBxyXK3iHAoinXNrEiqCCrQHCfnORMYosr7l+Ox8v9u1c8FFt+rt09vKssVCqYaZ/XRala3mjslDRiluFKSuqb7/JO3AxQjBK6M0iSZOlnmiXSIAq+UxJg4cem6wHi+g==" - ], - "Wechatpay-Signature-Type": [ - "WECHATPAY2-SHA256-RSA2048" - ], - "Wechatpay-Timestamp": [ - "1753690220" - ], - "X-Forwarded-For": [ - "121.51.58.168" - ], - "X-Forwarded-Proto": [ - "https" - ], - "X-Real-Ip": [ - "121.51.58.168" - ] - }` + "Content-Type": [ + "application/json" + ], + "Wechatpay-Nonce": [ + "H3Bs5sCbMJZOBLA4w3S6YbN4cCcO9i00" + ], + "Wechatpay-Serial": [ + "PUB_KEY_ID_0111000406952026032500382251001000" + ], + "Wechatpay-Signature": [ + "Kk5uQqZA3pyOV1cfJpRDNEghsG4vE0gBM4XIncJGgsuyG6SNNMbmXUy9TaBRoqylE5EoIYWXaXWOXn7gHW8rOK6bDkiJW2zoZUI6lRrPZSN8Mm1qaIiB42NsfExA+J6H3VqlsUtfyDiXJdsMRe2Y6pwGhj/GDJpb74PlsyaGOuZUfp/Z1Fy4gFZ2hhBx9MVIdIy0aGpU7D096HaDf30YJmdqHX0Cy59bPI9paaOX39kHt0H0WYpOeQEAsCHtVJTXF7betIBAPT1HO0PVZDS9ShO19AhMbislvPRxMEsZ6QYA2TVdyU6qegzkc7vaRiRjDla2V7YSzrmT745OTvPu1g==" + ], + "Wechatpay-Signature-Type": [ + "WECHATPAY2-SHA256-RSA2048" + ], + "Wechatpay-Timestamp": [ + "1774490961" + ] +}` body := `{ - "id": "fd06376a-3e1b-5516-81f8-9b69cf1ba416", - "create_time": "2025-07-28T16:10:15+08:00", + "id": "56b4937c-8f32-52e0-880a-828c869fa2c0", + "create_time": "2026-03-26T10:09:21+08:00", "resource_type": "encrypt-resource", - "event_type": "MCHTRANSFER.BILL.FINISHED", - "summary": "商家转账单据终态通知", + "event_type": "COUPON.USE", + "summary": "代金券核销通知", "resource": { - "original_type": "mch_payment", - "algorithm": "AEAD_AES_256_GCM", - "ciphertext": "XJBIhrHgbe9NR5q/jLYmZKdT/3xuKm2x7EFu3T52Hj2hjPzarRSA2HCsGTxGojfD+CFyJHIULlL2adqLijAjpi3B6TaYKY4LqhtJ/RYSQtYNxYvBpWX1yLOWe8luJbWxmQvKZxIekFs8lGVgkPBUw0IfEAvJ6jHAGCcgxLIqxgOf6UtGUqxCCNp/V3xy8zCiHB0Mvlw8eXCTuG+ZESJIXvloVGNS79R6iNeqk4kNKRSaV86MNh1KQlmoBxZ4yEshD/vIlMulU3xEc+mM25y8vUS4Ot6pxEpUdUyjwcb9QTwTTnZzm6i+VWYymcItAVBQrvsKBMmqWnPtNXG8++13k3DeO1LyVKURmnWXXT1mImmGx/teN/1xPV5y6nChu/HTbcJGDQy2twuq6TPFbbYlTjZH047z/ZtozJNvGNeh", - "associated_data": "mch_payment", - "nonce": "YN3eW5H8mxLs" - } - }` - - fmt.Print(header) - fmt.Print(body) + "original_type": "coupon", + "algorithm": "AEAD_AES_256_GCM", + "ciphertext": "IfJmKJnBYezruwmk+IA+ENQBn13oWiLR1YnYi3bhYaqYreaXwFcFWbnnQjWfh8RDF36S1v+H1eLj0OtiQVDveLJ9vOcCrtr0M8bkV6NpRN7amqcf02vg+xLcs6UiZDedkN353nMnVVY1SAyuJj0AjKTUJrMhPNtxzdPpL/A3bMVCHrw4sqUItAOsDWVyJ5Kb09BEbdNOesmFkCc+Pcqmr554UHXzjQxzYimMpdbZ8OCovEy6JY1jnbxyiduDI7XBiTvxLr5CzLGlxNk1tIHUSrrDnRzUCpFJzNETgcrF3JWvQVAPThelGnTLN/TT3/pyM7/Kz60YNGaSbJTNKOtxbLXopk72x5hlt45fgrueP+RsnVQWmjULJ0AVEVmPNtlHTYRw7WV0cGwwrF7CCSDifYPed9QZPU1+awVtnGb3If53l109HojJiJB7Y0C/ZPX/vH/KLmtUx+10YKIFOo3vYnxEBV5lv4D5ZqXn4gOOtufVH/URYqgRqnzNFqJV6sW2qP6K9AgBhe/BO4AoeDDHG00XP89XFbeHmR2zZ019jBapq92YkvDVtQc2oLr9MWPoHJUhkmApwB6AmOK7ldWJ14P/Fws3/zBzCxVSlQsgLoDyBKm461ZnA6k09c+hffhTutLt89HYv+pqA1nukdTGuCWqPqUgx0AsK5i1CngrBQZJwc7+ylpakpOrX8oulyuA3So65hgMuqgl5dgkR0e8nvjjv7dVl0aRkqTZt3VL7BB1xwGqbvO3aTEuqqPrFQKLpnaY5Gz64dgg6huQFOBnZww=", + "associated_data": "coupon", + "nonce": "pV96KsdLrSAd" + }` httpHeaders := make(http.Header) if err := json.Unmarshal([]byte(header), &httpHeaders); err != nil { @@ -151,7 +138,7 @@ func Test_MarketingNotify(t *testing.T) { _, bizContent, err := marketingFJLF().Notify(context.Background(), &httpHeaders, []byte(body)) if err != nil { - fmt.Printf("notify err: %+v\n", err) + t.Errorf("notify err: %+v\n", err) return } From 7565c48cd9e494b031be0e35fd130babf1484479 Mon Sep 17 00:00:00 2001 From: ziming Date: Thu, 26 Mar 2026 11:17:56 +0800 Subject: [PATCH 16/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/service/notify.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/notify.go b/internal/service/notify.go index 2222ece..8ce8f18 100644 --- a/internal/service/notify.go +++ b/internal/service/notify.go @@ -71,7 +71,7 @@ func (srv *NotifyService) Notify(ctx http.Context) error { if err = srv.VoucherBiz.WechatNotifyConsumer(ctx, ip, response); err != nil { headerJson, _ := json.Marshal(headers) - log.Errorf("微信回调通知[%s],Consumer处理失败,headers:%s,body:%s\n解析数据:%+v,err:%s", mchId, headerJson, string(bodyBytes), response, err.Error()) + log.Errorf("微信回调通知[%s],Consumer处理失败:%s\nheaders:%s\nbody:%s\n解析数据:%+v", mchId, err.Error(), headerJson, string(bodyBytes), response) return ctx.JSON(http2.StatusBadRequest, map[string]string{ "code": "FAIL", From 5db078e26a69810bc5d74c488749ddacfdf75a20 Mon Sep 17 00:00:00 2001 From: ziming Date: Thu, 26 Mar 2026 11:27:08 +0800 Subject: [PATCH 17/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wechatrepoimpl/bank_multi_activity.go | 8 +-- test/bank_multi_activity_test.go | 57 +++++++------------ 2 files changed, 26 insertions(+), 39 deletions(-) diff --git a/internal/data/wechatrepoimpl/bank_multi_activity.go b/internal/data/wechatrepoimpl/bank_multi_activity.go index a18a6bc..b651f8f 100644 --- a/internal/data/wechatrepoimpl/bank_multi_activity.go +++ b/internal/data/wechatrepoimpl/bank_multi_activity.go @@ -59,20 +59,20 @@ func (w *BankMultiActivityImpl) Order(order *bo.OrderBo) (couponId string, err e return *resp.CouponId, nil } -func (w *BankMultiActivityImpl) Notify(ctx context.Context, mchId string, headers *http.Header, respBody []byte) (response *bo.WechatVoucherNotifyBo, err error) { +func (w *BankMultiActivityImpl) Notify(ctx context.Context, mchId string, headers *http.Header, respBody []byte) (*bo.WechatVoucherNotifyBo, error) { t, err := w.wx.Get(mchId) if err != nil { - return + return nil, err } body, decodeBodyStr, err := t.Notify(ctx, headers, respBody) if err != nil { - return + return nil, err } var plainText bo.PlainText - if err = json.Unmarshal([]byte(decodeBodyStr), &response); err != nil { + if err = json.Unmarshal([]byte(decodeBodyStr), &plainText); err != nil { return nil, err } diff --git a/test/bank_multi_activity_test.go b/test/bank_multi_activity_test.go index 4537df9..9ce5368 100644 --- a/test/bank_multi_activity_test.go +++ b/test/bank_multi_activity_test.go @@ -96,39 +96,8 @@ func Test_QixingNotifyData(t *testing.T) { func Test_MarketingNotify(t *testing.T) { - header := `{ - "Content-Type": [ - "application/json" - ], - "Wechatpay-Nonce": [ - "H3Bs5sCbMJZOBLA4w3S6YbN4cCcO9i00" - ], - "Wechatpay-Serial": [ - "PUB_KEY_ID_0111000406952026032500382251001000" - ], - "Wechatpay-Signature": [ - "Kk5uQqZA3pyOV1cfJpRDNEghsG4vE0gBM4XIncJGgsuyG6SNNMbmXUy9TaBRoqylE5EoIYWXaXWOXn7gHW8rOK6bDkiJW2zoZUI6lRrPZSN8Mm1qaIiB42NsfExA+J6H3VqlsUtfyDiXJdsMRe2Y6pwGhj/GDJpb74PlsyaGOuZUfp/Z1Fy4gFZ2hhBx9MVIdIy0aGpU7D096HaDf30YJmdqHX0Cy59bPI9paaOX39kHt0H0WYpOeQEAsCHtVJTXF7betIBAPT1HO0PVZDS9ShO19AhMbislvPRxMEsZ6QYA2TVdyU6qegzkc7vaRiRjDla2V7YSzrmT745OTvPu1g==" - ], - "Wechatpay-Signature-Type": [ - "WECHATPAY2-SHA256-RSA2048" - ], - "Wechatpay-Timestamp": [ - "1774490961" - ] -}` - body := `{ - "id": "56b4937c-8f32-52e0-880a-828c869fa2c0", - "create_time": "2026-03-26T10:09:21+08:00", - "resource_type": "encrypt-resource", - "event_type": "COUPON.USE", - "summary": "代金券核销通知", - "resource": { - "original_type": "coupon", - "algorithm": "AEAD_AES_256_GCM", - "ciphertext": "IfJmKJnBYezruwmk+IA+ENQBn13oWiLR1YnYi3bhYaqYreaXwFcFWbnnQjWfh8RDF36S1v+H1eLj0OtiQVDveLJ9vOcCrtr0M8bkV6NpRN7amqcf02vg+xLcs6UiZDedkN353nMnVVY1SAyuJj0AjKTUJrMhPNtxzdPpL/A3bMVCHrw4sqUItAOsDWVyJ5Kb09BEbdNOesmFkCc+Pcqmr554UHXzjQxzYimMpdbZ8OCovEy6JY1jnbxyiduDI7XBiTvxLr5CzLGlxNk1tIHUSrrDnRzUCpFJzNETgcrF3JWvQVAPThelGnTLN/TT3/pyM7/Kz60YNGaSbJTNKOtxbLXopk72x5hlt45fgrueP+RsnVQWmjULJ0AVEVmPNtlHTYRw7WV0cGwwrF7CCSDifYPed9QZPU1+awVtnGb3If53l109HojJiJB7Y0C/ZPX/vH/KLmtUx+10YKIFOo3vYnxEBV5lv4D5ZqXn4gOOtufVH/URYqgRqnzNFqJV6sW2qP6K9AgBhe/BO4AoeDDHG00XP89XFbeHmR2zZ019jBapq92YkvDVtQc2oLr9MWPoHJUhkmApwB6AmOK7ldWJ14P/Fws3/zBzCxVSlQsgLoDyBKm461ZnA6k09c+hffhTutLt89HYv+pqA1nukdTGuCWqPqUgx0AsK5i1CngrBQZJwc7+ylpakpOrX8oulyuA3So65hgMuqgl5dgkR0e8nvjjv7dVl0aRkqTZt3VL7BB1xwGqbvO3aTEuqqPrFQKLpnaY5Gz64dgg6huQFOBnZww=", - "associated_data": "coupon", - "nonce": "pV96KsdLrSAd" - }` + header := `{"Accept":["*/*"], "Cache-Control":["no-cache"], "Connection":["close"], "Content-Length":["1109"], "Content-Type":["application/json"], "Pragma":["no-cache"], "User-Agent":["Mozilla/4.0"], "Wechatpay-Nonce":["N61AnMoOlDsi6WnCV1xgasslXt6Hqndb"], "Wechatpay-Serial":["PUB_KEY_ID_0111000406952026032500382251001000"], "Wechatpay-Signature":["skDPjPLpIway2ll3J+o3FYnF+3Z75PuNge92LaxDDWHu8V6OXWXHUPLaJilxg/UidBcaP5JKza9t2JIgNtdRXfjPEehi7Ufn5yhPR9EUg4T1PFhkri/N8x1bvZ2oDemsTDHNNxr2PrPla2K5EcNRe6MfVpLKLp/xxX5UXfII8UHjI50jDSSoJQpSZZS0jbm5yfAeokbus0aD7835JKWksLGIuM94s5TYV8fCNL1RVab/ArLJ6UsTf0mjPEVWMdT3TePwdtVFZhFqGxlTFnR2LYSuLNflxQiE7cgtBK76L1+lMit7KlARy7aPSsOpn3DAl1sivnSvP6hX5ljOXnjv6g=="], "Wechatpay-Signature-Type":["WECHATPAY2-SHA256-RSA2048"], "Wechatpay-Timestamp":["1774495201"], "X-Forwarded-For":["121.51.58.172"], "X-Real-Ip":["121.51.58.172"]}` + bodyStr := `{"id":"2f6dba61-17d2-554c-99f1-c2527bac8013","create_time":"2026-03-26T11:14:56+08:00","resource_type":"encrypt-resource","event_type":"COUPON.USE","summary":"代金券核销通知","resource":{"original_type":"coupon","algorithm":"AEAD_AES_256_GCM","ciphertext":"fr2/gKAby7C+R8Yl/pcWLQEa+RYFDmi2LGSaZtghMtMWs+6z5XolFtNTs65JArRUjptKR4ATn0uAp+Yi2S2ly6+jV07VnUmEBk7ijPXHgkZ53VkNSKgYIC6pOumNcA3MdZxVHPRYawvsluat4m0X7/BqW3qjR9L9DYq4uXtc4pXBC2lcSmtcIENU5lHRmSU83iLp9Gp+LZ3MGaF8ExMc+ZkQcj7RRruiXqWaICeY7c0ebXQJtulytT1FSMksb5nfaf0MmElO+9fOwH1naOrdvSSQ4tglThpfEHuIZ2xaT7DwcBDYvmye+vg+fgnxE66sskz5fwdVtgiGdNSWzA8CZrP/4oXUpbNpjpY2r0GJs5etpCU5hqimz0S1zCLEOtgl/LQ3GCZA1AzZeRcIferbJotC1toJD3tALyO89VSbwcGr8kk6e1HAGyXhoIt5RC3sFnbUdHQLf01MbNhJg13p1YbqInLI3ktjlst4N+gGXmCHHzHlpeOHgdF2uvrWTE/XTDlfumGKBDLFkSOu62pldQfLKVU++BPvvqaRXK9m4g5F87dwOJQJyltm3LYwKfpg1vIMw6ch1Nvrj79+xQs0Xa32Ds8omjI3GmMyly/BMvWJkFpTivrgUnXXMLfzDIFLRB5cR4dGQkJWFBY/qkjHu3yvTqcJuiaUqaHFKjhJJt1WpVMWaj4WlsbOb/oI3eTroxzynNOhgmYWMrM47OSfHJ1MhGU3c18Zk89zyY8miVe/CaPhwwmBnIgeYoBzj5ic36HXJN1Qkup1FYAOadWWXwc=","associated_data":"coupon","nonce":"I4U3QlKjYHlc"}}` httpHeaders := make(http.Header) if err := json.Unmarshal([]byte(header), &httpHeaders); err != nil { @@ -136,11 +105,29 @@ func Test_MarketingNotify(t *testing.T) { return } - _, bizContent, err := marketingFJLF().Notify(context.Background(), &httpHeaders, []byte(body)) + body, bizContent, err := marketingFJLF().Notify(context.Background(), &httpHeaders, []byte(bodyStr)) if err != nil { t.Errorf("notify err: %+v\n", err) return } - fmt.Printf("bizContent: %+v\n", bizContent) + var plainText bo.PlainText + if err = json.Unmarshal([]byte(bizContent), &plainText); err != nil { + t.Errorf("Unmarshal err: %+v\n", err) + return + } + + notifyBo := bo.WechatVoucherNotifyBo{ + ID: body.Id, + CreateTime: body.CreateTime, + ResourceType: body.ResourceType, + EventType: body.EventType, + Summary: body.Summary, + OriginalType: body.Resource.OriginalType, + AssociatedData: body.Resource.AssociatedData, + PlainText: plainText, + } + + fmt.Printf("bizContent: %s\n", bizContent) + fmt.Printf("notifyBo: %+v\n", notifyBo) } From 4ec1809383a003256126e64947613f049c88fd83 Mon Sep 17 00:00:00 2001 From: ziming Date: Thu, 26 Mar 2026 11:27:34 +0800 Subject: [PATCH 18/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/data/wechatrepoimpl/bank_multi_activity.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/data/wechatrepoimpl/bank_multi_activity.go b/internal/data/wechatrepoimpl/bank_multi_activity.go index b651f8f..660a6bb 100644 --- a/internal/data/wechatrepoimpl/bank_multi_activity.go +++ b/internal/data/wechatrepoimpl/bank_multi_activity.go @@ -73,7 +73,7 @@ func (w *BankMultiActivityImpl) Notify(ctx context.Context, mchId string, header var plainText bo.PlainText if err = json.Unmarshal([]byte(decodeBodyStr), &plainText); err != nil { - return nil, err + return nil, fmt.Errorf("plainText json.Unmarshal error: %v", err) } return &bo.WechatVoucherNotifyBo{ From 64c7fbecd2864b6a8aa0a16dd8996f31bd9e4ac6 Mon Sep 17 00:00:00 2001 From: ziming Date: Thu, 26 Mar 2026 11:28:33 +0800 Subject: [PATCH 19/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/service/notify.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/notify.go b/internal/service/notify.go index 8ce8f18..02dd75d 100644 --- a/internal/service/notify.go +++ b/internal/service/notify.go @@ -71,7 +71,7 @@ func (srv *NotifyService) Notify(ctx http.Context) error { if err = srv.VoucherBiz.WechatNotifyConsumer(ctx, ip, response); err != nil { headerJson, _ := json.Marshal(headers) - log.Errorf("微信回调通知[%s],Consumer处理失败:%s\nheaders:%s\nbody:%s\n解析数据:%+v", mchId, err.Error(), headerJson, string(bodyBytes), response) + log.Errorf("微信回调通知[%s],consumer处理失败:%s\nheaders:%s\nbody:%s\n解析数据:%+v", mchId, err.Error(), headerJson, string(bodyBytes), response) return ctx.JSON(http2.StatusBadRequest, map[string]string{ "code": "FAIL", From 6aaf9f3e127d6a2252c2f478806127eeb4aa6f38 Mon Sep 17 00:00:00 2001 From: ziming Date: Thu, 26 Mar 2026 11:30:55 +0800 Subject: [PATCH 20/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/biz/wechat_notify.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/biz/wechat_notify.go b/internal/biz/wechat_notify.go index 18c0f8f..7907bbd 100644 --- a/internal/biz/wechat_notify.go +++ b/internal/biz/wechat_notify.go @@ -17,6 +17,10 @@ func (this *VoucherBiz) WechatNotifyConsumer(ctx context.Context, ip string, req order, err := this.getOrder(ctx, req) if err != nil { + // 系统订单不存在 + //if errors.Is(err, gorm.ErrRecordNotFound) { + // return nil + //} return err } @@ -53,7 +57,7 @@ func (this *VoucherBiz) getOrder(ctx context.Context, req *bo.WechatVoucherNotif order, err := this.OrderRepo.GetByCouponId(ctx, req.PlainText.StockCreatorMchid, req.PlainText.StockID, req.PlainText.CouponID) if err != nil { - return nil, fmt.Errorf("订单查询错误 error: %v", err) + return nil, fmt.Errorf("订单查询错误 error: %w", err) } return order, nil From a7f41d1c2798d657f0e2b64c69d65c66a3aaddee Mon Sep 17 00:00:00 2001 From: ziming Date: Thu, 26 Mar 2026 11:31:55 +0800 Subject: [PATCH 21/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/biz/wechat_notify.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/internal/biz/wechat_notify.go b/internal/biz/wechat_notify.go index 7907bbd..b64d7fe 100644 --- a/internal/biz/wechat_notify.go +++ b/internal/biz/wechat_notify.go @@ -2,7 +2,9 @@ package biz import ( "context" + "errors" "fmt" + "gorm.io/gorm" errPb "voucher/api/err" "voucher/internal/biz/bo" "voucher/internal/biz/vo" @@ -18,9 +20,9 @@ func (this *VoucherBiz) WechatNotifyConsumer(ctx context.Context, ip string, req order, err := this.getOrder(ctx, req) if err != nil { // 系统订单不存在 - //if errors.Is(err, gorm.ErrRecordNotFound) { - // return nil - //} + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil + } return err } From 9c465d1f525308d3dfc74a69db6f8730d9ef4bd4 Mon Sep 17 00:00:00 2001 From: ziming Date: Thu, 26 Mar 2026 11:33:43 +0800 Subject: [PATCH 22/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/biz/wechat_notify.go | 7 ------- internal/service/notify.go | 7 +++++++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/biz/wechat_notify.go b/internal/biz/wechat_notify.go index b64d7fe..89d33e8 100644 --- a/internal/biz/wechat_notify.go +++ b/internal/biz/wechat_notify.go @@ -2,9 +2,7 @@ package biz import ( "context" - "errors" "fmt" - "gorm.io/gorm" errPb "voucher/api/err" "voucher/internal/biz/bo" "voucher/internal/biz/vo" @@ -19,10 +17,6 @@ func (this *VoucherBiz) WechatNotifyConsumer(ctx context.Context, ip string, req order, err := this.getOrder(ctx, req) if err != nil { - // 系统订单不存在 - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil - } return err } @@ -57,7 +51,6 @@ func (this *VoucherBiz) getOrder(ctx context.Context, req *bo.WechatVoucherNotif } order, err := this.OrderRepo.GetByCouponId(ctx, req.PlainText.StockCreatorMchid, req.PlainText.StockID, req.PlainText.CouponID) - if err != nil { return nil, fmt.Errorf("订单查询错误 error: %w", err) } diff --git a/internal/service/notify.go b/internal/service/notify.go index 02dd75d..b338ded 100644 --- a/internal/service/notify.go +++ b/internal/service/notify.go @@ -2,9 +2,11 @@ package service import ( "encoding/json" + "errors" "fmt" "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/transport/http" + "gorm.io/gorm" "io" http2 "net/http" "voucher/internal/biz" @@ -73,6 +75,11 @@ func (srv *NotifyService) Notify(ctx http.Context) error { headerJson, _ := json.Marshal(headers) log.Errorf("微信回调通知[%s],consumer处理失败:%s\nheaders:%s\nbody:%s\n解析数据:%+v", mchId, err.Error(), headerJson, string(bodyBytes), response) + // 系统订单不存在 + if errors.Is(err, gorm.ErrRecordNotFound) { + return ctx.JSON(http2.StatusOK, nil) + } + return ctx.JSON(http2.StatusBadRequest, map[string]string{ "code": "FAIL", "message": err.Error(), From 2ec6b6291269b550fd3223a1d470e201d1d088a8 Mon Sep 17 00:00:00 2001 From: ziming Date: Thu, 26 Mar 2026 13:44:05 +0800 Subject: [PATCH 23/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/biz/repo/product.go | 1 + internal/biz/wechat_notify.go | 6 ++++++ internal/data/repoimpl/product.go | 24 +++++++++++++++++++----- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/internal/biz/repo/product.go b/internal/biz/repo/product.go index 1194a81..fbf75f2 100644 --- a/internal/biz/repo/product.go +++ b/internal/biz/repo/product.go @@ -10,6 +10,7 @@ type ProductRepo interface { GetById(ctx context.Context, id int32) (*bo.ProductBo, error) FindWarningBudget(ctx context.Context, fun func(ctx context.Context, rows []*bo.ProductBo) error) error GetByBatchNo(ctx context.Context, batchNo string) (*bo.ProductBo, error) + GetByMchStockId(ctx context.Context, mchId, stockId string) (*bo.ProductBo, error) GetByProductNo(ctx context.Context, productNo string) (*bo.ProductBo, error) UpdateByWxResp(ctx context.Context, id int32, req *do.WxResp) error } diff --git a/internal/biz/wechat_notify.go b/internal/biz/wechat_notify.go index 89d33e8..874207e 100644 --- a/internal/biz/wechat_notify.go +++ b/internal/biz/wechat_notify.go @@ -11,6 +11,12 @@ import ( func (this *VoucherBiz) WechatNotifyConsumer(ctx context.Context, ip string, req *bo.WechatVoucherNotifyBo) error { + // 商品数据量较少,先查询商品是否存在,过滤多余的通知信息 + _, err := this.ProductRepo.GetByMchStockId(ctx, req.PlainText.StockCreatorMchid, req.PlainText.StockID) + if err != nil { + return fmt.Errorf("商品查询错误 error: %w", err) + } + c := vo.WechatNotifyConsumeLockKey.BuildCache([]string{req.PlainText.StockCreatorMchid, req.PlainText.StockID, req.PlainText.CouponID}) return lock.NewMutex(this.rdb.Rdb, c.TTL).Lock(ctx, c.Key, func(ctx context.Context) error { diff --git a/internal/data/repoimpl/product.go b/internal/data/repoimpl/product.go index bad4723..d1510b4 100644 --- a/internal/data/repoimpl/product.go +++ b/internal/data/repoimpl/product.go @@ -104,14 +104,28 @@ func (r *ProductRepoImpl) GetByBatchNo(ctx context.Context, batchNo string) (*bo tx := db.Where(model.Product{BatchNo: batchNo}).First(&item) if tx.Error != nil { - if errors.Is(tx.Error, gorm.ErrRecordNotFound) { - return nil, err2.ErrorDbNotFound("商品数据不存在") - } - return nil, fmt.Errorf("product db fail %w", tx.Error) + return nil, tx.Error } if tx.RowsAffected == 0 { - return nil, err2.ErrorDbNotFound("商品数据不存在") + return nil, gorm.ErrRecordNotFound + } + + return r.ToBo(item), nil +} + +func (r *ProductRepoImpl) GetByMchStockId(ctx context.Context, mchId, stockId string) (*bo.ProductBo, error) { + + var item *model.Product + + tx := r.db.DB(ctx).Where(model.Product{MchId: mchId, BatchNo: stockId}).First(&item) + + if tx.Error != nil { + return nil, tx.Error + } + + if tx.RowsAffected == 0 { + return nil, gorm.ErrRecordNotFound } return r.ToBo(item), nil From 3f3bcbab8e5dc0c3608e6fa2f6e0659ea7c704f8 Mon Sep 17 00:00:00 2001 From: ziming Date: Thu, 26 Mar 2026 13:44:41 +0800 Subject: [PATCH 24/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/service/notify.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/service/notify.go b/internal/service/notify.go index b338ded..6a8a412 100644 --- a/internal/service/notify.go +++ b/internal/service/notify.go @@ -75,7 +75,6 @@ func (srv *NotifyService) Notify(ctx http.Context) error { headerJson, _ := json.Marshal(headers) log.Errorf("微信回调通知[%s],consumer处理失败:%s\nheaders:%s\nbody:%s\n解析数据:%+v", mchId, err.Error(), headerJson, string(bodyBytes), response) - // 系统订单不存在 if errors.Is(err, gorm.ErrRecordNotFound) { return ctx.JSON(http2.StatusOK, nil) } From 886d140cebd6c1f85e0a27136546c968ec86520d Mon Sep 17 00:00:00 2001 From: ziming Date: Thu, 26 Mar 2026 13:46:36 +0800 Subject: [PATCH 25/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/service/notify.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/service/notify.go b/internal/service/notify.go index 6a8a412..c8f7429 100644 --- a/internal/service/notify.go +++ b/internal/service/notify.go @@ -57,7 +57,7 @@ func (srv *NotifyService) Notify(ctx http.Context) error { }) } - response, err := srv.WechatBiz.CallBack(ctx, mchId, &headers, bodyBytes) + bizData, err := srv.WechatBiz.CallBack(ctx, mchId, &headers, bodyBytes) if err != nil { headerJson, _ := json.Marshal(headers) log.Errorf("微信回调通知[%s],CallBack处理失败,headers:%s,body:%s,err:%s", mchId, headerJson, string(bodyBytes), err.Error()) @@ -70,10 +70,10 @@ func (srv *NotifyService) Notify(ctx http.Context) error { ip := helper.GetClientIP(ctx) - if err = srv.VoucherBiz.WechatNotifyConsumer(ctx, ip, response); err != nil { + if err = srv.VoucherBiz.WechatNotifyConsumer(ctx, ip, bizData); err != nil { headerJson, _ := json.Marshal(headers) - log.Errorf("微信回调通知[%s],consumer处理失败:%s\nheaders:%s\nbody:%s\n解析数据:%+v", mchId, err.Error(), headerJson, string(bodyBytes), response) + log.Errorf("微信回调通知[%s],consumer处理失败:%s\nheaders:%s\nbody:%s\n解析数据:%+v", mchId, err.Error(), headerJson, string(bodyBytes), bizData) if errors.Is(err, gorm.ErrRecordNotFound) { return ctx.JSON(http2.StatusOK, nil) From cfe1973bf9c9dd6f58d1a01e0fbddbea9a317e6f Mon Sep 17 00:00:00 2001 From: ziming Date: Thu, 26 Mar 2026 13:57:09 +0800 Subject: [PATCH 26/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/biz/wechat_notify.go | 5 +++++ internal/service/notify.go | 6 ------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/internal/biz/wechat_notify.go b/internal/biz/wechat_notify.go index 874207e..4641c65 100644 --- a/internal/biz/wechat_notify.go +++ b/internal/biz/wechat_notify.go @@ -2,7 +2,9 @@ package biz import ( "context" + "errors" "fmt" + "gorm.io/gorm" errPb "voucher/api/err" "voucher/internal/biz/bo" "voucher/internal/biz/vo" @@ -14,6 +16,9 @@ func (this *VoucherBiz) WechatNotifyConsumer(ctx context.Context, ip string, req // 商品数据量较少,先查询商品是否存在,过滤多余的通知信息 _, err := this.ProductRepo.GetByMchStockId(ctx, req.PlainText.StockCreatorMchid, req.PlainText.StockID) if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil + } return fmt.Errorf("商品查询错误 error: %w", err) } diff --git a/internal/service/notify.go b/internal/service/notify.go index c8f7429..956e0c3 100644 --- a/internal/service/notify.go +++ b/internal/service/notify.go @@ -2,11 +2,9 @@ package service import ( "encoding/json" - "errors" "fmt" "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/transport/http" - "gorm.io/gorm" "io" http2 "net/http" "voucher/internal/biz" @@ -75,10 +73,6 @@ func (srv *NotifyService) Notify(ctx http.Context) error { headerJson, _ := json.Marshal(headers) log.Errorf("微信回调通知[%s],consumer处理失败:%s\nheaders:%s\nbody:%s\n解析数据:%+v", mchId, err.Error(), headerJson, string(bodyBytes), bizData) - if errors.Is(err, gorm.ErrRecordNotFound) { - return ctx.JSON(http2.StatusOK, nil) - } - return ctx.JSON(http2.StatusBadRequest, map[string]string{ "code": "FAIL", "message": err.Error(), From 8cf3c66cb354897196954d9a8aecc3b55b8ac333 Mon Sep 17 00:00:00 2001 From: ziming Date: Thu, 26 Mar 2026 14:00:55 +0800 Subject: [PATCH 27/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/biz/wechat_notify.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/biz/wechat_notify.go b/internal/biz/wechat_notify.go index 4641c65..94d576c 100644 --- a/internal/biz/wechat_notify.go +++ b/internal/biz/wechat_notify.go @@ -13,6 +13,10 @@ import ( func (this *VoucherBiz) WechatNotifyConsumer(ctx context.Context, ip string, req *bo.WechatVoucherNotifyBo) error { + if req.PlainText.StockCreatorMchid == "" || req.PlainText.StockID == "" || req.PlainText.CouponID == "" { + return fmt.Errorf("回调必要信息不能为空") + } + // 商品数据量较少,先查询商品是否存在,过滤多余的通知信息 _, err := this.ProductRepo.GetByMchStockId(ctx, req.PlainText.StockCreatorMchid, req.PlainText.StockID) if err != nil { @@ -57,10 +61,6 @@ func (this *VoucherBiz) WechatNotifyConsumer(ctx context.Context, ip string, req func (this *VoucherBiz) getOrder(ctx context.Context, req *bo.WechatVoucherNotifyBo) (*bo.OrderBo, error) { - if req.PlainText.StockCreatorMchid == "" || req.PlainText.StockID == "" || req.PlainText.CouponID == "" { - return nil, fmt.Errorf("订单查询参数错误") - } - order, err := this.OrderRepo.GetByCouponId(ctx, req.PlainText.StockCreatorMchid, req.PlainText.StockID, req.PlainText.CouponID) if err != nil { return nil, fmt.Errorf("订单查询错误 error: %w", err) From 2e364f03327390db1201dff9fe0f438b2ec77694 Mon Sep 17 00:00:00 2001 From: ziming Date: Thu, 26 Mar 2026 14:03:03 +0800 Subject: [PATCH 28/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/biz/wechat_notify.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/internal/biz/wechat_notify.go b/internal/biz/wechat_notify.go index 94d576c..50b650e 100644 --- a/internal/biz/wechat_notify.go +++ b/internal/biz/wechat_notify.go @@ -2,9 +2,7 @@ package biz import ( "context" - "errors" "fmt" - "gorm.io/gorm" errPb "voucher/api/err" "voucher/internal/biz/bo" "voucher/internal/biz/vo" @@ -20,9 +18,9 @@ func (this *VoucherBiz) WechatNotifyConsumer(ctx context.Context, ip string, req // 商品数据量较少,先查询商品是否存在,过滤多余的通知信息 _, err := this.ProductRepo.GetByMchStockId(ctx, req.PlainText.StockCreatorMchid, req.PlainText.StockID) if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil - } + //if errors.Is(err, gorm.ErrRecordNotFound) { + // return nil + //} return fmt.Errorf("商品查询错误 error: %w", err) } From c77e23af8a6092e8a8bd1b4f74813df7c4e31acb Mon Sep 17 00:00:00 2001 From: ziming Date: Thu, 26 Mar 2026 14:09:15 +0800 Subject: [PATCH 29/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/service/notify.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/service/notify.go b/internal/service/notify.go index 956e0c3..c8f7429 100644 --- a/internal/service/notify.go +++ b/internal/service/notify.go @@ -2,9 +2,11 @@ package service import ( "encoding/json" + "errors" "fmt" "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/transport/http" + "gorm.io/gorm" "io" http2 "net/http" "voucher/internal/biz" @@ -73,6 +75,10 @@ func (srv *NotifyService) Notify(ctx http.Context) error { headerJson, _ := json.Marshal(headers) log.Errorf("微信回调通知[%s],consumer处理失败:%s\nheaders:%s\nbody:%s\n解析数据:%+v", mchId, err.Error(), headerJson, string(bodyBytes), bizData) + if errors.Is(err, gorm.ErrRecordNotFound) { + return ctx.JSON(http2.StatusOK, nil) + } + return ctx.JSON(http2.StatusBadRequest, map[string]string{ "code": "FAIL", "message": err.Error(), From ce8d6ed988b20ec81b47f86fa8c4e89a5c76cc59 Mon Sep 17 00:00:00 2001 From: ziming Date: Thu, 26 Mar 2026 14:15:12 +0800 Subject: [PATCH 30/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/server/wechat_consumer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/server/wechat_consumer.go b/internal/server/wechat_consumer.go index 5428942..c1241ef 100644 --- a/internal/server/wechat_consumer.go +++ b/internal/server/wechat_consumer.go @@ -150,7 +150,7 @@ func (w *WechatNotifyConsumer) processMessage(msg mq_http_sdk.ConsumeMessageEntr } }() - log.Warnf("微信回调消费接收消息成功 messageId:%s, messageTag:%s, message:%s", msg.MessageId, msg.MessageTag, msg.MessageBody) + //log.Warnf("微信回调消费接收消息成功 messageId:%s, messageTag:%s, message:%s", msg.MessageId, msg.MessageTag, msg.MessageBody) ctx := context.Background() if err := w.voucherService.WechatUseNotifyConsumer(ctx, msg.MessageTag, msg.MessageBody); err != nil { From c89a6406b6f5503669a2cfe0e81a24b82ae3112a Mon Sep 17 00:00:00 2001 From: ziming Date: Thu, 26 Mar 2026 14:54:45 +0800 Subject: [PATCH 31/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/service/notify.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/service/notify.go b/internal/service/notify.go index c8f7429..18347db 100644 --- a/internal/service/notify.go +++ b/internal/service/notify.go @@ -68,11 +68,14 @@ func (srv *NotifyService) Notify(ctx http.Context) error { }) } + headerJson, _ := json.Marshal(headers) + + log.Warnf("微信回调通知[%s],consumer处理失败:%s\nheaders:%s\nbody:%s\n解析数据:%+v", mchId, err.Error(), headerJson, string(bodyBytes), bizData) + ip := helper.GetClientIP(ctx) if err = srv.VoucherBiz.WechatNotifyConsumer(ctx, ip, bizData); err != nil { - headerJson, _ := json.Marshal(headers) log.Errorf("微信回调通知[%s],consumer处理失败:%s\nheaders:%s\nbody:%s\n解析数据:%+v", mchId, err.Error(), headerJson, string(bodyBytes), bizData) if errors.Is(err, gorm.ErrRecordNotFound) { From 0d5259c9b0ee2ff5e81479ed87420358d2be4399 Mon Sep 17 00:00:00 2001 From: ziming Date: Thu, 26 Mar 2026 14:54:54 +0800 Subject: [PATCH 32/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/service/notify.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/notify.go b/internal/service/notify.go index 18347db..c227934 100644 --- a/internal/service/notify.go +++ b/internal/service/notify.go @@ -76,7 +76,7 @@ func (srv *NotifyService) Notify(ctx http.Context) error { if err = srv.VoucherBiz.WechatNotifyConsumer(ctx, ip, bizData); err != nil { - log.Errorf("微信回调通知[%s],consumer处理失败:%s\nheaders:%s\nbody:%s\n解析数据:%+v", mchId, err.Error(), headerJson, string(bodyBytes), bizData) + //log.Errorf("微信回调通知[%s],consumer处理失败:%s\nheaders:%s\nbody:%s\n解析数据:%+v", mchId, err.Error(), headerJson, string(bodyBytes), bizData) if errors.Is(err, gorm.ErrRecordNotFound) { return ctx.JSON(http2.StatusOK, nil) From 417e813b84d52a8fe0b4083d2dcc5b0bec383463 Mon Sep 17 00:00:00 2001 From: ziming Date: Thu, 26 Mar 2026 15:05:18 +0800 Subject: [PATCH 33/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/service/notify.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/internal/service/notify.go b/internal/service/notify.go index c227934..999e14d 100644 --- a/internal/service/notify.go +++ b/internal/service/notify.go @@ -68,15 +68,11 @@ func (srv *NotifyService) Notify(ctx http.Context) error { }) } - headerJson, _ := json.Marshal(headers) - - log.Warnf("微信回调通知[%s],consumer处理失败:%s\nheaders:%s\nbody:%s\n解析数据:%+v", mchId, err.Error(), headerJson, string(bodyBytes), bizData) - ip := helper.GetClientIP(ctx) if err = srv.VoucherBiz.WechatNotifyConsumer(ctx, ip, bizData); err != nil { - - //log.Errorf("微信回调通知[%s],consumer处理失败:%s\nheaders:%s\nbody:%s\n解析数据:%+v", mchId, err.Error(), headerJson, string(bodyBytes), bizData) + headerJson, _ := json.Marshal(headers) + log.Errorf("微信回调通知[%s],consumer处理失败:%s\nheaders:%s\nbody:%s\n解析数据:%+v", mchId, err.Error(), headerJson, string(bodyBytes), bizData) if errors.Is(err, gorm.ErrRecordNotFound) { return ctx.JSON(http2.StatusOK, nil) From f34a27fbf255a7bfb0d96b105aaa52793433abca Mon Sep 17 00:00:00 2001 From: ziming Date: Thu, 26 Mar 2026 15:13:02 +0800 Subject: [PATCH 34/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pkg/wechat/srv/marketing/marketing.go | 8 ++++-- internal/pkg/wechat/utils/wxpay_utility.go | 25 +++++++++++++------ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/internal/pkg/wechat/srv/marketing/marketing.go b/internal/pkg/wechat/srv/marketing/marketing.go index 362776a..ea67fd5 100644 --- a/internal/pkg/wechat/srv/marketing/marketing.go +++ b/internal/pkg/wechat/srv/marketing/marketing.go @@ -62,10 +62,14 @@ func (srv *Marketing) Query(appId, openId, couponId string) (response *SendResp, return response, nil } -// Notify . func (srv *Marketing) Notify(_ context.Context, headers *http.Header, respBody []byte) (body *utils.WxNotifyBody, response string, err error) { - wxNotifyBody, bizStr, err := srv.GetDecodeBody(headers, respBody) + wxNotifyBody, err := srv.GetNotifyBody(headers, respBody) + if err != nil { + return nil, "", err + } + + bizStr, err := srv.DecodeBody(wxNotifyBody) if err != nil { return nil, "", err } diff --git a/internal/pkg/wechat/utils/wxpay_utility.go b/internal/pkg/wechat/utils/wxpay_utility.go index 1089bbe..d8718af 100644 --- a/internal/pkg/wechat/utils/wxpay_utility.go +++ b/internal/pkg/wechat/utils/wxpay_utility.go @@ -575,10 +575,10 @@ func (srv *MchConfig) Verify(request *http.Request) (string, error) { return EncryptOAEPWithPublicKey(string(respBody), srv.wechatPayPublicKey) } -func (srv *MchConfig) GetDecodeBody(headers *http.Header, respBody []byte) (*WxNotifyBody, string, error) { +func (srv *MchConfig) GetNotifyBody(headers *http.Header, respBody []byte) (*WxNotifyBody, error) { if respBody == nil { - return nil, "", fmt.Errorf("request HttpBody is nil") + return nil, fmt.Errorf("request HttpBody is nil") } err := ValidateResponse( @@ -588,25 +588,34 @@ func (srv *MchConfig) GetDecodeBody(headers *http.Header, respBody []byte) (*WxN respBody, ) if err != nil { - return nil, "", err + return nil, err } var wxNotifyBody WxNotifyBody if err = json.Unmarshal(respBody, &wxNotifyBody); err != nil { - return nil, "", err + return nil, err } + return &wxNotifyBody, nil +} + +func (srv *MchConfig) DecodeBody(wxNotifyBody *WxNotifyBody) (string, error) { + aesUtil, err := NewAesUtil(srv.aesKey) if err != nil { - return nil, "", err + return "", err } - decryptedText, err := aesUtil.DecryptToString(wxNotifyBody.Resource.AssociatedData, wxNotifyBody.Resource.Nonce, wxNotifyBody.Resource.Ciphertext) + decryptedText, err := aesUtil.DecryptToString( + wxNotifyBody.Resource.AssociatedData, + wxNotifyBody.Resource.Nonce, + wxNotifyBody.Resource.Ciphertext, + ) if err != nil { - return nil, "", err + return "", err } - return &wxNotifyBody, decryptedText, nil + return decryptedText, nil } // BuildSortedQueryString 函数接受一个 map,返回按照字段名排序后的 URL 键值对格式字符串 From 3c4b932f03abd0c587c9c783dfcc04b11726c356 Mon Sep 17 00:00:00 2001 From: ziming Date: Thu, 26 Mar 2026 15:14:36 +0800 Subject: [PATCH 35/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/service/notify.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/service/notify.go b/internal/service/notify.go index 999e14d..360bd66 100644 --- a/internal/service/notify.go +++ b/internal/service/notify.go @@ -57,10 +57,14 @@ func (srv *NotifyService) Notify(ctx http.Context) error { }) } + headerJson, _ := json.Marshal(headers) + log.Warn("微信回调通知[%s],CallBack处理失败,headers:%s,body:%s", mchId, headerJson, string(bodyBytes)) + bizData, err := srv.WechatBiz.CallBack(ctx, mchId, &headers, bodyBytes) if err != nil { - headerJson, _ := json.Marshal(headers) - log.Errorf("微信回调通知[%s],CallBack处理失败,headers:%s,body:%s,err:%s", mchId, headerJson, string(bodyBytes), err.Error()) + + //log.Errorf("微信回调通知[%s],CallBack处理失败,headers:%s,body:%s,err:%s", mchId, headerJson, string(bodyBytes), err.Error()) + log.Errorf("微信回调通知[%s],CallBack处理失败,:%s", mchId, err.Error()) return ctx.JSON(http2.StatusNetworkAuthenticationRequired, map[string]string{ "code": "FAIL", From b4d0688bfc4d7c346b192aa3cf1c9208f94892f2 Mon Sep 17 00:00:00 2001 From: ziming Date: Thu, 26 Mar 2026 15:17:56 +0800 Subject: [PATCH 36/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/service/notify.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/notify.go b/internal/service/notify.go index 360bd66..f46a0f3 100644 --- a/internal/service/notify.go +++ b/internal/service/notify.go @@ -58,7 +58,7 @@ func (srv *NotifyService) Notify(ctx http.Context) error { } headerJson, _ := json.Marshal(headers) - log.Warn("微信回调通知[%s],CallBack处理失败,headers:%s,body:%s", mchId, headerJson, string(bodyBytes)) + log.Warnf("微信回调通知[%s],CallBack处理失败,headers:%s,body:%s", mchId, headerJson, string(bodyBytes)) bizData, err := srv.WechatBiz.CallBack(ctx, mchId, &headers, bodyBytes) if err != nil { From 8f290eb0c2715b2e2f16b1b47ef23060a466fcd3 Mon Sep 17 00:00:00 2001 From: ziming Date: Thu, 26 Mar 2026 15:20:25 +0800 Subject: [PATCH 37/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/service/notify.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/internal/service/notify.go b/internal/service/notify.go index f46a0f3..3ad1ea4 100644 --- a/internal/service/notify.go +++ b/internal/service/notify.go @@ -62,10 +62,7 @@ func (srv *NotifyService) Notify(ctx http.Context) error { bizData, err := srv.WechatBiz.CallBack(ctx, mchId, &headers, bodyBytes) if err != nil { - - //log.Errorf("微信回调通知[%s],CallBack处理失败,headers:%s,body:%s,err:%s", mchId, headerJson, string(bodyBytes), err.Error()) - log.Errorf("微信回调通知[%s],CallBack处理失败,:%s", mchId, err.Error()) - + log.Errorf("微信回调通知[%s],CallBack处理失败,headers:%s,body:%s,err:%s", mchId, headerJson, string(bodyBytes), err.Error()) return ctx.JSON(http2.StatusNetworkAuthenticationRequired, map[string]string{ "code": "FAIL", "message": err.Error(), @@ -75,8 +72,8 @@ func (srv *NotifyService) Notify(ctx http.Context) error { ip := helper.GetClientIP(ctx) if err = srv.VoucherBiz.WechatNotifyConsumer(ctx, ip, bizData); err != nil { - headerJson, _ := json.Marshal(headers) - log.Errorf("微信回调通知[%s],consumer处理失败:%s\nheaders:%s\nbody:%s\n解析数据:%+v", mchId, err.Error(), headerJson, string(bodyBytes), bizData) + //log.Errorf("微信回调通知[%s],consumer处理失败:%s\nheaders:%s\nbody:%s\n解析数据:%+v", mchId, err.Error(), headerJson, string(bodyBytes), bizData) + log.Errorf("微信回调通知[%s],consumer处理失败[%s-%s]", mchId, bizData.PlainText.CouponID, err.Error()) if errors.Is(err, gorm.ErrRecordNotFound) { return ctx.JSON(http2.StatusOK, nil) From 4be755c113bdd24151cdbd9841c7716798c8f3cb Mon Sep 17 00:00:00 2001 From: ziming Date: Thu, 26 Mar 2026 16:07:47 +0800 Subject: [PATCH 38/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/service/notify.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/notify.go b/internal/service/notify.go index 3ad1ea4..ab00723 100644 --- a/internal/service/notify.go +++ b/internal/service/notify.go @@ -73,7 +73,7 @@ func (srv *NotifyService) Notify(ctx http.Context) error { if err = srv.VoucherBiz.WechatNotifyConsumer(ctx, ip, bizData); err != nil { //log.Errorf("微信回调通知[%s],consumer处理失败:%s\nheaders:%s\nbody:%s\n解析数据:%+v", mchId, err.Error(), headerJson, string(bodyBytes), bizData) - log.Errorf("微信回调通知[%s],consumer处理失败[%s-%s]", mchId, bizData.PlainText.CouponID, err.Error()) + log.Errorf("微信回调通知[%s],consumer处理失败[%s-%s-%s]", mchId, bizData.PlainText.StockID, bizData.PlainText.CouponID, err.Error()) if errors.Is(err, gorm.ErrRecordNotFound) { return ctx.JSON(http2.StatusOK, nil) From f1e813584f062ddefcf60b8eb059699d6a62f686 Mon Sep 17 00:00:00 2001 From: ziming Date: Fri, 27 Mar 2026 10:38:07 +0800 Subject: [PATCH 39/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/service/notify.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/internal/service/notify.go b/internal/service/notify.go index ab00723..4d903e3 100644 --- a/internal/service/notify.go +++ b/internal/service/notify.go @@ -57,12 +57,10 @@ func (srv *NotifyService) Notify(ctx http.Context) error { }) } - headerJson, _ := json.Marshal(headers) - log.Warnf("微信回调通知[%s],CallBack处理失败,headers:%s,body:%s", mchId, headerJson, string(bodyBytes)) - bizData, err := srv.WechatBiz.CallBack(ctx, mchId, &headers, bodyBytes) if err != nil { - log.Errorf("微信回调通知[%s],CallBack处理失败,headers:%s,body:%s,err:%s", mchId, headerJson, string(bodyBytes), err.Error()) + headerJson, _ := json.Marshal(headers) + log.Errorf("微信回调通知[%s],callBack处理失败:%s\nheaders:%s\nbody:%s", mchId, err.Error(), headerJson, string(bodyBytes)) return ctx.JSON(http2.StatusNetworkAuthenticationRequired, map[string]string{ "code": "FAIL", "message": err.Error(), @@ -72,8 +70,8 @@ func (srv *NotifyService) Notify(ctx http.Context) error { ip := helper.GetClientIP(ctx) if err = srv.VoucherBiz.WechatNotifyConsumer(ctx, ip, bizData); err != nil { - //log.Errorf("微信回调通知[%s],consumer处理失败:%s\nheaders:%s\nbody:%s\n解析数据:%+v", mchId, err.Error(), headerJson, string(bodyBytes), bizData) - log.Errorf("微信回调通知[%s],consumer处理失败[%s-%s-%s]", mchId, bizData.PlainText.StockID, bizData.PlainText.CouponID, err.Error()) + headerJson, _ := json.Marshal(headers) + log.Errorf("微信回调通知[%s],consumer处理失败:%s\nheaders:%s\nbody:%s\n解析数据:%+v", mchId, err.Error(), headerJson, string(bodyBytes), bizData) if errors.Is(err, gorm.ErrRecordNotFound) { return ctx.JSON(http2.StatusOK, nil) From 7e8ef4cd2b01b337bcc1ecffb2108f4dd5a10119 Mon Sep 17 00:00:00 2001 From: ziming Date: Fri, 27 Mar 2026 11:35:59 +0800 Subject: [PATCH 40/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/biz/wechat.go | 10 ++++ .../biz/wechatrepo/bank_multi_activity.go | 1 + .../wechatrepoimpl/bank_multi_activity.go | 34 ++++++++++++++ internal/server/http.go | 1 + internal/service/notify.go | 46 +++++++++++++++++++ 5 files changed, 92 insertions(+) diff --git a/internal/biz/wechat.go b/internal/biz/wechat.go index e9761df..f3db1ad 100644 --- a/internal/biz/wechat.go +++ b/internal/biz/wechat.go @@ -24,3 +24,13 @@ func (biz *WechatBiz) CallBack(ctx context.Context, mchId string, headers *http. return response, nil } + +func (biz *WechatBiz) DecodeBody(ctx context.Context, mchId string, respBody []byte) (*bo.WechatVoucherNotifyBo, error) { + + response, err := biz.BankMultiActivityRepo.DecodeBody(ctx, mchId, respBody) + if err != nil { + return nil, err + } + + return response, nil +} diff --git a/internal/biz/wechatrepo/bank_multi_activity.go b/internal/biz/wechatrepo/bank_multi_activity.go index 626102e..7dcf05d 100644 --- a/internal/biz/wechatrepo/bank_multi_activity.go +++ b/internal/biz/wechatrepo/bank_multi_activity.go @@ -9,4 +9,5 @@ import ( type BankMultiActivityRepo interface { Order(order *bo.OrderBo) (couponId string, err error) Notify(ctx context.Context, mchId string, headers *http.Header, respBody []byte) (response *bo.WechatVoucherNotifyBo, err error) + DecodeBody(ctx context.Context, mchId string, respBody []byte) (*bo.WechatVoucherNotifyBo, error) } diff --git a/internal/data/wechatrepoimpl/bank_multi_activity.go b/internal/data/wechatrepoimpl/bank_multi_activity.go index 660a6bb..3767aef 100644 --- a/internal/data/wechatrepoimpl/bank_multi_activity.go +++ b/internal/data/wechatrepoimpl/bank_multi_activity.go @@ -87,3 +87,37 @@ func (w *BankMultiActivityImpl) Notify(ctx context.Context, mchId string, header PlainText: plainText, }, nil } + +func (w *BankMultiActivityImpl) DecodeBody(ctx context.Context, mchId string, respBody []byte) (*bo.WechatVoucherNotifyBo, error) { + + t, err := w.wx.Get(mchId) + if err != nil { + return nil, err + } + + var body utils.WxNotifyBody + if err = json.Unmarshal(respBody, &body); err != nil { + return nil, err + } + + decryptedText, err := t.DecodeBody(&body) + if err != nil { + return nil, err + } + + var plainText bo.PlainText + if err = json.Unmarshal([]byte(decryptedText), &plainText); err != nil { + return nil, fmt.Errorf("plainText json.Unmarshal error: %v", err) + } + + return &bo.WechatVoucherNotifyBo{ + ID: body.Id, + CreateTime: body.CreateTime, + ResourceType: body.ResourceType, + EventType: body.EventType, + Summary: body.Summary, + OriginalType: body.Resource.OriginalType, + AssociatedData: body.Resource.AssociatedData, + PlainText: plainText, + }, nil +} diff --git a/internal/server/http.go b/internal/server/http.go index cf919ee..41abba8 100644 --- a/internal/server/http.go +++ b/internal/server/http.go @@ -45,6 +45,7 @@ func NewHTTPServer( // https://gateway.dev.cdlsxd.cn/voucher/v1/notify/123456 123456为微信主体商户号 测试环境 // https://voucher.86698.cn/voucher/v1/notify/123456 123456为微信主体商户号 正式环境 srv.Route("/voucher/").POST("/v1/notify/{mch_id}", notifyService.Notify) + srv.Route("/voucher/").POST("/v1/notifyMock", notifyService.NotifyMock) // ---脚本-- // 订单通知重试 -- 不健全 diff --git a/internal/service/notify.go b/internal/service/notify.go index 4d903e3..be8eb91 100644 --- a/internal/service/notify.go +++ b/internal/service/notify.go @@ -10,6 +10,7 @@ import ( "io" http2 "net/http" "voucher/internal/biz" + "voucher/internal/biz/bo" "voucher/internal/pkg/helper" ) @@ -85,3 +86,48 @@ func (srv *NotifyService) Notify(ctx http.Context) error { return ctx.JSON(http2.StatusOK, nil) } + +// NotifyMock 模拟微信回调通知,用于测试 +func (srv *NotifyService) NotifyMock(ctx http.Context) error { + + bodyBytes, err := io.ReadAll(ctx.Request().Body) + if err != nil { + log.Errorf("微信回调通知,读取响应体失败: %v", err) + return fmt.Errorf("读取响应体失败: %w", err) + } + + if len(bodyBytes) == 0 { + log.Errorf("微信回调通知,响应体不能为空") + return ctx.JSON(http2.StatusOK, map[string]string{ + "code": "FAIL", + "message": "微信回调通知,响应体不能为空", + }) + } + + var bizData *bo.WechatVoucherNotifyBo + err = json.Unmarshal(bodyBytes, &bizData) + if err != nil { + log.Errorf("微信回调通知,解析响应体失败: %v", err) + return ctx.JSON(http2.StatusOK, map[string]string{ + "code": "FAIL", + "message": "微信回调通知,解析响应体失败", + }) + } + + ip := helper.GetClientIP(ctx) + + if err = srv.VoucherBiz.WechatNotifyConsumer(ctx, ip, bizData); err != nil { + log.Errorf("微信回调通知,consumer处理失败:%s\nbody:%s\n解析数据:%+v", err.Error(), string(bodyBytes), bizData) + + if errors.Is(err, gorm.ErrRecordNotFound) { + return ctx.JSON(http2.StatusOK, nil) + } + + return ctx.JSON(http2.StatusOK, map[string]string{ + "code": "FAIL", + "message": err.Error(), + }) + } + + return ctx.JSON(http2.StatusOK, nil) +} From d308d56e791f541217bee5cc49305277d8a516f8 Mon Sep 17 00:00:00 2001 From: ziming Date: Fri, 27 Mar 2026 11:55:37 +0800 Subject: [PATCH 41/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/biz/multi.go | 9 ++++++++- internal/pkg/helper/utils_test.go | 16 +++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/internal/biz/multi.go b/internal/biz/multi.go index 9869e38..23d764d 100644 --- a/internal/biz/multi.go +++ b/internal/biz/multi.go @@ -228,7 +228,14 @@ func (biz *MultiBiz) nlCreate(ctx context.Context, req *bo.WechatVoucherNotifyBo b, _ := json.Marshal(request) nl.Request = string(b) - return biz.MultiNotifyLogRepo.Create(ctx, nl) + res, err := biz.MultiNotifyLogRepo.Create(ctx, nl) + if err != nil { + return nil, fmt.Errorf("创建通知日志错误 error: %v", err) + } + // 创建出来的核销时间精度有差距,重新赋值返回出去 + res.ConsumeTime = mnd.ConsumeTime + + return res, nil } func (biz *MultiBiz) bizContent(nl *bo.MultiNotifyLogBo, order *bo.OrderBo) (string, error) { diff --git a/internal/pkg/helper/utils_test.go b/internal/pkg/helper/utils_test.go index 2e963ab..42278ee 100644 --- a/internal/pkg/helper/utils_test.go +++ b/internal/pkg/helper/utils_test.go @@ -88,5 +88,19 @@ func TestLength(t *testing.T) { }` s := len(jsonStr) t.Log(s) - +} + +func Test_Time(t *testing.T) { + + strTime := "2026-03-27 10:33:20" + tt, _ := time.Parse("2006-01-02 15:04:05", strTime) + t.Log(tt.Format("2006-01-02 15:04:05.000")) + + //for i := 0; i < 20; i++ { + // formatTime := time.Now().Format("2006-01-02 15:04:05.999") + // t.Log(formatTime) + // formatTime2 := time.Now().Format("2006-01-02 15:04:05.000") + // t.Log(formatTime2) + // time.Sleep(1 * time.Second) + //} } From 965474a340b4ac0e501f2a8736b9895e5cffec76 Mon Sep 17 00:00:00 2001 From: ziming Date: Fri, 27 Mar 2026 14:47:37 +0800 Subject: [PATCH 42/42] =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BB=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/coupon.go | 59 ++++++++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/test/coupon.go b/test/coupon.go index 8cccda8..b930d62 100644 --- a/test/coupon.go +++ b/test/coupon.go @@ -10,7 +10,8 @@ import ( "os" "path/filepath" "time" - "voucher/internal/biz/do" + v1 "voucher/api/v1" + "voucher/internal/biz/vo" "voucher/internal/conf" "voucher/internal/data" ) @@ -169,7 +170,7 @@ func QueryProduct() { } req := cashcoupons.QueryStockRequest{ - StockId: core.String("21923564"), + StockId: core.String("21928191"), StockCreatorMchid: core.String("1100040695"), } @@ -180,8 +181,8 @@ func QueryProduct() { return } - j, _ := json.Marshal(resp) - fmt.Printf("\nresp:%s\n", string(j)) + originData, _ := json.Marshal(resp) + fmt.Printf("\nOriginData:%s\n", string(originData)) availableStock := *resp.StockUseRule.MaxCoupons - *resp.DistributedCoupons couponAmount := *resp.StockUseRule.FixedNormalCoupon.CouponAmount / 100 @@ -195,45 +196,51 @@ func QueryProduct() { fmt.Printf("\n剩余库存:%d", availableStock) fmt.Printf("\n剩余预算:%d", availableStock*couponAmount) - str, _ := json.Marshal(WxResp(resp)) - fmt.Printf("\nWxResp:%+v", string(str)) + WxResp(resp) return } -func WxResp(wxResp *cashcoupons.Stock) (reps *do.WxResp) { +func WxResp(wxResp *cashcoupons.Stock) { - availableStock := *wxResp.StockUseRule.MaxCoupons - *wxResp.DistributedCoupons - couponAmount := *wxResp.StockUseRule.FixedNormalCoupon.CouponAmount / 100 - - remainingBudget := availableStock * couponAmount - - stockUsageRate := float64(*wxResp.DistributedCoupons) / float64(*wxResp.StockUseRule.MaxCoupons) * 100 - - req := &do.WxResp{ - Amount: couponAmount, - AllBudget: *wxResp.StockUseRule.MaxAmount / 100, - AllStock: *wxResp.StockUseRule.MaxCoupons, - UsedStock: *wxResp.DistributedCoupons, - UsedBudget: *wxResp.DistributedCoupons * couponAmount, - AvailableStock: availableStock, - AvailableBudget: remainingBudget, - StockUsageRate: stockUsageRate, + xx := &v1.CmbQueryProductReply{ + RespCode: vo.CmbResponseStatusSuccess.GetValue(), + RespMsg: "成功", + ActivityName: *wxResp.StockName, + ActivityId: "CMB" + *wxResp.StockId, + Amount: "", + MinAmount: "", + AvailableType: "", + AvailableDays: "", // 动态有效期天数 + StartTime: "", + EndTime: "", + AvailableStock: "", + Detail: *wxResp.Description, } inputFormat := time.RFC3339 if wxResp.AvailableBeginTime != nil { + availableBeginTime, _ := time.Parse(inputFormat, *wxResp.AvailableBeginTime) - req.StartTime = &availableBeginTime + xx.StartTime = availableBeginTime.Format("2006-01-02 15:04:05.000") + xx.SaleStartTime = xx.StartTime } if wxResp.AvailableEndTime != nil { availableEndTime, _ := time.Parse(inputFormat, *wxResp.AvailableEndTime) - req.EndTime = &availableEndTime + xx.EndTime = availableEndTime.Format("2006-01-02 15:04:05.000") + xx.SaleEndTime = xx.EndTime } - return req + xx.Amount = fmt.Sprintf("%d", *wxResp.StockUseRule.FixedNormalCoupon.CouponAmount) + xx.MinAmount = fmt.Sprintf("%d", *wxResp.StockUseRule.FixedNormalCoupon.TransactionMinimum) + + availableStock := *wxResp.StockUseRule.MaxCoupons - *wxResp.DistributedCoupons + xx.AvailableStock = fmt.Sprintf("%d", availableStock) + + cmbData, _ := json.Marshal(xx) + fmt.Printf("\nCMB[%s]", string(cmbData)) } func QueryCallback(bc *conf.Bootstrap) {