feat:去掉重复的点赞约束

This commit is contained in:
zerosaturation 2026-05-25 14:44:26 +08:00
parent 057f9bce1c
commit d7d17d8a38
12 changed files with 66 additions and 23 deletions

View File

@ -2247,6 +2247,8 @@ func (x *UnlikeAssetResponse) GetLikeCount() int32 {
type CheckAssetLikeRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
AssetId int64 `protobuf:"varint,1,opt,name=asset_id,json=assetId,proto3" json:"asset_id,omitempty"` // 资产ID
UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` // 用户ID
StarId int64 `protobuf:"varint,3,opt,name=star_id,json=starId,proto3" json:"star_id,omitempty"` // 明星ID
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@ -2288,6 +2290,20 @@ func (x *CheckAssetLikeRequest) GetAssetId() int64 {
return 0
}
func (x *CheckAssetLikeRequest) GetUserId() int64 {
if x != nil {
return x.UserId
}
return 0
}
func (x *CheckAssetLikeRequest) GetStarId() int64 {
if x != nil {
return x.StarId
}
return 0
}
// 检查是否已点赞响应
type CheckAssetLikeResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
@ -3828,9 +3844,11 @@ const file_asset_proto_rawDesc = "" +
"\x13UnlikeAssetResponse\x120\n" +
"\x04base\x18\x01 \x01(\v2\x1c.topfans.common.BaseResponseR\x04base\x12\x1d\n" +
"\n" +
"like_count\x18\x02 \x01(\x05R\tlikeCount\"2\n" +
"like_count\x18\x02 \x01(\x05R\tlikeCount\"d\n" +
"\x15CheckAssetLikeRequest\x12\x19\n" +
"\basset_id\x18\x01 \x01(\x03R\aassetId\"e\n" +
"\basset_id\x18\x01 \x01(\x03R\aassetId\x12\x17\n" +
"\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x17\n" +
"\astar_id\x18\x03 \x01(\x03R\x06starId\"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\"\x87\x01\n" +

View File

@ -279,6 +279,8 @@ message UnlikeAssetResponse {
// RPCSocial Service调用
message CheckAssetLikeRequest {
int64 asset_id = 1; // ID
int64 user_id = 2; // ID
int64 star_id = 3; // ID
}
//

View File

@ -0,0 +1,6 @@
-- Drop the overly strict unique constraint that prevents users from liking
-- the same asset across different exhibitions. The correct constraint is
-- (user_id, asset_id, exhibition_id), not (user_id, asset_id, star_id).
-- This allows a user to like the same asset once per exhibition.
ALTER TABLE asset_likes DROP CONSTRAINT IF EXISTS uk_asset_likes_user_asset_star;

View File

@ -392,13 +392,14 @@ func (p *AssetProvider) LikeAsset(ctx context.Context, req *pb.LikeAssetRequest)
zap.Int64("asset_id", req.AssetId),
zap.Error(err),
)
// 返回响应但不返回 error让客户端检查 Base.Code
return &pb.LikeAssetResponse{
Base: &pbCommon.BaseResponse{
Code: appErrors.ToStatusCode(err),
Message: err.Error(),
Timestamp: 0,
},
}, err
}, nil
}
logger.Logger.Info("LikeAsset successful",
@ -475,13 +476,18 @@ func (p *AssetProvider) UnlikeAsset(ctx context.Context, req *pb.UnlikeAssetRequ
func (p *AssetProvider) CheckAssetLike(ctx context.Context, req *pb.CheckAssetLikeRequest) (*pb.CheckAssetLikeResponse, error) {
logger.Logger.Info("Received CheckAssetLike request",
zap.Int64("asset_id", req.AssetId),
zap.Int64("user_id", req.UserId),
zap.Int64("star_id", req.StarId),
)
// 从 Dubbo attachments 获取用户信息
userID, starID, err := extractUserInfoFromDubboAttachments(ctx)
if err != nil {
logger.Logger.Error("Failed to extract user info from attachments",
zap.Error(err),
// 直接使用请求中的用户信息
userID := req.UserId
starID := req.StarId
if userID == 0 || starID == 0 {
logger.Logger.Error("Invalid user info in request",
zap.Int64("user_id", userID),
zap.Int64("star_id", starID),
)
return &pb.CheckAssetLikeResponse{
Base: &pbCommon.BaseResponse{
@ -489,7 +495,7 @@ func (p *AssetProvider) CheckAssetLike(ctx context.Context, req *pb.CheckAssetLi
Message: "user authentication required",
Timestamp: 0,
},
}, err
}, fmt.Errorf("invalid user_id or star_id")
}
// 调用Service层

View File

@ -47,6 +47,10 @@ func (s *AssetLikeService) isAssetExhibiting(assetID int64) (int64, error) {
Where("asset_id = ? AND expire_at > ?", assetID, nowMs).
First(&exhibition).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
// 资产不在任何展出中,返回 0 表示未展出
return 0, nil
}
return 0, fmt.Errorf("failed to check exhibition status: %w", err)
}
return exhibition.ID, nil

View File

@ -62,6 +62,8 @@ func (s *AssetLikeService) LikeAsset(ctx context.Context, assetID, userID, starI
// 2. 检查是否已点赞
checkLikeReq := &assetPb.CheckAssetLikeRequest{
AssetId: assetID,
UserId: userID,
StarId: starID,
}
checkLikeResp, err := s.assetClient.CheckAssetLike(ctx, checkLikeReq)
if err != nil {
@ -125,6 +127,8 @@ func (s *AssetLikeService) UnlikeAsset(ctx context.Context, assetID, userID, sta
// 1. 检查是否已点赞
checkLikeReq := &assetPb.CheckAssetLikeRequest{
AssetId: assetID,
UserId: userID,
StarId: starID,
}
checkLikeResp, err := s.assetClient.CheckAssetLike(ctx, checkLikeReq)
if err != nil {
@ -187,6 +191,8 @@ func (s *AssetLikeService) CheckAssetLike(ctx context.Context, assetID, userID,
checkLikeReq := &assetPb.CheckAssetLikeRequest{
AssetId: assetID,
UserId: userID,
StarId: starID,
}
checkLikeResp, err := s.assetClient.CheckAssetLike(ctx, checkLikeReq)
if err != nil {

BIN
backend/socialService Executable file

Binary file not shown.

View File

@ -383,13 +383,13 @@ const handleAvatarClick = () => {
if (pages.length > 0) {
const currentPage = pages[pages.length - 1];
//
if (currentPage.route === 'pages/profile/myWorks') {
if (currentPage.route === 'pages/profile/profile') {
//
return;
}
}
uni.navigateTo({
url: '/pages/profile/myWorks'
url: '/pages/profile/profile'
});
};

View File

@ -10,9 +10,9 @@
</view>
<!-- <text class="nav-title">我的作品</text> -->
<view class="nav-placeholder"></view>
<view class="nav-settings" @tap="goToSettings">
<!-- <view class="nav-settings" @tap="goToSettings">
<image class="nav-settings-icon" src="/static/icon/settings.png" mode="aspectFit"></image>
</view>
</view> -->
</view>
<view class="scroll-content">

View File

@ -1296,8 +1296,8 @@ onShow(() => {
}
.close-icon-img {
width: 80rpx;
height: 80rpx;
width: 48rpx;
height: 48rpx;
position: relative;
top: 96rpx;
left: 32rpx;
@ -1372,15 +1372,11 @@ onShow(() => {
}
.uid-value {}
.toggle-icon {
width: 32rpx;
height: 32rpx;
}
.address-value {}
.info-btn {
font-size: 20rpx;
color: #FFD700;

View File

@ -33,7 +33,7 @@
<script setup>
defineProps({
modelValue: { type: String, default: 'random' }
modelValue: { type: String, default: 'hot' }
})
defineEmits(['update:modelValue'])
@ -42,7 +42,7 @@ const tabs = [
{ key: 'hot', label: '人气王者', emoji: null, icon: '/static/square/3.png',iconWidth: 80, iconHeight: 80 },
{ key: 'new', label: '新鲜上架', emoji: null, icon: '/static/square/2.png',iconWidth: 80, iconHeight: 80 },
{ key: 'potential', label: '潜力之星', emoji: null, icon: '/static/square/1.png',iconWidth: 96, iconHeight: 96 },
{ key: 'random', label: '随机寻宝', emoji: null, icon: '/static/square/4.png',iconWidth: 80, iconHeight: 80 },
{ key: 'myworks', label: '我的', emoji: null, icon: '/static/square/4.png',iconWidth: 80, iconHeight: 80 },
]
</script>

View File

@ -89,14 +89,18 @@ const currentUserNickname = computed(() => store.state.user?.userInfo?.nickname
const currentStarId = ref(uni.getStorageSync('star_id') || null)
// ========== UI State ==========
const activeContentTab = ref('random')
const activeContentTab = ref('hot')
const waterfallKey = ref(0) // WaterfallGrid
const navExpanded = ref(false)
const showRankingModal = ref(false)
const isActive = ref(true)
// ========== Watch activeContentTab ==========
watch(activeContentTab, () => {
watch(activeContentTab, (newTab) => {
if (newTab === 'myworks') {
uni.navigateTo({ url: '/pages/profile/myWorks' })
return
}
// WaterfallGridiOS CSS
waterfallKey.value++
})
@ -191,6 +195,7 @@ onMounted(() => {
onShow(() => {
isActive.value = true
activeContentTab.value = 'hot'
//
// if (shouldShowGuideStartModal()) {
// uni.navigateTo({