更新项目文档,添加会话启动自检说明,强化 GORM 使用规范,确保数据操作遵循事务管理和预加载原则。修正多个处理函数以使用链式查询和数据验证,提升代码一致性和可维护性。

This commit is contained in:
2026-02-24 11:41:38 +08:00
parent 715772ecfb
commit 5f09416377
8 changed files with 363 additions and 165 deletions

View File

@@ -78,29 +78,18 @@ func MiniprogramLogin(c *gin.Context) {
db.Model(&user).Update("session_key", sessionKey)
}
// 从 orders 表查询真实购买记录
// 从 orders 表查询真实购买记录Pluck 替代 Raw
var purchasedSections []string
var orderRows []struct {
ProductID string `gorm:"column:product_id"`
}
db.Raw(`
SELECT DISTINCT product_id
FROM orders
WHERE user_id = ?
AND status = 'paid'
AND product_type = 'section'
`, user.ID).Scan(&orderRows)
for _, row := range orderRows {
if row.ProductID != "" {
purchasedSections = append(purchasedSections, row.ProductID)
db.Model(&model.Order{}).Where("user_id = ? AND status = ? AND product_type = ?", user.ID, "paid", "section").
Distinct("product_id").Pluck("product_id", &purchasedSections)
// 过滤空字符串
filtered := make([]string, 0, len(purchasedSections))
for _, s := range purchasedSections {
if s != "" {
filtered = append(filtered, s)
}
}
if purchasedSections == nil {
purchasedSections = []string{}
}
purchasedSections = filtered
// 构建返回的用户对象
responseUser := map[string]interface{}{
@@ -177,7 +166,7 @@ func miniprogramPayPost(c *gin.Context) {
OpenID string `json:"openId" binding:"required"`
ProductType string `json:"productType" binding:"required"`
ProductID string `json:"productId"`
Amount float64 `json:"amount" binding:"required"`
Amount float64 `json:"amount" binding:"required,gte=0"`
Description string `json:"description"`
UserID string `json:"userId"`
ReferralCode string `json:"referralCode"`
@@ -198,18 +187,11 @@ func miniprogramPayPost(c *gin.Context) {
// 查询用户的有效推荐人(先查 binding再查 referralCode
var referrerID *string
if req.UserID != "" {
var binding struct {
ReferrerID string `gorm:"column:referrer_id"`
}
err := db.Raw(`
SELECT referrer_id
FROM referral_bindings
WHERE referee_id = ? AND status = 'active' AND expiry_date > NOW()
ORDER BY binding_date DESC
LIMIT 1
`, req.UserID).Scan(&binding).Error
if err == nil && binding.ReferrerID != "" {
referrerID = &binding.ReferrerID
var refID string
err := db.Model(&model.ReferralBinding{}).Where("referee_id = ? AND status = ? AND expiry_date > ?", req.UserID, "active", time.Now()).
Order("binding_date DESC").Limit(1).Pluck("referrer_id", &refID).Error
if err == nil && refID != "" {
referrerID = &refID
}
}
if referrerID == nil && req.ReferralCode != "" {
@@ -381,97 +363,99 @@ func MiniprogramPayNotify(c *gin.Context) {
}
db := database.DB()
buyerUserID := attach.UserID
if openID != "" {
var user model.User
if err := db.Where("open_id = ?", openID).First(&user).Error; err == nil {
if attach.UserID != "" && user.ID != attach.UserID {
fmt.Printf("[PayNotify] 买家身份校验: attach.userId 与 openId 解析不一致,以 openId 为准\n")
}
buyerUserID = user.ID
}
}
if buyerUserID == "" && attach.UserID != "" {
buyerUserID = attach.UserID
}
var order model.Order
result := db.Where("order_sn = ?", orderSn).First(&order)
if result.Error != nil {
fmt.Printf("[PayNotify] 订单不存在,补记订单: %s\n", orderSn)
productID := attach.ProductID
if productID == "" {
productID = "fullbook"
}
productType := attach.ProductType
if productType == "" {
productType = "unknown"
}
desc := "支付回调补记订单"
status := "paid"
now := time.Now()
order = model.Order{
ID: orderSn,
OrderSN: orderSn,
UserID: buyerUserID,
OpenID: openID,
ProductType: productType,
ProductID: &productID,
Amount: totalAmount,
Description: &desc,
Status: &status,
TransactionID: &transactionID,
PayTime: &now,
}
if err := db.Create(&order).Error; err != nil {
fmt.Printf("[PayNotify] 补记订单失败: %s, err=%v\n", orderSn, err)
return fmt.Errorf("create order: %w", err)
}
} else if *order.Status != "paid" {
status := "paid"
now := time.Now()
if err := db.Model(&order).Updates(map[string]interface{}{
"status": status,
"transaction_id": transactionID,
"pay_time": now,
}).Error; err != nil {
fmt.Printf("[PayNotify] 更新订单状态失败: %s, err=%v\n", orderSn, err)
return fmt.Errorf("update order: %w", err)
}
fmt.Printf("[PayNotify] 订单状态已更新为已支付: %s\n", orderSn)
} else {
fmt.Printf("[PayNotify] 订单已支付,跳过更新: %s\n", orderSn)
}
if buyerUserID != "" && attach.ProductType != "" {
if attach.ProductType == "fullbook" {
db.Model(&model.User{}).Where("id = ?", buyerUserID).Update("has_full_book", true)
fmt.Printf("[PayNotify] 用户已购全书: %s\n", buyerUserID)
} else if attach.ProductType == "match" {
fmt.Printf("[PayNotify] 用户购买匹配次数: %s订单 %s\n", buyerUserID, orderSn)
} else if attach.ProductType == "section" && attach.ProductID != "" {
var count int64
db.Model(&model.Order{}).Where(
"user_id = ? AND product_type = 'section' AND product_id = ? AND status = 'paid' AND order_sn != ?",
buyerUserID, attach.ProductID, orderSn,
).Count(&count)
if count == 0 {
fmt.Printf("[PayNotify] 用户首次购买章节: %s - %s\n", buyerUserID, attach.ProductID)
} else {
fmt.Printf("[PayNotify] 用户已有该章节的其他已支付订单: %s - %s\n", buyerUserID, attach.ProductID)
return db.Transaction(func(tx *gorm.DB) error {
buyerUserID := attach.UserID
if openID != "" {
var user model.User
if err := tx.Where("open_id = ?", openID).First(&user).Error; err == nil {
if attach.UserID != "" && user.ID != attach.UserID {
fmt.Printf("[PayNotify] 买家身份校验: attach.userId 与 openId 解析不一致,以 openId 为准\n")
}
buyerUserID = user.ID
}
}
productID := attach.ProductID
if productID == "" {
productID = "fullbook"
if buyerUserID == "" && attach.UserID != "" {
buyerUserID = attach.UserID
}
db.Where(
"user_id = ? AND product_type = ? AND product_id = ? AND status = 'created' AND order_sn != ?",
buyerUserID, attach.ProductType, productID, orderSn,
).Delete(&model.Order{})
processReferralCommission(db, buyerUserID, totalAmount, orderSn, &order)
}
return nil
var order model.Order
result := tx.Where("order_sn = ?", orderSn).First(&order)
if result.Error != nil {
fmt.Printf("[PayNotify] 订单不存在,补记订单: %s\n", orderSn)
productID := attach.ProductID
if productID == "" {
productID = "fullbook"
}
productType := attach.ProductType
if productType == "" {
productType = "unknown"
}
desc := "支付回调补记订单"
status := "paid"
now := time.Now()
order = model.Order{
ID: orderSn,
OrderSN: orderSn,
UserID: buyerUserID,
OpenID: openID,
ProductType: productType,
ProductID: &productID,
Amount: totalAmount,
Description: &desc,
Status: &status,
TransactionID: &transactionID,
PayTime: &now,
}
if err := tx.Create(&order).Error; err != nil {
fmt.Printf("[PayNotify] 补记订单失败: %s, err=%v\n", orderSn, err)
return fmt.Errorf("create order: %w", err)
}
} else if *order.Status != "paid" {
status := "paid"
now := time.Now()
if err := tx.Model(&order).Updates(map[string]interface{}{
"status": status,
"transaction_id": transactionID,
"pay_time": now,
}).Error; err != nil {
fmt.Printf("[PayNotify] 更新订单状态失败: %s, err=%v\n", orderSn, err)
return fmt.Errorf("update order: %w", err)
}
fmt.Printf("[PayNotify] 订单状态已更新为已支付: %s\n", orderSn)
} else {
fmt.Printf("[PayNotify] 订单已支付,跳过更新: %s\n", orderSn)
}
if buyerUserID != "" && attach.ProductType != "" {
if attach.ProductType == "fullbook" {
tx.Model(&model.User{}).Where("id = ?", buyerUserID).Update("has_full_book", true)
fmt.Printf("[PayNotify] 用户已购全书: %s\n", buyerUserID)
} else if attach.ProductType == "match" {
fmt.Printf("[PayNotify] 用户购买匹配次数: %s订单 %s\n", buyerUserID, orderSn)
} else if attach.ProductType == "section" && attach.ProductID != "" {
var count int64
tx.Model(&model.Order{}).Where(
"user_id = ? AND product_type = 'section' AND product_id = ? AND status = 'paid' AND order_sn != ?",
buyerUserID, attach.ProductID, orderSn,
).Count(&count)
if count == 0 {
fmt.Printf("[PayNotify] 用户首次购买章节: %s - %s\n", buyerUserID, attach.ProductID)
} else {
fmt.Printf("[PayNotify] 用户已有该章节的其他已支付订单: %s - %s\n", buyerUserID, attach.ProductID)
}
}
productID := attach.ProductID
if productID == "" {
productID = "fullbook"
}
tx.Where(
"user_id = ? AND product_type = ? AND product_id = ? AND status = 'created' AND order_sn != ?",
buyerUserID, attach.ProductType, productID, orderSn,
).Delete(&model.Order{})
processReferralCommission(tx, buyerUserID, totalAmount, orderSn, &order)
}
return nil
})
})
if err != nil {
fmt.Printf("[PayNotify] 处理回调失败: %v\n", err)
@@ -514,24 +498,9 @@ func processReferralCommission(db *gorm.DB, buyerUserID string, amount float64,
}
}
// 查找有效推广绑定
type Binding struct {
ID int `gorm:"column:id"`
ReferrerID string `gorm:"column:referrer_id"`
ExpiryDate time.Time `gorm:"column:expiry_date"`
PurchaseCount int `gorm:"column:purchase_count"`
TotalCommission float64 `gorm:"column:total_commission"`
}
var binding Binding
err := db.Raw(`
SELECT id, referrer_id, expiry_date, purchase_count, total_commission
FROM referral_bindings
WHERE referee_id = ? AND status = 'active'
ORDER BY binding_date DESC
LIMIT 1
`, buyerUserID).Scan(&binding).Error
// 查找有效推广绑定GORM 替代 Raw
var binding model.ReferralBinding
err := db.Where("referee_id = ? AND status = ?", buyerUserID, "active").Order("binding_date DESC").First(&binding).Error
if err != nil {
fmt.Printf("[PayNotify] 用户无有效推广绑定,跳过分佣: %s\n", buyerUserID)
return
@@ -545,24 +514,30 @@ func processReferralCommission(db *gorm.DB, buyerUserID string, amount float64,
// 计算佣金(按原价)
commission := commissionBase * distributorShare
newPurchaseCount := binding.PurchaseCount + 1
newTotalCommission := binding.TotalCommission + commission
purchaseCount := 0
if binding.PurchaseCount != nil {
purchaseCount = *binding.PurchaseCount
}
totalCommission := 0.0
if binding.TotalCommission != nil {
totalCommission = *binding.TotalCommission
}
newPurchaseCount := purchaseCount + 1
newTotalCommission := totalCommission + commission
fmt.Printf("[PayNotify] 处理分佣: referrerId=%s, amount=%.2f, commission=%.2f, shareRate=%.0f%%\n",
binding.ReferrerID, amount, commission, distributorShare*100)
// 更新推广者的待结算收益
db.Model(&model.User{}).Where("id = ?", binding.ReferrerID).
Update("pending_earnings", db.Raw("pending_earnings + ?", commission))
Update("pending_earnings", gorm.Expr("COALESCE(pending_earnings, 0) + ?", commission))
// 更新绑定记录COALESCE 避免 total_commission 为 NULL 时 NULL+?=NULL
db.Exec(`
UPDATE referral_bindings
SET last_purchase_date = NOW(),
purchase_count = COALESCE(purchase_count, 0) + 1,
total_commission = COALESCE(total_commission, 0) + ?
WHERE id = ?
`, commission, binding.ID)
db.Model(&model.ReferralBinding{}).Where("id = ?", binding.ID).Updates(map[string]interface{}{
"last_purchase_date": time.Now(),
"purchase_count": gorm.Expr("COALESCE(purchase_count, 0) + 1"),
"total_commission": gorm.Expr("COALESCE(total_commission, 0) + ?", commission),
})
fmt.Printf("[PayNotify] 分佣完成: 推广者 %s 获得 %.2f 元(第 %d 次购买,累计 %.2f 元)\n",
binding.ReferrerID, commission, newPurchaseCount, newTotalCommission)

View File

@@ -93,18 +93,16 @@ func WechatPhoneLogin(c *gin.Context) {
user.Phone = &phone
}
var orderRows []struct {
ProductID string `gorm:"column:product_id"`
}
db.Raw(`
SELECT DISTINCT product_id FROM orders WHERE user_id = ? AND status = 'paid' AND product_type = 'section'
`, user.ID).Scan(&orderRows)
purchasedSections := []string{}
for _, row := range orderRows {
if row.ProductID != "" {
purchasedSections = append(purchasedSections, row.ProductID)
var purchasedSections []string
database.DB().Model(&model.Order{}).Where("user_id = ? AND status = ? AND product_type = ?", user.ID, "paid", "section").
Distinct("product_id").Pluck("product_id", &purchasedSections)
filtered := make([]string, 0, len(purchasedSections))
for _, s := range purchasedSections {
if s != "" {
filtered = append(filtered, s)
}
}
purchasedSections = filtered
responseUser := map[string]interface{}{
"id": user.ID,

View File

@@ -57,7 +57,7 @@ func generateWithdrawID() string {
func WithdrawPost(c *gin.Context) {
var req struct {
UserID string `json:"userId" binding:"required"`
Amount float64 `json:"amount" binding:"required"`
Amount float64 `json:"amount" binding:"required,gte=0"`
UserName string `json:"userName"`
Remark string `json:"remark"`
}