Compare commits
31 Commits
ebe57bc078
...
eb051f6562
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb051f6562 | ||
|
|
c5bf9df955 | ||
| 2c2e707971 | |||
| ff24cd55cd | |||
| 4d10fc4fb3 | |||
| f5de8927fb | |||
| 6d52af4b98 | |||
| 1ccf94c750 | |||
| ccb659e52a | |||
| c0c5426780 | |||
|
|
a90f7155c6 | ||
|
|
d6b0397230 | ||
| 15852260e6 | |||
| af74dd5aad | |||
|
|
3ef3510303 | ||
|
|
2a0faeb835 | ||
|
|
2b4077c0cd | ||
|
|
29becda3bc | ||
|
|
7d807d395b | ||
|
|
5c46ae660f | ||
|
|
e599f8a349 | ||
|
|
2ebb4a3a4d | ||
|
|
07b3520e6e | ||
|
|
f4f987d068 | ||
|
|
9978310e12 | ||
|
|
ac0eb55bc0 | ||
|
|
342beb5f17 | ||
| 281e5b5c59 | |||
| 375816ea79 | |||
| e106a490ce | |||
|
|
74182ad662 |
@ -2,7 +2,9 @@
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Skill(superpowers:subagent-driven-development)",
|
||||
"Skill(superpowers:subagent-driven-development:*)"
|
||||
"Skill(superpowers:subagent-driven-development:*)",
|
||||
"Bash(go build:*)",
|
||||
"Bash(go vet:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -339,7 +339,12 @@ echo -e "${YELLOW}🚀 启动所有服务...${NC}"
|
||||
start_service "userService" "services/userService/userService" 20000 1
|
||||
start_service "assetService" "services/assetService/assetService" 20003 1
|
||||
start_service "socialService" "services/socialService/socialService" 20002 1
|
||||
start_service "galleryService" "services/galleryService/galleryService" 20004 1
|
||||
# galleryService 需要连接 taskService (20006),单独处理
|
||||
echo -e "${GREEN}🚀 启动 galleryService...${NC}"
|
||||
"$SCRIPT_DIR/services/galleryService/galleryService" -port=20004 -task-service-url="tri://localhost:20006" "${DB_ARGS[@]}" > "/tmp/galleryService.log" 2>&1 &
|
||||
echo $! > "/tmp/dev_sh_galleryService.pid"
|
||||
sleep 2
|
||||
echo -e "${GREEN}✅ galleryService 已启动 (PID: $(cat /tmp/dev_sh_galleryService.pid), 端口: 20004)${NC}"
|
||||
start_service "activityService" "services/activityService/activityService" 20005 1
|
||||
start_service "taskService" "services/taskService/taskService" 20006 1
|
||||
start_service "starbookService" "services/starbookService/starbookService" 20007 1
|
||||
@ -352,7 +357,6 @@ start_watcher "gateway" "gateway" "gateway/gateway" 8
|
||||
start_watcher "userService" "services/userService" "services/userService/userService" 20000 1
|
||||
start_watcher "assetService" "services/assetService" "services/assetService/assetService" 20003 1
|
||||
start_watcher "socialService" "services/socialService" "services/socialService/socialService" 20002 1
|
||||
start_watcher "galleryService" "services/galleryService" "services/galleryService/galleryService" 20004 1
|
||||
start_watcher "activityService" "services/activityService" "services/activityService/activityService" 20005 1
|
||||
start_watcher "taskService" "services/taskService" "services/taskService/taskService" 20006 1
|
||||
start_watcher "starbookService" "services/starbookService" "services/starbookService/starbookService" 20007 1
|
||||
|
||||
@ -32,13 +32,13 @@ type ServerConfig struct {
|
||||
|
||||
// DubboConfig Dubbo 服务配置
|
||||
type DubboConfig struct {
|
||||
UserServiceURL string
|
||||
SocialServiceURL string
|
||||
AssetServiceURL string
|
||||
GalleryServiceURL string
|
||||
ActivityServiceURL string
|
||||
TaskServiceURL string
|
||||
StarbookServiceURL string
|
||||
UserServiceURL string
|
||||
SocialServiceURL string
|
||||
AssetServiceURL string
|
||||
GalleryServiceURL string
|
||||
ActivityServiceURL string
|
||||
TaskServiceURL string
|
||||
StarbookServiceURL string
|
||||
}
|
||||
|
||||
// JWTConfig JWT 配置
|
||||
@ -104,7 +104,7 @@ func Load() *Config {
|
||||
Redis: RedisConfig{
|
||||
Host: getEnv("REDIS_HOST", "127.0.0.1"),
|
||||
Port: getEnvInt("REDIS_PORT", 6379),
|
||||
Password: getEnv("REDIS_PASSWORD", ""),
|
||||
Password: getEnv("REDIS_PASSWORD", "123456"),
|
||||
DB: getEnvInt("REDIS_DB", 0),
|
||||
},
|
||||
}
|
||||
|
||||
@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@ -605,7 +602,9 @@ func (ctrl *TaskController) ClaimExhibitionRevenue(c *gin.Context) {
|
||||
}
|
||||
|
||||
response.Success(c, map[string]interface{}{
|
||||
"success": resp.Success,
|
||||
"success": resp.Success,
|
||||
"crystal_amount": resp.CrystalAmount,
|
||||
"total_balance": resp.TotalBalance,
|
||||
})
|
||||
}
|
||||
|
||||
@ -677,7 +676,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 +694,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 +714,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 +733,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,
|
||||
})
|
||||
|
||||
@ -71,20 +71,22 @@ func ConvertMintOrder(pbOrder *pbAsset.MintOrder) MintOrderDTO {
|
||||
// 注意:预签名 URL 需要在 Controller 层生成,因为需要访问 OSS 配置
|
||||
func ConvertAsset(pbAsset *pbAsset.Asset) AssetDTO {
|
||||
dto := AssetDTO{
|
||||
AssetID: pbAsset.AssetId,
|
||||
OwnerUID: pbAsset.OwnerUid,
|
||||
OwnerNickname: pbAsset.OwnerNickname,
|
||||
StarID: pbAsset.StarId,
|
||||
Name: pbAsset.Name,
|
||||
CoverURL: pbAsset.CoverUrl,
|
||||
Visibility: pbAsset.Visibility,
|
||||
Status: pbAsset.Status,
|
||||
LikeCount: pbAsset.LikeCount,
|
||||
CreatedAt: pbAsset.CreatedAt,
|
||||
UpdatedAt: pbAsset.UpdatedAt,
|
||||
IsLiked: pbAsset.IsLiked,
|
||||
Info: pbAsset.Info,
|
||||
DisplayStatus: pbAsset.DisplayStatus,
|
||||
AssetID: pbAsset.AssetId,
|
||||
OwnerUID: pbAsset.OwnerUid,
|
||||
OwnerNickname: pbAsset.OwnerNickname,
|
||||
StarID: pbAsset.StarId,
|
||||
Name: pbAsset.Name,
|
||||
CoverURL: pbAsset.CoverUrl,
|
||||
Visibility: pbAsset.Visibility,
|
||||
Status: pbAsset.Status,
|
||||
LikeCount: pbAsset.LikeCount,
|
||||
CreatedAt: pbAsset.CreatedAt,
|
||||
UpdatedAt: pbAsset.UpdatedAt,
|
||||
IsLiked: pbAsset.IsLiked,
|
||||
Info: pbAsset.Info,
|
||||
DisplayStatus: pbAsset.DisplayStatus,
|
||||
Earnings: pbAsset.Earnings,
|
||||
ExhibitionExpireAt: pbAsset.ExhibitionExpireAt,
|
||||
}
|
||||
|
||||
// 可选字段
|
||||
|
||||
@ -80,7 +80,9 @@ type AssetDTO struct {
|
||||
Owner *OwnerInfoDTO `json:"owner,omitempty"` // 持有者信息(可选,保留用于兼容性)
|
||||
IsLiked bool `json:"is_liked"` // 当前用户是否已点赞
|
||||
Info string `json:"info"` // 藏品信息
|
||||
DisplayStatus int32 `json:"display_status"` // 展示状态:0=待展示, 1=已展示
|
||||
DisplayStatus int32 `json:"display_status"` // 展示状态:0=待展示, 1=已展示
|
||||
Earnings int64 `json:"earnings"` // 当前展出收益(实时计算,仅展出中时有值)
|
||||
ExhibitionExpireAt int64 `json:"exhibition_expire_at"` // 展出过期时间(毫秒时间戳,仅展出中时有值,0=未展出)
|
||||
}
|
||||
|
||||
// OwnerInfoDTO 持有者信息
|
||||
|
||||
@ -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,
|
||||
})
|
||||
|
||||
@ -48,6 +48,8 @@ github.com/aws/smithy-go v1.8.0 h1:AEwwwXQZtUwP5Mz506FeXXrKBe0jA8gVM+1gEcSRooc=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
|
||||
github.com/bketelsen/crypt v0.0.4 h1:w/jqZtC9YD4DS/Vp9GhWfWcCpuAL58oTnLoI8vE9YHU=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/casbin/casbin/v2 v2.1.2 h1:bTwon/ECRx9dwBy2ewRVr5OiqjeXSGiTUY74sDPQi/g=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
|
||||
@ -289,6 +291,7 @@ github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/X
|
||||
github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
|
||||
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
|
||||
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
|
||||
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
|
||||
go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts=
|
||||
|
||||
@ -23,7 +23,7 @@ func (BoothSlot) TableName() string {
|
||||
// Exhibition 展品展示表
|
||||
type Exhibition struct {
|
||||
ID int64 `gorm:"primaryKey;column:id;autoIncrement"`
|
||||
AssetID int64 `gorm:"column:asset_id;not null;uniqueIndex:uk_asset"`
|
||||
AssetID int64 `gorm:"column:asset_id;not null;index:idx_asset"`
|
||||
SlotID int64 `gorm:"column:slot_id;not null;index:idx_slot"`
|
||||
HostProfileID int64 `gorm:"column:host_profile_id;not null;index:idx_host"`
|
||||
OccupierUID int64 `gorm:"column:occupier_uid;not null;index:idx_occupier"`
|
||||
|
||||
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"
|
||||
}
|
||||
@ -2,13 +2,13 @@ package models
|
||||
|
||||
// SystemConfig 系统配置表模型
|
||||
type SystemConfig struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement;column:id"`
|
||||
ConfigKey string `gorm:"type:varchar(100);uniqueIndex;not null;column:config_key"`
|
||||
ConfigValue int `gorm:"not null;column:config_value"`
|
||||
Description string `gorm:"type:varchar(500);column:description"`
|
||||
IsActive bool `gorm:"default:true;column:is_active"`
|
||||
CreatedAt int64 `gorm:"column:created_at"`
|
||||
UpdatedAt int64 `gorm:"column:updated_at"`
|
||||
ID int64 `gorm:"primaryKey;autoIncrement;column:id"`
|
||||
ConfigKey string `gorm:"type:varchar(100);uniqueIndex;not null;column:config_key"`
|
||||
ConfigValue float64 `gorm:"type:numeric(10,2);not null;column:config_value"` // 使用 float64 支持小数
|
||||
Description string `gorm:"type:varchar(500);column:description"`
|
||||
IsActive bool `gorm:"default:true;column:is_active"`
|
||||
CreatedAt int64 `gorm:"column:created_at"`
|
||||
UpdatedAt int64 `gorm:"column:updated_at"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
|
||||
@ -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"
|
||||
}
|
||||
|
||||
@ -44,13 +44,15 @@ type Asset struct {
|
||||
UpdatedAt int64 `protobuf:"varint,16,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` // 更新时间(毫秒时间戳)
|
||||
MintedAt int64 `protobuf:"varint,17,opt,name=minted_at,json=mintedAt,proto3" json:"minted_at,omitempty"` // 上链成功时间(毫秒时间戳,可选)
|
||||
// 扩展字段:持有者信息(用于详情展示)
|
||||
Owner *OwnerInfo `protobuf:"bytes,18,opt,name=owner,proto3" json:"owner,omitempty"` // 持有者信息(保留用于兼容性)
|
||||
OwnerNickname string `protobuf:"bytes,19,opt,name=owner_nickname,json=ownerNickname,proto3" json:"owner_nickname,omitempty"` // 持有者昵称(在该star下的昵称)
|
||||
IsLiked bool `protobuf:"varint,20,opt,name=is_liked,json=isLiked,proto3" json:"is_liked,omitempty"` // 当前用户是否已点赞
|
||||
Info string `protobuf:"bytes,21,opt,name=info,proto3" json:"info,omitempty"` // 藏品信息
|
||||
DisplayStatus int32 `protobuf:"varint,22,opt,name=display_status,json=displayStatus,proto3" json:"display_status,omitempty"` // 展示状态:0=待展示, 1=已展示
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
Owner *OwnerInfo `protobuf:"bytes,18,opt,name=owner,proto3" json:"owner,omitempty"` // 持有者信息(保留用于兼容性)
|
||||
OwnerNickname string `protobuf:"bytes,19,opt,name=owner_nickname,json=ownerNickname,proto3" json:"owner_nickname,omitempty"` // 持有者昵称(在该star下的昵称)
|
||||
IsLiked bool `protobuf:"varint,20,opt,name=is_liked,json=isLiked,proto3" json:"is_liked,omitempty"` // 当前用户是否已点赞
|
||||
Info string `protobuf:"bytes,21,opt,name=info,proto3" json:"info,omitempty"` // 藏品信息
|
||||
DisplayStatus int32 `protobuf:"varint,22,opt,name=display_status,json=displayStatus,proto3" json:"display_status,omitempty"` // 展示状态:0=待展示, 1=已展示
|
||||
Earnings int64 `protobuf:"varint,23,opt,name=earnings,proto3" json:"earnings,omitempty"` // 当前展出收益(实时计算,仅展出中时有值)
|
||||
ExhibitionExpireAt int64 `protobuf:"varint,24,opt,name=exhibition_expire_at,json=exhibitionExpireAt,proto3" json:"exhibition_expire_at,omitempty"` // 展出过期时间(毫秒时间戳,仅展出中时有值,0=未展出)
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Asset) Reset() {
|
||||
@ -237,6 +239,20 @@ func (x *Asset) GetDisplayStatus() int32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Asset) GetEarnings() int64 {
|
||||
if x != nil {
|
||||
return x.Earnings
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Asset) GetExhibitionExpireAt() int64 {
|
||||
if x != nil {
|
||||
return x.ExhibitionExpireAt
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// 持有者信息
|
||||
type OwnerInfo struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
@ -2618,7 +2634,7 @@ var File_asset_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_asset_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\vasset.proto\x12\rtopfans.asset\x1a\x12proto/common.proto\x1a\x1cgoogle/api/annotations.proto\"\x93\x05\n" +
|
||||
"\vasset.proto\x12\rtopfans.asset\x1a\x12proto/common.proto\x1a\x1cgoogle/api/annotations.proto\"\xe1\x05\n" +
|
||||
"\x05Asset\x12\x19\n" +
|
||||
"\basset_id\x18\x01 \x01(\x03R\aassetId\x12\x1b\n" +
|
||||
"\towner_uid\x18\x02 \x01(\x03R\bownerUid\x12\x17\n" +
|
||||
@ -2647,7 +2663,9 @@ const file_asset_proto_rawDesc = "" +
|
||||
"\x0eowner_nickname\x18\x13 \x01(\tR\rownerNickname\x12\x19\n" +
|
||||
"\bis_liked\x18\x14 \x01(\bR\aisLiked\x12\x12\n" +
|
||||
"\x04info\x18\x15 \x01(\tR\x04info\x12%\n" +
|
||||
"\x0edisplay_status\x18\x16 \x01(\x05R\rdisplayStatus\"X\n" +
|
||||
"\x0edisplay_status\x18\x16 \x01(\x05R\rdisplayStatus\x12\x1a\n" +
|
||||
"\bearnings\x18\x17 \x01(\x03R\bearnings\x120\n" +
|
||||
"\x14exhibition_expire_at\x18\x18 \x01(\x03R\x12exhibitionExpireAt\"X\n" +
|
||||
"\tOwnerInfo\x12\x17\n" +
|
||||
"\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x1a\n" +
|
||||
"\bnickname\x18\x02 \x01(\tR\bnickname\x12\x16\n" +
|
||||
|
||||
@ -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 ()
|
||||
@ -939,6 +939,96 @@ func (x *RemoveFromSlotResponse) GetBase() *common.BaseResponse {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 根据资产ID移除展品请求
|
||||
type RemoveExhibitionByAssetRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
AssetId int64 `protobuf:"varint,1,opt,name=asset_id,json=assetId,proto3" json:"asset_id,omitempty"` // 资产ID(必填)
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *RemoveExhibitionByAssetRequest) Reset() {
|
||||
*x = RemoveExhibitionByAssetRequest{}
|
||||
mi := &file_gallery_proto_msgTypes[16]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *RemoveExhibitionByAssetRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*RemoveExhibitionByAssetRequest) ProtoMessage() {}
|
||||
|
||||
func (x *RemoveExhibitionByAssetRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_gallery_proto_msgTypes[16]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use RemoveExhibitionByAssetRequest.ProtoReflect.Descriptor instead.
|
||||
func (*RemoveExhibitionByAssetRequest) Descriptor() ([]byte, []int) {
|
||||
return file_gallery_proto_rawDescGZIP(), []int{16}
|
||||
}
|
||||
|
||||
func (x *RemoveExhibitionByAssetRequest) GetAssetId() int64 {
|
||||
if x != nil {
|
||||
return x.AssetId
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// 根据资产ID移除展品响应
|
||||
type RemoveExhibitionByAssetResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Base *common.BaseResponse `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *RemoveExhibitionByAssetResponse) Reset() {
|
||||
*x = RemoveExhibitionByAssetResponse{}
|
||||
mi := &file_gallery_proto_msgTypes[17]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *RemoveExhibitionByAssetResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*RemoveExhibitionByAssetResponse) ProtoMessage() {}
|
||||
|
||||
func (x *RemoveExhibitionByAssetResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_gallery_proto_msgTypes[17]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use RemoveExhibitionByAssetResponse.ProtoReflect.Descriptor instead.
|
||||
func (*RemoveExhibitionByAssetResponse) Descriptor() ([]byte, []int) {
|
||||
return file_gallery_proto_rawDescGZIP(), []int{17}
|
||||
}
|
||||
|
||||
func (x *RemoveExhibitionByAssetResponse) GetBase() *common.BaseResponse {
|
||||
if x != nil {
|
||||
return x.Base
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 获取我展出的作品列表请求
|
||||
type GetMyExhibitedAssetsRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
@ -950,7 +1040,7 @@ type GetMyExhibitedAssetsRequest struct {
|
||||
|
||||
func (x *GetMyExhibitedAssetsRequest) Reset() {
|
||||
*x = GetMyExhibitedAssetsRequest{}
|
||||
mi := &file_gallery_proto_msgTypes[16]
|
||||
mi := &file_gallery_proto_msgTypes[18]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -962,7 +1052,7 @@ func (x *GetMyExhibitedAssetsRequest) String() string {
|
||||
func (*GetMyExhibitedAssetsRequest) ProtoMessage() {}
|
||||
|
||||
func (x *GetMyExhibitedAssetsRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_gallery_proto_msgTypes[16]
|
||||
mi := &file_gallery_proto_msgTypes[18]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -975,7 +1065,7 @@ func (x *GetMyExhibitedAssetsRequest) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use GetMyExhibitedAssetsRequest.ProtoReflect.Descriptor instead.
|
||||
func (*GetMyExhibitedAssetsRequest) Descriptor() ([]byte, []int) {
|
||||
return file_gallery_proto_rawDescGZIP(), []int{16}
|
||||
return file_gallery_proto_rawDescGZIP(), []int{18}
|
||||
}
|
||||
|
||||
func (x *GetMyExhibitedAssetsRequest) GetPage() int32 {
|
||||
@ -1003,7 +1093,7 @@ type GetMyExhibitedAssetsResponse struct {
|
||||
|
||||
func (x *GetMyExhibitedAssetsResponse) Reset() {
|
||||
*x = GetMyExhibitedAssetsResponse{}
|
||||
mi := &file_gallery_proto_msgTypes[17]
|
||||
mi := &file_gallery_proto_msgTypes[19]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -1015,7 +1105,7 @@ func (x *GetMyExhibitedAssetsResponse) String() string {
|
||||
func (*GetMyExhibitedAssetsResponse) ProtoMessage() {}
|
||||
|
||||
func (x *GetMyExhibitedAssetsResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_gallery_proto_msgTypes[17]
|
||||
mi := &file_gallery_proto_msgTypes[19]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -1028,7 +1118,7 @@ func (x *GetMyExhibitedAssetsResponse) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use GetMyExhibitedAssetsResponse.ProtoReflect.Descriptor instead.
|
||||
func (*GetMyExhibitedAssetsResponse) Descriptor() ([]byte, []int) {
|
||||
return file_gallery_proto_rawDescGZIP(), []int{17}
|
||||
return file_gallery_proto_rawDescGZIP(), []int{19}
|
||||
}
|
||||
|
||||
func (x *GetMyExhibitedAssetsResponse) GetBase() *common.BaseResponse {
|
||||
@ -1059,7 +1149,7 @@ type ExhibitedAssetsData struct {
|
||||
|
||||
func (x *ExhibitedAssetsData) Reset() {
|
||||
*x = ExhibitedAssetsData{}
|
||||
mi := &file_gallery_proto_msgTypes[18]
|
||||
mi := &file_gallery_proto_msgTypes[20]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -1071,7 +1161,7 @@ func (x *ExhibitedAssetsData) String() string {
|
||||
func (*ExhibitedAssetsData) ProtoMessage() {}
|
||||
|
||||
func (x *ExhibitedAssetsData) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_gallery_proto_msgTypes[18]
|
||||
mi := &file_gallery_proto_msgTypes[20]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -1084,7 +1174,7 @@ func (x *ExhibitedAssetsData) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use ExhibitedAssetsData.ProtoReflect.Descriptor instead.
|
||||
func (*ExhibitedAssetsData) Descriptor() ([]byte, []int) {
|
||||
return file_gallery_proto_rawDescGZIP(), []int{18}
|
||||
return file_gallery_proto_rawDescGZIP(), []int{20}
|
||||
}
|
||||
|
||||
func (x *ExhibitedAssetsData) GetItems() []*ExhibitedAssetItem {
|
||||
@ -1138,7 +1228,7 @@ type ExhibitedAssetItem struct {
|
||||
|
||||
func (x *ExhibitedAssetItem) Reset() {
|
||||
*x = ExhibitedAssetItem{}
|
||||
mi := &file_gallery_proto_msgTypes[19]
|
||||
mi := &file_gallery_proto_msgTypes[21]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -1150,7 +1240,7 @@ func (x *ExhibitedAssetItem) String() string {
|
||||
func (*ExhibitedAssetItem) ProtoMessage() {}
|
||||
|
||||
func (x *ExhibitedAssetItem) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_gallery_proto_msgTypes[19]
|
||||
mi := &file_gallery_proto_msgTypes[21]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -1163,7 +1253,7 @@ func (x *ExhibitedAssetItem) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use ExhibitedAssetItem.ProtoReflect.Descriptor instead.
|
||||
func (*ExhibitedAssetItem) Descriptor() ([]byte, []int) {
|
||||
return file_gallery_proto_rawDescGZIP(), []int{19}
|
||||
return file_gallery_proto_rawDescGZIP(), []int{21}
|
||||
}
|
||||
|
||||
func (x *ExhibitedAssetItem) GetAssetId() int64 {
|
||||
@ -1229,7 +1319,7 @@ type GetInspirationFlowRequest struct {
|
||||
|
||||
func (x *GetInspirationFlowRequest) Reset() {
|
||||
*x = GetInspirationFlowRequest{}
|
||||
mi := &file_gallery_proto_msgTypes[20]
|
||||
mi := &file_gallery_proto_msgTypes[22]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -1241,7 +1331,7 @@ func (x *GetInspirationFlowRequest) String() string {
|
||||
func (*GetInspirationFlowRequest) ProtoMessage() {}
|
||||
|
||||
func (x *GetInspirationFlowRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_gallery_proto_msgTypes[20]
|
||||
mi := &file_gallery_proto_msgTypes[22]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -1254,7 +1344,7 @@ func (x *GetInspirationFlowRequest) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use GetInspirationFlowRequest.ProtoReflect.Descriptor instead.
|
||||
func (*GetInspirationFlowRequest) Descriptor() ([]byte, []int) {
|
||||
return file_gallery_proto_rawDescGZIP(), []int{20}
|
||||
return file_gallery_proto_rawDescGZIP(), []int{22}
|
||||
}
|
||||
|
||||
func (x *GetInspirationFlowRequest) GetCursor() string {
|
||||
@ -1303,7 +1393,7 @@ type GetInspirationFlowResponse struct {
|
||||
|
||||
func (x *GetInspirationFlowResponse) Reset() {
|
||||
*x = GetInspirationFlowResponse{}
|
||||
mi := &file_gallery_proto_msgTypes[21]
|
||||
mi := &file_gallery_proto_msgTypes[23]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -1315,7 +1405,7 @@ func (x *GetInspirationFlowResponse) String() string {
|
||||
func (*GetInspirationFlowResponse) ProtoMessage() {}
|
||||
|
||||
func (x *GetInspirationFlowResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_gallery_proto_msgTypes[21]
|
||||
mi := &file_gallery_proto_msgTypes[23]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -1328,7 +1418,7 @@ func (x *GetInspirationFlowResponse) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use GetInspirationFlowResponse.ProtoReflect.Descriptor instead.
|
||||
func (*GetInspirationFlowResponse) Descriptor() ([]byte, []int) {
|
||||
return file_gallery_proto_rawDescGZIP(), []int{21}
|
||||
return file_gallery_proto_rawDescGZIP(), []int{23}
|
||||
}
|
||||
|
||||
func (x *GetInspirationFlowResponse) GetBase() *common.BaseResponse {
|
||||
@ -1358,7 +1448,7 @@ type InspirationFlowData struct {
|
||||
|
||||
func (x *InspirationFlowData) Reset() {
|
||||
*x = InspirationFlowData{}
|
||||
mi := &file_gallery_proto_msgTypes[22]
|
||||
mi := &file_gallery_proto_msgTypes[24]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -1370,7 +1460,7 @@ func (x *InspirationFlowData) String() string {
|
||||
func (*InspirationFlowData) ProtoMessage() {}
|
||||
|
||||
func (x *InspirationFlowData) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_gallery_proto_msgTypes[22]
|
||||
mi := &file_gallery_proto_msgTypes[24]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -1383,7 +1473,7 @@ func (x *InspirationFlowData) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use InspirationFlowData.ProtoReflect.Descriptor instead.
|
||||
func (*InspirationFlowData) Descriptor() ([]byte, []int) {
|
||||
return file_gallery_proto_rawDescGZIP(), []int{22}
|
||||
return file_gallery_proto_rawDescGZIP(), []int{24}
|
||||
}
|
||||
|
||||
func (x *InspirationFlowData) GetItems() []*InspirationFlowItem {
|
||||
@ -1430,7 +1520,7 @@ type InspirationFlowItem struct {
|
||||
|
||||
func (x *InspirationFlowItem) Reset() {
|
||||
*x = InspirationFlowItem{}
|
||||
mi := &file_gallery_proto_msgTypes[23]
|
||||
mi := &file_gallery_proto_msgTypes[25]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -1442,7 +1532,7 @@ func (x *InspirationFlowItem) String() string {
|
||||
func (*InspirationFlowItem) ProtoMessage() {}
|
||||
|
||||
func (x *InspirationFlowItem) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_gallery_proto_msgTypes[23]
|
||||
mi := &file_gallery_proto_msgTypes[25]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -1455,7 +1545,7 @@ func (x *InspirationFlowItem) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use InspirationFlowItem.ProtoReflect.Descriptor instead.
|
||||
func (*InspirationFlowItem) Descriptor() ([]byte, []int) {
|
||||
return file_gallery_proto_rawDescGZIP(), []int{23}
|
||||
return file_gallery_proto_rawDescGZIP(), []int{25}
|
||||
}
|
||||
|
||||
func (x *InspirationFlowItem) GetAssetId() int64 {
|
||||
@ -1519,7 +1609,7 @@ type GetUserExhibitedAssetsRequest struct {
|
||||
|
||||
func (x *GetUserExhibitedAssetsRequest) Reset() {
|
||||
*x = GetUserExhibitedAssetsRequest{}
|
||||
mi := &file_gallery_proto_msgTypes[24]
|
||||
mi := &file_gallery_proto_msgTypes[26]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -1531,7 +1621,7 @@ func (x *GetUserExhibitedAssetsRequest) String() string {
|
||||
func (*GetUserExhibitedAssetsRequest) ProtoMessage() {}
|
||||
|
||||
func (x *GetUserExhibitedAssetsRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_gallery_proto_msgTypes[24]
|
||||
mi := &file_gallery_proto_msgTypes[26]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -1544,7 +1634,7 @@ func (x *GetUserExhibitedAssetsRequest) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use GetUserExhibitedAssetsRequest.ProtoReflect.Descriptor instead.
|
||||
func (*GetUserExhibitedAssetsRequest) Descriptor() ([]byte, []int) {
|
||||
return file_gallery_proto_rawDescGZIP(), []int{24}
|
||||
return file_gallery_proto_rawDescGZIP(), []int{26}
|
||||
}
|
||||
|
||||
func (x *GetUserExhibitedAssetsRequest) GetUserId() int64 {
|
||||
@ -1579,7 +1669,7 @@ type GetUserExhibitedAssetsResponse struct {
|
||||
|
||||
func (x *GetUserExhibitedAssetsResponse) Reset() {
|
||||
*x = GetUserExhibitedAssetsResponse{}
|
||||
mi := &file_gallery_proto_msgTypes[25]
|
||||
mi := &file_gallery_proto_msgTypes[27]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -1591,7 +1681,7 @@ func (x *GetUserExhibitedAssetsResponse) String() string {
|
||||
func (*GetUserExhibitedAssetsResponse) ProtoMessage() {}
|
||||
|
||||
func (x *GetUserExhibitedAssetsResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_gallery_proto_msgTypes[25]
|
||||
mi := &file_gallery_proto_msgTypes[27]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -1604,7 +1694,7 @@ func (x *GetUserExhibitedAssetsResponse) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use GetUserExhibitedAssetsResponse.ProtoReflect.Descriptor instead.
|
||||
func (*GetUserExhibitedAssetsResponse) Descriptor() ([]byte, []int) {
|
||||
return file_gallery_proto_rawDescGZIP(), []int{25}
|
||||
return file_gallery_proto_rawDescGZIP(), []int{27}
|
||||
}
|
||||
|
||||
func (x *GetUserExhibitedAssetsResponse) GetBase() *common.BaseResponse {
|
||||
@ -1695,6 +1785,10 @@ const file_gallery_proto_rawDesc = "" +
|
||||
"\x15RemoveFromSlotRequest\x12\x17\n" +
|
||||
"\aslot_id\x18\x01 \x01(\x03R\x06slotId\"J\n" +
|
||||
"\x16RemoveFromSlotResponse\x120\n" +
|
||||
"\x04base\x18\x01 \x01(\v2\x1c.topfans.common.BaseResponseR\x04base\";\n" +
|
||||
"\x1eRemoveExhibitionByAssetRequest\x12\x19\n" +
|
||||
"\basset_id\x18\x01 \x01(\x03R\aassetId\"S\n" +
|
||||
"\x1fRemoveExhibitionByAssetResponse\x120\n" +
|
||||
"\x04base\x18\x01 \x01(\v2\x1c.topfans.common.BaseResponseR\x04base\"N\n" +
|
||||
"\x1bGetMyExhibitedAssetsRequest\x12\x12\n" +
|
||||
"\x04page\x18\x01 \x01(\x05R\x04page\x12\x1b\n" +
|
||||
@ -1748,7 +1842,7 @@ const file_gallery_proto_rawDesc = "" +
|
||||
"\tpage_size\x18\x03 \x01(\x05R\bpageSize\"\x8c\x01\n" +
|
||||
"\x1eGetUserExhibitedAssetsResponse\x120\n" +
|
||||
"\x04base\x18\x01 \x01(\v2\x1c.topfans.common.BaseResponseR\x04base\x128\n" +
|
||||
"\x04data\x18\x02 \x01(\v2$.topfans.gallery.ExhibitedAssetsDataR\x04data2\xf4\b\n" +
|
||||
"\x04data\x18\x02 \x01(\v2$.topfans.gallery.ExhibitedAssetsDataR\x04data2\xf2\t\n" +
|
||||
"\x0eGalleryService\x12u\n" +
|
||||
"\fGetMyGallery\x12$.topfans.gallery.GetMyGalleryRequest\x1a%.topfans.gallery.GetMyGalleryResponse\"\x18\x82\xd3\xe4\x93\x02\x12\x12\x10/api/mygalleries\x12\x86\x01\n" +
|
||||
"\x0eGetUserGallery\x12&.topfans.gallery.GetUserGalleryRequest\x1a'.topfans.gallery.GetUserGalleryResponse\"#\x82\xd3\xe4\x93\x02\x1d\x12\x1b/api/galleries/{target_uid}\x12v\n" +
|
||||
@ -1756,7 +1850,8 @@ const file_gallery_proto_rawDesc = "" +
|
||||
"PlaceAsset\x12\".topfans.gallery.PlaceAssetRequest\x1a#.topfans.gallery.PlaceAssetResponse\"\x1f\x82\xd3\xe4\x93\x02\x19:\x01*\"\x14/api/galleries/place\x12}\n" +
|
||||
"\n" +
|
||||
"UnlockSlot\x12\".topfans.gallery.UnlockSlotRequest\x1a#.topfans.gallery.UnlockSlotResponse\"&\x82\xd3\xe4\x93\x02 :\x01*\"\x1b/api/galleries/slots_unlock\x12\x8f\x01\n" +
|
||||
"\x0eRemoveFromSlot\x12&.topfans.gallery.RemoveFromSlotRequest\x1a'.topfans.gallery.RemoveFromSlotResponse\",\x82\xd3\xe4\x93\x02&*$/api/galleries/slots/{slot_id}/asset\x12\x98\x01\n" +
|
||||
"\x0eRemoveFromSlot\x12&.topfans.gallery.RemoveFromSlotRequest\x1a'.topfans.gallery.RemoveFromSlotResponse\",\x82\xd3\xe4\x93\x02&*$/api/galleries/slots/{slot_id}/asset\x12|\n" +
|
||||
"\x17RemoveExhibitionByAsset\x12/.topfans.gallery.RemoveExhibitionByAssetRequest\x1a0.topfans.gallery.RemoveExhibitionByAssetResponse\x12\x98\x01\n" +
|
||||
"\x14GetMyExhibitedAssets\x12,.topfans.gallery.GetMyExhibitedAssetsRequest\x1a-.topfans.gallery.GetMyExhibitedAssetsResponse\"#\x82\xd3\xe4\x93\x02\x1d\x12\x1b/api/v1/me/exhibited-assets\x12\x8f\x01\n" +
|
||||
"\x12GetInspirationFlow\x12*.topfans.gallery.GetInspirationFlowRequest\x1a+.topfans.gallery.GetInspirationFlowResponse\" \x82\xd3\xe4\x93\x02\x1a\x12\x18/api/v1/inspiration-flow\x12\xab\x01\n" +
|
||||
"\x16GetUserExhibitedAssets\x12..topfans.gallery.GetUserExhibitedAssetsRequest\x1a/.topfans.gallery.GetUserExhibitedAssetsResponse\"0\x82\xd3\xe4\x93\x02*\x12(/api/v1/users/{user_id}/exhibited-assetsB6Z4github.com/topfans/backend/pkg/proto/gallery;galleryb\x06proto3"
|
||||
@ -1773,78 +1868,83 @@ func file_gallery_proto_rawDescGZIP() []byte {
|
||||
return file_gallery_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_gallery_proto_msgTypes = make([]protoimpl.MessageInfo, 26)
|
||||
var file_gallery_proto_msgTypes = make([]protoimpl.MessageInfo, 28)
|
||||
var file_gallery_proto_goTypes = []any{
|
||||
(*GetMyGalleryRequest)(nil), // 0: topfans.gallery.GetMyGalleryRequest
|
||||
(*GetMyGalleryResponse)(nil), // 1: topfans.gallery.GetMyGalleryResponse
|
||||
(*GetUserGalleryRequest)(nil), // 2: topfans.gallery.GetUserGalleryRequest
|
||||
(*GetUserGalleryResponse)(nil), // 3: topfans.gallery.GetUserGalleryResponse
|
||||
(*PlaceAssetRequest)(nil), // 4: topfans.gallery.PlaceAssetRequest
|
||||
(*PlaceAssetResponse)(nil), // 5: topfans.gallery.PlaceAssetResponse
|
||||
(*UnlockSlotRequest)(nil), // 6: topfans.gallery.UnlockSlotRequest
|
||||
(*UnlockSlotResponse)(nil), // 7: topfans.gallery.UnlockSlotResponse
|
||||
(*GalleryData)(nil), // 8: topfans.gallery.GalleryData
|
||||
(*SlotInfo)(nil), // 9: topfans.gallery.SlotInfo
|
||||
(*AssetInfo)(nil), // 10: topfans.gallery.AssetInfo
|
||||
(*UnlockCondition)(nil), // 11: topfans.gallery.UnlockCondition
|
||||
(*PlaceAssetData)(nil), // 12: topfans.gallery.PlaceAssetData
|
||||
(*UnlockSlotData)(nil), // 13: topfans.gallery.UnlockSlotData
|
||||
(*RemoveFromSlotRequest)(nil), // 14: topfans.gallery.RemoveFromSlotRequest
|
||||
(*RemoveFromSlotResponse)(nil), // 15: topfans.gallery.RemoveFromSlotResponse
|
||||
(*GetMyExhibitedAssetsRequest)(nil), // 16: topfans.gallery.GetMyExhibitedAssetsRequest
|
||||
(*GetMyExhibitedAssetsResponse)(nil), // 17: topfans.gallery.GetMyExhibitedAssetsResponse
|
||||
(*ExhibitedAssetsData)(nil), // 18: topfans.gallery.ExhibitedAssetsData
|
||||
(*ExhibitedAssetItem)(nil), // 19: topfans.gallery.ExhibitedAssetItem
|
||||
(*GetInspirationFlowRequest)(nil), // 20: topfans.gallery.GetInspirationFlowRequest
|
||||
(*GetInspirationFlowResponse)(nil), // 21: topfans.gallery.GetInspirationFlowResponse
|
||||
(*InspirationFlowData)(nil), // 22: topfans.gallery.InspirationFlowData
|
||||
(*InspirationFlowItem)(nil), // 23: topfans.gallery.InspirationFlowItem
|
||||
(*GetUserExhibitedAssetsRequest)(nil), // 24: topfans.gallery.GetUserExhibitedAssetsRequest
|
||||
(*GetUserExhibitedAssetsResponse)(nil), // 25: topfans.gallery.GetUserExhibitedAssetsResponse
|
||||
(*common.BaseResponse)(nil), // 26: topfans.common.BaseResponse
|
||||
(*GetMyGalleryRequest)(nil), // 0: topfans.gallery.GetMyGalleryRequest
|
||||
(*GetMyGalleryResponse)(nil), // 1: topfans.gallery.GetMyGalleryResponse
|
||||
(*GetUserGalleryRequest)(nil), // 2: topfans.gallery.GetUserGalleryRequest
|
||||
(*GetUserGalleryResponse)(nil), // 3: topfans.gallery.GetUserGalleryResponse
|
||||
(*PlaceAssetRequest)(nil), // 4: topfans.gallery.PlaceAssetRequest
|
||||
(*PlaceAssetResponse)(nil), // 5: topfans.gallery.PlaceAssetResponse
|
||||
(*UnlockSlotRequest)(nil), // 6: topfans.gallery.UnlockSlotRequest
|
||||
(*UnlockSlotResponse)(nil), // 7: topfans.gallery.UnlockSlotResponse
|
||||
(*GalleryData)(nil), // 8: topfans.gallery.GalleryData
|
||||
(*SlotInfo)(nil), // 9: topfans.gallery.SlotInfo
|
||||
(*AssetInfo)(nil), // 10: topfans.gallery.AssetInfo
|
||||
(*UnlockCondition)(nil), // 11: topfans.gallery.UnlockCondition
|
||||
(*PlaceAssetData)(nil), // 12: topfans.gallery.PlaceAssetData
|
||||
(*UnlockSlotData)(nil), // 13: topfans.gallery.UnlockSlotData
|
||||
(*RemoveFromSlotRequest)(nil), // 14: topfans.gallery.RemoveFromSlotRequest
|
||||
(*RemoveFromSlotResponse)(nil), // 15: topfans.gallery.RemoveFromSlotResponse
|
||||
(*RemoveExhibitionByAssetRequest)(nil), // 16: topfans.gallery.RemoveExhibitionByAssetRequest
|
||||
(*RemoveExhibitionByAssetResponse)(nil), // 17: topfans.gallery.RemoveExhibitionByAssetResponse
|
||||
(*GetMyExhibitedAssetsRequest)(nil), // 18: topfans.gallery.GetMyExhibitedAssetsRequest
|
||||
(*GetMyExhibitedAssetsResponse)(nil), // 19: topfans.gallery.GetMyExhibitedAssetsResponse
|
||||
(*ExhibitedAssetsData)(nil), // 20: topfans.gallery.ExhibitedAssetsData
|
||||
(*ExhibitedAssetItem)(nil), // 21: topfans.gallery.ExhibitedAssetItem
|
||||
(*GetInspirationFlowRequest)(nil), // 22: topfans.gallery.GetInspirationFlowRequest
|
||||
(*GetInspirationFlowResponse)(nil), // 23: topfans.gallery.GetInspirationFlowResponse
|
||||
(*InspirationFlowData)(nil), // 24: topfans.gallery.InspirationFlowData
|
||||
(*InspirationFlowItem)(nil), // 25: topfans.gallery.InspirationFlowItem
|
||||
(*GetUserExhibitedAssetsRequest)(nil), // 26: topfans.gallery.GetUserExhibitedAssetsRequest
|
||||
(*GetUserExhibitedAssetsResponse)(nil), // 27: topfans.gallery.GetUserExhibitedAssetsResponse
|
||||
(*common.BaseResponse)(nil), // 28: topfans.common.BaseResponse
|
||||
}
|
||||
var file_gallery_proto_depIdxs = []int32{
|
||||
26, // 0: topfans.gallery.GetMyGalleryResponse.base:type_name -> topfans.common.BaseResponse
|
||||
28, // 0: topfans.gallery.GetMyGalleryResponse.base:type_name -> topfans.common.BaseResponse
|
||||
8, // 1: topfans.gallery.GetMyGalleryResponse.data:type_name -> topfans.gallery.GalleryData
|
||||
26, // 2: topfans.gallery.GetUserGalleryResponse.base:type_name -> topfans.common.BaseResponse
|
||||
28, // 2: topfans.gallery.GetUserGalleryResponse.base:type_name -> topfans.common.BaseResponse
|
||||
8, // 3: topfans.gallery.GetUserGalleryResponse.data:type_name -> topfans.gallery.GalleryData
|
||||
26, // 4: topfans.gallery.PlaceAssetResponse.base:type_name -> topfans.common.BaseResponse
|
||||
28, // 4: topfans.gallery.PlaceAssetResponse.base:type_name -> topfans.common.BaseResponse
|
||||
12, // 5: topfans.gallery.PlaceAssetResponse.data:type_name -> topfans.gallery.PlaceAssetData
|
||||
26, // 6: topfans.gallery.UnlockSlotResponse.base:type_name -> topfans.common.BaseResponse
|
||||
28, // 6: topfans.gallery.UnlockSlotResponse.base:type_name -> topfans.common.BaseResponse
|
||||
13, // 7: topfans.gallery.UnlockSlotResponse.data:type_name -> topfans.gallery.UnlockSlotData
|
||||
9, // 8: topfans.gallery.GalleryData.slots:type_name -> topfans.gallery.SlotInfo
|
||||
10, // 9: topfans.gallery.SlotInfo.asset:type_name -> topfans.gallery.AssetInfo
|
||||
11, // 10: topfans.gallery.SlotInfo.unlock_condition:type_name -> topfans.gallery.UnlockCondition
|
||||
26, // 11: topfans.gallery.RemoveFromSlotResponse.base:type_name -> topfans.common.BaseResponse
|
||||
26, // 12: topfans.gallery.GetMyExhibitedAssetsResponse.base:type_name -> topfans.common.BaseResponse
|
||||
18, // 13: topfans.gallery.GetMyExhibitedAssetsResponse.data:type_name -> topfans.gallery.ExhibitedAssetsData
|
||||
19, // 14: topfans.gallery.ExhibitedAssetsData.items:type_name -> topfans.gallery.ExhibitedAssetItem
|
||||
26, // 15: topfans.gallery.GetInspirationFlowResponse.base:type_name -> topfans.common.BaseResponse
|
||||
22, // 16: topfans.gallery.GetInspirationFlowResponse.data:type_name -> topfans.gallery.InspirationFlowData
|
||||
23, // 17: topfans.gallery.InspirationFlowData.items:type_name -> topfans.gallery.InspirationFlowItem
|
||||
26, // 18: topfans.gallery.GetUserExhibitedAssetsResponse.base:type_name -> topfans.common.BaseResponse
|
||||
18, // 19: topfans.gallery.GetUserExhibitedAssetsResponse.data:type_name -> topfans.gallery.ExhibitedAssetsData
|
||||
0, // 20: topfans.gallery.GalleryService.GetMyGallery:input_type -> topfans.gallery.GetMyGalleryRequest
|
||||
2, // 21: topfans.gallery.GalleryService.GetUserGallery:input_type -> topfans.gallery.GetUserGalleryRequest
|
||||
4, // 22: topfans.gallery.GalleryService.PlaceAsset:input_type -> topfans.gallery.PlaceAssetRequest
|
||||
6, // 23: topfans.gallery.GalleryService.UnlockSlot:input_type -> topfans.gallery.UnlockSlotRequest
|
||||
14, // 24: topfans.gallery.GalleryService.RemoveFromSlot:input_type -> topfans.gallery.RemoveFromSlotRequest
|
||||
16, // 25: topfans.gallery.GalleryService.GetMyExhibitedAssets:input_type -> topfans.gallery.GetMyExhibitedAssetsRequest
|
||||
20, // 26: topfans.gallery.GalleryService.GetInspirationFlow:input_type -> topfans.gallery.GetInspirationFlowRequest
|
||||
24, // 27: topfans.gallery.GalleryService.GetUserExhibitedAssets:input_type -> topfans.gallery.GetUserExhibitedAssetsRequest
|
||||
1, // 28: topfans.gallery.GalleryService.GetMyGallery:output_type -> topfans.gallery.GetMyGalleryResponse
|
||||
3, // 29: topfans.gallery.GalleryService.GetUserGallery:output_type -> topfans.gallery.GetUserGalleryResponse
|
||||
5, // 30: topfans.gallery.GalleryService.PlaceAsset:output_type -> topfans.gallery.PlaceAssetResponse
|
||||
7, // 31: topfans.gallery.GalleryService.UnlockSlot:output_type -> topfans.gallery.UnlockSlotResponse
|
||||
15, // 32: topfans.gallery.GalleryService.RemoveFromSlot:output_type -> topfans.gallery.RemoveFromSlotResponse
|
||||
17, // 33: topfans.gallery.GalleryService.GetMyExhibitedAssets:output_type -> topfans.gallery.GetMyExhibitedAssetsResponse
|
||||
21, // 34: topfans.gallery.GalleryService.GetInspirationFlow:output_type -> topfans.gallery.GetInspirationFlowResponse
|
||||
25, // 35: topfans.gallery.GalleryService.GetUserExhibitedAssets:output_type -> topfans.gallery.GetUserExhibitedAssetsResponse
|
||||
28, // [28:36] is the sub-list for method output_type
|
||||
20, // [20:28] is the sub-list for method input_type
|
||||
20, // [20:20] is the sub-list for extension type_name
|
||||
20, // [20:20] is the sub-list for extension extendee
|
||||
0, // [0:20] is the sub-list for field type_name
|
||||
28, // 11: topfans.gallery.RemoveFromSlotResponse.base:type_name -> topfans.common.BaseResponse
|
||||
28, // 12: topfans.gallery.RemoveExhibitionByAssetResponse.base:type_name -> topfans.common.BaseResponse
|
||||
28, // 13: topfans.gallery.GetMyExhibitedAssetsResponse.base:type_name -> topfans.common.BaseResponse
|
||||
20, // 14: topfans.gallery.GetMyExhibitedAssetsResponse.data:type_name -> topfans.gallery.ExhibitedAssetsData
|
||||
21, // 15: topfans.gallery.ExhibitedAssetsData.items:type_name -> topfans.gallery.ExhibitedAssetItem
|
||||
28, // 16: topfans.gallery.GetInspirationFlowResponse.base:type_name -> topfans.common.BaseResponse
|
||||
24, // 17: topfans.gallery.GetInspirationFlowResponse.data:type_name -> topfans.gallery.InspirationFlowData
|
||||
25, // 18: topfans.gallery.InspirationFlowData.items:type_name -> topfans.gallery.InspirationFlowItem
|
||||
28, // 19: topfans.gallery.GetUserExhibitedAssetsResponse.base:type_name -> topfans.common.BaseResponse
|
||||
20, // 20: topfans.gallery.GetUserExhibitedAssetsResponse.data:type_name -> topfans.gallery.ExhibitedAssetsData
|
||||
0, // 21: topfans.gallery.GalleryService.GetMyGallery:input_type -> topfans.gallery.GetMyGalleryRequest
|
||||
2, // 22: topfans.gallery.GalleryService.GetUserGallery:input_type -> topfans.gallery.GetUserGalleryRequest
|
||||
4, // 23: topfans.gallery.GalleryService.PlaceAsset:input_type -> topfans.gallery.PlaceAssetRequest
|
||||
6, // 24: topfans.gallery.GalleryService.UnlockSlot:input_type -> topfans.gallery.UnlockSlotRequest
|
||||
14, // 25: topfans.gallery.GalleryService.RemoveFromSlot:input_type -> topfans.gallery.RemoveFromSlotRequest
|
||||
16, // 26: topfans.gallery.GalleryService.RemoveExhibitionByAsset:input_type -> topfans.gallery.RemoveExhibitionByAssetRequest
|
||||
18, // 27: topfans.gallery.GalleryService.GetMyExhibitedAssets:input_type -> topfans.gallery.GetMyExhibitedAssetsRequest
|
||||
22, // 28: topfans.gallery.GalleryService.GetInspirationFlow:input_type -> topfans.gallery.GetInspirationFlowRequest
|
||||
26, // 29: topfans.gallery.GalleryService.GetUserExhibitedAssets:input_type -> topfans.gallery.GetUserExhibitedAssetsRequest
|
||||
1, // 30: topfans.gallery.GalleryService.GetMyGallery:output_type -> topfans.gallery.GetMyGalleryResponse
|
||||
3, // 31: topfans.gallery.GalleryService.GetUserGallery:output_type -> topfans.gallery.GetUserGalleryResponse
|
||||
5, // 32: topfans.gallery.GalleryService.PlaceAsset:output_type -> topfans.gallery.PlaceAssetResponse
|
||||
7, // 33: topfans.gallery.GalleryService.UnlockSlot:output_type -> topfans.gallery.UnlockSlotResponse
|
||||
15, // 34: topfans.gallery.GalleryService.RemoveFromSlot:output_type -> topfans.gallery.RemoveFromSlotResponse
|
||||
17, // 35: topfans.gallery.GalleryService.RemoveExhibitionByAsset:output_type -> topfans.gallery.RemoveExhibitionByAssetResponse
|
||||
19, // 36: topfans.gallery.GalleryService.GetMyExhibitedAssets:output_type -> topfans.gallery.GetMyExhibitedAssetsResponse
|
||||
23, // 37: topfans.gallery.GalleryService.GetInspirationFlow:output_type -> topfans.gallery.GetInspirationFlowResponse
|
||||
27, // 38: topfans.gallery.GalleryService.GetUserExhibitedAssets:output_type -> topfans.gallery.GetUserExhibitedAssetsResponse
|
||||
30, // [30:39] is the sub-list for method output_type
|
||||
21, // [21:30] is the sub-list for method input_type
|
||||
21, // [21:21] is the sub-list for extension type_name
|
||||
21, // [21:21] is the sub-list for extension extendee
|
||||
0, // [0:21] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_gallery_proto_init() }
|
||||
@ -1858,7 +1958,7 @@ func file_gallery_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_gallery_proto_rawDesc), len(file_gallery_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 26,
|
||||
NumMessages: 28,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-triple. DO NOT EDIT.
|
||||
//
|
||||
// Source: proto/gallery.proto
|
||||
// Source: gallery.proto
|
||||
package gallery
|
||||
|
||||
import (
|
||||
@ -46,6 +46,8 @@ const (
|
||||
GalleryServiceUnlockSlotProcedure = "/topfans.gallery.GalleryService/UnlockSlot"
|
||||
// GalleryServiceRemoveFromSlotProcedure is the fully-qualified name of the GalleryService's RemoveFromSlot RPC.
|
||||
GalleryServiceRemoveFromSlotProcedure = "/topfans.gallery.GalleryService/RemoveFromSlot"
|
||||
// GalleryServiceRemoveExhibitionByAssetProcedure is the fully-qualified name of the GalleryService's RemoveExhibitionByAsset RPC.
|
||||
GalleryServiceRemoveExhibitionByAssetProcedure = "/topfans.gallery.GalleryService/RemoveExhibitionByAsset"
|
||||
// GalleryServiceGetMyExhibitedAssetsProcedure is the fully-qualified name of the GalleryService's GetMyExhibitedAssets RPC.
|
||||
GalleryServiceGetMyExhibitedAssetsProcedure = "/topfans.gallery.GalleryService/GetMyExhibitedAssets"
|
||||
// GalleryServiceGetInspirationFlowProcedure is the fully-qualified name of the GalleryService's GetInspirationFlow RPC.
|
||||
@ -65,6 +67,7 @@ type GalleryService interface {
|
||||
PlaceAsset(ctx context.Context, req *PlaceAssetRequest, opts ...client.CallOption) (*PlaceAssetResponse, error)
|
||||
UnlockSlot(ctx context.Context, req *UnlockSlotRequest, opts ...client.CallOption) (*UnlockSlotResponse, error)
|
||||
RemoveFromSlot(ctx context.Context, req *RemoveFromSlotRequest, opts ...client.CallOption) (*RemoveFromSlotResponse, error)
|
||||
RemoveExhibitionByAsset(ctx context.Context, req *RemoveExhibitionByAssetRequest, opts ...client.CallOption) (*RemoveExhibitionByAssetResponse, error)
|
||||
GetMyExhibitedAssets(ctx context.Context, req *GetMyExhibitedAssetsRequest, opts ...client.CallOption) (*GetMyExhibitedAssetsResponse, error)
|
||||
GetInspirationFlow(ctx context.Context, req *GetInspirationFlowRequest, opts ...client.CallOption) (*GetInspirationFlowResponse, error)
|
||||
GetUserExhibitedAssets(ctx context.Context, req *GetUserExhibitedAssetsRequest, opts ...client.CallOption) (*GetUserExhibitedAssetsResponse, error)
|
||||
@ -130,6 +133,14 @@ func (c *GalleryServiceImpl) RemoveFromSlot(ctx context.Context, req *RemoveFrom
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *GalleryServiceImpl) RemoveExhibitionByAsset(ctx context.Context, req *RemoveExhibitionByAssetRequest, opts ...client.CallOption) (*RemoveExhibitionByAssetResponse, error) {
|
||||
resp := new(RemoveExhibitionByAssetResponse)
|
||||
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "RemoveExhibitionByAsset", opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *GalleryServiceImpl) GetMyExhibitedAssets(ctx context.Context, req *GetMyExhibitedAssetsRequest, opts ...client.CallOption) (*GetMyExhibitedAssetsResponse, error) {
|
||||
resp := new(GetMyExhibitedAssetsResponse)
|
||||
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "GetMyExhibitedAssets", opts...); err != nil {
|
||||
@ -156,7 +167,7 @@ func (c *GalleryServiceImpl) GetUserExhibitedAssets(ctx context.Context, req *Ge
|
||||
|
||||
var GalleryService_ClientInfo = client.ClientInfo{
|
||||
InterfaceName: "topfans.gallery.GalleryService",
|
||||
MethodNames: []string{"GetMyGallery", "GetUserGallery", "PlaceAsset", "UnlockSlot", "RemoveFromSlot", "GetMyExhibitedAssets", "GetInspirationFlow", "GetUserExhibitedAssets"},
|
||||
MethodNames: []string{"GetMyGallery", "GetUserGallery", "PlaceAsset", "UnlockSlot", "RemoveFromSlot", "RemoveExhibitionByAsset", "GetMyExhibitedAssets", "GetInspirationFlow", "GetUserExhibitedAssets"},
|
||||
ConnectionInjectFunc: func(dubboCliRaw interface{}, conn *client.Connection) {
|
||||
dubboCli := dubboCliRaw.(*GalleryServiceImpl)
|
||||
dubboCli.conn = conn
|
||||
@ -170,6 +181,7 @@ type GalleryServiceHandler interface {
|
||||
PlaceAsset(context.Context, *PlaceAssetRequest) (*PlaceAssetResponse, error)
|
||||
UnlockSlot(context.Context, *UnlockSlotRequest) (*UnlockSlotResponse, error)
|
||||
RemoveFromSlot(context.Context, *RemoveFromSlotRequest) (*RemoveFromSlotResponse, error)
|
||||
RemoveExhibitionByAsset(context.Context, *RemoveExhibitionByAssetRequest) (*RemoveExhibitionByAssetResponse, error)
|
||||
GetMyExhibitedAssets(context.Context, *GetMyExhibitedAssetsRequest) (*GetMyExhibitedAssetsResponse, error)
|
||||
GetInspirationFlow(context.Context, *GetInspirationFlowRequest) (*GetInspirationFlowResponse, error)
|
||||
GetUserExhibitedAssets(context.Context, *GetUserExhibitedAssetsRequest) (*GetUserExhibitedAssetsResponse, error)
|
||||
@ -262,6 +274,21 @@ var GalleryService_ServiceInfo = server.ServiceInfo{
|
||||
return triple_protocol.NewResponse(res), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "RemoveExhibitionByAsset",
|
||||
Type: constant.CallUnary,
|
||||
ReqInitFunc: func() interface{} {
|
||||
return new(RemoveExhibitionByAssetRequest)
|
||||
},
|
||||
MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) {
|
||||
req := args[0].(*RemoveExhibitionByAssetRequest)
|
||||
res, err := handler.(GalleryServiceHandler).RemoveExhibitionByAsset(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return triple_protocol.NewResponse(res), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "GetMyExhibitedAssets",
|
||||
Type: constant.CallUnary,
|
||||
|
||||
@ -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"`
|
||||
@ -1491,6 +1451,8 @@ type ClaimExhibitionRevenueResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
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"`
|
||||
CrystalAmount int64 `protobuf:"varint,3,opt,name=crystal_amount,json=crystalAmount,proto3" json:"crystal_amount,omitempty"` // 当前领取的收益金额
|
||||
TotalBalance int64 `protobuf:"varint,4,opt,name=total_balance,json=totalBalance,proto3" json:"total_balance,omitempty"` // 领取后的当前用户全部余额
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@ -1539,6 +1501,20 @@ func (x *ClaimExhibitionRevenueResponse) GetSuccess() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *ClaimExhibitionRevenueResponse) GetCrystalAmount() int64 {
|
||||
if x != nil {
|
||||
return x.CrystalAmount
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ClaimExhibitionRevenueResponse) GetTotalBalance() int64 {
|
||||
if x != nil {
|
||||
return x.TotalBalance
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type ClaimAllExhibitionRevenueRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
StarId int64 `protobuf:"varint,1,opt,name=star_id,json=starId,proto3" json:"star_id,omitempty"`
|
||||
@ -1904,17 +1880,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 +1907,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 +1953,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" +
|
||||
@ -2022,10 +1985,12 @@ const file_task_proto_rawDesc = "" +
|
||||
"\x1dClaimExhibitionRevenueRequest\x12\x1d\n" +
|
||||
"\n" +
|
||||
"revenue_id\x18\x01 \x01(\x03R\trevenueId\x12\x17\n" +
|
||||
"\astar_id\x18\x02 \x01(\x03R\x06starId\"l\n" +
|
||||
"\astar_id\x18\x02 \x01(\x03R\x06starId\"\xb8\x01\n" +
|
||||
"\x1eClaimExhibitionRevenueResponse\x120\n" +
|
||||
"\x04base\x18\x01 \x01(\v2\x1c.topfans.common.BaseResponseR\x04base\x12\x18\n" +
|
||||
"\asuccess\x18\x02 \x01(\bR\asuccess\";\n" +
|
||||
"\asuccess\x18\x02 \x01(\bR\asuccess\x12%\n" +
|
||||
"\x0ecrystal_amount\x18\x03 \x01(\x03R\rcrystalAmount\x12#\n" +
|
||||
"\rtotal_balance\x18\x04 \x01(\x03R\ftotalBalance\";\n" +
|
||||
" ClaimAllExhibitionRevenueRequest\x12\x17\n" +
|
||||
"\astar_id\x18\x01 \x01(\x03R\x06starId\"z\n" +
|
||||
"!ClaimAllExhibitionRevenueResponse\x120\n" +
|
||||
@ -2050,7 +2015,7 @@ const file_task_proto_rawDesc = "" +
|
||||
"\texpire_at\x18\t \x01(\x03R\bexpireAt\"}\n" +
|
||||
"\x1dOnExhibitionCompletedResponse\x120\n" +
|
||||
"\x04base\x18\x01 \x01(\v2\x1c.topfans.common.BaseResponseR\x04base\x12*\n" +
|
||||
"\x11revenue_record_id\x18\x02 \x01(\x03R\x0frevenueRecordId2\xbd\f\n" +
|
||||
"\x11revenue_record_id\x18\x02 \x01(\x03R\x0frevenueRecordId2\xc6\f\n" +
|
||||
"\x11TaskMobileService\x12r\n" +
|
||||
"\rGetDailyTasks\x12\".topfans.task.GetDailyTasksRequest\x1a#.topfans.task.GetDailyTasksResponse\"\x18\x82\xd3\xe4\x93\x02\x12\x12\x10/api/tasks/daily\x12v\n" +
|
||||
"\vReportEvent\x12 .topfans.task.ReportEventRequest\x1a!.topfans.task.ReportEventResponse\"\"\x82\xd3\xe4\x93\x02\x1c:\x01*\"\x17/api/tasks/report-event\x12~\n" +
|
||||
@ -2059,10 +2024,10 @@ const file_task_proto_rawDesc = "" +
|
||||
"\rCompleteGuide\x12\".topfans.task.CompleteGuideRequest\x1a#.topfans.task.CompleteGuideResponse\"$\x82\xd3\xe4\x93\x02\x1e:\x01*\"\x19/api/tasks/guide/complete\x12\x90\x01\n" +
|
||||
"\x13GetOnboardingStatus\x12(.topfans.task.GetOnboardingStatusRequest\x1a).topfans.task.GetOnboardingStatusResponse\"$\x82\xd3\xe4\x93\x02\x1e\x12\x1c/api/tasks/onboarding/status\x12\x85\x01\n" +
|
||||
"\fAdvanceStage\x12!.topfans.task.AdvanceStageRequest\x1a\".topfans.task.AdvanceStageResponse\".\x82\xd3\xe4\x93\x02(:\x01*\"#/api/tasks/onboarding/advance-stage\x12\x9f\x01\n" +
|
||||
"\x15ClaimOnboardingReward\x12*.topfans.task.ClaimOnboardingRewardRequest\x1a+.topfans.task.ClaimOnboardingRewardResponse\"-\x82\xd3\xe4\x93\x02':\x01*\"\"/api/tasks/onboarding/claim-reward\x12\x94\x01\n" +
|
||||
"\x14GetExhibitionRevenue\x12).topfans.task.GetExhibitionRevenueRequest\x1a*.topfans.task.GetExhibitionRevenueResponse\"%\x82\xd3\xe4\x93\x02\x1f\x12\x1d/api/tasks/exhibition-revenue\x12\xa3\x01\n" +
|
||||
"\x16ClaimExhibitionRevenue\x12+.topfans.task.ClaimExhibitionRevenueRequest\x1a,.topfans.task.ClaimExhibitionRevenueResponse\".\x82\xd3\xe4\x93\x02(:\x01*\"#/api/tasks/exhibition-revenue/claim\x12\xb0\x01\n" +
|
||||
"\x19ClaimAllExhibitionRevenue\x12..topfans.task.ClaimAllExhibitionRevenueRequest\x1a/.topfans.task.ClaimAllExhibitionRevenueResponse\"2\x82\xd3\xe4\x93\x02,:\x01*\"'/api/tasks/exhibition-revenue/claim-all2\xe1\x01\n" +
|
||||
"\x15ClaimOnboardingReward\x12*.topfans.task.ClaimOnboardingRewardRequest\x1a+.topfans.task.ClaimOnboardingRewardResponse\"-\x82\xd3\xe4\x93\x02':\x01*\"\"/api/tasks/onboarding/claim-reward\x12\x97\x01\n" +
|
||||
"\x14GetExhibitionRevenue\x12).topfans.task.GetExhibitionRevenueRequest\x1a*.topfans.task.GetExhibitionRevenueResponse\"(\x82\xd3\xe4\x93\x02\"\x12 /api/v1/tasks/exhibition-revenue\x12\xa6\x01\n" +
|
||||
"\x16ClaimExhibitionRevenue\x12+.topfans.task.ClaimExhibitionRevenueRequest\x1a,.topfans.task.ClaimExhibitionRevenueResponse\"1\x82\xd3\xe4\x93\x02+:\x01*\"&/api/v1/tasks/exhibition-revenue/claim\x12\xb3\x01\n" +
|
||||
"\x19ClaimAllExhibitionRevenue\x12..topfans.task.ClaimAllExhibitionRevenueRequest\x1a/.topfans.task.ClaimAllExhibitionRevenueResponse\"5\x82\xd3\xe4\x93\x02/:\x01*\"*/api/v1/tasks/exhibition-revenue/claim-all2\xe1\x01\n" +
|
||||
"\x13TaskInternalService\x12X\n" +
|
||||
"\rInitUserTasks\x12\".topfans.task.InitUserTasksRequest\x1a#.topfans.task.InitUserTasksResponse\x12p\n" +
|
||||
"\x15OnExhibitionCompleted\x12*.topfans.task.OnExhibitionCompletedRequest\x1a+.topfans.task.OnExhibitionCompletedResponseB0Z.github.com/topfans/backend/pkg/proto/task;taskb\x06proto3"
|
||||
|
||||
@ -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,31 @@ 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"` // 本次展出的时长(小时)
|
||||
SourceId string `protobuf:"bytes,4,opt,name=source_id,json=sourceId,proto3" json:"source_id,omitempty"` // 关联业务ID,用于升级奖励流水的溯源
|
||||
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 +1778,64 @@ 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 {
|
||||
func (x *AddExhibitionHoursRequest) GetSourceId() string {
|
||||
if x != nil {
|
||||
return x.SourceId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// 增加累计上架时长响应
|
||||
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 +1847,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 +2869,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 +2878,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 +2976,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 +2995,18 @@ 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\"\x95\x01\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\x12\x1b\n" +
|
||||
"\tsource_id\x18\x04 \x01(\tR\bsourceId\"\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 +3068,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 +3081,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 +3134,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 +3177,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 +3212,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 +3234,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
|
||||
}
|
||||
|
||||
@ -35,6 +35,8 @@ message Asset {
|
||||
bool is_liked = 20; // 当前用户是否已点赞
|
||||
string info = 21; // 藏品信息
|
||||
int32 display_status = 22; // 展示状态:0=待展示, 1=已展示
|
||||
int64 earnings = 23; // 当前展出收益(实时计算,仅展出中时有值)
|
||||
int64 exhibition_expire_at = 24; // 展出过期时间(毫秒时间戳,仅展出中时有值,0=未展出)
|
||||
}
|
||||
|
||||
// 持有者信息
|
||||
|
||||
@ -46,6 +46,9 @@ service GalleryService {
|
||||
};
|
||||
}
|
||||
|
||||
// 内部RPC:根据资产ID移除展品(用于领取收益后下架)
|
||||
rpc RemoveExhibitionByAsset(RemoveExhibitionByAssetRequest) returns (RemoveExhibitionByAssetResponse);
|
||||
|
||||
// ========== 我的作品相关 ==========
|
||||
|
||||
// 获取我展出的作品列表
|
||||
@ -162,6 +165,16 @@ message RemoveFromSlotResponse {
|
||||
topfans.common.BaseResponse base = 1;
|
||||
}
|
||||
|
||||
// 根据资产ID移除展品请求
|
||||
message RemoveExhibitionByAssetRequest {
|
||||
int64 asset_id = 1; // 资产ID(必填)
|
||||
}
|
||||
|
||||
// 根据资产ID移除展品响应
|
||||
message RemoveExhibitionByAssetResponse {
|
||||
topfans.common.BaseResponse base = 1;
|
||||
}
|
||||
|
||||
// ==================== 我的作品相关消息 ====================
|
||||
|
||||
// 获取我展出的作品列表请求
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
// ==================== 展示收益 ====================
|
||||
@ -165,6 +160,8 @@ message ClaimExhibitionRevenueRequest {
|
||||
message ClaimExhibitionRevenueResponse {
|
||||
topfans.common.BaseResponse base = 1;
|
||||
bool success = 2;
|
||||
int64 crystal_amount = 3; // 当前领取的收益金额
|
||||
int64 total_balance = 4; // 领取后的当前用户全部余额
|
||||
}
|
||||
|
||||
message ClaimAllExhibitionRevenueRequest {
|
||||
@ -233,13 +230,13 @@ service TaskMobileService {
|
||||
option (google.api.http) = { post: "/api/tasks/onboarding/claim-reward"; body: "*"; };
|
||||
}
|
||||
rpc GetExhibitionRevenue(GetExhibitionRevenueRequest) returns (GetExhibitionRevenueResponse) {
|
||||
option (google.api.http) = { get: "/api/tasks/exhibition-revenue"; };
|
||||
option (google.api.http) = { get: "/api/v1/tasks/exhibition-revenue"; };
|
||||
}
|
||||
rpc ClaimExhibitionRevenue(ClaimExhibitionRevenueRequest) returns (ClaimExhibitionRevenueResponse) {
|
||||
option (google.api.http) = { post: "/api/tasks/exhibition-revenue/claim"; body: "*"; };
|
||||
option (google.api.http) = { post: "/api/v1/tasks/exhibition-revenue/claim"; body: "*"; };
|
||||
}
|
||||
rpc ClaimAllExhibitionRevenue(ClaimAllExhibitionRevenueRequest) returns (ClaimAllExhibitionRevenueResponse) {
|
||||
option (google.api.http) = { post: "/api/tasks/exhibition-revenue/claim-all"; body: "*"; };
|
||||
option (google.api.http) = { post: "/api/v1/tasks/exhibition-revenue/claim-all"; body: "*"; };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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,20 @@ 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; // 本次展出的时长(小时)
|
||||
string source_id = 4; // 关联业务ID,用于升级奖励流水的溯源
|
||||
}
|
||||
|
||||
// 增加经验值响应
|
||||
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 +419,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()
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
-- ============================================
|
||||
-- 展览表索引和外键修改
|
||||
-- 移除 asset_id 的唯一约束,改为普通索引
|
||||
-- 执行方式: psql -h <host> -U <user> -d <db> -f backend/scripts/migrate_exhibition_remove_unique_constraint.sql
|
||||
-- 创建日期: 2026-05-15
|
||||
-- ============================================
|
||||
|
||||
-- 1. 移除 asset_id 的唯一约束
|
||||
ALTER TABLE exhibitions DROP CONSTRAINT IF EXISTS uk_asset;
|
||||
|
||||
-- 2. 添加普通索引(如果不存在)
|
||||
CREATE INDEX IF NOT EXISTS idx_asset ON exhibitions(asset_id);
|
||||
|
||||
-- 3. 确认修改结果
|
||||
DO $$
|
||||
BEGIN
|
||||
-- 检查唯一约束是否已移除
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint WHERE conname = 'uk_asset'
|
||||
) THEN
|
||||
RAISE NOTICE '唯一约束 uk_asset 已成功移除';
|
||||
ELSE
|
||||
RAISE WARNING '唯一约束 uk_asset 仍然存在';
|
||||
END IF;
|
||||
|
||||
-- 检查索引是否已创建
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM pg_indexes WHERE indexname = 'idx_asset'
|
||||
) THEN
|
||||
RAISE NOTICE '索引 idx_asset 已成功创建';
|
||||
ELSE
|
||||
RAISE WARNING '索引 idx_asset 创建失败';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- ============================================
|
||||
-- 完成
|
||||
-- ============================================
|
||||
@ -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")
|
||||
|
||||
@ -50,6 +50,12 @@ type AssetRepository interface {
|
||||
|
||||
// IsExhibiting 检查资产是否正在展出中
|
||||
IsExhibiting(assetID int64) (bool, error)
|
||||
|
||||
// GetExhibitionStartTime 获取资产展出开始时间(如果正在展出)
|
||||
GetExhibitionStartTime(assetID int64) (int64, error)
|
||||
|
||||
// GetExhibitionExpireTime 获取资产展出过期时间(如果正在展出)
|
||||
GetExhibitionExpireTime(assetID int64) (int64, error)
|
||||
}
|
||||
|
||||
// assetRepository 资产Repository实现
|
||||
@ -336,3 +342,43 @@ func (r *assetRepository) IsExhibiting(assetID int64) (bool, error) {
|
||||
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
// GetExhibitionStartTime 获取资产展出开始时间(如果正在展出)
|
||||
func (r *assetRepository) GetExhibitionStartTime(assetID int64) (int64, error) {
|
||||
if assetID <= 0 {
|
||||
return 0, errors.New("asset_id must be greater than 0")
|
||||
}
|
||||
|
||||
var exhibition models.Exhibition
|
||||
err := r.db.Where("asset_id = ? AND expire_at > ?", assetID, time.Now().UnixMilli()).
|
||||
First(&exhibition).Error
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return 0, nil // 未展出
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return exhibition.StartTime, nil
|
||||
}
|
||||
|
||||
// GetExhibitionExpireTime 获取资产展出过期时间(如果正在展出)
|
||||
func (r *assetRepository) GetExhibitionExpireTime(assetID int64) (int64, error) {
|
||||
if assetID <= 0 {
|
||||
return 0, errors.New("asset_id must be greater than 0")
|
||||
}
|
||||
|
||||
var exhibition models.Exhibition
|
||||
err := r.db.Where("asset_id = ? AND expire_at > ?", assetID, time.Now().UnixMilli()).
|
||||
First(&exhibition).Error
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return 0, nil // 未展出
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return exhibition.ExpireAt, nil
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -27,7 +27,15 @@ type UserRPCClient interface {
|
||||
GetFanProfile(userID, starID int64) (*FanProfile, error)
|
||||
|
||||
// UpdateCrystalBalance 更新水晶余额(返回更新后的余额)
|
||||
UpdateCrystalBalance(userID, starID int64, delta int64) (int64, error)
|
||||
// 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 增加用户累计上架时长
|
||||
// sourceID: 关联业务ID,用于升级奖励流水的溯源
|
||||
// 返回: newLevel, levelDelta, crystalReward, error
|
||||
AddExhibitionHours(userID, starID int64, hours int64, sourceID string) (int32, int32, int64, error)
|
||||
}
|
||||
|
||||
// userRPCClient User Service RPC客户端实现
|
||||
@ -97,18 +105,22 @@ func (c *userRPCClient) GetFanProfile(userID, starID int64) (*FanProfile, error)
|
||||
}
|
||||
|
||||
// UpdateCrystalBalance 更新水晶余额(返回更新后的余额)
|
||||
func (c *userRPCClient) UpdateCrystalBalance(userID, starID int64, delta int64) (int64, error) {
|
||||
func (c *userRPCClient) UpdateCrystalBalance(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),
|
||||
zap.String("change_type", changeType),
|
||||
)
|
||||
|
||||
ctx := context.Background()
|
||||
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 {
|
||||
@ -144,3 +156,56 @@ func (c *userRPCClient) UpdateCrystalBalance(userID, starID int64, delta int64)
|
||||
|
||||
return resp.NewBalance, nil
|
||||
}
|
||||
|
||||
// AddExhibitionHours 增加用户累计上架时长
|
||||
// sourceID: 关联业务ID,用于升级奖励流水的溯源(本参数暂未透传到RPC,仅Go层保留)
|
||||
func (c *userRPCClient) AddExhibitionHours(userID, starID int64, hours int64, sourceID string) (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,
|
||||
SourceId: sourceID,
|
||||
})
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -134,6 +134,6 @@ func GetExhibitionDuration() int64 {
|
||||
zap.Error(err))
|
||||
return 14400
|
||||
}
|
||||
// 数据库存储的是小时,转换为秒
|
||||
return int64(config.ConfigValue) * 3600
|
||||
// 数据库存储的是小时,转换为秒(先乘后转换,避免 int64(0.10)=0 的截断问题)
|
||||
return int64(config.ConfigValue * 3600)
|
||||
}
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
@ -97,9 +99,28 @@ func main() {
|
||||
|
||||
// 创建 Repository 层实例
|
||||
db := database.GetDB()
|
||||
// 自动迁移展馆相关表(booth_slots / exhibitions)
|
||||
if err := db.AutoMigrate(&models.BoothSlot{}, &models.Exhibition{}); err != nil {
|
||||
logger.Logger.Fatal(fmt.Sprintf("Failed to migrate gallery tables: %v", err))
|
||||
|
||||
// 安全迁移:只在表不存在时才创建表,不自动重建
|
||||
// 这样可以避免 Gorm AutoMigrate 在检测到索引不匹配时重建表导致数据丢失
|
||||
if !db.Migrator().HasTable(&models.Exhibition{}) {
|
||||
logger.Logger.Info("Exhibitions table does not exist, creating...")
|
||||
if err := db.AutoMigrate(&models.Exhibition{}); err != nil {
|
||||
logger.Logger.Fatal(fmt.Sprintf("Failed to migrate Exhibition table: %v", err))
|
||||
}
|
||||
logger.Logger.Info("Exhibition table created successfully")
|
||||
} else {
|
||||
logger.Logger.Info("Exhibitions table already exists, skipping AutoMigrate to preserve data")
|
||||
}
|
||||
|
||||
// 同样处理 booth_slots 表
|
||||
if !db.Migrator().HasTable(&models.BoothSlot{}) {
|
||||
logger.Logger.Info("BoothSlots table does not exist, creating...")
|
||||
if err := db.AutoMigrate(&models.BoothSlot{}); err != nil {
|
||||
logger.Logger.Fatal(fmt.Sprintf("Failed to migrate BoothSlot table: %v", err))
|
||||
}
|
||||
logger.Logger.Info("BoothSlot table created successfully")
|
||||
} else {
|
||||
logger.Logger.Info("BoothSlots table already exists, skipping AutoMigrate to preserve data")
|
||||
}
|
||||
|
||||
galleryRepo := repository.NewGalleryRepository(db)
|
||||
@ -136,14 +157,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")
|
||||
|
||||
|
||||
@ -421,6 +421,41 @@ func (p *GalleryProvider) GetUserExhibitedAssets(ctx context.Context, req *pb.Ge
|
||||
return p.exhibitionService.GetUserExhibitedAssets(ctx, userID, starID, req)
|
||||
}
|
||||
|
||||
// RemoveExhibitionByAsset 根据资产ID移除展品(内部RPC,用于领取收益后下架)
|
||||
func (p *GalleryProvider) RemoveExhibitionByAsset(ctx context.Context, req *pb.RemoveExhibitionByAssetRequest) (*pb.RemoveExhibitionByAssetResponse, error) {
|
||||
logger.Logger.Info("Received RemoveExhibitionByAsset request",
|
||||
zap.Int64("asset_id", req.AssetId),
|
||||
)
|
||||
|
||||
// 调用Service层
|
||||
err := p.exhibitionService.RemoveExhibitionByAsset(ctx, req.AssetId)
|
||||
if err != nil {
|
||||
logger.Logger.Error("RemoveExhibitionByAsset failed",
|
||||
zap.Int64("asset_id", req.AssetId),
|
||||
zap.Error(err),
|
||||
)
|
||||
return &pb.RemoveExhibitionByAssetResponse{
|
||||
Base: &pbCommon.BaseResponse{
|
||||
Code: appErrors.ToStatusCode(err),
|
||||
Message: err.Error(),
|
||||
Timestamp: 0,
|
||||
},
|
||||
}, err
|
||||
}
|
||||
|
||||
logger.Logger.Info("RemoveExhibitionByAsset successful",
|
||||
zap.Int64("asset_id", req.AssetId),
|
||||
)
|
||||
|
||||
return &pb.RemoveExhibitionByAssetResponse{
|
||||
Base: &pbCommon.BaseResponse{
|
||||
Code: pbCommon.StatusCode_STATUS_OK,
|
||||
Message: "success",
|
||||
Timestamp: 0,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ==================== 辅助函数 ====================
|
||||
|
||||
// extractUserInfoFromDubboAttachments 从Dubbo attachments提取用户信息
|
||||
|
||||
@ -23,6 +23,7 @@ type GalleryRepository interface {
|
||||
// 展品相关
|
||||
GetExhibitionByAsset(assetID int64) (*models.Exhibition, error)
|
||||
GetExhibitionBySlot(slotID int64) (*models.Exhibition, error)
|
||||
GetActiveExhibitionBySlot(slotID, now int64) (*models.Exhibition, error)
|
||||
GetExhibitionsByUser(userID, starID int64) ([]*models.Exhibition, error)
|
||||
CreateExhibition(exhibition *models.Exhibition) error
|
||||
DeleteExhibition(exhibitionID int64) error
|
||||
@ -37,6 +38,8 @@ type GalleryRepository interface {
|
||||
PlaceExhibitionTx(exhibition *models.Exhibition, displayStatus int32) error
|
||||
// 事务性操作:删除展品并更新展示状态(原子操作)
|
||||
RemoveExhibitionTx(exhibitionID int64, assetID int64) error
|
||||
// 更新展览过期时间(用于清理后防止重复处理)
|
||||
UpdateExhibitionExpireAt(exhibitionID int64, expireAt int64) error
|
||||
|
||||
// ========== 我的作品相关 ==========
|
||||
|
||||
@ -70,6 +73,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 灵感瀑布展品项
|
||||
@ -224,6 +230,19 @@ func (r *galleryRepository) GetExhibitionBySlot(slotID int64) (*models.Exhibitio
|
||||
return &exhibition, nil
|
||||
}
|
||||
|
||||
// GetActiveExhibitionBySlot 根据展位ID获取活跃展品展示记录(未删除且未过期)
|
||||
func (r *galleryRepository) GetActiveExhibitionBySlot(slotID, now int64) (*models.Exhibition, error) {
|
||||
var exhibition models.Exhibition
|
||||
err := r.db.Where("slot_id = ? AND deleted_at IS NULL AND expire_at > ?", slotID, now).First(&exhibition).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &exhibition, nil
|
||||
}
|
||||
|
||||
// GetExhibitionsByUser 获取用户的所有展品展示记录(不含已删除)
|
||||
func (r *galleryRepository) GetExhibitionsByUser(userID, starID int64) ([]*models.Exhibition, error) {
|
||||
var exhibitions []*models.Exhibition
|
||||
@ -232,24 +251,27 @@ func (r *galleryRepository) GetExhibitionsByUser(userID, starID int64) ([]*model
|
||||
return exhibitions, err
|
||||
}
|
||||
|
||||
// CreateExhibition 创建展品展示记录(支持软删除后重新展出)
|
||||
// asset_id 唯一索引冲突时:若旧记录已软删除则物理删除后重新插入;否则报错
|
||||
// CreateExhibition 创建展品展示记录
|
||||
// 允许多个 asset_id 记录,但同一 asset_id 只能有一个未删除且未过期的展出记录
|
||||
func (r *galleryRepository) CreateExhibition(exhibition *models.Exhibition) error {
|
||||
now := time.Now().UnixMilli()
|
||||
|
||||
// 检查是否有未删除且未过期的同一 asset_id 记录
|
||||
var count int64
|
||||
err := r.db.Model(&models.Exhibition{}).
|
||||
Where("asset_id = ? AND deleted_at IS NULL AND expire_at > ?", exhibition.AssetID, now).
|
||||
Count(&count).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return errors.New("该藏品正在展出中,无法重复放置")
|
||||
}
|
||||
|
||||
exhibition.CreatedAt = now
|
||||
exhibition.UpdatedAt = now
|
||||
exhibition.DeletedAt = nil
|
||||
|
||||
// 在事务中:先物理删除已软删除的旧记录,再插入新记录
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Exec(`
|
||||
DELETE FROM exhibitions
|
||||
WHERE asset_id = ? AND deleted_at IS NOT NULL
|
||||
`, exhibition.AssetID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Create(exhibition).Error
|
||||
})
|
||||
return r.db.Create(exhibition).Error
|
||||
}
|
||||
|
||||
// DeleteExhibition 软删除展品展示记录(根据ID)
|
||||
@ -283,13 +305,14 @@ func (r *galleryRepository) GetExpiredExhibitions(beforeTime int64) ([]*models.E
|
||||
|
||||
// GetAssetsWithInvalidDisplayStatus 获取 display_status=1 但没有有效 exhibition 的资产ID列表
|
||||
// 用于清理手动软删除导致的 display_status 不同步问题
|
||||
// 注意:这里只要 exhibition 未删除(deleted_at IS NULL)就认为有效,包含已过期未领取的
|
||||
func (r *galleryRepository) GetAssetsWithInvalidDisplayStatus() ([]int64, error) {
|
||||
var assetIDs []int64
|
||||
|
||||
// 子查询:查找有有效 exhibition 记录的 asset_id
|
||||
// 子查询:查找有有效 exhibition 记录的 asset_id(不管是否过期,只要未删除)
|
||||
subQuery := r.db.Model(&models.Exhibition{}).
|
||||
Select("asset_id").
|
||||
Where("deleted_at IS NULL AND expire_at > ?", time.Now().UnixMilli())
|
||||
Where("deleted_at IS NULL")
|
||||
|
||||
// 查找 display_status=1 但没有有效 exhibition 记录的资产
|
||||
err := r.db.Model(&models.AssetRegistry{}).
|
||||
@ -310,23 +333,29 @@ func (r *galleryRepository) UpdateAssetRegistryDisplayStatus(assetID int64, disp
|
||||
// PlaceExhibitionTx 事务性创建展品并更新展示状态(原子操作)
|
||||
func (r *galleryRepository) PlaceExhibitionTx(exhibition *models.Exhibition, displayStatus int32) error {
|
||||
now := time.Now().UnixMilli()
|
||||
|
||||
// 检查是否有未删除且未过期的同一 asset_id 记录
|
||||
var count int64
|
||||
err := r.db.Model(&models.Exhibition{}).
|
||||
Where("asset_id = ? AND deleted_at IS NULL AND expire_at > ?", exhibition.AssetID, now).
|
||||
Count(&count).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return errors.New("该藏品正在展出中,无法重复放置")
|
||||
}
|
||||
|
||||
exhibition.CreatedAt = now
|
||||
exhibition.UpdatedAt = now
|
||||
exhibition.DeletedAt = nil
|
||||
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
// 1. 物理删除已软删除的旧记录
|
||||
if err := tx.Exec(`
|
||||
DELETE FROM exhibitions
|
||||
WHERE asset_id = ? AND deleted_at IS NOT NULL
|
||||
`, exhibition.AssetID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// 2. 插入新记录
|
||||
// 1. 插入新记录(允许多个 asset_id 记录)
|
||||
if err := tx.Create(exhibition).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// 3. 更新展示状态(与展出操作在同一事务中)
|
||||
// 2. 更新展示状态(与展出操作在同一事务中)
|
||||
if err := tx.Model(&models.AssetRegistry{}).
|
||||
Where("asset_id = ?", exhibition.AssetID).
|
||||
Update("display_status", displayStatus).Error; err != nil {
|
||||
@ -360,45 +389,55 @@ func (r *galleryRepository) RemoveExhibitionTx(exhibitionID int64, assetID int64
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateExhibitionExpireAt 更新展览过期时间(用于清理后防止重复处理)
|
||||
func (r *galleryRepository) UpdateExhibitionExpireAt(exhibitionID int64, expireAt int64) error {
|
||||
return r.db.Model(&models.Exhibition{}).
|
||||
Where("id = ?", exhibitionID).
|
||||
Update("expire_at", expireAt).Error
|
||||
}
|
||||
|
||||
// ========== 我的作品相关实现 ==========
|
||||
|
||||
// GetMyExhibitedAssets 获取我展出的作品列表(只返回展出中且未过期的,含收益)
|
||||
// GetMyExhibitedAssets 获取我展出的作品列表(返回展出中且未下架的,含收益)
|
||||
// 包含未过期和已过期但未领取的展品,用户领取收益时才下架
|
||||
func (r *galleryRepository) GetMyExhibitedAssets(userID, starID int64, page, pageSize int) ([]*ExhibitedAssetInfo, int64, error) {
|
||||
var items []*ExhibitedAssetInfo
|
||||
var total int64
|
||||
|
||||
now := time.Now().UnixMilli()
|
||||
|
||||
// 计数查询
|
||||
// 计数查询:只要未下架(deleted_at IS NULL)都显示,包含已过期待领取的
|
||||
err := r.db.Model(&models.Exhibition{}).
|
||||
Where("occupier_uid = ? AND occupier_star_id = ? AND deleted_at IS NULL AND expire_at > ?", userID, starID, now).
|
||||
Where("occupier_uid = ? AND occupier_star_id = ? AND deleted_at IS NULL", userID, starID).
|
||||
Count(&total).Error
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 数据查询
|
||||
// 数据查询:只过滤已下架的,不过滤过期(用户领取收益时才下架)
|
||||
offset := (page - 1) * pageSize
|
||||
err = r.db.Model(&models.Exhibition{}).
|
||||
Raw(`
|
||||
SELECT exhibitions.asset_id, a.name, a.cover_url, a.like_count,
|
||||
exhibitions.start_time as exhibited_at, exhibitions.expire_at, bs.slot_index,
|
||||
COALESCE(CAST(SUM(err.crystal_amount) / 10 AS bigint), 0) as earnings
|
||||
exhibitions.start_time as exhibited_at, exhibitions.expire_at, bs.slot_index
|
||||
FROM exhibitions
|
||||
JOIN assets a ON a.id = exhibitions.asset_id
|
||||
JOIN booth_slots bs ON bs.slot_id = exhibitions.slot_id
|
||||
LEFT JOIN exhibition_revenue_records err ON err.asset_id = a.id AND err.status = 'claimable'
|
||||
WHERE exhibitions.occupier_uid = ? AND exhibitions.occupier_star_id = ?
|
||||
AND exhibitions.deleted_at IS NULL AND exhibitions.expire_at > ?
|
||||
GROUP BY exhibitions.asset_id, a.name, a.cover_url, a.like_count, exhibitions.start_time, exhibitions.expire_at, bs.slot_index
|
||||
ORDER BY bs.slot_index ASC
|
||||
AND exhibitions.deleted_at IS NULL
|
||||
ORDER BY exhibitions.expire_at DESC, bs.slot_index ASC
|
||||
LIMIT ? OFFSET ?
|
||||
`, userID, starID, now, pageSize, offset).Scan(&items).Error
|
||||
`, userID, starID, pageSize, offset).Scan(&items).Error
|
||||
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 实时计算展示收益:R1 = R0 × T × [100% + Buff(n)]
|
||||
// R0 = 5 水晶/小时
|
||||
now := time.Now().UnixMilli()
|
||||
for _, item := range items {
|
||||
item.Earnings = calculateRealtimeEarnings(item.LikeCount, item.ExhibitedAt, now)
|
||||
}
|
||||
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
@ -417,20 +456,17 @@ func (r *galleryRepository) GetUserExhibitedAssets(userID, starID int64, page, p
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 数据查询
|
||||
// 数据查询(实时计算收益)
|
||||
offset := (page - 1) * pageSize
|
||||
err = r.db.Model(&models.Exhibition{}).
|
||||
Raw(`
|
||||
SELECT exhibitions.asset_id, a.name, a.cover_url, a.like_count,
|
||||
exhibitions.start_time as exhibited_at, exhibitions.expire_at, bs.slot_index,
|
||||
COALESCE(CAST(SUM(err.crystal_amount) / 10 AS bigint), 0) as earnings
|
||||
exhibitions.start_time as exhibited_at, exhibitions.expire_at, bs.slot_index
|
||||
FROM exhibitions
|
||||
JOIN assets a ON a.id = exhibitions.asset_id
|
||||
JOIN booth_slots bs ON bs.slot_id = exhibitions.slot_id
|
||||
LEFT JOIN exhibition_revenue_records err ON err.asset_id = a.id AND err.status = 'claimable'
|
||||
WHERE exhibitions.occupier_uid = ? AND exhibitions.occupier_star_id = ?
|
||||
AND exhibitions.deleted_at IS NULL AND exhibitions.expire_at > ?
|
||||
GROUP BY exhibitions.asset_id, a.name, a.cover_url, a.like_count, exhibitions.start_time, exhibitions.expire_at, bs.slot_index
|
||||
ORDER BY bs.slot_index ASC
|
||||
LIMIT ? OFFSET ?
|
||||
`, userID, starID, now, pageSize, offset).Scan(&items).Error
|
||||
@ -439,6 +475,11 @@ func (r *galleryRepository) GetUserExhibitedAssets(userID, starID int64, page, p
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 实时计算每个资产的收益
|
||||
for _, item := range items {
|
||||
item.Earnings = calculateRealtimeEarnings(item.LikeCount, item.ExhibitedAt, now)
|
||||
}
|
||||
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
@ -555,6 +596,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
|
||||
@ -564,3 +614,37 @@ func generateHostProfileID(userID, starID int64) int64 {
|
||||
// 实际项目中应该使用与 User Service 一致的逻辑
|
||||
return userID*1000000 + starID
|
||||
}
|
||||
|
||||
// calculateRealtimeEarnings 实时计算展示收益
|
||||
// 公式:R1 = R0 × T × [100% + Buff(n)]
|
||||
// R0 = 5 水晶/小时,T = 上架时长(小时),Buff(n) 根据点赞数计算
|
||||
func calculateRealtimeEarnings(likeCount int32, startTime, now int64) int64 {
|
||||
R0 := int64(5) // 水晶/小时
|
||||
|
||||
// 计算上架时长(毫秒转小时)
|
||||
T := (now - startTime) / 3600000
|
||||
if T <= 0 {
|
||||
T = 1 // 最少1小时
|
||||
}
|
||||
|
||||
// 计算Buff
|
||||
var buff int
|
||||
switch {
|
||||
case likeCount >= 30:
|
||||
buff = 30
|
||||
case likeCount >= 10:
|
||||
buff = 20
|
||||
case likeCount >= 5:
|
||||
buff = 10
|
||||
default:
|
||||
buff = 0
|
||||
}
|
||||
|
||||
// 基础收益
|
||||
baseRevenue := R0 * T
|
||||
|
||||
// 应用Buff加成:R1 = R0 × T × (100% + Buff)
|
||||
buffedRevenue := baseRevenue * (100 + int64(buff)) / 100
|
||||
|
||||
return buffedRevenue
|
||||
}
|
||||
|
||||
@ -169,5 +169,5 @@ func GetMaxFriends() int {
|
||||
zap.Error(err))
|
||||
return 50
|
||||
}
|
||||
return config.ConfigValue
|
||||
return int(config.ConfigValue)
|
||||
}
|
||||
|
||||
@ -578,33 +578,29 @@ func (r *socialRepositoryImpl) GetMyLikedAssets(userID, starID int64, page, page
|
||||
var items []*LikedAssetInfo
|
||||
var total int64
|
||||
|
||||
// 子查询:查找当前时间未过期的展出记录
|
||||
now := time.Now().UnixMilli()
|
||||
|
||||
// 计数查询(使用 DISTINCT 因为一个资产可能在多个展位展出)
|
||||
// 只要资产未删除且未下架就显示,包含已过期的(用户可继续查看点赞记录)
|
||||
countQuery := r.db.Model(&models.AssetLike{}).
|
||||
Joins("JOIN assets a ON a.id = asset_likes.asset_id").
|
||||
Joins("JOIN exhibitions e ON e.asset_id = a.id").
|
||||
Where("asset_likes.user_id = ? AND asset_likes.star_id = ?", userID, starID).
|
||||
Where("a.deleted_at IS NULL AND a.is_active = ?", true).
|
||||
Where("e.deleted_at IS NULL AND e.expire_at > ?", now)
|
||||
Where("e.deleted_at IS NULL")
|
||||
|
||||
if err := countQuery.Distinct("asset_likes.asset_id").Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 数据查询
|
||||
// 数据查询:只过滤已下架的,不过滤过期
|
||||
offset := (page - 1) * pageSize
|
||||
err := r.db.Model(&models.AssetLike{}).
|
||||
Select(`asset_likes.asset_id, a.name, a.cover_url, a.like_count,
|
||||
asset_likes.created_at as liked_at,
|
||||
COALESCE(SUM(err.crystal_amount), 0) as earnings`).
|
||||
asset_likes.created_at as liked_at`).
|
||||
Joins("JOIN assets a ON a.id = asset_likes.asset_id").
|
||||
Joins("JOIN exhibitions e ON e.asset_id = a.id").
|
||||
Joins("LEFT JOIN exhibition_revenue_records err ON err.asset_id = a.id AND err.status = 'claimable'").
|
||||
Where("asset_likes.user_id = ? AND asset_likes.star_id = ?", userID, starID).
|
||||
Where("a.deleted_at IS NULL AND a.is_active = ?", true).
|
||||
Where("e.deleted_at IS NULL AND e.expire_at > ?", now).
|
||||
Where("e.deleted_at IS NULL").
|
||||
Group("asset_likes.asset_id, a.name, a.cover_url, a.like_count, asset_likes.created_at").
|
||||
Order("asset_likes.created_at DESC").
|
||||
Limit(pageSize).
|
||||
@ -615,9 +611,54 @@ func (r *socialRepositoryImpl) GetMyLikedAssets(userID, starID int64, page, page
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 实时计算每个资产的收益
|
||||
now := time.Now().UnixMilli()
|
||||
for _, item := range items {
|
||||
// 获取该资产的展出开始时间(如果还在展出中就计算收益)
|
||||
var exhibition models.Exhibition
|
||||
if err := r.db.Where("asset_id = ? AND deleted_at IS NULL", item.AssetID).
|
||||
First(&exhibition).Error; err == nil {
|
||||
item.Earnings = calculateRealtimeEarnings(item.LikeCount, exhibition.StartTime, now)
|
||||
}
|
||||
}
|
||||
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
// calculateRealtimeEarnings 实时计算展示收益
|
||||
// 公式:R1 = R0 × T × [100% + Buff(n)]
|
||||
// R0 = 5 水晶/小时,T = 上架时长(小时),Buff(n) 根据点赞数计算
|
||||
func calculateRealtimeEarnings(likeCount int32, startTime, now int64) int64 {
|
||||
R0 := int64(5) // 水晶/小时
|
||||
|
||||
// 计算上架时长(毫秒转小时)
|
||||
T := (now - startTime) / 3600000
|
||||
if T <= 0 {
|
||||
T = 1 // 最少1小时
|
||||
}
|
||||
|
||||
// 计算Buff
|
||||
var buff int
|
||||
switch {
|
||||
case likeCount >= 30:
|
||||
buff = 30
|
||||
case likeCount >= 10:
|
||||
buff = 20
|
||||
case likeCount >= 5:
|
||||
buff = 10
|
||||
default:
|
||||
buff = 0
|
||||
}
|
||||
|
||||
// 基础收益
|
||||
baseRevenue := R0 * T
|
||||
|
||||
// 应用Buff加成:R1 = R0 × T × (100% + Buff)
|
||||
buffedRevenue := baseRevenue * (100 + int64(buff)) / 100
|
||||
|
||||
return buffedRevenue
|
||||
}
|
||||
|
||||
// GetMyTodayLikedAssets 获取我今日点赞的作品列表(只返回展出中且未过期的)
|
||||
func (r *socialRepositoryImpl) GetMyTodayLikedAssets(userID, starID int64, page, pageSize int) ([]*LikedAssetInfo, int64, error) {
|
||||
now := time.Now()
|
||||
@ -641,11 +682,9 @@ func (r *socialRepositoryImpl) GetMyTodayLikedAssets(userID, starID int64, page,
|
||||
offset := (page - 1) * pageSize
|
||||
err := r.db.Model(&models.AssetLike{}).
|
||||
Select(`asset_likes.asset_id, a.name, a.cover_url, a.like_count,
|
||||
asset_likes.created_at as liked_at,
|
||||
COALESCE(SUM(err.crystal_amount), 0) as earnings`).
|
||||
asset_likes.created_at as liked_at`).
|
||||
Joins("JOIN assets a ON a.id = asset_likes.asset_id").
|
||||
Joins("JOIN exhibitions e ON e.asset_id = a.id").
|
||||
Joins("LEFT JOIN exhibition_revenue_records err ON err.asset_id = a.id AND err.status = 'claimable'").
|
||||
Where("asset_likes.user_id = ? AND asset_likes.star_id = ?", userID, starID).
|
||||
Where("asset_likes.created_at >= ?", startOfDay).
|
||||
Where("a.deleted_at IS NULL AND a.is_active = ?", true).
|
||||
@ -660,6 +699,15 @@ func (r *socialRepositoryImpl) GetMyTodayLikedAssets(userID, starID int64, page,
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 实时计算每个资产的收益
|
||||
for _, item := range items {
|
||||
var exhibition models.Exhibition
|
||||
if err := r.db.Where("asset_id = ? AND deleted_at IS NULL AND expire_at > ?", item.AssetID, now.UnixMilli()).
|
||||
First(&exhibition).Error; err == nil {
|
||||
item.Earnings = calculateRealtimeEarnings(item.LikeCount, exhibition.StartTime, now.UnixMilli())
|
||||
}
|
||||
}
|
||||
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
@ -693,11 +741,9 @@ func (r *socialRepositoryImpl) GetMyWeekLikedAssets(userID, starID int64, page,
|
||||
offset := (page - 1) * pageSize
|
||||
err := r.db.Model(&models.AssetLike{}).
|
||||
Select(`asset_likes.asset_id, a.name, a.cover_url, a.like_count,
|
||||
asset_likes.created_at as liked_at,
|
||||
COALESCE(SUM(err.crystal_amount), 0) as earnings`).
|
||||
asset_likes.created_at as liked_at`).
|
||||
Joins("JOIN assets a ON a.id = asset_likes.asset_id").
|
||||
Joins("JOIN exhibitions e ON e.asset_id = a.id").
|
||||
Joins("LEFT JOIN exhibition_revenue_records err ON err.asset_id = a.id AND err.status = 'claimable'").
|
||||
Where("asset_likes.user_id = ? AND asset_likes.star_id = ?", userID, starID).
|
||||
Where("asset_likes.created_at >= ?", startOfWeekMillis).
|
||||
Where("a.deleted_at IS NULL AND a.is_active = ?", true).
|
||||
@ -712,6 +758,16 @@ func (r *socialRepositoryImpl) GetMyWeekLikedAssets(userID, starID int64, page,
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 实时计算每个资产的收益
|
||||
nowMillis := now.UnixMilli()
|
||||
for _, item := range items {
|
||||
var exhibition models.Exhibition
|
||||
if err := r.db.Where("asset_id = ? AND deleted_at IS NULL AND expire_at > ?", item.AssetID, nowMillis).
|
||||
First(&exhibition).Error; err == nil {
|
||||
item.Earnings = calculateRealtimeEarnings(item.LikeCount, exhibition.StartTime, nowMillis)
|
||||
}
|
||||
}
|
||||
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
@ -736,11 +792,9 @@ func (r *socialRepositoryImpl) GetUserLikedAssets(userID, starID int64, page, pa
|
||||
offset := (page - 1) * pageSize
|
||||
err := r.db.Model(&models.AssetLike{}).
|
||||
Select(`asset_likes.asset_id, a.name, a.cover_url, a.like_count,
|
||||
asset_likes.created_at as liked_at,
|
||||
COALESCE(SUM(err.crystal_amount), 0) as earnings`).
|
||||
asset_likes.created_at as liked_at`).
|
||||
Joins("JOIN assets a ON a.id = asset_likes.asset_id").
|
||||
Joins("JOIN exhibitions e ON e.asset_id = a.id").
|
||||
Joins("LEFT JOIN exhibition_revenue_records err ON err.asset_id = a.id AND err.status = 'claimable'").
|
||||
Where("asset_likes.user_id = ? AND asset_likes.star_id = ?", userID, starID).
|
||||
Where("a.deleted_at IS NULL AND a.is_active = ?", true).
|
||||
Where("e.deleted_at IS NULL AND e.expire_at > ?", now).
|
||||
@ -754,5 +808,14 @@ func (r *socialRepositoryImpl) GetUserLikedAssets(userID, starID int64, page, pa
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 实时计算每个资产的收益
|
||||
for _, item := range items {
|
||||
var exhibition models.Exhibition
|
||||
if err := r.db.Where("asset_id = ? AND deleted_at IS NULL AND expire_at > ?", item.AssetID, now).
|
||||
First(&exhibition).Error; err == nil {
|
||||
item.Earnings = calculateRealtimeEarnings(item.LikeCount, exhibition.StartTime, now)
|
||||
}
|
||||
}
|
||||
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
40
backend/services/taskService/client/gallery_rpc_client.go
Normal file
40
backend/services/taskService/client/gallery_rpc_client.go
Normal file
@ -0,0 +1,40 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/topfans/backend/pkg/logger"
|
||||
pbCommon "github.com/topfans/backend/pkg/proto/common"
|
||||
pbGallery "github.com/topfans/backend/pkg/proto/gallery"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type GalleryServiceClient interface {
|
||||
RemoveExhibitionByAsset(ctx context.Context, assetID int64) error
|
||||
}
|
||||
|
||||
type galleryServiceClient struct {
|
||||
client pbGallery.GalleryService
|
||||
}
|
||||
|
||||
func NewGalleryServiceClient(client pbGallery.GalleryService) GalleryServiceClient {
|
||||
return &galleryServiceClient{client: client}
|
||||
}
|
||||
|
||||
func (c *galleryServiceClient) RemoveExhibitionByAsset(ctx context.Context, assetID int64) error {
|
||||
logger.Logger.Debug("Calling GalleryService.RemoveExhibitionByAsset",
|
||||
zap.Int64("asset_id", assetID))
|
||||
resp, err := c.client.RemoveExhibitionByAsset(ctx, &pbGallery.RemoveExhibitionByAssetRequest{
|
||||
AssetId: assetID,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Logger.Error("GalleryService.RemoveExhibitionByAsset failed", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
if resp.Base.Code != pbCommon.StatusCode_STATUS_OK {
|
||||
logger.Logger.Warn("RemoveExhibitionByAsset non-zero code", zap.Int32("code", int32(resp.Base.Code)), zap.String("message", resp.Base.Message))
|
||||
return fmt.Errorf("RemoveExhibitionByAsset failed with code: %d, message: %s", resp.Base.Code, resp.Base.Message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -10,9 +10,12 @@ 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)
|
||||
// AddExhibitionHours 增加用户累计上架时长
|
||||
// sourceID: 关联业务ID,用于升级奖励流水的溯源
|
||||
// 返回: newLevel, levelDelta, crystalReward, error
|
||||
AddExhibitionHours(ctx context.Context, userID, starID int64, hours int64, sourceID string) (int32, int32, int64, error)
|
||||
}
|
||||
|
||||
type userServiceClient struct {
|
||||
@ -23,11 +26,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 +43,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))
|
||||
@ -74,3 +60,24 @@ func (c *userServiceClient) GetFanProfile(ctx context.Context, userID, starID in
|
||||
}
|
||||
return resp.Profile, nil
|
||||
}
|
||||
|
||||
// AddExhibitionHours 增加用户累计上架时长
|
||||
func (c *userServiceClient) AddExhibitionHours(ctx context.Context, userID, starID int64, hours int64, sourceID string) (int32, int32, int64, error) {
|
||||
logger.Logger.Debug("Calling UserService.AddExhibitionHours",
|
||||
zap.Int64("user_id", userID), zap.Int64("star_id", starID), zap.Int64("hours", hours))
|
||||
resp, err := c.client.AddExhibitionHours(ctx, &pbUser.AddExhibitionHoursRequest{
|
||||
UserId: userID,
|
||||
StarId: starID,
|
||||
ExhibitionHours: hours,
|
||||
SourceId: sourceID,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Logger.Error("UserService.AddExhibitionHours failed", zap.Error(err))
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
if resp.Base.Code != pbCommon.StatusCode_STATUS_OK {
|
||||
logger.Logger.Warn("AddExhibitionHours non-zero code", zap.Int32("code", int32(resp.Base.Code)))
|
||||
return 0, 0, 0, fmt.Errorf("AddExhibitionHours failed with code: %d", resp.Base.Code)
|
||||
}
|
||||
return resp.NewLevel, resp.LevelDelta, resp.CrystalReward, nil
|
||||
}
|
||||
|
||||
@ -14,7 +14,8 @@ type DatabaseConfig struct {
|
||||
}
|
||||
|
||||
type ServiceURLs struct {
|
||||
UserService string
|
||||
UserService string
|
||||
GalleryService string
|
||||
}
|
||||
|
||||
type WorkerConfig struct {
|
||||
@ -25,7 +26,7 @@ type WorkerConfig struct {
|
||||
|
||||
var (
|
||||
DBConfig = &DatabaseConfig{}
|
||||
ServiceURLsConfig = &ServiceURLs{UserService: "tri://localhost:20000"}
|
||||
ServiceURLsConfig = &ServiceURLs{UserService: "tri://localhost:20000", GalleryService: "tri://localhost:20004"}
|
||||
WorkerConfigData = &WorkerConfig{
|
||||
ResetHour: 5, ResetMinute: 0,
|
||||
RevenueBatchSize: 100, RevenueMaxRetries: 3,
|
||||
@ -52,6 +53,7 @@ func InitConfig() {
|
||||
flag.StringVar(&DBConfig.DBName, "db-name", getEnv("DB_NAME", "topfans"), "数据库名称")
|
||||
flag.StringVar(&DBConfig.SSLMode, "db-sslmode", "disable", "数据库 SSL 模式")
|
||||
flag.StringVar(&ServiceURLsConfig.UserService, "user-service-url", getEnv("USER_SERVICE_URL", "tri://localhost:20000"), "User Service RPC 地址")
|
||||
flag.StringVar(&ServiceURLsConfig.GalleryService, "gallery-service-url", getEnv("GALLERY_SERVICE_URL", "tri://localhost:20004"), "Gallery Service RPC 地址")
|
||||
flag.IntVar(&WorkerConfigData.ResetHour, "reset-hour", getEnvInt("RESET_HOUR", 5), "每日重置小时(Asia/Shanghai)")
|
||||
flag.IntVar(&WorkerConfigData.ResetMinute, "reset-minute", getEnvInt("RESET_MINUTE", 0), "每日重置分钟")
|
||||
flag.IntVar(&WorkerConfigData.RevenueBatchSize, "revenue-batch-size", getEnvInt("REVENUE_BATCH_SIZE", 100), "收益自动发放批次大小")
|
||||
@ -60,5 +62,6 @@ func InitConfig() {
|
||||
log.Println("taskService 配置初始化完成")
|
||||
log.Printf(" 数据库: %s:%d/%s", DBConfig.Host, DBConfig.Port, DBConfig.DBName)
|
||||
log.Printf(" User Service: %s", ServiceURLsConfig.UserService)
|
||||
log.Printf(" Gallery Service: %s", ServiceURLsConfig.GalleryService)
|
||||
log.Printf(" 重置时间: %02d:%02d Asia/Shanghai", WorkerConfigData.ResetHour, WorkerConfigData.ResetMinute)
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ import (
|
||||
"github.com/topfans/backend/pkg/database"
|
||||
"github.com/topfans/backend/pkg/logger"
|
||||
pb "github.com/topfans/backend/pkg/proto/task"
|
||||
pbGallery "github.com/topfans/backend/pkg/proto/gallery"
|
||||
pbUser "github.com/topfans/backend/pkg/proto/user"
|
||||
"github.com/topfans/backend/services/taskService/client"
|
||||
"github.com/topfans/backend/services/taskService/config"
|
||||
@ -89,10 +90,24 @@ func main() {
|
||||
userRPCClient := client.NewUserServiceClient(userServiceClient)
|
||||
logger.Logger.Info("User RPC client initialized")
|
||||
|
||||
// 5.1 Init galleryService Dubbo client + RPC client
|
||||
galleryCli, err := dubboclient.NewClient(
|
||||
dubboclient.WithClientURL(config.ServiceURLsConfig.GalleryService),
|
||||
)
|
||||
if err != nil {
|
||||
logger.Logger.Fatal(fmt.Sprintf("Failed to create galleryService client: %v", err))
|
||||
}
|
||||
galleryServiceClient, err := pbGallery.NewGalleryService(galleryCli)
|
||||
if err != nil {
|
||||
logger.Logger.Fatal(fmt.Sprintf("Failed to get galleryService: %v", err))
|
||||
}
|
||||
galleryRPCClient := client.NewGalleryServiceClient(galleryServiceClient)
|
||||
logger.Logger.Info("Gallery RPC client initialized")
|
||||
|
||||
// 6. Init services
|
||||
dailySvc := service.NewDailyTaskService(dailyRepo, userRPCClient)
|
||||
onboardingSvc := service.NewOnboardingService(onboardingRepo, dailyRepo, userRPCClient)
|
||||
revenueSvc := service.NewRevenueService(revenueRepo, userRPCClient)
|
||||
revenueSvc := service.NewRevenueService(revenueRepo, userRPCClient, galleryRPCClient)
|
||||
logger.Logger.Info("Services initialized")
|
||||
|
||||
// 7. Init worker(goroutine 中启动)
|
||||
|
||||
@ -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,17 @@ package repository
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"fmt"
|
||||
"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 检查字符串是否包含子串(不区分大小写)
|
||||
@ -16,40 +20,15 @@ func contains(s, substr string) bool {
|
||||
return strings.Contains(strings.ToLower(s), strings.ToLower(substr))
|
||||
}
|
||||
|
||||
// CalculateLevel 根据经验值计算等级
|
||||
// 公式: 升级到等级L需要的累计经验 = (L-1) * L * 50
|
||||
// Level 1: 0经验, Level 2: 100经验, Level 3: 300经验, Level 4: 600经验...
|
||||
func CalculateLevel(experience int64) int32 {
|
||||
if experience < 0 {
|
||||
return 1
|
||||
}
|
||||
// 使用公式: (L-1) * L * 50 <= experience
|
||||
// 解方程: L^2 - L - experience/50 <= 0
|
||||
// L = (1 + sqrt(1 + 4*experience/50)) / 2
|
||||
level := int32((1 + math.Sqrt(1+4*float64(experience)/50)) / 2)
|
||||
if level < 1 {
|
||||
level = 1
|
||||
}
|
||||
return level
|
||||
}
|
||||
|
||||
// GetExperienceForLevel 获取指定等级需要的经验值
|
||||
func GetExperienceForLevel(level int32) int64 {
|
||||
if level <= 1 {
|
||||
return 0
|
||||
}
|
||||
return int64((level-1) * level * 50)
|
||||
}
|
||||
|
||||
// FanProfileRepository 粉丝档案Repository接口
|
||||
type FanProfileRepository interface {
|
||||
// Create 创建粉丝档案
|
||||
Create(profile *models.FanProfile) error
|
||||
|
||||
// GetByUserAndStar 根据user_id + star_id查询
|
||||
// GetByUserAndStar 根据user_id + star_id查询(等级为实时计算,基于累计时长+当前展出中时长)
|
||||
GetByUserAndStar(userID, starID int64) (*models.FanProfile, error)
|
||||
|
||||
// GetByUserID 查询用户的所有粉丝身份
|
||||
// GetByUserID 查询用户的所有粉丝身份(等级为实时计算)
|
||||
GetByUserID(userID int64) ([]*models.FanProfile, error)
|
||||
|
||||
// ExistsByNickname 检查昵称是否已存在
|
||||
@ -67,9 +46,6 @@ type FanProfileRepository interface {
|
||||
// IncrementAssetsCount 增加资产计数
|
||||
IncrementAssetsCount(userID, starID int64, delta int32) error
|
||||
|
||||
// SyncLevelFromExperience 根据经验值同步等级(只升级不降级)
|
||||
SyncLevelFromExperience(userID, starID int64) (int32, error)
|
||||
|
||||
// DecrementAssetsCount 减少资产计数
|
||||
DecrementAssetsCount(userID, starID int64, delta int32) error
|
||||
|
||||
@ -79,11 +55,16 @@ 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)
|
||||
|
||||
// UpdateExperience 更新经验值
|
||||
UpdateExperience(userID, starID int64, delta int64) (int64, error)
|
||||
// AddExhibitionHours 增加用户累计上架时长并同步等级(事务性)
|
||||
// sourceID: 关联业务ID,用于升级奖励流水的溯源
|
||||
// 返回: newLevel, levelDelta, crystalReward, error
|
||||
AddExhibitionHours(userID, starID int64, hours int64, sourceID string) (int32, int32, int64, error)
|
||||
|
||||
// UpdateAvatar 更新头像
|
||||
UpdateAvatar(userID, starID int64, avatarURL string) error
|
||||
@ -136,7 +117,7 @@ func (r *fanProfileRepository) Create(profile *models.FanProfile) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetByUserAndStar 根据user_id + star_id查询
|
||||
// GetByUserAndStar 根据user_id + star_id查询(等级为实时计算,基于累计时长+当前展出中时长)
|
||||
func (r *fanProfileRepository) GetByUserAndStar(userID, starID int64) (*models.FanProfile, error) {
|
||||
if userID <= 0 {
|
||||
return nil, errors.New("user_id must be greater than 0")
|
||||
@ -155,10 +136,49 @@ func (r *fanProfileRepository) GetByUserAndStar(userID, starID int64) (*models.F
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 实时计算等级:累计时长 + 当前展出中时长
|
||||
profile.Level = r.calculateRealtimeLevel(userID, starID)
|
||||
|
||||
return &profile, nil
|
||||
}
|
||||
|
||||
// GetByUserID 查询用户的所有粉丝身份
|
||||
// calculateRealtimeLevel 实时计算用户等级(基于累计时长 + 当前展出中的展品时长)
|
||||
func (r *fanProfileRepository) calculateRealtimeLevel(userID, starID int64) int32 {
|
||||
// 1. 获取累计时长
|
||||
var exhibitionHours models.UserExhibitionHours
|
||||
err := r.db.Where("user_id = ? AND star_id = ?", userID, starID).First(&exhibitionHours).Error
|
||||
if err != nil {
|
||||
return 1
|
||||
}
|
||||
totalHours := exhibitionHours.TotalExhibitionHours
|
||||
|
||||
// 2. 获取当前展出中的展品累计时长(从开始到现在)
|
||||
now := time.Now().UnixMilli()
|
||||
var activeHours int64
|
||||
var exhibitions []models.Exhibition
|
||||
if err := r.db.Where("occupier_uid = ? AND occupier_star_id = ? AND deleted_at IS NULL AND expire_at > ?", userID, starID, now).
|
||||
Find(&exhibitions).Error; err == nil {
|
||||
for _, e := range exhibitions {
|
||||
activeHours += (now - e.StartTime) / 3600000
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 取较大值:累计值 vs 当前展出中的实际值(防止负数)
|
||||
realtimeHours := totalHours
|
||||
if activeHours > realtimeHours {
|
||||
realtimeHours = activeHours
|
||||
}
|
||||
|
||||
// 4. 计算等级
|
||||
maxLevel := GetLevelCap()
|
||||
newLevel := CalculateLevelFromExhibitionHours(realtimeHours)
|
||||
if newLevel > maxLevel {
|
||||
newLevel = maxLevel
|
||||
}
|
||||
return newLevel
|
||||
}
|
||||
|
||||
// GetByUserID 查询用户的所有粉丝身份(等级为实时计算)
|
||||
func (r *fanProfileRepository) GetByUserID(userID int64) ([]*models.FanProfile, error) {
|
||||
if userID <= 0 {
|
||||
return nil, errors.New("user_id must be greater than 0")
|
||||
@ -171,6 +191,11 @@ func (r *fanProfileRepository) GetByUserID(userID int64) ([]*models.FanProfile,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 实时计算每个身份的等级
|
||||
for _, profile := range profiles {
|
||||
profile.Level = r.calculateRealtimeLevel(userID, profile.StarID)
|
||||
}
|
||||
|
||||
return profiles, nil
|
||||
}
|
||||
|
||||
@ -369,10 +394,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 +412,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 +423,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,63 +462,220 @@ func (r *fanProfileRepository) UpdateCrystalBalance(userID, starID int64, delta
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return newBalance, nil
|
||||
return newBalance, nil
|
||||
}
|
||||
|
||||
// UpdateExperience 更新经验值(同时自动更新等级)
|
||||
// delta: 变化量,正数表示增加,负数表示减少
|
||||
// 返回: 更新后的经验值
|
||||
func (r *fanProfileRepository) UpdateExperience(userID, starID int64, delta int64) (int64, error) {
|
||||
if userID <= 0 {
|
||||
return 0, errors.New("user_id must be greater than 0")
|
||||
// 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 starID <= 0 {
|
||||
return 0, errors.New("star_id must be greater than 0")
|
||||
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 增加用户累计上架时长并同步等级(事务性)
|
||||
// sourceID: 关联业务ID,用于升级奖励流水的溯源
|
||||
// 返回: newLevel, levelDelta, crystalReward, error
|
||||
func (r *fanProfileRepository) AddExhibitionHours(userID, starID int64, hours int64, sourceID string) (int32, int32, int64, error) {
|
||||
var result struct {
|
||||
OldLevel int32
|
||||
NewLevel int32
|
||||
CrystalReward int64
|
||||
}
|
||||
|
||||
// 使用事务确保原子性
|
||||
var newExperience int64
|
||||
err := r.db.Transaction(func(tx *gorm.DB) error {
|
||||
// 先查询当前的 experience 值
|
||||
var profile models.FanProfile
|
||||
if err := tx.Where("user_id = ? AND star_id = ?", userID, starID).
|
||||
First(&profile).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return appErrors.ErrFanProfileNotFound
|
||||
}
|
||||
// 1. 获取或创建累计时长记录
|
||||
exhibitionHours, err := r.GetOrCreateExhibitionHours(tx, userID, starID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 计算新经验值
|
||||
newExperience = profile.Experience + delta
|
||||
|
||||
// 确保不会小于 0
|
||||
if newExperience < 0 {
|
||||
newExperience = 0
|
||||
}
|
||||
|
||||
// 根据新经验值计算新等级
|
||||
newLevel := CalculateLevel(newExperience)
|
||||
|
||||
// 更新 experience 和 level 字段
|
||||
if err := tx.Model(&models.FanProfile{}).
|
||||
// 2. 原子性累加时长(避免竞态条件)
|
||||
now := time.Now().UnixMilli()
|
||||
if err := tx.Model(&models.UserExhibitionHours{}).
|
||||
Where("user_id = ? AND star_id = ?", userID, starID).
|
||||
Updates(map[string]interface{}{
|
||||
"experience": newExperience,
|
||||
"level": newLevel,
|
||||
"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: 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, err
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
|
||||
return newExperience, nil
|
||||
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
|
||||
}
|
||||
|
||||
// UpdateAvatar 更新头像
|
||||
@ -504,23 +707,4 @@ func (r *fanProfileRepository) UpdateAvatar(userID, starID int64, avatarURL stri
|
||||
return nil
|
||||
}
|
||||
|
||||
// SyncLevelFromExperience 根据经验值同步等级(只升级不降级)
|
||||
// 在获取用户信息时调用,确保等级与经验值匹配
|
||||
func (r *fanProfileRepository) SyncLevelFromExperience(userID, starID int64) (int32, error) {
|
||||
var profile models.FanProfile
|
||||
if err := r.db.Where("user_id = ? AND star_id = ?", userID, starID).First(&profile).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
newLevel := CalculateLevel(profile.Experience)
|
||||
|
||||
// 只升级,不降级
|
||||
if newLevel > profile.Level {
|
||||
if err := r.db.Model(&profile).Update("level", newLevel).Error; err != nil {
|
||||
return profile.Level, err
|
||||
}
|
||||
return newLevel, nil
|
||||
}
|
||||
|
||||
return profile.Level, nil
|
||||
}
|
||||
// UpdateAvatar 更新头像
|
||||
|
||||
@ -353,7 +353,6 @@ func TestFanProfileRepository_Update(t *testing.T) {
|
||||
|
||||
// 更新粉丝档案
|
||||
profile.Level = 2
|
||||
profile.Experience = 100
|
||||
err := repo.Update(profile)
|
||||
if err != nil {
|
||||
t.Fatalf("Update failed: %v", err)
|
||||
@ -368,10 +367,6 @@ func TestFanProfileRepository_Update(t *testing.T) {
|
||||
if retrieved.Level != 2 {
|
||||
t.Errorf("Level mismatch: expected 2, got %d", retrieved.Level)
|
||||
}
|
||||
|
||||
if retrieved.Experience != 100 {
|
||||
t.Errorf("Experience mismatch: expected 100, got %d", retrieved.Experience)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFanProfileRepository_UpdateNickname(t *testing.T) {
|
||||
|
||||
@ -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. 扣水晶
|
||||
|
||||
@ -953,4 +953,4 @@ lock: activity:{activityId}:contributions:lock
|
||||
|
||||
1. **多级缓存**:引入本地内存缓存(如 Go 的 `sync.Map`),减少 Redis 请求
|
||||
2. **窗口动态调整**:根据并发量动态调整窗口大小
|
||||
3. **监控告警**:监控缓存命中率、锁等待时间、DB 查询 QPS
|
||||
3. **监控告警**:监控缓存命中率、锁等待时间、DB 查询 QPS
|
||||
|
||||
@ -156,7 +156,8 @@
|
||||
<view class="chain-row">
|
||||
<text class="chain-label">链上哈希</text>
|
||||
<view class="chain-value-wrap">
|
||||
<text class="chain-value chain-hash" @longpress="copyHash">{{ hiddenTxHash }}</text>
|
||||
<text class="chain-value chain-hash" @longpress="copyHash">{{ showTxHash ? displayTxHash
|
||||
: hiddenTxHash }}</text>
|
||||
<view class="toggle-btn" @tap="showTxHash = !showTxHash">
|
||||
<image class="toggle-icon" :src="showTxHash ? '/static/icon/show.png' : '/static/icon/hide.png'" mode="aspectFit"></image>
|
||||
</view>
|
||||
|
||||
@ -188,7 +188,7 @@ const handleNavClick = (index) => {
|
||||
.monster-icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
filter: drop-shadow(0 8rpx 24rpx rgba(255, 107, 157, 0.5)) drop-shadow(0 0 40rpx rgba(255, 140, 180, 0.3));
|
||||
/* filter: drop-shadow(0 8rpx 24rpx rgba(255, 107, 157, 0.5)) drop-shadow(0 0 40rpx rgba(255, 140, 180, 0.3)); */
|
||||
transition: filter 0.3s ease, transform 0.3s ease;
|
||||
animation: breathe 2s ease-in-out infinite;
|
||||
}
|
||||
@ -242,7 +242,7 @@ const handleNavClick = (index) => {
|
||||
height: var(--icon-height, 100rpx);
|
||||
margin-bottom: 8rpx;
|
||||
transition: transform 0.3s ease, filter 0.3s ease;
|
||||
filter: drop-shadow(0 6rpx 16rpx rgba(0, 0, 0, 0.4)) drop-shadow(0 2rpx 8rpx rgba(0, 0, 0, 0.2));
|
||||
/* filter: drop-shadow(0 6rpx 16rpx rgba(0, 0, 0, 0.4)) dr8op-shadow(0 2rpx 8rpx rgba(0, 0, 0, 0.2)); */
|
||||
position: relative;
|
||||
transform: rotate(var(--rotate));
|
||||
}
|
||||
|
||||
@ -169,6 +169,7 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import { getMyExhibitedAssetsApi, getMyLikedAssetsApi, getMyTodayLikedAssetsApi, getMyWeekLikedAssetsApi, getMyGalleriesApi, placeAssetToGalleryApi } from '@/utils/api.js';
|
||||
import { getExhibitionRevenue, claimExhibitionRevenue } from '@/utils/task-api.js';
|
||||
import AssetSelector from '../components/AssetSelector.vue';
|
||||
import { onShow } from '@dcloudio/uni-app';
|
||||
import { doubleTapLike } from '@/utils/likeHelper.js';
|
||||
@ -276,9 +277,46 @@ const goToAssetDetail = (id) => {
|
||||
const cardTapTimers = {};
|
||||
|
||||
// 领取收益处理
|
||||
const handleClaimReward = (item, _index) => {
|
||||
const handleClaimReward = async (item, _index) => {
|
||||
console.log('领取收益:', item);
|
||||
uni.showToast({ title: '收益已领取', icon: 'success' });
|
||||
|
||||
uni.showLoading({ title: '领取中...' });
|
||||
|
||||
try {
|
||||
// 从缓存获取 star_id
|
||||
const starId = uni.getStorageSync('star_id') || 1;
|
||||
|
||||
// 查询该藏品的可领取收益记录
|
||||
const res = await getExhibitionRevenue(starId, 'claimable', 1, 100);
|
||||
const records = res.data?.items || [];
|
||||
|
||||
// 找到对应资产ID的收益记录
|
||||
const revenueRecord = records.find(r => r.asset_id === item.id);
|
||||
|
||||
if (!revenueRecord) {
|
||||
uni.showToast({ title: '暂无可领取收益', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
// 调用领取接口
|
||||
const claimRes = await claimExhibitionRevenue(revenueRecord.id, starId);
|
||||
|
||||
uni.showToast({ title: '收益已领取', icon: 'success' });
|
||||
|
||||
// 更新全局余额
|
||||
if (claimRes?.data?.total_balance !== undefined) {
|
||||
uni.$emit('balanceUpdated', { crystal_balance: claimRes.data.total_balance });
|
||||
}
|
||||
|
||||
// 刷新列表
|
||||
await loadExhibitedAssets();
|
||||
|
||||
} catch (err) {
|
||||
console.error('领取收益失败:', err);
|
||||
uni.showToast({ title: err.message || '领取失败', icon: 'none' });
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
};
|
||||
|
||||
const handleExhibitionCardTap = (item, index) => {
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
<view class="profile-container">
|
||||
<!-- 背景图片 - 固定在容器上 -->
|
||||
<image class="background-image" src="/static/square/squearbj.png" mode="aspectFill"></image>
|
||||
|
||||
<scroll-view class="profile-scroll" scroll-y :show-scrollbar="false" :enable-back-to-top="true">
|
||||
<!-- 上半部分:用户信息卡片 -->
|
||||
<view class="top-section">
|
||||
@ -1322,7 +1321,6 @@ onShow(() => {
|
||||
align-items: flex-start;
|
||||
/* gap: 30rpx; */
|
||||
margin-bottom: 0;
|
||||
|
||||
}
|
||||
|
||||
.avatar-container {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user