feat:修改点赞接口bug

This commit is contained in:
zerosaturation 2026-05-26 21:32:37 +08:00
parent 7dbcf50a39
commit dd34def217
9 changed files with 157 additions and 22 deletions

View File

@ -253,7 +253,8 @@ func (p *SocialProvider) LikeAsset(ctx context.Context, req *pb.LikeAssetRequest
) )
// 调用业务逻辑层 // 调用业务逻辑层
if err := p.assetLikeService.LikeAsset(ctx, req.AssetId, userID, starID); err != nil { likeCount, err := p.assetLikeService.LikeAsset(ctx, req.AssetId, userID, starID)
if err != nil {
logger.Logger.Error("Failed to like asset", logger.Logger.Error("Failed to like asset",
zap.Error(err), zap.Error(err),
zap.Int64("asset_id", req.AssetId), zap.Int64("asset_id", req.AssetId),
@ -274,6 +275,9 @@ func (p *SocialProvider) LikeAsset(ctx context.Context, req *pb.LikeAssetRequest
Message: "Successfully liked asset", Message: "Successfully liked asset",
Timestamp: time.Now().UnixMilli(), Timestamp: time.Now().UnixMilli(),
}, },
AssetId: req.AssetId,
NewLikeCount: likeCount,
IsLiked: true,
}, nil }, nil
} }

View File

@ -30,7 +30,7 @@ func NewAssetLikeService(assetClient *client.AssetClient, socialRepo repository.
} }
// LikeAsset 点赞资产 // LikeAsset 点赞资产
func (s *AssetLikeService) LikeAsset(ctx context.Context, assetID, userID, starID int64) error { func (s *AssetLikeService) LikeAsset(ctx context.Context, assetID, userID, starID int64) (int32, error) {
logger.Logger.Debug("AssetLikeService.LikeAsset called", logger.Logger.Debug("AssetLikeService.LikeAsset called",
zap.Int64("asset_id", assetID), zap.Int64("asset_id", assetID),
zap.Int64("user_id", userID), zap.Int64("user_id", userID),
@ -47,7 +47,7 @@ func (s *AssetLikeService) LikeAsset(ctx context.Context, assetID, userID, starI
zap.Error(err), zap.Error(err),
zap.Int64("asset_id", assetID), zap.Int64("asset_id", assetID),
) )
return fmt.Errorf("获取藏品信息失败,请稍后重试") return 0, fmt.Errorf("获取藏品信息失败,请稍后重试")
} }
if getAssetResp.Base.Code != pbCommon.StatusCode_STATUS_OK { if getAssetResp.Base.Code != pbCommon.StatusCode_STATUS_OK {
@ -56,7 +56,7 @@ func (s *AssetLikeService) LikeAsset(ctx context.Context, assetID, userID, starI
zap.Int32("code", int32(getAssetResp.Base.Code)), zap.Int32("code", int32(getAssetResp.Base.Code)),
zap.String("message", getAssetResp.Base.Message), zap.String("message", getAssetResp.Base.Message),
) )
return fmt.Errorf("藏品不存在") return 0, fmt.Errorf("藏品不存在")
} }
// 2. 检查是否已点赞 // 2. 检查是否已点赞
@ -71,7 +71,7 @@ func (s *AssetLikeService) LikeAsset(ctx context.Context, assetID, userID, starI
zap.Error(err), zap.Error(err),
zap.Int64("asset_id", assetID), zap.Int64("asset_id", assetID),
) )
return fmt.Errorf("点赞失败,请稍后重试") return 0, fmt.Errorf("点赞失败,请稍后重试")
} }
if checkLikeResp.IsLiked { if checkLikeResp.IsLiked {
@ -79,7 +79,7 @@ func (s *AssetLikeService) LikeAsset(ctx context.Context, assetID, userID, starI
zap.Int64("asset_id", assetID), zap.Int64("asset_id", assetID),
zap.Int64("user_id", userID), zap.Int64("user_id", userID),
) )
return fmt.Errorf("当前展示已点赞") return 0, fmt.Errorf("当前展示已点赞")
} }
// 3. 调用 Asset Service 点赞接口 // 3. 调用 Asset Service 点赞接口
@ -92,7 +92,7 @@ func (s *AssetLikeService) LikeAsset(ctx context.Context, assetID, userID, starI
zap.Error(err), zap.Error(err),
zap.Int64("asset_id", assetID), zap.Int64("asset_id", assetID),
) )
return fmt.Errorf("点赞失败,请稍后重试") return 0, fmt.Errorf("点赞失败,请稍后重试")
} }
if likeResp.Base.Code != pbCommon.StatusCode_STATUS_OK { if likeResp.Base.Code != pbCommon.StatusCode_STATUS_OK {
@ -101,19 +101,20 @@ func (s *AssetLikeService) LikeAsset(ctx context.Context, assetID, userID, starI
zap.Int32("code", int32(likeResp.Base.Code)), zap.Int32("code", int32(likeResp.Base.Code)),
zap.String("message", likeResp.Base.Message), zap.String("message", likeResp.Base.Message),
) )
return fmt.Errorf("点赞失败: %s", likeResp.Base.Message) return 0, fmt.Errorf("点赞失败: %s", likeResp.Base.Message)
} }
logger.Logger.Info("Successfully liked asset", logger.Logger.Info("Successfully liked asset",
zap.Int64("asset_id", assetID), zap.Int64("asset_id", assetID),
zap.Int64("user_id", userID), zap.Int64("user_id", userID),
zap.Int64("star_id", starID), zap.Int64("star_id", starID),
zap.Int32("new_like_count", likeResp.LikeCount),
) )
// 缓存失效 // 缓存失效
_ = database.InvalidateAssetLikersCache(ctx, assetID) _ = database.InvalidateAssetLikersCache(ctx, assetID)
return nil return likeResp.LikeCount, nil
} }
// UnlikeAsset 取消点赞资产 // UnlikeAsset 取消点赞资产

