feat(interaction): 点赞与评论按集统计
This commit is contained in:
1 parent
c7ff729f84
commit
c6f729fb27
28 files changed
+683
-41
No files matched your search
+2
@@ -40,6 +40,8 @@ public class DramaEpisode {
|
||||
private String status;
|
||||
private LocalDateTime releaseAt;
|
||||
private Long playCount;
|
||||
/** 本集点赞数(点赞目标为 EPISODE) */
|
||||
private Long likeCount;
|
||||
private Integer sort;
|
||||
private LocalDateTime createdAt;
|
||||
/** updated_at 交给数据库的 ON UPDATE CURRENT_TIMESTAMP 维护 */
|
||||
|
||||
+69
@@ -2,6 +2,7 @@
|
||||
|
||||
package com.fruitshorts.content.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.fruitshorts.common.exception.ErrorCode;
|
||||
import com.fruitshorts.common.exception.ServiceException;
|
||||
@@ -14,9 +15,12 @@ import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@@ -103,6 +107,7 @@ public class EpisodeService {
|
||||
apply(episode, req);
|
||||
episode.setStatus(req.getStatus() == null ? ContentStatus.DRAFT.getCode() : req.getStatus());
|
||||
episode.setPlayCount(0L);
|
||||
episode.setLikeCount(nonNegativeOrZero(req.getLikeCount()));
|
||||
episode.setVideoAssetId(0L);
|
||||
dramaEpisodeMapper.insert(episode);
|
||||
dramaService.syncTotalEpisodes(req.getDramaId());
|
||||
@@ -115,6 +120,9 @@ public class EpisodeService {
|
||||
dramaService.getExisting(req.getDramaId());
|
||||
ensureEpNoAvailable(req.getDramaId(), req.getNumber(), id);
|
||||
apply(episode, req);
|
||||
if (req.getLikeCount() != null) {
|
||||
episode.setLikeCount(nonNegative(req.getLikeCount()));
|
||||
}
|
||||
if (req.getStatus() != null) {
|
||||
episode.setStatus(req.getStatus());
|
||||
}
|
||||
@@ -157,6 +165,46 @@ public class EpisodeService {
|
||||
.set(DramaEpisode::getStatus, ContentStatus.PUBLISHED.getCode()));
|
||||
}
|
||||
|
||||
/** 本集点赞数原子增减,避免并发读改写丢计数。 */
|
||||
public void adjustLikeCount(Long episodeId, int delta) {
|
||||
if (episodeId == null || delta == 0) {
|
||||
return;
|
||||
}
|
||||
dramaEpisodeMapper.update(null, Wrappers.<DramaEpisode>lambdaUpdate()
|
||||
.eq(DramaEpisode::getId, episodeId)
|
||||
.setSql(delta > 0
|
||||
? "like_count = like_count + 1"
|
||||
: "like_count = GREATEST(like_count - 1, 0)"));
|
||||
}
|
||||
|
||||
/** 批量汇总多部剧的分集点赞数,避免后台逐剧 N+1 查询。 */
|
||||
public Map<Long, Long> sumLikeCountsByDramas(Collection<Long> dramaIds) {
|
||||
if (dramaIds == null || dramaIds.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
List<Long> ids = dramaIds.stream().filter(Objects::nonNull).distinct().toList();
|
||||
if (ids.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
QueryWrapper<DramaEpisode> wrapper = Wrappers.query();
|
||||
wrapper.select("drama_id AS drama_id", "COALESCE(SUM(like_count), 0) AS like_count")
|
||||
.in("drama_id", ids)
|
||||
.eq("deleted", 0)
|
||||
.groupBy("drama_id");
|
||||
List<Map<String, Object>> rows = dramaEpisodeMapper.selectMaps(wrapper);
|
||||
if (rows == null || rows.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
Map<Long, Long> counts = new HashMap<>();
|
||||
for (Map<String, Object> row : rows) {
|
||||
Long dramaId = asLong(row.get("drama_id"));
|
||||
if (dramaId != null) {
|
||||
counts.put(dramaId, asLong(row.get("like_count"), 0L));
|
||||
}
|
||||
}
|
||||
return counts;
|
||||
}
|
||||
|
||||
private void ensureEpNoAvailable(Long dramaId, Integer epNo, Long excludeId) {
|
||||
Long count = dramaEpisodeMapper.selectCount(Wrappers.<DramaEpisode>lambdaQuery()
|
||||
.eq(DramaEpisode::getDramaId, dramaId)
|
||||
@@ -194,10 +242,31 @@ public class EpisodeService {
|
||||
vo.setStatus(episode.getStatus());
|
||||
vo.setStatusText(ContentStatus.textOf(episode.getStatus()));
|
||||
vo.setViews(episode.getPlayCount());
|
||||
vo.setLikeCount(episode.getLikeCount() == null ? 0L : episode.getLikeCount());
|
||||
vo.setSort(episode.getSort());
|
||||
return vo;
|
||||
}
|
||||
|
||||
private Long nonNegativeOrZero(Long value) {
|
||||
return value == null ? 0L : nonNegative(value);
|
||||
}
|
||||
|
||||
private Long nonNegative(Long value) {
|
||||
if (value < 0) {
|
||||
throw new ServiceException(ErrorCode.BAD_REQUEST, "本集点赞数不能小于 0");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private Long asLong(Object value) {
|
||||
return value instanceof Number number ? number.longValue() : null;
|
||||
}
|
||||
|
||||
private long asLong(Object value, long defaultValue) {
|
||||
Long number = asLong(value);
|
||||
return number == null ? defaultValue : number;
|
||||
}
|
||||
|
||||
private String formatDuration(Integer seconds) {
|
||||
int value = seconds == null ? 0 : seconds;
|
||||
return String.format("%02d:%02d", value / 60, value % 60);
|
||||
|
||||
@@ -21,5 +21,13 @@ public class EpisodeRespVO {
|
||||
private String status;
|
||||
private String statusText;
|
||||
private Long views;
|
||||
/** 本集点赞数 */
|
||||
private Long likeCount;
|
||||
/** 本集评论数 */
|
||||
private Long commentCount;
|
||||
/** 所属剧收藏数,只读 */
|
||||
private Long favoriteCount;
|
||||
/** 所属剧热度(drama.view_count),只读 */
|
||||
private Long heat;
|
||||
private Integer sort;
|
||||
}
|
||||
+3
@@ -3,6 +3,7 @@
|
||||
package com.fruitshorts.content.vo;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.PositiveOrZero;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@@ -20,6 +21,8 @@ public class EpisodeSaveReqVO {
|
||||
private String videoUrl;
|
||||
private Boolean isFree;
|
||||
private Integer unlockCoin;
|
||||
@PositiveOrZero(message = "本集点赞数不能小于 0")
|
||||
private Long likeCount;
|
||||
private String status;
|
||||
private Integer sort;
|
||||
}
|
||||
Reference in new issue
Block a user