feat: add rebate data models

Add affiliate rebate configuration fields, option defaults, and rebate record persistence for the rebate workflow.
This commit is contained in:
zizi 2026-05-20 13:55:41 +08:00
parent 959ee42bcb
commit 16b2853d62
4 changed files with 105 additions and 31 deletions

View File

@ -288,6 +288,7 @@ func migrateDB() error {
&ChannelMonitor{},
&ChannelMonitorHistory{},
&ChannelMonitorDailyRollup{},
&RebateRecord{},
)
if err != nil {
return err
@ -344,6 +345,7 @@ func migrateDBFast() error {
{&ChannelMonitor{}, "ChannelMonitor"},
{&ChannelMonitorHistory{}, "ChannelMonitorHistory"},
{&ChannelMonitorDailyRollup{}, "ChannelMonitorDailyRollup"},
{&RebateRecord{}, "RebateRecord"},
}
// 动态计算migration数量确保errChan缓冲区足够大
errChan := make(chan error, len(migrations))

View File

@ -19,6 +19,12 @@ type Option struct {
Value string `json:"value"`
}
const (
AffRebateRatePercentKey = "AffRebateRatePercent"
AffRebateFrozenDaysKey = "AffRebateFrozenDays"
AffRebateEnabledKey = "AffRebateEnabled"
)
func AllOption() ([]*Option, error) {
var options []*Option
var err error
@ -135,6 +141,9 @@ func InitOptionMap() {
common.OptionMap["QuotaForNewUser"] = strconv.Itoa(common.QuotaForNewUser)
common.OptionMap["QuotaForInviter"] = strconv.Itoa(common.QuotaForInviter)
common.OptionMap["QuotaForInvitee"] = strconv.Itoa(common.QuotaForInvitee)
common.OptionMap[AffRebateRatePercentKey] = "0"
common.OptionMap[AffRebateFrozenDaysKey] = "0"
common.OptionMap[AffRebateEnabledKey] = strconv.FormatBool(false)
common.OptionMap["QuotaRemindThreshold"] = strconv.Itoa(common.QuotaRemindThreshold)
common.OptionMap["PreConsumedQuota"] = strconv.Itoa(common.PreConsumedQuota)
common.OptionMap["ModelRequestRateLimitCount"] = strconv.Itoa(setting.ModelRequestRateLimitCount)

61
model/rebate_record.go Normal file
View File

@ -0,0 +1,61 @@
package model
import "time"
type RebateRecord struct {
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
InviterId int `json:"inviter_id" gorm:"index;not null"`
InviteeId int `json:"invitee_id" gorm:"index;not null"`
OrderId int `json:"order_id" gorm:"index"`
OrderType string `json:"order_type" gorm:"type:varchar(16);not null"`
OrderAmount int `json:"order_amount" gorm:"not null"`
RebateAmount int `json:"rebate_amount" gorm:"not null"`
RatePercent int `json:"rate_percent" gorm:"not null"`
Status string `json:"status" gorm:"type:varchar(16);not null;default:'frozen';index"`
FrozenUntil *time.Time `json:"frozen_until" gorm:"index"`
ReleasedAt *time.Time `json:"released_at"`
CreatedAt time.Time `json:"created_at"`
}
func (RebateRecord) TableName() string {
return "rebate_records"
}
func (r *RebateRecord) Insert() error {
return DB.Create(r).Error
}
func (r *RebateRecord) Update() error {
return DB.Save(r).Error
}
func GetFrozenRebatesDue() ([]RebateRecord, error) {
var records []RebateRecord
err := DB.Where("status = ? AND frozen_until IS NOT NULL AND frozen_until <= ?", "frozen", time.Now()).
Order("id ASC").
Find(&records).Error
return records, err
}
func GetRebateRecords(page int, pageSize int, status string, userId int) ([]RebateRecord, int64, error) {
var records []RebateRecord
var total int64
query := DB.Model(&RebateRecord{})
if status != "" {
query = query.Where("status = ?", status)
}
if userId > 0 {
query = query.Where("inviter_id = ? OR invitee_id = ?", userId, userId)
}
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
if page < 1 {
page = 1
}
if pageSize < 1 {
pageSize = 20
}
err := query.Order("id DESC").Offset((page - 1) * pageSize).Limit(pageSize).Find(&records).Error
return records, total, err
}

View File

@ -22,37 +22,39 @@ const UserNameMaxLength = 20
// User if you add sensitive fields, don't forget to clean them in setupLogin function.
// Otherwise, the sensitive information will be saved on local storage in plain text!
type User struct {
Id int `json:"id"`
Username string `json:"username" gorm:"unique;index" validate:"max=20"`
Password string `json:"password" gorm:"not null;" validate:"min=8,max=20"`
OriginalPassword string `json:"original_password" gorm:"-:all"` // this field is only for Password change verification, don't save it to database!
DisplayName string `json:"display_name" gorm:"index" validate:"max=20"`
Role int `json:"role" gorm:"type:int;default:1"` // admin, common
Status int `json:"status" gorm:"type:int;default:1"` // enabled, disabled
Email string `json:"email" gorm:"index" validate:"max=50"`
GitHubId string `json:"github_id" gorm:"column:github_id;index"`
DiscordId string `json:"discord_id" gorm:"column:discord_id;index"`
OidcId string `json:"oidc_id" gorm:"column:oidc_id;index"`
WeChatId string `json:"wechat_id" gorm:"column:wechat_id;index"`
TelegramId string `json:"telegram_id" gorm:"column:telegram_id;index"`
VerificationCode string `json:"verification_code" gorm:"-:all"` // this field is only for Email verification, don't save it to database!
AccessToken *string `json:"access_token" gorm:"type:char(32);column:access_token;uniqueIndex"` // this token is for system management
Quota int `json:"quota" gorm:"type:int;default:0"`
UsedQuota int `json:"used_quota" gorm:"type:int;default:0;column:used_quota"` // used quota
RequestCount int `json:"request_count" gorm:"type:int;default:0;"` // request number
Group string `json:"group" gorm:"type:varchar(64);default:'default'"`
AffCode string `json:"aff_code" gorm:"type:varchar(32);column:aff_code;uniqueIndex"`
AffCount int `json:"aff_count" gorm:"type:int;default:0;column:aff_count"`
AffQuota int `json:"aff_quota" gorm:"type:int;default:0;column:aff_quota"` // 邀请剩余额度
AffHistoryQuota int `json:"aff_history_quota" gorm:"type:int;default:0;column:aff_history"` // 邀请历史额度
InviterId int `json:"inviter_id" gorm:"type:int;column:inviter_id;index"`
DeletedAt gorm.DeletedAt `gorm:"index"`
LinuxDOId string `json:"linux_do_id" gorm:"column:linux_do_id;index"`
Setting string `json:"setting" gorm:"type:text;column:setting"`
Remark string `json:"remark,omitempty" gorm:"type:varchar(255)" validate:"max=255"`
StripeCustomer string `json:"stripe_customer" gorm:"type:varchar(64);column:stripe_customer;index"`
CreatedAt int64 `json:"created_at" gorm:"autoCreateTime;column:created_at"`
LastLoginAt int64 `json:"last_login_at" gorm:"default:0;column:last_login_at"`
Id int `json:"id"`
Username string `json:"username" gorm:"unique;index" validate:"max=20"`
Password string `json:"password" gorm:"not null;" validate:"min=8,max=20"`
OriginalPassword string `json:"original_password" gorm:"-:all"` // this field is only for Password change verification, don't save it to database!
DisplayName string `json:"display_name" gorm:"index" validate:"max=20"`
Role int `json:"role" gorm:"type:int;default:1"` // admin, common
Status int `json:"status" gorm:"type:int;default:1"` // enabled, disabled
Email string `json:"email" gorm:"index" validate:"max=50"`
GitHubId string `json:"github_id" gorm:"column:github_id;index"`
DiscordId string `json:"discord_id" gorm:"column:discord_id;index"`
OidcId string `json:"oidc_id" gorm:"column:oidc_id;index"`
WeChatId string `json:"wechat_id" gorm:"column:wechat_id;index"`
TelegramId string `json:"telegram_id" gorm:"column:telegram_id;index"`
VerificationCode string `json:"verification_code" gorm:"-:all"` // this field is only for Email verification, don't save it to database!
AccessToken *string `json:"access_token" gorm:"type:char(32);column:access_token;uniqueIndex"` // this token is for system management
Quota int `json:"quota" gorm:"type:int;default:0"`
UsedQuota int `json:"used_quota" gorm:"type:int;default:0;column:used_quota"` // used quota
RequestCount int `json:"request_count" gorm:"type:int;default:0;"` // request number
Group string `json:"group" gorm:"type:varchar(64);default:'default'"`
AffCode string `json:"aff_code" gorm:"type:varchar(32);column:aff_code;uniqueIndex"`
AffCount int `json:"aff_count" gorm:"type:int;default:0;column:aff_count"`
AffQuota int `json:"aff_quota" gorm:"type:int;default:0;column:aff_quota"` // 邀请剩余额度
AffHistoryQuota int `json:"aff_history_quota" gorm:"type:int;default:0;column:aff_history"` // 邀请历史额度
AffRebateRatePercent int `json:"aff_rebate_rate_percent" gorm:"type:int;default:0"`
AffRebateFrozenDays int `json:"aff_rebate_frozen_days" gorm:"type:int;default:0"`
InviterId int `json:"inviter_id" gorm:"type:int;column:inviter_id;index"`
DeletedAt gorm.DeletedAt `gorm:"index"`
LinuxDOId string `json:"linux_do_id" gorm:"column:linux_do_id;index"`
Setting string `json:"setting" gorm:"type:text;column:setting"`
Remark string `json:"remark,omitempty" gorm:"type:varchar(255)" validate:"max=255"`
StripeCustomer string `json:"stripe_customer" gorm:"type:varchar(64);column:stripe_customer;index"`
CreatedAt int64 `json:"created_at" gorm:"autoCreateTime;column:created_at"`
LastLoginAt int64 `json:"last_login_at" gorm:"default:0;column:last_login_at"`
}
func (user *User) ToBaseUser() *UserBase {