package biz import ( "context" "encoding/json" "errors" "fmt" "github.com/go-kratos/kratos/v2/log" "gorm.io/gorm" "time" v1 "voucher/api/v1" "voucher/internal/biz/bo" "voucher/internal/biz/cmb" "voucher/internal/biz/mixrepos" "voucher/internal/biz/repo" "voucher/internal/biz/vo" "voucher/internal/conf" "voucher/internal/data" "voucher/internal/pkg/lock" ) type MultiBiz struct { bc *conf.Bootstrap rdb *data.Rdb Cmb *cmb.Cmb ProductRepo repo.ProductRepo OrderRepo repo.OrderRepo MultiNotifyDataRepo repo.MultiNotifyDataRepo MultiNotifyLogRepo repo.MultiNotifyLogRepo CmbMixRepo mixrepos.CmbMixRepo } func NewMultiBiz( bc *conf.Bootstrap, rdb *data.Rdb, cmb *cmb.Cmb, productRepo repo.ProductRepo, orderRepo repo.OrderRepo, multiNotifyDataRepo repo.MultiNotifyDataRepo, multiNotifyLogRepo repo.MultiNotifyLogRepo, cmbMixRepo mixrepos.CmbMixRepo, ) *MultiBiz { return &MultiBiz{ bc: bc, rdb: rdb, Cmb: cmb, ProductRepo: productRepo, OrderRepo: orderRepo, MultiNotifyDataRepo: multiNotifyDataRepo, MultiNotifyLogRepo: multiNotifyLogRepo, CmbMixRepo: cmbMixRepo, } } func (biz *MultiBiz) Notify(ctx context.Context, source string, req *bo.WechatVoucherNotifyBo) error { cl := vo.MultiNotifyLockKey.BuildCache([]string{ source, req.PlainText.StockCreatorMchid, req.PlainText.StockID, req.PlainText.CouponID, }) return lock.NewMutex(biz.rdb.Rdb, cl.TTL).Lock(ctx, cl.Key, func(ctx context.Context) error { order, err := biz.order(ctx, req) if err != nil { return err } if err = biz.Run(ctx, source, req, order); err != nil { return err } return nil }) } func (biz *MultiBiz) order(ctx context.Context, req *bo.WechatVoucherNotifyBo) (*bo.OrderBo, error) { order, err := biz.OrderRepo.GetByCouponId(ctx, req.PlainText.StockCreatorMchid, req.PlainText.StockID, req.PlainText.CouponID) if err != nil { return nil, fmt.Errorf("订单查询错误 error: %v", err) } return order, nil } func (biz *MultiBiz) Run(ctx context.Context, source string, req *bo.WechatVoucherNotifyBo, order *bo.OrderBo) error { if order.ActivityId == "" { return fmt.Errorf("批次活动ID为空,不是多笔立减金,请检查") } mnd, err := biz.MultiNotifyDataRepo.GetByNotifyID(ctx, source, req.ID) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return fmt.Errorf("查询通知数据错误 error: %v", err) } if mnd != nil { if mnd.NoticeNum > 0 { log.Warnf("[%s] multi notify log already exists,req:%+v", source, req) return nil } } else { mnd, err = biz.mndCreate(ctx, source, req, order) if err != nil { return fmt.Errorf("创建通知数据错误 error: %v", err) } } nl, err := biz.nlCreate(ctx, req, mnd, order) if err != nil { return fmt.Errorf("创建通知日志错误 error: %v", err) } return biz.Request(ctx, mnd, nl) } func (biz *MultiBiz) RetryRunByMultiNotifyDataId(ctx context.Context, multiNotifyDataId int64) error { mnd, err := biz.MultiNotifyDataRepo.GetByID(ctx, multiNotifyDataId) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return fmt.Errorf("查询通知数据错误 error: %v", err) } order, err := biz.OrderRepo.GetByOrderNo(ctx, mnd.OrderNo) if err != nil { return fmt.Errorf("订单查询错误 error: %v", err) } var req *bo.WechatVoucherNotifyBo if err = json.Unmarshal([]byte(mnd.OriginalData), &req); err != nil { return fmt.Errorf("通知数据 json unmarshal 错误 error: %v", err) } nl, err := biz.nlCreate(ctx, req, mnd, order) if err != nil { return fmt.Errorf("创建通知日志错误 error: %v", err) } return biz.Request(ctx, mnd, nl) } func (biz *MultiBiz) mndCreate(ctx context.Context, source string, req *bo.WechatVoucherNotifyBo, order *bo.OrderBo) (*bo.MultiNotifyDataBo, error) { originalData, err := req.Str() if err != nil { return nil, fmt.Errorf("通知数据 json str 错误 error: %v", err) } return biz.MultiNotifyDataRepo.Create(ctx, &bo.MultiNotifyDataBo{ Source: source, NotifyID: req.ID, OrderNo: order.OrderNo, OutBizNo: order.OutBizNo, CouponID: req.PlainText.CouponID, StockID: req.PlainText.StockID, ConsumeAmount: int32(req.PlainText.ConsumeInformation.ConsumeAmount), ConsumeTime: &req.PlainText.ConsumeInformation.ConsumeTime, EventType: req.EventType, OriginalData: originalData, }) } func (biz *MultiBiz) nlCreate(ctx context.Context, req *bo.WechatVoucherNotifyBo, mnd *bo.MultiNotifyDataBo, order *bo.OrderBo) (*bo.MultiNotifyLogBo, error) { nl := &bo.MultiNotifyLogBo{ MultiNotifyDataID: mnd.ID, OrderNo: mnd.OrderNo, OutBizNo: mnd.OutBizNo, CouponID: mnd.CouponID, ActivityNo: order.ProductNo, StockID: mnd.StockID, EventType: mnd.EventType, Status: req.PlainText.Status.GetValue(), ConsumeAmount: mnd.ConsumeAmount, ConsumeTime: mnd.ConsumeTime, TransactionID: req.PlainText.ConsumeInformation.TransactionID, RequestURL: order.NotifyUrl, RequestStatus: vo.MultiNotifyLogStatusWait.GetValue(), OrderCreateTime: order.CreateTime, CouponCreateTime: &req.PlainText.CreateTime, } request, err := biz.GetRequest(ctx, nl) if err != nil { return nil, err } b, _ := json.Marshal(request) nl.Request = string(b) return biz.MultiNotifyLogRepo.Create(ctx, nl) } func (biz *MultiBiz) bizContent(nl *bo.MultiNotifyLogBo) (string, error) { req := &v1.CmbNotifyRequest{ // 待确定 Ticket: nl.OrderNo, TransDate: "", // 格式yyyy-mm-dd hh:mm:ss.sss OrgNo: biz.bc.Cmb.OrgNo, //Attach: nl.Attach, Ext: "", } if nl.ConsumeTime != nil { req.TransDate = nl.ConsumeTime.Format("2006-01-02 15:04:05.000") } else { req.TransDate = time.Now().Format("2006-01-02 15:04:05.000") } bizJsonBytes, err := json.Marshal(req) if err != nil { return "", fmt.Errorf("json.Marshal CmbNotifyRequest error: %v", err) } return string(bizJsonBytes), nil } func (biz *MultiBiz) GetRequest(ctx context.Context, nl *bo.MultiNotifyLogBo) (*v1.CmbRequest, error) { bizContent, err := biz.bizContent(nl) if err != nil { return nil, err } request, err := biz.CmbMixRepo.GetRequest(ctx, &bo.CmbRequestBo{ FuncName: vo.CmbNotifyFuncName, // 待确定 BizContent: bizContent, }) if err != nil { return nil, err } return request, nil } func (biz *MultiBiz) Request(ctx context.Context, mmd *bo.MultiNotifyDataBo, nl *bo.MultiNotifyLogBo) error { if nl.RequestURL == "" { if err := biz.notifyFail(ctx, nl, "回调通知招行地址为空"); err != nil { return err } // 回调通知地址为空,不反回错误,不做再次通知处理 return nil } request, err := biz.GetRequest(ctx, nl) if err != nil { return err } reply, err := biz.CmbMixRepo.Request(ctx, request, nl.RequestURL) if err != nil { if err2 := biz.notifyFail(ctx, nl, err.Error()); err2 != nil { return err2 } return err } if err = biz.CmbMixRepo.VerifyResponse(ctx, reply); err != nil { errMsg := fmt.Sprintf("回调通知招行返回验证结果发生错误,resp:%+v error:%s", reply, err.Error()) if err2 := biz.notifyFail(ctx, nl, errMsg); err2 != nil { return err2 } return err } return biz.notifySuccess(ctx, mmd, nl, reply) } func (biz *MultiBiz) notifyFail(ctx context.Context, nl *bo.MultiNotifyLogBo, remark string) error { return biz.MultiNotifyLogRepo.Fail(ctx, nl.ID, remark) } func (biz *MultiBiz) notifySuccess(ctx context.Context, mmd *bo.MultiNotifyDataBo, nl *bo.MultiNotifyLogBo, reply *v1.CmbReply) error { response, err := json.Marshal(reply) if err != nil { return err } if err = biz.MultiNotifyLogRepo.Success(ctx, nl.ID, string(response)); err != nil { return err } if err = biz.MultiNotifyDataRepo.AddNoticeNum(ctx, mmd.ID); err != nil { return err } return nil }