chore: 清理敏感与开发文档,仅同步代码

- 永久忽略并从仓库移除 开发文档/
- 移除并忽略 .env 与小程序私有配置
- 同步小程序/管理端/API与脚本改动

Made-with: Cursor
This commit is contained in:
卡若
2026-03-17 17:50:12 +08:00
parent 868b0a10d9
commit 76965adb23
443 changed files with 24175 additions and 64154 deletions

View File

@@ -98,6 +98,9 @@ func MiniprogramLogin(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "创建用户失败"})
return
}
// 记录注册行为到 user_tracks
trackID := fmt.Sprintf("track_%d", time.Now().UnixNano()%100000000)
db.Create(&model.UserTrack{ID: trackID, UserID: user.ID, Action: "register"})
// 新用户异步调用神射手自动打标手机号尚未绑定phone 为空时暂不调用)
AdminShensheShouAutoTag(userID, "")
} else {
@@ -134,7 +137,7 @@ func MiniprogramLogin(c *gin.Context) {
"id": user.ID,
"openId": getStringValue(user.OpenID),
"nickname": getStringValue(user.Nickname),
"avatar": getStringValue(user.Avatar),
"avatar": getUrlValue(user.Avatar),
"phone": getStringValue(user.Phone),
"wechatId": getStringValue(user.WechatID),
"referralCode": getStringValue(user.ReferralCode),
@@ -160,6 +163,86 @@ func MiniprogramLogin(c *gin.Context) {
})
}
// MiniprogramDevLoginAs POST /api/miniprogram/dev/login-as 开发专用:按 userId 切换账号(仅 APP_ENV=development 可用)
func MiniprogramDevLoginAs(c *gin.Context) {
if strings.ToLower(strings.TrimSpace(os.Getenv("APP_ENV"))) != "development" {
c.JSON(http.StatusForbidden, gin.H{"success": false, "error": "仅开发环境可用"})
return
}
var req struct {
UserID string `json:"userId" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "缺少 userId"})
return
}
userID := strings.TrimSpace(req.UserID)
if userID == "" {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "userId 不能为空"})
return
}
db := database.DB()
var user model.User
if err := db.Where("id = ?", userID).First(&user).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"success": false, "error": "用户不存在"})
return
}
openID := getStringValue(user.OpenID)
if openID == "" {
openID = user.ID // 部分用户 id 即 openId
}
tokenSuffix := openID
if len(openID) >= 8 {
tokenSuffix = openID[len(openID)-8:]
}
token := fmt.Sprintf("tk_%s_%d", tokenSuffix, time.Now().Unix())
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)
}
}
if purchasedSections == nil {
purchasedSections = []string{}
}
responseUser := map[string]interface{}{
"id": user.ID,
"openId": openID,
"nickname": getStringValue(user.Nickname),
"avatar": getUrlValue(user.Avatar),
"phone": getStringValue(user.Phone),
"wechatId": getStringValue(user.WechatID),
"referralCode": getStringValue(user.ReferralCode),
"hasFullBook": getBoolValue(user.HasFullBook),
"purchasedSections": purchasedSections,
"earnings": getFloatValue(user.Earnings),
"pendingEarnings": getFloatValue(user.PendingEarnings),
"referralCount": getIntValue(user.ReferralCount),
"createdAt": user.CreatedAt,
}
if user.IsVip != nil {
responseUser["isVip"] = *user.IsVip
}
if user.VipExpireDate != nil {
responseUser["vipExpireDate"] = user.VipExpireDate.Format("2006-01-02")
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": map[string]interface{}{
"openId": openID,
"user": responseUser,
"token": token,
},
})
}
// 辅助函数
func getStringValue(ptr *string) string {
if ptr == nil {
@@ -168,6 +251,18 @@ func getStringValue(ptr *string) string {
return *ptr
}
// getUrlValue 取字符串指针值并修复缺少冒号的 URL"https//..." → "https://..."
func getUrlValue(ptr *string) string {
s := getStringValue(ptr)
if strings.HasPrefix(s, "https//") {
return "https://" + s[7:]
}
if strings.HasPrefix(s, "http//") {
return "http://" + s[6:]
}
return s
}
func getBoolValue(ptr *bool) bool {
if ptr == nil {
return false
@@ -222,54 +317,81 @@ func miniprogramPayPost(c *gin.Context) {
db := database.DB()
// 查询用户的有效推荐人(先查 binding再查 referralCode
var finalAmount float64
var orderSn string
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
}
}
if referrerID == nil && req.ReferralCode != "" {
var refUser model.User
if err := db.Where("referral_code = ?", req.ReferralCode).First(&refUser).Error; err == nil {
referrerID = &refUser.ID
}
}
// 有推荐人时应用好友优惠(无论是 binding 还是 referralCode
finalAmount := req.Amount
if referrerID != nil {
var cfg model.SystemConfig
if err := db.Where("config_key = ?", "referral_config").First(&cfg).Error; err == nil {
var config map[string]interface{}
if err := json.Unmarshal(cfg.ConfigValue, &config); err == nil {
if userDiscount, ok := config["userDiscount"].(float64); ok && userDiscount > 0 {
discountRate := userDiscount / 100
finalAmount = req.Amount * (1 - discountRate)
if finalAmount < 0.01 {
finalAmount = 0.01
if req.ProductType == "balance_recharge" {
// 充值从已创建的订单取金额productId=orderSn
var existOrder model.Order
if err := db.Where("order_sn = ? AND product_type = ? AND status = ?", req.ProductID, "balance_recharge", "created").First(&existOrder).Error; err != nil {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "充值订单不存在或已支付"})
return
}
orderSn = existOrder.OrderSN
finalAmount = existOrder.Amount
if req.UserID != "" && existOrder.UserID != req.UserID {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "订单用户不匹配"})
return
}
} else {
// -------- V1.1 后端价格:从 DB 读取标准价 --------
standardPrice, priceErr := getStandardPrice(db, req.ProductType, req.ProductID)
if priceErr != nil {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": priceErr.Error()})
return
}
finalAmount = standardPrice
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
}
}
if referrerID == nil && req.ReferralCode != "" {
var refUser model.User
if err := db.Where("referral_code = ?", req.ReferralCode).First(&refUser).Error; err == nil {
referrerID = &refUser.ID
}
}
if referrerID != nil {
var cfg model.SystemConfig
if err := db.Where("config_key = ?", "referral_config").First(&cfg).Error; err == nil {
var config map[string]interface{}
if err := json.Unmarshal(cfg.ConfigValue, &config); err == nil {
if userDiscount, ok := config["userDiscount"].(float64); ok && userDiscount > 0 {
discountRate := userDiscount / 100
finalAmount = finalAmount * (1 - discountRate)
if finalAmount < 0.01 {
finalAmount = 0.01
}
}
}
}
}
if req.Amount-finalAmount > 0.05 || finalAmount-req.Amount > 0.05 {
fmt.Printf("[PayCreate] 金额差异: 客户端=%.2f 后端=%.2f productType=%s productId=%s userId=%s\n",
req.Amount, finalAmount, req.ProductType, req.ProductID, req.UserID)
}
orderSn = wechat.GenerateOrderSn()
}
// 生成订单号
orderSn := wechat.GenerateOrderSn()
totalFee := int(finalAmount * 100) // 转为分
description := req.Description
if description == "" {
if req.ProductType == "fullbook" {
if req.ProductType == "balance_recharge" {
description = fmt.Sprintf("余额充值 ¥%.2f", finalAmount)
} else if req.ProductType == "fullbook" {
description = "《一场Soul的创业实验》全书"
} else if req.ProductType == "vip" {
description = "卡若创业派对VIP年度会员365天"
@@ -286,7 +408,6 @@ func miniprogramPayPost(c *gin.Context) {
clientIP = "127.0.0.1"
}
// 插入订单到数据库
userID := req.UserID
if userID == "" {
userID = req.OpenID
@@ -304,24 +425,27 @@ func miniprogramPayPost(c *gin.Context) {
}
}
status := "created"
order := model.Order{
ID: orderSn,
OrderSN: orderSn,
UserID: userID,
OpenID: req.OpenID,
ProductType: req.ProductType,
ProductID: &productID,
Amount: finalAmount,
Description: &description,
Status: &status,
ReferrerID: referrerID,
ReferralCode: &req.ReferralCode,
}
if err := db.Create(&order).Error; err != nil {
// 订单创建失败,但不中断支付流程
fmt.Printf("[MiniprogramPay] 插入订单失败: %v\n", err)
// 充值订单已存在,不重复创建
if req.ProductType != "balance_recharge" {
status := "created"
pm := "wechat"
order := model.Order{
ID: orderSn,
OrderSN: orderSn,
UserID: userID,
OpenID: req.OpenID,
ProductType: req.ProductType,
ProductID: &productID,
Amount: finalAmount,
Description: &description,
Status: &status,
ReferrerID: referrerID,
ReferralCode: &req.ReferralCode,
PaymentMethod: &pm,
}
if err := db.Create(&order).Error; err != nil {
fmt.Printf("[MiniprogramPay] 插入订单失败: %v\n", err)
}
}
attach := fmt.Sprintf(`{"productType":"%s","productId":"%s","userId":"%s"}`, req.ProductType, req.ProductID, userID)
@@ -372,7 +496,7 @@ func miniprogramPayGet(c *gin.Context) {
switch tradeState {
case "SUCCESS":
status = "paid"
// 若微信已支付,主动同步到本地 orders(不等 PayNotify便于购买次数即时生效
// V1.3 修复:主动同步到本地 orders并激活对应权益VIP/全书),避免等待 PayNotify 延迟
db := database.DB()
var order model.Order
if err := db.Where("order_sn = ?", orderSn).First(&order).Error; err == nil && order.Status != nil && *order.Status != "paid" {
@@ -382,7 +506,13 @@ func miniprogramPayGet(c *gin.Context) {
"transaction_id": transactionID,
"pay_time": now,
})
order.Status = strToPtr("paid")
order.PayTime = &now
orderPollLogf("主动同步订单已支付: %s", orderSn)
// 激活权益
if order.UserID != "" {
activateOrderBenefits(db, &order, now)
}
}
case "CLOSED", "REVOKED", "PAYERROR":
status = "failed"
@@ -408,9 +538,10 @@ func MiniprogramPayNotify(c *gin.Context) {
fmt.Printf("[PayNotify] 支付成功: orderSn=%s, transactionId=%s, amount=%.2f\n", orderSn, transactionID, totalAmount)
var attach struct {
ProductType string `json:"productType"`
ProductID string `json:"productId"`
UserID string `json:"userId"`
ProductType string `json:"productType"`
ProductID string `json:"productId"`
UserID string `json:"userId"`
GiftPayRequestSn string `json:"giftPayRequestSn"`
}
if attachStr != "" {
_ = json.Unmarshal([]byte(attachStr), &attach)
@@ -466,11 +597,12 @@ func MiniprogramPayNotify(c *gin.Context) {
} else if *order.Status != "paid" {
status := "paid"
now := time.Now()
if err := db.Model(&order).Updates(map[string]interface{}{
updates := map[string]interface{}{
"status": status,
"transaction_id": transactionID,
"pay_time": now,
}).Error; err != nil {
}
if err := db.Model(&order).Updates(updates).Error; err != nil {
fmt.Printf("[PayNotify] 更新订单状态失败: %s, err=%v\n", orderSn, err)
return fmt.Errorf("update order: %w", err)
}
@@ -479,35 +611,60 @@ func MiniprogramPayNotify(c *gin.Context) {
fmt.Printf("[PayNotify] 订单已支付,跳过更新: %s\n", orderSn)
}
if buyerUserID != "" && attach.ProductType != "" {
// 代付订单:更新 gift_pay_request、订单 payer_user_id
// 权益归属与分佣代付时归发起人order.UserID普通订单归 buyerUserID
beneficiaryUserID := buyerUserID
if attach.GiftPayRequestSn != "" && order.UserID != "" {
beneficiaryUserID = order.UserID
fmt.Printf("[PayNotify] 代付订单,权益归属发起人: %s\n", beneficiaryUserID)
}
if attach.GiftPayRequestSn != "" {
var payerUserID string
if openID != "" {
var payer model.User
if err := db.Where("open_id = ?", openID).First(&payer).Error; err == nil {
payerUserID = payer.ID
db.Model(&order).Update("payer_user_id", payerUserID)
}
}
db.Model(&model.GiftPayRequest{}).Where("request_sn = ?", attach.GiftPayRequestSn).
Updates(map[string]interface{}{
"status": "paid",
"payer_user_id": payerUserID,
"order_id": orderSn,
"updated_at": time.Now(),
})
}
if beneficiaryUserID != "" && attach.ProductType != "" {
if attach.ProductType == "fullbook" {
db.Model(&model.User{}).Where("id = ?", buyerUserID).Update("has_full_book", true)
fmt.Printf("[PayNotify] 用户已购全书: %s\n", buyerUserID)
db.Model(&model.User{}).Where("id = ?", beneficiaryUserID).Update("has_full_book", true)
fmt.Printf("[PayNotify] 用户已购全书: %s\n", beneficiaryUserID)
} else if attach.ProductType == "vip" {
// VIP 支付成功:更新 users.is_vip、vip_expire_date、vip_activated_at排序后付款在前
expireDate := time.Now().AddDate(0, 0, 365)
vipActivatedAt := time.Now()
if order.PayTime != nil {
vipActivatedAt = *order.PayTime
}
db.Model(&model.User{}).Where("id = ?", buyerUserID).Updates(map[string]interface{}{
"is_vip": true,
"vip_expire_date": expireDate,
"vip_activated_at": vipActivatedAt,
})
fmt.Printf("[VIP] 设置方式=支付设置, userId=%s, orderSn=%s, 过期日=%s, activatedAt=%s\n", buyerUserID, orderSn, expireDate.Format("2006-01-02"), vipActivatedAt.Format("2006-01-02 15:04:05"))
expireDate := activateVIP(db, beneficiaryUserID, 365, vipActivatedAt)
fmt.Printf("[VIP] 设置方式=支付设置, userId=%s, orderSn=%s, 过期日=%s, activatedAt=%s\n", beneficiaryUserID, orderSn, expireDate.Format("2006-01-02"), vipActivatedAt.Format("2006-01-02 15:04:05"))
} else if attach.ProductType == "match" {
fmt.Printf("[PayNotify] 用户购买匹配次数: %s订单 %s\n", buyerUserID, orderSn)
fmt.Printf("[PayNotify] 用户购买匹配次数: %s订单 %s\n", beneficiaryUserID, orderSn)
} else if attach.ProductType == "balance_recharge" {
if err := ConfirmBalanceRechargeByOrder(db, &order); err != nil {
fmt.Printf("[PayNotify] 余额充值确认失败: %s, err=%v\n", orderSn, err)
} else {
fmt.Printf("[PayNotify] 余额充值成功: %s, 金额 %.2f\n", beneficiaryUserID, totalAmount)
}
} 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,
beneficiaryUserID, attach.ProductID, orderSn,
).Count(&count)
if count == 0 {
fmt.Printf("[PayNotify] 用户首次购买章节: %s - %s\n", buyerUserID, attach.ProductID)
fmt.Printf("[PayNotify] 用户首次购买章节: %s - %s\n", beneficiaryUserID, attach.ProductID)
} else {
fmt.Printf("[PayNotify] 用户已有该章节的其他已支付订单: %s - %s\n", buyerUserID, attach.ProductID)
fmt.Printf("[PayNotify] 用户已有该章节的其他已支付订单: %s - %s\n", beneficiaryUserID, attach.ProductID)
}
}
productID := attach.ProductID
@@ -516,9 +673,9 @@ func MiniprogramPayNotify(c *gin.Context) {
}
db.Where(
"user_id = ? AND product_type = ? AND product_id = ? AND status = 'created' AND order_sn != ?",
buyerUserID, attach.ProductType, productID, orderSn,
beneficiaryUserID, attach.ProductType, productID, orderSn,
).Delete(&model.Order{})
processReferralCommission(db, buyerUserID, totalAmount, orderSn, &order)
processReferralCommission(db, beneficiaryUserID, totalAmount, orderSn, &order)
}
return nil
})
@@ -630,7 +787,13 @@ func MiniprogramPhone(c *gin.Context) {
if req.UserID != "" {
db := database.DB()
db.Model(&model.User{}).Where("id = ?", req.UserID).Update("phone", phoneNumber)
// 记录绑定手机号行为到 user_tracks
trackID := fmt.Sprintf("track_%d", time.Now().UnixNano()%100000000)
db.Create(&model.UserTrack{ID: trackID, UserID: req.UserID, Action: "bind_phone"})
fmt.Printf("[MiniprogramPhone] 手机号已绑定到用户: %s\n", req.UserID)
// 记录绑定手机行为
bindTrackID := fmt.Sprintf("track_%d", time.Now().UnixNano()%100000000)
database.DB().Create(&model.UserTrack{ID: bindTrackID, UserID: req.UserID, Action: "bind_phone"})
// 绑定手机号后,异步调用神射手自动完善标签
AdminShensheShouAutoTag(req.UserID, phoneNumber)
}
@@ -734,6 +897,45 @@ func MiniprogramQrcodeImage(c *gin.Context) {
c.Data(http.StatusOK, "image/png", imageData)
}
// GiftLinkGet GET /api/miniprogram/gift/link 代付链接(需登录,传 userId
// 返回 path、ref、scene供 gift-link 页展示与复制qrcodeImageUrl 供生成小程序码
func GiftLinkGet(c *gin.Context) {
userID := c.Query("userId")
if userID == "" {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "缺少 userId请先登录"})
return
}
db := database.DB()
var user model.User
if err := db.Where("id = ?", userID).First(&user).Error; err != nil {
if err == gorm.ErrRecordNotFound {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "用户不存在"})
return
}
c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()})
return
}
ref := getStringValue(user.ReferralCode)
if ref == "" {
suffix := userID
if len(userID) >= 6 {
suffix = userID[len(userID)-6:]
}
ref = "SOUL" + strings.ToUpper(suffix)
}
path := fmt.Sprintf("pages/gift-link/gift-link?ref=%s&gift=1", ref)
scene := fmt.Sprintf("ref_%s_gift_1", strings.ReplaceAll(ref, "&", "_"))
if len(scene) > 32 {
scene = scene[:32]
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"path": path,
"ref": ref,
"scene": scene,
})
}
// base64 编码
func base64Encode(data []byte) string {
const base64Table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
@@ -783,14 +985,17 @@ func MiniprogramUsers(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": true, "data": nil})
return
}
var cnt int64
db.Model(&model.Order{}).Where("user_id = ? AND status = ? AND (product_type = ? OR product_type = ?)",
id, "paid", "fullbook", "vip").Count(&cnt)
// V4.1 修复is_vip 同时校验过期时间is_vip=1 且 vip_expire_date>NOW而非仅凭订单数量
isVipActive, _ := isVipFromUsers(db, id)
if !isVipActive {
// 兜底orders 表有有效 VIP 订单
isVipActive, _ = isVipFromOrders(db, id)
}
// 用户信息与会员资料vip*、P3 资料扩展,供会员详情页完整展示
item := gin.H{
"id": user.ID,
"nickname": getStringValue(user.Nickname),
"avatar": getStringValue(user.Avatar),
"avatar": getUrlValue(user.Avatar),
"phone": getStringValue(user.Phone),
"wechatId": getStringValue(user.WechatID),
"vipName": getStringValue(user.VipName),
@@ -810,7 +1015,7 @@ func MiniprogramUsers(c *gin.Context) {
"helpOffer": getStringValue(user.HelpOffer),
"helpNeed": getStringValue(user.HelpNeed),
"projectIntro": getStringValue(user.ProjectIntro),
"is_vip": cnt > 0,
"is_vip": isVipActive,
}
c.JSON(http.StatusOK, gin.H{"success": true, "data": item})
return
@@ -821,15 +1026,121 @@ func MiniprogramUsers(c *gin.Context) {
list := make([]gin.H, 0, len(users))
for i := range users {
u := &users[i]
var cnt int64
db.Model(&model.Order{}).Where("user_id = ? AND status = ? AND (product_type = ? OR product_type = ?)",
u.ID, "paid", "fullbook", "vip").Count(&cnt)
// V4.1is_vip 同时校验过期时间
uvip, _ := isVipFromUsers(db, u.ID)
if !uvip {
uvip, _ = isVipFromOrders(db, u.ID)
}
list = append(list, gin.H{
"id": u.ID,
"nickname": getStringValue(u.Nickname),
"avatar": getStringValue(u.Avatar),
"is_vip": cnt > 0,
"avatar": getUrlValue(u.Avatar),
"is_vip": uvip,
})
}
c.JSON(http.StatusOK, gin.H{"success": true, "data": list})
}
// strToPtr 返回字符串指针(辅助函数)
func strToPtr(s string) *string { return &s }
// activateVIP 为用户激活 VIP续费时从 max(now, vip_expire_date) 累加 days 天
// 返回最终过期时间
func activateVIP(db *gorm.DB, userID string, days int, activatedAt time.Time) time.Time {
var u model.User
db.Select("id", "is_vip", "vip_expire_date").Where("id = ?", userID).First(&u)
base := activatedAt
if u.VipExpireDate != nil && u.VipExpireDate.After(base) {
base = *u.VipExpireDate // 续费累加
}
expireDate := base.AddDate(0, 0, days)
db.Model(&model.User{}).Where("id = ?", userID).Updates(map[string]interface{}{
"is_vip": true,
"vip_expire_date": expireDate,
"vip_activated_at": activatedAt,
})
return expireDate
}
// activateOrderBenefits 订单支付成功后激活对应权益VIP / 全书 / 余额充值)
func activateOrderBenefits(db *gorm.DB, order *model.Order, payTime time.Time) {
if order == nil {
return
}
userID := order.UserID
productType := order.ProductType
switch productType {
case "fullbook":
db.Model(&model.User{}).Where("id = ?", userID).Update("has_full_book", true)
case "vip":
activateVIP(db, userID, 365, payTime)
case "balance_recharge":
ConfirmBalanceRechargeByOrder(db, order)
}
}
// getStandardPrice 从 DB 读取商品标准价(后端校验用),防止客户端篡改金额
// productType: fullbook / vip / section / match
// productId: 章节购买时为章节 ID
func getStandardPrice(db *gorm.DB, productType, productID string) (float64, error) {
switch productType {
case "fullbook", "vip", "match":
// 从 system_config 读取
configKey := "chapter_config"
if productType == "vip" {
configKey = "vip_config"
}
var row model.SystemConfig
if err := db.Where("config_key = ?", configKey).First(&row).Error; err == nil {
var cfg map[string]interface{}
if json.Unmarshal(row.ConfigValue, &cfg) == nil {
fieldMap := map[string]string{
"fullbook": "fullbookPrice",
"vip": "price",
"match": "matchPrice",
}
if v, ok := cfg[fieldMap[productType]].(float64); ok && v > 0 {
return v, nil
}
}
}
// 兜底默认值
defaults := map[string]float64{"fullbook": 9.9, "vip": 1980, "match": 68}
if p, ok := defaults[productType]; ok {
return p, nil
}
return 0, fmt.Errorf("未知商品类型: %s", productType)
case "section", "gift":
if productID == "" {
return 0, fmt.Errorf("单章购买缺少 productId")
}
var ch model.Chapter
if err := db.Select("id", "price", "is_free").Where("id = ?", productID).First(&ch).Error; err != nil {
return 0, fmt.Errorf("章节不存在: %s", productID)
}
if ch.IsFree != nil && *ch.IsFree {
return 0, fmt.Errorf("该章节为免费章节,无需支付")
}
if ch.Price == nil || *ch.Price <= 0 {
return 0, fmt.Errorf("章节价格未配置: %s", productID)
}
return *ch.Price, nil
case "balance_recharge":
if productID == "" {
return 0, fmt.Errorf("充值订单号缺失")
}
var order model.Order
if err := db.Where("order_sn = ? AND product_type = ?", productID, "balance_recharge").First(&order).Error; err != nil {
return 0, fmt.Errorf("充值订单不存在: %s", productID)
}
if order.Amount <= 0 {
return 0, fmt.Errorf("充值金额无效")
}
return order.Amount, nil
default:
return 0, fmt.Errorf("未知商品类型: %s", productType)
}
}