diff --git a/backend/dev.sh b/backend/dev.sh index 6c6531b..2d33019 100755 --- a/backend/dev.sh +++ b/backend/dev.sh @@ -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 diff --git a/backend/gateway/controller/task_controller.go b/backend/gateway/controller/task_controller.go index 4333277..f01a56c 100644 --- a/backend/gateway/controller/task_controller.go +++ b/backend/gateway/controller/task_controller.go @@ -602,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, }) } diff --git a/backend/gateway/dto/asset_converter.go b/backend/gateway/dto/asset_converter.go index 01dd5c4..ed12549 100644 --- a/backend/gateway/dto/asset_converter.go +++ b/backend/gateway/dto/asset_converter.go @@ -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, } // 可选字段 diff --git a/backend/gateway/dto/asset_dto.go b/backend/gateway/dto/asset_dto.go index 8853996..f0ad164 100644 --- a/backend/gateway/dto/asset_dto.go +++ b/backend/gateway/dto/asset_dto.go @@ -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 持有者信息 diff --git a/backend/pkg/models/gallery.go b/backend/pkg/models/gallery.go index f7b4b2e..1b08b14 100644 --- a/backend/pkg/models/gallery.go +++ b/backend/pkg/models/gallery.go @@ -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"` diff --git a/backend/pkg/models/system_config.go b/backend/pkg/models/system_config.go index 61d205c..e2fc983 100644 --- a/backend/pkg/models/system_config.go +++ b/backend/pkg/models/system_config.go @@ -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 指定表名 diff --git a/backend/pkg/proto/asset/asset.pb.go b/backend/pkg/proto/asset/asset.pb.go index 4a192a1..21f1b2a 100644 --- a/backend/pkg/proto/asset/asset.pb.go +++ b/backend/pkg/proto/asset/asset.pb.go @@ -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" + diff --git a/backend/pkg/proto/gallery/gallery.pb.go b/backend/pkg/proto/gallery/gallery.pb.go index 2a2370a..6c6b0aa 100644 --- a/backend/pkg/proto/gallery/gallery.pb.go +++ b/backend/pkg/proto/gallery/gallery.pb.go @@ -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, }, diff --git a/backend/pkg/proto/gallery/gallery.triple.go b/backend/pkg/proto/gallery/gallery.triple.go index da9dbd5..5e94a5b 100644 --- a/backend/pkg/proto/gallery/gallery.triple.go +++ b/backend/pkg/proto/gallery/gallery.triple.go @@ -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, diff --git a/backend/pkg/proto/task/task.pb.go b/backend/pkg/proto/task/task.pb.go index a95424b..f52f929 100644 --- a/backend/pkg/proto/task/task.pb.go +++ b/backend/pkg/proto/task/task.pb.go @@ -1451,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 } @@ -1499,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"` @@ -1969,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" + @@ -1997,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" + @@ -2006,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" diff --git a/backend/proto/asset.proto b/backend/proto/asset.proto index d37e044..917ed51 100644 --- a/backend/proto/asset.proto +++ b/backend/proto/asset.proto @@ -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=未展出) } // 持有者信息 diff --git a/backend/proto/gallery.proto b/backend/proto/gallery.proto index 01831d7..80b5157 100644 --- a/backend/proto/gallery.proto +++ b/backend/proto/gallery.proto @@ -46,6 +46,9 @@ service GalleryService { }; } + // 内部RPC:根据资产ID移除展品(用于领取收益后下架) + rpc RemoveExhibitionByAsset(RemoveExhibitionByAssetRequest) returns (RemoveExhibitionByAssetResponse); + // ========== 我的作品相关 ========== // 获取我展出的作品列表 @@ -162,6 +165,16 @@ message RemoveFromSlotResponse { topfans.common.BaseResponse base = 1; } +// 根据资产ID移除展品请求 +message RemoveExhibitionByAssetRequest { + int64 asset_id = 1; // 资产ID(必填) +} + +// 根据资产ID移除展品响应 +message RemoveExhibitionByAssetResponse { + topfans.common.BaseResponse base = 1; +} + // ==================== 我的作品相关消息 ==================== // 获取我展出的作品列表请求 diff --git a/backend/proto/task.proto b/backend/proto/task.proto index ea9aeb6..ee7940b 100644 --- a/backend/proto/task.proto +++ b/backend/proto/task.proto @@ -160,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 { @@ -228,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: "*"; }; } } diff --git a/backend/scripts/migrate_exhibition_remove_unique_constraint.sql b/backend/scripts/migrate_exhibition_remove_unique_constraint.sql new file mode 100644 index 0000000..2a52132 --- /dev/null +++ b/backend/scripts/migrate_exhibition_remove_unique_constraint.sql @@ -0,0 +1,38 @@ +-- ============================================ +-- 展览表索引和外键修改 +-- 移除 asset_id 的唯一约束,改为普通索引 +-- 执行方式: psql -h -U -d -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 $$; + +-- ============================================ +-- 完成 +-- ============================================ \ No newline at end of file diff --git a/backend/services/assetService/repository/asset_repository.go b/backend/services/assetService/repository/asset_repository.go index 1e7ea90..2ced4f6 100644 --- a/backend/services/assetService/repository/asset_repository.go +++ b/backend/services/assetService/repository/asset_repository.go @@ -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 +} diff --git a/backend/services/galleryService/config/gallery_config.go b/backend/services/galleryService/config/gallery_config.go index c282846..8842da3 100644 --- a/backend/services/galleryService/config/gallery_config.go +++ b/backend/services/galleryService/config/gallery_config.go @@ -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) } diff --git a/backend/services/galleryService/main.go b/backend/services/galleryService/main.go index b30d461..600b352 100644 --- a/backend/services/galleryService/main.go +++ b/backend/services/galleryService/main.go @@ -99,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) diff --git a/backend/services/galleryService/provider/gallery_provider.go b/backend/services/galleryService/provider/gallery_provider.go index b1c8fae..653e3fe 100644 --- a/backend/services/galleryService/provider/gallery_provider.go +++ b/backend/services/galleryService/provider/gallery_provider.go @@ -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提取用户信息 diff --git a/backend/services/galleryService/repository/gallery_repository.go b/backend/services/galleryService/repository/gallery_repository.go index a85a1d5..12aa6b2 100644 --- a/backend/services/galleryService/repository/gallery_repository.go +++ b/backend/services/galleryService/repository/gallery_repository.go @@ -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 // ========== 我的作品相关 ========== @@ -227,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 @@ -235,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) @@ -286,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{}). @@ -313,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 { @@ -363,24 +389,30 @@ 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(` @@ -390,10 +422,10 @@ func (r *galleryRepository) GetMyExhibitedAssets(userID, starID int64, page, pag JOIN assets a ON a.id = exhibitions.asset_id JOIN booth_slots bs ON bs.slot_id = exhibitions.slot_id WHERE exhibitions.occupier_uid = ? AND exhibitions.occupier_star_id = ? - AND exhibitions.deleted_at IS NULL AND exhibitions.expire_at > ? - 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 @@ -401,6 +433,7 @@ func (r *galleryRepository) GetMyExhibitedAssets(userID, starID int64, page, pag // 实时计算展示收益:R1 = R0 × T × [100% + Buff(n)] // R0 = 5 水晶/小时 + now := time.Now().UnixMilli() for _, item := range items { item.Earnings = calculateRealtimeEarnings(item.LikeCount, item.ExhibitedAt, now) } @@ -423,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 @@ -445,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 } diff --git a/backend/services/socialService/config/social_config.go b/backend/services/socialService/config/social_config.go index f04b66a..119970c 100644 --- a/backend/services/socialService/config/social_config.go +++ b/backend/services/socialService/config/social_config.go @@ -169,5 +169,5 @@ func GetMaxFriends() int { zap.Error(err)) return 50 } - return config.ConfigValue + return int(config.ConfigValue) } diff --git a/backend/services/socialService/repository/social_repository.go b/backend/services/socialService/repository/social_repository.go index 9b2df7b..4abea5b 100644 --- a/backend/services/socialService/repository/social_repository.go +++ b/backend/services/socialService/repository/social_repository.go @@ -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 } diff --git a/backend/services/taskService/client/gallery_rpc_client.go b/backend/services/taskService/client/gallery_rpc_client.go new file mode 100644 index 0000000..aa6f0ed --- /dev/null +++ b/backend/services/taskService/client/gallery_rpc_client.go @@ -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 +} \ No newline at end of file diff --git a/backend/services/taskService/config/task_config.go b/backend/services/taskService/config/task_config.go index 010f202..f2e2fb8 100644 --- a/backend/services/taskService/config/task_config.go +++ b/backend/services/taskService/config/task_config.go @@ -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) } diff --git a/backend/services/taskService/main.go b/backend/services/taskService/main.go index b6dcab5..09dba85 100644 --- a/backend/services/taskService/main.go +++ b/backend/services/taskService/main.go @@ -15,6 +15,7 @@ import ( "github.com/topfans/backend/pkg/database" "github.com/topfans/backend/pkg/logger" pb "github.com/topfans/backend/pkg/proto/task" + pbGallery "github.com/topfans/backend/pkg/proto/gallery" pbUser "github.com/topfans/backend/pkg/proto/user" "github.com/topfans/backend/services/taskService/client" "github.com/topfans/backend/services/taskService/config" @@ -89,10 +90,24 @@ func main() { userRPCClient := client.NewUserServiceClient(userServiceClient) logger.Logger.Info("User RPC client initialized") + // 5.1 Init galleryService Dubbo client + RPC client + galleryCli, err := dubboclient.NewClient( + dubboclient.WithClientURL(config.ServiceURLsConfig.GalleryService), + ) + if err != nil { + logger.Logger.Fatal(fmt.Sprintf("Failed to create galleryService client: %v", err)) + } + galleryServiceClient, err := pbGallery.NewGalleryService(galleryCli) + if err != nil { + logger.Logger.Fatal(fmt.Sprintf("Failed to get galleryService: %v", err)) + } + galleryRPCClient := client.NewGalleryServiceClient(galleryServiceClient) + logger.Logger.Info("Gallery RPC client initialized") + // 6. Init services dailySvc := service.NewDailyTaskService(dailyRepo, userRPCClient) onboardingSvc := service.NewOnboardingService(onboardingRepo, dailyRepo, userRPCClient) - revenueSvc := service.NewRevenueService(revenueRepo, userRPCClient) + revenueSvc := service.NewRevenueService(revenueRepo, userRPCClient, galleryRPCClient) logger.Logger.Info("Services initialized") // 7. Init worker(goroutine 中启动) diff --git a/backend/services/userService/repository/fan_profile_repository.go b/backend/services/userService/repository/fan_profile_repository.go index 7950400..b41df0c 100644 --- a/backend/services/userService/repository/fan_profile_repository.go +++ b/backend/services/userService/repository/fan_profile_repository.go @@ -25,10 +25,10 @@ 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 检查昵称是否已存在 @@ -117,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") @@ -136,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") @@ -152,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 } diff --git a/frontend/pages/components/BottomNav.vue b/frontend/pages/components/BottomNav.vue index 0761970..f636e9f 100644 --- a/frontend/pages/components/BottomNav.vue +++ b/frontend/pages/components/BottomNav.vue @@ -181,7 +181,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; } @@ -235,7 +235,7 @@ const handleNavClick = (index) => { 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; } diff --git a/frontend/pages/profile/myWorks.vue b/frontend/pages/profile/myWorks.vue index 4da80b4..d40ef9c 100644 --- a/frontend/pages/profile/myWorks.vue +++ b/frontend/pages/profile/myWorks.vue @@ -169,6 +169,7 @@