feat: 新增历史点赞查询和修改点赞规则

This commit is contained in:
zerosaturation 2026-05-22 18:06:33 +08:00
parent 415d224798
commit 4d42d9a6d0
18 changed files with 928 additions and 168 deletions

View File

@ -885,6 +885,85 @@ func (ctrl *SocialController) UnlikeAsset(c *gin.Context) {
// 注意CheckAssetLike 已被删除
// 检查点赞状态请通过 GET /api/v1/assets/:asset_id 获取,响应中的 is_liked 字段表示当前用户是否已点赞
// GetAssetLikers 获取资产点赞用户列表
// @Summary 获取资产点赞用户列表
// @Description 获取指定藏品的点赞用户列表(带游标分页)
// @Tags social
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param asset_id path int true "资产ID"
// @Param page_size query int false "每页数量默认20最大100"
// @Param cursor query int false "游标(上一页最后一条的 created_at首次请求传0"
// @Success 200 {object} response.Response
// @Router /api/v1/social/assets/{asset_id}/likers [get]
func (ctrl *SocialController) GetAssetLikers(c *gin.Context) {
// 1. 解析资产ID
assetIDStr := c.Param("asset_id")
assetID, err := strconv.ParseInt(assetIDStr, 10, 64)
if err != nil {
response.Error(c, http.StatusBadRequest, "参数错误: asset_id 必须为数字")
return
}
// 2. 解析查询参数
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
cursorStr := c.DefaultQuery("cursor", "0")
cursor, err := strconv.ParseInt(cursorStr, 10, 64)
if err != nil {
cursor = 0
}
// 3. 设置上下文
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
ctx = context.WithValue(ctx, constant.AttachmentKey, map[string]interface{}{
"user_id": "0", // 查询点赞列表不需要 user_id
"star_id": "0",
})
// 4. 调用 RPC
resp, err := ctrl.socialService.GetAssetLikers(ctx, &pbSocial.GetAssetLikersRequest{
AssetId: assetID,
PageSize: int32(pageSize),
Cursor: cursor,
})
if err != nil {
logger.Logger.Error("GetAssetLikers RPC failed",
zap.Int64("asset_id", assetID),
zap.Error(err),
)
response.Error(c, http.StatusInternalServerError, "服务调用失败")
return
}
if resp.Base.Code != pbCommon.StatusCode_STATUS_OK {
response.ErrorWithCode(c, int(resp.Base.Code), resp.Base.Message)
return
}
// 5. 构建响应
users := make([]map[string]interface{}, 0, len(resp.Users))
for _, u := range resp.Users {
users = append(users, map[string]interface{}{
"user_id": u.UserId,
"nickname": u.Nickname,
"avatar": u.Avatar,
"fan_level": u.FanLevel,
"liked_at": u.LikedAt,
})
}
response.Success(c, gin.H{
"users": users,
"total": resp.Total,
"has_more": resp.HasMore,
"next_cursor": resp.NextCursor,
})
}
// GetMyLikedAssets 获取我点赞的作品列表
// @Summary 获取我点赞的作品列表
// @Description 获取当前用户点赞过的作品列表(只返回展出中且未过期的)

View File

@ -174,6 +174,7 @@ func SetupRouter(userClient *client.Client, socialClient *client.Client, assetCl
// 资产点赞操作(保留操作接口,查询状态已集成到资产详情)
social.POST("/assets/:asset_id/like", socialCtrl.LikeAsset) // 点赞资产
social.DELETE("/assets/:asset_id/like", socialCtrl.UnlikeAsset) // 取消点赞
social.GET("/assets/:asset_id/likers", socialCtrl.GetAssetLikers) // 获取点赞用户列表
// 注意GET /api/v1/assets/:asset_id 会返回 is_liked 字段,无需单独查询
}

View File

@ -13,8 +13,26 @@ import (
const (
BlacklistKeyPrefix = "blacklist:token:"
InspirationFlowKeyPrefix = "inspiration_flow:"
AssetLikersKeyPrefix = "asset_likers:"
)
// AssetLikersCache 缓存数据结构
type AssetLikersCache struct {
Users []AssetLikerWithTotal `json:"users"` // 完整用户列表(按 liked_at DESC
Total int64 `json:"total"` // 总数
UpdatedAt int64 `json:"updated_at"` // 缓存更新时间
}
// AssetLikerWithTotal 用户+点赞时间
type AssetLikerWithTotal struct {
UserID int64 `json:"user_id"`
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
FanLevel int32 `json:"fan_level"`
LikedAt int64 `json:"liked_at"`
StarID int64 `json:"star_id"` // 用于 JOIN fan_profiles缓存时保留
}
// RedisClient Redis 客户端单例
var RedisClient *redis.Client
@ -249,3 +267,55 @@ func GetHistoryPage(cache *InspirationFlowCache, offset, limit int) []Inspiratio
}
return items[start:end]
}
// AssetLikersKey 生成藏品点赞用户列表缓存 Key
func AssetLikersKey(assetID int64) string {
return fmt.Sprintf("%s%d", AssetLikersKeyPrefix, assetID)
}
// GetAssetLikersCache 获取藏品点赞用户列表缓存
func GetAssetLikersCache(ctx context.Context, assetID int64) (*AssetLikersCache, error) {
if RedisClient == nil {
return nil, fmt.Errorf("redis client is not initialized")
}
key := AssetLikersKey(assetID)
data, err := RedisClient.Get(ctx, key).Result()
if err == redis.Nil {
return nil, nil // 缓存不存在
}
if err != nil {
return nil, err
}
var cache AssetLikersCache
if err := json.Unmarshal([]byte(data), &cache); err != nil {
return nil, err
}
return &cache, nil
}
// SetAssetLikersCache 设置藏品点赞用户列表缓存
func SetAssetLikersCache(ctx context.Context, assetID int64, cache *AssetLikersCache, ttl time.Duration) error {
if RedisClient == nil {
return fmt.Errorf("redis client is not initialized")
}
key := AssetLikersKey(assetID)
data, err := json.Marshal(cache)
if err != nil {
return err
}
return RedisClient.Set(ctx, key, data, ttl).Err()
}
// InvalidateAssetLikersCache 删除藏品点赞用户列表缓存
func InvalidateAssetLikersCache(ctx context.Context, assetID int64) error {
if RedisClient == nil {
return nil // Redis 未初始化时跳过
}
key := AssetLikersKey(assetID)
return RedisClient.Del(ctx, key).Err()
}

View File

