diff --git a/api/amqp/consumer/CancelUnPayOrder.go b/api/amqp/consumer/CancelUnPayOrder.go new file mode 100644 index 0000000..e2a1a51 --- /dev/null +++ b/api/amqp/consumer/CancelUnPayOrder.go @@ -0,0 +1,102 @@ +package consumer + +import ( + "encoding/json" + "github.com/rabbitmq/amqp091-go" + "hepa-calc-api/api/service" + "hepa-calc-api/extend/weChat" + "hepa-calc-api/global" + "hepa-calc-api/utils" + "strconv" +) + +type cancelUnPayOrderData struct { + OrderId string `json:"order_id"` + OrderNo string `json:"order_no"` + UserId string `json:"user_id"` + OrderType int `json:"order_type"` // 订单类型(1:单项 2:会员) + PayChannel int `json:"pay_channel"` // 支付渠道(1:h5支付 2:app支付 3:会员支付) +} + +// CancelUnPayOrder 取消未支付订单 +func CancelUnPayOrder(msg amqp091.Delivery) { + defer func() { + if r := recover(); r != nil { + utils.LogJsonInfo("consumer.CancelUnPayOrder:", r) + _ = msg.Reject(false) + } + }() + + // 记录日志 + utils.LogJsonInfo("consumer.CancelUnPayOrder:", string(msg.Body)) + if msg.Body == nil { + _ = msg.Ack(false) + return + } + + // 解析JSON字符串到结构体 + var data cancelUnPayOrderData + err := json.Unmarshal(msg.Body, &data) + if err != nil { + _ = msg.Ack(false) + return + } + + orderId, err := strconv.ParseInt(data.OrderId, 10, 64) + if err != nil { + _ = msg.Ack(false) + return + } + + userId, err := strconv.ParseInt(data.UserId, 10, 64) + if err != nil { + _ = msg.Ack(false) + return + } + + // 开始事务 + tx := global.Db.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + utils.LogJsonErr("consumer.CouponExpire:", r) + _ = msg.Reject(false) + } + }() + + // 单项订单 + if data.OrderType == 1 { + orderSingleService := service.OrderSingleService{} + res, err := orderSingleService.CancelOrderSingle(tx, userId, orderId, 3) + if err != nil { + tx.Rollback() + utils.LogJsonErr("consumer.CouponExpire:", err) + _ = msg.Reject(true) + return + } + + if res == false { + tx.Rollback() + utils.LogJsonErr("consumer.CouponExpire:", "取消失败") + _ = msg.Reject(true) + return + } + } + + // 关闭支付订单 + if data.PayChannel == 1 { + // h5支付 + err = weChat.CloseJsapiOrder(data.OrderNo) + utils.LogJsonErr("consumer.CouponExpire:", err) + } + + if data.PayChannel == 2 { + // app支付 + err = weChat.CloseAppOrder(data.OrderNo) + utils.LogJsonErr("consumer.CouponExpire:", err) + } + + tx.Commit() + + _ = msg.Ack(false) +} diff --git a/api/amqp/consumer/CouponExpire.go b/api/amqp/consumer/CouponExpire.go new file mode 100644 index 0000000..b247f2e --- /dev/null +++ b/api/amqp/consumer/CouponExpire.go @@ -0,0 +1,123 @@ +package consumer + +import ( + "encoding/json" + "fmt" + "github.com/rabbitmq/amqp091-go" + "hepa-calc-api/api/dao" + "hepa-calc-api/extend/rabbitMq" + "hepa-calc-api/global" + "hepa-calc-api/utils" + "strconv" + "time" +) + +type couponExpireData struct { + CouponId string `json:"coupon_id"` +} + +// CouponExpire 优惠卷过期 +func CouponExpire(msg amqp091.Delivery) { + defer func() { + if r := recover(); r != nil { + utils.LogJsonInfo("consumer.CouponExpire:", r) + _ = msg.Reject(false) + } + }() + + // 记录日志 + utils.LogJsonInfo("consumer.CouponExpire:", string(msg.Body)) + if msg.Body == nil { + _ = msg.Ack(false) + return + } + + // 解析JSON字符串到结构体 + var data couponExpireData + err := json.Unmarshal(msg.Body, &data) + if err != nil { + _ = msg.Ack(false) + return + } + + couponId, err := strconv.ParseInt(data.CouponId, 10, 64) + if err != nil { + _ = msg.Ack(false) + return + } + + // 获取优惠卷数据 + couponDao := dao.CouponDao{} + coupon, err := couponDao.GetCouponById(couponId) + if err != nil || coupon == nil { + utils.LogJsonInfo("consumer.CouponExpire:", "无优惠卷数据") + _ = msg.Reject(false) + return + } + + // 检测优惠卷状态-状态(1:正常 2:强制失效 3:结束 4:删除) + if coupon.CouponStatus != 1 { + // 正常,无需处理 + _ = msg.Ack(false) + return + } + + // 检测优惠卷过期类型 + if coupon.ValidType == 2 { + // 正常,无需处理 + _ = msg.Ack(false) + return + } + + // 检测优惠卷过期时间 + now := time.Now() + validEndTime := time.Time(coupon.ValidEndTime) + diffTime := validEndTime.Sub(now) + if diffTime >= 60*time.Second { + // 重新添加入队列 + data := make(map[string]interface{}) + data["coupon_id"] = fmt.Sprintf("%d", coupon.CouponId) + p := rabbitMq.PublishS{ + QueueName: "coupon.expired.delay.queue", + ExchangeName: "amqp.delay.direct", + RoutingKey: "CouponExpired", + Message: data, + Delay: diffTime, + } + err = p.PublishWithDelay() + if err != nil { + utils.LogJsonErr("consumer.CouponExpire:", err.Error()) + // 重回队列 + _ = msg.Reject(true) + return + } + + _ = msg.Ack(false) + return + } + + // 开始事务 + tx := global.Db.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + utils.LogJsonErr("consumer.CouponExpire:", r) + _ = msg.Reject(false) + } + }() + + // 修改用户优惠卷表 + couponData := make(map[string]interface{}) + couponData["coupon_status"] = 3 + err = couponDao.EditCouponById(tx, couponId, couponData) + if err != nil { + tx.Rollback() + utils.LogJsonErr("consumer.CouponExpire:", err.Error()) + _ = msg.Reject(false) + return + } + + tx.Commit() + + _ = msg.Ack(false) +} diff --git a/api/amqp/consumer/UserCouponExpire.go b/api/amqp/consumer/UserCouponExpire.go index ab9df72..4492625 100644 --- a/api/amqp/consumer/UserCouponExpire.go +++ b/api/amqp/consumer/UserCouponExpire.go @@ -12,7 +12,7 @@ import ( "time" ) -type UserCouponExpireData struct { +type userCouponExpireData struct { UserCouponId string `json:"user_coupon_id"` } @@ -33,7 +33,7 @@ func UserCouponExpire(msg amqp091.Delivery) { } // 解析JSON字符串到结构体 - var data UserCouponExpireData + var data userCouponExpireData err := json.Unmarshal(msg.Body, &data) if err != nil { _ = msg.Ack(false) @@ -117,5 +117,5 @@ func UserCouponExpire(msg amqp091.Delivery) { tx.Commit() - err = msg.Ack(false) + _ = msg.Ack(false) } diff --git a/api/amqp/consumer/base.go b/api/amqp/consumer/base.go new file mode 100644 index 0000000..1931fdc --- /dev/null +++ b/api/amqp/consumer/base.go @@ -0,0 +1,5 @@ +package consumer + +func checkHandleNumber(key string) { + +} diff --git a/api/controller/OrderSingle.go b/api/controller/OrderSingle.go index 05c4472..ff0e40c 100644 --- a/api/controller/OrderSingle.go +++ b/api/controller/OrderSingle.go @@ -168,7 +168,7 @@ func (b *OrderSingle) PutCancelOrderSingle(c *gin.Context) { // 取消单项订单 orderSingleService := service.OrderSingleService{} - res, err := orderSingleService.PutCancelOrderSingle(tx, userId, orderId, 1) + res, err := orderSingleService.CancelOrderSingle(tx, userId, orderId, 1) if err != nil { tx.Rollback() responses.FailWithMessage(err.Error(), c) diff --git a/api/crontab/SystemCouponExpire.go b/api/crontab/SystemCouponExpire.go index b547c84..7edf7d8 100644 --- a/api/crontab/SystemCouponExpire.go +++ b/api/crontab/SystemCouponExpire.go @@ -1,8 +1,10 @@ package crontab import ( + "fmt" "hepa-calc-api/api/dao" "hepa-calc-api/api/model" + "hepa-calc-api/extend/rabbitMq" "hepa-calc-api/utils" "time" ) @@ -19,12 +21,27 @@ func SystemCouponExpire() { // 计算过期时间 validEndTime := time.Time(coupon.ValidEndTime) - diffTime := validEndTime.Sub(time.Now()) - if diffTime < 5*time.Second { - diffTime = 5 * time.Second + delay := validEndTime.Sub(time.Now()) + if delay < 5*time.Second { + delay = 5 * time.Second } // 添加处理优惠卷过期队列 + data := make(map[string]interface{}) + data["coupon_id"] = fmt.Sprintf("%d", coupon.CouponId) + + p := rabbitMq.PublishS{ + QueueName: "coupon.expired.delay.queue", + ExchangeName: "amqp.delay.direct", + RoutingKey: "CouponExpired", + Message: data, + Delay: delay, + } + err := p.PublishWithDelay() + if err != nil { + utils.LogJsonErr("添加处理优惠卷过期队列失败:", err.Error()) + return + } } } diff --git a/api/crontab/UserCouponExpire.go b/api/crontab/UserCouponExpire.go index 9f4c95c..db49374 100644 --- a/api/crontab/UserCouponExpire.go +++ b/api/crontab/UserCouponExpire.go @@ -42,10 +42,6 @@ func UserCouponExpire() { utils.LogJsonErr("添加处理用户优惠卷过期队列失败:", err.Error()) return } - - fmt.Println(userCoupon.UserCouponId) - now := time.Now().Format("2006-01-02 15:04:05") - fmt.Println("添加队列成功" + now) } } diff --git a/api/service/OrderMember.go b/api/service/OrderMember.go index 78c7bb1..822adb5 100644 --- a/api/service/OrderMember.go +++ b/api/service/OrderMember.go @@ -187,7 +187,7 @@ func (r *OrderMemberService) AddOrderMember(tx *gorm.DB, UserId, SystemMemberId // cancelReason:订单取消原因(1:主动取消 2:后台取消 3:支付超时取消) func (r *OrderMemberService) PutCancelOrderMember(tx *gorm.DB, userId, orderId int64, cancelReason int) (bool, error) { // 检测多次请求 - redisKey := "PutCancelOrderSingle" + fmt.Sprintf("%d", userId) + fmt.Sprintf("%d", orderId) + redisKey := "CancelOrderSingle" + fmt.Sprintf("%d", userId) + fmt.Sprintf("%d", orderId) res, _ := global.Redis.Get(context.Background(), redisKey).Result() if res != "" { return false, errors.New("请勿重复操作") diff --git a/api/service/OrderSingle.go b/api/service/OrderSingle.go index 749d410..9974265 100644 --- a/api/service/OrderSingle.go +++ b/api/service/OrderSingle.go @@ -10,6 +10,7 @@ import ( "hepa-calc-api/api/dao" "hepa-calc-api/api/model" "hepa-calc-api/config" + "hepa-calc-api/extend/rabbitMq" "hepa-calc-api/extend/weChat" "hepa-calc-api/global" "hepa-calc-api/utils" @@ -171,19 +172,43 @@ func (r *OrderSingleService) AddOrderSingle(tx *gorm.DB, UserId, QuestionId int6 } } - // 增加至未支付取消订单延迟队列 + // 增加未支付取消订单延迟队列 if payChannel == 1 || payChannel == 2 { + delay := 30 * time.Minute + if config.C.Env == "dev" { + delay = 3 * time.Minute + } + + data := make(map[string]interface{}) + data["order_id"] = fmt.Sprintf("%d", orderSingle.OrderId) + data["order_no"] = orderSingle.OrderNo + data["user_id"] = fmt.Sprintf("%d", orderSingle.UserId) + data["order_type"] = 1 + data["pay_channel"] = orderSingle.PayChannel + + p := rabbitMq.PublishS{ + QueueName: "cancel.unpay.order.delay.queue", + ExchangeName: "amqp.delay.direct", + RoutingKey: "CancelUnPayOrder", + Message: data, + Delay: delay, + } + err := p.PublishWithDelay() + if err != nil { + utils.LogJsonErr("添加处理取消未支付订单队列失败:", err.Error()) + return nil, errors.New("订单创建失败") + } } return orderSingle, nil } -// PutCancelOrderSingle 取消单项订单 +// CancelOrderSingle 取消单项订单 // cancelReason:订单取消原因(1:主动取消 2:后台取消 3:支付超时取消) -func (r *OrderSingleService) PutCancelOrderSingle(tx *gorm.DB, userId, orderId int64, cancelReason int) (bool, error) { +func (r *OrderSingleService) CancelOrderSingle(tx *gorm.DB, userId, orderId int64, cancelReason int) (bool, error) { // 检测多次请求 - redisKey := "PutCancelOrderSingle" + fmt.Sprintf("%d", userId) + fmt.Sprintf("%d", orderId) + redisKey := "CancelOrderSingle" + fmt.Sprintf("%d", userId) + fmt.Sprintf("%d", orderId) res, _ := global.Redis.Get(context.Background(), redisKey).Result() if res != "" { return false, errors.New("请勿重复操作") @@ -373,6 +398,13 @@ func (r *OrderSingleService) CompleteUnPayOrderSingle(tx *gorm.DB, userId int64) if err != nil { return false, err } + + // 增加题目支付次数 + questionDao := dao.QuestionDao{} + err = questionDao.Inc(tx, single.QuestionId, "pay_count", 1) + if err != nil { + return false, err + } } return true, nil diff --git a/core/cron.go b/core/cron.go index 8ed59bc..4fa4366 100644 --- a/core/cron.go +++ b/core/cron.go @@ -10,13 +10,13 @@ func StartCron() { c := cron.New(cron.WithSeconds()) // 系统优惠卷过期 - //_, err := c.AddFunc("* * * * * *", crontab.SystemCouponExpire) - //if err != nil { - // panic("定时器启动失败:" + err.Error()) - //} + _, err := c.AddFunc("0 0 0 * * *", crontab.SystemCouponExpire) + if err != nil { + panic("定时器启动失败:" + err.Error()) + } // 用户优惠卷过期 - _, err := c.AddFunc("0 0 0 * * *", crontab.UserCouponExpire) + _, err = c.AddFunc("0 0 0 * * *", crontab.UserCouponExpire) if err != nil { panic("定时器启动失败:" + err.Error()) } diff --git a/core/rabbitMq.go b/core/rabbitMq.go index cfb5df2..8fac9af 100644 --- a/core/rabbitMq.go +++ b/core/rabbitMq.go @@ -23,33 +23,36 @@ func StartRabbitMq() { // StartRabbitMqConsume 启动消费者端 func StartRabbitMqConsume() { + // 用户优惠卷过期 s := rabbitMq.ConsumeS{ QueueName: "user.coupon.expired.delay.queue", ExchangeName: "amqp.delay.direct", RoutingKey: "UserCouponExpired", Handler: consumer.UserCouponExpire, } + go startConsumer(s) + + // 系统优惠卷过期 + s = rabbitMq.ConsumeS{ + QueueName: "coupon.expired.delay.queue", + ExchangeName: "amqp.delay.direct", + RoutingKey: "CouponExpired", + Handler: consumer.CouponExpire, + } + + go startConsumer(s) + + // 取消未支付订单 + s = rabbitMq.ConsumeS{ + QueueName: "cancel.unpay.order.delay.queue", + ExchangeName: "amqp.delay.direct", + RoutingKey: "CancelUnPayOrder", + Handler: consumer.CancelUnPayOrder, + } - // 用户优惠卷过期 go startConsumer(s) } -// UserCouponExpire 用户优惠卷过期 -//func userCouponExpire() { -// go func() { -// err := rabbitMq.Consume( -// "user.coupon.expired.delay.queue", // 交换机名称 -// "amqp.delay.direct", // 队列名称 -// "UserCouponExpired", // 路由键 -// consumer.UserCouponExpire, // 消息处理函数 -// ) -// if err != nil { -// fmt.Println(err.Error()) -// utils.LogJsonErr("消费用户优惠卷过期队列失败", err) -// } -// }() -//} - // startConsumer 启动指定的消费者协程 func startConsumer(s rabbitMq.ConsumeS) { go func() { diff --git a/extend/weChat/close.go b/extend/weChat/close.go new file mode 100644 index 0000000..7d8093e --- /dev/null +++ b/extend/weChat/close.go @@ -0,0 +1,62 @@ +package weChat + +import ( + "context" + "errors" + "github.com/wechatpay-apiv3/wechatpay-go/core" + "github.com/wechatpay-apiv3/wechatpay-go/services/payments/app" + "github.com/wechatpay-apiv3/wechatpay-go/services/payments/jsapi" + "hepa-calc-api/config" +) + +// CloseJsapiOrder 关闭jsapi支付订单 +func CloseJsapiOrder(outTradeNo string) error { + client, err := createClient() + if err != nil { + return err + } + + svc := jsapi.JsapiApiService{Client: client} + + req := jsapi.CloseOrderRequest{ + OutTradeNo: &outTradeNo, + Mchid: core.String(config.C.Wechat.Pay1281030301.MchId), + } + + result, err := svc.CloseOrder(context.TODO(), req) + if err != nil { + return err + } + + if result.Response.StatusCode != 204 { + return errors.New("关闭订单失败") + } + + return nil +} + +// CloseAppOrder 关闭app支付订单 +func CloseAppOrder(outTradeNo string) error { + client, err := createClient() + if err != nil { + return err + } + + svc := app.AppApiService{Client: client} + + req := app.CloseOrderRequest{ + OutTradeNo: &outTradeNo, + Mchid: core.String(config.C.Wechat.Pay1281030301.MchId), + } + + result, err := svc.CloseOrder(context.TODO(), req) + if err != nil { + return err + } + + if result.Response.StatusCode != 204 { + return errors.New("关闭订单失败") + } + + return nil +} diff --git a/extend/weChat/prepay.go b/extend/weChat/prepay.go index cba522b..1beda6e 100644 --- a/extend/weChat/prepay.go +++ b/extend/weChat/prepay.go @@ -60,7 +60,6 @@ func (r JsapiRequest) GetJsapiPrepay() (prepay *jsapi.PrepayWithRequestPaymentRe svc := jsapi.JsapiApiService{Client: client} // 得到prepay_id,以及调起支付所需的参数和签名 - resp, result, err := svc.PrepayWithRequestPayment(context.Background(), jsapi.PrepayRequest{ Appid: core.String(r.AppId),