This commit is contained in:
ziming 2025-05-20 14:16:54 +08:00
parent 9c62904de5
commit ad95880eef
16 changed files with 698 additions and 422 deletions

69
internal/biz/alarm.go Normal file
View File

@ -0,0 +1,69 @@
package biz
import (
"context"
"fmt"
"github.com/redis/go-redis/v9"
"voucher/internal/biz/bo"
"voucher/internal/biz/vo"
"voucher/internal/pkg/lock"
)
func (v *VoucherBiz) alarm(ctx context.Context, order *bo.OrderBo, errMsg string) error {
// 1小时 内 指定的批次号 发放 发生错误 预警
c := vo.OrderConsumeFailAlarmKey.BuildCache([]string{order.ProductNo})
_, err := v.rdb.Rdb.Get(ctx, c.Key).Result()
if err == nil {
// 缓存存在,直接返回
return nil
}
if err != redis.Nil {
return fmt.Errorf(fmt.Sprintf("alarm 获取redis缓存%s异常:%v", c.Key, err))
}
cl := vo.OrderConsumeFailAlarmLockKey.BuildCache([]string{order.ProductNo})
return lock.NewMutex(v.rdb.Rdb, cl.TTL).Lock(ctx, cl.Key, func(ctx context.Context) error {
// 二次获取,判定处理,以免获取锁后又执行了一次
cacheValue, err3 := v.rdb.Rdb.Get(ctx, c.Key).Result()
if err3 != nil && err3 != redis.Nil {
return fmt.Errorf(fmt.Sprintf("alarm 二次获取redis缓存%s异常:%v", c.Key, err))
}
if len(cacheValue) > 0 {
return nil // 有直接返回
}
// 通知
if err = v.DingMixRepo.SendMarkdownMessage(ctx, "异常通知", v.alarmText(ctx, order, errMsg)); err != nil {
return err
}
if err = v.rdb.Rdb.Set(ctx, c.Key, order.ProductNo, c.TTL).Err(); err != nil {
return fmt.Errorf(fmt.Sprintf("设置redis缓存%s异常:%v", c.Key, err))
}
return nil
})
}
func (v *VoucherBiz) alarmText(_ context.Context, order *bo.OrderBo, errMsg string) string {
remarks := fmt.Sprintf("订单号:%s商品编号:%s,原因:%s", order.OrderNo, order.ProductNo, errMsg)
msg := "# <font color='green'>" +
"<h1>立减金发放平台报警通知</h1>" +
"</font> \n" +
"<font color='black'>" +
"不好了,订单发放发生异常了" +
"[<font color='red'>%s</font>]请尽快处理@相关人员。" +
"</font>"
return fmt.Sprintf(msg, remarks)
}

View File

@ -1,162 +1 @@
package biz package biz
import (
"context"
"fmt"
"time"
err2 "voucher/api/err"
v1 "voucher/api/v1"
"voucher/internal/biz/bo"
"voucher/internal/biz/vo"
"voucher/internal/pkg/lock"
)
func (v *VoucherBiz) GetByOutBizNo(ctx context.Context, req *bo.OrderCreateReqBo) (*bo.OrderBo, error) {
order, err := v.OrderRepo.GetByOutBizNo(ctx, vo.OrderTypeCmb, req.OutBizNo)
if err != nil && !err2.IsDbNotFound(err) {
return nil, err
}
return order, nil
}
func (v *VoucherBiz) CmbOrder(ctx context.Context, req *bo.OrderCreateReqBo) (orderNo string, err error) {
order, err3 := v.GetByOutBizNo(ctx, req)
if err3 != nil {
return orderNo, err3
}
if order != nil {
if order.Status.IsFail() {
if err4 := v.orderRetry(ctx, order); err4 != nil {
return orderNo, err4
}
}
orderNo = order.OrderNo
return orderNo, err
}
product, err3 := v.ProductRepo.GetByProductNo(ctx, req.ProductNo)
if err3 != nil {
return orderNo, err3
}
order, err3 = v.order(ctx, req, product)
if err3 != nil {
return orderNo, err3
}
orderNo = order.OrderNo
return orderNo, nil
}
func (v *VoucherBiz) CmbQuery(ctx context.Context, orderNo string) (resp *v1.CmbQueryReply, err error) {
c := vo.CmbQueryLockKey.BuildCache([]string{orderNo})
err = lock.NewMutex(v.rdb.Rdb, c.TTL).Lock(ctx, c.Key, func(ctx context.Context) error {
order, err3 := v.OrderRepo.GetByOrderNo(ctx, orderNo)
if err3 != nil {
return err3
}
if err = v.Query(ctx, order); err != nil {
return err
}
status, err3 := order.Status.GetCmbStatusText()
if err3 != nil {
return err3
}
resp = &v1.CmbQueryReply{
Ticket: order.OrderNo,
Status: status.GetValue(),
TransDate: time.Now().Format("20060102150405"),
OrgNo: v.bc.Cmb.OrgNo,
Ext: "",
}
return nil
})
return
}
func (v *VoucherBiz) CmbProductQuery(ctx context.Context, productNo string) (reps *v1.CmbQueryProductReply, err error) {
c := vo.CmbProductQueryLockKey.BuildCache([]string{productNo})
err = lock.NewMutex(v.rdb.Rdb, c.TTL).Lock(ctx, c.Key, func(ctx context.Context) error {
product, err3 := v.ProductRepo.GetByProductNo(ctx, productNo)
if err3 != nil {
return err3
}
if !product.Channel.IsWeChat() {
return fmt.Errorf("只支持微信")
}
wechatResp, err4 := v.WechatCpnRepo.QueryProduct(ctx, product.MchId, product.BatchNo)
if err4 != nil {
return err4
}
reps = &v1.CmbQueryProductReply{
RespCode: vo.CmbResponseStatusSuccess.GetValue(),
RespMsg: "成功",
ActivityName: product.Name,
ActivityId: product.ProductNo,
Amount: "",
MinAmount: "",
AvailableType: "",
AvailableDays: "", // 动态有效期天数
StartTime: "",
EndTime: "",
AvailableStock: "",
Detail: *wechatResp.Description,
}
inputFormat := time.RFC3339
if wechatResp.AvailableBeginTime != nil {
availableBeginTime, _ := time.Parse(inputFormat, *wechatResp.AvailableBeginTime)
reps.StartTime = availableBeginTime.Format("2006-01-02 15:04:05.000")
reps.SaleStartTime = reps.StartTime
}
if wechatResp.AvailableEndTime != nil {
availableEndTime, _ := time.Parse(inputFormat, *wechatResp.AvailableEndTime)
reps.EndTime = availableEndTime.Format("2006-01-02 15:04:05.000")
reps.SaleEndTime = reps.EndTime
}
reps.Amount = fmt.Sprintf("%d", *wechatResp.StockUseRule.FixedNormalCoupon.CouponAmount)
reps.MinAmount = fmt.Sprintf("%d", *wechatResp.StockUseRule.FixedNormalCoupon.TransactionMinimum)
availableStock := *wechatResp.StockUseRule.MaxCoupons - *wechatResp.DistributedCoupons
reps.AvailableStock = fmt.Sprintf("%d", availableStock)
availableType, err3 := product.AvailableType.GetCmbAvailableType()
if err3 != nil {
return err3
}
reps.AvailableType = availableType.GetValue()
reps.AvailableDays = fmt.Sprintf("%d", product.AvailableDays)
return nil
})
return
}

