feat: 新增经济系统
This commit is contained in:
parent
1af0a0527a
commit
74182ad662
File diff suppressed because it is too large
Load Diff
@ -211,7 +211,6 @@ func (ctrl *TaskController) ClaimDailyTask(c *gin.Context) {
|
||||
response.Success(c, map[string]interface{}{
|
||||
"success": resp.Success,
|
||||
"crystal_balance": resp.CrystalBalance,
|
||||
"experience": resp.Experience,
|
||||
})
|
||||
}
|
||||
|
||||
@ -270,7 +269,6 @@ func (ctrl *TaskController) ClaimAllDailyTasks(c *gin.Context) {
|
||||
response.Success(c, map[string]interface{}{
|
||||
"claimed_count": resp.ClaimedCount,
|
||||
"crystal_balance": resp.CrystalBalance,
|
||||
"experience": resp.Experience,
|
||||
"claimed_task_keys": resp.ClaimedTaskKeys,
|
||||
})
|
||||
}
|
||||
@ -481,7 +479,6 @@ func (ctrl *TaskController) ClaimOnboardingReward(c *gin.Context) {
|
||||
response.Success(c, map[string]interface{}{
|
||||
"success": resp.Success,
|
||||
"crystal_balance": resp.CrystalBalance,
|
||||
"experience": resp.Experience,
|
||||
})
|
||||
}
|
||||
|
||||
@ -677,7 +674,6 @@ func convertDailyTasksResponse(resp *pbTask.GetDailyTasksResponse) map[string]in
|
||||
"name": task.Name,
|
||||
"description": task.Description,
|
||||
"crystal_reward": task.CrystalReward,
|
||||
"exp_reward": task.ExpReward,
|
||||
"status": task.Status,
|
||||
"can_claim": task.CanClaim,
|
||||
})
|
||||
@ -696,7 +692,6 @@ func convertOnboardingStatusResponse(resp *pbTask.GetOnboardingStatusResponse) m
|
||||
"name": stage.Name,
|
||||
"required_task_keys": stage.RequiredTaskKeys,
|
||||
"crystal_reward": stage.CrystalReward,
|
||||
"exp_reward": stage.ExpReward,
|
||||
"status": stage.Status,
|
||||
"is_current": stage.IsCurrent,
|
||||
})
|
||||
@ -717,7 +712,6 @@ func convertAdvanceStageResponse(resp *pbTask.AdvanceStageResponse) map[string]i
|
||||
"name": stage.Name,
|
||||
"required_task_keys": stage.RequiredTaskKeys,
|
||||
"crystal_reward": stage.CrystalReward,
|
||||
"exp_reward": stage.ExpReward,
|
||||
"status": stage.Status,
|
||||
"is_current": stage.IsCurrent,
|
||||
})
|
||||
@ -737,7 +731,6 @@ func convertCompleteGuideResponse(resp *pbTask.CompleteGuideResponse) map[string
|
||||
"name": stage.Name,
|
||||
"required_task_keys": stage.RequiredTaskKeys,
|
||||
"crystal_reward": stage.CrystalReward,
|
||||
"exp_reward": stage.ExpReward,
|
||||
"status": stage.Status,
|
||||
"is_current": stage.IsCurrent,
|
||||
})
|
||||
|
||||
@ -17,7 +17,6 @@ type CurrentIdentityDTO struct {
|
||||
IdentityName string `json:"identity_name"` // "王一博"
|
||||
Tag string `json:"tag"` // "小摩托"
|
||||
Level int32 `json:"level"`
|
||||
Experience int64 `json:"experience"`
|
||||
CrystalBalance int64 `json:"crystal_balance"`
|
||||
}
|
||||
|
||||
@ -109,7 +108,6 @@ type MyFanIdentityItemDTO struct {
|
||||
StarTag string `json:"star_tag"` // 明星标签
|
||||
Nickname string `json:"nickname"` // 用户昵称
|
||||
Level int32 `json:"level"` // 等级
|
||||
Experience int64 `json:"experience"` // 经验值
|
||||
CrystalBalance int64 `json:"crystal_balance"` // 水晶余额
|
||||
IsActive bool `json:"is_active"` // 是否激活(当前使用)
|
||||
}
|
||||
|
||||
@ -24,7 +24,6 @@ func ToCurrentIdentityDTO(profile *pb.FanProfile, star *pb.Star) CurrentIdentity
|
||||
IdentityName: star.Name, // "王一博"
|
||||
Tag: star.Tag, // "小摩托"
|
||||
Level: profile.Level,
|
||||
Experience: profile.Experience,
|
||||
CrystalBalance: profile.CrystalBalance,
|
||||
}
|
||||
}
|
||||
@ -199,7 +198,6 @@ func ToMyFanIdentitiesResponseDTO(items []*pb.MyFanIdentityItem, currentStarID i
|
||||
StarTag: star.Tag,
|
||||
Nickname: profile.Nickname,
|
||||
Level: profile.Level,
|
||||
Experience: profile.Experience,
|
||||
CrystalBalance: profile.CrystalBalance,
|
||||
IsActive: profile.StarId == currentStarID,
|
||||
})
|
||||
|
||||
132
backend/pkg/models/level.go
Normal file
132
backend/pkg/models/level.go
Normal file
@ -0,0 +1,132 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// LevelThreshold 等级阈值配置表
|
||||
type LevelThreshold struct {
|
||||
Level int32 `gorm:"primaryKey;column:level"` // 等级 1-20
|
||||
MaxExhibitionHours int64 `gorm:"not null;column:max_exhibition_hours"` // 升级到该等级需要的累计上架时长(小时)
|
||||
LikeBetCount int32 `gorm:"not null;column:like_bet_count"` // 升级后解锁的点赞押注次数
|
||||
Description string `gorm:"type:varchar(100);column:description"` // 描述
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (LevelThreshold) TableName() string {
|
||||
return "level_thresholds"
|
||||
}
|
||||
|
||||
// LevelUpgradeCondition 等级升级条件配置表(21级+)
|
||||
type LevelUpgradeCondition struct {
|
||||
Level int32 `gorm:"primaryKey;column:level"` // 主等级(如21级)
|
||||
RequireTotalHours int64 `gorm:"not null;column:require_total_hours"` // 需要的总上架时长
|
||||
RequireDaziLevel int32 `gorm:"default:0;column:require_dazi_level"` // 需要的搭子等级(0=不需要)
|
||||
Description string `gorm:"type:varchar(100);column:description"` // 描述
|
||||
UpdatedAt int64 `gorm:"not null;column:updated_at"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (LevelUpgradeCondition) TableName() string {
|
||||
return "level_upgrade_conditions"
|
||||
}
|
||||
|
||||
// BeforeUpdate 更新前钩子
|
||||
func (l *LevelUpgradeCondition) BeforeUpdate(tx *gorm.DB) error {
|
||||
l.UpdatedAt = time.Now().UnixMilli()
|
||||
return nil
|
||||
}
|
||||
|
||||
// LevelCapConfig 等级上限配置表
|
||||
type LevelCapConfig struct {
|
||||
ID int64 `gorm:"primaryKey;column:id"`
|
||||
MaxLevel int32 `gorm:"not null;column:max_level"` // 当前等级上限
|
||||
UpdatedAt int64 `gorm:"not null;column:updated_at"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (LevelCapConfig) TableName() string {
|
||||
return "level_cap_config"
|
||||
}
|
||||
|
||||
// BeforeUpdate 更新前钩子
|
||||
func (l *LevelCapConfig) BeforeUpdate(tx *gorm.DB) error {
|
||||
l.UpdatedAt = time.Now().UnixMilli()
|
||||
return nil
|
||||
}
|
||||
|
||||
// UserExhibitionHours 用户累计上架时长表
|
||||
type UserExhibitionHours struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement;column:id"`
|
||||
UserID int64 `gorm:"uniqueIndex:uk_exhibition_user_star;not null;column:user_id"`
|
||||
StarID int64 `gorm:"uniqueIndex:uk_exhibition_user_star;not null;column:star_id"`
|
||||
TotalExhibitionHours int64 `gorm:"default:0;not null;column:total_exhibition_hours"` // 累计上架时长(小时)
|
||||
UpdatedAt int64 `gorm:"not null;column:updated_at"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (UserExhibitionHours) TableName() string {
|
||||
return "user_exhibition_hours"
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前钩子
|
||||
func (u *UserExhibitionHours) BeforeCreate(tx *gorm.DB) error {
|
||||
u.UpdatedAt = time.Now().UnixMilli()
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeUpdate 更新前钩子
|
||||
func (u *UserExhibitionHours) BeforeUpdate(tx *gorm.DB) error {
|
||||
u.UpdatedAt = time.Now().UnixMilli()
|
||||
return nil
|
||||
}
|
||||
|
||||
// LevelUpRewardConfig 升级奖励配置表
|
||||
type LevelUpRewardConfig struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement;column:id"`
|
||||
Level int32 `gorm:"uniqueIndex:uk_level_reward_type;not null;column:level"` // 等级
|
||||
RewardType string `gorm:"uniqueIndex:uk_level_reward_type;type:varchar(50);not null;column:reward_type"` // 奖励类型:crystal/like_bet_count
|
||||
RewardValue int64 `gorm:"default:0;column:reward_value"` // 奖励值
|
||||
IsEnabled bool `gorm:"default:true;column:is_enabled"` // 功能开关
|
||||
UpdatedAt int64 `gorm:"not null;column:updated_at"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (LevelUpRewardConfig) TableName() string {
|
||||
return "level_up_reward_config"
|
||||
}
|
||||
|
||||
// BeforeUpdate 更新前钩子
|
||||
func (l *LevelUpRewardConfig) BeforeUpdate(tx *gorm.DB) error {
|
||||
l.UpdatedAt = time.Now().UnixMilli()
|
||||
return nil
|
||||
}
|
||||
|
||||
// DaziLevelThreshold 搭子等级阈值配置表(预留)
|
||||
type DaziLevelThreshold struct {
|
||||
Level int32 `gorm:"primaryKey;column:level"` // 搭子等级 1-N
|
||||
UpgradeCondition string `gorm:"type:varchar(100);column:upgrade_condition"` // 升级条件描述
|
||||
ConditionParam int `gorm:"default:0;column:condition_param"` // 条件参数
|
||||
Description string `gorm:"type:varchar(100);column:description"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (DaziLevelThreshold) TableName() string {
|
||||
return "dazi_level_thresholds"
|
||||
}
|
||||
|
||||
// UserDaziLevel 用户搭子等级表(预留)
|
||||
type UserDaziLevel struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement;column:id"`
|
||||
UserID int64 `gorm:"uniqueIndex:uk_dazi_user_star;not null;column:user_id"`
|
||||
StarID int64 `gorm:"uniqueIndex:uk_dazi_user_star;not null;column:star_id"`
|
||||
DaziLevel int32 `gorm:"default:1;not null;column:dazi_level"` // 搭子等级
|
||||
UpdatedAt int64 `gorm:"not null;column:updated_at"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (UserDaziLevel) TableName() string {
|
||||
return "user_dazi_level"
|
||||
}
|
||||
85
backend/pkg/models/mint.go
Normal file
85
backend/pkg/models/mint.go
Normal file
@ -0,0 +1,85 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// MintCostConfig 铸造消耗配置表
|
||||
type MintCostConfig struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement;column:id"`
|
||||
MintCount int32 `gorm:"uniqueIndex;not null;column:mint_count"` // 铸爱次数 1-10
|
||||
CostCrystal int64 `gorm:"not null;column:cost_crystal"` // 消耗水晶数
|
||||
Probability int64 `gorm:"default:0;column:probability"` // 保底触发概率 0-100
|
||||
RewardType *string `gorm:"type:varchar(50);column:reward_type"` // 奖励类型
|
||||
RewardValue int64 `gorm:"default:0;column:reward_value"` // 奖励值(bps)
|
||||
Description *string `gorm:"type:varchar(255);column:description"` // 描述
|
||||
UpdatedAt int64 `gorm:"not null;column:updated_at"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (MintCostConfig) TableName() string {
|
||||
return "mint_cost_config"
|
||||
}
|
||||
|
||||
// BeforeUpdate 更新前钩子
|
||||
func (m *MintCostConfig) BeforeUpdate(tx *gorm.DB) error {
|
||||
m.UpdatedAt = time.Now().UnixMilli()
|
||||
return nil
|
||||
}
|
||||
|
||||
// UserMintCount 用户铸爱累计表
|
||||
type UserMintCount struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement;column:id"`
|
||||
UserID int64 `gorm:"uniqueIndex:uk_user_mint_star;not null;column:user_id"`
|
||||
StarID int64 `gorm:"uniqueIndex:uk_user_mint_star;not null;column:star_id"`
|
||||
MintCount int32 `gorm:"default:0;not null;column:mint_count"` // 累计铸造次数
|
||||
RevenueBoostBps int32 `gorm:"default:0;not null;column:revenue_boost_bps"` // 永久收益提升(基点),500=+5%
|
||||
UpdatedAt int64 `gorm:"not null;column:updated_at"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (UserMintCount) TableName() string {
|
||||
return "user_mint_count"
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前钩子
|
||||
func (u *UserMintCount) BeforeCreate(tx *gorm.DB) error {
|
||||
u.UpdatedAt = time.Now().UnixMilli()
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeUpdate 更新前钩子
|
||||
func (u *UserMintCount) BeforeUpdate(tx *gorm.DB) error {
|
||||
u.UpdatedAt = time.Now().UnixMilli()
|
||||
return nil
|
||||
}
|
||||
|
||||
// MintRewardConfig 铸造奖励配置表(预留)
|
||||
type MintRewardConfig struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement;column:id"`
|
||||
StarID int64 `gorm:"uniqueIndex;not null;column:star_id"` // 偶像ID,0=全服默认
|
||||
BaseReward int64 `gorm:"default:0;column:base_reward"` // 每次铸造基础返还水晶数
|
||||
IsEnabled bool `gorm:"default:true;column:is_enabled"` // 功能开关
|
||||
UpdatedAt int64 `gorm:"not null;column:updated_at"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (MintRewardConfig) TableName() string {
|
||||
return "mint_reward_config"
|
||||
}
|
||||
|
||||
// MintMilestoneConfig 铸造阶梯奖励配置表(预留)
|
||||
type MintMilestoneConfig struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement;column:id"`
|
||||
StarID int64 `gorm:"uniqueIndex:uk_milestone_star_count;not null;column:star_id"`
|
||||
MilestoneCount int32 `gorm:"uniqueIndex:uk_milestone_star_count;not null;column:milestone_count"` // 累计次数阈值
|
||||
BonusReward int64 `gorm:"not null;column:bonus_reward"` // 达到该阶梯时额外奖励水晶
|
||||
CreatedAt int64 `gorm:"not null;column:created_at"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (MintMilestoneConfig) TableName() string {
|
||||
return "mint_milestone_config"
|
||||
}
|
||||
@ -55,17 +55,17 @@ type FanProfile struct {
|
||||
Level int32 `gorm:"default:1;not null;column:level"`
|
||||
Times int32 `gorm:"default:1;not null;column:times"` // 剩余铸造次数
|
||||
Social int32 `gorm:"default:0;not null;column:social"` // 好友个数
|
||||
Experience int64 `gorm:"default:0;not null;column:experience"`
|
||||
CoinBalance int64 `gorm:"default:0;not null;column:coin_balance"`
|
||||
CrystalBalance int64 `gorm:"default:0;not null;column:crystal_balance"`
|
||||
Tags StringArray `gorm:"type:jsonb;column:tags"`
|
||||
AvatarURL *string `gorm:"type:varchar(500);column:avatar_url"`
|
||||
|
||||
// 新增字段
|
||||
StarbookLimit int32 `gorm:"default:3;not null;column:starbook_limit"`
|
||||
SlotLimit int32 `gorm:"default:3;not null;column:slot_limit"`
|
||||
AssetsCount int32 `gorm:"default:0;not null;column:assets_count"`
|
||||
ChainAddress *string `gorm:"type:varchar(100);column:chain_address"`
|
||||
StarbookLimit int32 `gorm:"default:3;not null;column:starbook_limit"`
|
||||
SlotLimit int32 `gorm:"default:3;not null;column:slot_limit"`
|
||||
AssetsCount int32 `gorm:"default:0;not null;column:assets_count"`
|
||||
LikeBetCount int32 `gorm:"default:0;not null;column:like_bet_count"` // 点赞押注次数
|
||||
ChainAddress *string `gorm:"type:varchar(100);column:chain_address"`
|
||||
|
||||
IsActive bool `gorm:"default:true;not null;column:is_active"`
|
||||
CreatedAt int64 `gorm:"not null;column:created_at"`
|
||||
@ -170,3 +170,41 @@ func (sa *StringArray) Scan(value interface{}) error {
|
||||
|
||||
return json.Unmarshal(bytes, sa)
|
||||
}
|
||||
|
||||
// CrystalTransactionRecord 水晶交易流水表
|
||||
type CrystalTransactionRecord struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement;column:id"`
|
||||
UserID int64 `gorm:"not null;index:ix_crystal_tx_user_star;column:user_id"`
|
||||
StarID int64 `gorm:"not null;index:ix_crystal_tx_user_star;column:star_id"`
|
||||
ChangeType string `gorm:"type:varchar(30);not null;index:ix_crystal_tx_change_type;column:change_type"` // task_reward/mint_cost/mint_reward/exhibition_revenue/level_up_bonus/manual_adjust
|
||||
Delta int64 `gorm:"not null;column:delta"` // 正数=收入,负数=消耗
|
||||
BalanceBefore int64 `gorm:"not null;column:balance_before"` // 变化前余额快照
|
||||
BalanceAfter int64 `gorm:"not null;column:balance_after"` // 变化后余额快照
|
||||
SourceID string `gorm:"type:varchar(100);column:source_id"` // 关联业务ID
|
||||
Description string `gorm:"type:varchar(255);column:description"` // 可读描述
|
||||
CreatedAt int64 `gorm:"not null;index:ix_crystal_tx_created;column:created_at"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (CrystalTransactionRecord) TableName() string {
|
||||
return "crystal_transaction_records"
|
||||
}
|
||||
|
||||
// CoinTransactionRecord 游戏币交易流水表(预留)
|
||||
type CoinTransactionRecord struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement;column:id"`
|
||||
UserID int64 `gorm:"not null;index:ix_coin_tx_user_star;column:user_id"`
|
||||
StarID int64 `gorm:"not null;index:ix_coin_tx_user_star;column:star_id"`
|
||||
ChangeType string `gorm:"type:varchar(30);not null;column:change_type"`
|
||||
Delta int64 `gorm:"not null;column:delta"`
|
||||
BalanceBefore int64 `gorm:"not null;column:balance_before"`
|
||||
BalanceAfter int64 `gorm:"not null;column:balance_after"`
|
||||
SourceID string `gorm:"type:varchar(100);column:source_id"`
|
||||
Description string `gorm:"type:varchar(255);column:description"`
|
||||
CreatedAt int64 `gorm:"not null;index:ix_coin_tx_created;column:created_at"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (CoinTransactionRecord) TableName() string {
|
||||
return "coin_transaction_records"
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-triple. DO NOT EDIT.
|
||||
//
|
||||
// Source: proto/asset.proto
|
||||
// Source: asset.proto
|
||||
package asset
|
||||
|
||||
import (
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
// Code generated by protoc-gen-triple. DO NOT EDIT.
|
||||
//
|
||||
// Source: proto/common.proto
|
||||
package common
|
||||
|
||||
import (
|
||||
"dubbo.apache.org/dubbo-go/v3/protocol/triple/triple_protocol"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file and the Triple package
|
||||
// are compatible. If you get a compiler error that this constant is not defined, this code was
|
||||
// generated with a version of Triple newer than the one compiled into your binary. You can fix the
|
||||
// problem by either regenerating this code with an older version of Triple or updating the Triple
|
||||
// version compiled into your binary.
|
||||
const _ = triple_protocol.IsAtLeastVersion0_1_0
|
||||
|
||||
var ()
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-triple. DO NOT EDIT.
|
||||
//
|
||||
// Source: proto/gallery.proto
|
||||
// Source: gallery.proto
|
||||
package gallery
|
||||
|
||||
import (
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-triple. DO NOT EDIT.
|
||||
//
|
||||
// Source: proto/ranking.proto
|
||||
// Source: ranking.proto
|
||||
package ranking
|
||||
|
||||
import (
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-triple. DO NOT EDIT.
|
||||
//
|
||||
// Source: proto/social.proto
|
||||
// Source: social.proto
|
||||
package social
|
||||
|
||||
import (
|
||||
@ -66,6 +66,12 @@ const (
|
||||
SocialServiceCheckAssetLikeProcedure = "/topfans.social.SocialService/CheckAssetLike"
|
||||
// SocialServiceGetMyLikedAssetsProcedure is the fully-qualified name of the SocialService's GetMyLikedAssets RPC.
|
||||
SocialServiceGetMyLikedAssetsProcedure = "/topfans.social.SocialService/GetMyLikedAssets"
|
||||
// SocialServiceGetMyTodayLikedAssetsProcedure is the fully-qualified name of the SocialService's GetMyTodayLikedAssets RPC.
|
||||
SocialServiceGetMyTodayLikedAssetsProcedure = "/topfans.social.SocialService/GetMyTodayLikedAssets"
|
||||
// SocialServiceGetMyWeekLikedAssetsProcedure is the fully-qualified name of the SocialService's GetMyWeekLikedAssets RPC.
|
||||
SocialServiceGetMyWeekLikedAssetsProcedure = "/topfans.social.SocialService/GetMyWeekLikedAssets"
|
||||
// SocialServiceGetUserLikedAssetsProcedure is the fully-qualified name of the SocialService's GetUserLikedAssets RPC.
|
||||
SocialServiceGetUserLikedAssetsProcedure = "/topfans.social.SocialService/GetUserLikedAssets"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@ -30,9 +30,8 @@ type DailyTaskItem struct {
|
||||
Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"`
|
||||
CrystalReward int64 `protobuf:"varint,5,opt,name=crystal_reward,json=crystalReward,proto3" json:"crystal_reward,omitempty"`
|
||||
ExpReward int64 `protobuf:"varint,6,opt,name=exp_reward,json=expReward,proto3" json:"exp_reward,omitempty"`
|
||||
Status string `protobuf:"bytes,7,opt,name=status,proto3" json:"status,omitempty"` // pending/completed/claimed
|
||||
CanClaim bool `protobuf:"varint,8,opt,name=can_claim,json=canClaim,proto3" json:"can_claim,omitempty"`
|
||||
Status string `protobuf:"bytes,6,opt,name=status,proto3" json:"status,omitempty"` // pending/completed/claimed
|
||||
CanClaim bool `protobuf:"varint,7,opt,name=can_claim,json=canClaim,proto3" json:"can_claim,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@ -102,13 +101,6 @@ func (x *DailyTaskItem) GetCrystalReward() int64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *DailyTaskItem) GetExpReward() int64 {
|
||||
if x != nil {
|
||||
return x.ExpReward
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *DailyTaskItem) GetStatus() string {
|
||||
if x != nil {
|
||||
return x.Status
|
||||
@ -412,7 +404,6 @@ type ClaimDailyTaskResponse struct {
|
||||
Base *common.BaseResponse `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"`
|
||||
Success bool `protobuf:"varint,2,opt,name=success,proto3" json:"success,omitempty"`
|
||||
CrystalBalance int64 `protobuf:"varint,3,opt,name=crystal_balance,json=crystalBalance,proto3" json:"crystal_balance,omitempty"`
|
||||
Experience int64 `protobuf:"varint,4,opt,name=experience,proto3" json:"experience,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@ -468,13 +459,6 @@ func (x *ClaimDailyTaskResponse) GetCrystalBalance() int64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ClaimDailyTaskResponse) GetExperience() int64 {
|
||||
if x != nil {
|
||||
return x.Experience
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type ClaimAllDailyTasksRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
StarId int64 `protobuf:"varint,1,opt,name=star_id,json=starId,proto3" json:"star_id,omitempty"`
|
||||
@ -524,8 +508,7 @@ type ClaimAllDailyTasksResponse struct {
|
||||
Base *common.BaseResponse `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"`
|
||||
ClaimedCount int32 `protobuf:"varint,2,opt,name=claimed_count,json=claimedCount,proto3" json:"claimed_count,omitempty"`
|
||||
CrystalBalance int64 `protobuf:"varint,3,opt,name=crystal_balance,json=crystalBalance,proto3" json:"crystal_balance,omitempty"`
|
||||
Experience int64 `protobuf:"varint,4,opt,name=experience,proto3" json:"experience,omitempty"`
|
||||
ClaimedTaskKeys []string `protobuf:"bytes,5,rep,name=claimed_task_keys,json=claimedTaskKeys,proto3" json:"claimed_task_keys,omitempty"`
|
||||
ClaimedTaskKeys []string `protobuf:"bytes,4,rep,name=claimed_task_keys,json=claimedTaskKeys,proto3" json:"claimed_task_keys,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@ -581,13 +564,6 @@ func (x *ClaimAllDailyTasksResponse) GetCrystalBalance() int64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ClaimAllDailyTasksResponse) GetExperience() int64 {
|
||||
if x != nil {
|
||||
return x.Experience
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ClaimAllDailyTasksResponse) GetClaimedTaskKeys() []string {
|
||||
if x != nil {
|
||||
return x.ClaimedTaskKeys
|
||||
@ -601,11 +577,10 @@ type OnboardingStage struct {
|
||||
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
|
||||
RequiredTaskKeys []string `protobuf:"bytes,3,rep,name=required_task_keys,json=requiredTaskKeys,proto3" json:"required_task_keys,omitempty"`
|
||||
CrystalReward int64 `protobuf:"varint,4,opt,name=crystal_reward,json=crystalReward,proto3" json:"crystal_reward,omitempty"`
|
||||
ExpReward int64 `protobuf:"varint,5,opt,name=exp_reward,json=expReward,proto3" json:"exp_reward,omitempty"`
|
||||
Status string `protobuf:"bytes,6,opt,name=status,proto3" json:"status,omitempty"` // pending/completed/in_progress
|
||||
IsCurrent bool `protobuf:"varint,7,opt,name=is_current,json=isCurrent,proto3" json:"is_current,omitempty"`
|
||||
AllTasksCompleted bool `protobuf:"varint,8,opt,name=all_tasks_completed,json=allTasksCompleted,proto3" json:"all_tasks_completed,omitempty"` // 该阶段所有任务是否完成
|
||||
IsRewardClaimed bool `protobuf:"varint,9,opt,name=is_reward_claimed,json=isRewardClaimed,proto3" json:"is_reward_claimed,omitempty"` // 该阶段奖励是否已领取
|
||||
Status string `protobuf:"bytes,5,opt,name=status,proto3" json:"status,omitempty"` // pending/completed/in_progress
|
||||
IsCurrent bool `protobuf:"varint,6,opt,name=is_current,json=isCurrent,proto3" json:"is_current,omitempty"`
|
||||
AllTasksCompleted bool `protobuf:"varint,7,opt,name=all_tasks_completed,json=allTasksCompleted,proto3" json:"all_tasks_completed,omitempty"` // 该阶段所有任务是否完成
|
||||
IsRewardClaimed bool `protobuf:"varint,8,opt,name=is_reward_claimed,json=isRewardClaimed,proto3" json:"is_reward_claimed,omitempty"` // 该阶段奖励是否已领取
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@ -668,13 +643,6 @@ func (x *OnboardingStage) GetCrystalReward() int64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *OnboardingStage) GetExpReward() int64 {
|
||||
if x != nil {
|
||||
return x.ExpReward
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *OnboardingStage) GetStatus() string {
|
||||
if x != nil {
|
||||
return x.Status
|
||||
@ -1104,7 +1072,6 @@ type ClaimOnboardingRewardResponse struct {
|
||||
Base *common.BaseResponse `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"`
|
||||
Success bool `protobuf:"varint,2,opt,name=success,proto3" json:"success,omitempty"`
|
||||
CrystalBalance string `protobuf:"bytes,3,opt,name=crystal_balance,json=crystalBalance,proto3" json:"crystal_balance,omitempty"` // 使用 string 避免 Dubbo int64 序列化 bug
|
||||
Experience string `protobuf:"bytes,4,opt,name=experience,proto3" json:"experience,omitempty"` // 使用 string 避免 Dubbo int64 序列化 bug
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@ -1160,13 +1127,6 @@ func (x *ClaimOnboardingRewardResponse) GetCrystalBalance() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ClaimOnboardingRewardResponse) GetExperience() string {
|
||||
if x != nil {
|
||||
return x.Experience
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ExhibitionRevenueItem struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
@ -1904,17 +1864,15 @@ var File_task_proto protoreflect.FileDescriptor
|
||||
const file_task_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"task.proto\x12\ftopfans.task\x1a\x12proto/common.proto\x1a\x1cgoogle/api/annotations.proto\"\xf4\x01\n" +
|
||||
"task.proto\x12\ftopfans.task\x1a\x12proto/common.proto\x1a\x1cgoogle/api/annotations.proto\"\xd5\x01\n" +
|
||||
"\rDailyTaskItem\x12\x19\n" +
|
||||
"\btask_key\x18\x01 \x01(\tR\ataskKey\x12\x17\n" +
|
||||
"\astar_id\x18\x02 \x01(\x03R\x06starId\x12\x12\n" +
|
||||
"\x04name\x18\x03 \x01(\tR\x04name\x12 \n" +
|
||||
"\vdescription\x18\x04 \x01(\tR\vdescription\x12%\n" +
|
||||
"\x0ecrystal_reward\x18\x05 \x01(\x03R\rcrystalReward\x12\x1d\n" +
|
||||
"\n" +
|
||||
"exp_reward\x18\x06 \x01(\x03R\texpReward\x12\x16\n" +
|
||||
"\x06status\x18\a \x01(\tR\x06status\x12\x1b\n" +
|
||||
"\tcan_claim\x18\b \x01(\bR\bcanClaim\"/\n" +
|
||||
"\x0ecrystal_reward\x18\x05 \x01(\x03R\rcrystalReward\x12\x16\n" +
|
||||
"\x06status\x18\x06 \x01(\tR\x06status\x12\x1b\n" +
|
||||
"\tcan_claim\x18\a \x01(\bR\bcanClaim\"/\n" +
|
||||
"\x14GetDailyTasksRequest\x12\x17\n" +
|
||||
"\astar_id\x18\x01 \x01(\x03R\x06starId\"\x95\x01\n" +
|
||||
"\x15GetDailyTasksResponse\x120\n" +
|
||||
@ -1933,36 +1891,28 @@ const file_task_proto_rawDesc = "" +
|
||||
"\amessage\x18\x05 \x01(\tR\amessage\"K\n" +
|
||||
"\x15ClaimDailyTaskRequest\x12\x19\n" +
|
||||
"\btask_key\x18\x01 \x01(\tR\ataskKey\x12\x17\n" +
|
||||
"\astar_id\x18\x02 \x01(\x03R\x06starId\"\xad\x01\n" +
|
||||
"\astar_id\x18\x02 \x01(\x03R\x06starId\"\x8d\x01\n" +
|
||||
"\x16ClaimDailyTaskResponse\x120\n" +
|
||||
"\x04base\x18\x01 \x01(\v2\x1c.topfans.common.BaseResponseR\x04base\x12\x18\n" +
|
||||
"\asuccess\x18\x02 \x01(\bR\asuccess\x12'\n" +
|
||||
"\x0fcrystal_balance\x18\x03 \x01(\x03R\x0ecrystalBalance\x12\x1e\n" +
|
||||
"\n" +
|
||||
"experience\x18\x04 \x01(\x03R\n" +
|
||||
"experience\"4\n" +
|
||||
"\x0fcrystal_balance\x18\x03 \x01(\x03R\x0ecrystalBalance\"4\n" +
|
||||
"\x19ClaimAllDailyTasksRequest\x12\x17\n" +
|
||||
"\astar_id\x18\x01 \x01(\x03R\x06starId\"\xe8\x01\n" +
|
||||
"\astar_id\x18\x01 \x01(\x03R\x06starId\"\xc8\x01\n" +
|
||||
"\x1aClaimAllDailyTasksResponse\x120\n" +
|
||||
"\x04base\x18\x01 \x01(\v2\x1c.topfans.common.BaseResponseR\x04base\x12#\n" +
|
||||
"\rclaimed_count\x18\x02 \x01(\x05R\fclaimedCount\x12'\n" +
|
||||
"\x0fcrystal_balance\x18\x03 \x01(\x03R\x0ecrystalBalance\x12\x1e\n" +
|
||||
"\n" +
|
||||
"experience\x18\x04 \x01(\x03R\n" +
|
||||
"experience\x12*\n" +
|
||||
"\x11claimed_task_keys\x18\x05 \x03(\tR\x0fclaimedTaskKeys\"\xc2\x02\n" +
|
||||
"\x0fcrystal_balance\x18\x03 \x01(\x03R\x0ecrystalBalance\x12*\n" +
|
||||
"\x11claimed_task_keys\x18\x04 \x03(\tR\x0fclaimedTaskKeys\"\xa3\x02\n" +
|
||||
"\x0fOnboardingStage\x12\x14\n" +
|
||||
"\x05stage\x18\x01 \x01(\x05R\x05stage\x12\x12\n" +
|
||||
"\x04name\x18\x02 \x01(\tR\x04name\x12,\n" +
|
||||
"\x12required_task_keys\x18\x03 \x03(\tR\x10requiredTaskKeys\x12%\n" +
|
||||
"\x0ecrystal_reward\x18\x04 \x01(\x03R\rcrystalReward\x12\x1d\n" +
|
||||
"\x0ecrystal_reward\x18\x04 \x01(\x03R\rcrystalReward\x12\x16\n" +
|
||||
"\x06status\x18\x05 \x01(\tR\x06status\x12\x1d\n" +
|
||||
"\n" +
|
||||
"exp_reward\x18\x05 \x01(\x03R\texpReward\x12\x16\n" +
|
||||
"\x06status\x18\x06 \x01(\tR\x06status\x12\x1d\n" +
|
||||
"\n" +
|
||||
"is_current\x18\a \x01(\bR\tisCurrent\x12.\n" +
|
||||
"\x13all_tasks_completed\x18\b \x01(\bR\x11allTasksCompleted\x12*\n" +
|
||||
"\x11is_reward_claimed\x18\t \x01(\bR\x0fisRewardClaimed\"h\n" +
|
||||
"is_current\x18\x06 \x01(\bR\tisCurrent\x12.\n" +
|
||||
"\x13all_tasks_completed\x18\a \x01(\bR\x11allTasksCompleted\x12*\n" +
|
||||
"\x11is_reward_claimed\x18\b \x01(\bR\x0fisRewardClaimed\"h\n" +
|
||||
"\x14CompleteGuideRequest\x12\x19\n" +
|
||||
"\btask_key\x18\x01 \x01(\tR\ataskKey\x125\n" +
|
||||
"\x06stages\x18\x02 \x03(\v2\x1d.topfans.task.OnboardingStageR\x06stages\"\xd6\x01\n" +
|
||||
@ -1987,14 +1937,11 @@ const file_task_proto_rawDesc = "" +
|
||||
"\x06status\x18\x03 \x01(\tR\x06status\x125\n" +
|
||||
"\x06stages\x18\x04 \x03(\v2\x1d.topfans.task.OnboardingStageR\x06stages\"4\n" +
|
||||
"\x1cClaimOnboardingRewardRequest\x12\x14\n" +
|
||||
"\x05stage\x18\x01 \x01(\x05R\x05stage\"\xb4\x01\n" +
|
||||
"\x05stage\x18\x01 \x01(\x05R\x05stage\"\x94\x01\n" +
|
||||
"\x1dClaimOnboardingRewardResponse\x120\n" +
|
||||
"\x04base\x18\x01 \x01(\v2\x1c.topfans.common.BaseResponseR\x04base\x12\x18\n" +
|
||||
"\asuccess\x18\x02 \x01(\bR\asuccess\x12'\n" +
|
||||
"\x0fcrystal_balance\x18\x03 \x01(\tR\x0ecrystalBalance\x12\x1e\n" +
|
||||
"\n" +
|
||||
"experience\x18\x04 \x01(\tR\n" +
|
||||
"experience\"\xe2\x02\n" +
|
||||
"\x0fcrystal_balance\x18\x03 \x01(\tR\x0ecrystalBalance\"\xe2\x02\n" +
|
||||
"\x15ExhibitionRevenueItem\x12\x0e\n" +
|
||||
"\x02id\x18\x01 \x01(\x03R\x02id\x12\x17\n" +
|
||||
"\astar_id\x18\x02 \x01(\x03R\x06starId\x12#\n" +
|
||||
|
||||
@ -113,21 +113,20 @@ type FanProfile struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
|
||||
StarId int64 `protobuf:"varint,3,opt,name=star_id,json=starId,proto3" json:"star_id,omitempty"` // 核心隔离键
|
||||
Nickname string `protobuf:"bytes,4,opt,name=nickname,proto3" json:"nickname,omitempty"` // 星球专属昵称
|
||||
Level int32 `protobuf:"varint,5,opt,name=level,proto3" json:"level,omitempty"` // 等级
|
||||
Times int32 `protobuf:"varint,6,opt,name=times,proto3" json:"times,omitempty"` // 剩余铸造次数
|
||||
Social int32 `protobuf:"varint,7,opt,name=social,proto3" json:"social,omitempty"` // 好友个数
|
||||
Experience int64 `protobuf:"varint,8,opt,name=experience,proto3" json:"experience,omitempty"` // 经验值
|
||||
CoinBalance int64 `protobuf:"varint,9,opt,name=coin_balance,json=coinBalance,proto3" json:"coin_balance,omitempty"` // 游戏币余额
|
||||
CrystalBalance int64 `protobuf:"varint,10,opt,name=crystal_balance,json=crystalBalance,proto3" json:"crystal_balance,omitempty"` // 顶粉水晶余额
|
||||
Tags []string `protobuf:"bytes,11,rep,name=tags,proto3" json:"tags,omitempty"` // 标签数组
|
||||
AvatarUrl string `protobuf:"bytes,17,opt,name=avatar_url,json=avatarUrl,proto3" json:"avatar_url,omitempty"` // 头像URL(星球专属)
|
||||
CreatedAt int64 `protobuf:"varint,12,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
|
||||
StarbookLimit int32 `protobuf:"varint,13,opt,name=starbook_limit,json=starbookLimit,proto3" json:"starbook_limit,omitempty"` // 星书限制
|
||||
SlotLimit int32 `protobuf:"varint,14,opt,name=slot_limit,json=slotLimit,proto3" json:"slot_limit,omitempty"` // 槽位限制
|
||||
AssetsCount int32 `protobuf:"varint,15,opt,name=assets_count,json=assetsCount,proto3" json:"assets_count,omitempty"` // 资产数量
|
||||
ChainAddress string `protobuf:"bytes,16,opt,name=chain_address,json=chainAddress,proto3" json:"chain_address,omitempty"` // 链地址
|
||||
StarId int64 `protobuf:"varint,3,opt,name=star_id,json=starId,proto3" json:"star_id,omitempty"` // 核心隔离键
|
||||
Nickname string `protobuf:"bytes,4,opt,name=nickname,proto3" json:"nickname,omitempty"` // 星球专属昵称
|
||||
Level int32 `protobuf:"varint,5,opt,name=level,proto3" json:"level,omitempty"` // 等级
|
||||
Times int32 `protobuf:"varint,6,opt,name=times,proto3" json:"times,omitempty"` // 剩余铸造次数
|
||||
Social int32 `protobuf:"varint,7,opt,name=social,proto3" json:"social,omitempty"` // 好友个数
|
||||
CoinBalance int64 `protobuf:"varint,8,opt,name=coin_balance,json=coinBalance,proto3" json:"coin_balance,omitempty"` // 游戏币余额
|
||||
CrystalBalance int64 `protobuf:"varint,9,opt,name=crystal_balance,json=crystalBalance,proto3" json:"crystal_balance,omitempty"` // 顶粉水晶余额
|
||||
Tags []string `protobuf:"bytes,10,rep,name=tags,proto3" json:"tags,omitempty"` // 标签数组
|
||||
AvatarUrl string `protobuf:"bytes,17,opt,name=avatar_url,json=avatarUrl,proto3" json:"avatar_url,omitempty"` // 头像URL(星球专属)
|
||||
CreatedAt int64 `protobuf:"varint,11,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
|
||||
StarbookLimit int32 `protobuf:"varint,12,opt,name=starbook_limit,json=starbookLimit,proto3" json:"starbook_limit,omitempty"` // 星书限制
|
||||
SlotLimit int32 `protobuf:"varint,13,opt,name=slot_limit,json=slotLimit,proto3" json:"slot_limit,omitempty"` // 槽位限制
|
||||
AssetsCount int32 `protobuf:"varint,14,opt,name=assets_count,json=assetsCount,proto3" json:"assets_count,omitempty"` // 资产数量
|
||||
ChainAddress string `protobuf:"bytes,15,opt,name=chain_address,json=chainAddress,proto3" json:"chain_address,omitempty"` // 链地址
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@ -211,13 +210,6 @@ func (x *FanProfile) GetSocial() int32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *FanProfile) GetExperience() int64 {
|
||||
if x != nil {
|
||||
return x.Experience
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *FanProfile) GetCoinBalance() int64 {
|
||||
if x != nil {
|
||||
return x.CoinBalance
|
||||
@ -1501,9 +1493,12 @@ func (x *UpdateFanProfileSocialResponse) GetNewSocial() int32 {
|
||||
// 更新水晶余额请求(内部RPC调用,用于资产服务扣除/退款水晶)
|
||||
type UpdateCrystalBalanceRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` // 用户ID
|
||||
StarId int64 `protobuf:"varint,2,opt,name=star_id,json=starId,proto3" json:"star_id,omitempty"` // 明星ID
|
||||
Delta int64 `protobuf:"varint,3,opt,name=delta,proto3" json:"delta,omitempty"` // 变化量(正数增加,负数减少)
|
||||
UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` // 用户ID
|
||||
StarId int64 `protobuf:"varint,2,opt,name=star_id,json=starId,proto3" json:"star_id,omitempty"` // 明星ID
|
||||
Delta int64 `protobuf:"varint,3,opt,name=delta,proto3" json:"delta,omitempty"` // 变化量(正数增加,负数减少)
|
||||
ChangeType string `protobuf:"bytes,4,opt,name=change_type,json=changeType,proto3" json:"change_type,omitempty"` // 变化类型,如 task_reward/mint_cost/mint_reward/exhibition_revenue/level_up_bonus/manual_adjust
|
||||
SourceId string `protobuf:"bytes,5,opt,name=source_id,json=sourceId,proto3" json:"source_id,omitempty"` // 关联业务ID
|
||||
Description string `protobuf:"bytes,6,opt,name=description,proto3" json:"description,omitempty"` // 可读描述
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@ -1559,6 +1554,27 @@ func (x *UpdateCrystalBalanceRequest) GetDelta() int64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *UpdateCrystalBalanceRequest) GetChangeType() string {
|
||||
if x != nil {
|
||||
return x.ChangeType
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *UpdateCrystalBalanceRequest) GetSourceId() string {
|
||||
if x != nil {
|
||||
return x.SourceId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *UpdateCrystalBalanceRequest) GetDescription() string {
|
||||
if x != nil {
|
||||
return x.Description
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// 更新水晶余额响应
|
||||
type UpdateCrystalBalanceResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
@ -1726,30 +1742,30 @@ func (x *UpdateAssetsCountResponse) GetNewCount() int32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// 增加经验值请求(内部RPC调用,用于taskService增加经验)
|
||||
type AddExperienceRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` // 用户ID
|
||||
StarId int64 `protobuf:"varint,2,opt,name=star_id,json=starId,proto3" json:"star_id,omitempty"` // 明星ID
|
||||
Delta int64 `protobuf:"varint,3,opt,name=delta,proto3" json:"delta,omitempty"` // 变化量(正数增加,负数减少)
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
// 增加累计上架时长请求(内部RPC调用,用于galleryService展品下架时累加时长)
|
||||
type AddExhibitionHoursRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` // 用户ID
|
||||
StarId int64 `protobuf:"varint,2,opt,name=star_id,json=starId,proto3" json:"star_id,omitempty"` // 明星ID
|
||||
ExhibitionHours int64 `protobuf:"varint,3,opt,name=exhibition_hours,json=exhibitionHours,proto3" json:"exhibition_hours,omitempty"` // 本次展出的时长(小时)
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *AddExperienceRequest) Reset() {
|
||||
*x = AddExperienceRequest{}
|
||||
func (x *AddExhibitionHoursRequest) Reset() {
|
||||
*x = AddExhibitionHoursRequest{}
|
||||
mi := &file_user_proto_msgTypes[27]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *AddExperienceRequest) String() string {
|
||||
func (x *AddExhibitionHoursRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*AddExperienceRequest) ProtoMessage() {}
|
||||
func (*AddExhibitionHoursRequest) ProtoMessage() {}
|
||||
|
||||
func (x *AddExperienceRequest) ProtoReflect() protoreflect.Message {
|
||||
func (x *AddExhibitionHoursRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_user_proto_msgTypes[27]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
@ -1761,55 +1777,57 @@ func (x *AddExperienceRequest) ProtoReflect() protoreflect.Message {
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use AddExperienceRequest.ProtoReflect.Descriptor instead.
|
||||
func (*AddExperienceRequest) Descriptor() ([]byte, []int) {
|
||||
// Deprecated: Use AddExhibitionHoursRequest.ProtoReflect.Descriptor instead.
|
||||
func (*AddExhibitionHoursRequest) Descriptor() ([]byte, []int) {
|
||||
return file_user_proto_rawDescGZIP(), []int{27}
|
||||
}
|
||||
|
||||
func (x *AddExperienceRequest) GetUserId() int64 {
|
||||
func (x *AddExhibitionHoursRequest) GetUserId() int64 {
|
||||
if x != nil {
|
||||
return x.UserId
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *AddExperienceRequest) GetStarId() int64 {
|
||||
func (x *AddExhibitionHoursRequest) GetStarId() int64 {
|
||||
if x != nil {
|
||||
return x.StarId
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *AddExperienceRequest) GetDelta() int64 {
|
||||
func (x *AddExhibitionHoursRequest) GetExhibitionHours() int64 {
|
||||
if x != nil {
|
||||
return x.Delta
|
||||
return x.ExhibitionHours
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// 增加经验值响应
|
||||
type AddExperienceResponse struct {
|
||||
// 增加累计上架时长响应
|
||||
type AddExhibitionHoursResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Base *common.BaseResponse `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"`
|
||||
NewExperience int64 `protobuf:"varint,2,opt,name=new_experience,json=newExperience,proto3" json:"new_experience,omitempty"` // 更新后的经验值
|
||||
NewLevel int32 `protobuf:"varint,2,opt,name=new_level,json=newLevel,proto3" json:"new_level,omitempty"` // 新的等级
|
||||
LevelDelta int32 `protobuf:"varint,3,opt,name=level_delta,json=levelDelta,proto3" json:"level_delta,omitempty"` // 等级变化量(正数=升级,0=无变化)
|
||||
CrystalReward int64 `protobuf:"varint,4,opt,name=crystal_reward,json=crystalReward,proto3" json:"crystal_reward,omitempty"` // 升级水晶奖励(无升级时为0)
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *AddExperienceResponse) Reset() {
|
||||
*x = AddExperienceResponse{}
|
||||
func (x *AddExhibitionHoursResponse) Reset() {
|
||||
*x = AddExhibitionHoursResponse{}
|
||||
mi := &file_user_proto_msgTypes[28]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *AddExperienceResponse) String() string {
|
||||
func (x *AddExhibitionHoursResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*AddExperienceResponse) ProtoMessage() {}
|
||||
func (*AddExhibitionHoursResponse) ProtoMessage() {}
|
||||
|
||||
func (x *AddExperienceResponse) ProtoReflect() protoreflect.Message {
|
||||
func (x *AddExhibitionHoursResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_user_proto_msgTypes[28]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
@ -1821,21 +1839,35 @@ func (x *AddExperienceResponse) ProtoReflect() protoreflect.Message {
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use AddExperienceResponse.ProtoReflect.Descriptor instead.
|
||||
func (*AddExperienceResponse) Descriptor() ([]byte, []int) {
|
||||
// Deprecated: Use AddExhibitionHoursResponse.ProtoReflect.Descriptor instead.
|
||||
func (*AddExhibitionHoursResponse) Descriptor() ([]byte, []int) {
|
||||
return file_user_proto_rawDescGZIP(), []int{28}
|
||||
}
|
||||
|
||||
func (x *AddExperienceResponse) GetBase() *common.BaseResponse {
|
||||
func (x *AddExhibitionHoursResponse) GetBase() *common.BaseResponse {
|
||||
if x != nil {
|
||||
return x.Base
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *AddExperienceResponse) GetNewExperience() int64 {
|
||||
func (x *AddExhibitionHoursResponse) GetNewLevel() int32 {
|
||||
if x != nil {
|
||||
return x.NewExperience
|
||||
return x.NewLevel
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *AddExhibitionHoursResponse) GetLevelDelta() int32 {
|
||||
if x != nil {
|
||||
return x.LevelDelta
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *AddExhibitionHoursResponse) GetCrystalReward() int64 {
|
||||
if x != nil {
|
||||
return x.CrystalReward
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@ -2829,7 +2861,7 @@ const file_user_proto_rawDesc = "" +
|
||||
"\x15global_wallet_address\x18\x04 \x01(\tR\x13globalWalletAddress\x12\x1b\n" +
|
||||
"\tis_active\x18\x05 \x01(\bR\bisActive\x12\x1d\n" +
|
||||
"\n" +
|
||||
"created_at\x18\x06 \x01(\x03R\tcreatedAt\"\xfa\x03\n" +
|
||||
"created_at\x18\x06 \x01(\x03R\tcreatedAt\"\xda\x03\n" +
|
||||
"\n" +
|
||||
"FanProfile\x12\x0e\n" +
|
||||
"\x02id\x18\x01 \x01(\x03R\x02id\x12\x17\n" +
|
||||
@ -2838,23 +2870,20 @@ const file_user_proto_rawDesc = "" +
|
||||
"\bnickname\x18\x04 \x01(\tR\bnickname\x12\x14\n" +
|
||||
"\x05level\x18\x05 \x01(\x05R\x05level\x12\x14\n" +
|
||||
"\x05times\x18\x06 \x01(\x05R\x05times\x12\x16\n" +
|
||||
"\x06social\x18\a \x01(\x05R\x06social\x12\x1e\n" +
|
||||
"\n" +
|
||||
"experience\x18\b \x01(\x03R\n" +
|
||||
"experience\x12!\n" +
|
||||
"\fcoin_balance\x18\t \x01(\x03R\vcoinBalance\x12'\n" +
|
||||
"\x0fcrystal_balance\x18\n" +
|
||||
" \x01(\x03R\x0ecrystalBalance\x12\x12\n" +
|
||||
"\x04tags\x18\v \x03(\tR\x04tags\x12\x1d\n" +
|
||||
"\x06social\x18\a \x01(\x05R\x06social\x12!\n" +
|
||||
"\fcoin_balance\x18\b \x01(\x03R\vcoinBalance\x12'\n" +
|
||||
"\x0fcrystal_balance\x18\t \x01(\x03R\x0ecrystalBalance\x12\x12\n" +
|
||||
"\x04tags\x18\n" +
|
||||
" \x03(\tR\x04tags\x12\x1d\n" +
|
||||
"\n" +
|
||||
"avatar_url\x18\x11 \x01(\tR\tavatarUrl\x12\x1d\n" +
|
||||
"\n" +
|
||||
"created_at\x18\f \x01(\x03R\tcreatedAt\x12%\n" +
|
||||
"\x0estarbook_limit\x18\r \x01(\x05R\rstarbookLimit\x12\x1d\n" +
|
||||
"created_at\x18\v \x01(\x03R\tcreatedAt\x12%\n" +
|
||||
"\x0estarbook_limit\x18\f \x01(\x05R\rstarbookLimit\x12\x1d\n" +
|
||||
"\n" +
|
||||
"slot_limit\x18\x0e \x01(\x05R\tslotLimit\x12!\n" +
|
||||
"\fassets_count\x18\x0f \x01(\x05R\vassetsCount\x12#\n" +
|
||||
"\rchain_address\x18\x10 \x01(\tR\fchainAddress\"\xf6\x01\n" +
|
||||
"slot_limit\x18\r \x01(\x05R\tslotLimit\x12!\n" +
|
||||
"\fassets_count\x18\x0e \x01(\x05R\vassetsCount\x12#\n" +
|
||||
"\rchain_address\x18\x0f \x01(\tR\fchainAddress\"\xf6\x01\n" +
|
||||
"\x04Star\x12\x17\n" +
|
||||
"\astar_id\x18\x01 \x01(\x03R\x06starId\x12\x12\n" +
|
||||
"\x04name\x18\x02 \x01(\tR\x04name\x12\x17\n" +
|
||||
@ -2939,11 +2968,15 @@ const file_user_proto_rawDesc = "" +
|
||||
"\x1eUpdateFanProfileSocialResponse\x120\n" +
|
||||
"\x04base\x18\x01 \x01(\v2\x1c.topfans.common.BaseResponseR\x04base\x12\x1d\n" +
|
||||
"\n" +
|
||||
"new_social\x18\x02 \x01(\x05R\tnewSocial\"e\n" +
|
||||
"new_social\x18\x02 \x01(\x05R\tnewSocial\"\xc5\x01\n" +
|
||||
"\x1bUpdateCrystalBalanceRequest\x12\x17\n" +
|
||||
"\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x17\n" +
|
||||
"\astar_id\x18\x02 \x01(\x03R\x06starId\x12\x14\n" +
|
||||
"\x05delta\x18\x03 \x01(\x03R\x05delta\"q\n" +
|
||||
"\x05delta\x18\x03 \x01(\x03R\x05delta\x12\x1f\n" +
|
||||
"\vchange_type\x18\x04 \x01(\tR\n" +
|
||||
"changeType\x12\x1b\n" +
|
||||
"\tsource_id\x18\x05 \x01(\tR\bsourceId\x12 \n" +
|
||||
"\vdescription\x18\x06 \x01(\tR\vdescription\"q\n" +
|
||||
"\x1cUpdateCrystalBalanceResponse\x120\n" +
|
||||
"\x04base\x18\x01 \x01(\v2\x1c.topfans.common.BaseResponseR\x04base\x12\x1f\n" +
|
||||
"\vnew_balance\x18\x02 \x01(\x03R\n" +
|
||||
@ -2954,14 +2987,17 @@ const file_user_proto_rawDesc = "" +
|
||||
"\x05delta\x18\x03 \x01(\x05R\x05delta\"j\n" +
|
||||
"\x19UpdateAssetsCountResponse\x120\n" +
|
||||
"\x04base\x18\x01 \x01(\v2\x1c.topfans.common.BaseResponseR\x04base\x12\x1b\n" +
|
||||
"\tnew_count\x18\x02 \x01(\x05R\bnewCount\"^\n" +
|
||||
"\x14AddExperienceRequest\x12\x17\n" +
|
||||
"\tnew_count\x18\x02 \x01(\x05R\bnewCount\"x\n" +
|
||||
"\x19AddExhibitionHoursRequest\x12\x17\n" +
|
||||
"\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x17\n" +
|
||||
"\astar_id\x18\x02 \x01(\x03R\x06starId\x12\x14\n" +
|
||||
"\x05delta\x18\x03 \x01(\x03R\x05delta\"p\n" +
|
||||
"\x15AddExperienceResponse\x120\n" +
|
||||
"\x04base\x18\x01 \x01(\v2\x1c.topfans.common.BaseResponseR\x04base\x12%\n" +
|
||||
"\x0enew_experience\x18\x02 \x01(\x03R\rnewExperience\"\x17\n" +
|
||||
"\astar_id\x18\x02 \x01(\x03R\x06starId\x12)\n" +
|
||||
"\x10exhibition_hours\x18\x03 \x01(\x03R\x0fexhibitionHours\"\xb3\x01\n" +
|
||||
"\x1aAddExhibitionHoursResponse\x120\n" +
|
||||
"\x04base\x18\x01 \x01(\v2\x1c.topfans.common.BaseResponseR\x04base\x12\x1b\n" +
|
||||
"\tnew_level\x18\x02 \x01(\x05R\bnewLevel\x12\x1f\n" +
|
||||
"\vlevel_delta\x18\x03 \x01(\x05R\n" +
|
||||
"levelDelta\x12%\n" +
|
||||
"\x0ecrystal_reward\x18\x04 \x01(\x03R\rcrystalReward\"\x17\n" +
|
||||
"\x15GetCurrentUserRequest\"\xea\x01\n" +
|
||||
"\x16GetCurrentUserResponse\x120\n" +
|
||||
"\x04base\x18\x01 \x01(\v2\x1c.topfans.common.BaseResponseR\x04base\x12&\n" +
|
||||
@ -3023,7 +3059,7 @@ const file_user_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"expires_in\x18\x03 \x01(\x03R\texpiresIn\x129\n" +
|
||||
"\vfan_profile\x18\x04 \x01(\v2\x18.topfans.user.FanProfileR\n" +
|
||||
"fanProfile2\xca\x14\n" +
|
||||
"fanProfile2\xd9\x14\n" +
|
||||
"\x11UserSocialService\x12k\n" +
|
||||
"\bRegister\x12\x1d.topfans.user.RegisterRequest\x1a\x1e.topfans.user.RegisterResponse\" \x82\xd3\xe4\x93\x02\x1a:\x01*\"\x15/api/v1/auth/register\x12_\n" +
|
||||
"\x05Login\x12\x1a.topfans.user.LoginRequest\x1a\x1b.topfans.user.LoginResponse\"\x1d\x82\xd3\xe4\x93\x02\x17:\x01*\"\x12/api/v1/auth/login\x12v\n" +
|
||||
@ -3036,8 +3072,8 @@ const file_user_proto_rawDesc = "" +
|
||||
"\rGetFanProfile\x12\".topfans.user.GetFanProfileRequest\x1a#.topfans.user.GetFanProfileResponse\"6\x82\xd3\xe4\x93\x020\x12./api/v1/users/{user_id}/fan-profiles/{star_id}\x12s\n" +
|
||||
"\x16UpdateFanProfileSocial\x12+.topfans.user.UpdateFanProfileSocialRequest\x1a,.topfans.user.UpdateFanProfileSocialResponse\x12m\n" +
|
||||
"\x14UpdateCrystalBalance\x12).topfans.user.UpdateCrystalBalanceRequest\x1a*.topfans.user.UpdateCrystalBalanceResponse\x12d\n" +
|
||||
"\x11UpdateAssetsCount\x12&.topfans.user.UpdateAssetsCountRequest\x1a'.topfans.user.UpdateAssetsCountResponse\x12X\n" +
|
||||
"\rAddExperience\x12\".topfans.user.AddExperienceRequest\x1a#.topfans.user.AddExperienceResponse\x12t\n" +
|
||||
"\x11UpdateAssetsCount\x12&.topfans.user.UpdateAssetsCountRequest\x1a'.topfans.user.UpdateAssetsCountResponse\x12g\n" +
|
||||
"\x12AddExhibitionHours\x12'.topfans.user.AddExhibitionHoursRequest\x1a(.topfans.user.AddExhibitionHoursResponse\x12t\n" +
|
||||
"\x0eGetCurrentUser\x12#.topfans.user.GetCurrentUserRequest\x1a$.topfans.user.GetCurrentUserResponse\"\x17\x82\xd3\xe4\x93\x02\x11\x12\x0f/api/v1/auth/me\x12q\n" +
|
||||
"\fGetMyProfile\x12!.topfans.user.GetMyProfileRequest\x1a\".topfans.user.GetMyProfileResponse\"\x1a\x82\xd3\xe4\x93\x02\x14\x12\x12/api/v1/me/profile\x12z\n" +
|
||||
"\x0eUpdateNickname\x12#.topfans.user.UpdateNicknameRequest\x1a$.topfans.user.UpdateNicknameResponse\"\x1d\x82\xd3\xe4\x93\x02\x17:\x01*\"\x12/api/v1/me/profile\x12\x80\x01\n" +
|
||||
@ -3089,8 +3125,8 @@ var file_user_proto_goTypes = []any{
|
||||
(*UpdateCrystalBalanceResponse)(nil), // 24: topfans.user.UpdateCrystalBalanceResponse
|
||||
(*UpdateAssetsCountRequest)(nil), // 25: topfans.user.UpdateAssetsCountRequest
|
||||
(*UpdateAssetsCountResponse)(nil), // 26: topfans.user.UpdateAssetsCountResponse
|
||||
(*AddExperienceRequest)(nil), // 27: topfans.user.AddExperienceRequest
|
||||
(*AddExperienceResponse)(nil), // 28: topfans.user.AddExperienceResponse
|
||||
(*AddExhibitionHoursRequest)(nil), // 27: topfans.user.AddExhibitionHoursRequest
|
||||
(*AddExhibitionHoursResponse)(nil), // 28: topfans.user.AddExhibitionHoursResponse
|
||||
(*GetCurrentUserRequest)(nil), // 29: topfans.user.GetCurrentUserRequest
|
||||
(*GetCurrentUserResponse)(nil), // 30: topfans.user.GetCurrentUserResponse
|
||||
(*GetMyProfileRequest)(nil), // 31: topfans.user.GetMyProfileRequest
|
||||
@ -3132,7 +3168,7 @@ var file_user_proto_depIdxs = []int32{
|
||||
48, // 16: topfans.user.UpdateFanProfileSocialResponse.base:type_name -> topfans.common.BaseResponse
|
||||
48, // 17: topfans.user.UpdateCrystalBalanceResponse.base:type_name -> topfans.common.BaseResponse
|
||||
48, // 18: topfans.user.UpdateAssetsCountResponse.base:type_name -> topfans.common.BaseResponse
|
||||
48, // 19: topfans.user.AddExperienceResponse.base:type_name -> topfans.common.BaseResponse
|
||||
48, // 19: topfans.user.AddExhibitionHoursResponse.base:type_name -> topfans.common.BaseResponse
|
||||
48, // 20: topfans.user.GetCurrentUserResponse.base:type_name -> topfans.common.BaseResponse
|
||||
0, // 21: topfans.user.GetCurrentUserResponse.user:type_name -> topfans.user.User
|
||||
1, // 22: topfans.user.GetCurrentUserResponse.fan_profile:type_name -> topfans.user.FanProfile
|
||||
@ -3167,7 +3203,7 @@ var file_user_proto_depIdxs = []int32{
|
||||
21, // 51: topfans.user.UserSocialService.UpdateFanProfileSocial:input_type -> topfans.user.UpdateFanProfileSocialRequest
|
||||
23, // 52: topfans.user.UserSocialService.UpdateCrystalBalance:input_type -> topfans.user.UpdateCrystalBalanceRequest
|
||||
25, // 53: topfans.user.UserSocialService.UpdateAssetsCount:input_type -> topfans.user.UpdateAssetsCountRequest
|
||||
27, // 54: topfans.user.UserSocialService.AddExperience:input_type -> topfans.user.AddExperienceRequest
|
||||
27, // 54: topfans.user.UserSocialService.AddExhibitionHours:input_type -> topfans.user.AddExhibitionHoursRequest
|
||||
29, // 55: topfans.user.UserSocialService.GetCurrentUser:input_type -> topfans.user.GetCurrentUserRequest
|
||||
31, // 56: topfans.user.UserSocialService.GetMyProfile:input_type -> topfans.user.GetMyProfileRequest
|
||||
33, // 57: topfans.user.UserSocialService.UpdateNickname:input_type -> topfans.user.UpdateNicknameRequest
|
||||
@ -3189,7 +3225,7 @@ var file_user_proto_depIdxs = []int32{
|
||||
22, // 73: topfans.user.UserSocialService.UpdateFanProfileSocial:output_type -> topfans.user.UpdateFanProfileSocialResponse
|
||||
24, // 74: topfans.user.UserSocialService.UpdateCrystalBalance:output_type -> topfans.user.UpdateCrystalBalanceResponse
|
||||
26, // 75: topfans.user.UserSocialService.UpdateAssetsCount:output_type -> topfans.user.UpdateAssetsCountResponse
|
||||
28, // 76: topfans.user.UserSocialService.AddExperience:output_type -> topfans.user.AddExperienceResponse
|
||||
28, // 76: topfans.user.UserSocialService.AddExhibitionHours:output_type -> topfans.user.AddExhibitionHoursResponse
|
||||
30, // 77: topfans.user.UserSocialService.GetCurrentUser:output_type -> topfans.user.GetCurrentUserResponse
|
||||
32, // 78: topfans.user.UserSocialService.GetMyProfile:output_type -> topfans.user.GetMyProfileResponse
|
||||
34, // 79: topfans.user.UserSocialService.UpdateNickname:output_type -> topfans.user.UpdateNicknameResponse
|
||||
|
||||
@ -60,8 +60,8 @@ const (
|
||||
UserSocialServiceUpdateCrystalBalanceProcedure = "/topfans.user.UserSocialService/UpdateCrystalBalance"
|
||||
// UserSocialServiceUpdateAssetsCountProcedure is the fully-qualified name of the UserSocialService's UpdateAssetsCount RPC.
|
||||
UserSocialServiceUpdateAssetsCountProcedure = "/topfans.user.UserSocialService/UpdateAssetsCount"
|
||||
// UserSocialServiceAddExperienceProcedure is the fully-qualified name of the UserSocialService's AddExperience RPC.
|
||||
UserSocialServiceAddExperienceProcedure = "/topfans.user.UserSocialService/AddExperience"
|
||||
// UserSocialServiceAddExhibitionHoursProcedure is the fully-qualified name of the UserSocialService's AddExhibitionHours RPC.
|
||||
UserSocialServiceAddExhibitionHoursProcedure = "/topfans.user.UserSocialService/AddExhibitionHours"
|
||||
// UserSocialServiceGetCurrentUserProcedure is the fully-qualified name of the UserSocialService's GetCurrentUser RPC.
|
||||
UserSocialServiceGetCurrentUserProcedure = "/topfans.user.UserSocialService/GetCurrentUser"
|
||||
// UserSocialServiceGetMyProfileProcedure is the fully-qualified name of the UserSocialService's GetMyProfile RPC.
|
||||
@ -100,7 +100,7 @@ type UserSocialService interface {
|
||||
UpdateFanProfileSocial(ctx context.Context, req *UpdateFanProfileSocialRequest, opts ...client.CallOption) (*UpdateFanProfileSocialResponse, error)
|
||||
UpdateCrystalBalance(ctx context.Context, req *UpdateCrystalBalanceRequest, opts ...client.CallOption) (*UpdateCrystalBalanceResponse, error)
|
||||
UpdateAssetsCount(ctx context.Context, req *UpdateAssetsCountRequest, opts ...client.CallOption) (*UpdateAssetsCountResponse, error)
|
||||
AddExperience(ctx context.Context, req *AddExperienceRequest, opts ...client.CallOption) (*AddExperienceResponse, error)
|
||||
AddExhibitionHours(ctx context.Context, req *AddExhibitionHoursRequest, opts ...client.CallOption) (*AddExhibitionHoursResponse, error)
|
||||
GetCurrentUser(ctx context.Context, req *GetCurrentUserRequest, opts ...client.CallOption) (*GetCurrentUserResponse, error)
|
||||
GetMyProfile(ctx context.Context, req *GetMyProfileRequest, opts ...client.CallOption) (*GetMyProfileResponse, error)
|
||||
UpdateNickname(ctx context.Context, req *UpdateNicknameRequest, opts ...client.CallOption) (*UpdateNicknameResponse, error)
|
||||
@ -228,9 +228,9 @@ func (c *UserSocialServiceImpl) UpdateAssetsCount(ctx context.Context, req *Upda
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *UserSocialServiceImpl) AddExperience(ctx context.Context, req *AddExperienceRequest, opts ...client.CallOption) (*AddExperienceResponse, error) {
|
||||
resp := new(AddExperienceResponse)
|
||||
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "AddExperience", opts...); err != nil {
|
||||
func (c *UserSocialServiceImpl) AddExhibitionHours(ctx context.Context, req *AddExhibitionHoursRequest, opts ...client.CallOption) (*AddExhibitionHoursResponse, error) {
|
||||
resp := new(AddExhibitionHoursResponse)
|
||||
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "AddExhibitionHours", opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
@ -310,7 +310,7 @@ func (c *UserSocialServiceImpl) SwitchIdentity(ctx context.Context, req *SwitchI
|
||||
|
||||
var UserSocialService_ClientInfo = client.ClientInfo{
|
||||
InterfaceName: "topfans.user.UserSocialService",
|
||||
MethodNames: []string{"Register", "Login", "RefreshToken", "ValidateToken", "Logout", "CheckNickname", "CheckMobile", "GetUser", "GetFanProfile", "UpdateFanProfileSocial", "UpdateCrystalBalance", "UpdateAssetsCount", "AddExperience", "GetCurrentUser", "GetMyProfile", "UpdateNickname", "UpdatePassword", "UpdateAvatar", "GetFanIdentities", "GetMyFanIdentities", "AddIdentity", "SwitchIdentity"},
|
||||
MethodNames: []string{"Register", "Login", "RefreshToken", "ValidateToken", "Logout", "CheckNickname", "CheckMobile", "GetUser", "GetFanProfile", "UpdateFanProfileSocial", "UpdateCrystalBalance", "UpdateAssetsCount", "AddExhibitionHours", "GetCurrentUser", "GetMyProfile", "UpdateNickname", "UpdatePassword", "UpdateAvatar", "GetFanIdentities", "GetMyFanIdentities", "AddIdentity", "SwitchIdentity"},
|
||||
ConnectionInjectFunc: func(dubboCliRaw interface{}, conn *client.Connection) {
|
||||
dubboCli := dubboCliRaw.(*UserSocialServiceImpl)
|
||||
dubboCli.conn = conn
|
||||
@ -331,7 +331,7 @@ type UserSocialServiceHandler interface {
|
||||
UpdateFanProfileSocial(context.Context, *UpdateFanProfileSocialRequest) (*UpdateFanProfileSocialResponse, error)
|
||||
UpdateCrystalBalance(context.Context, *UpdateCrystalBalanceRequest) (*UpdateCrystalBalanceResponse, error)
|
||||
UpdateAssetsCount(context.Context, *UpdateAssetsCountRequest) (*UpdateAssetsCountResponse, error)
|
||||
AddExperience(context.Context, *AddExperienceRequest) (*AddExperienceResponse, error)
|
||||
AddExhibitionHours(context.Context, *AddExhibitionHoursRequest) (*AddExhibitionHoursResponse, error)
|
||||
GetCurrentUser(context.Context, *GetCurrentUserRequest) (*GetCurrentUserResponse, error)
|
||||
GetMyProfile(context.Context, *GetMyProfileRequest) (*GetMyProfileResponse, error)
|
||||
UpdateNickname(context.Context, *UpdateNicknameRequest) (*UpdateNicknameResponse, error)
|
||||
@ -536,14 +536,14 @@ var UserSocialService_ServiceInfo = server.ServiceInfo{
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "AddExperience",
|
||||
Name: "AddExhibitionHours",
|
||||
Type: constant.CallUnary,
|
||||
ReqInitFunc: func() interface{} {
|
||||
return new(AddExperienceRequest)
|
||||
return new(AddExhibitionHoursRequest)
|
||||
},
|
||||
MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) {
|
||||
req := args[0].(*AddExperienceRequest)
|
||||
res, err := handler.(UserSocialServiceHandler).AddExperience(ctx, req)
|
||||
req := args[0].(*AddExhibitionHoursRequest)
|
||||
res, err := handler.(UserSocialServiceHandler).AddExhibitionHours(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -15,9 +15,8 @@ message DailyTaskItem {
|
||||
string name = 3;
|
||||
string description = 4;
|
||||
int64 crystal_reward = 5;
|
||||
int64 exp_reward = 6;
|
||||
string status = 7; // pending/completed/claimed
|
||||
bool can_claim = 8;
|
||||
string status = 6; // pending/completed/claimed
|
||||
bool can_claim = 7;
|
||||
}
|
||||
|
||||
message GetDailyTasksRequest {
|
||||
@ -52,7 +51,6 @@ message ClaimDailyTaskResponse {
|
||||
topfans.common.BaseResponse base = 1;
|
||||
bool success = 2;
|
||||
int64 crystal_balance = 3;
|
||||
int64 experience = 4;
|
||||
}
|
||||
|
||||
message ClaimAllDailyTasksRequest {
|
||||
@ -63,8 +61,7 @@ message ClaimAllDailyTasksResponse {
|
||||
topfans.common.BaseResponse base = 1;
|
||||
int32 claimed_count = 2;
|
||||
int64 crystal_balance = 3;
|
||||
int64 experience = 4;
|
||||
repeated string claimed_task_keys = 5;
|
||||
repeated string claimed_task_keys = 4;
|
||||
}
|
||||
|
||||
// ==================== 引导任务 ====================
|
||||
@ -74,11 +71,10 @@ message OnboardingStage {
|
||||
string name = 2;
|
||||
repeated string required_task_keys = 3;
|
||||
int64 crystal_reward = 4;
|
||||
int64 exp_reward = 5;
|
||||
string status = 6; // pending/completed/in_progress
|
||||
bool is_current = 7;
|
||||
bool all_tasks_completed = 8; // 该阶段所有任务是否完成
|
||||
bool is_reward_claimed = 9; // 该阶段奖励是否已领取
|
||||
string status = 5; // pending/completed/in_progress
|
||||
bool is_current = 6;
|
||||
bool all_tasks_completed = 7; // 该阶段所有任务是否完成
|
||||
bool is_reward_claimed = 8; // 该阶段奖励是否已领取
|
||||
}
|
||||
|
||||
message CompleteGuideRequest {
|
||||
@ -123,7 +119,6 @@ message ClaimOnboardingRewardResponse {
|
||||
topfans.common.BaseResponse base = 1;
|
||||
bool success = 2;
|
||||
string crystal_balance = 3; // 使用 string 避免 Dubbo int64 序列化 bug
|
||||
string experience = 4; // 使用 string 避免 Dubbo int64 序列化 bug
|
||||
}
|
||||
|
||||
// ==================== 展示收益 ====================
|
||||
|
||||
@ -28,16 +28,15 @@ message FanProfile {
|
||||
int32 level = 5; // 等级
|
||||
int32 times = 6; // 剩余铸造次数
|
||||
int32 social = 7; // 好友个数
|
||||
int64 experience = 8; // 经验值
|
||||
int64 coin_balance = 9; // 游戏币余额
|
||||
int64 crystal_balance = 10; // 顶粉水晶余额
|
||||
repeated string tags = 11; // 标签数组
|
||||
int64 coin_balance = 8; // 游戏币余额
|
||||
int64 crystal_balance = 9; // 顶粉水晶余额
|
||||
repeated string tags = 10; // 标签数组
|
||||
string avatar_url = 17; // 头像URL(星球专属)
|
||||
int64 created_at = 12;
|
||||
int32 starbook_limit = 13; // 星书限制
|
||||
int32 slot_limit = 14; // 槽位限制
|
||||
int32 assets_count = 15; // 资产数量
|
||||
string chain_address = 16; // 链地址
|
||||
int64 created_at = 11;
|
||||
int32 starbook_limit = 12; // 星书限制
|
||||
int32 slot_limit = 13; // 槽位限制
|
||||
int32 assets_count = 14; // 资产数量
|
||||
string chain_address = 15; // 链地址
|
||||
}
|
||||
|
||||
// 明星信息
|
||||
@ -190,6 +189,9 @@ message UpdateCrystalBalanceRequest {
|
||||
int64 user_id = 1; // 用户ID
|
||||
int64 star_id = 2; // 明星ID
|
||||
int64 delta = 3; // 变化量(正数增加,负数减少)
|
||||
string change_type = 4; // 变化类型,如 task_reward/mint_cost/mint_reward/exhibition_revenue/level_up_bonus/manual_adjust
|
||||
string source_id = 5; // 关联业务ID
|
||||
string description = 6; // 可读描述
|
||||
}
|
||||
|
||||
// 更新水晶余额响应
|
||||
@ -211,17 +213,19 @@ message UpdateAssetsCountResponse {
|
||||
int32 new_count = 2; // 更新后的资产数量
|
||||
}
|
||||
|
||||
// 增加经验值请求(内部RPC调用,用于taskService增加经验)
|
||||
message AddExperienceRequest {
|
||||
// 增加累计上架时长请求(内部RPC调用,用于galleryService展品下架时累加时长)
|
||||
message AddExhibitionHoursRequest {
|
||||
int64 user_id = 1; // 用户ID
|
||||
int64 star_id = 2; // 明星ID
|
||||
int64 delta = 3; // 变化量(正数增加,负数减少)
|
||||
int64 exhibition_hours = 3; // 本次展出的时长(小时)
|
||||
}
|
||||
|
||||
// 增加经验值响应
|
||||
message AddExperienceResponse {
|
||||
// 增加累计上架时长响应
|
||||
message AddExhibitionHoursResponse {
|
||||
topfans.common.BaseResponse base = 1;
|
||||
int64 new_experience = 2; // 更新后的经验值
|
||||
int32 new_level = 2; // 新的等级
|
||||
int32 level_delta = 3; // 等级变化量(正数=升级,0=无变化)
|
||||
int64 crystal_reward = 4; // 升级水晶奖励(无升级时为0)
|
||||
}
|
||||
|
||||
// 获取当前登录用户信息请求
|
||||
@ -414,8 +418,8 @@ service UserSocialService {
|
||||
// 内部RPC:更新资产数量(仅供assetService调用)
|
||||
rpc UpdateAssetsCount(UpdateAssetsCountRequest) returns (UpdateAssetsCountResponse);
|
||||
|
||||
// 内部RPC:增加经验值(仅供taskService调用)
|
||||
rpc AddExperience(AddExperienceRequest) returns (AddExperienceResponse);
|
||||
// 内部RPC:增加累计上架时长(仅供galleryService调用)
|
||||
rpc AddExhibitionHours(AddExhibitionHoursRequest) returns (AddExhibitionHoursResponse);
|
||||
|
||||
rpc GetCurrentUser(GetCurrentUserRequest) returns (GetCurrentUserResponse) {
|
||||
option (google.api.http) = {
|
||||
|
||||
278
backend/scripts/20260513_economic_system.sql
Normal file
278
backend/scripts/20260513_economic_system.sql
Normal file
@ -0,0 +1,278 @@
|
||||
-- ============================================================
|
||||
-- 经济系统建表脚本
|
||||
-- 执行方式: psql -h <host> -U <user> -d <db> -f backend/scripts/20260513_economic_system.sql
|
||||
-- 创建日期: 2026-05-13
|
||||
-- 说明: 本脚本用于初始化经济系统相关的表和配置
|
||||
-- - fan_profiles 表新增字段: revenue_boost_bps, like_bet_count
|
||||
-- - 铸造消耗配置、用户铸爱累计
|
||||
-- - 等级阈值、升级奖励配置
|
||||
-- - 水晶/游戏币流水表
|
||||
-- ============================================================
|
||||
|
||||
-- 1. Add revenue_boost_bps column to fan_profiles (if not exists)
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'fan_profiles' AND column_name = 'revenue_boost_bps') THEN
|
||||
ALTER TABLE fan_profiles ADD COLUMN revenue_boost_bps INT NOT NULL DEFAULT 0;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- 1.2 Add like_bet_count column to fan_profiles (if not exists)
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'fan_profiles' AND column_name = 'like_bet_count') THEN
|
||||
ALTER TABLE fan_profiles ADD COLUMN like_bet_count INT NOT NULL DEFAULT 0;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- 2. 铸造消耗配置表
|
||||
CREATE TABLE IF NOT EXISTS mint_cost_config (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
mint_count INT NOT NULL UNIQUE,
|
||||
cost_crystal BIGINT NOT NULL,
|
||||
probability BIGINT DEFAULT 0,
|
||||
reward_type VARCHAR(50) DEFAULT NULL,
|
||||
reward_value BIGINT DEFAULT 0,
|
||||
description VARCHAR(255),
|
||||
updated_at BIGINT NOT NULL
|
||||
);
|
||||
COMMENT ON TABLE mint_cost_config IS '铸造消耗配置表,记录每次铸造的水晶消耗和保底概率';
|
||||
|
||||
-- 初始数据
|
||||
INSERT INTO mint_cost_config (mint_count, cost_crystal, probability, reward_type, reward_value, description, updated_at) VALUES
|
||||
(1, 2, 0, NULL, 0, '第1次铸造', ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(2, 4, 0, NULL, 0, '第2次铸造', ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(3, 8, 0, NULL, 0, '第3次铸造', ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(4, 16, 0, NULL, 0, '第4次铸造', ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(5, 32, 0, NULL, 0, '第5次铸造', ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(6, 64, 0, NULL, 0, '第6次铸造', ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(7, 128, 0, NULL, 0, '第7次铸造', ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(8, 256, 0, NULL, 0, '第8次铸造', ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(9, 512, 20, '收益提升', 500, '20%概率,获得500 bps(+5%)永久收益提升(小保底)', ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(10, 1024, 100, '收益提升', 500, '100%概率,获得500 bps(+5%)永久收益提升(大保底)', ROUND(EXTRACT(EPOCH FROM NOW()) * 1000))
|
||||
ON CONFLICT (mint_count) DO NOTHING;
|
||||
|
||||
-- 3. 用户铸爱累计表
|
||||
CREATE TABLE IF NOT EXISTS user_mint_count (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL,
|
||||
star_id BIGINT NOT NULL,
|
||||
mint_count INT NOT NULL DEFAULT 0,
|
||||
revenue_boost_bps INT NOT NULL DEFAULT 0,
|
||||
updated_at BIGINT NOT NULL,
|
||||
CONSTRAINT uk_user_mint_star UNIQUE (user_id, star_id)
|
||||
);
|
||||
COMMENT ON TABLE user_mint_count IS '用户铸爱累计表,记录用户累计铸造次数和永久收益提升基点';
|
||||
|
||||
-- 4. 等级阈值配置表
|
||||
CREATE TABLE IF NOT EXISTS level_thresholds (
|
||||
level INT PRIMARY KEY,
|
||||
max_exhibition_hours BIGINT NOT NULL,
|
||||
like_bet_count INT NOT NULL,
|
||||
description VARCHAR(100)
|
||||
);
|
||||
COMMENT ON TABLE level_thresholds IS '等级阈值配置表,记录升级到每个等级需要的累计上架时长和点赞押注次数';
|
||||
|
||||
-- 初始数据(20级满级)
|
||||
INSERT INTO level_thresholds (level, max_exhibition_hours, like_bet_count, description) VALUES
|
||||
(1, 0, 0, '1级新手'),
|
||||
(2, 6, 6, '2级粉丝'),
|
||||
(3, 12, 7, '3级真爱'),
|
||||
(4, 18, 8, '4级铁粉'),
|
||||
(5, 24, 9, '5级钻石粉'),
|
||||
(6, 30, 9, '6级钻石粉'),
|
||||
(7, 36, 10, '7级钻石粉'),
|
||||
(8, 42, 11, '8级钻石粉'),
|
||||
(9, 48, 12, '9级钻石粉'),
|
||||
(10, 54, 13, '10级钻石粉'),
|
||||
(11, 60, 13, '11级钻石粉'),
|
||||
(12, 66, 13, '12级钻石粉'),
|
||||
(13, 72, 14, '13级钻石粉'),
|
||||
(14, 78, 15, '14级钻石粉'),
|
||||
(15, 84, 16, '15级钻石粉'),
|
||||
(16, 90, 16, '16级钻石粉'),
|
||||
(17, 96, 17, '17级钻石粉'),
|
||||
(18, 102, 18, '18级钻石粉'),
|
||||
(19, 108, 19, '19级钻石粉'),
|
||||
(20, 114, 20, '20级终极粉')
|
||||
ON CONFLICT (level) DO NOTHING;
|
||||
|
||||
-- 5. 用户累计上架时长表
|
||||
CREATE TABLE IF NOT EXISTS user_exhibition_hours (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL,
|
||||
star_id BIGINT NOT NULL,
|
||||
total_exhibition_hours BIGINT NOT NULL DEFAULT 0,
|
||||
updated_at BIGINT NOT NULL,
|
||||
CONSTRAINT uk_exhibition_user_star UNIQUE (user_id, star_id)
|
||||
);
|
||||
COMMENT ON TABLE user_exhibition_hours IS '用户累计上架时长表,记录用户累计上架时长';
|
||||
|
||||
-- 6. 等级上限配置表
|
||||
CREATE TABLE IF NOT EXISTS level_cap_config (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
max_level INT NOT NULL DEFAULT 20,
|
||||
updated_at BIGINT NOT NULL
|
||||
);
|
||||
COMMENT ON TABLE level_cap_config IS '等级上限配置表,记录最高等级';
|
||||
|
||||
INSERT INTO level_cap_config (max_level, updated_at) VALUES (20, ROUND(EXTRACT(EPOCH FROM NOW()) * 1000))
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- 7. 等级升级条件配置表(21级+)
|
||||
CREATE TABLE IF NOT EXISTS level_upgrade_conditions (
|
||||
level INT PRIMARY KEY,
|
||||
require_total_hours BIGINT NOT NULL,
|
||||
require_dazi_level INT DEFAULT 0,
|
||||
description VARCHAR(100),
|
||||
updated_at BIGINT NOT NULL
|
||||
);
|
||||
COMMENT ON TABLE level_upgrade_conditions IS '等级升级条件配置表,记录21级及以上的升级条件';
|
||||
|
||||
-- 8. 升级奖励配置表
|
||||
CREATE TABLE IF NOT EXISTS level_up_reward_config (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
level INT NOT NULL,
|
||||
reward_type VARCHAR(50) NOT NULL,
|
||||
reward_value BIGINT NOT NULL DEFAULT 0,
|
||||
is_enabled BOOLEAN DEFAULT true,
|
||||
updated_at BIGINT NOT NULL,
|
||||
CONSTRAINT uk_level_reward_type UNIQUE (level, reward_type)
|
||||
);
|
||||
COMMENT ON TABLE level_up_reward_config IS '升级奖励配置表,记录升级时发放的奖励类型和数值';
|
||||
|
||||
-- 初始数据(21级示例,后续可扩展更多等级)
|
||||
INSERT INTO level_upgrade_conditions (level, require_total_hours, require_dazi_level, description, updated_at) VALUES
|
||||
(21, 120, 21, '21级:总时长120h + 搭子21级', ROUND(EXTRACT(EPOCH FROM NOW()) * 1000))
|
||||
ON CONFLICT (level) DO NOTHING;
|
||||
|
||||
-- 8. 升级奖励配置表
|
||||
CREATE TABLE IF NOT EXISTS level_up_reward_config (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
level INT NOT NULL,
|
||||
reward_type VARCHAR(50) NOT NULL,
|
||||
reward_value BIGINT NOT NULL DEFAULT 0,
|
||||
is_enabled BOOLEAN DEFAULT true,
|
||||
updated_at BIGINT NOT NULL,
|
||||
CONSTRAINT uk_level_reward_type UNIQUE (level, reward_type)
|
||||
);
|
||||
|
||||
-- 初始数据示例
|
||||
INSERT INTO level_up_reward_config (level, reward_type, reward_value, is_enabled, updated_at) VALUES
|
||||
(2, 'crystal', 10, true, ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(2, 'like_bet_count', 1, true, ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(3, 'crystal', 20, true, ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(3, 'like_bet_count', 1, true, ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(4, 'crystal', 30, true, ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(4, 'like_bet_count', 1, true, ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(5, 'crystal', 50, true, ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(5, 'like_bet_count', 1, true, ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(6, 'crystal', 80, true, ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(7, 'crystal', 120, true, ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(8, 'crystal', 180, true, ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(9, 'crystal', 280, true, ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(10, 'crystal', 500, true, ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(11, 'crystal', 500, true, ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(12, 'crystal', 500, true, ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(13, 'crystal', 500, true, ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(14, 'crystal', 500, true, ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(15, 'crystal', 500, true, ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(16, 'crystal', 500, true, ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(17, 'crystal', 500, true, ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(18, 'crystal', 500, true, ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(19, 'crystal', 500, true, ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(20, 'crystal', 500, true, ROUND(EXTRACT(EPOCH FROM NOW()) * 1000))
|
||||
ON CONFLICT (level, reward_type) DO NOTHING;
|
||||
|
||||
-- 9. 搭子等级阈值配置表(预留)
|
||||
CREATE TABLE IF NOT EXISTS dazi_level_thresholds (
|
||||
level INT PRIMARY KEY,
|
||||
upgrade_condition VARCHAR(100),
|
||||
condition_param INT DEFAULT 0,
|
||||
description VARCHAR(100)
|
||||
);
|
||||
COMMENT ON TABLE dazi_level_thresholds IS '搭子等级阈值配置表(预留)';
|
||||
|
||||
-- 10. 用户搭子等级表(预留)
|
||||
CREATE TABLE IF NOT EXISTS user_dazi_level (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL,
|
||||
star_id BIGINT NOT NULL,
|
||||
dazi_level INT NOT NULL DEFAULT 1,
|
||||
updated_at BIGINT NOT NULL,
|
||||
CONSTRAINT uk_dazi_user_star UNIQUE (user_id, star_id)
|
||||
);
|
||||
COMMENT ON TABLE user_dazi_level IS '用户搭子等级表(预留)';
|
||||
|
||||
-- 11. 水晶交易流水表(预留)
|
||||
CREATE TABLE IF NOT EXISTS crystal_transaction_records (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL,
|
||||
star_id BIGINT NOT NULL,
|
||||
change_type VARCHAR(30) NOT NULL,
|
||||
delta BIGINT NOT NULL,
|
||||
balance_before BIGINT NOT NULL,
|
||||
balance_after BIGINT NOT NULL,
|
||||
source_id VARCHAR(100),
|
||||
description VARCHAR(255),
|
||||
created_at BIGINT NOT NULL
|
||||
);
|
||||
COMMENT ON TABLE crystal_transaction_records IS '水晶交易流水表,记录水晶的收支明细(复式记账)';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_crystal_tx_user_star ON crystal_transaction_records(user_id, star_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_crystal_tx_created ON crystal_transaction_records(created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS ix_crystal_tx_change_type ON crystal_transaction_records(change_type);
|
||||
|
||||
-- 12. 游戏币交易流水表(预留)
|
||||
CREATE TABLE IF NOT EXISTS coin_transaction_records (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL,
|
||||
star_id BIGINT NOT NULL,
|
||||
change_type VARCHAR(30) NOT NULL,
|
||||
delta BIGINT NOT NULL,
|
||||
balance_before BIGINT NOT NULL,
|
||||
balance_after BIGINT NOT NULL,
|
||||
source_id VARCHAR(100),
|
||||
description VARCHAR(255),
|
||||
created_at BIGINT NOT NULL
|
||||
);
|
||||
COMMENT ON TABLE coin_transaction_records IS '游戏币交易流水表(预留)';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_coin_tx_user_star ON coin_transaction_records(user_id, star_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_coin_tx_created ON coin_transaction_records(created_at DESC);
|
||||
|
||||
-- 13. 铸造奖励配置表(预留)
|
||||
CREATE TABLE IF NOT EXISTS mint_reward_config (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
star_id BIGINT NOT NULL UNIQUE,
|
||||
base_reward BIGINT NOT NULL DEFAULT 0,
|
||||
is_enabled BOOLEAN DEFAULT true,
|
||||
updated_at BIGINT NOT NULL
|
||||
);
|
||||
COMMENT ON TABLE mint_reward_config IS '铸造奖励配置表(预留),记录每个明星的铸造基础奖励';
|
||||
|
||||
-- 初始数据(0=全服默认)
|
||||
INSERT INTO mint_reward_config (star_id, base_reward, is_enabled, updated_at) VALUES (0, 0, false, ROUND(EXTRACT(EPOCH FROM NOW()) * 1000))
|
||||
ON CONFLICT (star_id) DO NOTHING;
|
||||
|
||||
-- 14. 铸造阶梯奖励配置表(预留)
|
||||
CREATE TABLE IF NOT EXISTS mint_milestone_config (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
star_id BIGINT NOT NULL,
|
||||
milestone_count INT NOT NULL,
|
||||
bonus_reward BIGINT NOT NULL,
|
||||
created_at BIGINT NOT NULL,
|
||||
CONSTRAINT uk_milestone_star_count UNIQUE (star_id, milestone_count)
|
||||
);
|
||||
COMMENT ON TABLE mint_milestone_config IS '铸造阶梯奖励配置表(预留),记录铸造里程碑奖励';
|
||||
|
||||
-- 初始数据示例
|
||||
INSERT INTO mint_milestone_config (star_id, milestone_count, bonus_reward, created_at) VALUES
|
||||
(0, 10, 5, ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(0, 30, 15, ROUND(EXTRACT(EPOCH FROM NOW()) * 1000)),
|
||||
(0, 100, 50, ROUND(EXTRACT(EPOCH FROM NOW()) * 1000))
|
||||
ON CONFLICT (star_id, milestone_count) DO NOTHING;
|
||||
|
||||
-- ============================================================
|
||||
-- 完成
|
||||
-- ============================================================
|
||||
@ -63,8 +63,8 @@ ON CONFLICT (mobile) DO UPDATE SET password_hash = EXCLUDED.password_hash;
|
||||
fmt.Println()
|
||||
|
||||
// 2. 插入粉丝档案
|
||||
fmt.Printf(`INSERT INTO fan_profiles (user_id, star_id, nickname, level, times, social, experience, coin_balance, crystal_balance, tags, starbook_limit, slot_limit, assets_count, is_active, created_at, updated_at)
|
||||
VALUES (%d, %d, '%s', 1, 1, 0, 0, 0, 1000, '[]'::jsonb, 3, 3, 2, true, %d, %d)
|
||||
fmt.Printf(`INSERT INTO fan_profiles (user_id, star_id, nickname, level, times, social, coin_balance, crystal_balance, tags, starbook_limit, slot_limit, assets_count, is_active, created_at, updated_at)
|
||||
VALUES (%d, %d, '%s', 1, 1, 0, 0, 1000, '[]'::jsonb, 3, 3, 2, true, %d, %d)
|
||||
ON CONFLICT (user_id, star_id) DO UPDATE SET nickname = EXCLUDED.nickname, crystal_balance = EXCLUDED.crystal_balance;
|
||||
`, user1ID, star1ID, user1Nickname, now, now)
|
||||
fmt.Println()
|
||||
@ -114,8 +114,8 @@ ON CONFLICT (mobile) DO UPDATE SET password_hash = EXCLUDED.password_hash;
|
||||
fmt.Println()
|
||||
|
||||
// 2. 插入粉丝档案
|
||||
fmt.Printf(`INSERT INTO fan_profiles (user_id, star_id, nickname, level, times, social, experience, coin_balance, crystal_balance, tags, starbook_limit, slot_limit, assets_count, is_active, created_at, updated_at)
|
||||
VALUES (%d, %d, '%s', 1, 1, 0, 0, 0, 1000, '[]'::jsonb, 3, 3, 2, true, %d, %d)
|
||||
fmt.Printf(`INSERT INTO fan_profiles (user_id, star_id, nickname, level, times, social, coin_balance, crystal_balance, tags, starbook_limit, slot_limit, assets_count, is_active, created_at, updated_at)
|
||||
VALUES (%d, %d, '%s', 1, 1, 0, 0, 1000, '[]'::jsonb, 3, 3, 2, true, %d, %d)
|
||||
ON CONFLICT (user_id, star_id) DO UPDATE SET nickname = EXCLUDED.nickname, crystal_balance = EXCLUDED.crystal_balance;
|
||||
`, user2ID, star2ID, user2Nickname, now, now)
|
||||
fmt.Println()
|
||||
|
||||
@ -13,7 +13,7 @@ import (
|
||||
// UserServiceClient User Service RPC 客户端接口
|
||||
type UserServiceClient interface {
|
||||
// UpdateCrystalBalance 更新水晶余额
|
||||
UpdateCrystalBalance(ctx context.Context, userID, starID int64, delta int64) (int64, error)
|
||||
UpdateCrystalBalance(ctx context.Context, userID, starID int64, delta int64, changeType string, sourceID string, description string) (int64, error)
|
||||
|
||||
// UpdateAssetsCount 更新资产数量
|
||||
UpdateAssetsCount(ctx context.Context, userID, starID int64, delta int32) (int32, error)
|
||||
@ -35,7 +35,7 @@ func NewUserServiceClient(client pbUser.UserSocialService) UserServiceClient {
|
||||
}
|
||||
|
||||
// UpdateCrystalBalance 更新水晶余额
|
||||
func (c *userServiceClient) UpdateCrystalBalance(ctx context.Context, userID, starID int64, delta int64) (int64, error) {
|
||||
func (c *userServiceClient) UpdateCrystalBalance(ctx context.Context, userID, starID int64, delta int64, changeType string, sourceID string, description string) (int64, error) {
|
||||
logger.Logger.Debug("Calling UserService.UpdateCrystalBalance",
|
||||
zap.Int64("user_id", userID),
|
||||
zap.Int64("star_id", starID),
|
||||
@ -46,6 +46,9 @@ func (c *userServiceClient) UpdateCrystalBalance(ctx context.Context, userID, st
|
||||
UserId: userID,
|
||||
StarId: starID,
|
||||
Delta: delta,
|
||||
ChangeType: changeType,
|
||||
SourceId: sourceID,
|
||||
Description: description,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
||||
@ -110,6 +110,8 @@ func main() {
|
||||
mintOrderRepo := repository.NewMintOrderRepository(database.GetDB())
|
||||
assetLikeRepo := repository.NewAssetLikeRepository(database.GetDB())
|
||||
rankingRepo := repository.NewRankingRepository(database.GetDB())
|
||||
mintCostRepo := repository.NewMintCostRepository()
|
||||
userMintCountRepo := repository.NewUserMintCountRepository()
|
||||
logger.Logger.Info("Repository layer initialized")
|
||||
|
||||
// 创建 Dubbo 客户端
|
||||
@ -131,7 +133,7 @@ func main() {
|
||||
// 创建 Service 层实例
|
||||
registryRepo := starbookRepo.NewAssetRegistryRepository(database.GetDB())
|
||||
assetService := service.NewAssetService(assetRepo, mintOrderRepo, assetLikeRepo, userClient, database.GetDB(), registryRepo)
|
||||
mintService := service.NewMintService(assetRepo, mintOrderRepo, userClient, database.GetDB(), config.GlobalAssetConfig, registryRepo)
|
||||
mintService := service.NewMintService(assetRepo, mintOrderRepo, userClient, database.GetDB(), config.GlobalAssetConfig, registryRepo, mintCostRepo, userMintCountRepo)
|
||||
assetLikeService := service.NewAssetLikeService(assetRepo, assetLikeRepo, database.GetDB())
|
||||
rankingService := service.NewRankingService(rankingRepo, userClient)
|
||||
logger.Logger.Info("Service layer initialized")
|
||||
|
||||
@ -0,0 +1,45 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"github.com/topfans/backend/pkg/database"
|
||||
"github.com/topfans/backend/pkg/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// MintCostRepository 铸造消耗配置仓库
|
||||
type MintCostRepository interface {
|
||||
// GetByMintCount 根据铸爱次数获取配置
|
||||
GetByMintCount(mintCount int32) (*models.MintCostConfig, error)
|
||||
|
||||
// GetAll 获取所有配置
|
||||
GetAll() ([]*models.MintCostConfig, error)
|
||||
}
|
||||
|
||||
type mintCostRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewMintCostRepository 创建铸造消耗配置仓库
|
||||
func NewMintCostRepository() MintCostRepository {
|
||||
return &mintCostRepository{
|
||||
db: database.GetDB(),
|
||||
}
|
||||
}
|
||||
|
||||
// GetByMintCount 根据铸爱次数获取配置
|
||||
func (r *mintCostRepository) GetByMintCount(mintCount int32) (*models.MintCostConfig, error) {
|
||||
var config models.MintCostConfig
|
||||
if err := r.db.Where("mint_count = ?", mintCount).First(&config).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// GetAll 获取所有配置
|
||||
func (r *mintCostRepository) GetAll() ([]*models.MintCostConfig, error) {
|
||||
var configs []*models.MintCostConfig
|
||||
if err := r.db.Order("mint_count ASC").Find(&configs).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return configs, nil
|
||||
}
|
||||
@ -0,0 +1,115 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/topfans/backend/pkg/database"
|
||||
"github.com/topfans/backend/pkg/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// UserMintCountRepository 用户铸爱累计仓库
|
||||
type UserMintCountRepository interface {
|
||||
// GetOrCreate 获取或创建用户铸爱累计记录
|
||||
GetOrCreate(tx *gorm.DB, userID, starID int64) (*models.UserMintCount, bool, error)
|
||||
|
||||
// Get 获取用户铸爱累计记录
|
||||
Get(userID, starID int64) (*models.UserMintCount, error)
|
||||
|
||||
// IncrementMintCount 累加铸爱次数
|
||||
IncrementMintCount(tx *gorm.DB, userID, starID int64, delta int32) (*models.UserMintCount, error)
|
||||
|
||||
// UpdateRevenueBoost 更新永久收益提升
|
||||
UpdateRevenueBoost(tx *gorm.DB, userID, starID int64, addBps int32) (*models.UserMintCount, error)
|
||||
}
|
||||
|
||||
type userMintCountRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewUserMintCountRepository 创建用户铸爱累计仓库
|
||||
func NewUserMintCountRepository() UserMintCountRepository {
|
||||
return &userMintCountRepository{
|
||||
db: database.GetDB(),
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrCreate 获取或创建用户铸爱累计记录
|
||||
func (r *userMintCountRepository) GetOrCreate(tx *gorm.DB, userID, starID int64) (*models.UserMintCount, bool, error) {
|
||||
if tx == nil {
|
||||
tx = r.db
|
||||
}
|
||||
|
||||
var record models.UserMintCount
|
||||
err := tx.Where("user_id = ? AND star_id = ?", userID, starID).First(&record).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
// 创建新记录
|
||||
record = models.UserMintCount{
|
||||
UserID: userID,
|
||||
StarID: starID,
|
||||
MintCount: 0,
|
||||
RevenueBoostBps: 0,
|
||||
UpdatedAt: time.Now().UnixMilli(),
|
||||
}
|
||||
if err := tx.Create(&record).Error; err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return &record, true, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return &record, false, nil
|
||||
}
|
||||
|
||||
// Get 获取用户铸爱累计记录
|
||||
func (r *userMintCountRepository) Get(userID, starID int64) (*models.UserMintCount, error) {
|
||||
var record models.UserMintCount
|
||||
if err := r.db.Where("user_id = ? AND star_id = ?", userID, starID).First(&record).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &record, nil
|
||||
}
|
||||
|
||||
// IncrementMintCount 累加铸爱次数
|
||||
func (r *userMintCountRepository) IncrementMintCount(tx *gorm.DB, userID, starID int64, delta int32) (*models.UserMintCount, error) {
|
||||
if tx == nil {
|
||||
tx = r.db
|
||||
}
|
||||
|
||||
// 先查询当前记录
|
||||
var record models.UserMintCount
|
||||
if err := tx.Where("user_id = ? AND star_id = ?", userID, starID).First(&record).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 更新铸爱次数
|
||||
record.MintCount += delta
|
||||
record.UpdatedAt = time.Now().UnixMilli()
|
||||
if err := tx.Save(&record).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &record, nil
|
||||
}
|
||||
|
||||
// UpdateRevenueBoost 更新永久收益提升
|
||||
func (r *userMintCountRepository) UpdateRevenueBoost(tx *gorm.DB, userID, starID int64, addBps int32) (*models.UserMintCount, error) {
|
||||
if tx == nil {
|
||||
tx = r.db
|
||||
}
|
||||
|
||||
// 先查询当前记录
|
||||
var record models.UserMintCount
|
||||
if err := tx.Where("user_id = ? AND star_id = ?", userID, starID).First(&record).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 更新收益提升
|
||||
record.RevenueBoostBps += addBps
|
||||
record.UpdatedAt = time.Now().UnixMilli()
|
||||
if err := tx.Save(&record).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &record, nil
|
||||
}
|
||||
@ -31,6 +31,9 @@ type AssetRPCClient interface {
|
||||
// GetAssetInfo 获取资产信息(简化版,用于展示)
|
||||
GetAssetInfo(assetID, userID, starID int64) (*AssetInfo, error)
|
||||
|
||||
// GetAssetLikeCount 获取资产的点赞数
|
||||
GetAssetLikeCount(assetID int64) int
|
||||
|
||||
// ClearAssetLikeRecords 清除资产点赞记录(不修改 like_count),在藏品下架时调用
|
||||
ClearAssetLikeRecords(assetID int64) error
|
||||
}
|
||||
@ -220,3 +223,26 @@ func (c *assetRPCClient) ClearAssetLikeRecords(assetID int64) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAssetLikeCount 获取资产的点赞数(不验证用户)
|
||||
func (c *assetRPCClient) GetAssetLikeCount(assetID int64) int {
|
||||
ctx := context.Background()
|
||||
resp, err := c.client.GetAsset(ctx, &pbAsset.GetAssetRequest{
|
||||
AssetId: assetID,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logger.Logger.Error("Failed to call AssetService.GetAsset for like count",
|
||||
zap.Int64("asset_id", assetID),
|
||||
zap.Error(err))
|
||||
return 0
|
||||
}
|
||||
|
||||
if resp == nil || resp.Base == nil || resp.Base.Code != pbCommon.StatusCode_STATUS_OK {
|
||||
logger.Logger.Warn("AssetService.GetAsset returned error for like count",
|
||||
zap.Int64("asset_id", assetID))
|
||||
return 0
|
||||
}
|
||||
|
||||
return int(resp.Asset.LikeCount)
|
||||
}
|
||||
|
||||
100
backend/services/galleryService/client/task_rpc_client.go
Normal file
100
backend/services/galleryService/client/task_rpc_client.go
Normal file
@ -0,0 +1,100 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/topfans/backend/pkg/logger"
|
||||
pbCommon "github.com/topfans/backend/pkg/proto/common"
|
||||
pbTask "github.com/topfans/backend/pkg/proto/task"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// TaskRPCClient Task Service RPC客户端接口
|
||||
type TaskRPCClient interface {
|
||||
// OnExhibitionCompleted 当展位到期完成时调用,创建展示收益记录
|
||||
OnExhibitionCompleted(ctx context.Context, req *OnExhibitionCompletedRequest) (*OnExhibitionCompletedResponse, error)
|
||||
}
|
||||
|
||||
// OnExhibitionCompletedRequest 展位完成请求
|
||||
type OnExhibitionCompletedRequest struct {
|
||||
ExhibitionId int64
|
||||
AssetId int64
|
||||
SlotId int64
|
||||
OccupierUid int64
|
||||
OccupierStarId int64
|
||||
SlotOwnerUid int64
|
||||
StartTime int64
|
||||
ExpireAt int64
|
||||
CrystalAmount int64
|
||||
}
|
||||
|
||||
// OnExhibitionCompletedResponse 展位完成响应
|
||||
type OnExhibitionCompletedResponse struct {
|
||||
RevenueRecordId int64
|
||||
}
|
||||
|
||||
// taskRPCClient Task Service RPC客户端实现
|
||||
type taskRPCClient struct {
|
||||
client pbTask.TaskInternalService
|
||||
}
|
||||
|
||||
// NewTaskRPCClient 创建Task Service RPC客户端
|
||||
func NewTaskRPCClient(client pbTask.TaskInternalService) TaskRPCClient {
|
||||
return &taskRPCClient{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
// OnExhibitionCompleted 当展位到期完成时调用,创建展示收益记录
|
||||
func (c *taskRPCClient) OnExhibitionCompleted(ctx context.Context, req *OnExhibitionCompletedRequest) (*OnExhibitionCompletedResponse, error) {
|
||||
logger.Logger.Debug("Calling TaskService.OnExhibitionCompleted",
|
||||
zap.Int64("exhibition_id", req.ExhibitionId),
|
||||
zap.Int64("asset_id", req.AssetId),
|
||||
zap.Int64("slot_id", req.SlotId),
|
||||
zap.Int64("occupier_uid", req.OccupierUid),
|
||||
zap.Int64("slot_owner_uid", req.SlotOwnerUid),
|
||||
zap.Int64("crystal_amount", req.CrystalAmount))
|
||||
|
||||
if c.client == nil {
|
||||
logger.Logger.Error("TaskRPCClient: client is nil")
|
||||
return nil, errors.New("task client is nil")
|
||||
}
|
||||
|
||||
pbReq := &pbTask.OnExhibitionCompletedRequest{
|
||||
ExhibitionId: req.ExhibitionId,
|
||||
AssetId: req.AssetId,
|
||||
SlotId: req.SlotId,
|
||||
OccupierUid: req.OccupierUid,
|
||||
OccupierStarId: req.OccupierStarId,
|
||||
SlotOwnerUid: req.SlotOwnerUid,
|
||||
StartTime: req.StartTime,
|
||||
ExpireAt: req.ExpireAt,
|
||||
CrystalAmount: req.CrystalAmount,
|
||||
}
|
||||
|
||||
resp, err := c.client.OnExhibitionCompleted(ctx, pbReq)
|
||||
if err != nil {
|
||||
logger.Logger.Error("Failed to call TaskService.OnExhibitionCompleted",
|
||||
zap.Int64("exhibition_id", req.ExhibitionId),
|
||||
zap.Error(err))
|
||||
return nil, fmt.Errorf("call TaskService.OnExhibitionCompleted failed: %w", err)
|
||||
}
|
||||
|
||||
if resp.Base.Code != pbCommon.StatusCode_STATUS_OK {
|
||||
logger.Logger.Warn("TaskService.OnExhibitionCompleted returned error",
|
||||
zap.Int64("exhibition_id", req.ExhibitionId),
|
||||
zap.Int32("code", int32(resp.Base.Code)),
|
||||
zap.String("message", resp.Base.Message))
|
||||
return nil, fmt.Errorf("TaskService.OnExhibitionCompleted error: %s", resp.Base.Message)
|
||||
}
|
||||
|
||||
logger.Logger.Info("TaskService.OnExhibitionCompleted successful",
|
||||
zap.Int64("exhibition_id", req.ExhibitionId),
|
||||
zap.Int64("revenue_record_id", resp.RevenueRecordId))
|
||||
|
||||
return &OnExhibitionCompletedResponse{
|
||||
RevenueRecordId: resp.RevenueRecordId,
|
||||
}, nil
|
||||
}
|
||||
@ -28,6 +28,10 @@ type UserRPCClient interface {
|
||||
|
||||
// UpdateCrystalBalance 更新水晶余额(返回更新后的余额)
|
||||
UpdateCrystalBalance(userID, starID int64, delta int64) (int64, error)
|
||||
|
||||
// AddExhibitionHours 增加用户累计上架时长
|
||||
// 返回: newLevel, levelDelta, crystalReward, error
|
||||
AddExhibitionHours(userID, starID int64, hours int64) (int32, int32, int64, error)
|
||||
}
|
||||
|
||||
// userRPCClient User Service RPC客户端实现
|
||||
@ -144,3 +148,54 @@ func (c *userRPCClient) UpdateCrystalBalance(userID, starID int64, delta int64)
|
||||
|
||||
return resp.NewBalance, nil
|
||||
}
|
||||
|
||||
// AddExhibitionHours 增加用户累计上架时长
|
||||
func (c *userRPCClient) AddExhibitionHours(userID, starID int64, hours int64) (int32, int32, int64, error) {
|
||||
logger.Logger.Debug("Calling UserService.AddExhibitionHours",
|
||||
zap.Int64("user_id", userID),
|
||||
zap.Int64("star_id", starID),
|
||||
zap.Int64("hours", hours),
|
||||
)
|
||||
|
||||
ctx := context.Background()
|
||||
resp, err := c.client.AddExhibitionHours(ctx, &pbUser.AddExhibitionHoursRequest{
|
||||
UserId: userID,
|
||||
StarId: starID,
|
||||
ExhibitionHours: hours,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logger.Logger.Error("Failed to call UserService.AddExhibitionHours",
|
||||
zap.Int64("user_id", userID),
|
||||
zap.Int64("star_id", starID),
|
||||
zap.Int64("hours", hours),
|
||||
zap.Error(err),
|
||||
)
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
|
||||
if resp.Base.Code != pbCommon.StatusCode_STATUS_OK {
|
||||
errorMsg := resp.Base.Message
|
||||
if errorMsg == "" {
|
||||
errorMsg = fmt.Sprintf("UserService返回错误码: %d", resp.Base.Code)
|
||||
}
|
||||
logger.Logger.Warn("UserService.AddExhibitionHours returned error",
|
||||
zap.Int64("user_id", userID),
|
||||
zap.Int64("star_id", starID),
|
||||
zap.Int32("code", int32(resp.Base.Code)),
|
||||
zap.String("message", errorMsg),
|
||||
)
|
||||
return 0, 0, 0, errors.New(errorMsg)
|
||||
}
|
||||
|
||||
logger.Logger.Debug("UserService.AddExhibitionHours successful",
|
||||
zap.Int64("user_id", userID),
|
||||
zap.Int64("star_id", starID),
|
||||
zap.Int64("hours", hours),
|
||||
zap.Int32("new_level", resp.NewLevel),
|
||||
zap.Int32("level_delta", resp.LevelDelta),
|
||||
zap.Int64("crystal_reward", resp.CrystalReward),
|
||||
)
|
||||
|
||||
return resp.NewLevel, resp.LevelDelta, resp.CrystalReward, nil
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ import (
|
||||
"github.com/topfans/backend/pkg/models"
|
||||
pbAsset "github.com/topfans/backend/pkg/proto/asset"
|
||||
pbGallery "github.com/topfans/backend/pkg/proto/gallery"
|
||||
pbTask "github.com/topfans/backend/pkg/proto/task"
|
||||
pbUser "github.com/topfans/backend/pkg/proto/user"
|
||||
rpcclient "github.com/topfans/backend/services/galleryService/client"
|
||||
"github.com/topfans/backend/services/galleryService/provider"
|
||||
@ -35,6 +36,7 @@ var (
|
||||
dbName = flag.String("db-name", getEnv("DB_NAME", "top-fans"), "Database name")
|
||||
assetServiceURL = flag.String("asset-service-url", getEnv("ASSET_SERVICE_URL", "tri://localhost:20003"), "Asset service URL")
|
||||
userServiceURL = flag.String("user-service-url", getEnv("USER_SERVICE_URL", "tri://localhost:20000"), "User service URL")
|
||||
taskServiceURL = flag.String("task-service-url", getEnv("TASK_SERVICE_URL", "tri://localhost:20002"), "Task service URL")
|
||||
healthHandler *health.Handler
|
||||
)
|
||||
|
||||
@ -136,14 +138,30 @@ func main() {
|
||||
userRPCClient := rpcclient.NewUserRPCClient(userServiceClient)
|
||||
logger.Logger.Info("User Service RPC client initialized")
|
||||
|
||||
// 创建 Task Service Dubbo 客户端
|
||||
taskCli, err := dubboclient.NewClient(
|
||||
dubboclient.WithClientURL(*taskServiceURL),
|
||||
)
|
||||
if err != nil {
|
||||
logger.Logger.Fatal(fmt.Sprintf("Failed to create Task Service Dubbo client: %v", err))
|
||||
}
|
||||
|
||||
// 获取 Task Service RPC 客户端
|
||||
taskServiceClient, err := pbTask.NewTaskInternalService(taskCli)
|
||||
if err != nil {
|
||||
logger.Logger.Fatal(fmt.Sprintf("Failed to create Task Service RPC client: %v", err))
|
||||
}
|
||||
taskRPCClient := rpcclient.NewTaskRPCClient(taskServiceClient)
|
||||
logger.Logger.Info("Task Service RPC client initialized")
|
||||
|
||||
// 创建 Service 层实例
|
||||
galleryService := service.NewGalleryService(galleryRepo, assetRPCClient, userRPCClient)
|
||||
slotService := service.NewSlotService(galleryRepo, userRPCClient)
|
||||
exhibitionService := service.NewExhibitionService(galleryRepo, assetRPCClient)
|
||||
logger.Logger.Info("Service layer initialized")
|
||||
|
||||
// 创建并启动清理 Worker(注入 assetRPCClient 以便下架时清除点赞记录)
|
||||
cleanupWorker := service.NewCleanupWorker(galleryRepo, assetRPCClient)
|
||||
// 创建并启动清理 Worker(注入 assetRPCClient, userClient 和 taskClient)
|
||||
cleanupWorker := service.NewCleanupWorker(galleryRepo, assetRPCClient, userRPCClient, taskRPCClient)
|
||||
go cleanupWorker.Start()
|
||||
logger.Logger.Info("Cleanup worker started")
|
||||
|
||||
|
||||
@ -70,6 +70,9 @@ type GalleryRepository interface {
|
||||
// limit: 返回数量
|
||||
// offset: 偏移量(随机生成)
|
||||
GetRandomExhibitions(starID int64, materialType string, excludeIDs []int64, limit, offset int) ([]*InspirationFlowItem, error)
|
||||
|
||||
// GetSlotOwnerUserID 获取展位所有者的用户ID
|
||||
GetSlotOwnerUserID(slotID int64) (int64, error)
|
||||
}
|
||||
|
||||
// InspirationFlowItem 灵感瀑布展品项
|
||||
@ -555,6 +558,15 @@ func calcSpanByLikes(likes int32) int32 {
|
||||
return 4
|
||||
}
|
||||
|
||||
// GetSlotOwnerUserID 获取展位所有者的用户ID
|
||||
func (r *galleryRepository) GetSlotOwnerUserID(slotID int64) (int64, error) {
|
||||
var slot models.BoothSlot
|
||||
if err := r.db.Where("slot_id = ?", slotID).First(&slot).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return slot.UserID, nil
|
||||
}
|
||||
|
||||
// ==================== 辅助函数 ====================
|
||||
|
||||
// generateHostProfileID 生成 host_profile_id
|
||||
|
||||
@ -10,8 +10,7 @@ import (
|
||||
)
|
||||
|
||||
type UserServiceClient interface {
|
||||
UpdateCrystalBalance(ctx context.Context, userID, starID int64, delta int64) (int64, error)
|
||||
AddExperience(ctx context.Context, userID, starID int64, delta int64) (int64, error)
|
||||
UpdateCrystalBalance(ctx context.Context, userID, starID int64, delta int64, changeType string, sourceID string, description string) (int64, error)
|
||||
GetFanProfile(ctx context.Context, userID, starID int64) (*pbUser.FanProfile, error)
|
||||
}
|
||||
|
||||
@ -23,11 +22,11 @@ func NewUserServiceClient(client pbUser.UserSocialService) UserServiceClient {
|
||||
return &userServiceClient{client: client}
|
||||
}
|
||||
|
||||
func (c *userServiceClient) UpdateCrystalBalance(ctx context.Context, userID, starID int64, delta int64) (int64, error) {
|
||||
func (c *userServiceClient) UpdateCrystalBalance(ctx context.Context, userID, starID int64, delta int64, changeType string, sourceID string, description string) (int64, error) {
|
||||
logger.Logger.Debug("Calling UserService.UpdateCrystalBalance",
|
||||
zap.Int64("user_id", userID), zap.Int64("star_id", starID), zap.Int64("delta", delta))
|
||||
resp, err := c.client.UpdateCrystalBalance(ctx, &pbUser.UpdateCrystalBalanceRequest{
|
||||
UserId: userID, StarId: starID, Delta: delta,
|
||||
UserId: userID, StarId: starID, Delta: delta, ChangeType: changeType, SourceId: sourceID, Description: description,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Logger.Error("UserService.UpdateCrystalBalance failed", zap.Error(err))
|
||||
@ -40,23 +39,6 @@ func (c *userServiceClient) UpdateCrystalBalance(ctx context.Context, userID, st
|
||||
return resp.NewBalance, nil
|
||||
}
|
||||
|
||||
func (c *userServiceClient) AddExperience(ctx context.Context, userID, starID int64, delta int64) (int64, error) {
|
||||
logger.Logger.Debug("Calling UserService.AddExperience",
|
||||
zap.Int64("user_id", userID), zap.Int64("star_id", starID), zap.Int64("delta", delta))
|
||||
resp, err := c.client.AddExperience(ctx, &pbUser.AddExperienceRequest{
|
||||
UserId: userID, StarId: starID, Delta: delta,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Logger.Error("UserService.AddExperience failed", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
if resp.Base.Code != pbCommon.StatusCode_STATUS_OK {
|
||||
logger.Logger.Warn("AddExperience non-zero code", zap.Int32("code", int32(resp.Base.Code)))
|
||||
return 0, fmt.Errorf("AddExperience failed with code: %d", resp.Base.Code)
|
||||
}
|
||||
return resp.NewExperience, nil
|
||||
}
|
||||
|
||||
func (c *userServiceClient) GetFanProfile(ctx context.Context, userID, starID int64) (*pbUser.FanProfile, error) {
|
||||
logger.Logger.Debug("Calling UserService.GetFanProfile",
|
||||
zap.Int64("user_id", userID), zap.Int64("star_id", starID))
|
||||
|
||||
@ -9,7 +9,6 @@ type TaskDefinition struct {
|
||||
Name string `gorm:"column:name;size:100;not null"`
|
||||
Description string `gorm:"column:description;type:text"`
|
||||
CrystalReward int64 `gorm:"column:crystal_reward;default:0"`
|
||||
ExpReward int64 `gorm:"column:exp_reward;default:0"`
|
||||
SortOrder int `gorm:"column:sort_order;default:0"`
|
||||
IsActive bool `gorm:"column:is_active;default:true"`
|
||||
CreatedAt int64 `gorm:"column:created_at"`
|
||||
@ -77,7 +76,6 @@ type OnboardingStageConfig struct {
|
||||
Description string `gorm:"column:description;type:text"`
|
||||
RequiredTaskKeys []string `gorm:"column:required_task_keys;type:text;serializer:json"` // 存储为 JSON 字符串
|
||||
CrystalReward int64 `gorm:"column:crystal_reward;default:0"`
|
||||
ExpReward int64 `gorm:"column:exp_reward;default:0"`
|
||||
SortOrder int `gorm:"column:sort_order;default:0"`
|
||||
IsActive bool `gorm:"column:is_active;default:true"`
|
||||
CreatedAt int64 `gorm:"column:created_at"`
|
||||
|
||||
@ -183,15 +183,14 @@ func (r *onboardingRepository) SaveStageConfigs(configs []*model.OnboardingStage
|
||||
zap.Int("stage", cfg.Stage),
|
||||
zap.String("name", cfg.Name),
|
||||
zap.Strings("required_task_keys", cfg.RequiredTaskKeys),
|
||||
zap.Int64("crystal_reward", cfg.CrystalReward),
|
||||
zap.Int64("exp_reward", cfg.ExpReward))
|
||||
zap.Int64("crystal_reward", cfg.CrystalReward))
|
||||
|
||||
cfg.UpdatedAt = now
|
||||
// Use upsert via ON CONFLICT to properly handle JSON serialization
|
||||
upsert := clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "stage"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{
|
||||
"name", "required_task_keys", "crystal_reward", "exp_reward", "is_active", "updated_at",
|
||||
"name", "required_task_keys", "crystal_reward", "is_active", "updated_at",
|
||||
}),
|
||||
}
|
||||
if err := r.db.Clauses(upsert).Create(cfg).Error; err != nil {
|
||||
|
||||
@ -120,7 +120,8 @@ func (w *DailyResetWorker) autoClaimExhibitionRevenue() {
|
||||
for _, record := range records {
|
||||
var lastErr error
|
||||
for attempt := 0; attempt < maxRetries; attempt++ {
|
||||
_, err := w.userClient.UpdateCrystalBalance(context.Background(), record.UserID, record.StarID, record.CrystalAmount)
|
||||
_, err := w.userClient.UpdateCrystalBalance(context.Background(), record.UserID, record.StarID, record.CrystalAmount,
|
||||
"exhibition_revenue", fmt.Sprintf("%d", record.ID), fmt.Sprintf("展示收益 #%d", record.ID))
|
||||
if err == nil {
|
||||
if err := w.revenueRepo.UpdateRevenueStatus(record.ID, "claimed"); err != nil {
|
||||
logger.Logger.Error("DailyResetWorker: failed to update status to claimed",
|
||||
|
||||
@ -145,7 +145,7 @@ func (p *UnifiedProvider) UpdateAssetsCount(ctx context.Context, req *pb.UpdateA
|
||||
return p.userProvider.UpdateAssetsCount(ctx, req)
|
||||
}
|
||||
|
||||
// AddExperience 增加经验值(内部RPC调用)
|
||||
func (p *UnifiedProvider) AddExperience(ctx context.Context, req *pb.AddExperienceRequest) (*pb.AddExperienceResponse, error) {
|
||||
return p.userProvider.AddExperience(ctx, req)
|
||||
// AddExhibitionHours 增加用户累计上架时长(内部RPC调用)
|
||||
func (p *UnifiedProvider) AddExhibitionHours(ctx context.Context, req *pb.AddExhibitionHoursRequest) (*pb.AddExhibitionHoursResponse, error) {
|
||||
return p.userProvider.AddExhibitionHours(ctx, req)
|
||||
}
|
||||
|
||||
@ -852,27 +852,27 @@ func (p *UserProvider) UpdateAssetsCount(ctx context.Context, req *pb.UpdateAsse
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// AddExperience 增加经验值(内部RPC调用)
|
||||
func (p *UserProvider) AddExperience(ctx context.Context, req *pb.AddExperienceRequest) (*pb.AddExperienceResponse, error) {
|
||||
logger.Logger.Info("Received AddExperience request",
|
||||
// AddExhibitionHours 增加用户累计上架时长(内部RPC调用)
|
||||
func (p *UserProvider) AddExhibitionHours(ctx context.Context, req *pb.AddExhibitionHoursRequest) (*pb.AddExhibitionHoursResponse, error) {
|
||||
logger.Logger.Info("Received AddExhibitionHours request",
|
||||
zap.Int64("user_id", req.UserId),
|
||||
zap.Int64("star_id", req.StarId),
|
||||
zap.Int64("delta", req.Delta),
|
||||
zap.Int64("exhibition_hours", req.ExhibitionHours),
|
||||
)
|
||||
|
||||
// 调用Service层
|
||||
resp, err := p.userService.AddExperience(req)
|
||||
resp, err := p.userService.AddExhibitionHours(req)
|
||||
if err != nil {
|
||||
logger.Logger.Error("AddExperience failed",
|
||||
logger.Logger.Error("AddExhibitionHours failed",
|
||||
zap.Int64("user_id", req.UserId),
|
||||
zap.Int64("star_id", req.StarId),
|
||||
zap.Int64("delta", req.Delta),
|
||||
zap.Int64("exhibition_hours", req.ExhibitionHours),
|
||||
zap.Error(err),
|
||||
)
|
||||
|
||||
// 如果响应为空,构建错误响应
|
||||
if resp == nil {
|
||||
resp = &pb.AddExperienceResponse{
|
||||
resp = &pb.AddExhibitionHoursResponse{
|
||||
Base: &pbCommon.BaseResponse{
|
||||
Code: appErrors.ToStatusCode(err),
|
||||
Message: err.Error(),
|
||||
@ -884,11 +884,13 @@ func (p *UserProvider) AddExperience(ctx context.Context, req *pb.AddExperienceR
|
||||
return resp, err
|
||||
}
|
||||
|
||||
logger.Logger.Info("AddExperience successful",
|
||||
logger.Logger.Info("AddExhibitionHours successful",
|
||||
zap.Int64("user_id", req.UserId),
|
||||
zap.Int64("star_id", req.StarId),
|
||||
zap.Int64("delta", req.Delta),
|
||||
zap.Int64("new_experience", resp.NewExperience),
|
||||
zap.Int64("exhibition_hours", req.ExhibitionHours),
|
||||
zap.Int32("new_level", resp.NewLevel),
|
||||
zap.Int32("level_delta", resp.LevelDelta),
|
||||
zap.Int64("crystal_reward", resp.CrystalReward),
|
||||
)
|
||||
|
||||
return resp, nil
|
||||
|
||||
@ -2,13 +2,18 @@ package repository
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/topfans/backend/pkg/database"
|
||||
appErrors "github.com/topfans/backend/pkg/errors"
|
||||
"github.com/topfans/backend/pkg/logger"
|
||||
"github.com/topfans/backend/pkg/models"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// contains 检查字符串是否包含子串(不区分大小写)
|
||||
@ -79,8 +84,15 @@ type FanProfileRepository interface {
|
||||
// UpdateSocial 更新好友数量(social字段)
|
||||
UpdateSocial(userID, starID int64, delta int32) (int32, error)
|
||||
|
||||
// UpdateCrystalBalance 更新水晶余额
|
||||
UpdateCrystalBalance(userID, starID int64, delta int64) (int64, error)
|
||||
// UpdateCrystalBalance 更新水晶余额(支持流水记录)
|
||||
// changeType: 变化类型,如 task_reward/mint_cost/mint_reward/exhibition_revenue/level_up_bonus/manual_adjust
|
||||
// sourceID: 关联业务ID
|
||||
// description: 可读描述
|
||||
UpdateCrystalBalance(userID, starID int64, delta int64, changeType string, sourceID string, description string) (int64, error)
|
||||
|
||||
// AddExhibitionHours 增加用户累计上架时长并同步等级(事务性)
|
||||
// 返回: newLevel, levelDelta, crystalReward, error
|
||||
AddExhibitionHours(userID, starID int64, hours int64) (int32, int32, int64, error)
|
||||
|
||||
// UpdateExperience 更新经验值
|
||||
UpdateExperience(userID, starID int64, delta int64) (int64, error)
|
||||
@ -369,10 +381,13 @@ func (r *fanProfileRepository) UpdateSocial(userID, starID int64, delta int32) (
|
||||
return newSocial, nil
|
||||
}
|
||||
|
||||
// UpdateCrystalBalance 更新水晶余额
|
||||
// UpdateCrystalBalance 更新水晶余额(支持流水记录)
|
||||
// delta: 变化量,正数表示增加,负数表示减少
|
||||
// changeType: 变化类型,如 task_reward/mint_cost/mint_reward/exhibition_revenue/level_up_bonus/manual_adjust
|
||||
// sourceID: 关联业务ID
|
||||
// description: 可读描述
|
||||
// 返回: 更新后的水晶余额
|
||||
func (r *fanProfileRepository) UpdateCrystalBalance(userID, starID int64, delta int64) (int64, error) {
|
||||
func (r *fanProfileRepository) UpdateCrystalBalance(userID, starID int64, delta int64, changeType string, sourceID string, description string) (int64, error) {
|
||||
if userID <= 0 {
|
||||
return 0, errors.New("user_id must be greater than 0")
|
||||
}
|
||||
@ -384,9 +399,10 @@ func (r *fanProfileRepository) UpdateCrystalBalance(userID, starID int64, delta
|
||||
// 使用事务确保原子性
|
||||
var newBalance int64
|
||||
err := r.db.Transaction(func(tx *gorm.DB) error {
|
||||
// 先查询当前的 crystal_balance 值
|
||||
// 1. SELECT FOR UPDATE 加行锁(悲观锁策略)
|
||||
var profile models.FanProfile
|
||||
if err := tx.Where("user_id = ? AND star_id = ?", userID, starID).
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||
Where("user_id = ? AND star_id = ?", userID, starID).
|
||||
First(&profile).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return appErrors.ErrFanProfileNotFound
|
||||
@ -394,15 +410,32 @@ func (r *fanProfileRepository) UpdateCrystalBalance(userID, starID int64, delta
|
||||
return err
|
||||
}
|
||||
|
||||
// 计算新值
|
||||
newBalance = profile.CrystalBalance + delta
|
||||
// 2. 计算余额变化
|
||||
balanceBefore := profile.CrystalBalance
|
||||
newBalance = balanceBefore + delta
|
||||
|
||||
// 确保不会小于 0
|
||||
if newBalance < 0 {
|
||||
newBalance = 0
|
||||
}
|
||||
|
||||
// 更新 crystal_balance 字段
|
||||
// 3. 写入水晶流水(复式记账,包含余额快照)
|
||||
crystalRecord := &models.CrystalTransactionRecord{
|
||||
UserID: userID,
|
||||
StarID: starID,
|
||||
ChangeType: changeType,
|
||||
Delta: delta,
|
||||
BalanceBefore: balanceBefore,
|
||||
BalanceAfter: newBalance,
|
||||
SourceID: sourceID,
|
||||
Description: description,
|
||||
CreatedAt: time.Now().UnixMilli(),
|
||||
}
|
||||
if err := tx.Create(crystalRecord).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 4. 更新 crystal_balance 字段
|
||||
if err := tx.Model(&models.FanProfile{}).
|
||||
Where("user_id = ? AND star_id = ?", userID, starID).
|
||||
Update("crystal_balance", newBalance).Error; err != nil {
|
||||
@ -416,9 +449,222 @@ func (r *fanProfileRepository) UpdateCrystalBalance(userID, starID int64, delta
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return newBalance, nil
|
||||
return newBalance, nil
|
||||
}
|
||||
|
||||
// GetOrCreateExhibitionHours 获取或创建用户累计上架时长记录
|
||||
func (r *fanProfileRepository) GetOrCreateExhibitionHours(tx *gorm.DB, userID, starID int64) (*models.UserExhibitionHours, error) {
|
||||
var existing models.UserExhibitionHours
|
||||
err := tx.Where("user_id = ? AND star_id = ?", userID, starID).First(&existing).Error
|
||||
|
||||
if err == nil {
|
||||
return &existing, nil
|
||||
}
|
||||
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 创建新记录
|
||||
now := time.Now().UnixMilli()
|
||||
newRecord := &models.UserExhibitionHours{
|
||||
UserID: userID,
|
||||
StarID: starID,
|
||||
TotalExhibitionHours: 0,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
if err := tx.Create(newRecord).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newRecord, nil
|
||||
}
|
||||
|
||||
// CalculateLevelFromExhibitionHours 根据累计上架时长计算等级
|
||||
func CalculateLevelFromExhibitionHours(totalHours int64) int32 {
|
||||
db := database.GetDB()
|
||||
if db == nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
var threshold models.LevelThreshold
|
||||
err := db.Where("max_exhibition_hours <= ?", totalHours).
|
||||
Order("level DESC").
|
||||
First(&threshold).Error
|
||||
|
||||
if err != nil || threshold.Level == 0 {
|
||||
return 1
|
||||
}
|
||||
|
||||
return threshold.Level
|
||||
}
|
||||
|
||||
// GetLevelCap 获取当前等级上限
|
||||
func GetLevelCap() int32 {
|
||||
db := database.GetDB()
|
||||
if db == nil {
|
||||
return 20
|
||||
}
|
||||
|
||||
var config models.LevelCapConfig
|
||||
err := db.First(&config).Error
|
||||
if err != nil {
|
||||
return 20 // 默认20级
|
||||
}
|
||||
|
||||
return config.MaxLevel
|
||||
}
|
||||
|
||||
// AddExhibitionHours 增加用户累计上架时长并同步等级(事务性)
|
||||
// 返回: newLevel, levelDelta, crystalReward, error
|
||||
func (r *fanProfileRepository) AddExhibitionHours(userID, starID int64, hours int64) (int32, int32, int64, error) {
|
||||
var result struct {
|
||||
OldLevel int32
|
||||
NewLevel int32
|
||||
CrystalReward int64
|
||||
}
|
||||
|
||||
err := r.db.Transaction(func(tx *gorm.DB) error {
|
||||
// 1. 获取或创建累计时长记录
|
||||
exhibitionHours, err := r.GetOrCreateExhibitionHours(tx, userID, starID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. 原子性累加时长(避免竞态条件)
|
||||
now := time.Now().UnixMilli()
|
||||
if err := tx.Model(&models.UserExhibitionHours{}).
|
||||
Where("user_id = ? AND star_id = ?", userID, starID).
|
||||
Updates(map[string]interface{}{
|
||||
"total_exhibition_hours": gorm.Expr("total_exhibition_hours + ?", hours),
|
||||
"updated_at": now,
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 重新查询更新后的时长
|
||||
if err := tx.Where("user_id = ? AND star_id = ?", userID, starID).First(exhibitionHours).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. 获取当前等级上限
|
||||
maxLevel := GetLevelCap()
|
||||
|
||||
// 4. 计算新等级(基于累计时长)
|
||||
newLevel := CalculateLevelFromExhibitionHours(exhibitionHours.TotalExhibitionHours)
|
||||
if newLevel > maxLevel {
|
||||
newLevel = maxLevel
|
||||
}
|
||||
|
||||
// 5. SELECT FOR UPDATE 加行锁获取粉丝档案当前等级
|
||||
var profile models.FanProfile
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||
Where("user_id = ? AND star_id = ?", userID, starID).First(&profile).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result.OldLevel = profile.Level
|
||||
result.NewLevel = newLevel
|
||||
|
||||
// 6. 如有升级,发放奖励
|
||||
if newLevel > profile.Level {
|
||||
// 查询升级奖励
|
||||
rewards, err := r.getLevelUpRewards(tx, newLevel)
|
||||
if err != nil {
|
||||
logger.Logger.Warn("Failed to get level up rewards",
|
||||
zap.Int64("user_id", userID),
|
||||
zap.Int64("star_id", starID),
|
||||
zap.Int32("level", newLevel),
|
||||
zap.Error(err))
|
||||
}
|
||||
|
||||
// 计算水晶奖励总额
|
||||
var crystalReward int64 = 0
|
||||
var likeBetCountReward int32 = 0
|
||||
for _, reward := range rewards {
|
||||
if reward.RewardType == "crystal" && reward.IsEnabled {
|
||||
crystalReward += reward.RewardValue
|
||||
}
|
||||
if reward.RewardType == "like_bet_count" && reward.IsEnabled {
|
||||
likeBetCountReward += int32(reward.RewardValue)
|
||||
}
|
||||
}
|
||||
|
||||
result.CrystalReward = crystalReward
|
||||
|
||||
// 发放升级奖励(水晶 + 点赞押注次数)
|
||||
if crystalReward > 0 || likeBetCountReward > 0 {
|
||||
balanceBefore := profile.CrystalBalance
|
||||
balanceAfter := balanceBefore + crystalReward
|
||||
|
||||
// 写入水晶流水(只有水晶有流水)
|
||||
if crystalReward > 0 {
|
||||
crystalRecord := &models.CrystalTransactionRecord{
|
||||
UserID: userID,
|
||||
StarID: starID,
|
||||
ChangeType: "level_up_bonus",
|
||||
Delta: crystalReward,
|
||||
BalanceBefore: balanceBefore,
|
||||
BalanceAfter: balanceAfter,
|
||||
SourceID: "",
|
||||
Description: fmt.Sprintf("升级到%d级奖励", newLevel),
|
||||
CreatedAt: time.Now().UnixMilli(),
|
||||
}
|
||||
if err := tx.Create(crystalRecord).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 更新 FanProfile(等级 + 水晶余额 + 点赞押注次数)
|
||||
updates := map[string]interface{}{
|
||||
"level": newLevel,
|
||||
}
|
||||
if crystalReward > 0 {
|
||||
updates["crystal_balance"] = balanceAfter
|
||||
}
|
||||
if likeBetCountReward > 0 {
|
||||
updates["like_bet_count"] = gorm.Expr("like_bet_count + ?", likeBetCountReward)
|
||||
}
|
||||
if err := tx.Model(&profile).Updates(updates).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// 只更新等级
|
||||
if err := tx.Model(&profile).Update("level", newLevel).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
logger.Logger.Info("Level up from exhibition hours",
|
||||
zap.Int64("user_id", userID),
|
||||
zap.Int64("star_id", starID),
|
||||
zap.Int32("old_level", profile.Level),
|
||||
zap.Int32("new_level", newLevel),
|
||||
zap.Int64("crystal_reward", crystalReward),
|
||||
zap.Int32("like_bet_count_reward", likeBetCountReward),
|
||||
zap.Int64("total_hours", exhibitionHours.TotalExhibitionHours))
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
|
||||
levelDelta := result.NewLevel - result.OldLevel
|
||||
return result.NewLevel, levelDelta, result.CrystalReward, nil
|
||||
}
|
||||
|
||||
// getLevelUpRewards 获取指定等级的升级奖励
|
||||
func (r *fanProfileRepository) getLevelUpRewards(tx *gorm.DB, level int32) ([]*models.LevelUpRewardConfig, error) {
|
||||
var rewards []*models.LevelUpRewardConfig
|
||||
err := tx.Where("level = ? AND is_enabled = ?", level, true).Find(&rewards).Error
|
||||
return rewards, err
|
||||
}
|
||||
|
||||
// UpdateExperience 更新经验值(同时自动更新等级)
|
||||
// UpdateExperience 更新经验值(同时自动更新等级)
|
||||
// delta: 变化量,正数表示增加,负数表示减少
|
||||
// 返回: 更新后的经验值
|
||||
|
||||
@ -987,13 +987,12 @@ ON CONFLICT (level) DO NOTHING;
|
||||
-- 升级奖励配置表
|
||||
CREATE TABLE IF NOT EXISTS level_up_reward_config (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
level INT NOT NULL UNIQUE,
|
||||
level INT NOT NULL,
|
||||
reward_type VARCHAR(50) NOT NULL,
|
||||
reward_value BIGINT NOT NULL DEFAULT 0,
|
||||
is_enabled BOOLEAN DEFAULT true,
|
||||
level INT NOT NULL, -- 等级 2-20+
|
||||
reward_type VARCHAR(50) NOT NULL, -- 奖励类型:crystal(水晶) / like_bet_count(点赞押注数) / exhibition_count(上架藏品数) / badge(勋章) / 其他
|
||||
reward_value BIGINT NOT NULL DEFAULT 0, -- 奖励值
|
||||
is_enabled BOOLEAN DEFAULT true, -- 功能开关
|
||||
updated_at BIGINT NOT NULL,
|
||||
UNIQUE(level, reward_type)
|
||||
UNIQUE(level, reward_type) -- 同一等级同类型奖励唯一
|
||||
);
|
||||
|
||||
-- 插入初始数据示例(可由运营后台动态调整)
|
||||
@ -1080,7 +1079,9 @@ CREATE TABLE IF NOT EXISTS user_dazi_level (
|
||||
|
||||
### 13.1 铸爱次数与消耗水晶
|
||||
|
||||
用户铸造藏品时,每次铸造消耗水晶数根据累计铸爱次数动态计算。**达到第10次(消耗1024水晶)后重置铸爱次数,重新从第1次开始。**
|
||||
用户铸造藏品时,每次铸造消耗水晶数根据累计铸爱次数动态计算。**达到第10次(消耗1024水晶)后重置铸爱次数为0,重新从第1次开始累积。**
|
||||
|
||||
> **重要:** 铸爱次数重置仅清除计数,**永久收益提升(revenue_boost_bps)不清除**,会持续累加。例如用户第9次触发小保底(+500 bps = +5%),第10次触发大保底(+500 bps = +5%),则 `revenue_boost_bps = 1000`(即 +10% 永久收益提升),铸爱次数重置为0,但收益提升保留。
|
||||
|
||||
| 铸爱次数 | 消耗水晶数 | 备注 |
|
||||
|---------|-----------|------|
|
||||
@ -1092,10 +1093,10 @@ CREATE TABLE IF NOT EXISTS user_dazi_level (
|
||||
| 6 | 64 | |
|
||||
| 7 | 128 | |
|
||||
| 8 | 256 | 接近1天收益 |
|
||||
| 9 | 512 | 20%概率,获得5%永久收益提升(俗称小保底) |
|
||||
| 10 | 1024 | 接近3天收益,建议为最高值;100%概率,获得5%永久收益提升(俗称大保底) |
|
||||
| 9 | 512 | 20%概率,获得500 bps(+5%)永久收益提升(俗称小保底) |
|
||||
| 10 | 1024 | 接近3天收益,建议为最高值;100%概率,获得500 bps(+5%)永久收益提升(俗称大保底) |
|
||||
|
||||
> **注:** 消耗水晶数上限为1024,达到第10次后重置铸爱次数。
|
||||
> **注:** 消耗水晶数上限为1024,达到第10次后铸爱次数重置为0,永久收益提升保留。
|
||||
|
||||
### 13.2 数据库设计
|
||||
|
||||
@ -1107,7 +1108,7 @@ CREATE TABLE IF NOT EXISTS user_dazi_level (
|
||||
- `cost_crystal`:消耗水晶数(上限1024)
|
||||
- `probability`:保底触发概率(0-100),0=不触发
|
||||
- `reward_type`:触发奖励类型(收益提升等)
|
||||
- `reward_value`:奖励值(如5代表5%)
|
||||
- `reward_value`:奖励值,单位为 bps(basis points),如 500 代表 +5% 永久收益提升
|
||||
|
||||
```sql
|
||||
CREATE TABLE mint_cost_config (
|
||||
@ -1116,7 +1117,7 @@ CREATE TABLE mint_cost_config (
|
||||
cost_crystal BIGINT NOT NULL,
|
||||
probability BIGINT DEFAULT 0,
|
||||
reward_type VARCHAR(50) DEFAULT NULL,
|
||||
reward_value BIGINT DEFAULT 0,
|
||||
reward_value BIGINT DEFAULT 0, -- 单位:bps(基点),500=+5%
|
||||
description VARCHAR(255),
|
||||
updated_at BIGINT NOT NULL
|
||||
);
|
||||
@ -1131,8 +1132,8 @@ INSERT INTO mint_cost_config (mint_count, cost_crystal, probability, reward_type
|
||||
(6, 64, 0, NULL, 0, '', UNIX_MILLIS()),
|
||||
(7, 128, 0, NULL, 0, '', UNIX_MILLIS()),
|
||||
(8, 256, 0, NULL, 0, '接近1天收益', UNIX_MILLIS()),
|
||||
(9, 512, 20, '收益提升', 5, '20%概率,获得5%永久收益提升(俗称小保底)', UNIX_MILLIS()),
|
||||
(10, 1024, 100, '收益提升', 5, '接近3天收益,建议为最高值;100%概率,获得5%永久收益提升(俗称大保底)', UNIX_MILLIS());
|
||||
(9, 512, 20, '收益提升', 500, '20%概率,获得500 bps(+5%)永久收益提升(俗称小保底)', UNIX_MILLIS()),
|
||||
(10, 1024, 100, '收益提升', 500, '接近3天收益,建议为最高值;100%概率,获得500 bps(+5%)永久收益提升(俗称大保底)', UNIX_MILLIS());
|
||||
```
|
||||
|
||||
#### 13.2.2 用户累计铸爱次数表 (user_mint_count)
|
||||
@ -1162,7 +1163,7 @@ func (s *MintService) CreateMintOrder(userID, starID int64, ...) (orderID string
|
||||
// 3. 检查是否触发保底
|
||||
var boost int64 = 0
|
||||
if cost.Probability > 0 && rand.Intn(100) < cost.Probability {
|
||||
boost = cost.RewardValue // 如5代表5%
|
||||
boost = cost.RewardValue // 单位:bps,如 500 代表 +5%
|
||||
}
|
||||
|
||||
// 4. 扣水晶
|
||||
|
||||
Loading…
Reference in New Issue
Block a user