View File

@ -30,3 +30,10 @@ REDIS_HOST=topfans-redis
REDIS_PORT=6379 REDIS_PORT=6379
REDIS_PASSWORD=123456 REDIS_PASSWORD=123456
REDIS_DB=0 REDIS_DB=0
# ==================== SMS Configuration ====================
SMS_ACCESS_KEY_ID=LTAI5t6QcdJHpYbCPxM8SXYE
SMS_ACCESS_KEY_SECRET=ybvjSEb7wilMt3qT5nOppYPoNVayCD
SMS_SIGN_NAME=TopFans
SMS_TEMPLATE_CODE=SMS_314621237
SMS_REGION=cn-hangzhou

View File

@ -120,10 +120,10 @@ RUN apk add --no-cache ca-certificates tzdata
WORKDIR /app WORKDIR /app
COPY --from=builder /tmp/galleryservice /app/galleryservice COPY --from=builder /tmp/galleryservice /app/galleryservice
EXPOSE 20004 EXPOSE 20001
HEALTHCHECK --interval=10s --timeout=5s --start-period=10s --retries=3 \ HEALTHCHECK --interval=10s --timeout=5s --start-period=10s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:20004 || exit 1 CMD wget --no-verbose --tries=1 --spider http://localhost:21001 || exit 1
ENTRYPOINT ["/app/galleryservice"] ENTRYPOINT ["/app/galleryservice"]
@ -135,10 +135,10 @@ RUN apk add --no-cache ca-certificates tzdata
WORKDIR /app WORKDIR /app
COPY --from=builder /tmp/activityservice /app/activityservice COPY --from=builder /tmp/activityservice /app/activityservice
EXPOSE 20005 EXPOSE 20004
HEALTHCHECK --interval=10s --timeout=5s --start-period=10s --retries=3 \ HEALTHCHECK --interval=10s --timeout=5s --start-period=10s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:20005 || exit 1 CMD wget --no-verbose --tries=1 --spider http://localhost:21004 || exit 1
ENTRYPOINT ["/app/activityservice"] ENTRYPOINT ["/app/activityservice"]
@ -165,9 +165,9 @@ RUN apk add --no-cache ca-certificates tzdata
WORKDIR /app WORKDIR /app
COPY --from=builder /tmp/starbookservice /app/starbookservice COPY --from=builder /tmp/starbookservice /app/starbookservice
EXPOSE 20007 EXPOSE 20005
HEALTHCHECK --interval=10s --timeout=5s --start-period=10s --retries=3 \ HEALTHCHECK --interval=10s --timeout=5s --start-period=10s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:21007 || exit 1 CMD wget --no-verbose --tries=1 --spider http://localhost:21005 || exit 1
ENTRYPOINT ["/app/starbookservice"] ENTRYPOINT ["/app/starbookservice"]

