package biz import ( "context" "fmt" "github.com/go-kratos/kratos/v2/log" "github.com/redis/go-redis/v9" err2 "voucher/api/err" "voucher/internal/biz/bo" "voucher/internal/biz/vo" "voucher/internal/pkg/lock" ) func (v *VoucherBiz) order(ctx context.Context, req *bo.OrderCreateReqBo, product *bo.ProductBo) (*bo.OrderBo, error) { order, err := v.create(ctx, req, product) if err != nil { return nil, err } if product.ProductNo == "001" { // 压测商品-直接返回 return order, nil } // 注册通知标签 order.MerchantNo 批次创建商户, order.BatchNo 商品批次号 if err = v.registerNotifyTag(ctx, order.MerchantNo, order.BatchNo); err != nil { return nil, err } // 真实发放 voucherNo, err := v.WechatCpnRepo.Order(ctx, order) if err != nil { if err3 := v.fail(ctx, order, err.Error()); err3 != nil { return nil, err3 } return nil, err } return order, v.success(ctx, order, voucherNo) } func (v *VoucherBiz) orderRetry(ctx context.Context, order *bo.OrderBo) error { voucherNo, err := v.WechatCpnRepo.Order(ctx, order) if err != nil { if err3 := v.fail(ctx, order, err.Error()); err3 != nil { return err3 } return err } return v.success(ctx, order, voucherNo) } func (v *VoucherBiz) create(ctx context.Context, req *bo.OrderCreateReqBo, product *bo.ProductBo) (*bo.OrderBo, error) { o := &bo.OrderBo{ OrderNo: v.GenerateMixRepo.GeneratorString(ctx, fmt.Sprintf("%d%s", req.Type, req.OutBizNo)), OutBizNo: req.OutBizNo, ProductNo: req.ProductNo, Account: req.Account, AppID: req.AppID, MerchantNo: product.MchId, Channel: product.Channel, BatchNo: product.BatchNo, NotifyUrl: req.NotifyUrl, AccountType: vo.OrderAccountTypeOpenId, Type: req.Type, Status: vo.OrderStatusIng, // 同步发放,状态至为发放中 Attach: req.Attach, } return v.OrderRepo.Create(ctx, o) } func (v *VoucherBiz) registerNotifyTag(ctx context.Context, stockCreatorMchID, stockID string) error { c := vo.WechatNotifyRegisterTagCacheKey.BuildCache([]string{v.bc.WechatNotifyMQ.Tag, stockCreatorMchID, stockID}) _, err := v.rdb.Rdb.Get(ctx, c.Key).Result() if err == nil { // 缓存存在,直接返回 return nil } if err != redis.Nil { return fmt.Errorf(fmt.Sprintf("获取redis缓存%s异常:%v", c.Key, err)) } cl := vo.WechatNotifyRegisterTagCacheLockKey.BuildCache([]string{v.bc.WechatNotifyMQ.Tag, stockCreatorMchID, stockID}) return lock.NewMutex(v.rdb.Rdb, cl.TTL).Lock(ctx, cl.Key, func(ctx context.Context) error { // 二次获取,判定处理,以免获取锁后又执行了一次 cacheValue, err := v.rdb.Rdb.Get(ctx, c.Key).Result() if err != nil && err != redis.Nil { return fmt.Errorf(fmt.Sprintf("二次获取redis缓存%s异常:%v", c.Key, err)) } if cacheValue != "" { return nil // 有直接返回 } wechatNotifyTag, err := v.WechatNotifyRegisterTagRepo.GetByStockIdAndMchId(ctx, stockCreatorMchID, stockID) if err != nil && !err2.IsDbNotFound(err) { return err } if wechatNotifyTag != nil { if wechatNotifyTag.Tag != v.bc.WechatNotifyMQ.Tag { return fmt.Errorf("tag不一致,请检查tag配置:%s", wechatNotifyTag.Tag) } return v.setCache(ctx, c, wechatNotifyTag) } wechatNotifyTag, err = v.createWechatNotifyRegisterTag(ctx, stockCreatorMchID, stockID) if err != nil { return err } if err = v.WechatCpnRepo.RegisterNotifyTag(ctx, stockID); err != nil { return v.WechatNotifyRegisterTagRepo.Fail(ctx, wechatNotifyTag.ID, err.Error()) } if err = v.WechatNotifyRegisterTagRepo.Success(ctx, wechatNotifyTag.ID); err != nil { return err } return v.setCache(ctx, c, wechatNotifyTag) }) } func (v *VoucherBiz) createWechatNotifyRegisterTag(ctx context.Context, stockCreatorMchID, stockID string) (*bo.WechatNotifyRegisterTagBo, error) { return v.WechatNotifyRegisterTagRepo.Create(ctx, &bo.WechatNotifyRegisterTagBo{ StockID: stockID, StockCreatorMchID: stockCreatorMchID, Tag: v.bc.WechatNotifyMQ.Tag, }) } func (v *VoucherBiz) setCache(ctx context.Context, c *vo.Cache, wechatNotifyTag *bo.WechatNotifyRegisterTagBo) error { if err := v.rdb.Rdb.Set(ctx, c.Key, wechatNotifyTag.Tag, c.TTL).Err(); err != nil { return fmt.Errorf(fmt.Sprintf("设置redis缓存%s异常:%v", c.Key, err)) } return nil } func (v *VoucherBiz) ing(ctx context.Context, id uint64) error { return v.OrderRepo.Ing(ctx, id) } func (v *VoucherBiz) success(ctx context.Context, order *bo.OrderBo, voucherNo string) error { return v.OrderRepo.Success(ctx, order.ID, voucherNo) } func (v *VoucherBiz) fail(ctx context.Context, order *bo.OrderBo, remark string) error { if err := v.OrderRepo.Fail(ctx, order.ID, remark); err != nil { return err } return v.alarm(ctx, order, remark) } func (v *VoucherBiz) alarm(ctx context.Context, order *bo.OrderBo, errMsg string) error { // 1小时 内 指定的批次号 发放 发生错误 预警 c := vo.OrderConsumeFailAlarmKey.BuildCache([]string{order.ProductNo}) _, err := v.rdb.Rdb.Get(ctx, c.Key).Result() if err == nil { // 缓存存在,直接返回 return nil } if err != redis.Nil { return fmt.Errorf(fmt.Sprintf("alarm 获取redis缓存%s异常:%v", c.Key, err)) } cl := vo.OrderConsumeFailAlarmLockKey.BuildCache([]string{order.ProductNo}) return lock.NewMutex(v.rdb.Rdb, cl.TTL).Lock(ctx, cl.Key, func(ctx context.Context) error { // 二次获取,判定处理,以免获取锁后又执行了一次 cacheValue, err := v.rdb.Rdb.Get(ctx, c.Key).Result() if err != nil && err != redis.Nil { return fmt.Errorf(fmt.Sprintf("alarm 二次获取redis缓存%s异常:%v", c.Key, err)) } if len(cacheValue) > 0 { return nil // 有直接返回 } // 通知 if err = v.DingMixRepo.SendMarkdownMessage(ctx, "异常通知", v.alarmText(ctx, order, errMsg)); err != nil { return err } if err = v.rdb.Rdb.Set(ctx, c.Key, order.ProductNo, c.TTL).Err(); err != nil { return fmt.Errorf(fmt.Sprintf("设置redis缓存%s异常:%v", c.Key, err)) } return nil }) } func (v *VoucherBiz) alarmText(_ context.Context, order *bo.OrderBo, errMsg string) string { remarks := fmt.Sprintf("订单号:%s,商品编号:%s,原因:%s", order.OrderNo, order.ProductNo, errMsg) msg := "# " + "

立减金发放平台报警通知

" + "
\n" + "" + "不好了,订单发放发生异常了" + "[%s]请尽快处理@相关人员。" + "" return fmt.Sprintf(msg, remarks) } func (v *VoucherBiz) Query(ctx context.Context, order *bo.OrderBo) error { status, err := v.WechatCpnRepo.Query(ctx, order) if err != nil { return err } if order.Status == status { log.Warnf("券状态未改变:%s,忽略不处理,orderNo:%s", order.Status.GetText(), order.OrderNo) return nil } if err = v.UpdateOrderStatus(ctx, order.ID, status); err != nil { return err } order.Status = status return nil } func (v *VoucherBiz) UpdateOrderStatus(ctx context.Context, orderId uint64, status vo.OrderStatus) error { if status.IsSuccess() { return v.OrderRepo.Available(ctx, orderId) } else if status.IsUse() { return v.OrderRepo.Used(ctx, orderId) } else if status.IsExpired() { return v.OrderRepo.Expired(ctx, orderId) } return fmt.Errorf("notice 未知券状态,orderId:%d,statuText:%s", orderId, status.GetText()) }