Compare commits

...

31 Commits

Author SHA1 Message Date
zerosaturation
eb051f6562 Merge branch 'feature/economic-system-redesign' into dev 2026-05-15 23:22:38 +08:00
zerosaturation
c5bf9df955 feat: 经济系统重构 - 任务服务与画廊服务解耦
- galleryService 独立启动,配置 task-service-url
- 新增 taskService 到 galleryService 的 RPC 调用
- 更新 proto 文件并重新生成代码
- 新增展览唯一约束迁移脚本
- 修复多个 service 的配置和依赖关系

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 23:12:02 +08:00
2c2e707971 修改展示页的样式 2026-05-15 23:12:02 +08:00
ff24cd55cd 修改展示页的样式 2026-05-15 23:12:02 +08:00
4d10fc4fb3 feat: 替换个人设置为图片 2026-05-15 23:12:02 +08:00
f5de8927fb style: 藏品详细样式修改 2026-05-15 23:12:02 +08:00
6d52af4b98 docs: 修复通知系统设计文档问题
修复内容:
- 修正章节编号 8.2 -> 8.3
- 查询逻辑增加 star_id 条件
- HTTP 接口说明增加 star_id 传递方式

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 23:12:02 +08:00
1ccf94c750 docs: 更新通知系统设计方案
主要修改:
- 添加 star_id 数据隔离字段到 notifications 和 notification_stats 表
- 更新索引包含 star_id
- 添加数据一致性保证章节(事务边界、点赞通知防重复唯一约束)
- 所有 API 接口参数增加 starID
- JSON data 示例增加 star_id 字段

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 23:12:02 +08:00
ccb659e52a docs: 添加通知系统设计方案
- 设计统一通知表 (notifications) 存储所有通知类型
- 设计通知统计表 (notification_stats) 支持未读数查询
- 定义 Notification Service 职责和 API 接口
- 支持今日/历史 Tab 查询
- 支持未读数统计、通知直达、批量操作

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 23:12:02 +08:00
c0c5426780 feat: 新增图片,修改应援消耗道具 2026-05-15 23:12:02 +08:00
liulong
a90f7155c6 feat新增光栅卡和镭射卡 2026-05-15 23:06:51 +08:00
liulong
d6b0397230 chore: 归档当前现有代码,准备新建分支开发插件验证 2026-05-15 23:06:51 +08:00
15852260e6 docs:修改文档 2026-05-15 23:06:50 +08:00
af74dd5aad style:修改个人设置页面和藏品详细页的样式 2026-05-15 23:06:50 +08:00
zerosaturation
3ef3510303 feat:修改展示收益 2026-05-15 11:49:00 +08:00
zerosaturation
2a0faeb835 feat: 实现灵感瀑布流双向滚动 Redis 缓存
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-15 11:49:00 +08:00
zerosaturation
2b4077c0cd feat: 添加灵感瀑布流 Redis 会话缓存操作
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-15 11:49:00 +08:00
zerosaturation
29becda3bc docs: 添加灵感瀑布流 Redis 会话缓存实现计划
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-15 11:49:00 +08:00
zerosaturation
7d807d395b feat: JWT 中间件添加 Token 黑名单检查
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-15 11:49:00 +08:00
zerosaturation
5c46ae660f feat: 初始化 Redis 连接
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-15 11:49:00 +08:00
zerosaturation
e599f8a349 feat: 添加 Redis 配置项
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-15 11:49:00 +08:00
zerosaturation
2ebb4a3a4d deps: 添加 redis go-redis/v9 依赖
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-15 11:49:00 +08:00
zerosaturation
07b3520e6e feat: 添加 Redis 客户端和 Token 黑名单模块
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-15 11:49:00 +08:00
zerosaturation
f4f987d068 docs: 修复实现计划中的问题
- 修改 Redis 错误时 fail-closed(安全策略)
- 添加 Task 7 说明后续封禁 API 的扩展方向
- 添加 go.mod replace 说明

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-15 11:49:00 +08:00
zerosaturation
9978310e12 docs: 添加 Redis Token 黑名单实现计划
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-15 11:49:00 +08:00
zerosaturation
ac0eb55bc0 docs: 优化 Redis Token 黑名单设计
- 使用 SHA256 哈希代替原始 token 作为 Key
- 使用 JSON 格式存储 value,避免解析问题
- 添加输入验证

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-15 11:49:00 +08:00
zerosaturation
342beb5f17 docs: 添加 JWT Token 黑名单设计文档
设计 Redis Token 黑名单功能,用于账号封禁和强制下线场景。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-15 11:49:00 +08:00
281e5b5c59 style:修改样式 2026-05-15 11:49:00 +08:00
375816ea79 feat: 添加 ActivityRankingModal 活动榜单弹窗组件
- 新建 ActivityRankingModal.vue 组件,实现单活动排名展示
- TOP3 展示使用 TOP3Card 组件
- 排名列表使用 RankingListItem 组件
- 支持下拉刷新和滚动加载更多
- 当前用户栏固定底部显示
- ThemeBanner 添加 @tap 事件触发弹窗
- index.vue 集成 ActivityRankingModal

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 11:49:00 +08:00
e106a490ce docs: 添加活动榜单弹窗设计文档
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 11:49:00 +08:00
zerosaturation
74182ad662 feat: 新增经济系统 2026-05-14 15:59:07 +08:00
63 changed files with 2282 additions and 2222 deletions