@ -2148,6 +2148,221 @@ func (x *CheckAssetLikeResponse) GetIsLiked() bool {
return false
}
// 获取资产点赞用户列表请求
type GetAssetLikersRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
AssetId int64 `protobuf:"varint,1,opt,name=asset_id,json=assetId,proto3" json:"asset_id,omitempty"` // 资产ID
PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` // 每页数量默认20最大100
Cursor int64 `protobuf:"varint,3,opt,name=cursor,proto3" json:"cursor,omitempty"` // 游标(上一页最后一条的 created_at首次请求传0
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *GetAssetLikersRequest) Reset() {
*x = GetAssetLikersRequest{}
mi := &file_social_proto_msgTypes[33]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *GetAssetLikersRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetAssetLikersRequest) ProtoMessage() {}
func (x *GetAssetLikersRequest) ProtoReflect() protoreflect.Message {
mi := &file_social_proto_msgTypes[33]
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 GetAssetLikersRequest.ProtoReflect.Descriptor instead.
func (*GetAssetLikersRequest) Descriptor() ([]byte, []int) {
return file_social_proto_rawDescGZIP(), []int{33}
}
func (x *GetAssetLikersRequest) GetAssetId() int64 {
if x != nil {
return x.AssetId
}
return 0
}
func (x *GetAssetLikersRequest) GetPageSize() int32 {
if x != nil {
return x.PageSize
}
return 0
}
func (x *GetAssetLikersRequest) GetCursor() int64 {
if x != nil {
return x.Cursor
}
return 0
}
// 获取资产点赞用户列表响应
type GetAssetLikersResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Base *common.BaseResponse `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"`
Users []*AssetLiker `protobuf:"bytes,2,rep,name=users,proto3" json:"users,omitempty"` // 点赞用户列表
Total int64 `protobuf:"varint,3,opt,name=total,proto3" json:"total,omitempty"` // 总数
HasMore bool `protobuf:"varint,4,opt,name=has_more,json=hasMore,proto3" json:"has_more,omitempty"` // 是否有更多
NextCursor int64 `protobuf:"varint,5,opt,name=next_cursor,json=nextCursor,proto3" json:"next_cursor,omitempty"` // 下一页游标
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *GetAssetLikersResponse) Reset() {
*x = GetAssetLikersResponse{}
mi := &file_social_proto_msgTypes[34]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *GetAssetLikersResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetAssetLikersResponse) ProtoMessage() {}
func (x *GetAssetLikersResponse) ProtoReflect() protoreflect.Message {
mi := &file_social_proto_msgTypes[34]
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 GetAssetLikersResponse.ProtoReflect.Descriptor instead.
func (*GetAssetLikersResponse) Descriptor() ([]byte, []int) {
return file_social_proto_rawDescGZIP(), []int{34}
}
func (x *GetAssetLikersResponse) GetBase() *common.BaseResponse {
if x != nil {
return x.Base
}
return nil
}
func (x *GetAssetLikersResponse) GetUsers() []*AssetLiker {
if x != nil {
return x.Users
}
return nil
}
func (x *GetAssetLikersResponse) GetTotal() int64 {
if x != nil {
return x.Total
}
return 0
}
func (x *GetAssetLikersResponse) GetHasMore() bool {
if x != nil {
return x.HasMore
}
return false
}
func (x *GetAssetLikersResponse) GetNextCursor() int64 {
if x != nil {
return x.NextCursor
}
return 0
}
// 点赞用户信息
type AssetLiker struct {
state protoimpl.MessageState `protogen:"open.v1"`
UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` // 用户ID
Nickname string `protobuf:"bytes,2,opt,name=nickname,proto3" json:"nickname,omitempty"` // 昵称
Avatar string `protobuf:"bytes,3,opt,name=avatar,proto3" json:"avatar,omitempty"` // 头像URL
FanLevel int32 `protobuf:"varint,4,opt,name=fan_level,json=fanLevel,proto3" json:"fan_level,omitempty"` // 粉丝等级
LikedAt int64 `protobuf:"varint,5,opt,name=liked_at,json=likedAt,proto3" json:"liked_at,omitempty"` // 点赞时间(毫秒时间戳)
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *AssetLiker) Reset() {
*x = AssetLiker{}
mi := &file_social_proto_msgTypes[35]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *AssetLiker) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AssetLiker) ProtoMessage() {}
func (x *AssetLiker) ProtoReflect() protoreflect.Message {
mi := &file_social_proto_msgTypes[35]
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 AssetLiker.ProtoReflect.Descriptor instead.
func (*AssetLiker) Descriptor() ([]byte, []int) {
return file_social_proto_rawDescGZIP(), []int{35}
}
func (x *AssetLiker) GetUserId() int64 {
if x != nil {
return x.UserId
}
return 0
}
func (x *AssetLiker) GetNickname() string {
if x != nil {
return x.Nickname
}
return ""
}
func (x *AssetLiker) GetAvatar() string {
if x != nil {
return x.Avatar
}
return ""
}
func (x *AssetLiker) GetFanLevel() int32 {
if x != nil {
return x.FanLevel
}
return 0
}
func (x *AssetLiker) GetLikedAt() int64 {
if x != nil {
return x.LikedAt
}
return 0
}
// 获取我点赞的作品列表请求
type GetMyLikedAssetsRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
@ -2160,7 +2375,7 @@ type GetMyLikedAssetsRequest struct {
func (x *GetMyLikedAssetsRequest) Reset() {
*x = GetMyLikedAssetsRequest{}
mi := &file_social_proto_msgTypes[33]
mi := &file_social_proto_msgTypes[36]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -2172,7 +2387,7 @@ func (x *GetMyLikedAssetsRequest) String() string {
func (*GetMyLikedAssetsRequest) ProtoMessage() {}
func (x *GetMyLikedAssetsRequest) ProtoReflect() protoreflect.Message {
mi := &file_social_proto_msgTypes[33]
mi := &file_social_proto_msgTypes[36]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -2185,7 +2400,7 @@ func (x *GetMyLikedAssetsRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetMyLikedAssetsRequest.ProtoReflect.Descriptor instead.
func (*GetMyLikedAssetsRequest) Descriptor() ([]byte, []int) {
return file_social_proto_rawDescGZIP(), []int{33}
return file_social_proto_rawDescGZIP(), []int{36}
}
func (x *GetMyLikedAssetsRequest) GetPage() int32 {
@ -2220,7 +2435,7 @@ type GetMyLikedAssetsResponse struct {
func (x *GetMyLikedAssetsResponse) Reset() {
*x = GetMyLikedAssetsResponse{}
mi := &file_social_proto_msgTypes[34]
mi := &file_social_proto_msgTypes[37]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -2232,7 +2447,7 @@ func (x *GetMyLikedAssetsResponse) String() string {
func (*GetMyLikedAssetsResponse) ProtoMessage() {}
func (x *GetMyLikedAssetsResponse) ProtoReflect() protoreflect.Message {
mi := &file_social_proto_msgTypes[34]
mi := &file_social_proto_msgTypes[37]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -2245,7 +2460,7 @@ func (x *GetMyLikedAssetsResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetMyLikedAssetsResponse.ProtoReflect.Descriptor instead.
func (*GetMyLikedAssetsResponse) Descriptor() ([]byte, []int) {
return file_social_proto_rawDescGZIP(), []int{34}
return file_social_proto_rawDescGZIP(), []int{37}
}
func (x *GetMyLikedAssetsResponse) GetBase() *common.BaseResponse {
@ -2276,7 +2491,7 @@ type LikedAssetsData struct {
func (x *LikedAssetsData) Reset() {
*x = LikedAssetsData{}
mi := &file_social_proto_msgTypes[35]
mi := &file_social_proto_msgTypes[38]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -2288,7 +2503,7 @@ func (x *LikedAssetsData) String() string {
func (*LikedAssetsData) ProtoMessage() {}
func (x *LikedAssetsData) ProtoReflect() protoreflect.Message {
mi := &file_social_proto_msgTypes[35]
mi := &file_social_proto_msgTypes[38]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -2301,7 +2516,7 @@ func (x *LikedAssetsData) ProtoReflect() protoreflect.Message {
// Deprecated: Use LikedAssetsData.ProtoReflect.Descriptor instead.
func (*LikedAssetsData) Descriptor() ([]byte, []int) {
return file_social_proto_rawDescGZIP(), []int{35}
return file_social_proto_rawDescGZIP(), []int{38}
}
func (x *LikedAssetsData) GetItems() []*LikedAssetItem {
@ -2357,7 +2572,7 @@ type LikedAssetItem struct {
func (x *LikedAssetItem) Reset() {
*x = LikedAssetItem{}
mi := &file_social_proto_msgTypes[36]
mi := &file_social_proto_msgTypes[39]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -2369,7 +2584,7 @@ func (x *LikedAssetItem) String() string {
func (*LikedAssetItem) ProtoMessage() {}
func (x *LikedAssetItem) ProtoReflect() protoreflect.Message {
mi := &file_social_proto_msgTypes[36]
mi := &file_social_proto_msgTypes[39]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -2382,7 +2597,7 @@ func (x *LikedAssetItem) ProtoReflect() protoreflect.Message {
// Deprecated: Use LikedAssetItem.ProtoReflect.Descriptor instead.
func (*LikedAssetItem) Descriptor() ([]byte, []int) {
return file_social_proto_rawDescGZIP(), []int{36}
return file_social_proto_rawDescGZIP(), []int{39}
}
func (x *LikedAssetItem) GetAssetId() int64 {
@ -2459,7 +2674,7 @@ type GetMyTodayLikedAssetsRequest struct {
func (x *GetMyTodayLikedAssetsRequest) Reset() {
*x = GetMyTodayLikedAssetsRequest{}
mi := &file_social_proto_msgTypes[37]
mi := &file_social_proto_msgTypes[40]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -2471,7 +2686,7 @@ func (x *GetMyTodayLikedAssetsRequest) String() string {
func (*GetMyTodayLikedAssetsRequest) ProtoMessage() {}
func (x *GetMyTodayLikedAssetsRequest) ProtoReflect() protoreflect.Message {
mi := &file_social_proto_msgTypes[37]
mi := &file_social_proto_msgTypes[40]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -2484,7 +2699,7 @@ func (x *GetMyTodayLikedAssetsRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetMyTodayLikedAssetsRequest.ProtoReflect.Descriptor instead.
func (*GetMyTodayLikedAssetsRequest) Descriptor() ([]byte, []int) {
return file_social_proto_rawDescGZIP(), []int{37}
return file_social_proto_rawDescGZIP(), []int{40}
}
func (x *GetMyTodayLikedAssetsRequest) GetPage() int32 {
@ -2512,7 +2727,7 @@ type GetMyTodayLikedAssetsResponse struct {
func (x *GetMyTodayLikedAssetsResponse) Reset() {
*x = GetMyTodayLikedAssetsResponse{}
mi := &file_social_proto_msgTypes[38]
mi := &file_social_proto_msgTypes[41]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -2524,7 +2739,7 @@ func (x *GetMyTodayLikedAssetsResponse) String() string {
func (*GetMyTodayLikedAssetsResponse) ProtoMessage() {}
func (x *GetMyTodayLikedAssetsResponse) ProtoReflect() protoreflect.Message {
mi := &file_social_proto_msgTypes[38]
mi := &file_social_proto_msgTypes[41]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -2537,7 +2752,7 @@ func (x *GetMyTodayLikedAssetsResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetMyTodayLikedAssetsResponse.ProtoReflect.Descriptor instead.
func (*GetMyTodayLikedAssetsResponse) Descriptor() ([]byte, []int) {
return file_social_proto_rawDescGZIP(), []int{38}
return file_social_proto_rawDescGZIP(), []int{41}
}
func (x *GetMyTodayLikedAssetsResponse) GetBase() *common.BaseResponse {
@ -2565,7 +2780,7 @@ type GetMyWeekLikedAssetsRequest struct {
func (x *GetMyWeekLikedAssetsRequest) Reset() {
*x = GetMyWeekLikedAssetsRequest{}
mi := &file_social_proto_msgTypes[39]
mi := &file_social_proto_msgTypes[42]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -2577,7 +2792,7 @@ func (x *GetMyWeekLikedAssetsRequest) String() string {
func (*GetMyWeekLikedAssetsRequest) ProtoMessage() {}
func (x *GetMyWeekLikedAssetsRequest) ProtoReflect() protoreflect.Message {
mi := &file_social_proto_msgTypes[39]
mi := &file_social_proto_msgTypes[42]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -2590,7 +2805,7 @@ func (x *GetMyWeekLikedAssetsRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetMyWeekLikedAssetsRequest.ProtoReflect.Descriptor instead.
func (*GetMyWeekLikedAssetsRequest) Descriptor() ([]byte, []int) {
return file_social_proto_rawDescGZIP(), []int{39}
return file_social_proto_rawDescGZIP(), []int{42}
}
func (x *GetMyWeekLikedAssetsRequest) GetPage() int32 {
@ -2618,7 +2833,7 @@ type GetMyWeekLikedAssetsResponse struct {
func (x *GetMyWeekLikedAssetsResponse) Reset() {
*x = GetMyWeekLikedAssetsResponse{}
mi := &file_social_proto_msgTypes[40]
mi := &file_social_proto_msgTypes[43]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -2630,7 +2845,7 @@ func (x *GetMyWeekLikedAssetsResponse) String() string {
func (*GetMyWeekLikedAssetsResponse) ProtoMessage() {}
func (x *GetMyWeekLikedAssetsResponse) ProtoReflect() protoreflect.Message {
mi := &file_social_proto_msgTypes[40]
mi := &file_social_proto_msgTypes[43]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -2643,7 +2858,7 @@ func (x *GetMyWeekLikedAssetsResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetMyWeekLikedAssetsResponse.ProtoReflect.Descriptor instead.
func (*GetMyWeekLikedAssetsResponse) Descriptor() ([]byte, []int) {
return file_social_proto_rawDescGZIP(), []int{40}
return file_social_proto_rawDescGZIP(), []int{43}
}
func (x *GetMyWeekLikedAssetsResponse) GetBase() *common.BaseResponse {
@ -2672,7 +2887,7 @@ type GetUserLikedAssetsRequest struct {
func (x *GetUserLikedAssetsRequest) Reset() {
*x = GetUserLikedAssetsRequest{}
mi := &file_social_proto_msgTypes[41]
mi := &file_social_proto_msgTypes[44]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -2684,7 +2899,7 @@ func (x *GetUserLikedAssetsRequest) String() string {
func (*GetUserLikedAssetsRequest) ProtoMessage() {}
func (x *GetUserLikedAssetsRequest) ProtoReflect() protoreflect.Message {
mi := &file_social_proto_msgTypes[41]
mi := &file_social_proto_msgTypes[44]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -2697,7 +2912,7 @@ func (x *GetUserLikedAssetsRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetUserLikedAssetsRequest.ProtoReflect.Descriptor instead.
func (*GetUserLikedAssetsRequest) Descriptor() ([]byte, []int) {
return file_social_proto_rawDescGZIP(), []int{41}
return file_social_proto_rawDescGZIP(), []int{44}
}
func (x *GetUserLikedAssetsRequest) GetUserId() int64 {
@ -2732,7 +2947,7 @@ type GetUserLikedAssetsResponse struct {
func (x *GetUserLikedAssetsResponse) Reset() {
*x = GetUserLikedAssetsResponse{}
mi := &file_social_proto_msgTypes[42]
mi := &file_social_proto_msgTypes[45]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -2744,7 +2959,7 @@ func (x *GetUserLikedAssetsResponse) String() string {
func (*GetUserLikedAssetsResponse) ProtoMessage() {}
func (x *GetUserLikedAssetsResponse) ProtoReflect() protoreflect.Message {
mi := &file_social_proto_msgTypes[42]
mi := &file_social_proto_msgTypes[45]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -2757,7 +2972,7 @@ func (x *GetUserLikedAssetsResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetUserLikedAssetsResponse.ProtoReflect.Descriptor instead.
func (*GetUserLikedAssetsResponse) Descriptor() ([]byte, []int) {
return file_social_proto_rawDescGZIP(), []int{42}
return file_social_proto_rawDescGZIP(), []int{45}
}
func (x *GetUserLikedAssetsResponse) GetBase() *common.BaseResponse {
@ -2939,7 +3154,25 @@ const file_social_proto_rawDesc = "" +
"\basset_id\x18\x01 \x01(\x03R\aassetId\"e\n" +
"\x16CheckAssetLikeResponse\x120\n" +
"\x04base\x18\x01 \x01(\v2\x1c.topfans.common.BaseResponseR\x04base\x12\x19\n" +
"\bis_liked\x18\x02 \x01(\bR\aisLiked\"e\n" +
"\bis_liked\x18\x02 \x01(\bR\aisLiked\"g\n" +
"\x15GetAssetLikersRequest\x12\x19\n" +
"\basset_id\x18\x01 \x01(\x03R\aassetId\x12\x1b\n" +
"\tpage_size\x18\x02 \x01(\x05R\bpageSize\x12\x16\n" +
"\x06cursor\x18\x03 \x01(\x03R\x06cursor\"\xce\x01\n" +
"\x16GetAssetLikersResponse\x120\n" +
"\x04base\x18\x01 \x01(\v2\x1c.topfans.common.BaseResponseR\x04base\x120\n" +
"\x05users\x18\x02 \x03(\v2\x1a.topfans.social.AssetLikerR\x05users\x12\x14\n" +
"\x05total\x18\x03 \x01(\x03R\x05total\x12\x19\n" +
"\bhas_more\x18\x04 \x01(\bR\ahasMore\x12\x1f\n" +
"\vnext_cursor\x18\x05 \x01(\x03R\n" +
"nextCursor\"\x91\x01\n" +
"\n" +
"AssetLiker\x12\x17\n" +
"\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x1a\n" +
"\bnickname\x18\x02 \x01(\tR\bnickname\x12\x16\n" +
"\x06avatar\x18\x03 \x01(\tR\x06avatar\x12\x1b\n" +
"\tfan_level\x18\x04 \x01(\x05R\bfanLevel\x12\x19\n" +
"\bliked_at\x18\x05 \x01(\x03R\alikedAt\"e\n" +
"\x17GetMyLikedAssetsRequest\x12\x12\n" +
"\x04page\x18\x01 \x01(\x05R\x04page\x12\x1b\n" +
"\tpage_size\x18\x02 \x01(\x05R\bpageSize\x12\x19\n" +
@ -2982,7 +3215,7 @@ const file_social_proto_rawDesc = "" +
"\tpage_size\x18\x03 \x01(\x05R\bpageSize\"\x83\x01\n" +
"\x1aGetUserLikedAssetsResponse\x120\n" +
"\x04base\x18\x01 \x01(\v2\x1c.topfans.common.BaseResponseR\x04base\x123\n" +
"\x04data\x18\x02 \x01(\v2\x1f.topfans.social.LikedAssetsDataR\x04data2\x94\x14\n" +
"\x04data\x18\x02 \x01(\v2\x1f.topfans.social.LikedAssetsDataR\x04data2\xa7\x15\n" +
"\rSocialService\x12\x8d\x01\n" +
"\x11SendFriendRequest\x12(.topfans.social.SendFriendRequestRequest\x1a).topfans.social.SendFriendRequestResponse\"#\x82\xd3\xe4\x93\x02\x1d:\x01*\"\x18/api/v1/friends/requests\x12\x8a\x01\n" +
"\x11GetFriendRequests\x12(.topfans.social.GetFriendRequestsRequest\x1a).topfans.social.GetFriendRequestsResponse\" \x82\xd3\xe4\x93\x02\x1a\x12\x18/api/v1/friends/requests\x12\xa7\x01\n" +
@ -2997,7 +3230,8 @@ const file_social_proto_rawDesc = "" +
"\rGetUsersPaged\x12$.topfans.social.GetUsersPagedRequest\x1a%.topfans.social.GetUsersPagedResponse\"\x1c\x82\xd3\xe4\x93\x02\x16\x12\x14/api/v1/social/users\x12\x7f\n" +
"\tLikeAsset\x12 .topfans.social.LikeAssetRequest\x1a!.topfans.social.LikeAssetResponse\"-\x82\xd3\xe4\x93\x02'\"%/api/v1/social/assets/{asset_id}/like\x12\x85\x01\n" +
"\vUnlikeAsset\x12\".topfans.social.UnlikeAssetRequest\x1a#.topfans.social.UnlikeAssetResponse\"-\x82\xd3\xe4\x93\x02'*%/api/v1/social/assets/{asset_id}/like\x12\x8e\x01\n" +
"\x0eCheckAssetLike\x12%.topfans.social.CheckAssetLikeRequest\x1a&.topfans.social.CheckAssetLikeResponse\"-\x82\xd3\xe4\x93\x02'\x12%/api/v1/social/assets/{asset_id}/like\x12\x86\x01\n" +
"\x0eCheckAssetLike\x12%.topfans.social.CheckAssetLikeRequest\x1a&.topfans.social.CheckAssetLikeResponse\"-\x82\xd3\xe4\x93\x02'\x12%/api/v1/social/assets/{asset_id}/like\x12\x90\x01\n" +
"\x0eGetAssetLikers\x12%.topfans.social.GetAssetLikersRequest\x1a&.topfans.social.GetAssetLikersResponse\"/\x82\xd3\xe4\x93\x02)\x12'/api/v1/social/assets/{asset_id}/likers\x12\x86\x01\n" +
"\x10GetMyLikedAssets\x12'.topfans.social.GetMyLikedAssetsRequest\x1a(.topfans.social.GetMyLikedAssetsResponse\"\x1f\x82\xd3\xe4\x93\x02\x19\x12\x17/api/v1/me/liked-assets\x12\x9b\x01\n" +
"\x15GetMyTodayLikedAssets\x12,.topfans.social.GetMyTodayLikedAssetsRequest\x1a-.topfans.social.GetMyTodayLikedAssetsResponse\"%\x82\xd3\xe4\x93\x02\x1f\x12\x1d/api/v1/me/today-liked-assets\x12\x97\x01\n" +
"\x14GetMyWeekLikedAssets\x12+.topfans.social.GetMyWeekLikedAssetsRequest\x1a,.topfans.social.GetMyWeekLikedAssetsResponse\"$\x82\xd3\xe4\x93\x02\x1e\x12\x1c/api/v1/me/week-liked-assets\x12\x99\x01\n" +
@ -3015,7 +3249,7 @@ func file_social_proto_rawDescGZIP() []byte {
return file_social_proto_rawDescData
}
var file_social_proto_msgTypes = make([]protoimpl.MessageInfo, 43)
var file_social_proto_msgTypes = make([]protoimpl.MessageInfo, 46)
var file_social_proto_goTypes = []any{
(*FriendRequest)(nil), // 0: topfans.social.FriendRequest
(*Friendship)(nil), // 1: topfans.social.Friendship
@ -3050,89 +3284,96 @@ var file_social_proto_goTypes = []any{
(*UnlikeAssetResponse)(nil), // 30: topfans.social.UnlikeAssetResponse
(*CheckAssetLikeRequest)(nil), // 31: topfans.social.CheckAssetLikeRequest
(*CheckAssetLikeResponse)(nil), // 32: topfans.social.CheckAssetLikeResponse
(*GetMyLikedAssetsRequest)(nil), // 33: topfans.social.GetMyLikedAssetsRequest
(*GetMyLikedAssetsResponse)(nil), // 34: topfans.social.GetMyLikedAssetsResponse
(*LikedAssetsData)(nil), // 35: topfans.social.LikedAssetsData
(*LikedAssetItem)(nil), // 36: topfans.social.LikedAssetItem
(*GetMyTodayLikedAssetsRequest)(nil), // 37: topfans.social.GetMyTodayLikedAssetsRequest
(*GetMyTodayLikedAssetsResponse)(nil), // 38: topfans.social.GetMyTodayLikedAssetsResponse
(*GetMyWeekLikedAssetsRequest)(nil), // 39: topfans.social.GetMyWeekLikedAssetsRequest
(*GetMyWeekLikedAssetsResponse)(nil), // 40: topfans.social.GetMyWeekLikedAssetsResponse
(*GetUserLikedAssetsRequest)(nil), // 41: topfans.social.GetUserLikedAssetsRequest
(*GetUserLikedAssetsResponse)(nil), // 42: topfans.social.GetUserLikedAssetsResponse
(*common.BaseResponse)(nil), // 43: topfans.common.BaseResponse
(*GetAssetLikersRequest)(nil), // 33: topfans.social.GetAssetLikersRequest
(*GetAssetLikersResponse)(nil), // 34: topfans.social.GetAssetLikersResponse
(*AssetLiker)(nil), // 35: topfans.social.AssetLiker
(*GetMyLikedAssetsRequest)(nil), // 36: topfans.social.GetMyLikedAssetsRequest
(*GetMyLikedAssetsResponse)(nil), // 37: topfans.social.GetMyLikedAssetsResponse
(*LikedAssetsData)(nil), // 38: topfans.social.LikedAssetsData
(*LikedAssetItem)(nil), // 39: topfans.social.LikedAssetItem
(*GetMyTodayLikedAssetsRequest)(nil), // 40: topfans.social.GetMyTodayLikedAssetsRequest
(*GetMyTodayLikedAssetsResponse)(nil), // 41: topfans.social.GetMyTodayLikedAssetsResponse
(*GetMyWeekLikedAssetsRequest)(nil), // 42: topfans.social.GetMyWeekLikedAssetsRequest
(*GetMyWeekLikedAssetsResponse)(nil), // 43: topfans.social.GetMyWeekLikedAssetsResponse
(*GetUserLikedAssetsRequest)(nil), // 44: topfans.social.GetUserLikedAssetsRequest
(*GetUserLikedAssetsResponse)(nil), // 45: topfans.social.GetUserLikedAssetsResponse
(*common.BaseResponse)(nil), // 46: topfans.common.BaseResponse
}
var file_social_proto_depIdxs = []int32{
43, // 0: topfans.social.SendFriendRequestResponse.base:type_name -> topfans.common.BaseResponse
46, // 0: topfans.social.SendFriendRequestResponse.base:type_name -> topfans.common.BaseResponse
20, // 1: topfans.social.SendFriendRequestResponse.matched_users:type_name -> topfans.social.FanProfileSearchResult
43, // 2: topfans.social.GetFriendRequestsResponse.base:type_name -> topfans.common.BaseResponse
46, // 2: topfans.social.GetFriendRequestsResponse.base:type_name -> topfans.common.BaseResponse
0, // 3: topfans.social.GetFriendRequestsResponse.items:type_name -> topfans.social.FriendRequest
43, // 4: topfans.social.HandleFriendRequestResponse.base:type_name -> topfans.common.BaseResponse
43, // 5: topfans.social.GetFriendListResponse.base:type_name -> topfans.common.BaseResponse
46, // 4: topfans.social.HandleFriendRequestResponse.base:type_name -> topfans.common.BaseResponse
46, // 5: topfans.social.GetFriendListResponse.base:type_name -> topfans.common.BaseResponse
1, // 6: topfans.social.GetFriendListResponse.items:type_name -> topfans.social.Friendship
43, // 7: topfans.social.DeleteFriendResponse.base:type_name -> topfans.common.BaseResponse
43, // 8: topfans.social.SetFriendRemarkResponse.base:type_name -> topfans.common.BaseResponse
43, // 9: topfans.social.CheckFriendshipResponse.base:type_name -> topfans.common.BaseResponse
43, // 10: topfans.social.GetFriendCountResponse.base:type_name -> topfans.common.BaseResponse
43, // 11: topfans.social.SearchUserForFriendResponse.base:type_name -> topfans.common.BaseResponse
46, // 7: topfans.social.DeleteFriendResponse.base:type_name -> topfans.common.BaseResponse
46, // 8: topfans.social.SetFriendRemarkResponse.base:type_name -> topfans.common.BaseResponse
46, // 9: topfans.social.CheckFriendshipResponse.base:type_name -> topfans.common.BaseResponse
46, // 10: topfans.social.GetFriendCountResponse.base:type_name -> topfans.common.BaseResponse
46, // 11: topfans.social.SearchUserForFriendResponse.base:type_name -> topfans.common.BaseResponse
20, // 12: topfans.social.SearchUserForFriendResponse.user:type_name -> topfans.social.FanProfileSearchResult
43, // 13: topfans.social.GetRandomUsersResponse.base:type_name -> topfans.common.BaseResponse
46, // 13: topfans.social.GetRandomUsersResponse.base:type_name -> topfans.common.BaseResponse
21, // 14: topfans.social.GetRandomUsersResponse.users:type_name -> topfans.social.RandomUser
43, // 15: topfans.social.GetUsersPagedResponse.base:type_name -> topfans.common.BaseResponse
46, // 15: topfans.social.GetUsersPagedResponse.base:type_name -> topfans.common.BaseResponse
24, // 16: topfans.social.GetUsersPagedResponse.users:type_name -> topfans.social.PagedUser
43, // 17: topfans.social.LikeAssetResponse.base:type_name -> topfans.common.BaseResponse
43, // 18: topfans.social.UnlikeAssetResponse.base:type_name -> topfans.common.BaseResponse
43, // 19: topfans.social.CheckAssetLikeResponse.base:type_name -> topfans.common.BaseResponse
43, // 20: topfans.social.GetMyLikedAssetsResponse.base:type_name -> topfans.common.BaseResponse
35, // 21: topfans.social.GetMyLikedAssetsResponse.data:type_name -> topfans.social.LikedAssetsData
36, // 22: topfans.social.LikedAssetsData.items:type_name -> topfans.social.LikedAssetItem
43, // 23: topfans.social.GetMyTodayLikedAssetsResponse.base:type_name -> topfans.common.BaseResponse
35, // 24: topfans.social.GetMyTodayLikedAssetsResponse.data:type_name -> topfans.social.LikedAssetsData
43, // 25: topfans.social.GetMyWeekLikedAssetsResponse.base:type_name -> topfans.common.BaseResponse
35, // 26: topfans.social.GetMyWeekLikedAssetsResponse.data:type_name -> topfans.social.LikedAssetsData
43, // 27: topfans.social.GetUserLikedAssetsResponse.base:type_name -> topfans.common.BaseResponse
35, // 28: topfans.social.GetUserLikedAssetsResponse.data:type_name -> topfans.social.LikedAssetsData
2, // 29: topfans.social.SocialService.SendFriendRequest:input_type -> topfans.social.SendFriendRequestRequest
4, // 30: topfans.social.SocialService.GetFriendRequests:input_type -> topfans.social.GetFriendRequestsRequest
6, // 31: topfans.social.SocialService.HandleFriendRequest:input_type -> topfans.social.HandleFriendRequestRequest
8, // 32: topfans.social.SocialService.GetFriendList:input_type -> topfans.social.GetFriendListRequest
10, // 33: topfans.social.SocialService.DeleteFriend:input_type -> topfans.social.DeleteFriendRequest
12, // 34: topfans.social.SocialService.SetFriendRemark:input_type -> topfans.social.SetFriendRemarkRequest
14, // 35: topfans.social.SocialService.CheckFriendship:input_type -> topfans.social.CheckFriendshipRequest
16, // 36: topfans.social.SocialService.GetFriendCount:input_type -> topfans.social.GetFriendCountRequest
18, // 37: topfans.social.SocialService.SearchUserForFriend:input_type -> topfans.social.SearchUserForFriendRequest
22, // 38: topfans.social.SocialService.GetRandomUsers:input_type -> topfans.social.GetRandomUsersRequest
25, // 39: topfans.social.SocialService.GetUsersPaged:input_type -> topfans.social.GetUsersPagedRequest
27, // 40: topfans.social.SocialService.LikeAsset:input_type -> topfans.social.LikeAssetRequest
29, // 41: topfans.social.SocialService.UnlikeAsset:input_type -> topfans.social.UnlikeAssetRequest
31, // 42: topfans.social.SocialService.CheckAssetLike:input_type -> topfans.social.CheckAssetLikeRequest
33, // 43: topfans.social.SocialService.GetMyLikedAssets:input_type -> topfans.social.GetMyLikedAssetsRequest
37, // 44: topfans.social.SocialService.GetMyTodayLikedAssets:input_type -> topfans.social.GetMyTodayLikedAssetsRequest
39, // 45: topfans.social.SocialService.GetMyWeekLikedAssets:input_type -> topfans.social.GetMyWeekLikedAssetsRequest
41, // 46: topfans.social.SocialService.GetUserLikedAssets:input_type -> topfans.social.GetUserLikedAssetsRequest
3, // 47: topfans.social.SocialService.SendFriendRequest:output_type -> topfans.social.SendFriendRequestResponse
5, // 48: topfans.social.SocialService.GetFriendRequests:output_type -> topfans.social.GetFriendRequestsResponse
7, // 49: topfans.social.SocialService.HandleFriendRequest:output_type -> topfans.social.HandleFriendRequestResponse
9, // 50: topfans.social.SocialService.GetFriendList:output_type -> topfans.social.GetFriendListResponse
11, // 51: topfans.social.SocialService.DeleteFriend:output_type -> topfans.social.DeleteFriendResponse
13, // 52: topfans.social.SocialService.SetFriendRemark:output_type -> topfans.social.SetFriendRemarkResponse
15, // 53: topfans.social.SocialService.CheckFriendship:output_type -> topfans.social.CheckFriendshipResponse
17, // 54: topfans.social.SocialService.GetFriendCount:output_type -> topfans.social.GetFriendCountResponse
19, // 55: topfans.social.SocialService.SearchUserForFriend:output_type -> topfans.social.SearchUserForFriendResponse
23, // 56: topfans.social.SocialService.GetRandomUsers:output_type -> topfans.social.GetRandomUsersResponse
26, // 57: topfans.social.SocialService.GetUsersPaged:output_type -> topfans.social.GetUsersPagedResponse
28, // 58: topfans.social.SocialService.LikeAsset:output_type -> topfans.social.LikeAssetResponse
30, // 59: topfans.social.SocialService.UnlikeAsset:output_type -> topfans.social.UnlikeAssetResponse
32, // 60: topfans.social.SocialService.CheckAssetLike:output_type -> topfans.social.CheckAssetLikeResponse
34, // 61: topfans.social.SocialService.GetMyLikedAssets:output_type -> topfans.social.GetMyLikedAssetsResponse
38, // 62: topfans.social.SocialService.GetMyTodayLikedAssets:output_type -> topfans.social.GetMyTodayLikedAssetsResponse
40, // 63: topfans.social.SocialService.GetMyWeekLikedAssets:output_type -> topfans.social.GetMyWeekLikedAssetsResponse
42, // 64: topfans.social.SocialService.GetUserLikedAssets:output_type -> topfans.social.GetUserLikedAssetsResponse
47, // [47:65] is the sub-list for method output_type
29, // [29:47] is the sub-list for method input_type
29, // [29:29] is the sub-list for extension type_name
29, // [29:29] is the sub-list for extension extendee
0, // [0:29] is the sub-list for field type_name
46, // 17: topfans.social.LikeAssetResponse.base:type_name -> topfans.common.BaseResponse
46, // 18: topfans.social.UnlikeAssetResponse.base:type_name -> topfans.common.BaseResponse
46, // 19: topfans.social.CheckAssetLikeResponse.base:type_name -> topfans.common.BaseResponse
46, // 20: topfans.social.GetAssetLikersResponse.base:type_name -> topfans.common.BaseResponse
35, // 21: topfans.social.GetAssetLikersResponse.users:type_name -> topfans.social.AssetLiker
46, // 22: topfans.social.GetMyLikedAssetsResponse.base:type_name -> topfans.common.BaseResponse
38, // 23: topfans.social.GetMyLikedAssetsResponse.data:type_name -> topfans.social.LikedAssetsData
39, // 24: topfans.social.LikedAssetsData.items:type_name -> topfans.social.LikedAssetItem
46, // 25: topfans.social.GetMyTodayLikedAssetsResponse.base:type_name -> topfans.common.BaseResponse
38, // 26: topfans.social.GetMyTodayLikedAssetsResponse.data:type_name -> topfans.social.LikedAssetsData
46, // 27: topfans.social.GetMyWeekLikedAssetsResponse.base:type_name -> topfans.common.BaseResponse
38, // 28: topfans.social.GetMyWeekLikedAssetsResponse.data:type_name -> topfans.social.LikedAssetsData
46, // 29: topfans.social.GetUserLikedAssetsResponse.base:type_name -> topfans.common.BaseResponse
38, // 30: topfans.social.GetUserLikedAssetsResponse.data:type_name -> topfans.social.LikedAssetsData
2, // 31: topfans.social.SocialService.SendFriendRequest:input_type -> topfans.social.SendFriendRequestRequest
4, // 32: topfans.social.SocialService.GetFriendRequests:input_type -> topfans.social.GetFriendRequestsRequest
6, // 33: topfans.social.SocialService.HandleFriendRequest:input_type -> topfans.social.HandleFriendRequestRequest
8, // 34: topfans.social.SocialService.GetFriendList:input_type -> topfans.social.GetFriendListRequest
10, // 35: topfans.social.SocialService.DeleteFriend:input_type -> topfans.social.DeleteFriendRequest
12, // 36: topfans.social.SocialService.SetFriendRemark:input_type -> topfans.social.SetFriendRemarkRequest
14, // 37: topfans.social.SocialService.CheckFriendship:input_type -> topfans.social.CheckFriendshipRequest
16, // 38: topfans.social.SocialService.GetFriendCount:input_type -> topfans.social.GetFriendCountRequest
18, // 39: topfans.social.SocialService.SearchUserForFriend:input_type -> topfans.social.SearchUserForFriendRequest
22, // 40: topfans.social.SocialService.GetRandomUsers:input_type -> topfans.social.GetRandomUsersRequest
25, // 41: topfans.social.SocialService.GetUsersPaged:input_type -> topfans.social.GetUsersPagedRequest
27, // 42: topfans.social.SocialService.LikeAsset:input_type -> topfans.social.LikeAssetRequest
29, // 43: topfans.social.SocialService.UnlikeAsset:input_type -> topfans.social.UnlikeAssetRequest
31, // 44: topfans.social.SocialService.CheckAssetLike:input_type -> topfans.social.CheckAssetLikeRequest
33, // 45: topfans.social.SocialService.GetAssetLikers:input_type -> topfans.social.GetAssetLikersRequest
36, // 46: topfans.social.SocialService.GetMyLikedAssets:input_type -> topfans.social.GetMyLikedAssetsRequest
40, // 47: topfans.social.SocialService.GetMyTodayLikedAssets:input_type -> topfans.social.GetMyTodayLikedAssetsRequest
42, // 48: topfans.social.SocialService.GetMyWeekLikedAssets:input_type -> topfans.social.GetMyWeekLikedAssetsRequest
44, // 49: topfans.social.SocialService.GetUserLikedAssets:input_type -> topfans.social.GetUserLikedAssetsRequest
3, // 50: topfans.social.SocialService.SendFriendRequest:output_type -> topfans.social.SendFriendRequestResponse
5, // 51: topfans.social.SocialService.GetFriendRequests:output_type -> topfans.social.GetFriendRequestsResponse
7, // 52: topfans.social.SocialService.HandleFriendRequest:output_type -> topfans.social.HandleFriendRequestResponse
9, // 53: topfans.social.SocialService.GetFriendList:output_type -> topfans.social.GetFriendListResponse
11, // 54: topfans.social.SocialService.DeleteFriend:output_type -> topfans.social.DeleteFriendResponse
13, // 55: topfans.social.SocialService.SetFriendRemark:output_type -> topfans.social.SetFriendRemarkResponse
15, // 56: topfans.social.SocialService.CheckFriendship:output_type -> topfans.social.CheckFriendshipResponse
17, // 57: topfans.social.SocialService.GetFriendCount:output_type -> topfans.social.GetFriendCountResponse
19, // 58: topfans.social.SocialService.SearchUserForFriend:output_type -> topfans.social.SearchUserForFriendResponse
23, // 59: topfans.social.SocialService.GetRandomUsers:output_type -> topfans.social.GetRandomUsersResponse
26, // 60: topfans.social.SocialService.GetUsersPaged:output_type -> topfans.social.GetUsersPagedResponse
28, // 61: topfans.social.SocialService.LikeAsset:output_type -> topfans.social.LikeAssetResponse
30, // 62: topfans.social.SocialService.UnlikeAsset:output_type -> topfans.social.UnlikeAssetResponse
32, // 63: topfans.social.SocialService.CheckAssetLike:output_type -> topfans.social.CheckAssetLikeResponse
34, // 64: topfans.social.SocialService.GetAssetLikers:output_type -> topfans.social.GetAssetLikersResponse
37, // 65: topfans.social.SocialService.GetMyLikedAssets:output_type -> topfans.social.GetMyLikedAssetsResponse
41, // 66: topfans.social.SocialService.GetMyTodayLikedAssets:output_type -> topfans.social.GetMyTodayLikedAssetsResponse
43, // 67: topfans.social.SocialService.GetMyWeekLikedAssets:output_type -> topfans.social.GetMyWeekLikedAssetsResponse
45, // 68: topfans.social.SocialService.GetUserLikedAssets:output_type -> topfans.social.GetUserLikedAssetsResponse
50, // [50:69] is the sub-list for method output_type
31, // [31:50] is the sub-list for method input_type
31, // [31:31] is the sub-list for extension type_name
31, // [31:31] is the sub-list for extension extendee
0, // [0:31] is the sub-list for field type_name
}
func init() { file_social_proto_init() }
@ -3146,7 +3387,7 @@ func file_social_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_social_proto_rawDesc), len(file_social_proto_rawDesc)),
NumEnums: 0,
NumMessages: 43,
NumMessages: 46,
NumExtensions: 0,
NumServices: 1,
},

View File

@ -64,6 +64,8 @@ const (
SocialServiceUnlikeAssetProcedure = "/topfans.social.SocialService/UnlikeAsset"
// SocialServiceCheckAssetLikeProcedure is the fully-qualified name of the SocialService's CheckAssetLike RPC.
SocialServiceCheckAssetLikeProcedure = "/topfans.social.SocialService/CheckAssetLike"
// SocialServiceGetAssetLikersProcedure is the fully-qualified name of the SocialService's GetAssetLikers RPC.
SocialServiceGetAssetLikersProcedure = "/topfans.social.SocialService/GetAssetLikers"
// 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.
@ -94,6 +96,7 @@ type SocialService interface {
LikeAsset(ctx context.Context, req *LikeAssetRequest, opts ...client.CallOption) (*LikeAssetResponse, error)
UnlikeAsset(ctx context.Context, req *UnlikeAssetRequest, opts ...client.CallOption) (*UnlikeAssetResponse, error)
CheckAssetLike(ctx context.Context, req *CheckAssetLikeRequest, opts ...client.CallOption) (*CheckAssetLikeResponse, error)
GetAssetLikers(ctx context.Context, req *GetAssetLikersRequest, opts ...client.CallOption) (*GetAssetLikersResponse, error)
GetMyLikedAssets(ctx context.Context, req *GetMyLikedAssetsRequest, opts ...client.CallOption) (*GetMyLikedAssetsResponse, error)
GetMyTodayLikedAssets(ctx context.Context, req *GetMyTodayLikedAssetsRequest, opts ...client.CallOption) (*GetMyTodayLikedAssetsResponse, error)
GetMyWeekLikedAssets(ctx context.Context, req *GetMyWeekLikedAssetsRequest, opts ...client.CallOption) (*GetMyWeekLikedAssetsResponse, error)
@ -232,6 +235,14 @@ func (c *SocialServiceImpl) CheckAssetLike(ctx context.Context, req *CheckAssetL
return resp, nil
}
func (c *SocialServiceImpl) GetAssetLikers(ctx context.Context, req *GetAssetLikersRequest, opts ...client.CallOption) (*GetAssetLikersResponse, error) {
resp := new(GetAssetLikersResponse)
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "GetAssetLikers", opts...); err != nil {
return nil, err
}
return resp, nil
}
func (c *SocialServiceImpl) GetMyLikedAssets(ctx context.Context, req *GetMyLikedAssetsRequest, opts ...client.CallOption) (*GetMyLikedAssetsResponse, error) {
resp := new(GetMyLikedAssetsResponse)
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "GetMyLikedAssets", opts...); err != nil {
@ -266,7 +277,7 @@ func (c *SocialServiceImpl) GetUserLikedAssets(ctx context.Context, req *GetUser
var SocialService_ClientInfo = client.ClientInfo{
InterfaceName: "topfans.social.SocialService",
MethodNames: []string{"SendFriendRequest", "GetFriendRequests", "HandleFriendRequest", "GetFriendList", "DeleteFriend", "SetFriendRemark", "CheckFriendship", "GetFriendCount", "SearchUserForFriend", "GetRandomUsers", "GetUsersPaged", "LikeAsset", "UnlikeAsset", "CheckAssetLike", "GetMyLikedAssets", "GetMyTodayLikedAssets", "GetMyWeekLikedAssets", "GetUserLikedAssets"},
MethodNames: []string{"SendFriendRequest", "GetFriendRequests", "HandleFriendRequest", "GetFriendList", "DeleteFriend", "SetFriendRemark", "CheckFriendship", "GetFriendCount", "SearchUserForFriend", "GetRandomUsers", "GetUsersPaged", "LikeAsset", "UnlikeAsset", "CheckAssetLike", "GetAssetLikers", "GetMyLikedAssets", "GetMyTodayLikedAssets", "GetMyWeekLikedAssets", "GetUserLikedAssets"},
ConnectionInjectFunc: func(dubboCliRaw interface{}, conn *client.Connection) {
dubboCli := dubboCliRaw.(*SocialServiceImpl)
dubboCli.conn = conn
@ -289,6 +300,7 @@ type SocialServiceHandler interface {
LikeAsset(context.Context, *LikeAssetRequest) (*LikeAssetResponse, error)
UnlikeAsset(context.Context, *UnlikeAssetRequest) (*UnlikeAssetResponse, error)
CheckAssetLike(context.Context, *CheckAssetLikeRequest) (*CheckAssetLikeResponse, error)
GetAssetLikers(context.Context, *GetAssetLikersRequest) (*GetAssetLikersResponse, error)
GetMyLikedAssets(context.Context, *GetMyLikedAssetsRequest) (*GetMyLikedAssetsResponse, error)
GetMyTodayLikedAssets(context.Context, *GetMyTodayLikedAssetsRequest) (*GetMyTodayLikedAssetsResponse, error)
GetMyWeekLikedAssets(context.Context, *GetMyWeekLikedAssetsRequest) (*GetMyWeekLikedAssetsResponse, error)
@ -517,6 +529,21 @@ var SocialService_ServiceInfo = server.ServiceInfo{
return triple_protocol.NewResponse(res), nil
},
},
{
Name: "GetAssetLikers",
Type: constant.CallUnary,
ReqInitFunc: func() interface{} {
return new(GetAssetLikersRequest)
},
MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) {
req := args[0].(*GetAssetLikersRequest)
res, err := handler.(SocialServiceHandler).GetAssetLikers(ctx, req)
if err != nil {
return nil, err
}
return triple_protocol.NewResponse(res), nil
},
},
{
Name: "GetMyLikedAssets",
Type: constant.CallUnary,

View File

@ -260,6 +260,31 @@ message CheckAssetLikeResponse {
bool is_liked = 2; //
}
//
message GetAssetLikersRequest {
int64 asset_id = 1; // ID
int32 page_size = 2; // 20100
int64 cursor = 3; // created_at0
}
//
message GetAssetLikersResponse {
topfans.common.BaseResponse base = 1;
repeated AssetLiker users = 2; //
int64 total = 3; //
bool has_more = 4; //
int64 next_cursor = 5; //
}
//
message AssetLiker {
int64 user_id = 1; // ID
string nickname = 2; //
string avatar = 3; // URL
int32 fan_level = 4; //
int64 liked_at = 5; //
}
// ==================== ====================
//
@ -446,6 +471,13 @@ service SocialService {
};
}
//
rpc GetAssetLikers(GetAssetLikersRequest) returns (GetAssetLikersResponse) {
option (google.api.http) = {
get: "/api/v1/social/assets/{asset_id}/likers"
};
}
// ========== ==========
//

View File

@ -159,7 +159,7 @@ CREATE TABLE IF NOT EXISTS asset_likes (
star_id BIGINT NOT NULL,
created_at BIGINT NOT NULL,
CONSTRAINT uk_asset_likes_user_asset UNIQUE (user_id, asset_id)
CONSTRAINT uk_asset_likes_user_asset_exhibition UNIQUE (user_id, asset_id, exhibition_id)
);
CREATE INDEX IF NOT EXISTS idx_asset_likes_asset ON asset_likes(asset_id);

View File

@ -0,0 +1,3 @@
-- 为游标分页新增索引(幂等)
-- 支持查询WHERE asset_id = ? AND created_at < ? ORDER BY created_at DESC
CREATE INDEX IF NOT EXISTS idx_asset_likes_asset_created ON asset_likes(asset_id, created_at DESC);

View File

@ -0,0 +1,26 @@
-- 修改 asset_likes 表唯一约束(幂等)
-- 原约束: (user_id, asset_id) - 用户对某藏品只能点赞一次
-- 新约束: (user_id, asset_id, exhibition_id) - 用户对某藏品在同一展出中只能点赞一次
-- 下架后重新展出,用户可以再次点赞
-- 删除旧约束(如果存在)
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'uk_asset_likes_user_asset'
) THEN
ALTER TABLE asset_likes DROP CONSTRAINT uk_asset_likes_user_asset;
END IF;
END $$;
-- 添加新约束(如果不存在)
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'uk_asset_likes_user_asset_exhibition'
) THEN
ALTER TABLE asset_likes ADD CONSTRAINT uk_asset_likes_user_asset_exhibition UNIQUE (user_id, asset_id, exhibition_id);
END IF;
END $$;

View File

@ -373,6 +373,28 @@ func (p *SocialProvider) CheckAssetLike(ctx context.Context, req *pb.CheckAssetL
}, nil
}
// GetAssetLikers 获取资产点赞用户列表
func (p *SocialProvider) GetAssetLikers(ctx context.Context, req *pb.GetAssetLikersRequest) (*pb.GetAssetLikersResponse, error) {
// 从上下文获取用户信息starID 可选,用于 JOIN fan_profiles
_, starID, err := extractUserInfo(ctx)
if err != nil {
// starID 获取失败不影响查询,使用 0 让 service 层自动获取
starID = 0
logger.Logger.Warn("Failed to extract star_id from context, will get from asset",
zap.Error(err),
)
}
logger.Logger.Debug("GetAssetLikers called",
zap.Int64("asset_id", req.AssetId),
zap.Int64("star_id", starID),
zap.Int64("cursor", req.Cursor),
zap.Int32("page_size", req.PageSize),
)
return p.assetLikeService.GetAssetLikers(ctx, req.AssetId, starID, req.Cursor, req.PageSize)
}
// ========== 我的作品相关 ==========
// GetMyLikedAssets 获取我点赞的作品列表

View File

@ -136,6 +136,26 @@ type SocialRepository interface {
// pageSize: 每页数量
// 返回: 作品列表、总数量
GetUserLikedAssets(userID, starID int64, page, pageSize int) ([]*LikedAssetInfo, int64, error)
// ========== 藏品点赞用户相关 ==========
// GetByAssetWithUsers 获取资产的点赞用户列表(带用户信息,游标分页)
// assetID: 资产ID
// starID: 明星ID用于 JOIN fan_profiles
// cursor: 游标(上一页最后一条的 created_at首次请求传0
// limit: 每页数量
// 返回: 点赞用户列表
GetByAssetWithUsers(assetID, starID int64, cursor int64, limit int) ([]*AssetLikeWithUser, error)
}
// AssetLikeWithUser 点赞记录+用户信息
type AssetLikeWithUser struct {
UserID int64
Nickname string `gorm:"column:nickname"` // 来自 fan_profiles.nickname
Avatar string `gorm:"column:avatar_url"` // 来自 users.avatar_url
FanLevel int32 `gorm:"column:fan_level"` // 来自 fan_profiles.level
LikedAt int64 `gorm:"column:liked_at"`
StarID int64 `gorm:"column:star_id"` // 来自 al.star_id用于 JOIN fan_profiles
}
// LikedAssetInfo 我点赞的作品信息
@ -849,3 +869,36 @@ func (r *socialRepositoryImpl) GetUserLikedAssets(userID, starID int64, page, pa
return items, total, nil
}
// ========== 藏品点赞用户相关实现 ==========
// GetByAssetWithUsers 获取资产的点赞用户列表(带用户信息,游标分页)
func (r *socialRepositoryImpl) GetByAssetWithUsers(assetID, starID int64, cursor int64, limit int) ([]*AssetLikeWithUser, error) {
var results []*AssetLikeWithUser
query := r.db.Table("asset_likes al").
Select(`al.user_id,
al.star_id,
COALESCE(fp.nickname, '匿名用户') as nickname,
u.avatar_url,
COALESCE(fp.level, 1) as fan_level,
al.created_at as liked_at`).
Joins("JOIN users u ON al.user_id = u.id").
Joins("LEFT JOIN fan_profiles fp ON al.user_id = fp.user_id AND al.star_id = fp.star_id").
Where("al.asset_id = ?", assetID)
// 游标分页cursor=0 时跳过游标条件,返回第一页
if cursor > 0 {
query = query.Where("al.created_at < ?", cursor)
}
err := query.Order("al.created_at DESC").
Limit(limit).
Scan(&results).Error
if err != nil {
return nil, err
}
return results, nil
}

View File

@ -5,6 +5,7 @@ import (
"fmt"
"time"
"github.com/topfans/backend/pkg/database"
"github.com/topfans/backend/pkg/logger"
assetPb "github.com/topfans/backend/pkg/proto/asset"
pbCommon "github.com/topfans/backend/pkg/proto/common"
@ -107,6 +108,9 @@ func (s *AssetLikeService) LikeAsset(ctx context.Context, assetID, userID, starI
zap.Int64("star_id", starID),
)
// 缓存失效
_ = database.InvalidateAssetLikersCache(ctx, assetID)
return nil
}
@ -167,6 +171,9 @@ func (s *AssetLikeService) UnlikeAsset(ctx context.Context, assetID, userID, sta
zap.Int64("star_id", starID),
)
// 缓存失效
_ = database.InvalidateAssetLikersCache(ctx, assetID)
return nil
}
@ -484,3 +491,162 @@ func (s *AssetLikeService) GetUserLikedAssets(ctx context.Context, req *pb.GetUs
}, nil
}
// GetAssetLikers 获取资产点赞用户列表(带缓存)
func (s *AssetLikeService) GetAssetLikers(ctx context.Context, assetID, starID int64, cursor int64, pageSize int32) (*pb.GetAssetLikersResponse, error) {
logger.Logger.Debug("AssetLikeService.GetAssetLikers called",
zap.Int64("asset_id", assetID),
zap.Int64("star_id", starID),
zap.Int64("cursor", cursor),
zap.Int32("page_size", pageSize),
)
// 参数校验
if pageSize <= 0 {
pageSize = 20
}
if pageSize > 100 {
pageSize = 100
}
// 0. 校验资产是否存在
getAssetReq := &assetPb.GetAssetForRPCRequest{AssetId: assetID}
getAssetResp, err := s.assetClient.GetAssetForRPC(ctx, getAssetReq)
if err != nil {
logger.Logger.Error("Failed to get asset for RPC",
zap.Error(err),
zap.Int64("asset_id", assetID),
)
return &pb.GetAssetLikersResponse{
Base: &pbCommon.BaseResponse{
Code: pbCommon.StatusCode_STATUS_INTERNAL_ERROR,
Message: "Failed to get asset",
Timestamp: time.Now().UnixMilli(),
},
}, nil
}
if getAssetResp.Base.Code != pbCommon.StatusCode_STATUS_OK {
logger.Logger.Warn("Asset not found",
zap.Int64("asset_id", assetID),
)
return &pb.GetAssetLikersResponse{
Base: &pbCommon.BaseResponse{
Code: pbCommon.StatusCode_STATUS_NOT_FOUND,
Message: "Asset not found",
Timestamp: time.Now().UnixMilli(),
},
}, nil
}
// 1. 先查缓存
cache, err := database.GetAssetLikersCache(ctx, assetID)
if err != nil {
logger.Logger.Warn("Failed to get asset likers cache",
zap.Error(err),
zap.Int64("asset_id", assetID),
)
// 缓存错误不影响主流程,继续查 DB
}
if cache != nil && len(cache.Users) > 0 {
// 缓存命中,从缓存中切片返回
return sliceFromCache(cache, cursor, pageSize)
}
// 2. 缓存未命中,查 DB
// 从已获取的资产信息中获取 starID
actualStarID := getAssetResp.StarId
if actualStarID == 0 {
actualStarID = starID // 兜底使用传入的 starID
}
// 查询数据库,最多缓存 1000 条
dbResults, err := s.socialRepo.GetByAssetWithUsers(assetID, actualStarID, 0, 1000)
if err != nil {
logger.Logger.Error("Failed to get asset likers from DB",
zap.Error(err),
zap.Int64("asset_id", assetID),
)
return &pb.GetAssetLikersResponse{
Base: &pbCommon.BaseResponse{
Code: pbCommon.StatusCode_STATUS_INTERNAL_ERROR,
Message: "Failed to get asset likers",
Timestamp: time.Now().UnixMilli(),
},
}, nil
}
// 3. 写入缓存
total := int64(len(dbResults))
cacheUsers := make([]database.AssetLikerWithTotal, 0, len(dbResults))
for _, r := range dbResults {
cacheUsers = append(cacheUsers, database.AssetLikerWithTotal{
UserID: r.UserID,
Nickname: r.Nickname,
Avatar: r.Avatar,
FanLevel: r.FanLevel,
LikedAt: r.LikedAt,
StarID: r.StarID,
})
}
cache = &database.AssetLikersCache{
Users: cacheUsers,
Total: total,
UpdatedAt: time.Now().UnixMilli(),
}
_ = database.SetAssetLikersCache(ctx, assetID, cache, 60*time.Second)
// 4. 返回数据
return sliceFromCache(cache, cursor, pageSize)
}
// sliceFromCache 从缓存中按游标切片
func sliceFromCache(cache *database.AssetLikersCache, cursor int64, pageSize int32) (*pb.GetAssetLikersResponse, error) {
users := cache.Users
total := cache.Total
// 找到起始位置
start := 0
if cursor > 0 {
for i, u := range users {
if u.LikedAt < cursor {
start = i
break
}
}
}
// 找到结束位置
end := start + int(pageSize)
hasMore := end < len(users)
if end > len(users) {
end = len(users)
}
// 构建返回结果
pbUsers := make([]*pb.AssetLiker, 0, end-start)
var nextCursor int64
for i := start; i < end; i++ {
u := users[i]
pbUsers = append(pbUsers, &pb.AssetLiker{
UserId: u.UserID,
Nickname: u.Nickname,
Avatar: u.Avatar,
FanLevel: u.FanLevel,
LikedAt: u.LikedAt,
})
nextCursor = u.LikedAt
}
return &pb.GetAssetLikersResponse{
Base: &pbCommon.BaseResponse{
Code: pbCommon.StatusCode_STATUS_OK,
Message: "success",
Timestamp: time.Now().UnixMilli(),
},
Users: pbUsers,
Total: total,
HasMore: hasMore,
NextCursor: nextCursor,
}, nil
}

View File

@ -1129,11 +1129,11 @@ ALTER TABLE ONLY public.activity_user_stats
--
-- Name: asset_likes uk_asset_likes_user_asset; Type: CONSTRAINT; Schema: public; Owner: -
-- Name: asset_likes uk_asset_likes_user_asset_exhibition; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.asset_likes
ADD CONSTRAINT uk_asset_likes_user_asset UNIQUE (user_id, asset_id);
ADD CONSTRAINT uk_asset_likes_user_asset_exhibition UNIQUE (user_id, asset_id, exhibition_id);
--
@ -1250,6 +1250,12 @@ CREATE INDEX idx_asset_likes_asset ON public.asset_likes USING btree (asset_id);
CREATE INDEX idx_asset_likes_user_star ON public.asset_likes USING btree (user_id, star_id);
--
-- Name: idx_asset_likes_asset_created; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX IF NOT EXISTS idx_asset_likes_asset_created ON public.asset_likes USING btree (asset_id, created_at DESC);
--
-- Name: idx_registry_owner_star; Type: INDEX; Schema: public; Owner: -

View File

@ -196,17 +196,16 @@
</scroll-view>
<!-- 点赞用户弹窗 -->
<LikeUsersModal :visible="showLikeUsersModal" :tabs="['今日', '历史']" @close="showLikeUsersModal = false"
@tab-change="handleLikeUsersTabChange">
<LikeUsersModal :visible="showLikeUsersModal" :tabs="[]" @close="showLikeUsersModal = false">
<template #content>
<view class="like-users-list">
<view v-for="(user, index) in displayedLikeUsers" :key="index" class="like-user-item">
<view v-for="(user, index) in allLikedUsers" :key="index" class="like-user-item">
<image class="like-user-avatar" :src="user.avatar" mode="aspectFill"></image>
<view class="like-user-info">
<text class="like-user-name">{{ user.nickname }}</text>
</view>
<!-- 拜访按钮 -->
<view class="visit-button" @tap="handleVisit">
<view class="visit-button" @tap="handleVisit(user)">
<image class="visit-icon" src="/static/square/dianjibaifang.png" mode="aspectFit" lazy-load>
</image>
<text class="visit-text">点击拜访</text>
@ -221,7 +220,7 @@
<script setup>
import { ref, computed, onUnmounted } from 'vue';
import { onLoad, onShow, onHide } from '@dcloudio/uni-app';
import { getAssetDetailApi, getMintOrderDetailApi, likeAssetApi, unlikeAssetApi, getAssetMaterialsApi } from '@/utils/api.js';
import { getAssetDetailApi, getMintOrderDetailApi, likeAssetApi, unlikeAssetApi, getAssetMaterialsApi, getAssetLikersApi } from '@/utils/api.js';
import { getAssetCoverRealUrl } from '@/utils/assetImageHelper.js';
import LikeUsersModal from '@/pages/components/LikeUsersModal.vue';
import ShareReportButtons from '@/pages/components/ShareReportButtons.vue';
@ -317,15 +316,53 @@ const loadCurrentUser = () => {
};
// 6
// avatar: , ellipseX: X, ellipseY: Y, size:
const likedUsers = ref([
{ avatar: '/static/sucai/image-01.png', ellipseX: -40, ellipseY: -40, size: 0.85 },
{ avatar: '/static/sucai/image-02.png', ellipseX: 40, ellipseY: -40, size: 0.85 },
{ avatar: '/static/sucai/image-03.png', ellipseX: -96, ellipseY: 0, size: 0.9 },
{ avatar: '/static/sucai/image-04.png', ellipseX: 120, ellipseY: -40, size: 0.9 },
{ avatar: '/static/sucai/image-05.png', ellipseX: 64, ellipseY: 32 },
{ avatar: '/static/sucai/image-06.png', ellipseX: -16, ellipseY: 32, size: 1.15 }
]);
// API
const ellipseConfig = [
{ ellipseX: -40, ellipseY: -40, size: 0.85 },
{ ellipseX: 40, ellipseY: -40, size: 0.85 },
{ ellipseX: -96, ellipseY: 0, size: 0.9 },
{ ellipseX: 120, ellipseY: -40, size: 0.9 },
{ ellipseX: 64, ellipseY: 32, size: 1 },
{ ellipseX: -16, ellipseY: 32, size: 1.15 }
];
// API
const likedUsers = ref([]);
//
const loadLikedUsers = async (assetId) => {
try {
likedUsersLoading.value = true;
//
const res = await getAssetLikersApi(assetId, 20, 0);
if (res.code === 200 && res.data?.users) {
//
likedUsers.value = res.data?.users.slice(0, 6).map((user, index) => {
const config = ellipseConfig[index] || { ellipseX: 0, ellipseY: 0, size: 1 };
return {
avatar: user.avatar || '/static/sucai/default-avatar.png',
ellipseX: config.ellipseX,
ellipseY: config.ellipseY,
size: config.size
};
});
//
allLikedUsers.value = res.data?.users.map(user => ({
userId: user.user_id,
nickname: user.nickname || '匿名用户',
avatar: user.avatar || '/static/sucai/default-avatar.png',
liked_at: user.liked_at || 0
}));
}
} catch (e) {
console.error('加载点赞用户失败:', e);
// 使 v-if="likeCount > 0"
likedUsers.value = [];
allLikedUsers.value = [];
} finally {
likedUsersLoading.value = false;
}
};
// ---- ----
const activeStickers = ref([]);
@ -383,27 +420,16 @@ const showShareModal = ref(false);
const shareCoverUrl = ref('');
const shareQrcodeUrl = ref('');
// /
const todayLikeUsers = ref([
{ userId: '1', nickname: '用户A', avatar: '/static/sucai/image-01.png', time: '10分钟前' },
{ userId: '2', nickname: '用户B', avatar: '/static/sucai/image-02.png', time: '30分钟前' },
{ userId: '3', nickname: '用户C', avatar: '/static/sucai/image-03.png', time: '1小时前' },
]);
const historyLikeUsers = ref([
{ userId: '4', nickname: '用户D', avatar: '/static/sucai/image-04.png', time: '昨天' },
{ userId: '5', nickname: '用户E', avatar: '/static/sucai/image-05.png', time: '3天前' },
]);
const displayedLikeUsers = computed(() => {
return likeUsersActiveTab.value === 0 ? todayLikeUsers.value : historyLikeUsers.value;
});
// API
const allLikedUsers = ref([]);
const likedUsersLoading = ref(false);
const handleLikeUsersTabChange = ({ tabIndex }) => {
likeUsersActiveTab.value = tabIndex;
};
// 访
const handleVisit = () => {
const handleVisit = (user) => {
uni.navigateTo({
url: `/pages/profile/hisWorks?userId=${user.userId}&nickname=${encodeURIComponent(user.nickname)}`
});
@ -598,6 +624,8 @@ const loadData = async () => {
coverUrl.value = ''; //
});
console.log('loadData 执行成功');
//
loadLikedUsers(assetId);
} else {
throw new Error(res.message || '获取藏品详情失败');
}
@ -641,12 +669,11 @@ const handleLike = async () => {
});
} catch (err) {
const errMsg = err?.message || '';
const errCode = err?.code || err?.data?.code || '';
if (errMsg.includes('未在展示中')) {
uni.showToast({ title: '未展览无法点赞', icon: 'none', duration: 2000 });
} else {
uni.showToast({ title: '今日已点赞', icon: 'none', duration: 2000 });
}
const errData = err?.data?.message || '';
//
const showMsg = errData || errMsg || '点赞失败';
uni.showToast({ title: showMsg, icon: 'none', duration: 2000 });
console.log('点赞错误:', err);
} finally {
liking.value = false;
}
@ -812,8 +839,8 @@ onUnmounted(() => {
}
.back-icon {
width: 80rpx;
height: 80rpx;
width: 64rpx;
height: 64rpx;
}
.header-actions {

View File

@ -31,7 +31,7 @@
</view>
<!-- 胶囊按钮区域 -->
<view class="tab-section">
<view v-if="tabs && tabs.length > 0" class="tab-section">
<view v-for="(tab, index) in tabs" :key="index" class="tab-item"
:class="{ 'active': activeTabIndex === index }" @tap="handleTabClick(index)">
<text class="tab-text">{{ tab }}</text>
@ -60,7 +60,7 @@ const props = defineProps({
},
tabs: {
type: Array,
default: () => ['今日', '历史']
default: () => []
}
});
@ -209,8 +209,8 @@ defineExpose({
}
.back-icon {
width: 56rpx;
height: 56rpx;
width: 40rpx;
height: 40rpx;
}
.close-icon {

View File

@ -439,7 +439,6 @@ const handleExhibitionCardTap = (item, index) => {
//
await loadExhibitedAssets();
await loadLikedAssets();
// }
uni.showToast({ title: '点赞成功', icon: 'success' });

View File

@ -380,6 +380,14 @@ export function unlikeAssetApi(assetId) {
})
}
// 获取资产点赞用户列表
export function getAssetLikersApi(assetId, pageSize = 6, cursor = 0) {
return request({
url: `/api/v1/social/assets/${assetId}/likers?page_size=${pageSize}&cursor=${cursor}`,
method: 'GET'
})
}
// 获取我的展馆槽位信息
export function getMyGalleriesApi() {
return request({

View File

@ -44,17 +44,14 @@ export function doubleTapLike(assetId, exhibitionId, callback) {
const key = getStorageKey(assetId, actualExhibitionId);
try {
const storage = uni.getStorageSync(LIKE_STORAGE_KEY) || {};
if (storage[key]) {
const msg = actualExhibitionId ? '本次展示已点赞' : '今日已点赞';
uni.showToast({ title: msg, icon: 'none' });
if (actualCallback) actualCallback(false);
return;
}
} catch (e) {
console.error('读取点赞记录失败:', e);
}
// 不再做本地缓存检查,统一由后端判断唯一性
// 这样可以避免:用户清缓存后、后端已重置、但本地存储还有旧数据 导致的问题
// if (storage[key]) {
// const msg = actualExhibitionId ? '本次展示已点赞' : '今日已点赞';
// uni.showToast({ title: msg, icon: 'none' });
// if (actualCallback) actualCallback(false);
// return;
// }
likeAssetApi(assetId).then(res => {
console.log('点赞成功', res);
@ -75,7 +72,10 @@ export function doubleTapLike(assetId, exhibitionId, callback) {
if (actualCallback) actualCallback(true, res.data);
}).catch(err => {
console.error('点赞失败:', err);
uni.showToast({ title: '点赞失败', icon: 'none' });
const errMsg = err?.message || '';
const errData = err?.data?.message || '';
const showMsg = errData || errMsg || '点赞失败';
uni.showToast({ title: showMsg, icon: 'none' });
if (actualCallback) actualCallback(false);
});
}