一波更新

This commit is contained in:
tangping
2025-01-23 16:12:49 +08:00
parent 22ac6c1fed
commit 5f3a40a50e
90 changed files with 2392 additions and 1791 deletions

View File

@@ -51,4 +51,12 @@ root:
idle_timeout: 300
# 连接超时时间, 单位:秒
dial_connect_timeout: 10
dial_connect_timeout: 10
# 微信相关配置
wx_config:
# 微信移动应用appId
appId: "45678"
# 微信移动应用appSecret
appSecret: "954821"

View File

@@ -0,0 +1,125 @@
package mesqueue
import (
"fmt"
"goutil/fileUtil"
"goutil/logUtilPlus"
"goutil/webUtil"
"time"
)
type GameMsg struct {
//订单id
OrderID int64
//玩家id
PlayerID string
//区服id
ServerID int64
//游戏id
GameId string
//充值金额
price int64
// 充值商品id
storeId int64
// 推送次数
pushCount int32
}
var (
//消息队列
msgQueue chan GameMsg = make(chan GameMsg, 100)
fileName = "ErrPushMsg"
)
func init() {
ConsumeQueue()
}
// AddQueue 添加消息队列
func AddQueue(gameMsg GameMsg) {
msgQueue <- gameMsg
}
// ConsumeQueue 消费消息队列
func ConsumeQueue() {
go func() {
//捕获异常
defer func() {
if err := recover(); err != nil {
//TODO 捕获异常
logUtilPlus.ErrorLog("推送充值信息到game异常 err:%s", err)
//重新开启
restartConsumer()
}
}()
for {
gameMsg := <-msgQueue
url := fmt.Sprintf("http://www.game.com/pay %s", gameMsg.GameId)
//消费消息队列 推送重置信息到game
result, err := webUtil.GetWebData(url, map[string]string{})
if err != nil {
logUtilPlus.ErrorLog("推送充值信息到game异常 err:%s", err)
//放入消息队列重新推送
if gameMsg.pushCount < 3 {
msgQueue <- gameMsg
gameMsg.pushCount++
} else { //加入文件放弃推送
WriteErrPushMsg(url)
}
}
if string(result) != "" {
}
}
}()
}
// WriteErrPushMsg 推送异常消息 写入文件
func WriteErrPushMsg(messages string) {
//文件名拼接
filePath := fileName + "/" + time.Now().Format("2006-01-02")
fileName := time.Now().Format("2006-01-02 09") + ".txt"
//消息 添加换行符
messages += ";\r\n"
err := fileUtil.WriteFile(filePath, fileName, true, messages)
if err != nil {
logUtilPlus.ErrorLog(" MegPush 写入文件失败 err:%s", err)
}
}
// restartConsumer 重启消费者
func restartConsumer() {
// 设置重试次数
maxRetries := 5
retryCount := 0
for {
select {
case <-time.After(5 * time.Second): // 等待5秒后重试
if retryCount >= maxRetries {
logUtilPlus.ErrorLog("消费者重启失败,达到最大重试次数")
return
}
logUtilPlus.InfoLog("重新启动消费者,重试次数: %d", retryCount+1)
ConsumeQueue()
return
}
retryCount++
}
}

View File

