package controller import ( "errors" "strconv" "strings" "github.com/QuantumNous/new-api/common" "github.com/QuantumNous/new-api/model" "github.com/gin-gonic/gin" ) const ( maxAffiliateRatePercent = 100 maxAffiliateFrozenDays = 3650 maxAffiliateBatchUsers = 1000 ) type affiliateUserSettingsRequest struct { AffRebateRatePercent int `json:"aff_rebate_rate_percent"` AffRebateFrozenDays int `json:"aff_rebate_frozen_days"` } type affiliateBatchRateRequest struct { UserIds []int `json:"user_ids"` AffRebateRatePercent int `json:"aff_rebate_rate_percent"` } type affiliateConfigRequest struct { AffRebateRatePercent *int `json:"aff_rebate_rate_percent"` AffRebateFrozenDays *int `json:"aff_rebate_frozen_days"` AffRebateEnabled *bool `json:"aff_rebate_enabled"` } func AdminGetAffiliateOverview(c *gin.Context) { overview, err := model.GetAffiliateOverview() if err != nil { common.ApiError(c, err) return } common.ApiSuccess(c, overview) } func AdminGetAffiliateRebates(c *gin.Context) { pageInfo := common.GetPageQuery(c) status := strings.TrimSpace(c.Query("status")) if status != "" && status != "frozen" && status != "released" { common.ApiErrorMsg(c, "status 只能是 frozen 或 released") return } userId := 0 if userIdText := strings.TrimSpace(c.Query("user_id")); userIdText != "" { parsed, err := strconv.Atoi(userIdText) if err != nil || parsed <= 0 { common.ApiErrorMsg(c, "user_id 参数格式不正确") return } userId = parsed } records, total, err := model.GetRebateRecords(pageInfo.GetPage(), pageInfo.GetPageSize(), status, userId) if err != nil { common.ApiError(c, err) return } pageInfo.SetTotal(int(total)) pageInfo.SetItems(records) common.ApiSuccess(c, pageInfo) } func AdminGetAffiliateInvites(c *gin.Context) { pageInfo := common.GetPageQuery(c) userId, ok := parseOptionalPositiveQuery(c, "user_id") if !ok { return } startTimestamp, ok := parseOptionalTimestampQuery(c, "start_timestamp") if !ok { return } endTimestamp, ok := parseOptionalTimestampQuery(c, "end_timestamp") if !ok { return } invites, total, err := model.GetAffiliateInvites(pageInfo.GetPage(), pageInfo.GetPageSize(), userId, startTimestamp, endTimestamp, c.Query("keyword")) if err != nil { common.ApiError(c, err) return } pageInfo.SetTotal(int(total)) pageInfo.SetItems(invites) common.ApiSuccess(c, pageInfo) } func AdminGetAffiliateTransfers(c *gin.Context) { pageInfo := common.GetPageQuery(c) userId, ok := parseOptionalPositiveQuery(c, "user_id") if !ok { return } startTimestamp, ok := parseOptionalTimestampQuery(c, "start_timestamp") if !ok { return } endTimestamp, ok := parseOptionalTimestampQuery(c, "end_timestamp") if !ok { return } logs, total, err := model.GetAffiliateTransferLogs(pageInfo.GetPage(), pageInfo.GetPageSize(), userId, startTimestamp, endTimestamp) if err != nil { common.ApiError(c, err) return } pageInfo.SetTotal(int(total)) pageInfo.SetItems(logs) common.ApiSuccess(c, pageInfo) } func AdminGetAffiliateUsers(c *gin.Context) { pageInfo := common.GetPageQuery(c) users, total, err := model.GetAffiliateUsers(pageInfo.GetPage(), pageInfo.GetPageSize(), c.Query("keyword")) if err != nil { common.ApiError(c, err) return } pageInfo.SetTotal(int(total)) pageInfo.SetItems(users) common.ApiSuccess(c, pageInfo) } func AdminGetAffiliateUserOverview(c *gin.Context) { userId, ok := getAffiliateUserIdParam(c) if !ok { return } overview, err := model.GetAffiliateUserOverview(userId) if err != nil { common.ApiError(c, err) return } common.ApiSuccess(c, overview) } func AdminUpdateAffiliateUserSettings(c *gin.Context) { userId, ok := getAffiliateUserIdParam(c) if !ok { return } var req affiliateUserSettingsRequest if err := c.ShouldBindJSON(&req); err != nil { common.ApiError(c, err) return } if err := validateAffiliateRate(req.AffRebateRatePercent); err != nil { common.ApiErrorMsg(c, err.Error()) return } if err := validateAffiliateFrozenDays(req.AffRebateFrozenDays); err != nil { common.ApiErrorMsg(c, err.Error()) return } user, err := model.UpdateUserAffiliateSettings(userId, req.AffRebateRatePercent, req.AffRebateFrozenDays) if err != nil { common.ApiError(c, err) return } common.ApiSuccess(c, user) } func AdminBatchUpdateAffiliateRate(c *gin.Context) { var req affiliateBatchRateRequest if err := c.ShouldBindJSON(&req); err != nil { common.ApiError(c, err) return } if len(req.UserIds) == 0 { common.ApiErrorMsg(c, "user_ids 不能为空") return } if len(req.UserIds) > maxAffiliateBatchUsers { common.ApiErrorMsg(c, "user_ids 一次最多 1000 个") return } if err := validateAffiliateRate(req.AffRebateRatePercent); err != nil { common.ApiErrorMsg(c, err.Error()) return } userIds := uniquePositiveUserIds(req.UserIds) if len(userIds) != len(req.UserIds) { common.ApiErrorMsg(c, "user_ids 必须是正整数且不能重复") return } affected, err := model.BatchUpdateUserAffiliateRate(userIds, req.AffRebateRatePercent) if err != nil { common.ApiError(c, err) return } common.ApiSuccess(c, gin.H{"affected": affected}) } func AdminClearAffiliateUserSettings(c *gin.Context) { userId, ok := getAffiliateUserIdParam(c) if !ok { return } user, err := model.ClearUserAffiliateSettings(userId) if err != nil { common.ApiError(c, err) return } common.ApiSuccess(c, user) } func AdminUpdateAffiliateConfig(c *gin.Context) { var req affiliateConfigRequest if err := c.ShouldBindJSON(&req); err != nil { common.ApiError(c, err) return } if req.AffRebateRatePercent != nil { if err := validateAffiliateRate(*req.AffRebateRatePercent); err != nil { common.ApiErrorMsg(c, err.Error()) return } } if req.AffRebateFrozenDays != nil { if err := validateAffiliateFrozenDays(*req.AffRebateFrozenDays); err != nil { common.ApiErrorMsg(c, err.Error()) return } } if req.AffRebateRatePercent != nil { if err := model.UpdateOption(model.AffRebateRatePercentKey, strconv.Itoa(*req.AffRebateRatePercent)); err != nil { common.ApiError(c, err) return } } if req.AffRebateFrozenDays != nil { if err := model.UpdateOption(model.AffRebateFrozenDaysKey, strconv.Itoa(*req.AffRebateFrozenDays)); err != nil { common.ApiError(c, err) return } } if req.AffRebateEnabled != nil { if err := model.UpdateOption(model.AffRebateEnabledKey, strconv.FormatBool(*req.AffRebateEnabled)); err != nil { common.ApiError(c, err) return } } common.ApiSuccess(c, getAffiliateConfigSnapshot()) } func getAffiliateUserIdParam(c *gin.Context) (int, bool) { userId, err := strconv.Atoi(c.Param("user_id")) if err != nil || userId <= 0 { common.ApiErrorMsg(c, "user_id 参数格式不正确") return 0, false } return userId, true } func parseOptionalPositiveQuery(c *gin.Context, key string) (int, bool) { value := strings.TrimSpace(c.Query(key)) if value == "" { return 0, true } parsed, err := strconv.Atoi(value) if err != nil || parsed <= 0 { common.ApiErrorMsg(c, key+" 参数格式不正确") return 0, false } return parsed, true } func parseOptionalTimestampQuery(c *gin.Context, key string) (int64, bool) { value := strings.TrimSpace(c.Query(key)) if value == "" { return 0, true } parsed, err := strconv.ParseInt(value, 10, 64) if err != nil || parsed < 0 { common.ApiErrorMsg(c, key+" 参数格式不正确") return 0, false } return parsed, true } func validateAffiliateRate(rate int) error { if rate < 0 || rate > maxAffiliateRatePercent { return errors.New("返利比例必须在 0-100 之间") } return nil } func validateAffiliateFrozenDays(days int) error { if days < 0 || days > maxAffiliateFrozenDays { return errors.New("冻结天数必须在 0-3650 之间") } return nil } func uniquePositiveUserIds(userIds []int) []int { seen := make(map[int]struct{}, len(userIds)) uniqueIds := make([]int, 0, len(userIds)) for _, userId := range userIds { if userId <= 0 { return uniqueIds } if _, ok := seen[userId]; ok { return uniqueIds } seen[userId] = struct{}{} uniqueIds = append(uniqueIds, userId) } return uniqueIds } func getAffiliateConfigSnapshot() gin.H { common.OptionMapRWMutex.RLock() defer common.OptionMapRWMutex.RUnlock() return gin.H{ "aff_rebate_rate_percent": common.OptionMap[model.AffRebateRatePercentKey], "aff_rebate_frozen_days": common.OptionMap[model.AffRebateFrozenDaysKey], "aff_rebate_enabled": common.OptionMap[model.AffRebateEnabledKey], } }