View File

@ -0,0 +1,33 @@
package kx
// BBToWechatRequest 蓝色兄弟请求微信发券接口数据同步Api
type BBToWechatRequest struct {
// 微信为每个批次分配的唯一id
StockId string `protobuf:"bytes,9,opt,name=stockId,proto3" json:"stockId,omitempty"`
// 商户此次发放凭据号格式商户id+日期+流水号)
OutRequestNo string `protobuf:"bytes,10,opt,name=outRequestNo,proto3" json:"outRequestNo,omitempty"`
// 微信为发券方商户分配的公众账号ID
AppId string `protobuf:"bytes,11,opt,name=appId,proto3" json:"appId,omitempty"`
// 批次创建方商户号
StockCreatorMhId string `protobuf:"bytes,12,opt,name=stockCreatorMhId,json=stockCreatorMchid,proto3" json:"stockCreatorMhId,omitempty"`
// 券面额,单位:分
CouponValue int32 `protobuf:"bytes,13,opt,name=couponValue,proto3" json:"couponValue,omitempty"`
// 面额发券批次门槛,单位:分
CouponMinimum int32 `protobuf:"bytes,14,opt,name=couponMinimum,proto3" json:"couponMinimum,omitempty"`
// 微信为代金券唯一分配的id, 在微信请求失败时可能为空
CouponId string `protobuf:"bytes,15,opt,name=couponId,proto3" json:"couponId,omitempty"`
// 微信返回结果
WxRes string `protobuf:"bytes,16,opt,name=wxRes,proto3" json:"wxRes,omitempty"`
// 招行返回结果
CmbRes string `protobuf:"bytes,17,opt,name=cmbRes,proto3" json:"cmbRes,omitempty"`
// 招行此次请求的数据的唯一流水号
TransactionId string `protobuf:"bytes,18,opt,name=transactionId,proto3" json:"transactionId,omitempty"`
}
func (this *BBToWechatRequest) GetSynNotice() *SynNotice {
return &SynNotice{
OutBizBo: this.TransactionId,
Type: SynNoticeTypeBBToWechat,
BizContent: this,
}
}

View File

