diff --git a/docs/plans/2026-10-21-drama-price-sync-idempotent.md b/docs/plans/2026-10-21-drama-price-sync-idempotent.md new file mode 100644 index 0000000..e7f1638 --- /dev/null +++ b/docs/plans/2026-10-21-drama-price-sync-idempotent.md @@ -0,0 +1,24 @@ +# 2026-10-21 | 短剧保存始终同步分集价格(幂等纠偏) + +## 背景 + +承接 K104。短剧 `free_episodes=9` 重存后,分集 5~9 仍收费。 + +## 根因 + +`DramaService.update()` 仅在 `freeEpisodes` / `unlockCoin` 相对更新前有变化时调用 `EpisodePriceSync`。历史数据已漂移时,同值重存不触发同步。 + +## 方案 + +1. 测试:覆盖「规则字段未变、分集已漂移」仍纠正。 +2. `DramaService.update()`:每次保存都调用 `EpisodePriceSync.syncByDramaRule(drama)`。 +3. `EpisodePriceSync`:仅对有差异的行走 `updateById`(已具备,保持)。 + +## 范围 + +只改 `fruitshorts-module-content` 的 `DramaService` / `EpisodePriceSync` / `DramaServiceTest`。不改 Controller / VO / 契约 / 前端。 + +## 提交 + +1. `test(content): 覆盖免费集数未变但分集漂移的修复场景` +2. `fix(content): 短剧保存时始终同步分集价格` diff --git a/docs/plans/PROGRESS.md b/docs/plans/PROGRESS.md index 843526f..255cfc4 100644 --- a/docs/plans/PROGRESS.md +++ b/docs/plans/PROGRESS.md @@ -24633,3 +24633,18 @@ $ git push origin develop ``` 解读:作者 Zhou Weidong;无 Co-authored-by / AI 痕迹;develop 与 feature 均已 push。未启动/重启 8080/8081。停住等验收。 + +--- + +## 2026-10-21 短剧保存始终同步分集价格 + +计划:`docs/plans/2026-10-21-drama-price-sync-idempotent.md`。 +同值重存也调用 `EpisodePriceSync.syncByDramaRule(drama)`,只更新有差异的分集。 + +``` +$ mvn -pl fruitshorts-module-content -am test -Dtest=DramaServiceTest -Dsurefire.failIfNoSpecifiedTests=false +[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.184 s -- in com.fruitshorts.content.service.DramaServiceTest +[INFO] BUILD SUCCESS +``` + +解读:新增「规则未变但分集漂移」用例;原「已对齐则不写库」仍通过,但会查询分集。 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 8734c64..01f0974 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,7 +30,6 @@ 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 @@ -111,8 +110,6 @@ 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(), "收藏数")); @@ -124,18 +121,7 @@ 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); + episodePriceSync.syncByDramaRule(drama); } @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 index 9e2bd1e..48b9aa0 100644 --- 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 @@ -3,6 +3,7 @@ package com.fruitshorts.content.service; import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.fruitshorts.content.dal.entity.Drama; import com.fruitshorts.content.dal.entity.DramaEpisode; import com.fruitshorts.content.dal.mapper.DramaEpisodeMapper; import lombok.RequiredArgsConstructor; @@ -22,6 +23,17 @@ public class EpisodePriceSync { private final DramaEpisodeMapper dramaEpisodeMapper; + /** + * 按短剧当前免费集数与解锁金币同步全部分集。 + * + * @return 实际写库的分集数 + */ + public int syncByDramaRule(Drama drama) { + int freeEpisodes = drama.getFreeEpisodes() == null ? 0 : Math.max(0, drama.getFreeEpisodes()); + int unlockCoin = drama.getUnlockCoin() == null ? 0 : Math.max(0, drama.getUnlockCoin()); + return syncByDramaRule(drama.getId(), freeEpisodes, unlockCoin); + } + /** * 全量同步某剧全部分集价格。 *