merge: 短剧保存免费集数同步分集免费状态
This commit is contained in:
commit
e92ec118fe
6 files changed
+359
-45
No files matched your search
@@ -0,0 +1,30 @@
|
|||||||
|
# 2026-10-21 | 短剧保存免费集数同步分集
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
后台短剧编辑改 `freeEpisodes` / `unlockCoin` 后,同步 `drama_episode.is_free` / `unlock_coin`,与定价页 `PriceService.save()` 规则一致。
|
||||||
|
|
||||||
|
## 方案
|
||||||
|
|
||||||
|
1. 抽出 `EpisodePriceSync.syncByDramaRule(dramaId, freeEpisodes, unlockCoin)`,供 `DramaService.update` 与 `PriceService.save` 共用。
|
||||||
|
2. `DramaService.update` 在 `updateById` 后,仅当免费集数或解锁金币相对更新前有变化时调用同步。
|
||||||
|
3. TDD:先写 `DramaServiceTest` 复现失败,再改实现转绿。
|
||||||
|
|
||||||
|
## 范围
|
||||||
|
|
||||||
|
- 仅 `fruitshorts-module-content`
|
||||||
|
- 不改 Controller / VO / 契约 / 前端
|
||||||
|
|
||||||
|
## 门禁
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mvn -pl fruitshorts-module-content test -Dtest=DramaServiceTest
|
||||||
|
mvn -pl fruitshorts-module-content test
|
||||||
|
```
|
||||||
|
|
||||||
|
## 分支 / 提交
|
||||||
|
|
||||||
|
- `feature/drama-free-episodes-sync` ← `develop`
|
||||||
|
- `test(content): 覆盖短剧免费集数同步分集价格`
|
||||||
|
- `fix(content): 短剧保存免费集数时同步分集免费状态`
|
||||||
|
- `merge --no-ff` 回 develop 并 push
|
||||||
@@ -25,5 +25,10 @@
|
|||||||
<groupId>com.fruitshorts</groupId>
|
<groupId>com.fruitshorts</groupId>
|
||||||
<artifactId>fruitshorts-module-system</artifactId>
|
<artifactId>fruitshorts-module-system</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
+16
@@ -30,6 +30,7 @@ import java.time.LocalDateTime;
|
|||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@@ -42,6 +43,7 @@ public class DramaService {
|
|||||||
private final DramaEpisodeMapper dramaEpisodeMapper;
|
private final DramaEpisodeMapper dramaEpisodeMapper;
|
||||||
private final DramaReviewRecordMapper dramaReviewRecordMapper;
|
private final DramaReviewRecordMapper dramaReviewRecordMapper;
|
||||||
private final DramaCategoryService dramaCategoryService;
|
private final DramaCategoryService dramaCategoryService;
|
||||||
|
private final EpisodePriceSync episodePriceSync;
|
||||||
|
|
||||||
public PageResult<DramaRespVO> page(DramaPageReqVO req) {
|
public PageResult<DramaRespVO> page(DramaPageReqVO req) {
|
||||||
List<String> dramaTypes = DramaMultiTagSupport.normalizeDramaTypes(req.getDramaTypes());
|
List<String> dramaTypes = DramaMultiTagSupport.normalizeDramaTypes(req.getDramaTypes());
|
||||||
@@ -109,6 +111,8 @@ public class DramaService {
|
|||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void update(Long id, DramaSaveReqVO req) {
|
public void update(Long id, DramaSaveReqVO req) {
|
||||||
Drama drama = getExisting(id);
|
Drama drama = getExisting(id);
|
||||||
|
Integer oldFreeEpisodes = drama.getFreeEpisodes();
|
||||||
|
Integer oldUnlockCoin = drama.getUnlockCoin();
|
||||||
apply(drama, req, false);
|
apply(drama, req, false);
|
||||||
if (req.getFavoriteCount() != null) {
|
if (req.getFavoriteCount() != null) {
|
||||||
drama.setFavoriteCount(nonNegative(req.getFavoriteCount(), "收藏数"));
|
drama.setFavoriteCount(nonNegative(req.getFavoriteCount(), "收藏数"));
|
||||||
@@ -120,6 +124,18 @@ public class DramaService {
|
|||||||
drama.setViewCount(nonNegative(req.getHeat(), "热度"));
|
drama.setViewCount(nonNegative(req.getHeat(), "热度"));
|
||||||
}
|
}
|
||||||
dramaMapper.updateById(drama);
|
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)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
|||||||
+62
@@ -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}。
|
||||||
|
*
|
||||||
|
* <p>定价页与短剧编辑共用同一套规则,避免两处口径漂移。</p>
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class EpisodePriceSync {
|
||||||
|
|
||||||
|
private final DramaEpisodeMapper dramaEpisodeMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全量同步某剧全部分集价格。
|
||||||
|
*
|
||||||
|
* <p>规则:{@code epNo > 0 && epNo <= freeEpisodes} 为免费且 coin=0,
|
||||||
|
* 否则收费且 coin={@code unlockCoin}。</p>
|
||||||
|
*
|
||||||
|
* @return 实际写库的分集数
|
||||||
|
*/
|
||||||
|
public int syncByDramaRule(Long dramaId, int freeEpisodes, int unlockCoin) {
|
||||||
|
List<DramaEpisode> episodes = dramaEpisodeMapper.selectList(Wrappers.<DramaEpisode>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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+56
-45
@@ -32,6 +32,7 @@ public class PriceService {
|
|||||||
private final DramaService dramaService;
|
private final DramaService dramaService;
|
||||||
private final DramaMapper dramaMapper;
|
private final DramaMapper dramaMapper;
|
||||||
private final DramaEpisodeMapper dramaEpisodeMapper;
|
private final DramaEpisodeMapper dramaEpisodeMapper;
|
||||||
|
private final EpisodePriceSync episodePriceSync;
|
||||||
|
|
||||||
/** 读取当前价格配置:后台定价页进入时先看现状再改。 */
|
/** 读取当前价格配置:后台定价页进入时先看现状再改。 */
|
||||||
public PriceRespVO detail(Long dramaId) {
|
public PriceRespVO detail(Long dramaId) {
|
||||||
@@ -72,55 +73,13 @@ public class PriceService {
|
|||||||
drama.setUnlockCoin(unlockCoin);
|
drama.setUnlockCoin(unlockCoin);
|
||||||
dramaMapper.updateById(drama);
|
dramaMapper.updateById(drama);
|
||||||
|
|
||||||
|
int changed = episodePriceSync.syncByDramaRule(drama.getId(), freeEpisodes, unlockCoin);
|
||||||
|
changed += applyEpisodeOverrides(drama.getId(), unlockCoin, req);
|
||||||
|
|
||||||
List<DramaEpisode> episodes = dramaEpisodeMapper.selectList(Wrappers.<DramaEpisode>lambdaQuery()
|
List<DramaEpisode> episodes = dramaEpisodeMapper.selectList(Wrappers.<DramaEpisode>lambdaQuery()
|
||||||
.eq(DramaEpisode::getDramaId, drama.getId())
|
.eq(DramaEpisode::getDramaId, drama.getId())
|
||||||
.orderByAsc(DramaEpisode::getEpNo));
|
.orderByAsc(DramaEpisode::getEpNo));
|
||||||
|
|
||||||
Map<Long, PriceSaveReqVO.EpisodePrice> 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();
|
PriceRespVO vo = new PriceRespVO();
|
||||||
vo.setDramaId(drama.getId());
|
vo.setDramaId(drama.getId());
|
||||||
vo.setFreeEpisodes(freeEpisodes);
|
vo.setFreeEpisodes(freeEpisodes);
|
||||||
@@ -139,4 +98,56 @@ public class PriceService {
|
|||||||
}).toList());
|
}).toList());
|
||||||
return vo;
|
return vo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 在整剧规则同步后再落单集覆盖(含试看秒数)。 */
|
||||||
|
private int applyEpisodeOverrides(Long dramaId, int unlockCoin, PriceSaveReqVO req) {
|
||||||
|
Map<Long, PriceSaveReqVO.EpisodePrice> 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<DramaEpisode> episodes = dramaEpisodeMapper.selectList(Wrappers.<DramaEpisode>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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+190
@@ -0,0 +1,190 @@
|
|||||||
|
// Author: fruit.com.cn
|
||||||
|
|
||||||
|
package com.fruitshorts.content.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||||
|
import com.fruitshorts.content.dal.entity.Drama;
|
||||||
|
import com.fruitshorts.content.dal.entity.DramaEpisode;
|
||||||
|
import com.fruitshorts.content.dal.mapper.DramaEpisodeMapper;
|
||||||
|
import com.fruitshorts.content.dal.mapper.DramaMapper;
|
||||||
|
import com.fruitshorts.content.dal.mapper.DramaReviewRecordMapper;
|
||||||
|
import com.fruitshorts.content.vo.DramaSaveReqVO;
|
||||||
|
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.BDDMockito.given;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 短剧保存时免费集数 / 解锁金币须同步分集价格。
|
||||||
|
*/
|
||||||
|
class DramaServiceTest {
|
||||||
|
|
||||||
|
private static final long DRAMA_ID = 1001L;
|
||||||
|
private static final int UNLOCK_COIN = 60;
|
||||||
|
|
||||||
|
private DramaMapper dramaMapper;
|
||||||
|
private DramaEpisodeMapper dramaEpisodeMapper;
|
||||||
|
private DramaService dramaService;
|
||||||
|
private List<DramaEpisode> episodes;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void initTableInfoCache() {
|
||||||
|
MapperBuilderAssistant assistant = new MapperBuilderAssistant(new MybatisConfiguration(), "");
|
||||||
|
TableInfoHelper.initTableInfo(assistant, Drama.class);
|
||||||
|
TableInfoHelper.initTableInfo(assistant, DramaEpisode.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
dramaMapper = mock(DramaMapper.class);
|
||||||
|
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, episodePriceSync);
|
||||||
|
episodes = new ArrayList<>();
|
||||||
|
given(dramaEpisodeMapper.selectList(any())).willAnswer(invocation -> List.copyOf(episodes));
|
||||||
|
given(dramaEpisodeMapper.updateById(any(DramaEpisode.class))).willAnswer(invocation -> {
|
||||||
|
DramaEpisode updated = invocation.getArgument(0);
|
||||||
|
for (int i = 0; i < episodes.size(); i++) {
|
||||||
|
if (episodes.get(i).getId().equals(updated.getId())) {
|
||||||
|
episodes.set(i, updated);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_mark_episodes_4_and_5_free_when_free_episodes_increase_from_3_to_5() {
|
||||||
|
givenExistingDrama(3, UNLOCK_COIN);
|
||||||
|
seedEpisodesAlignedToFree(3, 6);
|
||||||
|
|
||||||
|
dramaService.update(DRAMA_ID, saveReq(5, UNLOCK_COIN));
|
||||||
|
|
||||||
|
Map<Integer, DramaEpisode> byEpNo = episodeMap();
|
||||||
|
assertThat(byEpNo.get(4).getIsFree()).isEqualTo(1);
|
||||||
|
assertThat(byEpNo.get(4).getUnlockCoin()).isZero();
|
||||||
|
assertThat(byEpNo.get(5).getIsFree()).isEqualTo(1);
|
||||||
|
assertThat(byEpNo.get(5).getUnlockCoin()).isZero();
|
||||||
|
assertThat(byEpNo.get(6).getIsFree()).isZero();
|
||||||
|
assertThat(byEpNo.get(6).getUnlockCoin()).isEqualTo(UNLOCK_COIN);
|
||||||
|
verify(dramaEpisodeMapper, times(2)).updateById(any(DramaEpisode.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_mark_episodes_3_to_5_paid_when_free_episodes_decrease_from_5_to_2() {
|
||||||
|
givenExistingDrama(5, UNLOCK_COIN);
|
||||||
|
seedEpisodesAlignedToFree(5, 5);
|
||||||
|
|
||||||
|
dramaService.update(DRAMA_ID, saveReq(2, UNLOCK_COIN));
|
||||||
|
|
||||||
|
Map<Integer, DramaEpisode> byEpNo = episodeMap();
|
||||||
|
assertThat(byEpNo.get(3).getIsFree()).isZero();
|
||||||
|
assertThat(byEpNo.get(3).getUnlockCoin()).isEqualTo(UNLOCK_COIN);
|
||||||
|
assertThat(byEpNo.get(4).getIsFree()).isZero();
|
||||||
|
assertThat(byEpNo.get(4).getUnlockCoin()).isEqualTo(UNLOCK_COIN);
|
||||||
|
assertThat(byEpNo.get(5).getIsFree()).isZero();
|
||||||
|
assertThat(byEpNo.get(5).getUnlockCoin()).isEqualTo(UNLOCK_COIN);
|
||||||
|
verify(dramaEpisodeMapper, times(3)).updateById(any(DramaEpisode.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_skip_episode_updates_when_free_episodes_unchanged() {
|
||||||
|
givenExistingDrama(3, UNLOCK_COIN);
|
||||||
|
seedEpisodesAlignedToFree(3, 4);
|
||||||
|
AtomicInteger episodeUpdates = new AtomicInteger();
|
||||||
|
given(dramaEpisodeMapper.updateById(any(DramaEpisode.class))).willAnswer(invocation -> {
|
||||||
|
episodeUpdates.incrementAndGet();
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
dramaService.update(DRAMA_ID, saveReq(3, UNLOCK_COIN));
|
||||||
|
|
||||||
|
assertThat(episodeUpdates.get()).isZero();
|
||||||
|
verify(dramaEpisodeMapper, never()).selectList(any());
|
||||||
|
verify(dramaEpisodeMapper, never()).updateById(any(DramaEpisode.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_not_fail_when_drama_has_no_episodes() {
|
||||||
|
givenExistingDrama(3, UNLOCK_COIN);
|
||||||
|
episodes.clear();
|
||||||
|
|
||||||
|
assertThatCode(() -> dramaService.update(DRAMA_ID, saveReq(5, UNLOCK_COIN)))
|
||||||
|
.doesNotThrowAnyException();
|
||||||
|
verify(dramaEpisodeMapper, never()).updateById(any(DramaEpisode.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void givenExistingDrama(int freeEpisodes, int unlockCoin) {
|
||||||
|
Drama drama = new Drama();
|
||||||
|
drama.setId(DRAMA_ID);
|
||||||
|
drama.setTitle("测试短剧");
|
||||||
|
drama.setFreeEpisodes(freeEpisodes);
|
||||||
|
drama.setUnlockCoin(unlockCoin);
|
||||||
|
drama.setSubtitle("");
|
||||||
|
drama.setGenre("");
|
||||||
|
drama.setSynopsis("");
|
||||||
|
drama.setCoverUrl("");
|
||||||
|
drama.setTrailerUrl("");
|
||||||
|
drama.setLanguage("id-ID");
|
||||||
|
drama.setIsHot(0);
|
||||||
|
drama.setSort(0);
|
||||||
|
given(dramaMapper.selectById(DRAMA_ID)).willReturn(drama);
|
||||||
|
given(dramaMapper.updateById(any(Drama.class))).willReturn(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void seedEpisodesAlignedToFree(int freeEpisodes, int total) {
|
||||||
|
episodes.clear();
|
||||||
|
for (int epNo = 1; epNo <= total; epNo++) {
|
||||||
|
DramaEpisode episode = new DramaEpisode();
|
||||||
|
episode.setId((long) epNo);
|
||||||
|
episode.setDramaId(DRAMA_ID);
|
||||||
|
episode.setEpNo(epNo);
|
||||||
|
episode.setTitle("第" + epNo + "集");
|
||||||
|
boolean free = epNo <= freeEpisodes;
|
||||||
|
episode.setIsFree(free ? 1 : 0);
|
||||||
|
episode.setUnlockCoin(free ? 0 : UNLOCK_COIN);
|
||||||
|
episodes.add(episode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DramaSaveReqVO saveReq(int freeEpisodes, int unlockCoin) {
|
||||||
|
DramaSaveReqVO req = new DramaSaveReqVO();
|
||||||
|
req.setTitle("测试短剧");
|
||||||
|
req.setFreeEpisodes(freeEpisodes);
|
||||||
|
req.setUnlockCoin(unlockCoin);
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Integer, DramaEpisode> episodeMap() {
|
||||||
|
ArgumentCaptor<DramaEpisode> captor = ArgumentCaptor.forClass(DramaEpisode.class);
|
||||||
|
verify(dramaEpisodeMapper, org.mockito.Mockito.atLeastOnce()).updateById(captor.capture());
|
||||||
|
Map<Integer, DramaEpisode> updated = captor.getAllValues().stream()
|
||||||
|
.collect(Collectors.toMap(DramaEpisode::getEpNo, Function.identity(), (a, b) -> b));
|
||||||
|
for (DramaEpisode episode : episodes) {
|
||||||
|
updated.putIfAbsent(episode.getEpNo(), episode);
|
||||||
|
}
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user