@@ -21,11 +21,25 @@ var (
mchID string = wxpaycofnig.GetWxPayConfig().MchID // 商户号
mchCertificateSerialNumber string = wxpaycofnig.GetWxPayConfig().MchCertificateSerialNumber // 商户证书序列号
mchAPIv3Key string = wxpaycofnig.GetWxPayConfig().MchAPIv3Key // 商户APIv3密钥
appId string = wxpaycofnig.GetWxPayConfig().AppId // 应用ID
Address string = "成都市XXXXXXXXXXXXXXXXXXXXXXX" //公司地址
wxPayApiUrl string = wxpaycofnig.GetWxPayConfig().NotifyUrl //支付成功回调地址
)
// Prepay 预支付
func Prepay(outTradeNo int64, description string) (string, error) {
// Prepay 函数用于发起预支付请求。
// 参数:
//
// outTradeNo: 商户订单号。
// currency: 订单金额,单位为分。
// storeId: 商户门店编号。
// clientIp: 用户的客户端IP。
// description: 订单描述。
//
// 返回值:
//
// 成功时返回预支付ID和nil错误。
// 失败时返回空字符串和错误对象。
func Prepay(outTradeNo int64, currency int64, storeId string, clientIp string, description string) (string, error) {
// 使用 utils 提供的函数从本地文件中加载商户私钥,商户私钥会用来生成请求的签名
mchPrivateKey, err := utils.LoadPrivateKeyWithPath("/path/to/merchant/apiclient_key.pem")
@@ -48,39 +62,39 @@ func Prepay(outTradeNo int64, description string) (string, error) {
svc := app.AppApiService{Client: client}
resp, result, err := svc.Prepay(ctx,
app.PrepayRequest{
Appid: core.String("wxd678efh567hg6787"),
Mchid: core.String("1230000109"),
Description: core.String(description),
OutTradeNo: core.String(strconv.FormatInt(outTradeNo, 10)),
TimeExpire: core.Time(time.Now()),
Attach: core.String("自定义数据说明"),
NotifyUrl: core.String("https://www.weixin.qq.com/wxpay/pay.php"),
GoodsTag: core.String("WXG"),
LimitPay: []string{"LimitPay_example"},
Appid: core.String(appId),
Mchid: core.String(mchID),
Description: core.String(description),
OutTradeNo: core.String(strconv.FormatInt(outTradeNo, 10)),
TimeExpire: core.Time(time.Now().Add(time.Hour * 2)), //支付时效时间 2 小时后失效
Attach: core.String(""), //附加数据 这里不需要有个订单id 可以获取订单详细信息
NotifyUrl: core.String(fmt.Sprintf(wxPayApiUrl, strconv.FormatInt(outTradeNo, 10))), //回调地址
//GoodsTag: core.String("WXG"),//优惠标记 这里没用
//LimitPay: []string{"LimitPay_example"},
SupportFapiao: core.Bool(false),
Amount: &app.Amount{
Currency: core.String("CNY"),
Total: core.Int64(100),
Total: core.Int64(currency),
},
Detail: &app.Detail{
CostPrice: core.Int64(608800),
//CostPrice: core.Int64(608800),
GoodsDetail: []app.GoodsDetail{app.GoodsDetail{
GoodsName: core.String("iPhoneX 256G"),
MerchantGoodsId: core.String("ABC"),
Quantity: core.Int64(1),
UnitPrice: core.Int64(828800),
WechatpayGoodsId: core.String("1001"),
GoodsName: core.String(storeId), //商品编号
MerchantGoodsId: core.String(description),
Quantity: core.Int64(1),
UnitPrice: core.Int64(currency),
//WechatpayGoodsId: core.String("1001"),
}},
InvoiceId: core.String("wx123"),
//InvoiceId: core.String("wx123"),
},
SceneInfo: &app.SceneInfo{
DeviceId: core.String("013467007045764"),
PayerClientIp: core.String("14.23.150.211"),
//DeviceId: core.String("013467007045764"),
PayerClientIp: core.String(clientIp),
StoreInfo: &app.StoreInfo{
Address: core.String("广东省深圳市南山区科技中一道10000号"),
AreaCode: core.String("440305"),
Id: core.String("0001"),
Name: core.String("腾讯大厦分店"),
Address: core.String(Address),
//AreaCode: core.String("440305"),
//Id: core.String("0001"),
//Name: core.String("腾讯大厦分店"),
},
},
SettleInfo: &app.SettleInfo{
@@ -105,7 +119,7 @@ func Prepay(outTradeNo int64, description string) (string, error) {
}
// CloseOrder 关闭订单
func CloseOrder() {
func CloseOrder(outTradeNo int64) error {
// 使用 utils 提供的函数从本地文件中加载商户私钥,商户私钥会用来生成请求的签名
mchPrivateKey, err := utils.LoadPrivateKeyWithPath("/path/to/merchant/apiclient_key.pem")
@@ -124,24 +138,23 @@ func CloseOrder() {
}
svc := app.AppApiService{Client: client}
result, err := svc.CloseOrder(ctx,
_, err = svc.CloseOrder(ctx,
app.CloseOrderRequest{
//商户系统内部订单号只能是数字、大小写字母_-*且在同一个商户号下唯
OutTradeNo: core.String("OutTradeNo_example"),
OutTradeNo: core.String(strconv.FormatInt(outTradeNo, 10)),
//直连商户的商户号,由微信支付生成并下发。
Mchid: core.String("1230000109"),
Mchid: core.String(mchID),
},
)
if err != nil {
// 处理错误
log.Printf("call CloseOrder err:%s", err)
} else {
// 处理返回结果
log.Printf("status=%d", result.Response.StatusCode)
logUtilPlus.ErrorLog("call CloseOrder err:%s", err)
return err
}
return nil
}
// QueryOrderById 根据商户订单号查询订单
@@ -181,11 +194,18 @@ func QueryOrderById() {
}
// QueryOrderByOutTradeNo 根据商户订单号查询订单
func QueryOrderByOutTradeNo() {
func QueryOrderByOutTradeNo(outTradeNo int64) (string, error) {
//循环查询次数
var count int = 0
loop:
// 使用 utils 提供的函数从本地文件中加载商户私钥,商户私钥会用来生成请求的签名
mchPrivateKey, err := utils.LoadPrivateKeyWithPath("/path/to/merchant/apiclient_key.pem")
if err != nil {
log.Print("load merchant private key error")
return "", err
}
ctx := context.Background()
@@ -196,21 +216,37 @@ func QueryOrderByOutTradeNo() {
client, err := core.NewClient(ctx, opts...)
if err != nil {
log.Printf("new wechat pay client err:%s", err)
return "", err
}
svc := app.AppApiService{Client: client}
resp, result, err := svc.QueryOrderByOutTradeNo(ctx,
app.QueryOrderByOutTradeNoRequest{
OutTradeNo: core.String("OutTradeNo_example"),
Mchid: core.String("Mchid_example"),
OutTradeNo: core.String(strconv.FormatInt(outTradeNo, 10)),
Mchid: &mchID,
},
)
if err != nil {
// 处理错误
log.Printf("call QueryOrderByOutTradeNo err:%s", err)
return "", err
} else {
// 处理返回结果
log.Printf("status=%d resp=%s", result.Response.StatusCode, resp)
//支付成功
if resp.TradeState == core.String("SUCCESS") {
return "SUCCESS", nil
} else if resp.TradeState == core.String("NOTPAY") && count < 3 { //未支付,循环查找订单
//休息200毫秒
time.Sleep(200 * time.Millisecond)
count++
goto loop
}
//支付失败
return *resp.TradeState, nil
}
}

View File

@@ -5,7 +5,11 @@ import (
"common/resultStatus"
"common/webServer"
"goutil/intUtil"
"goutil/logUtilPlus"
"goutil/webUtil"
"net/http"
"paycenter/internal"
"strconv"
"time"
)
@@ -24,7 +28,7 @@ func init() {
remark.RegisterModuleRemark(moduleName, desc, author, mendor, date)
}
// PayApi 用户接口
// PayApi 支付接口
type PayApi struct {
}
@@ -37,7 +41,7 @@ func init() {
methodAuthor := "tangping"
methodMendor := ""
methodDate := "2025年1月8日15:51:34"
methodInParam := []string{"int32:充值模版id,string:玩家id,int32:区服id"}
methodInParam := []string{"int64:订单id由服务器生成,int32:充值模版id,int64:充值金额,string商品id, string:客户端IP地址,string:玩家id,int32:区服id"}
methodOutParam := `
{
"Code '类型:int'": "响应结果的状态值",
@@ -50,7 +54,14 @@ func init() {
}`
remark.RegisterMethodRemark(moduleName, methodName, methodDesc, methodAuthor, methodMendor, methodDate, methodInParam, methodOutParam, skipVerifyTokenPage)
}
func (a *PayApi) PlaceAnOrder(modelID int32, playerID string, serverID int64) (responseObj *webServer.ResponseObject) {
// 需要获取请求信息
func init() {
moduleName := "PayApi"
methodName := "PlaceAnOrder"
webServer.AddNeedGetRequestInfo(moduleName, methodName)
}
func (a *PayApi) PlaceAnOrder(orderId int64, modelID int32, currency int64, storeId string, playerID string, serverID int64, r *http.Request) (responseObj *webServer.ResponseObject) {
responseObj = webServer.GetInitResponseObj()
//校验参数
@@ -67,23 +78,17 @@ func (a *PayApi) PlaceAnOrder(modelID int32, playerID string, serverID int64) (r
return
}
//客户端请求ip
clientIp := webUtil.GetRequestIP(r)
//下微信订单
prepayId, err := internal.Prepay(orderId, "描述!!!!!!!!!!")
prepayId, err := internal.Prepay(orderId, currency, storeId, clientIp, "描述!!!!!!!!!!")
if err != nil {
responseObj.SetResultStatus(resultStatus.APIDataError)
return
}
//处理数据
order := &Order{
OrderID: orderId,
PrepayId: prepayId,
OrderMoney: 100,
OrderStatus: 1,
PlayerID: playerID,
ServerID: serverID,
UserID: playerID,
OrderDate: time.Now(),
}
order := NewOrder(orderId, prepayId, playerID, serverID, 1)
//添加到数据库
AddOrder(order)
@@ -94,3 +99,71 @@ func (a *PayApi) PlaceAnOrder(modelID int32, playerID string, serverID int64) (r
responseObj.SetData(resultMap)
return
}
func init() {
moduleName := "PayApi"
methodName := "CallBack"
skipVerifyTokenPage := true
methodDesc := "支付成功回调"
methodAuthor := "tangping"
methodMendor := ""
methodDate := "2025年1月21日16:40:57"
methodInParam := []string{"string:订单id"}
methodOutParam := `
{
"Code '类型:int'": "响应结果的状态值",
"Message '类型:string'": "响应结果的状态值所对应的描述信息",
"Data '类型:interface{}'": "响应结果的数据"
{
"OrderID '类型:int64'": "订单id",
"prepayId '类型:int64'": "预支付会话id",
}
}`
remark.RegisterMethodRemark(moduleName, methodName, methodDesc, methodAuthor, methodMendor, methodDate, methodInParam, methodOutParam, skipVerifyTokenPage)
}
func (a *PayApi) CallBack(orderIDStr string) (responseObj *webServer.ResponseObject) {
responseObj = webServer.GetInitResponseObj()
//参数错误
if orderIDStr == "" {
responseObj.SetResultStatus(resultStatus.APIDataError)
return
}
//orderID 转换成string
orderID, err := strconv.ParseInt(orderIDStr, 10, 64)
if err != nil {
logUtilPlus.ErrorLog("订单id转换失败", err.Error())
responseObj.SetResultStatus(resultStatus.APIParamError)
return
}
//是否已经处理
order, err := GetUserByID(orderID)
//支付成功
if err == nil && order != nil && order.OrderStatus == 1 {
return
}
//查询订单状态
statusStr, err := internal.QueryOrderByOutTradeNo(orderID)
if err != nil {
responseObj.SetResultStatus(resultStatus.DataError)
return
}
//状态错误
if statusStr != "SUCCESS" {
logUtilPlus.WarnLog("订单状态错误", statusStr)
return
}
err = ChangeOrderStatus(orderID, 1)
if err != nil {
responseObj.SetResultStatus(resultStatus.DataError)
return
}
return responseObj
}

View File

@@ -0,0 +1,95 @@
package pay
import (
"common/connection"
"goutil/logUtilPlus"
"paycenter/internal"
"strconv"
"time"
)
func init() {
go CheckOrderStatus()
}
// CheckOrderStatus 查询订单状态
func CheckOrderStatus() {
//捕获异常
defer func() {
if err := recover(); err != nil {
logUtilPlus.ErrorLog("CheckOrderStatus panic:", err)
restartConsumer()
}
}()
for {
//检索最近一个月的订单
for i := 0; i < 2; i++ {
//取i的负数
dbDate := connection.GetToMonthAdd(int32(-i))
var orders []Order // 使用切片存储查询结果
//这里使用原始sql
sql := "select * from order_" + strconv.Itoa(int(dbDate)) + " where order_status = 0"
dbResult := connection.GetPayDB().Exec(sql).Find(&orders)
if dbResult.Error != nil {
logUtilPlus.ErrorLog("查询订单状态失败", dbResult.Error.Error())
continue
}
// 处理查询结果
for _, order := range orders {
//查询订单状态
statusStr, err := internal.QueryOrderByOutTradeNo(order.OrderID)
if err != nil {
logUtilPlus.ErrorLog("查询订单状态失败", err.Error())
continue
}
if statusStr == "SUCCESS" {
//修改订单状态
err = ChangeOrderStatus(order.OrderID, 1)
if err != nil {
logUtilPlus.ErrorLog("修改订单状态失败", err.Error())
continue
}
} else if statusStr == "CLOSED" { //已关闭
order.OrderStatus = 2
//修改订单状态
connection.AsyncSave(connection.GetPayDB(), &order)
} else if order.OrderTime.Add(time.Hour * 1).Before(time.Now()) { //超一个小时未支付 直接关闭订单
//直接关闭订单
internal.CloseOrder(order.OrderID)
order.OrderStatus = 2
connection.AsyncSave(connection.GetPayDB(), &order)
}
}
}
//休息30分钟
time.Sleep(time.Minute * 30)
}
}
// restartConsumer 重启消费者
func restartConsumer() {
// 设置重试次数
maxRetries := 5
retryCount := 0
for {
select {
case <-time.After(5 * time.Second): // 等待5秒后重试
if retryCount >= maxRetries {
logUtilPlus.ErrorLog("查询订单状态,达到最大重试次数")
return
}
logUtilPlus.InfoLog("查询订单状态,重试次数: %d", retryCount+1)
go CheckOrderStatus()
return
}
retryCount++
}
}

View File

@@ -2,7 +2,8 @@ package pay
import (
"common/connection"
"goutil/logUtilPlus"
"fmt"
"paycenter/internal/mesqueue"
"sync"
)
@@ -59,14 +60,41 @@ func AddOrder(order *Order) (int64, error) {
//处理一些验证
// 写入到数据库
result := connection.GetPayDB().Create(&order) // 通过数据的指针来创建
if result.Error != nil {
logUtilPlus.ErrorLog("添加支付订单失败 错误信息:", result.Error.Error())
}
// 异步 写入到数据库
connection.AsyncCreate(connection.GetPayDB(), &order)
//添加缓存
AddOrderCache(order)
return order.OrderID, nil
}
// ChangeOrderStatus 改变订单状态
func ChangeOrderStatus(orderID int64, status int32) error {
//根据订单id获取订单
order, err := GetUserByID(orderID)
if err != nil {
return err
}
//判断订单是否存在
if order == nil {
return fmt.Errorf("订单不存在,订单id %d", orderID)
}
if status == 1 {
mesqueue.AddQueue(mesqueue.GameMsg{
GameId: "",
OrderID: orderID,
})
}
//更新订单状态
order.OrderStatus = status
order.IsNotifyGameServer = 1
//异步更新数据库
connection.AsyncSave(connection.GetPayDB(), &order)
return nil
}

View File

@@ -2,6 +2,7 @@ package pay
import (
"common/connection"
"strconv"
"time"
)
@@ -13,13 +14,11 @@ func init() {
type Order struct {
//订单id
OrderID int64 `gorm:"column:order_id;primary_key;comment:订单id" json:"order_id"`
//订单日期
OrderDate time.Time `gorm:"column:order_date;comment:订单日期" json:"order_date"`
//订单号
PrepayId string `gorm:"column:prepay_id;comment:预支付交易会话标识" json:"prepay_id"`
//订单金额
OrderMoney int64 `gorm:"column:order_money;comment:订单金额" json:"order_money"`
//订单状态
//订单状态 0:未支付 1:已支付 2:已关闭
OrderStatus int32 `gorm:"column:order_status;comment:订单状态" json:"order_status"`
//用户id
UserID string `gorm:"column:user_id;comment:用户id" json:"user_id"`
@@ -27,8 +26,42 @@ type Order struct {
ServerID int64 `gorm:"column:server_id;comment:区服id" json:"server_id"`
//玩家标识
PlayerID string `gorm:"column:player_id;comment:玩家标识" json:"player_id"`
//是否通知游戏服 0未通知 1已通知
IsNotifyGameServer int32 `gorm:"column:is_notify_game_server;comment:是否通知游戏服" json:"is_notify_game_server"`
//订单日期
OrderDate time.Time `gorm:"column:order_date;comment:订单日期" json:"order_date"`
//订单时间
OrderTime time.Time `gorm:"column:order_time;type:date;comment:订单时间" json:"order_time"`
//订单类型 1:微信支付 2:支付宝支付
OrderType int32 `gorm:"column:order_type;comment:订单类型" json:"order_type"`
//表的名字 这里是表的后缀
TableNameData int32 `gorm:"column:table_name;comment:表的名字 这里是表的后缀" json:"table_name"`
}
// TableName 表名
func (order *Order) TableName() string {
return "order"
return "order_" + strconv.Itoa(int(order.TableNameData))
}
// NewOrder 创建订单
// @param orderId 订单id
// @param prepayId 预支付id
// @param playerID 玩家id
// @param serverID 服务器id
// @param orderType 订单类型
func NewOrder(orderId int64, prepayId string, playerID string, serverID int64, orderType int32) *Order {
return &Order{
OrderID: orderId,
PrepayId: prepayId,
OrderMoney: 100,
OrderStatus: 1,
PlayerID: playerID,
ServerID: serverID,
UserID: playerID,
OrderDate: time.Now(),
OrderTime: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
OrderType: orderType,
TableNameData: connection.GetMonth(),
}
}

View File

@@ -10,6 +10,8 @@ type WxPayConfig struct {
MchID string
MchCertificateSerialNumber string
MchAPIv3Key string
AppId string
NotifyUrl string
}
var (
@@ -35,7 +37,7 @@ func init() {
// @error: 错误信息
func reloadConfig() error {
yamlFile, err := yamlUtil.LoadFromFile("payconfig/wxpayconfig.yaml.yaml")
yamlFile, err := yamlUtil.LoadFromFile("payconfig/wxpayconfig.yml")
if err != nil {
return err
}

View File

@@ -4,7 +4,7 @@ import (
"common/connection"
"sync"
_ "common/resultStatus"
_ "common/resultstatus"
"common/webServer"
_ "paycenter/internal"
)

View File

@@ -0,0 +1,15 @@
#微信支付相关配置
#商户id
MchID: '1900000109'
#商户证书序列号
MchCertificateSerialNumber: '3775D15B3B9A7F6D'
#商户APIv3密钥
MchAPIv3Key: 'X5g0l1vL9kRvP0VB2WlhtyVwqEG9zvGf'
# appId
AppID: 'wx8888888888888888'
# 微信回调地址
NotifyUrl: 'https://www.example.com/wxpay/notify?orderId=%s'