View File

@ -63,12 +63,14 @@ services:
image: redis:latest image: redis:latest
container_name: topfans-redis container_name: topfans-redis
restart: always restart: always
environment:
REDIS_PASSWORD: ${REDIS_PASSWORD:-123456}
ports: ports:
- "6379:6379" - "6379:6379"
networks: networks:
- topfans-net - topfans-net
healthcheck: healthcheck:
test: ["CMD", "redis-cli", "ping"] test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD:-123456}", "ping"]
interval: 30s interval: 30s
timeout: 10s timeout: 10s
retries: 5 retries: 5
@ -117,9 +119,20 @@ services:
DB_USER: postgres DB_USER: postgres
DB_PASSWORD: ${DB_PASSWORD:-postgres123} DB_PASSWORD: ${DB_PASSWORD:-postgres123}
DB_NAME: topfans DB_NAME: topfans
REDIS_HOST: topfans-redis
REDIS_PORT: 6379
REDIS_PASSWORD: ${REDIS_PASSWORD:-123456}
REDIS_DB: 0
SMS_ACCESS_KEY_ID: ${SMS_ACCESS_KEY_ID:-}
SMS_ACCESS_KEY_SECRET: ${SMS_ACCESS_KEY_SECRET:-}
SMS_SIGN_NAME: ${SMS_SIGN_NAME:-}
SMS_TEMPLATE_CODE: ${SMS_TEMPLATE_CODE:-}
SMS_REGION: ${SMS_REGION:-cn-hangzhou}
depends_on: depends_on:
postgres: postgres:
condition: service_healthy condition: service_healthy
redis:
condition: service_healthy
networks: networks:
- topfans-net - topfans-net
expose: expose:
@ -276,9 +289,15 @@ services:
DB_PASSWORD: ${DB_PASSWORD:-postgres123} DB_PASSWORD: ${DB_PASSWORD:-postgres123}
DB_NAME: topfans DB_NAME: topfans
USER_SERVICE_URL: tri://userservice:20000 USER_SERVICE_URL: tri://userservice:20000
REDIS_HOST: topfans-redis
REDIS_PORT: 6379
REDIS_PASSWORD: ${REDIS_PASSWORD:-123456}
REDIS_DB: 0
depends_on: depends_on:
userservice: userservice:
condition: service_started condition: service_started
redis:
condition: service_healthy
networks: networks:
- topfans-net - topfans-net
expose: expose:
@ -342,7 +361,7 @@ services:
restart: always restart: always
environment: environment:
<<: *common-env <<: *common-env
PORT: 20007 PORT: 20005
DB_HOST: postgres DB_HOST: postgres
DB_PORT: 5432 DB_PORT: 5432
DB_USER: postgres DB_USER: postgres
@ -357,9 +376,9 @@ services:
networks: networks:
- topfans-net - topfans-net
expose: expose:
- "20007" - "20005"
healthcheck: healthcheck:
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:21007 || exit 1"] test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:21005 || exit 1"]
<<: *healthcheck <<: *healthcheck
deploy: deploy:
resources: resources:
@ -391,8 +410,11 @@ services:
DUBBO_GALLERY_SERVICE_URL: tri://galleryservice:20001 DUBBO_GALLERY_SERVICE_URL: tri://galleryservice:20001
DUBBO_ACTIVITY_SERVICE_URL: tri://activityservice:20004 DUBBO_ACTIVITY_SERVICE_URL: tri://activityservice:20004
DUBBO_TASK_SERVICE_URL: tri://taskservice:20006 DUBBO_TASK_SERVICE_URL: tri://taskservice:20006
DUBBO_STARBOOK_SERVICE_URL: tri://starbookservice:20007 DUBBO_STARBOOK_SERVICE_URL: tri://starbookservice:20005
REDIS_HOST: topfans-redis REDIS_HOST: topfans-redis
REDIS_PORT: 6379
REDIS_PASSWORD: ${REDIS_PASSWORD:-123456}
REDIS_DB: 0
depends_on: depends_on:
userservice: userservice:
condition: service_started condition: service_started
@ -408,6 +430,8 @@ services:
condition: service_started condition: service_started
starbookservice: starbookservice:
condition: service_started condition: service_started
redis:
condition: service_healthy
networks: networks:
- topfans-net - topfans-net
ports: ports:

View File

@ -2,6 +2,10 @@
<view class="creation-grid"> <view class="creation-grid">
<view v-for="(item, index) in creationList" :key="item.id" class="creation-card" @click="handleCardClick(item)"> <view v-for="(item, index) in creationList" :key="item.id" class="creation-card" @click="handleCardClick(item)">
<image class="creation-image" :src="item.cover_image" mode="aspectFill"></image> <image class="creation-image" :src="item.cover_image" mode="aspectFill"></image>
<!-- 光波动画层 - 外层 -->
<view class="wf-like-wave wf-like-wave-outer" :class="{ 'wf-like-wave-active': likingMap[item.id] }" />
<!-- 光波动画层 - 内层 -->
<view class="wf-like-wave wf-like-wave-inner" :class="{ 'wf-like-wave-active': likingMap[item.id] }" />
<view class="creation-info"> <view class="creation-info">
<view class="creation-id"> <view class="creation-id">
<text class="id-text">#{{ item.certificate_id }}</text> <text class="id-text">#{{ item.certificate_id }}</text>
@ -47,6 +51,7 @@ const creationList = ref([])
const cursor = ref('') const cursor = ref('')
const isLoading = ref(false) const isLoading = ref(false)
const noMore = ref(false) const noMore = ref(false)
const likingMap = ref({})
let isComponentMounted = false let isComponentMounted = false
const formatCount = (count) => { const formatCount = (count) => {
@ -141,11 +146,28 @@ watch(() => props.isActive, (active) => {
onMounted(() => { onMounted(() => {
isComponentMounted = true isComponentMounted = true
//
uni.$on('assetLiked', ({ asset_id, data }) => {
console.log('[CreationGrid] 收到点赞事件', asset_id, data)
//
likingMap.value = { ...likingMap.value, [asset_id]: true }
setTimeout(() => {
likingMap.value = { ...likingMap.value, [asset_id]: false }
}, 600)
const idx = creationList.value.findIndex(c => c.id === asset_id)
console.log('[CreationGrid] 卡片索引:', idx, '总卡片数:', creationList.value.length)
if (idx !== -1 && data && typeof data.new_like_count === 'number') {
console.log('[CreationGrid] 更新点赞数:', creationList.value[idx].like_count, '->', data.new_like_count)
creationList.value[idx].like_count = data.new_like_count
creationList.value = [...creationList.value]
}
})
loadUsers() loadUsers()
}) })
onUnmounted(() => { onUnmounted(() => {
isComponentMounted = false isComponentMounted = false
uni.off('assetLiked')
}) })
defineExpose({ loadMore }) defineExpose({ loadMore })
@ -168,6 +190,7 @@ defineExpose({ loadMore })
overflow: hidden; overflow: hidden;
backdrop-filter: blur(10rpx); backdrop-filter: blur(10rpx);
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1); box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
position: relative;
} }
.creation-image { .creation-image {
@ -175,7 +198,44 @@ defineExpose({ loadMore })
height: 400rpx; height: 400rpx;
} }
/* 光波动画 */
.wf-like-wave {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 400rpx;
pointer-events: none;
opacity: 0;
z-index: 1;
}
.wf-like-wave-outer {
background: radial-gradient(circle, rgba(255, 107, 107, 0.8) 0%, transparent 70%);
}
.wf-like-wave-inner {
background: radial-gradient(circle, rgba(255, 184, 0, 0.6) 0%, transparent 70%);
}
.wf-like-wave-active {
animation: likeWave 0.6s ease-out forwards;
}
@keyframes likeWave {
0% {
opacity: 0.9;
transform: scale(0.8);
}
100% {
opacity: 0;
transform: scale(1.5);
}
}
.creation-info { .creation-info {
position: relative;
z-index: 2;
padding: 16rpx; padding: 16rpx;
} }

View File

@ -918,6 +918,15 @@ onMounted(() => {
isIOS = sysInfo.platform === 'ios' isIOS = sysInfo.platform === 'ios'
const containerH = props.screenHeight - props.bannerBottom const containerH = props.screenHeight - props.bannerBottom
layout = new WaterfallLayout(props.screenWidth, containerH, GAP) layout = new WaterfallLayout(props.screenWidth, containerH, GAP)
//
uni.$on('assetLiked', ({ asset_id, data }) => {
const idx = cards.value.findIndex(c => c.id === asset_id)
if (idx !== -1 && data && typeof data.likes === 'number') {
cards.value[idx].likes = data.likes
//
cards.value = [...cards.value]
}
})
loadUsers().then(() => { loadUsers().then(() => {
isInitialLoading = false isInitialLoading = false
nextTick(() => { nextTick(() => {
@ -1034,6 +1043,7 @@ onUnmounted(() => {
try { try {
uni.offAppShow(onAppShowHandler) uni.offAppShow(onAppShowHandler)
uni.offAppHide(onAppHideHandler) uni.offAppHide(onAppHideHandler)
uni.off('assetLiked')
} catch (_) { } } catch (_) { }
} }
}) })

View File

@ -97,6 +97,7 @@ import BannerCarousel from './components/BannerCarousel.vue'
import CreationGrid from './components/CreationGrid.vue' import CreationGrid from './components/CreationGrid.vue'
import { clearSubStepProgress, shouldShowGuideStartModal } from '@/utils/guideConfig.js' import { clearSubStepProgress, shouldShowGuideStartModal } from '@/utils/guideConfig.js'
import { useBanner } from './composables/useBanner.js' import { useBanner } from './composables/useBanner.js'
import { doubleTapLike } from '@/utils/likeHelper.js'
import { USE_MOCK_DATA } from './config/mockData.js' import { USE_MOCK_DATA } from './config/mockData.js'
// ========== Store & User Info ========== // ========== Store & User Info ==========
@ -111,6 +112,8 @@ const showRankingModal = ref(false)
const isActive = ref(true) const isActive = ref(true)
const isFixed = ref(false) const isFixed = ref(false)
const creationGridRef = ref(null) const creationGridRef = ref(null)
const cardTapTimers = {}
const likingMap = ref({})
// ========== ========== // ========== ==========
const categories = ref([ const categories = ref([
@ -145,7 +148,32 @@ const bannerBottomPx = computed(() => Math.round(screenWidth.value / 750 * 715))
// ========== Handlers ========== // ========== Handlers ==========
const handleCardClick = (card) => { const handleCardClick = (card) => {
// CreationGrid if (cardTapTimers[card.id]) {
//
clearTimeout(cardTapTimers[card.id]);
delete cardTapTimers[card.id];
//
likingMap.value = { ...likingMap.value, [card.id]: true };
setTimeout(() => {
likingMap.value = { ...likingMap.value, [card.id]: false };
}, 600);
doubleTapLike(card.id, card.exhibition_id || 0, (success) => {
if (success) {
uni.showToast({ title: '点赞成功', icon: 'success' })
}
});
} else {
//
if (card.id) {
cardTapTimers[card.id] = setTimeout(() => {
delete cardTapTimers[card.id];
uni.navigateTo({ url: `/pages/asset-detail/asset-detail?asset_id=${card.id}` });
}, 300);
}
}
} }
const handleScrollToLower = () => { const handleScrollToLower = () => {

View File

@ -54,7 +54,7 @@ export function doubleTapLike(assetId, exhibitionId, callback) {
// } // }
likeAssetApi(assetId).then(res => { likeAssetApi(assetId).then(res => {
console.log('点赞成功', res); console.log('[likeHelper] 点赞成功', res);
// 记录点赞 // 记录点赞
try { try {
const storage = uni.getStorageSync(LIKE_STORAGE_KEY) || {}; const storage = uni.getStorageSync(LIKE_STORAGE_KEY) || {};
@ -64,6 +64,7 @@ export function doubleTapLike(assetId, exhibitionId, callback) {
console.error('存储点赞记录失败:', e); console.error('存储点赞记录失败:', e);
} }
// 触发全局点赞成功事件 // 触发全局点赞成功事件
console.log('[likeHelper] 触发 assetLiked 事件', { asset_id: assetId, exhibition_id: actualExhibitionId, data: res.data });
uni.$emit('assetLiked', { uni.$emit('assetLiked', {
asset_id: assetId, asset_id: assetId,
exhibition_id: actualExhibitionId, exhibition_id: actualExhibitionId,