View File

@ -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

View File

@ -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

View File

@ -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),
},
}

View File

@ -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,
})

View File

@ -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,
}
// 可选字段

View File

@ -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 持有者信息

View File

@ -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"` // 是否激活(当前使用)
}

View File

@ -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,
})

View File

@ -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=

View File

@ -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
View 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"
}

View 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"` // 偶像ID0=全服默认
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"
}

View File

@ -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 指定表名

View File

@ -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"
}

View File

@ -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" +

View File

@ -1,6 +1,6 @@
// Code generated by protoc-gen-triple. DO NOT EDIT.
//
// Source: proto/asset.proto
// Source: asset.proto
package asset
import (

View File

@ -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 ()

View File

@ -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,
},

View File

@ -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,

View File

@ -1,6 +1,6 @@
// Code generated by protoc-gen-triple. DO NOT EDIT.
//
// Source: proto/ranking.proto
// Source: ranking.proto
package ranking
import (

View File

@ -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 (

View File

@ -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"

View File

@ -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

View File

@ -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
}

View File

@ -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=
}
//

View File

@ -46,6 +46,9 @@ service GalleryService {
};
}
// RPCID移除展品
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;
}
// ==================== ====================
//

View File

@ -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: "*"; };
}
}

View File

@ -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 {
// RPCassetService调用
rpc UpdateAssetsCount(UpdateAssetsCountRequest) returns (UpdateAssetsCountResponse);
// RPCtaskService调用
rpc AddExperience(AddExperienceRequest) returns (AddExperienceResponse);
// RPCgalleryService调用
rpc AddExhibitionHours(AddExhibitionHoursRequest) returns (AddExhibitionHoursResponse);
rpc GetCurrentUser(GetCurrentUserRequest) returns (GetCurrentUserResponse) {
option (google.api.http) = {

View 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;
-- ============================================================
-- 完成
-- ============================================================

View File

@ -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()

View File

@ -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 $$;
-- ============================================
-- 完成
-- ============================================

View File

@ -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 {

View File

@ -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")

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View 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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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")

View File

@ -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提取用户信息

View File

@ -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
}

View File

@ -169,5 +169,5 @@ func GetMaxFriends() int {
zap.Error(err))
return 50
}
return config.ConfigValue
return int(config.ConfigValue)
}

View File

@ -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
}

View 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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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 workergoroutine 中启动)

View File

@ -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"`

View File

@ -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 {

View File

@ -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",

View File

@ -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)
}

View File

@ -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

View File

@ -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 更新头像

View File

@ -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) {

View File

@ -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-1000=不触发
- `reward_type`:触发奖励类型(收益提升等)
- `reward_value`:奖励值如5代表5%
- `reward_value`:奖励值,单位为 bpsbasis 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. 扣水晶

View File

@ -953,4 +953,4 @@ lock: activity:{activityId}:contributions:lock
1. **多级缓存**:引入本地内存缓存(如 Go 的 `sync.Map`),减少 Redis 请求
2. **窗口动态调整**:根据并发量动态调整窗口大小
3. **监控告警**监控缓存命中率、锁等待时间、DB 查询 QPS
3. **监控告警**监控缓存命中率、锁等待时间、DB 查询 QPS

View File

@ -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>

View File

@ -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));
}

View File

@ -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) => {

View File

@ -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 {