diff --git a/controllers/client/live.go b/controllers/client/live.go index 939bfbe..14abe5a 100644 --- a/controllers/client/live.go +++ b/controllers/client/live.go @@ -1,16 +1,125 @@ package client import ( + "go.uber.org/zap" "hudongzhuanjia/controllers" "hudongzhuanjia/libs/filter" + "hudongzhuanjia/logger" "hudongzhuanjia/models" pay_service "hudongzhuanjia/services/pay" red_envelope_service "hudongzhuanjia/services/red_envelope" + "hudongzhuanjia/utils" "hudongzhuanjia/utils/code" "strings" "time" ) +var MaxQueueSize = 10000 +var RequestLimit = 60 * time.Second +var GetRedPackQueue = make(chan int64, MaxQueueSize) + +func loopGetRedPack() { + redPacks, err := models.GetRedPacksByStatus(1) + if err != nil { + panic(err) + } + + // 保证容量足够 + GetRedPackQueue = make(chan int64, MaxQueueSize+len(redPacks)) + + // 初始化 + for _, redPack := range redPacks { + GetRedPackQueue <- redPack.Id + } + + defer func() { + if errRec := recover(); errRec != nil { + logger.Error("用户转账轮询发生错误", + zap.String("函数", "loopGetRedPack"), zap.Any("错误原因", errRec)) + } + time.Sleep(5 * time.Second) + loopGetRedPack() + }() + for { + select { + case redPackId, ok := <-GetRedPackQueue: + if !ok { + panic("GetRedPackQueue通道关闭") + } + time.Sleep(RequestLimit) // 请求频率 + + redPack := new(models.LiveRedPack) + exist, err := models.GetById(redPack, redPackId) + if err != nil { + GetRedPackQueue <- redPackId + logger.Error("获取LiveRedPack表数据出现错误", + zap.Any("错误原因", err), zap.Int64("LiveRedPack表的id", redPack.Id)) + continue + } + if !exist { + logger.Error("不存在LiveRedPack表数据", zap.Int64("LiveRedPack表的id", redPack.Id)) + continue + } + + // todo: 增加一个查询机制 --查询此次付款是否成功 + // todo: 是否需要发放系统通知 + resp, err := pay_service.TransferInfo(redPack.TransferNo) + if err != nil { + GetRedPackQueue <- redPackId + logger.Error("微信企业转账查询API", + zap.Any("错误原因", err), zap.Int64("LiveRedPack表的id", redPack.Id)) + continue + } + if resp.Status == pay_service.CODE_FAIL { // 转账失败 + _, err = pay_service.Transfer("欧轩互动-红包活动", redPack.OpenId, redPack.TransferNo, redPack.Amount) + if err != nil { + GetRedPackQueue <- redPackId + logger.Error("微信企业转账API", + zap.Any("错误原因", err), zap.Int64("LiveRedPack表的id", redPack.Id)) + continue + } + _, err = redPack.UpdateStatusById(redPack.Id, 2) + if err != nil { + GetRedPackQueue <- redPackId + logger.Error("微信企业转账,LiveRedPack表更新错误", + zap.Any("错误原因", err), zap.Int64("LiveRedPack表id", redPack.Id)) + continue + } + } else if resp.Status == pay_service.CODE_SUCCESS { + _, err = redPack.UpdateStatusById(redPack.Id, 2) + if err != nil { + logger.Error("微信企业查询,转账成功", + zap.Any("错误原因", err), zap.Int64("LiveRedPack表id", redPack.Id)) + continue + } + } + } + } +} + +var SendRedPackQueue = make(chan int64, MaxQueueSize) + +func loopSendRedPack() { + redPackInfo := + defer func() { + if err := recover(); err != nil { + logger.Error("用户发送红包轮询出现错误", + zap.String("函数", "loopSendRedPack"), zap.Any("错误", err)) + } + time.Sleep(5 * time.Second) + loopSendRedPack() + }() + for { + select { + case redPackInfoId, ok := <-SendRedPackQueue: + if !ok { + panic("SendRedPackQueue通道异常关闭") + } + + } + } +} + type LiveCtl struct { controllers.AuthorCtl //controllers.BaseCtl @@ -76,7 +185,7 @@ func (t *LiveCtl) SendLiveRedPack() { userId := t.MustGetUID() // 用户 uid activityId := t.MustGetInt64("activity_id") // activity_id num := t.MustGetInt("num") // 红包数量 - amount := t.MustGetDouble("amount") // 金额 + amount := t.MustGetInt64("amount") // 金额 prompt := t.MustGet("prompt") // 提示 user := models.User{} @@ -85,11 +194,10 @@ func (t *LiveCtl) SendLiveRedPack() { t.Assert(exist, code.MSG_USER_NOT_EXIST, "用户不存在") ip := strings.Split(t.Request.OriginRequest.RemoteAddr, ":") - res, err := pay_service.Order("欧轩互动-直播红包", ip[0], user.Openid, int(amount*100), 3, userId, activityId) + res, err := pay_service.UnifiedOrder("欧轩互动-直播红包", ip[0], user.Openid, amount) t.CheckErr(err) info := models.LiveRedPackInfo{} - info.UserOrderId = res["user_order_id"].(int64) info.Amount = amount info.UserId = userId info.ActivityId = activityId @@ -136,18 +244,17 @@ func (t *LiveCtl) GetRedPack() { } // 乐观锁 ==> 防止并发 - row, err := redPack.Receive(userId) + redPack.OpenId = user.Openid + redPack.Receiver = user.Id + redPack.TransferType = 1 + redPack.TransferNo = utils.RandomStr(32) + row, err := redPack.UpdateStatusById(redPack.Id, 1) t.CheckErr(err) if row != 1 { t.ERROR("红包被领完了", code.MSG_LIVE_RED_PACK_NOT_EXIST) return } - ip := strings.Split(t.Request.OriginRequest.RemoteAddr, ":") - res, err := pay_service.Transfer("欧轩互动-红包活动", ip[0], user.Openid, redPack.Amount) - redPack.TransferType = 1 - redPack.TransferNo = res.PartnerTradeNo - _, err = redPack.UpdateStatusById(redPack, 2) - t.CheckErr(err) + GetRedPackQueue <- redPack.Id // 进入队列 t.JSON(redPack) } diff --git a/models/live_red_pack.go b/models/live_red_pack.go index 8dc3a88..d6c80f2 100644 --- a/models/live_red_pack.go +++ b/models/live_red_pack.go @@ -16,10 +16,11 @@ type LiveRedPack struct { LiveRedPackInfoId int64 `json:"live_red_pack_info_id" xorm:"not null default 0 comment('红包信息id') INT(11)"` ActivityId int64 `json:"activity_id" xorm:"not null default 0 comment('互动id') INT(11)"` Receiver int64 `json:"receiver" xorm:"not null default 0 comment('[0未领取/非0领取用户id]') INT(11)"` + OpenId string `json:"open_id" xorm:"not null default '' comment('用户openid') VARCHAR(128)"` Amount int `json:"amount" xorm:"not null default 0 comment('红包金额, 分') INT(18)"` - TransferType int `json:"transfer_type" xorm:"not null default 0 comment('转账方式[0微信红包1微信零钱]')"` + TransferType int `json:"transfer_type" xorm:"not null default 0 comment('转账方式[0微信红包1微信零钱]') TINYINT(1)"` TransferNo string `json:"transfer_no" xorm:"not null default '' comment('转账账号') VARCHAR(128) "` - Status int `json:"status" xorm:"not null default 0 comment('0 未被领取 1 已被领取 2 已发送')"` + Status int `json:"status" xorm:"not null default 0 comment('0 未被领取 1 已被领取 2 已发送') TINYINT(1)"` Version int `json:"version" xorm:"not null version comment('乐观锁') INT(11)"` } @@ -36,12 +37,18 @@ func (t *LiveRedPack) GetByInfoId(infoId int64) (bool, error) { } func (t *LiveRedPack) Receive(userId int64) (int64, error) { - t.Receiver = userId t.Status = 1 return core.GetXormAuto().Where("id=?", t.Id).Cols("user_id, status").Update(t) } func (t *LiveRedPack) UpdateStatusById(id interface{}, status int) (int64, error) { t.Status = status - return core.GetXormAuto().Where("id=?", t.Id).Cols("status").Update(t) + return core.GetXormAuto().Where("id=?", t.Id). + Cols("receiver, open_id, transfer_type, transfer_no, status").Update(t) +} + +func GetRedPacksByStatus(status interface{}) ([]*LiveRedPack, error) { + redPacks := make([]*LiveRedPack, 0) + err := core.GetXormAuto().Where("is_delete=0 and status=?", status).Find(&redPacks) + return redPacks, err } diff --git a/models/live_red_pack_info.go b/models/live_red_pack_info.go index 5ab1e5d..ddf7b50 100644 --- a/models/live_red_pack_info.go +++ b/models/live_red_pack_info.go @@ -13,11 +13,12 @@ type LiveRedPackInfo struct { CreatedAt time.Time `json:"created_at" xorm:"not null created comment('创建时间') DATETIME"` UpdatedAt time.Time `json:"updated_at" xorm:"not null updated default CURRENT_TIMESTAMP comment('更新时间') TIMESTAMP"` - UserOrderId int64 `json:"user_order_id" xorm:"not null default 0 comment('用户微信订单id') INT(11)"` - UserId int64 `json:"user_id" xorm:"not null default 0 comment('用户id') INT(11)"` - ActivityId int64 `json:"activity_id" xorm:"not null default 0 comment('互动id') INT(11)"` - Prompt string `json:"prompt" xorm:"not null default 0 comment('祝福语') VARCHAR(255)"` - Amount float64 `json:"amount" xorm:"not null default 0.0 comment('红包金额') DECIMAL(18,2)"` + UserId int64 `json:"user_id" xorm:"not null default 0 comment('用户id') INT(11)"` + ActivityId int64 `json:"activity_id" xorm:"not null default 0 comment('互动id') INT(11)"` + Prompt string `json:"prompt" xorm:"not null default 0 comment('祝福语') VARCHAR(255)"` + Amount int64 `json:"amount" xorm:"not null default 0 comment('红包金额, 分') INT(18)"` + OutTradeNo string `json:"out_trade_no" xorm:"not null default '' comment('订单号') VARCHAR(128)"` + Status int `json:"status" xorm:"not null default 0 comment('0尚未支付1取消支付2支付成功3订单关闭')"` } func (t *LiveRedPackInfo) TableName() string { @@ -27,3 +28,8 @@ func (t *LiveRedPackInfo) TableName() string { func (t *LiveRedPackInfo) Add() (int64, error) { return core.GetXormAuto().InsertOne(t) } + +func GetLiveRedPackInfos() { + infos := make([]*LiveRedPackInfo, 0) + core.GetXormAuto().Where("is_delete=0 and ") +} diff --git a/services/pay/order.go b/services/pay/order.go index 9227aa1..076d9fa 100644 --- a/services/pay/order.go +++ b/services/pay/order.go @@ -81,6 +81,47 @@ func Order(content, ip, openid string, fee, goodType int, userId, activityId int }, nil } +func UnifiedOrder(content, ip, openid string, fee int64) (map[string]interface{}, error) { + client, err := Client() + if err != nil { + return nil, err + } + + now := time.Now() + outTradeNo := utils.RandomStr(32) + nonceStr := utils.RandomStr(32) + resp, err := pay.UnifiedOrder2(client, &pay.UnifiedOrderRequest{ + Body: content, + OutTradeNo: outTradeNo, + TotalFee: int64(fee), + SpbillCreateIP: ip, + NotifyURL: CallbackOrderUrl, + TradeType: "JSAPI", + DeviceInfo: "WEB", + NonceStr: nonceStr, + SignType: core2.SignType_MD5, + TimeStart: now, + OpenId: openid, + }) + if err != nil { + return nil, err + } + + timestamp := strconv.FormatInt(time.Now().Unix(), 10) + //获取H5支付需要的paySign + pac := "prepay_id=" + resp.PrepayId + paySign := core2.JsapiSign(client.AppId(), nonceStr, pac, core2.SignType_MD5, timestamp, ApiKey) + return map[string]interface{}{ + "appid": Appid, + "timestamp": timestamp, + "nonce_str": nonceStr, + "package": pac, + "sign_type": core2.SignType_MD5, + "pay_sign": paySign, + "out_trade_no": outTradeNo, + }, nil +} + // Notify type NotifyRequest struct { ReturnCode string `xml:"return_code,omitempty" json:"return_code,omitempty"` diff --git a/services/pay/transfer.go b/services/pay/transfer.go index a708a0e..2ad36a0 100644 --- a/services/pay/transfer.go +++ b/services/pay/transfer.go @@ -6,6 +6,7 @@ import ( "github.com/chanxuehong/wechat/mch/mmpaymkttransfers/promotion" "hudongzhuanjia/models" "hudongzhuanjia/utils" + "strconv" "time" ) @@ -18,10 +19,12 @@ type TransferResponse struct { PaymentTime string `xml:"payment_time,omitempty" json:"payment_time,omitempty"` } -func Transfer(desc, ip, openId string, amount int) (*TransferResponse, error) { +func Transfer(desc, openId, partnerTradeNo string, amount int) (*TransferResponse, error) { nonceStr := utils.RandomStr(32) - partnerTradeNo := utils.RandomStr(32) + if partnerTradeNo == "" { // 需要提前存入 + partnerTradeNo = utils.RandomStr(32) + } // 初始化参数结构体 body := make(map[string]string) @@ -31,7 +34,6 @@ func Transfer(desc, ip, openId string, amount int) (*TransferResponse, error) { body["check_name"] = "NO_CHECK" // NO_CHECK:不校验真实姓名 , FORCE_CHECK:强校验真实姓名 body["amount"] = fmt.Sprintf("%d", amount) // 企业付款金额,单位为分 body["desc"] = desc // 企业付款备注,必填。注意:备注中的敏感词会被转成字符* - body["spbill_create_ip"] = ip body["mchid"] = Mchid body["mch_appid"] = Appid @@ -50,6 +52,47 @@ func Transfer(desc, ip, openId string, amount int) (*TransferResponse, error) { return res, nil } +type TransferInfoResponse struct { + Status string `json:"status"` + Reason string `json:"reason"` + Openid string `json:"openid"` + PaymentAmount int `json:"payment_amount"` + PaymentTime string `json:"payment_time"` + TransferTime string `json:"transfer_time"` + Desc string `json:"desc"` +} + +func TransferInfo(partnerTradeNo string) (*TransferInfoResponse, error) { + client, err := Client() + if err != nil { + return nil, err + } + + nonceStr := utils.RandomStr(32) + + var body = make(map[string]string, 0) + body["nonce_str"] = nonceStr + body["partner_trade_no"] = partnerTradeNo + + m, err := mmpaymkttransfers.GetTransferInfo(client, body) + if err != nil { + return nil, err + } + amount, _ := strconv.Atoi(m["payment_amount"]) + + resp := &TransferInfoResponse{ + Status: m["status"], + Reason: m["reason"], + Openid: m["openid"], + PaymentAmount: amount, + PaymentTime: m["payment_time"], + TransferTime: m["transfer_time"], + Desc: m["desc"], + } + return resp, nil + +} + type RedPackResult struct { MchBillno string `xml:"mch_billno,omitempty" json:"mch_billno,omitempty"` ReOpenid string `xml:"re_openid,omitempty" json:"re_openid,omitempty"` diff --git a/test/pay_test.go b/test/pay_test.go index 6f89e1a..aec3d7b 100644 --- a/test/pay_test.go +++ b/test/pay_test.go @@ -9,7 +9,7 @@ import ( var openId = "o9XM41s_NN8Y0QK6_MbM-aYMV3TE" func TestTransfer(t *testing.T) { - res, err := pay_service.Transfer("欧轩互动-转账测试", "127.0.0.1", openId, 0.30*100) + res, err := pay_service.Transfer("欧轩互动-转账测试", "127.0.0.1", openId, 0.30*10) fmt.Println(err) fmt.Printf("%+v\n", res) }