package biz import ( "context" "encoding/json" "errors" "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" "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 mu sync.RWMutex NonExistentBatchData map[string]bool } 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, 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, req.PlainText.CouponID, }) 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") } order, err := biz.order(ctx, req) if err != nil { return err } if err = biz.Run(ctx, ip, 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, ip, 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, ip, source, req, order) if err != nil { return fmt.Errorf("创建通知数据错误 error: %v", err) } } return biz.run(ctx, req, mnd, order) } 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) } return biz.run(ctx, req, mnd, order) } func (biz *MultiBiz) run(ctx context.Context, req *bo.WechatVoucherNotifyBo, mnd *bo.MultiNotifyDataBo, order *bo.OrderBo) error { if req.PlainText.Status.IsUsed() { if err := biz.OrderRepo.OverUsed(ctx, order.ID, req.PlainText.ConsumeInformation.ConsumeTime); err != nil { return fmt.Errorf("订单使用完成修改发生错误 error: %v", err) } } else { if err := biz.OrderRepo.LastUsed(ctx, order.ID, req.PlainText.ConsumeInformation.ConsumeTime); 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, order) } func (biz *MultiBiz) mndCreate(ctx context.Context, ip, 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, IP: ip, 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, TransactionID: req.PlainText.ConsumeInformation.TransactionID, 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, 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, order) 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, order *bo.OrderBo) (string, error) { req := &v1.CmbMultiNotifyRequest{ TransactionId: nl.OutBizNo, // cmb业务号 ActivityId: nl.ActivityNo, // 批次活动号 CouponId: nl.CouponID, // 微信券券号 AcquiredDate: order.ReceiveSuccessTime.Format("2006-01-02 15:04:05.000"), // 券领取时间 Status: "0", // 券状态 0:可使用,1:已使用 TransDate: nl.ConsumeTime.Format("2006-01-02 15:04:05.000"), // 核销时间,验券时间,格式yyyy-mm-dd hh:mm:ss.sss TransAmount: fmt.Sprintf("%d", nl.ConsumeAmount), OrderId: nl.TransactionID, // 券核销支付单号 Ticket: nl.OrderNo, // 券订单号,lsxd订单号 OrgNo: "LANSEXIONGDIMULTI", // cmb固定值 Attach: order.Attach, // cmb拓展参数 Ext: "", } if nl.Status.IsUsed() { req.Status = "1" } 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, order *bo.OrderBo) (*v1.CmbRequest, error) { bizContent, err := biz.bizContent(nl, order) if err != nil { return nil, err } request, err := biz.CmbMixRepo.GetRequest(ctx, &bo.CmbRequestBo{ FuncName: vo.CmbNotifyFuncNameUpdateCodeStatusForMulti, BizContent: bizContent, }) if err != nil { return nil, err } return request, nil } func (biz *MultiBiz) Request(ctx context.Context, mmd *bo.MultiNotifyDataBo, nl *bo.MultiNotifyLogBo, order *bo.OrderBo) error { if nl.RequestURL == "" { if err := biz.notifyFail(ctx, nl, "回调通知招行地址为空,不做通知"); err != nil { return err } // 回调通知地址为空,不返回错误,不做再次通知处理 return nil } request, err := biz.GetRequest(ctx, nl, order) if err != nil { return err } reply, err := biz.CmbMixRepo.Request(ctx, request, nl.RequestURL) if err != nil { if err3 := biz.notifyFail(ctx, nl, err.Error()); err3 != nil { return err3 } return err } if err = biz.CmbMixRepo.VerifyResponse(ctx, reply); err != nil { errMsg := fmt.Sprintf("回调通知招行返回验证结果发生错误,resp:%+v error:%s", reply, err.Error()) if err3 := biz.notifyFail(ctx, nl, errMsg); err3 != nil { return err3 } return err } return biz.notifySuccess(ctx, mmd, nl, reply) } func (biz *MultiBiz) notifyFail(ctx context.Context, nl *bo.MultiNotifyLogBo, remark string) error { if err := biz.MultiNotifyLogRepo.Fail(ctx, nl.ID, remark); err != nil { return fmt.Errorf("更新通知日志失败状态发生错误 error: %v", err) } return nil } 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 fmt.Errorf("json.Marshal CmbReply error: %v", err) } if err = biz.MultiNotifyLogRepo.Success(ctx, nl.ID, string(response)); err != nil { return fmt.Errorf("更新通知日志成功状态发生错误 error: %v", err) } if err = biz.MultiNotifyDataRepo.AddNoticeNum(ctx, mmd.ID); err != nil { return fmt.Errorf("更新通知数据通知次数发生错误 error: %v", err) } return nil }