Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07754ff4e4 | ||
|
|
4159af1732 | ||
|
|
0143fa036c | ||
|
|
5227762aeb | ||
|
|
a4317bb6f1 | ||
|
|
9adf8c5630 | ||
|
|
8bb7f63c62 | ||
|
|
b8b3fd867b | ||
|
|
e92ec118fe |
No files matched your search
@@ -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): 短剧保存时始终同步分集价格`
|
||||||
@@ -24571,3 +24571,172 @@ OK_EXIT=0
|
|||||||
解读:拦截生效,干净 message 正常通过 commitlint。
|
解读:拦截生效,干净 message 正常通过 commitlint。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-10-21 | 短剧保存免费集数同步分集免费状态
|
||||||
|
|
||||||
|
【Codex】根因:DramaService.update 只更新 drama,未同步 drama_episode.is_free/unlock_coin。
|
||||||
|
抽出 EpisodePriceSync.syncByDramaRule,DramaService.update 与 PriceService.save 共用;仅 freeEpisodes/unlockCoin 变化时同步。
|
||||||
|
|
||||||
|
### 门禁(原始输出摘录)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ mvn -pl fruitshorts-module-content -am test -Dsurefire.failIfNoSpecifiedTests=false
|
||||||
|
[INFO] T E S T S
|
||||||
|
[INFO] -------------------------------------------------------
|
||||||
|
[INFO] Running com.fruitshorts.content.service.DramaServiceTest
|
||||||
|
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
|
||||||
|
WARNING: A Java agent has been loaded dynamically (/Users/jianghaifeng/.m2/repository/net/bytebuddy/byte-buddy-agent/1.14.13/byte-buddy-agent-1.14.13.jar)
|
||||||
|
WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning
|
||||||
|
WARNING: If a serviceability tool is not in use, please run with -Djdk.instrument.traceUsage for more information
|
||||||
|
WARNING: Dynamic loading of agents will be disallowed by default in a future release
|
||||||
|
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.126 s -- in com.fruitshorts.content.service.DramaServiceTest
|
||||||
|
[INFO]
|
||||||
|
[INFO] Results:
|
||||||
|
[INFO]
|
||||||
|
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0
|
||||||
|
[INFO]
|
||||||
|
[INFO] ------------------------------------------------------------------------
|
||||||
|
[INFO] Reactor Summary for fruitshorts 1.0.0-SNAPSHOT:
|
||||||
|
[INFO]
|
||||||
|
[INFO] fruitshorts ........................................ SUCCESS [ 0.001 s]
|
||||||
|
[INFO] fruitshorts-common ................................. SUCCESS [ 3.685 s]
|
||||||
|
[INFO] fruitshorts-framework .............................. SUCCESS [ 31.492 s]
|
||||||
|
[INFO] fruitshorts-module-system .......................... SUCCESS [ 0.051 s]
|
||||||
|
[INFO] fruitshorts-module-content ......................... SUCCESS [ 4.451 s]
|
||||||
|
[INFO] ------------------------------------------------------------------------
|
||||||
|
[INFO] BUILD SUCCESS
|
||||||
|
[INFO] ------------------------------------------------------------------------
|
||||||
|
[INFO] Total time: 39.944 s
|
||||||
|
[INFO] Finished at: 2026-10-21T22:35:43+08:00
|
||||||
|
[INFO] ------------------------------------------------------------------------
|
||||||
|
```
|
||||||
|
|
||||||
|
解读:DramaServiceTest 4 条全过;module-content BUILD SUCCESS。
|
||||||
|
|
||||||
|
### 提交 / 合并 / push
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ git log -2 --format=%B
|
||||||
|
fix(content): 短剧保存免费集数时同步分集免费状态
|
||||||
|
|
||||||
|
test(content): 覆盖短剧免费集数同步分集价格
|
||||||
|
|
||||||
|
$ git log --oneline -4
|
||||||
|
e92ec11 merge: 短剧保存免费集数同步分集免费状态
|
||||||
|
4c4b53c fix(content): 短剧保存免费集数时同步分集免费状态
|
||||||
|
d7f2266 test(content): 覆盖短剧免费集数同步分集价格
|
||||||
|
979e6eb docs(plans): 登记 K100 提交 AI 痕迹已知偏差
|
||||||
|
|
||||||
|
$ git push origin feature/drama-free-episodes-sync
|
||||||
|
$ git push origin develop
|
||||||
|
# develop 979e6eb..e92ec11
|
||||||
|
```
|
||||||
|
|
||||||
|
解读:作者 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)`,只更新有差异的分集。
|
||||||
|
|
||||||
|
### 门禁(原始输出)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ mvn -pl fruitshorts-module-content test
|
||||||
|
[INFO] -------------------------------------------------------
|
||||||
|
[INFO] T E S T S
|
||||||
|
[INFO] -------------------------------------------------------
|
||||||
|
[INFO] Running com.fruitshorts.content.service.DramaServiceTest
|
||||||
|
[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.087 s -- in com.fruitshorts.content.service.DramaServiceTest
|
||||||
|
[INFO]
|
||||||
|
[INFO] Results:
|
||||||
|
[INFO]
|
||||||
|
[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0
|
||||||
|
[INFO]
|
||||||
|
[INFO] ------------------------------------------------------------------------
|
||||||
|
[INFO] BUILD SUCCESS
|
||||||
|
[INFO] ------------------------------------------------------------------------
|
||||||
|
[INFO] Total time: 38.170 s
|
||||||
|
[INFO] Finished at: 2026-10-21T22:52:35+08:00
|
||||||
|
[INFO] ------------------------------------------------------------------------
|
||||||
|
```
|
||||||
|
|
||||||
|
解读:DramaServiceTest 5 条全过(含漂移纠偏 + 已对齐幂等不写库);module-content BUILD SUCCESS。
|
||||||
|
|
||||||
|
### 提交 / 合并 / push
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ git log -3 --format=%B
|
||||||
|
merge: 短剧保存时始终同步分集价格
|
||||||
|
|
||||||
|
fix(content): 短剧保存时始终同步分集价格
|
||||||
|
|
||||||
|
test(content): 覆盖免费集数未变但分集漂移的修复场景
|
||||||
|
|
||||||
|
$ git log -3 --format='%h %an <%ae> %s'
|
||||||
|
a4317bb jianghaifeng <haifeng_hello@163.com> merge: 短剧保存时始终同步分集价格
|
||||||
|
9adf8c5 Vicky <10212746+haifeng-jiang@user.noreply.gitee.com> fix(content): 短剧保存时始终同步分集价格
|
||||||
|
8bb7f63 Vicky <10212746+haifeng-jiang@user.noreply.gitee.com> test(content): 覆盖免费集数未变但分集漂移的修复场景
|
||||||
|
```
|
||||||
|
|
||||||
|
解读:功能与分支/merge/push 已落地(develop=`a4317bb`,feature=`9adf8c5`)。**作者口径偏差**:三条均非 `Zhou Weidong <502333560@qq.com>`;按「已推送历史不 force push」登记,待 Trae 拍板是否改写。未启停 8080/8081。停住等验收。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 【Codex】2026-10-21 | GET /admin/episodes 服务端分页(K107 追加)
|
||||||
|
|
||||||
|
**契约变更(specs 由专人更新,此处留痕)**:
|
||||||
|
- `GET /admin/episodes` 新增 `pageIndex`(默认 1)、`pageSize`(默认 100,上限 200)
|
||||||
|
- 响应由 `List` 改为 `PageResult{total, list}`;`dramaId`/`status`/`episode:list` 不变
|
||||||
|
- 数据库分页(MyBatis-Plus selectPage),排序 `sort ASC, ep_no ASC`
|
||||||
|
|
||||||
|
**分支**:`feature/episode-list-api-pagination` ← develop
|
||||||
|
|
||||||
|
**命令**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mvn -pl fruitshorts-admin-api -am test
|
||||||
|
```
|
||||||
|
|
||||||
|
**原始输出(关键行)**:
|
||||||
|
|
||||||
|
```
|
||||||
|
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0 — EpisodeControllerContractTest
|
||||||
|
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0 — EpisodeServicePageTest
|
||||||
|
[INFO] Tests run: 38, Failures: 0, Errors: 0, Skipped: 0 — fruitshorts-admin-api
|
||||||
|
[INFO] BUILD SUCCESS
|
||||||
|
[INFO] Total time: 15.046 s
|
||||||
|
[INFO] Finished at: 2026-10-21T23:08:43+08:00
|
||||||
|
```
|
||||||
|
|
||||||
|
**解读**:默认每页 100、第二页不重叠、越界空列表+total 正确;未改 /app、未启停服务。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-10-22 | 已知偏差:ReelShortWeb 99bb666 合并提交作者不合规
|
||||||
|
|
||||||
|
### 事实
|
||||||
|
- 仓库:ReelShortWeb
|
||||||
|
- 提交:`99bb666 chore(merge): 剧集列表改为服务端分页`
|
||||||
|
- 实际作者:`Vicky <10212746+haifeng-jiang@user.noreply.gitee.com>`
|
||||||
|
- 规定身份:`Zhou Weidong <502333560@qq.com>`(AGENTS.md / CODING_STANDARDS.md / GIT_WORKFLOW.md)
|
||||||
|
|
||||||
|
### 原因
|
||||||
|
- ReelShortWeb 仓库本地 `user.name` / `user.email` 配置为 Vicky 口径
|
||||||
|
- `merge --no-ff` 继承该本地身份,未使用命令级 `-c user.name` / `-c user.email` 覆盖
|
||||||
|
|
||||||
|
### 处置
|
||||||
|
- 该提交已推送 `origin/develop`(本地与远端同步)
|
||||||
|
- 按 GIT_WORKFLOW「禁止 force push 到 develop」:**不做历史改写**
|
||||||
|
- 仅登记为已知偏差;后续 commit / merge 一律:
|
||||||
|
`git -c user.name="Zhou Weidong" -c user.email="502333560@qq.com" ...`
|
||||||
|
- ReelShortWeb 根目录留本地说明 `COMMIT_AUTHOR.local`(`*.local` 不进版本库)
|
||||||
|
|
||||||
|
### 关联
|
||||||
|
- 此前已登记的 Co-authored-by / AI 痕迹已知偏差(见本文件「2026-10-21 K100 收口:提交 AI 痕迹登记为已知偏差」)
|
||||||
|
- 此前已登记的短剧价格同步作者口径偏差(`8bb7f63`/`9adf8c5`/`a4317bb`,见 INBOX / 本文件 2026-10-21 条目)
|
||||||
|
|
||||||
|
解读:已推送历史不改写;身份合规靠命令级覆盖防复发;本条仅文档留痕,无代码改动。
|
||||||
|
|
||||||
+9
-5
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
package com.fruitshorts.admin.controller;
|
package com.fruitshorts.admin.controller;
|
||||||
|
|
||||||
|
import com.fruitshorts.common.pojo.PageResult;
|
||||||
import com.fruitshorts.common.pojo.Result;
|
import com.fruitshorts.common.pojo.Result;
|
||||||
import com.fruitshorts.content.dal.entity.Drama;
|
import com.fruitshorts.content.dal.entity.Drama;
|
||||||
import com.fruitshorts.content.service.DramaService;
|
import com.fruitshorts.content.service.DramaService;
|
||||||
@@ -39,12 +40,15 @@ public class EpisodeController {
|
|||||||
private final CommentService commentService;
|
private final CommentService commentService;
|
||||||
private final DramaService dramaService;
|
private final DramaService dramaService;
|
||||||
|
|
||||||
@Operation(summary = "剧集列表")
|
@Operation(summary = "剧集列表(分页)")
|
||||||
@GetMapping
|
@GetMapping
|
||||||
@PreAuthorize("@ss.hasPermission('episode:list')")
|
@PreAuthorize("@ss.hasPermission('episode:list')")
|
||||||
public Result<List<EpisodeRespVO>> list(@RequestParam Long dramaId,
|
public Result<PageResult<EpisodeRespVO>> list(@RequestParam Long dramaId,
|
||||||
@RequestParam(required = false) String status) {
|
@RequestParam(required = false) String status,
|
||||||
List<EpisodeRespVO> episodes = episodeService.listByDrama(dramaId, status);
|
@RequestParam(required = false) Integer pageIndex,
|
||||||
|
@RequestParam(required = false) Integer pageSize) {
|
||||||
|
PageResult<EpisodeRespVO> page = episodeService.pageByDrama(dramaId, status, pageIndex, pageSize);
|
||||||
|
List<EpisodeRespVO> episodes = page.getList();
|
||||||
Map<Long, Long> commentCounts = commentService.countByEpisodes(
|
Map<Long, Long> commentCounts = commentService.countByEpisodes(
|
||||||
episodes.stream().map(EpisodeRespVO::getId).toList());
|
episodes.stream().map(EpisodeRespVO::getId).toList());
|
||||||
Drama drama = dramaService.getExisting(dramaId);
|
Drama drama = dramaService.getExisting(dramaId);
|
||||||
@@ -53,7 +57,7 @@ public class EpisodeController {
|
|||||||
episode.setFavoriteCount(drama.getFavoriteCount());
|
episode.setFavoriteCount(drama.getFavoriteCount());
|
||||||
episode.setHeat(drama.getViewCount());
|
episode.setHeat(drama.getViewCount());
|
||||||
});
|
});
|
||||||
return Result.ok(episodes);
|
return Result.ok(new PageResult<>(page.getTotal(), episodes));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "新建剧集")
|
@Operation(summary = "新建剧集")
|
||||||
|
|||||||
+101
-16
@@ -3,6 +3,7 @@
|
|||||||
package com.fruitshorts.admin.controller;
|
package com.fruitshorts.admin.controller;
|
||||||
|
|
||||||
import com.fruitshorts.admin.support.ControllerContractSupport;
|
import com.fruitshorts.admin.support.ControllerContractSupport;
|
||||||
|
import com.fruitshorts.common.pojo.PageResult;
|
||||||
import com.fruitshorts.content.dal.entity.Drama;
|
import com.fruitshorts.content.dal.entity.Drama;
|
||||||
import com.fruitshorts.content.service.DramaService;
|
import com.fruitshorts.content.service.DramaService;
|
||||||
import com.fruitshorts.content.service.EpisodeService;
|
import com.fruitshorts.content.service.EpisodeService;
|
||||||
@@ -10,20 +11,30 @@ import com.fruitshorts.content.vo.EpisodeRespVO;
|
|||||||
import com.fruitshorts.interaction.service.CommentService;
|
import com.fruitshorts.interaction.service.CommentService;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.ArgumentMatchers.isNull;
|
||||||
import static org.mockito.BDDMockito.given;
|
import static org.mockito.BDDMockito.given;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
/** K89:后台剧集列表按集回填点赞/评论,并带所属剧收藏/热度。 */
|
/** 后台剧集列表:互动数量回填 + 分页契约。 */
|
||||||
class EpisodeControllerContractTest {
|
class EpisodeControllerContractTest {
|
||||||
|
|
||||||
|
private static final long DRAMA_ID = 5L;
|
||||||
|
private static final int DEFAULT_PAGE_SIZE = 100;
|
||||||
|
|
||||||
private EpisodeService episodeService;
|
private EpisodeService episodeService;
|
||||||
private CommentService commentService;
|
private CommentService commentService;
|
||||||
private DramaService dramaService;
|
private DramaService dramaService;
|
||||||
@@ -36,34 +47,108 @@ class EpisodeControllerContractTest {
|
|||||||
dramaService = mock(DramaService.class);
|
dramaService = mock(DramaService.class);
|
||||||
mockMvc = ControllerContractSupport.mockMvcOf(
|
mockMvc = ControllerContractSupport.mockMvcOf(
|
||||||
new EpisodeController(episodeService, commentService, dramaService));
|
new EpisodeController(episodeService, commentService, dramaService));
|
||||||
|
Drama drama = new Drama();
|
||||||
|
drama.setFavoriteCount(88L);
|
||||||
|
drama.setViewCount(1000L);
|
||||||
|
given(dramaService.getExisting(DRAMA_ID)).willReturn(drama);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void should_return_episode_and_drama_interaction_counts() throws Exception {
|
void should_return_episode_and_drama_interaction_counts() throws Exception {
|
||||||
given(episodeService.listByDrama(5L, null)).willReturn(List.of(
|
given(episodeService.pageByDrama(eq(DRAMA_ID), isNull(), isNull(), isNull()))
|
||||||
episode(11L, 10L),
|
.willReturn(new PageResult<>(2L, List.of(episode(11L, 10L), episode(12L, 20L))));
|
||||||
episode(12L, 20L)));
|
|
||||||
given(commentService.countByEpisodes(List.of(11L, 12L)))
|
given(commentService.countByEpisodes(List.of(11L, 12L)))
|
||||||
.willReturn(Map.of(11L, 3L));
|
.willReturn(Map.of(11L, 3L));
|
||||||
Drama drama = new Drama();
|
|
||||||
drama.setFavoriteCount(88L);
|
|
||||||
drama.setViewCount(1000L);
|
|
||||||
given(dramaService.getExisting(5L)).willReturn(drama);
|
|
||||||
|
|
||||||
mockMvc.perform(get("/admin/episodes").param("dramaId", "5"))
|
mockMvc.perform(get("/admin/episodes").param("dramaId", String.valueOf(DRAMA_ID)))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.data[0].likeCount").value("10"))
|
.andExpect(jsonPath("$.data.total").value(2))
|
||||||
.andExpect(jsonPath("$.data[0].commentCount").value("3"))
|
.andExpect(jsonPath("$.data.list[0].likeCount").value("10"))
|
||||||
.andExpect(jsonPath("$.data[0].favoriteCount").value("88"))
|
.andExpect(jsonPath("$.data.list[0].commentCount").value("3"))
|
||||||
.andExpect(jsonPath("$.data[0].heat").value("1000"))
|
.andExpect(jsonPath("$.data.list[0].favoriteCount").value("88"))
|
||||||
.andExpect(jsonPath("$.data[1].likeCount").value("20"))
|
.andExpect(jsonPath("$.data.list[0].heat").value("1000"))
|
||||||
.andExpect(jsonPath("$.data[1].commentCount").value("0"));
|
.andExpect(jsonPath("$.data.list[1].likeCount").value("20"))
|
||||||
|
.andExpect(jsonPath("$.data.list[1].commentCount").value("0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_use_default_page_size_100_when_page_size_omitted() throws Exception {
|
||||||
|
given(episodeService.pageByDrama(eq(DRAMA_ID), isNull(), isNull(), isNull()))
|
||||||
|
.willReturn(PageResult.empty());
|
||||||
|
given(commentService.countByEpisodes(List.of())).willReturn(Map.of());
|
||||||
|
|
||||||
|
mockMvc.perform(get("/admin/episodes").param("dramaId", String.valueOf(DRAMA_ID)))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.data.total").value(0))
|
||||||
|
.andExpect(jsonPath("$.data.list").isArray())
|
||||||
|
.andExpect(jsonPath("$.data.list").isEmpty());
|
||||||
|
|
||||||
|
ArgumentCaptor<Integer> pageIndexCaptor = ArgumentCaptor.forClass(Integer.class);
|
||||||
|
ArgumentCaptor<Integer> pageSizeCaptor = ArgumentCaptor.forClass(Integer.class);
|
||||||
|
verify(episodeService).pageByDrama(
|
||||||
|
eq(DRAMA_ID), isNull(), pageIndexCaptor.capture(), pageSizeCaptor.capture());
|
||||||
|
assertThat(pageIndexCaptor.getValue()).isNull();
|
||||||
|
assertThat(pageSizeCaptor.getValue()).isNull();
|
||||||
|
assertThat(DEFAULT_PAGE_SIZE).isEqualTo(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_non_overlapping_ids_when_second_page_requested() throws Exception {
|
||||||
|
List<EpisodeRespVO> page1 = List.of(episode(1L, 1L), episode(2L, 2L));
|
||||||
|
List<EpisodeRespVO> page2 = List.of(episode(3L, 3L), episode(4L, 4L));
|
||||||
|
given(episodeService.pageByDrama(DRAMA_ID, null, 1, 2))
|
||||||
|
.willReturn(new PageResult<>(4L, page1));
|
||||||
|
given(episodeService.pageByDrama(DRAMA_ID, null, 2, 2))
|
||||||
|
.willReturn(new PageResult<>(4L, page2));
|
||||||
|
given(commentService.countByEpisodes(List.of(1L, 2L))).willReturn(Map.of());
|
||||||
|
given(commentService.countByEpisodes(List.of(3L, 4L))).willReturn(Map.of());
|
||||||
|
|
||||||
|
mockMvc.perform(get("/admin/episodes")
|
||||||
|
.param("dramaId", String.valueOf(DRAMA_ID))
|
||||||
|
.param("pageIndex", "1")
|
||||||
|
.param("pageSize", "2"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.data.total").value(4))
|
||||||
|
.andExpect(jsonPath("$.data.list.length()").value(2));
|
||||||
|
|
||||||
|
String page2Body = mockMvc.perform(get("/admin/episodes")
|
||||||
|
.param("dramaId", String.valueOf(DRAMA_ID))
|
||||||
|
.param("pageIndex", "2")
|
||||||
|
.param("pageSize", "2"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.data.total").value(4))
|
||||||
|
.andExpect(jsonPath("$.data.list[0].id").value("3"))
|
||||||
|
.andExpect(jsonPath("$.data.list[1].id").value("4"))
|
||||||
|
.andReturn()
|
||||||
|
.getResponse()
|
||||||
|
.getContentAsString();
|
||||||
|
|
||||||
|
Set<Long> page1Ids = page1.stream().map(EpisodeRespVO::getId).collect(Collectors.toSet());
|
||||||
|
Set<Long> page2Ids = page2.stream().map(EpisodeRespVO::getId).collect(Collectors.toSet());
|
||||||
|
assertThat(page1Ids).doesNotContainAnyElementsOf(page2Ids);
|
||||||
|
assertThat(page2Body).contains("\"id\":\"3\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_empty_list_with_correct_total_when_page_out_of_range() throws Exception {
|
||||||
|
given(episodeService.pageByDrama(DRAMA_ID, null, 99, 100))
|
||||||
|
.willReturn(new PageResult<>(5L, List.of()));
|
||||||
|
given(commentService.countByEpisodes(List.of())).willReturn(Map.of());
|
||||||
|
|
||||||
|
mockMvc.perform(get("/admin/episodes")
|
||||||
|
.param("dramaId", String.valueOf(DRAMA_ID))
|
||||||
|
.param("pageIndex", "99")
|
||||||
|
.param("pageSize", "100"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.data.total").value(5))
|
||||||
|
.andExpect(jsonPath("$.data.list").isArray())
|
||||||
|
.andExpect(jsonPath("$.data.list").isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
private EpisodeRespVO episode(Long id, Long likeCount) {
|
private EpisodeRespVO episode(Long id, Long likeCount) {
|
||||||
EpisodeRespVO episode = new EpisodeRespVO();
|
EpisodeRespVO episode = new EpisodeRespVO();
|
||||||
episode.setId(id);
|
episode.setId(id);
|
||||||
episode.setDramaId(5L);
|
episode.setDramaId(DRAMA_ID);
|
||||||
episode.setLikeCount(likeCount);
|
episode.setLikeCount(likeCount);
|
||||||
return episode;
|
return episode;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-15
@@ -30,7 +30,6 @@ 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
|
||||||
@@ -111,8 +110,6 @@ 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(), "收藏数"));
|
||||||
@@ -124,18 +121,7 @@ public class DramaService {
|
|||||||
drama.setViewCount(nonNegative(req.getHeat(), "热度"));
|
drama.setViewCount(nonNegative(req.getHeat(), "热度"));
|
||||||
}
|
}
|
||||||
dramaMapper.updateById(drama);
|
dramaMapper.updateById(drama);
|
||||||
syncEpisodePricesIfNeeded(drama, oldFreeEpisodes, oldUnlockCoin);
|
episodePriceSync.syncByDramaRule(drama);
|
||||||
}
|
|
||||||
|
|
||||||
/** 免费集数或解锁金币变化时,按定价规则回写分集。 */
|
|
||||||
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)
|
||||||
|
|||||||
+12
@@ -3,6 +3,7 @@
|
|||||||
package com.fruitshorts.content.service;
|
package com.fruitshorts.content.service;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
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.entity.DramaEpisode;
|
||||||
import com.fruitshorts.content.dal.mapper.DramaEpisodeMapper;
|
import com.fruitshorts.content.dal.mapper.DramaEpisodeMapper;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@@ -22,6 +23,17 @@ public class EpisodePriceSync {
|
|||||||
|
|
||||||
private final DramaEpisodeMapper dramaEpisodeMapper;
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 全量同步某剧全部分集价格。
|
* 全量同步某剧全部分集价格。
|
||||||
*
|
*
|
||||||
|
|||||||
+30
-5
@@ -2,10 +2,14 @@
|
|||||||
|
|
||||||
package com.fruitshorts.content.service;
|
package com.fruitshorts.content.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.fruitshorts.common.exception.ErrorCode;
|
import com.fruitshorts.common.exception.ErrorCode;
|
||||||
import com.fruitshorts.common.exception.ServiceException;
|
import com.fruitshorts.common.exception.ServiceException;
|
||||||
|
import com.fruitshorts.common.pojo.PageResult;
|
||||||
import com.fruitshorts.content.dal.entity.DramaEpisode;
|
import com.fruitshorts.content.dal.entity.DramaEpisode;
|
||||||
import com.fruitshorts.content.dal.mapper.DramaEpisodeMapper;
|
import com.fruitshorts.content.dal.mapper.DramaEpisodeMapper;
|
||||||
import com.fruitshorts.content.enums.ContentStatus;
|
import com.fruitshorts.content.enums.ContentStatus;
|
||||||
@@ -14,6 +18,7 @@ import com.fruitshorts.content.vo.EpisodeSaveReqVO;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -26,19 +31,39 @@ import java.util.Objects;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class EpisodeService {
|
public class EpisodeService {
|
||||||
|
|
||||||
|
private static final int DEFAULT_PAGE_INDEX = 1;
|
||||||
|
private static final int DEFAULT_PAGE_SIZE = 100;
|
||||||
|
private static final int MAX_PAGE_SIZE = 200;
|
||||||
|
|
||||||
private final DramaEpisodeMapper dramaEpisodeMapper;
|
private final DramaEpisodeMapper dramaEpisodeMapper;
|
||||||
private final DramaService dramaService;
|
private final DramaService dramaService;
|
||||||
|
|
||||||
|
/** 后台:按短剧分页查询分集,status 在 SQL 层过滤;排序稳定(sort、ep_no)。 */
|
||||||
|
public PageResult<EpisodeRespVO> pageByDrama(Long dramaId, String status,
|
||||||
|
Integer pageIndex, Integer pageSize) {
|
||||||
|
dramaService.getExisting(dramaId);
|
||||||
|
int safeIndex = pageIndex == null || pageIndex < 1 ? DEFAULT_PAGE_INDEX : pageIndex;
|
||||||
|
int safeSize = pageSize == null || pageSize < 1
|
||||||
|
? DEFAULT_PAGE_SIZE : Math.min(pageSize, MAX_PAGE_SIZE);
|
||||||
|
IPage<DramaEpisode> result = dramaEpisodeMapper.selectPage(
|
||||||
|
new Page<>(safeIndex, safeSize), buildListQuery(dramaId, status));
|
||||||
|
return new PageResult<>(result.getTotal(), result.getRecords().stream().map(this::toResp).toList());
|
||||||
|
}
|
||||||
|
|
||||||
public List<EpisodeRespVO> listByDrama(Long dramaId, String status) {
|
public List<EpisodeRespVO> listByDrama(Long dramaId, String status) {
|
||||||
dramaService.getExisting(dramaId);
|
dramaService.getExisting(dramaId);
|
||||||
return dramaEpisodeMapper.selectList(Wrappers.<DramaEpisode>lambdaQuery()
|
return dramaEpisodeMapper.selectList(buildListQuery(dramaId, status))
|
||||||
.eq(DramaEpisode::getDramaId, dramaId)
|
|
||||||
.eq(status != null && !status.isBlank(), DramaEpisode::getStatus, status)
|
|
||||||
.orderByAsc(DramaEpisode::getSort)
|
|
||||||
.orderByAsc(DramaEpisode::getEpNo))
|
|
||||||
.stream().map(this::toResp).toList();
|
.stream().map(this::toResp).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private LambdaQueryWrapper<DramaEpisode> buildListQuery(Long dramaId, String status) {
|
||||||
|
return Wrappers.<DramaEpisode>lambdaQuery()
|
||||||
|
.eq(DramaEpisode::getDramaId, dramaId)
|
||||||
|
.eq(StringUtils.hasText(status), DramaEpisode::getStatus, status)
|
||||||
|
.orderByAsc(DramaEpisode::getSort)
|
||||||
|
.orderByAsc(DramaEpisode::getEpNo);
|
||||||
|
}
|
||||||
|
|
||||||
/** C 端:只返回已上线剧集,按播放顺序 */
|
/** C 端:只返回已上线剧集,按播放顺序 */
|
||||||
public List<DramaEpisode> listPublishedByDrama(Long dramaId) {
|
public List<DramaEpisode> listPublishedByDrama(Long dramaId) {
|
||||||
return dramaEpisodeMapper.selectList(Wrappers.<DramaEpisode>lambdaQuery()
|
return dramaEpisodeMapper.selectList(Wrappers.<DramaEpisode>lambdaQuery()
|
||||||
|
|||||||
+21
-1
@@ -122,10 +122,30 @@ class DramaServiceTest {
|
|||||||
dramaService.update(DRAMA_ID, saveReq(3, UNLOCK_COIN));
|
dramaService.update(DRAMA_ID, saveReq(3, UNLOCK_COIN));
|
||||||
|
|
||||||
assertThat(episodeUpdates.get()).isZero();
|
assertThat(episodeUpdates.get()).isZero();
|
||||||
verify(dramaEpisodeMapper, never()).selectList(any());
|
verify(dramaEpisodeMapper, times(1)).selectList(any());
|
||||||
verify(dramaEpisodeMapper, never()).updateById(any(DramaEpisode.class));
|
verify(dramaEpisodeMapper, never()).updateById(any(DramaEpisode.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 历史漂移:短剧 freeEpisodes/unlockCoin 已是目标值,但分集 is_free 仍是旧规则。
|
||||||
|
* 同值重存必须纠偏,不能因「规则字段未变」短路跳过同步。
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void should_correct_drifted_episode_is_free_when_free_episodes_and_unlock_coin_unchanged() {
|
||||||
|
givenExistingDrama(9, UNLOCK_COIN);
|
||||||
|
// 规则已是免费 9 集,分集仍按旧规则只免费到第 4 集(5~9 仍收费)
|
||||||
|
seedEpisodesAlignedToFree(4, 9);
|
||||||
|
|
||||||
|
dramaService.update(DRAMA_ID, saveReq(9, UNLOCK_COIN));
|
||||||
|
|
||||||
|
Map<Integer, DramaEpisode> byEpNo = episodeMap();
|
||||||
|
for (int epNo = 1; epNo <= 9; epNo++) {
|
||||||
|
assertThat(byEpNo.get(epNo).getIsFree()).isEqualTo(1);
|
||||||
|
assertThat(byEpNo.get(epNo).getUnlockCoin()).isZero();
|
||||||
|
}
|
||||||
|
verify(dramaEpisodeMapper, times(5)).updateById(any(DramaEpisode.class));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void should_not_fail_when_drama_has_no_episodes() {
|
void should_not_fail_when_drama_has_no_episodes() {
|
||||||
givenExistingDrama(3, UNLOCK_COIN);
|
givenExistingDrama(3, UNLOCK_COIN);
|
||||||
|
|||||||
+151
@@ -0,0 +1,151 @@
|
|||||||
|
// Author: fruit.com.cn
|
||||||
|
|
||||||
|
package com.fruitshorts.content.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.fruitshorts.common.pojo.PageResult;
|
||||||
|
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.vo.EpisodeRespVO;
|
||||||
|
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.List;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.BDDMockito.given;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 剧集后台分页:默认每页 100、上限 200、越界空列表、stable 排序走 DB selectPage。
|
||||||
|
*/
|
||||||
|
class EpisodeServicePageTest {
|
||||||
|
|
||||||
|
private static final long DRAMA_ID = 2001L;
|
||||||
|
|
||||||
|
private DramaEpisodeMapper dramaEpisodeMapper;
|
||||||
|
private DramaService dramaService;
|
||||||
|
private EpisodeService episodeService;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void initTableInfoCache() {
|
||||||
|
MapperBuilderAssistant assistant = new MapperBuilderAssistant(new MybatisConfiguration(), "");
|
||||||
|
TableInfoHelper.initTableInfo(assistant, DramaEpisode.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
dramaEpisodeMapper = mock(DramaEpisodeMapper.class);
|
||||||
|
dramaService = mock(DramaService.class);
|
||||||
|
episodeService = new EpisodeService(dramaEpisodeMapper, dramaService);
|
||||||
|
given(dramaService.getExisting(DRAMA_ID)).willReturn(new Drama());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_use_default_page_size_100_when_page_size_omitted() {
|
||||||
|
stubSelectPage(0L, List.of());
|
||||||
|
|
||||||
|
episodeService.pageByDrama(DRAMA_ID, null, null, null);
|
||||||
|
|
||||||
|
Page<?> page = capturePageArg();
|
||||||
|
assertThat(page.getCurrent()).isEqualTo(1L);
|
||||||
|
assertThat(page.getSize()).isEqualTo(100L);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_cap_page_size_at_200() {
|
||||||
|
stubSelectPage(0L, List.of());
|
||||||
|
|
||||||
|
episodeService.pageByDrama(DRAMA_ID, null, 1, 500);
|
||||||
|
|
||||||
|
Page<?> page = capturePageArg();
|
||||||
|
assertThat(page.getSize()).isEqualTo(200L);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_second_page_records_without_overlap_with_first() {
|
||||||
|
DramaEpisode first = episode(1L, 1, 1);
|
||||||
|
DramaEpisode second = episode(2L, 2, 2);
|
||||||
|
DramaEpisode third = episode(3L, 3, 3);
|
||||||
|
DramaEpisode fourth = episode(4L, 4, 4);
|
||||||
|
given(dramaEpisodeMapper.selectPage(any(Page.class), any())).willAnswer(invocation -> {
|
||||||
|
Page<DramaEpisode> page = invocation.getArgument(0);
|
||||||
|
Page<DramaEpisode> result = new Page<>(page.getCurrent(), page.getSize(), 4L);
|
||||||
|
if (page.getCurrent() == 1L) {
|
||||||
|
result.setRecords(List.of(first, second));
|
||||||
|
} else if (page.getCurrent() == 2L) {
|
||||||
|
result.setRecords(List.of(third, fourth));
|
||||||
|
} else {
|
||||||
|
result.setRecords(List.of());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
PageResult<EpisodeRespVO> page1 = episodeService.pageByDrama(DRAMA_ID, null, 1, 2);
|
||||||
|
PageResult<EpisodeRespVO> page2 = episodeService.pageByDrama(DRAMA_ID, null, 2, 2);
|
||||||
|
|
||||||
|
assertThat(page1.getTotal()).isEqualTo(4L);
|
||||||
|
assertThat(page2.getTotal()).isEqualTo(4L);
|
||||||
|
assertThat(page1.getList()).extracting(EpisodeRespVO::getId).containsExactly(1L, 2L);
|
||||||
|
assertThat(page2.getList()).extracting(EpisodeRespVO::getId).containsExactly(3L, 4L);
|
||||||
|
assertThat(page1.getList()).extracting(EpisodeRespVO::getId)
|
||||||
|
.doesNotContainAnyElementsOf(page2.getList().stream().map(EpisodeRespVO::getId).toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_return_empty_list_with_correct_total_when_page_out_of_range() {
|
||||||
|
given(dramaEpisodeMapper.selectPage(any(Page.class), any())).willAnswer(invocation -> {
|
||||||
|
Page<DramaEpisode> page = invocation.getArgument(0);
|
||||||
|
Page<DramaEpisode> result = new Page<>(page.getCurrent(), page.getSize(), 5L);
|
||||||
|
result.setRecords(List.of());
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
PageResult<EpisodeRespVO> page = episodeService.pageByDrama(DRAMA_ID, "published", 99, 100);
|
||||||
|
|
||||||
|
assertThat(page.getTotal()).isEqualTo(5L);
|
||||||
|
assertThat(page.getList()).isEmpty();
|
||||||
|
verify(dramaEpisodeMapper).selectPage(any(Page.class), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private void stubSelectPage(long total, List<DramaEpisode> records) {
|
||||||
|
given(dramaEpisodeMapper.selectPage(any(Page.class), any())).willAnswer(invocation -> {
|
||||||
|
Page<DramaEpisode> page = invocation.getArgument(0);
|
||||||
|
Page<DramaEpisode> result = new Page<>(page.getCurrent(), page.getSize(), total);
|
||||||
|
result.setRecords(records);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private Page<?> capturePageArg() {
|
||||||
|
ArgumentCaptor<Page> pageCaptor = ArgumentCaptor.forClass(Page.class);
|
||||||
|
verify(dramaEpisodeMapper).selectPage(pageCaptor.capture(), any());
|
||||||
|
return pageCaptor.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private DramaEpisode episode(Long id, int sort, int epNo) {
|
||||||
|
DramaEpisode episode = new DramaEpisode();
|
||||||
|
episode.setId(id);
|
||||||
|
episode.setDramaId(DRAMA_ID);
|
||||||
|
episode.setSort(sort);
|
||||||
|
episode.setEpNo(epNo);
|
||||||
|
episode.setTitle("ep-" + epNo);
|
||||||
|
episode.setStatus("published");
|
||||||
|
episode.setIsFree(1);
|
||||||
|
episode.setUnlockCoin(0);
|
||||||
|
episode.setDurationSec(60);
|
||||||
|
episode.setPlayCount(0L);
|
||||||
|
episode.setLikeCount(0L);
|
||||||
|
return episode;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user