@ -0,0 +1,27 @@
package kx
// CmbToBBRequest 招行请求蓝色兄弟发券接口数据同步Api
type CmbToBBRequest struct {
// 唯一流水号
TransactionId string `protobuf:"bytes,9,opt,name=transactionId,proto3" json:"transactionId,omitempty"`
// 外部合作方权益批次号
ActivityId string `protobuf:"bytes,10,opt,name=activityId,proto3" json:"activityId,omitempty"`
// 招商银行用户号 用户标识比如手机号、支付宝openId
CmbUid string `protobuf:"bytes,11,opt,name=cmbUid,proto3" json:"cmbUid,omitempty"`
// 用户标识类型0-手机号1-支付宝openId
CmbUidType string `protobuf:"bytes,12,opt,name=cmbUidType,proto3" json:"cmbUidType,omitempty"`
// 时间戳长度为13位精度为毫秒
Timestamp string `protobuf:"bytes,13,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
// appId
AppId string `protobuf:"bytes,14,opt,name=appId,proto3" json:"appId,omitempty"`
// 补丁
Attach string `protobuf:"bytes,15,opt,name=attach,proto3" json:"attach,omitempty"`
}
func (this *CmbToBBRequest) GetSynNotice() *SynNotice {
return &SynNotice{
OutBizBo: this.TransactionId,
Type: SynNoticeTypeCmbToBB,
BizContent: this,
}
}

38
internal/biz/kx/kx.go Normal file
View File

@ -0,0 +1,38 @@
package kx
type SynNoticeType uint8
const (
SynNoticeTypeCmbToBB SynNoticeType = iota + 1
SynNoticeTypeBBToWechat
SynNoticeTypeWechatToBB
)
var SynNoticeTypeMap = map[SynNoticeType]string{
SynNoticeTypeCmbToBB: "招行请求蓝色兄弟",
SynNoticeTypeBBToWechat: "蓝色兄弟请求微信",
SynNoticeTypeWechatToBB: "微信请求蓝色兄弟",
}
func (s SynNoticeType) GetText() string {
if t, ok := SynNoticeTypeMap[s]; ok {
return t
}
return "未知类型"
}
func (s SynNoticeType) GetValue() uint8 {
return uint8(s)
}
func (s SynNoticeType) IsCmbToBB() bool {
return s == SynNoticeTypeCmbToBB
}
func (s SynNoticeType) IsBBToWechat() bool {
return s == SynNoticeTypeBBToWechat
}
func (s SynNoticeType) IsWechatToBB() bool {
return s == SynNoticeTypeWechatToBB
}

View File

@ -0,0 +1,23 @@
package kx
import (
"encoding/json"
)
var _ SynApiInterface = (*CmbToBBRequest)(nil)
var _ SynApiInterface = (*BBToWechatRequest)(nil)
var _ SynApiInterface = (*WechatToBBRequest)(nil)
type SynApiInterface interface {
GetSynNotice() *SynNotice
}
type SynNotice struct {
OutBizBo string
Type SynNoticeType
BizContent SynApiInterface
}
func (this *SynNotice) Marshal() ([]byte, error) {
return json.Marshal(this)
}

View File

@ -0,0 +1,39 @@
package kx
// WechatToBBRequest 微信回调蓝色兄弟接口数据同步Api
type WechatToBBRequest struct {
// 活动ID
ActivityId string `protobuf:"bytes,9,opt,name=activityId,proto3" json:"activityId,omitempty"`
// 活动名称
ActivityName string `protobuf:"bytes,10,opt,name=activityName,proto3" json:"activityName,omitempty"`
// 优惠券ID
VoucherId string `protobuf:"bytes,11,opt,name=voucherId,proto3" json:"voucherId,omitempty"`
// 领取用户ID
UserId string `protobuf:"bytes,12,opt,name=userId,proto3" json:"userId,omitempty"`
// 核销时间Unix时间戳毫秒
UseTime string `protobuf:"bytes,13,opt,name=useTime,proto3" json:"useTime,omitempty"`
// 核销金额(分)
UseAmount string `protobuf:"bytes,14,opt,name=useAmount,proto3" json:"useAmount,omitempty"`
// 券消息类型例如券核销V_USE,V_REFUND
BizType string `protobuf:"bytes,15,opt,name=bizType,proto3" json:"bizType,omitempty"`
// 退款时间Unix时间戳毫秒
RefundTime string `protobuf:"bytes,16,opt,name=refundTime,proto3" json:"refundTime,omitempty"`
// 退款金额(分)
RefundAmount string `protobuf:"bytes,17,opt,name=refundAmount,proto3" json:"refundAmount,omitempty"`
// 券状态可用ENABLED/不可用DISABLED
VoucherStatus string `protobuf:"bytes,18,opt,name=voucherStatus,proto3" json:"voucherStatus,omitempty"`
// 幂等ID
OrderId string `protobuf:"bytes,19,opt,name=orderId,proto3" json:"orderId,omitempty"`
// 支付宝交易号
TradeNo string `protobuf:"bytes,20,opt,name=tradeNo,proto3" json:"tradeNo,omitempty"`
// 券领取时间Unix时间戳毫秒
GmtVoucherCreate string `protobuf:"bytes,21,opt,name=gmtVoucherCreate,proto3" json:"gmtVoucherCreate,omitempty"`
}
func (this *WechatToBBRequest) GetSynNotice() *SynNotice {
return &SynNotice{
OutBizBo: this.OrderId,
Type: SynNoticeTypeBBToWechat,
BizContent: this,
}
}

View File

@ -0,0 +1,10 @@
package mixrepos
import (
"context"
"voucher/internal/biz/kx"
)
type KxMixRepo interface {
Request(ctx context.Context, req *kx.SynNotice) error
}

View File

@ -0,0 +1,91 @@
package biz
import (
"context"
"fmt"
"github.com/redis/go-redis/v9"
err2 "voucher/api/err"
"voucher/internal/biz/bo"
"voucher/internal/biz/vo"
"voucher/internal/pkg/lock"
)
func (v *VoucherBiz) registerNotifyTag(ctx context.Context, stockCreatorMchID, stockID string) error {
c := vo.WechatNotifyRegisterTagCacheKey.BuildCache([]string{v.bc.WechatNotifyMQ.Tag, stockCreatorMchID, stockID})
_, err := v.rdb.Rdb.Get(ctx, c.Key).Result()
if err == nil {
// 缓存存在,直接返回
return nil
}
if err != redis.Nil {
return fmt.Errorf(fmt.Sprintf("获取redis缓存%s异常:%v", c.Key, err))
}
cl := vo.WechatNotifyRegisterTagCacheLockKey.BuildCache([]string{v.bc.WechatNotifyMQ.Tag, stockCreatorMchID, stockID})
return lock.NewMutex(v.rdb.Rdb, cl.TTL).Lock(ctx, cl.Key, func(ctx context.Context) error {
// 二次获取,判定处理,以免获取锁后又执行了一次
cacheValue, err3 := v.rdb.Rdb.Get(ctx, c.Key).Result()
if err3 != nil && err3 != redis.Nil {
return fmt.Errorf(fmt.Sprintf("二次获取redis缓存%s异常:%v", c.Key, err))
}
if cacheValue != "" {
return nil // 有直接返回
}
wechatNotifyTag, err3 := v.WechatNotifyRegisterTagRepo.GetByStockIdAndMchId(ctx, stockCreatorMchID, stockID)
if err3 != nil && !err2.IsDbNotFound(err3) {
return err3
}
if wechatNotifyTag != nil {
if wechatNotifyTag.Tag != v.bc.WechatNotifyMQ.Tag {
return fmt.Errorf("tag不一致请检查tag配置:%s", wechatNotifyTag.Tag)
}
if wechatNotifyTag.Status.IsSuccess() {
return v.setCache(ctx, c, wechatNotifyTag)
}
} else {
wechatNotifyTag, err3 = v.createWechatNotifyRegisterTag(ctx, stockCreatorMchID, stockID)
if err3 != nil {
return err3
}
}
if err = v.WechatCpnRepo.RegisterNotifyTag(ctx, stockID); err != nil {
return v.WechatNotifyRegisterTagRepo.Fail(ctx, wechatNotifyTag.ID, err.Error())
}
if err = v.WechatNotifyRegisterTagRepo.Success(ctx, wechatNotifyTag.ID); err != nil {
return err
}
return v.setCache(ctx, c, wechatNotifyTag)
})
}
func (v *VoucherBiz) createWechatNotifyRegisterTag(ctx context.Context, stockCreatorMchID, stockID string) (*bo.WechatNotifyRegisterTagBo, error) {
return v.WechatNotifyRegisterTagRepo.Create(ctx, &bo.WechatNotifyRegisterTagBo{
StockID: stockID,
StockCreatorMchID: stockCreatorMchID,
Tag: v.bc.WechatNotifyMQ.Tag,
})
}
func (v *VoucherBiz) setCache(ctx context.Context, c *vo.Cache, wechatNotifyTag *bo.WechatNotifyRegisterTagBo) error {
if err := v.rdb.Rdb.Set(ctx, c.Key, wechatNotifyTag.Tag, c.TTL).Err(); err != nil {
return fmt.Errorf(fmt.Sprintf("设置redis缓存%s异常:%v", c.Key, err))
}
return nil
}

View File

@ -2,22 +2,97 @@ package biz
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"github.com/go-kratos/kratos/v2/errors"
"github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/log"
"github.com/redis/go-redis/v9"
err2 "voucher/api/err" err2 "voucher/api/err"
v1 "voucher/api/v1"
"voucher/internal/biz/bo" "voucher/internal/biz/bo"
"voucher/internal/biz/vo" "voucher/internal/biz/vo"
"voucher/internal/pkg/lock"
) )
func (v *VoucherBiz) order(ctx context.Context, req *bo.OrderCreateReqBo, product *bo.ProductBo) (*bo.OrderBo, error) { func (c *VoucherBiz) CmbOrder(ctx context.Context, request *v1.CmbRequest) (*v1.CmbReply, error) {
order, err := c.cmbOrder(ctx, request)
if err != nil {
return c.OrderFail(ctx, err)
}
return c.OrderSuccess(ctx, order.OrderNo)
}
func (c *VoucherBiz) cmbOrder(ctx context.Context, request *v1.CmbRequest) (*bo.OrderBo, error) {
bizContent, err := c.CmbMixRepo.OrderVerify(ctx, request)
if err != nil {
return nil, err
}
ctx2 := context.Background()
boReq := &bo.OrderCreateReqBo{
OutBizNo: bizContent.TransactionId,
ProductNo: bizContent.ActivityId,
Account: bizContent.CmbUid,
AppID: bizContent.AppId,
Attach: bizContent.Attach,
AccountType: vo.OrderAccountTypeOpenId,
Type: vo.OrderTypeCmb,
NotifyUrl: c.bc.Cmb.NotifyUrl,
}
order, err := c.Order(ctx2, boReq, bizContent)
if err != nil {
return nil, err
}
return order, nil
}
func (v *VoucherBiz) Order(ctx context.Context, req *bo.OrderCreateReqBo, cmbReq *v1.CmbOrderRequest) (order *bo.OrderBo, err error) {
order, err = v.OrderRepo.GetByOutBizNo(ctx, vo.OrderTypeCmb, req.OutBizNo)
if err != nil && !err2.IsDbNotFound(err) {
return order, err
}
if order != nil {
if order.Status.IsFail() {
if err4 := v.orderRetry(ctx, order); err4 != nil {
return order, err4
}
}
return order, err
}
product, err3 := v.ProductRepo.GetByProductNo(ctx, req.ProductNo)
if err3 != nil {
return order, err3
}
order, err = v.order(ctx, req, product, cmbReq)
if err != nil {
return order, err
}
return order, nil
}
func (v *VoucherBiz) order(ctx context.Context, req *bo.OrderCreateReqBo, product *bo.ProductBo, cmbReq *v1.CmbOrderRequest) (*bo.OrderBo, error) {
order, err := v.create(ctx, req, product) order, err := v.create(ctx, req, product)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// 通知kx
// 注册通知标签 order.MerchantNo 批次创建商户, order.BatchNo 商品批次号 // 注册通知标签 order.MerchantNo 批次创建商户, order.BatchNo 商品批次号
if err = v.registerNotifyTag(ctx, order.MerchantNo, order.BatchNo); err != nil { if err = v.registerNotifyTag(ctx, order.MerchantNo, order.BatchNo); err != nil {
return nil, err return nil, err
@ -70,91 +145,12 @@ func (v *VoucherBiz) create(ctx context.Context, req *bo.OrderCreateReqBo, produ
return v.OrderRepo.Create(ctx, o) return v.OrderRepo.Create(ctx, o)
} }
func (v *VoucherBiz) registerNotifyTag(ctx context.Context, stockCreatorMchID, stockID string) error {
c := vo.WechatNotifyRegisterTagCacheKey.BuildCache([]string{v.bc.WechatNotifyMQ.Tag, stockCreatorMchID, stockID})
_, err := v.rdb.Rdb.Get(ctx, c.Key).Result()
if err == nil {
// 缓存存在,直接返回
return nil
}
if err != redis.Nil {
return fmt.Errorf(fmt.Sprintf("获取redis缓存%s异常:%v", c.Key, err))
}
cl := vo.WechatNotifyRegisterTagCacheLockKey.BuildCache([]string{v.bc.WechatNotifyMQ.Tag, stockCreatorMchID, stockID})
return lock.NewMutex(v.rdb.Rdb, cl.TTL).Lock(ctx, cl.Key, func(ctx context.Context) error {
// 二次获取,判定处理,以免获取锁后又执行了一次
cacheValue, err3 := v.rdb.Rdb.Get(ctx, c.Key).Result()
if err3 != nil && err3 != redis.Nil {
return fmt.Errorf(fmt.Sprintf("二次获取redis缓存%s异常:%v", c.Key, err))
}
if cacheValue != "" {
return nil // 有直接返回
}
wechatNotifyTag, err3 := v.WechatNotifyRegisterTagRepo.GetByStockIdAndMchId(ctx, stockCreatorMchID, stockID)
if err3 != nil && !err2.IsDbNotFound(err3) {
return err3
}
if wechatNotifyTag != nil {
if wechatNotifyTag.Tag != v.bc.WechatNotifyMQ.Tag {
return fmt.Errorf("tag不一致请检查tag配置:%s", wechatNotifyTag.Tag)
}
if wechatNotifyTag.Status.IsSuccess() {
return v.setCache(ctx, c, wechatNotifyTag)
}
} else {
wechatNotifyTag, err3 = v.createWechatNotifyRegisterTag(ctx, stockCreatorMchID, stockID)
if err3 != nil {
return err3
}
}
if err = v.WechatCpnRepo.RegisterNotifyTag(ctx, stockID); err != nil {
return v.WechatNotifyRegisterTagRepo.Fail(ctx, wechatNotifyTag.ID, err.Error())
}
if err = v.WechatNotifyRegisterTagRepo.Success(ctx, wechatNotifyTag.ID); err != nil {
return err
}
return v.setCache(ctx, c, wechatNotifyTag)
})
}
func (v *VoucherBiz) createWechatNotifyRegisterTag(ctx context.Context, stockCreatorMchID, stockID string) (*bo.WechatNotifyRegisterTagBo, error) {
return v.WechatNotifyRegisterTagRepo.Create(ctx, &bo.WechatNotifyRegisterTagBo{
StockID: stockID,
StockCreatorMchID: stockCreatorMchID,
Tag: v.bc.WechatNotifyMQ.Tag,
})
}
func (v *VoucherBiz) setCache(ctx context.Context, c *vo.Cache, wechatNotifyTag *bo.WechatNotifyRegisterTagBo) error {
if err := v.rdb.Rdb.Set(ctx, c.Key, wechatNotifyTag.Tag, c.TTL).Err(); err != nil {
return fmt.Errorf(fmt.Sprintf("设置redis缓存%s异常:%v", c.Key, err))
}
return nil
}
func (v *VoucherBiz) ing(ctx context.Context, id uint64) error { func (v *VoucherBiz) ing(ctx context.Context, id uint64) error {
return v.OrderRepo.Ing(ctx, id) return v.OrderRepo.Ing(ctx, id)
} }
func (v *VoucherBiz) success(ctx context.Context, order *bo.OrderBo, voucherNo string) error { func (v *VoucherBiz) success(ctx context.Context, order *bo.OrderBo, voucherNo string) error {
order.VoucherNo = voucherNo
return v.OrderRepo.Success(ctx, order.ID, voucherNo) return v.OrderRepo.Success(ctx, order.ID, voucherNo)
} }
@ -171,115 +167,37 @@ func (v *VoucherBiz) fail(ctx context.Context, order *bo.OrderBo, errReq error)
return v.alarm(ctx, order, errReq.Error()) return v.alarm(ctx, order, errReq.Error())
} }
func (v *VoucherBiz) alarm(ctx context.Context, order *bo.OrderBo, errMsg string) error { func (c *VoucherBiz) OrderSuccess(ctx context.Context, orderNo string) (*v1.CmbReply, error) {
// 1小时 内 指定的批次号 发放 发生错误 预警 bizReply := &v1.CmbOrderReply{
c := vo.OrderConsumeFailAlarmKey.BuildCache([]string{order.ProductNo}) RespCode: vo.CmbResponseStatusSuccess.GetValue(),
RespMsg: "成功",
_, err := v.rdb.Rdb.Get(ctx, c.Key).Result() CodeNo: orderNo,
if err == nil {
// 缓存存在,直接返回
return nil
} }
if err != redis.Nil { replyBizContent, _ := json.Marshal(bizReply)
return fmt.Errorf(fmt.Sprintf("alarm 获取redis缓存%s异常:%v", c.Key, err))
}
cl := vo.OrderConsumeFailAlarmLockKey.BuildCache([]string{order.ProductNo}) return c.GetResponse(ctx, replyBizContent)
return lock.NewMutex(v.rdb.Rdb, cl.TTL).Lock(ctx, cl.Key, func(ctx context.Context) error {
// 二次获取,判定处理,以免获取锁后又执行了一次
cacheValue, err3 := v.rdb.Rdb.Get(ctx, c.Key).Result()
if err3 != nil && err3 != redis.Nil {
return fmt.Errorf(fmt.Sprintf("alarm 二次获取redis缓存%s异常:%v", c.Key, err))
}
if len(cacheValue) > 0 {
return nil // 有直接返回
}
// 通知
if err = v.DingMixRepo.SendMarkdownMessage(ctx, "异常通知", v.alarmText(ctx, order, errMsg)); err != nil {
return err
}
if err = v.rdb.Rdb.Set(ctx, c.Key, order.ProductNo, c.TTL).Err(); err != nil {
return fmt.Errorf(fmt.Sprintf("设置redis缓存%s异常:%v", c.Key, err))
}
return nil
})
} }
func (v *VoucherBiz) alarmText(_ context.Context, order *bo.OrderBo, errMsg string) string { func (c *VoucherBiz) OrderFail(ctx context.Context, err error) (*v1.CmbReply, error) {
remarks := fmt.Sprintf("订单号:%s商品编号:%s,原因:%s", order.OrderNo, order.ProductNo, errMsg) se := errors.FromError(err)
msg := "# <font color='green'>" + if len(se.Reason) == 0 {
"<h1>立减金发放平台报警通知</h1>" + se.Reason = err2.CmbErr_CMB_UNKNOWN.String()
"</font> \n" + }
"<font color='black'>" +
"不好了,订单发放发生异常了" +
"[<font color='red'>%s</font>]请尽快处理@相关人员。" +
"</font>"
return fmt.Sprintf(msg, remarks) log.Errorf("order fail: %v", se)
}
bizReply := &v1.CmbOrderReply{
func (v *VoucherBiz) Query(ctx context.Context, order *bo.OrderBo) error { RespCode: vo.CmbResponseStatusFail.GetValue(),
RespMsg: se.Message,
status, err := v.WechatCpnRepo.Query(ctx, order) CodeNo: "",
if err != nil { ThirdErrCode: se.Reason,
return err }
}
replyBizContent, _ := json.Marshal(bizReply)
if order.Status == status {
log.Warnf("券状态未改变:%s忽略不处理,orderNo:%s", order.Status.GetText(), order.OrderNo) return c.GetResponse(ctx, replyBizContent)
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())
}
func (v *VoucherBiz) QueryOrder(ctx context.Context, orderNo string) (string, error) {
order, err3 := v.OrderRepo.GetByOrderNo(ctx, orderNo)
if err3 != nil {
return "", err3
}
status, err := v.WechatCpnRepo.Query(ctx, order)
if err != nil {
return "", err
}
return fmt.Sprintf("orderNo:%s,订单状态:%s,微信查询返回状态:%s", orderNo, order.Status.GetText(), status.GetText()), nil
} }

80
internal/biz/product.go Normal file
View File

@ -0,0 +1,80 @@
package biz
import (
"context"
"fmt"
"time"
v1 "voucher/api/v1"
"voucher/internal/biz/vo"
"voucher/internal/pkg/lock"
)
func (v *VoucherBiz) CmbProductQuery(ctx context.Context, productNo string) (reps *v1.CmbQueryProductReply, err error) {
c := vo.CmbProductQueryLockKey.BuildCache([]string{productNo})
err = lock.NewMutex(v.rdb.Rdb, c.TTL).Lock(ctx, c.Key, func(ctx context.Context) error {
product, err3 := v.ProductRepo.GetByProductNo(ctx, productNo)
if err3 != nil {
return err3
}
if !product.Channel.IsWeChat() {
return fmt.Errorf("只支持微信")
}
wechatResp, err4 := v.WechatCpnRepo.QueryProduct(ctx, product.MchId, product.BatchNo)
if err4 != nil {
return err4
}
reps = &v1.CmbQueryProductReply{
RespCode: vo.CmbResponseStatusSuccess.GetValue(),
RespMsg: "成功",
ActivityName: product.Name,
ActivityId: product.ProductNo,
Amount: "",
MinAmount: "",
AvailableType: "",
AvailableDays: "", // 动态有效期天数
StartTime: "",
EndTime: "",
AvailableStock: "",
Detail: *wechatResp.Description,
}
inputFormat := time.RFC3339
if wechatResp.AvailableBeginTime != nil {
availableBeginTime, _ := time.Parse(inputFormat, *wechatResp.AvailableBeginTime)
reps.StartTime = availableBeginTime.Format("2006-01-02 15:04:05.000")
reps.SaleStartTime = reps.StartTime
}
if wechatResp.AvailableEndTime != nil {
availableEndTime, _ := time.Parse(inputFormat, *wechatResp.AvailableEndTime)
reps.EndTime = availableEndTime.Format("2006-01-02 15:04:05.000")
reps.SaleEndTime = reps.EndTime
}
reps.Amount = fmt.Sprintf("%d", *wechatResp.StockUseRule.FixedNormalCoupon.CouponAmount)
reps.MinAmount = fmt.Sprintf("%d", *wechatResp.StockUseRule.FixedNormalCoupon.TransactionMinimum)
availableStock := *wechatResp.StockUseRule.MaxCoupons - *wechatResp.DistributedCoupons
reps.AvailableStock = fmt.Sprintf("%d", availableStock)
availableType, err3 := product.AvailableType.GetCmbAvailableType()
if err3 != nil {
return err3
}
reps.AvailableType = availableType.GetValue()
reps.AvailableDays = fmt.Sprintf("%d", product.AvailableDays)
return nil
})
return
}

100
internal/biz/query.go Normal file
View File

@ -0,0 +1,100 @@
package biz
import (
"context"
"fmt"
"github.com/go-kratos/kratos/v2/log"
"time"
v1 "voucher/api/v1"
"voucher/internal/biz/bo"
"voucher/internal/biz/vo"
"voucher/internal/pkg/lock"
)
func (v *VoucherBiz) CmbQuery(ctx context.Context, orderNo string) (resp *v1.CmbQueryReply, err error) {
c := vo.CmbQueryLockKey.BuildCache([]string{orderNo})
err = lock.NewMutex(v.rdb.Rdb, c.TTL).Lock(ctx, c.Key, func(ctx context.Context) error {
order, err3 := v.OrderRepo.GetByOrderNo(ctx, orderNo)
if err3 != nil {
return err3
}
if err = v.Query(ctx, order); err != nil {
return err
}
status, err3 := order.Status.GetCmbStatusText()
if err3 != nil {
return err3
}
resp = &v1.CmbQueryReply{
Ticket: order.OrderNo,
Status: status.GetValue(),
TransDate: time.Now().Format("20060102150405"),
OrgNo: v.bc.Cmb.OrgNo,
Ext: "",
}
return nil
})
return
}
func (v *VoucherBiz) Query(ctx context.Context, order *bo.OrderBo) error {
status, err := v.WechatCpnRepo.Query(ctx, order)
if err != nil {
return err
}
if order.Status == status {
log.Warnf("券状态未改变:%s忽略不处理,orderNo:%s", order.Status.GetText(), order.OrderNo)
return nil
}
if err = v.UpdateOrderStatus(ctx, order.ID, status); err != nil {
return err
}
order.Status = status
return nil
}
func (v *VoucherBiz) 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())
}
func (v *VoucherBiz) QueryOrder(ctx context.Context, orderNo string) (string, error) {
order, err3 := v.OrderRepo.GetByOrderNo(ctx, orderNo)
if err3 != nil {
return "", err3
}
status, err := v.WechatCpnRepo.Query(ctx, order)
if err != nil {
return "", err
}
return fmt.Sprintf("orderNo:%s,订单状态:%s,微信查询返回状态:%s", orderNo, order.Status.GetText(), status.GetText()), nil
}

View File

@ -1,9 +1,14 @@
package biz package biz
import ( import (
"context"
"github.com/go-kratos/kratos/v2/log"
v1 "voucher/api/v1"
"voucher/internal/biz/bo"
"voucher/internal/biz/cmb" "voucher/internal/biz/cmb"
"voucher/internal/biz/mixrepos" "voucher/internal/biz/mixrepos"
"voucher/internal/biz/repo" "voucher/internal/biz/repo"
"voucher/internal/biz/vo"
"voucher/internal/biz/wechatrepo" "voucher/internal/biz/wechatrepo"
"voucher/internal/conf" "voucher/internal/conf"
"voucher/internal/data" "voucher/internal/data"
@ -22,6 +27,7 @@ type VoucherBiz struct {
WechatCpnRepo wechatrepo.WechatCpnRepo WechatCpnRepo wechatrepo.WechatCpnRepo
DingMixRepo mixrepos.DingMixRepo DingMixRepo mixrepos.DingMixRepo
CmbMixRepo mixrepos.CmbMixRepo CmbMixRepo mixrepos.CmbMixRepo
KxMixRepo mixrepos.KxMixRepo
} }
func NewVoucherBiz( func NewVoucherBiz(
@ -37,6 +43,7 @@ func NewVoucherBiz(
WechatCpnRepo wechatrepo.WechatCpnRepo, WechatCpnRepo wechatrepo.WechatCpnRepo,
DingMixRepo mixrepos.DingMixRepo, DingMixRepo mixrepos.DingMixRepo,
CmbMixRepo mixrepos.CmbMixRepo, CmbMixRepo mixrepos.CmbMixRepo,
KxMixRepo mixrepos.KxMixRepo,
) *VoucherBiz { ) *VoucherBiz {
return &VoucherBiz{ return &VoucherBiz{
bc: bc, bc: bc,
@ -51,5 +58,23 @@ func NewVoucherBiz(
WechatCpnRepo: WechatCpnRepo, WechatCpnRepo: WechatCpnRepo,
DingMixRepo: DingMixRepo, DingMixRepo: DingMixRepo,
CmbMixRepo: CmbMixRepo, CmbMixRepo: CmbMixRepo,
KxMixRepo: KxMixRepo,
} }
} }
func (c *VoucherBiz) GetResponse(ctx context.Context, replyBizContent []byte) (*v1.CmbReply, error) {
req := &bo.CmbResponseBo{
RespCode: vo.CmbResponseStatusSuccess.GetValue(),
RespMsg: "成功",
BizContent: string(replyBizContent),
}
reply, err := c.CmbMixRepo.GetResponse(ctx, req)
if err != nil {
log.Errorf("build cmb response fail: %v", err)
return nil, err
}
return reply, nil
}

View File

@ -0,0 +1,58 @@
package mixrepoimpl
import (
"context"
"encoding/json"
"fmt"
"github.com/go-kratos/kratos/v2/log"
"net/http"
"time"
v1 "voucher/api/v1"
"voucher/internal/biz/kx"
"voucher/internal/biz/mixrepos"
"voucher/internal/biz/vo"
"voucher/internal/conf"
"voucher/internal/pkg/request"
)
type KxMixRepoImpl struct {
bc *conf.Bootstrap
}
func NewKxMixRepoImpl(bc *conf.Bootstrap) mixrepos.KxMixRepo {
return &KxMixRepoImpl{bc: bc}
}
func (s *KxMixRepoImpl) Request(ctx context.Context, req *kx.SynNotice) error {
body, err := req.Marshal()
if err != nil {
log.Errorf("请求掌上生活Marshal报错:%s", err.Error())
return err
}
h := http.Header{
"Content-Type": []string{"application/x-www-form-urlencoded"},
}
url := ""
_, bodyBytes, err := request.Post(ctx, url, body, request.WithHeaders(h), request.WithTimeout(time.Second*20))
if err != nil {
log.Errorf("请求kx报错,url:%s,err:%v", url, err)
return err
}
var response *v1.CmbReply
if err = json.Unmarshal(bodyBytes, &response); err != nil {
log.Errorf("请求kx返回数据解析报错:%s,url:%s,bodyBytes:%s", err.Error(), url, string(bodyBytes))
return err
}
if response.RespCode != vo.CmbResponseStatusSuccess.GetValue() {
log.Errorf("请求kx返回报错:msg:%s,url:%s,bodyBytes:%s", response.RespMsg, url, string(bodyBytes))
return fmt.Errorf(response.RespMsg)
}
return nil
}

View File

@ -10,4 +10,5 @@ var ProviderMixRepoImplSet = wire.NewSet(
NewMQSendMixRepoImpl, NewMQSendMixRepoImpl,
NewCmbMixRepoImpl, NewCmbMixRepoImpl,
NewDingMixRepoImpl, NewDingMixRepoImpl,
NewKxMixRepoImpl,
) )

View File

@ -2,85 +2,10 @@ package service
import ( import (
"context" "context"
"encoding/json"
"github.com/go-kratos/kratos/v2/errors"
"github.com/go-kratos/kratos/v2/log"
err2 "voucher/api/err"
v1 "voucher/api/v1" v1 "voucher/api/v1"
"voucher/internal/biz/bo"
"voucher/internal/biz/vo"
) )
func (c *CmbService) OrderSuccess(ctx context.Context, orderNo string) (*v1.CmbReply, error) {
bizReply := &v1.CmbOrderReply{
RespCode: vo.CmbResponseStatusSuccess.GetValue(),
RespMsg: "成功",
CodeNo: orderNo,
}
replyBizContent, _ := json.Marshal(bizReply)
return c.GetResponse(ctx, replyBizContent)
}
func (c *CmbService) OrderFail(ctx context.Context, err error) (*v1.CmbReply, error) {
se := errors.FromError(err)
if len(se.Reason) == 0 {
se.Reason = err2.CmbErr_CMB_UNKNOWN.String()
}
log.Errorf("order fail: %v", se)
bizReply := &v1.CmbOrderReply{
RespCode: vo.CmbResponseStatusFail.GetValue(),
RespMsg: se.Message,
CodeNo: "",
ThirdErrCode: se.Reason,
}
replyBizContent, _ := json.Marshal(bizReply)
return c.GetResponse(ctx, replyBizContent)
}
func (c *CmbService) Order(ctx context.Context, request *v1.CmbRequest) (*v1.CmbReply, error) { func (c *CmbService) Order(ctx context.Context, request *v1.CmbRequest) (*v1.CmbReply, error) {
orderNo, err := c.order(ctx, request) return c.VoucherBiz.CmbOrder(ctx, request)
if err != nil {
return c.OrderFail(ctx, err)
}
return c.OrderSuccess(ctx, orderNo)
}
func (c *CmbService) order(ctx context.Context, request *v1.CmbRequest) (string, error) {
bizContent, err := c.CmbMixRepo.OrderVerify(ctx, request)
if err != nil {
return "", err
}
ctx2 := context.Background()
boReq := &bo.OrderCreateReqBo{
OutBizNo: bizContent.TransactionId,
ProductNo: bizContent.ActivityId,
Account: bizContent.CmbUid,
AppID: bizContent.AppId,
Attach: bizContent.Attach,
AccountType: vo.OrderAccountTypeOpenId,
Type: vo.OrderTypeCmb,
NotifyUrl: c.bc.Cmb.NotifyUrl,
}
orderNo, err := c.VoucherBiz.CmbOrder(ctx2, boReq)
if err != nil {
return "", err
}
return orderNo, nil
} }