新增技术文档,详细描述了项目的技术栈、配置、鉴权与安全、数据层等内容。同时,更新小程序页面以支持收益数据的加载与刷新功能,优化用户体验。新增收益接口以返回用户的累计收益和可提现金额,并调整相关逻辑以确保数据准确性。

This commit is contained in:
乘风
2026-02-11 12:05:54 +08:00
parent a174d8e16d
commit 2c9364fd2f
11 changed files with 276 additions and 55 deletions

View File

@@ -18,11 +18,12 @@ type Config struct {
Version string // APP_VERSION打包/部署前写在 .env/health 返回
// 微信小程序配置
WechatAppID string
WechatAppSecret string
WechatMchID string
WechatMchKey string
WechatNotifyURL string
WechatAppID string
WechatAppSecret string
WechatMchID string
WechatMchKey string
WechatNotifyURL string
WechatMiniProgramState string // 订阅消息跳转版本developer/formal从 .env WECHAT_MINI_PROGRAM_STATE 读取
// 微信转账配置API v3
WechatAPIv3Key string
@@ -119,6 +120,10 @@ func Load() (*Config, error) {
if wechatNotifyURL == "" {
wechatNotifyURL = "https://soul.quwanzhi.com/api/miniprogram/pay/notify" // 默认回调地址
}
wechatMiniProgramState := os.Getenv("WECHAT_MINI_PROGRAM_STATE")
if wechatMiniProgramState != "developer" && wechatMiniProgramState != "trial" {
wechatMiniProgramState = "formal" // 默认正式版
}
// 转账配置
wechatAPIv3Key := os.Getenv("WECHAT_APIV3_KEY")
@@ -162,12 +167,13 @@ func Load() (*Config, error) {
TrustedProxies: []string{"127.0.0.1", "::1"},
CORSOrigins: parseCORSOrigins(),
Version: version,
WechatAppID: wechatAppID,
WechatAppSecret: wechatAppSecret,
WechatMchID: wechatMchID,
WechatMchKey: wechatMchKey,
WechatNotifyURL: wechatNotifyURL,
WechatAPIv3Key: wechatAPIv3Key,
WechatAppID: wechatAppID,
WechatAppSecret: wechatAppSecret,
WechatMchID: wechatMchID,
WechatMchKey: wechatMchKey,
WechatNotifyURL: wechatNotifyURL,
WechatMiniProgramState: wechatMiniProgramState,
WechatAPIv3Key: wechatAPIv3Key,
WechatCertPath: wechatCertPath,
WechatKeyPath: wechatKeyPath,
WechatSerialNo: wechatSerialNo,

View File

@@ -429,6 +429,67 @@ func round(val float64, precision int) float64 {
return math.Round(val*ratio) / ratio
}
// MyEarnings GET /api/miniprogram/earnings 仅返回「我的收益」卡片所需数据(累计、可提现、推荐人数),用于我的页展示与刷新
func MyEarnings(c *gin.Context) {
userId := c.Query("userId")
if userId == "" {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "用户ID不能为空"})
return
}
db := database.DB()
distributorShare := 0.9
var cfg model.SystemConfig
if err := db.Where("config_key = ?", "referral_config").First(&cfg).Error; err == nil {
var config map[string]interface{}
if _ = json.Unmarshal(cfg.ConfigValue, &config); config["distributorShare"] != nil {
if share, ok := config["distributorShare"].(float64); ok {
distributorShare = share / 100
}
}
}
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
}
var paidOrders []struct {
Amount float64
}
db.Model(&model.Order{}).
Select("amount").
Where("referrer_id = ? AND status = 'paid'", userId).
Find(&paidOrders)
totalAmount := 0.0
for _, o := range paidOrders {
totalAmount += o.Amount
}
var pendingWithdraw struct{ Total float64 }
db.Model(&model.Withdrawal{}).
Select("COALESCE(SUM(amount), 0) as total").
Where("user_id = ? AND status IN ?", userId, []string{"pending", "processing", "pending_confirm"}).
Scan(&pendingWithdraw)
var successWithdraw struct{ Total float64 }
db.Model(&model.Withdrawal{}).
Select("COALESCE(SUM(amount), 0) as total").
Where("user_id = ? AND status = ?", userId, "success").
Scan(&successWithdraw)
totalCommission := totalAmount * distributorShare
pendingWithdrawAmount := pendingWithdraw.Total
withdrawnFromTable := successWithdraw.Total
availableEarnings := totalCommission - withdrawnFromTable - pendingWithdrawAmount
if availableEarnings < 0 {
availableEarnings = 0
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": gin.H{
"totalCommission": round(totalCommission, 2),
"availableEarnings": round(availableEarnings, 2),
"referralCount": getIntValue(user.ReferralCount),
},
})
}
// ReferralVisit POST /api/referral/visit 记录推荐访问(不需登录)
func ReferralVisit(c *gin.Context) {
var req struct {

View File

@@ -223,6 +223,7 @@ func Setup(cfg *config.Config) *gin.Engine {
miniprogram.POST("/referral/visit", handler.ReferralVisit)
miniprogram.POST("/referral/bind", handler.ReferralBind)
miniprogram.GET("/referral/data", handler.ReferralData)
miniprogram.GET("/earnings", handler.MyEarnings)
miniprogram.GET("/match/config", handler.MatchConfigGet)
miniprogram.POST("/match/users", handler.MatchUsers)
miniprogram.POST("/ckb/join", handler.CKBJoin)

View File

@@ -419,8 +419,8 @@ func SendWithdrawSubscribeMessage(ctx context.Context, openID string, amount flo
"thing8": object.HashMap{"value": thing8},
}
state := "formal"
if cfg != nil && cfg.Mode == "debug" {
state = "developer"
if cfg != nil && cfg.WechatMiniProgramState != "" {
state = cfg.WechatMiniProgramState
}
_, err := miniProgramApp.SubscribeMessage.Send(ctx, &subrequest.RequestSubscribeMessageSend{
ToUser: openID,