diff --git a/fruitshorts-module-content/src/main/java/com/fruitshorts/content/service/DramaService.java b/fruitshorts-module-content/src/main/java/com/fruitshorts/content/service/DramaService.java index 24e17b2..8734c64 100644 --- a/fruitshorts-module-content/src/main/java/com/fruitshorts/content/service/DramaService.java +++ b/fruitshorts-module-content/src/main/java/com/fruitshorts/content/service/DramaService.java @@ -30,6 +30,7 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; @Service @@ -42,6 +43,7 @@ public class DramaService { private final DramaEpisodeMapper dramaEpisodeMapper; private final DramaReviewRecordMapper dramaReviewRecordMapper; private final DramaCategoryService dramaCategoryService; + private final EpisodePriceSync episodePriceSync; public PageResult page(DramaPageReqVO req) { List dramaTypes = DramaMultiTagSupport.normalizeDramaTypes(req.getDramaTypes()); @@ -109,6 +111,8 @@ public class DramaService { @Transactional(rollbackFor = Exception.class) public void update(Long id, DramaSaveReqVO req) { Drama drama = getExisting(id); + Integer oldFreeEpisodes = drama.getFreeEpisodes(); + Integer oldUnlockCoin = drama.getUnlockCoin(); apply(drama, req, false); if (req.getFavoriteCount() != null) { drama.setFavoriteCount(nonNegative(req.getFavoriteCount(), "收藏数")); @@ -120,6 +124,18 @@ public class DramaService { drama.setViewCount(nonNegative(req.getHeat(), "热度")); } dramaMapper.updateById(drama); + syncEpisodePricesIfNeeded(drama, oldFreeEpisodes, oldUnlockCoin); + } + + /** 免费集数或解锁金币变化时,按定价规则回写分集。 */ + private void syncEpisodePricesIfNeeded(Drama drama, Integer oldFreeEpisodes, Integer oldUnlockCoin) { + if (Objects.equals(oldFreeEpisodes, drama.getFreeEpisodes()) + && Objects.equals(oldUnlockCoin, drama.getUnlockCoin())) { + return; + } + int freeEpisodes = drama.getFreeEpisodes() == null ? 0 : Math.max(0, drama.getFreeEpisodes()); + int unlockCoin = drama.getUnlockCoin() == null ? 0 : Math.max(0, drama.getUnlockCoin()); + episodePriceSync.syncByDramaRule(drama.getId(), freeEpisodes, unlockCoin); } @Transactional(rollbackFor = Exception.class) diff --git a/fruitshorts-module-content/src/main/java/com/fruitshorts/content/service/EpisodePriceSync.java b/fruitshorts-module-content/src/main/java/com/fruitshorts/content/service/EpisodePriceSync.java new file mode 100644 index 0000000..9e2bd1e --- /dev/null +++ b/fruitshorts-module-content/src/main/java/com/fruitshorts/content/service/EpisodePriceSync.java @@ -0,0 +1,62 @@ +// Author: fruit.com.cn + +package com.fruitshorts.content.service; + +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.fruitshorts.content.dal.entity.DramaEpisode; +import com.fruitshorts.content.dal.mapper.DramaEpisodeMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Objects; + +/** + * 按短剧免费集数规则同步分集 {@code is_free} / {@code unlock_coin}。 + * + *

定价页与短剧编辑共用同一套规则,避免两处口径漂移。

+ */ +@Component +@RequiredArgsConstructor +public class EpisodePriceSync { + + private final DramaEpisodeMapper dramaEpisodeMapper; + + /** + * 全量同步某剧全部分集价格。 + * + *

规则:{@code epNo > 0 && epNo <= freeEpisodes} 为免费且 coin=0, + * 否则收费且 coin={@code unlockCoin}。

+ * + * @return 实际写库的分集数 + */ + public int syncByDramaRule(Long dramaId, int freeEpisodes, int unlockCoin) { + List episodes = dramaEpisodeMapper.selectList(Wrappers.lambdaQuery() + .eq(DramaEpisode::getDramaId, dramaId) + .orderByAsc(DramaEpisode::getEpNo)); + int changed = 0; + for (DramaEpisode episode : episodes) { + int epNo = episode.getEpNo() == null ? 0 : episode.getEpNo(); + int isFree = resolveIsFree(epNo, freeEpisodes); + int coin = resolveUnlockCoin(isFree, unlockCoin); + if (!Objects.equals(episode.getIsFree(), isFree) + || !Objects.equals(episode.getUnlockCoin(), coin)) { + episode.setIsFree(isFree); + episode.setUnlockCoin(coin); + dramaEpisodeMapper.updateById(episode); + changed++; + } + } + return changed; + } + + /** 集号落在免费区间内则为免费。 */ + public static int resolveIsFree(int epNo, int freeEpisodes) { + return epNo > 0 && epNo <= freeEpisodes ? 1 : 0; + } + + /** 免费集 coin 置 0,收费集用短剧解锁金币。 */ + public static int resolveUnlockCoin(int isFree, int unlockCoin) { + return isFree == 1 ? 0 : unlockCoin; + } +} diff --git a/fruitshorts-module-content/src/main/java/com/fruitshorts/content/service/PriceService.java b/fruitshorts-module-content/src/main/java/com/fruitshorts/content/service/PriceService.java index 3bb5c49..af65333 100644 --- a/fruitshorts-module-content/src/main/java/com/fruitshorts/content/service/PriceService.java +++ b/fruitshorts-module-content/src/main/java/com/fruitshorts/content/service/PriceService.java @@ -32,6 +32,7 @@ public class PriceService { private final DramaService dramaService; private final DramaMapper dramaMapper; private final DramaEpisodeMapper dramaEpisodeMapper; + private final EpisodePriceSync episodePriceSync; /** 读取当前价格配置:后台定价页进入时先看现状再改。 */ public PriceRespVO detail(Long dramaId) { @@ -72,55 +73,13 @@ public class PriceService { drama.setUnlockCoin(unlockCoin); dramaMapper.updateById(drama); + int changed = episodePriceSync.syncByDramaRule(drama.getId(), freeEpisodes, unlockCoin); + changed += applyEpisodeOverrides(drama.getId(), unlockCoin, req); + List episodes = dramaEpisodeMapper.selectList(Wrappers.lambdaQuery() .eq(DramaEpisode::getDramaId, drama.getId()) .orderByAsc(DramaEpisode::getEpNo)); - Map overrides = new LinkedHashMap<>(); - if (req.getEpisodes() != null) { - for (PriceSaveReqVO.EpisodePrice item : req.getEpisodes()) { - if (item != null && item.getEpisodeId() != null) { - overrides.put(item.getEpisodeId(), item); - } - } - } - - int changed = 0; - for (DramaEpisode episode : episodes) { - int epNo = episode.getEpNo() == null ? 0 : episode.getEpNo(); - int isFree = epNo > 0 && epNo <= freeEpisodes ? 1 : 0; - int coin = isFree == 1 ? 0 : unlockCoin; - - PriceSaveReqVO.EpisodePrice override = overrides.get(episode.getId()); - Integer previewSeconds = null; - if (override != null) { - if (override.getIsFree() != null) { - isFree = override.getIsFree() == 1 ? 1 : 0; - coin = isFree == 1 ? 0 : unlockCoin; - } - if (isFree == 0 && override.getUnlockCoin() != null) { - coin = Math.max(0, override.getUnlockCoin()); - } - if (override.getPreviewSeconds() != null) { - previewSeconds = Math.max(0, override.getPreviewSeconds()); - } - } - - boolean priceChanged = !Objects.equals(episode.getIsFree(), isFree) - || !Objects.equals(episode.getUnlockCoin(), coin); - boolean previewChanged = previewSeconds != null - && !Objects.equals(episode.getPreviewSeconds(), previewSeconds); - if (priceChanged || previewChanged) { - episode.setIsFree(isFree); - episode.setUnlockCoin(coin); - if (previewSeconds != null) { - episode.setPreviewSeconds(previewSeconds); - } - dramaEpisodeMapper.updateById(episode); - changed++; - } - } - PriceRespVO vo = new PriceRespVO(); vo.setDramaId(drama.getId()); vo.setFreeEpisodes(freeEpisodes); @@ -139,4 +98,56 @@ public class PriceService { }).toList()); return vo; } + + /** 在整剧规则同步后再落单集覆盖(含试看秒数)。 */ + private int applyEpisodeOverrides(Long dramaId, int unlockCoin, PriceSaveReqVO req) { + Map overrides = new LinkedHashMap<>(); + if (req.getEpisodes() != null) { + for (PriceSaveReqVO.EpisodePrice item : req.getEpisodes()) { + if (item != null && item.getEpisodeId() != null) { + overrides.put(item.getEpisodeId(), item); + } + } + } + if (overrides.isEmpty()) { + return 0; + } + List episodes = dramaEpisodeMapper.selectList(Wrappers.lambdaQuery() + .eq(DramaEpisode::getDramaId, dramaId) + .orderByAsc(DramaEpisode::getEpNo)); + int changed = 0; + for (DramaEpisode episode : episodes) { + PriceSaveReqVO.EpisodePrice override = overrides.get(episode.getId()); + if (override == null) { + continue; + } + int isFree = episode.getIsFree() == null ? 0 : episode.getIsFree(); + int coin = episode.getUnlockCoin() == null ? 0 : episode.getUnlockCoin(); + Integer previewSeconds = null; + if (override.getIsFree() != null) { + isFree = override.getIsFree() == 1 ? 1 : 0; + coin = EpisodePriceSync.resolveUnlockCoin(isFree, unlockCoin); + } + if (isFree == 0 && override.getUnlockCoin() != null) { + coin = Math.max(0, override.getUnlockCoin()); + } + if (override.getPreviewSeconds() != null) { + previewSeconds = Math.max(0, override.getPreviewSeconds()); + } + boolean priceChanged = !Objects.equals(episode.getIsFree(), isFree) + || !Objects.equals(episode.getUnlockCoin(), coin); + boolean previewChanged = previewSeconds != null + && !Objects.equals(episode.getPreviewSeconds(), previewSeconds); + if (priceChanged || previewChanged) { + episode.setIsFree(isFree); + episode.setUnlockCoin(coin); + if (previewSeconds != null) { + episode.setPreviewSeconds(previewSeconds); + } + dramaEpisodeMapper.updateById(episode); + changed++; + } + } + return changed; + } } diff --git a/fruitshorts-module-content/src/test/java/com/fruitshorts/content/service/DramaServiceTest.java b/fruitshorts-module-content/src/test/java/com/fruitshorts/content/service/DramaServiceTest.java index 22e6fac..4b4baca 100644 --- a/fruitshorts-module-content/src/test/java/com/fruitshorts/content/service/DramaServiceTest.java +++ b/fruitshorts-module-content/src/test/java/com/fruitshorts/content/service/DramaServiceTest.java @@ -58,8 +58,9 @@ class DramaServiceTest { dramaEpisodeMapper = mock(DramaEpisodeMapper.class); DramaReviewRecordMapper reviewRecordMapper = mock(DramaReviewRecordMapper.class); DramaCategoryService categoryService = mock(DramaCategoryService.class); + EpisodePriceSync episodePriceSync = new EpisodePriceSync(dramaEpisodeMapper); dramaService = new DramaService( - dramaMapper, dramaEpisodeMapper, reviewRecordMapper, categoryService); + dramaMapper, dramaEpisodeMapper, reviewRecordMapper, categoryService, episodePriceSync); episodes = new ArrayList<>(); given(dramaEpisodeMapper.selectList(any())).willAnswer(invocation -> List.copyOf(episodes)); given(dramaEpisodeMapper.updateById(any(DramaEpisode.class))).willAnswer(invocation -> {