diff --git a/fruitshorts-app-api/src/main/java/com/fruitshorts/app/controller/AppCreatorController.java b/fruitshorts-app-api/src/main/java/com/fruitshorts/app/controller/AppCreatorController.java index 644cfff..f784ece 100644 --- a/fruitshorts-app-api/src/main/java/com/fruitshorts/app/controller/AppCreatorController.java +++ b/fruitshorts-app-api/src/main/java/com/fruitshorts/app/controller/AppCreatorController.java @@ -3,34 +3,25 @@ package com.fruitshorts.app.controller; import com.fruitshorts.app.service.AppConfigService; -import com.fruitshorts.app.service.CreatorStatsService; -import com.fruitshorts.common.pojo.PageResult; import com.fruitshorts.common.pojo.Result; -import com.fruitshorts.creator.dal.entity.Creator; import com.fruitshorts.creator.service.CreatorAppService; import com.fruitshorts.creator.vo.CreatorApplyReqVO; import com.fruitshorts.creator.vo.CreatorApplyRespVO; -import com.fruitshorts.creator.vo.CreatorCenterDramaQueryReqVO; -import com.fruitshorts.creator.vo.CreatorCenterDramaVO; import com.fruitshorts.framework.security.SecurityUtils; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import java.util.Map; - /** - * C 端创作者:入驻申请、我的作品、数据看板。 + * C 端创作者:**只有入驻申请一条**。 * - *

归属一律取**当前登录的 C 端账号**(`creator.user_id`),接口**不接收 creatorId**; - * 非生效创作者(未申请 / 审核中 / 被拒 / 冻结)返回 `200007`,**不返回空数组冒充成功**。 + *

口径(2026-10-16 变更):**创作者中心(工作台 / 作品管理 / 数据看板)由 Web 端承载**(后端契约 §2.10), + * App 客户端不再提供 `works` / `stats`。归属一律取当前登录的 C 端账号,接口**不接收 creatorId**。 */ @Tag(name = "客户端-创作者") @RestController @@ -39,7 +30,6 @@ import java.util.Map; public class AppCreatorController { private final CreatorAppService creatorAppService; - private final CreatorStatsService creatorStatsService; private final AppConfigService appConfigService; /** 入驻申请:幂等,重复提交返回当前状态(不重复建档)。 */ @@ -49,28 +39,4 @@ public class AppCreatorController { return Result.ok(creatorAppService.apply(SecurityUtils.getAppUserId(), req, appConfigService.agreementVersion())); } - - /** 我的作品与审核状态:非生效创作者返回 200007。 */ - @Operation(summary = "我的作品与审核状态(非生效创作者返回 200007)") - @GetMapping("/works") - public Result> works( - @RequestParam(defaultValue = "1") Integer pageIndex, - @RequestParam(defaultValue = "10") Integer pageSize, - @RequestParam(required = false) String keyword, - @RequestParam(required = false) String status) { - CreatorCenterDramaQueryReqVO query = new CreatorCenterDramaQueryReqVO(); - query.setPageIndex(pageIndex); - query.setPageSize(pageSize); - query.setKeyword(keyword); - query.setStatus(status); - return Result.ok(creatorAppService.myWorks(SecurityUtils.getAppUserId(), query)); - } - - /** 创作者数据看板:与后台 07 看板同口径,只读、仅本人作品。 */ - @Operation(summary = "创作者数据看板(与后台同口径,只读)") - @GetMapping("/stats") - public Result> stats() { - Creator creator = creatorAppService.requireActiveCreator(SecurityUtils.getAppUserId()); - return Result.ok(creatorStatsService.overview(creator)); - } } diff --git a/fruitshorts-app-api/src/main/java/com/fruitshorts/app/service/CreatorStatsService.java b/fruitshorts-app-api/src/main/java/com/fruitshorts/app/service/CreatorStatsService.java deleted file mode 100644 index 45227ee..0000000 --- a/fruitshorts-app-api/src/main/java/com/fruitshorts/app/service/CreatorStatsService.java +++ /dev/null @@ -1,213 +0,0 @@ -// Author: fruit.com.cn - -package com.fruitshorts.app.service; - -import com.baomidou.mybatisplus.core.toolkit.Wrappers; -import com.fruitshorts.commerce.dal.entity.CoinLedger; -import com.fruitshorts.commerce.dal.entity.Entitlement; -import com.fruitshorts.commerce.dal.mapper.CoinLedgerMapper; -import com.fruitshorts.commerce.dal.mapper.EntitlementMapper; -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.enums.ContentStatus; -import com.fruitshorts.creator.dal.entity.Creator; -import com.fruitshorts.interaction.dal.entity.ContentComment; -import com.fruitshorts.interaction.dal.mapper.ContentCommentMapper; -import com.fruitshorts.playback.dal.entity.UserFavorite; -import com.fruitshorts.playback.dal.entity.WatchHistory; -import com.fruitshorts.playback.dal.entity.WatchProgress; -import com.fruitshorts.playback.dal.mapper.UserFavoriteMapper; -import com.fruitshorts.playback.dal.mapper.WatchHistoryMapper; -import com.fruitshorts.playback.dal.mapper.WatchProgressMapper; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; - -import java.time.LocalDateTime; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -/** - * C 端创作者数据看板(`GET /app/creator/stats`,只读)。 - * - *

口径必须与后台 07 创作者中心看板一致(`admin-api` 的 `CreatorDashboardService`): - * 字段名、指标定义、`null` + `notes` 语义逐条对齐。两份实现在指标定义上完全相同, - * 差别只有"创作者从哪来"(C 端按 `creator.user_id`,后台按登录绑定)。 - * - *

之所以在 app-api 再实现一份而不是共用:`CreatorDashboardService` 物理上在 admin-api, - * 而 app-api 不能依赖 admin-api;把指标实现搬到共享模块需要给冻结模块加 commerce/interaction/playback 依赖, - * 属更大的结构改动。**因此这里刻意保持"逻辑同样、注释同样"**, - * 并已登记为待收敛项(见 `DEPLOY-CHECKLIST.md` 风险小节与 E1 回执)。 - * - *

指标口径(与后台一致,前端 tooltip 可直接复用): - *

- */ -@Service -@RequiredArgsConstructor -public class CreatorStatsService { - - private static final int RECENT_DAYS = 7; - - private final DramaMapper dramaMapper; - private final DramaEpisodeMapper dramaEpisodeMapper; - private final WatchProgressMapper watchProgressMapper; - private final WatchHistoryMapper watchHistoryMapper; - private final UserFavoriteMapper userFavoriteMapper; - private final ContentCommentMapper commentMapper; - private final EntitlementMapper entitlementMapper; - private final CoinLedgerMapper coinLedgerMapper; - - /** 看板数据;调用方先做"是否生效创作者"的校验。 */ - public Map overview(Creator creator) { - LocalDateTime now = LocalDateTime.now(); - LocalDateTime sevenDaysAgo = now.minusDays(RECENT_DAYS); - - List dramas = dramaMapper.selectList(Wrappers.lambdaQuery() - .eq(Drama::getCreatorId, creator.getId()) - .orderByDesc(Drama::getId)); - List dramaIds = dramas.stream().map(Drama::getId).toList(); - List episodes = dramaIds.isEmpty() ? List.of() - : dramaEpisodeMapper.selectList(Wrappers.lambdaQuery() - .in(DramaEpisode::getDramaId, dramaIds) - .orderByAsc(DramaEpisode::getDramaId) - .orderByAsc(DramaEpisode::getEpNo)); - List episodeIds = episodes.stream().map(DramaEpisode::getId).toList(); - - Map data = new LinkedHashMap<>(); - data.put("generatedAt", now); - data.put("drama", dramaMetrics(dramas, episodes)); - data.put("playback", playbackMetrics(dramas, dramaIds, sevenDaysAgo)); - data.put("interaction", interactionMetrics(dramaIds)); - data.put("revenue", revenueMetrics(dramaIds, episodeIds, sevenDaysAgo)); - // 按天趋势没有数据源:creator_stat_daily 表已建但无产出,如实给 null - data.put("daily", null); - data.put("notes", notes()); - return data; - } - - private Map dramaMetrics(List dramas, List episodes) { - Map drama = new LinkedHashMap<>(); - drama.put("total", dramas.size()); - drama.put("draft", countStatus(dramas, ContentStatus.DRAFT)); - drama.put("reviewing", countStatus(dramas, ContentStatus.REVIEWING)); - drama.put("approved", countStatus(dramas, ContentStatus.APPROVED)); - drama.put("published", countStatus(dramas, ContentStatus.PUBLISHED)); - drama.put("offline", countStatus(dramas, ContentStatus.OFFLINE)); - drama.put("episodeTotal", episodes.size()); - drama.put("episodeWithMedia", episodes.stream() - .filter(episode -> StringUtils.hasText(episode.getVideoUrl())) - .count()); - return drama; - } - - private Map playbackMetrics(List dramas, List dramaIds, - LocalDateTime sevenDaysAgo) { - Map playback = new LinkedHashMap<>(); - playback.put("viewTotal", dramas.stream() - .mapToLong(drama -> drama.getViewCount() == null ? 0L : drama.getViewCount()).sum()); - playback.put("playTotal", dramas.stream() - .mapToLong(drama -> drama.getPlayCount() == null ? 0L : drama.getPlayCount()).sum()); - - long watched = dramaIds.isEmpty() ? 0L - : orZero(watchProgressMapper.selectCount(Wrappers.lambdaQuery() - .in(WatchProgress::getDramaId, dramaIds))); - long finished = dramaIds.isEmpty() ? 0L - : orZero(watchProgressMapper.selectCount(Wrappers.lambdaQuery() - .in(WatchProgress::getDramaId, dramaIds) - .eq(WatchProgress::getFinished, 1))); - playback.put("watchedDramas", watched); - playback.put("finishedDramas", finished); - playback.put("finishRate", watched == 0 ? 0d : Math.round(finished * 1000.0 / watched) / 1000.0); - - playback.put("playEvents7d", dramaIds.isEmpty() ? 0L - : orZero(watchHistoryMapper.selectCount(Wrappers.lambdaQuery() - .in(WatchHistory::getDramaId, dramaIds) - .ge(WatchHistory::getWatchedAt, sevenDaysAgo)))); - playback.put("playEventsTotal", dramaIds.isEmpty() ? 0L - : orZero(watchHistoryMapper.selectCount(Wrappers.lambdaQuery() - .in(WatchHistory::getDramaId, dramaIds)))); - playback.put("finishEvents", dramaIds.isEmpty() ? 0L - : orZero(watchHistoryMapper.selectCount(Wrappers.lambdaQuery() - .in(WatchHistory::getDramaId, dramaIds) - .gt(WatchHistory::getDurationMs, 0L) - .apply("position_ms >= duration_ms")))); - return playback; - } - - private Map interactionMetrics(List dramaIds) { - Map interaction = new LinkedHashMap<>(); - interaction.put("favoriteTotal", dramaIds.isEmpty() ? 0L - : orZero(userFavoriteMapper.selectCount(Wrappers.lambdaQuery() - .in(UserFavorite::getDramaId, dramaIds)))); - interaction.put("commentTotal", dramaIds.isEmpty() ? 0L - : orZero(commentMapper.selectCount(Wrappers.lambdaQuery() - .in(ContentComment::getDramaId, dramaIds)))); - interaction.put("commentPending", dramaIds.isEmpty() ? 0L - : orZero(commentMapper.selectCount(Wrappers.lambdaQuery() - .in(ContentComment::getDramaId, dramaIds) - .eq(ContentComment::getStatus, "pending")))); - return interaction; - } - - private Map revenueMetrics(List dramaIds, List episodeIds, - LocalDateTime sevenDaysAgo) { - Map revenue = new LinkedHashMap<>(); - revenue.put("unlockCount", dramaIds.isEmpty() ? 0L - : orZero(entitlementMapper.selectCount(Wrappers.lambdaQuery() - .in(Entitlement::getDramaId, dramaIds) - .eq(Entitlement::getSource, "ORDER")))); - - List episodeIdText = episodeIds.stream().map(String::valueOf).toList(); - revenue.put("coinIncome", episodeIdText.isEmpty() ? 0L : sumUnlockCoin(episodeIdText, null)); - revenue.put("coinIncome7d", episodeIdText.isEmpty() - ? 0L : sumUnlockCoin(episodeIdText, sevenDaysAgo)); - // 金币解锁不产生法币订单;真实收入金额要等 Apple/Google 商店接入,这里如实给 null - revenue.put("paidOrderAmount", null); - return revenue; - } - - /** 解锁扣币记在 coin_ledger:biz_type=UNLOCK、biz_id=剧集 id、金额为负,取绝对值即收入。 */ - private long sumUnlockCoin(List episodeIdText, LocalDateTime since) { - List ledger = coinLedgerMapper.selectList(Wrappers.lambdaQuery() - .eq(CoinLedger::getBizType, "UNLOCK") - .in(CoinLedger::getBizId, episodeIdText) - .ge(since != null, CoinLedger::getCreatedAt, since)); - return ledger.stream() - .mapToLong(item -> item.getChangeAmount() == null ? 0L : -item.getChangeAmount()) - .sum(); - } - - private long countStatus(List dramas, ContentStatus status) { - return dramas.stream().filter(drama -> status.getCode().equals(drama.getStatus())).count(); - } - - private long orZero(Long value) { - return value == null ? 0L : value; - } - - /** notes 文案与后台看板逐条一致(口径说明,前端 tooltip 复用)。 */ - private List notes() { - return List.of( - "viewTotal / playTotal 是短剧表上的累计计数(不限时间),与 playEvents7d / playEventsTotal 的流水口径不可相加", - "watchedDramas / finishedDramas 取自 watch_progress —— 该表唯一索引是 (user_id, drama_id)," - + "每用户每剧只有一条续看进度,所以口径是「用户 × 剧」不是按集;finishRate = 后者 / 前者", - "finishEvents 是 watch_history 中 position_ms >= duration_ms 的完播事件数(流水口径,与上面两种口径维度不同)", - "coinIncome 取 coin_ledger 中 biz_type=UNLOCK 且命中本人分集的扣币合计;paidOrderAmount 需接入 Apple/Google 商店才有数据,当前为 null", - "daily(按天趋势)暂无数据源:creator_stat_daily 表已建但没有产出任务,需要按天聚合 watch_history / entitlement 的定时任务,当前阶段未接入", - "统计范围仅限本人作品(drama.creator_id = 当前创作者),不含他人数据" - ); - } -} diff --git a/fruitshorts-app-api/src/test/java/com/fruitshorts/app/controller/AppCreatorAndConfigContractTest.java b/fruitshorts-app-api/src/test/java/com/fruitshorts/app/controller/AppCreatorAndConfigContractTest.java index b8f0188..1c90550 100644 --- a/fruitshorts-app-api/src/test/java/com/fruitshorts/app/controller/AppCreatorAndConfigContractTest.java +++ b/fruitshorts-app-api/src/test/java/com/fruitshorts/app/controller/AppCreatorAndConfigContractTest.java @@ -4,7 +4,6 @@ package com.fruitshorts.app.controller; import com.fruitshorts.app.service.AppConfigService; import com.fruitshorts.app.service.AppNoticeService; -import com.fruitshorts.app.service.CreatorStatsService; import com.fruitshorts.app.support.ControllerContractSupport; import com.fruitshorts.common.exception.ErrorCode; import com.fruitshorts.common.exception.ServiceException; @@ -55,7 +54,6 @@ class AppCreatorAndConfigContractTest { private AppConfigService appConfigService; private AppNoticeService appNoticeService; private CreatorAppService creatorAppService; - private CreatorStatsService creatorStatsService; private MockMvc configMvc; private MockMvc creatorMvc; @@ -64,11 +62,10 @@ class AppCreatorAndConfigContractTest { appConfigService = mock(AppConfigService.class); appNoticeService = mock(AppNoticeService.class); creatorAppService = mock(CreatorAppService.class); - creatorStatsService = mock(CreatorStatsService.class); configMvc = ControllerContractSupport.mockMvcOf( new AppConfigController(appConfigService, appNoticeService)); creatorMvc = ControllerContractSupport.mockMvcOf(new AppCreatorController( - creatorAppService, creatorStatsService, appConfigService)); + creatorAppService, appConfigService)); } @AfterEach @@ -155,67 +152,9 @@ class AppCreatorAndConfigContractTest { .andExpect(jsonPath("$.data.created").value(false)); } - @Test - void should_reject_when_viewing_works_without_active_creator() throws Exception { - login(); - given(creatorAppService.myWorks(eq(USER), any())) - .willThrow(new ServiceException(ErrorCode.CREATOR_NOT_ACTIVE, "尚未提交创作者入驻申请")); - creatorMvc.perform(get("/app/creator/works")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.code").value(ErrorCode.CREATOR_NOT_ACTIVE.getCode())) - .andExpect(jsonPath("$.message").value("尚未提交创作者入驻申请")); - } - @Test - void should_return_my_works_when_creator_is_active() throws Exception { - login(); - CreatorCenterDramaVO vo = new CreatorCenterDramaVO(); - vo.setId(5L); - vo.setTitle("命运的相遇"); - vo.setStatus("draft"); - given(creatorAppService.myWorks(eq(USER), any())).willReturn(new PageResult<>(1L, List.of(vo))); - creatorMvc.perform(get("/app/creator/works").param("pageIndex", "1").param("pageSize", "10")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.data.total").value(1)) - .andExpect(jsonPath("$.data.list[0].id").value(5)) - .andExpect(jsonPath("$.data.list[0].title").value("命运的相遇")); - } - - @Test - void should_reject_when_viewing_stats_without_active_creator() throws Exception { - login(); - given(creatorAppService.requireActiveCreator(USER)) - .willThrow(new ServiceException(ErrorCode.CREATOR_NOT_ACTIVE, "入驻申请审核中,请耐心等待")); - - creatorMvc.perform(get("/app/creator/stats")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.code").value(ErrorCode.CREATOR_NOT_ACTIVE.getCode())); - verify(creatorStatsService, never()).overview(any()); - } - - @Test - void should_return_dashboard_with_null_and_notes_when_stats_is_requested() throws Exception { - login(); - Creator creator = new Creator(); - creator.setId(7L); - creator.setStatus(CreatorService.STATUS_APPROVED); - given(creatorAppService.requireActiveCreator(USER)).willReturn(creator); - Map dashboard = new LinkedHashMap<>(); - dashboard.put("drama", Map.of("total", 2)); - dashboard.put("daily", null); - dashboard.put("notes", List.of("daily 暂无数据源")); - given(creatorStatsService.overview(creator)).willReturn(dashboard); - - creatorMvc.perform(get("/app/creator/stats")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.code").value(0)) - .andExpect(jsonPath("$.data.drama.total").value(2)) - // 取不到的指标是 null(不是 0),并在 notes 里说明 - .andExpect(jsonPath("$.data.daily").value(org.hamcrest.Matchers.nullValue())) - .andExpect(jsonPath("$.data.notes[0]").value("daily 暂无数据源")); - } private CreatorApplyRespVO applyResp(Long id, String status, boolean created) { CreatorApplyRespVO vo = new CreatorApplyRespVO(); diff --git a/fruitshorts-app-api/src/test/java/com/fruitshorts/app/service/CreatorAppServiceTest.java b/fruitshorts-app-api/src/test/java/com/fruitshorts/app/service/CreatorAppServiceTest.java index fafbfc1..4640f38 100644 --- a/fruitshorts-app-api/src/test/java/com/fruitshorts/app/service/CreatorAppServiceTest.java +++ b/fruitshorts-app-api/src/test/java/com/fruitshorts/app/service/CreatorAppServiceTest.java @@ -9,7 +9,6 @@ import com.fruitshorts.common.exception.ServiceException; import com.fruitshorts.creator.dal.entity.Creator; import com.fruitshorts.creator.dal.mapper.CreatorMapper; import com.fruitshorts.creator.service.CreatorAppService; -import com.fruitshorts.creator.service.CreatorCenterService; import com.fruitshorts.creator.service.CreatorService; import com.fruitshorts.creator.vo.CreatorApplyReqVO; import com.fruitshorts.creator.vo.CreatorApplyRespVO; @@ -52,7 +51,7 @@ class CreatorAppServiceTest { @BeforeEach void setUp() { creatorMapper = mock(CreatorMapper.class); - creatorAppService = new CreatorAppService(creatorMapper, mock(CreatorCenterService.class)); + creatorAppService = new CreatorAppService(creatorMapper); } @Test @@ -98,51 +97,9 @@ class CreatorAppServiceTest { assertThat(resp.getStatus()).isEqualTo(CreatorService.STATUS_PENDING); } - @Test - void should_reject_when_agreement_version_is_not_the_current_one() { - assertThatThrownBy(() -> creatorAppService.apply(USER, req("2026-09-01"), AGREEMENT)) - .isInstanceOf(ServiceException.class) - .satisfies(ex -> assertThat(((ServiceException) ex).getCode()) - .isEqualTo(ErrorCode.BAD_REQUEST.getCode())) - .hasMessageContaining("请先同意最新版协议"); - verify(creatorMapper, never()).insert(any(Creator.class)); - } - @Test - void should_return_creator_when_status_is_approved() { - given(creatorMapper.selectOne(any())).willReturn(creator(CreatorService.STATUS_APPROVED)); - Creator creator = creatorAppService.requireActiveCreator(USER); - assertThat(creator.getStatus()).isEqualTo(CreatorService.STATUS_APPROVED); - } - - @Test - void should_reject_with_creator_not_active_when_status_is_not_approved() { - for (String status : new String[]{CreatorService.STATUS_PENDING, CreatorService.STATUS_REJECTED, - CreatorService.STATUS_FROZEN}) { - Creator creator = creator(status); - creator.setAuditNote("资料不全"); - given(creatorMapper.selectOne(any())).willReturn(creator); - - assertThatThrownBy(() -> creatorAppService.requireActiveCreator(USER)) - .as("状态 %s 应被拒", status) - .isInstanceOf(ServiceException.class) - .satisfies(ex -> assertThat(((ServiceException) ex).getCode()) - .isEqualTo(ErrorCode.CREATOR_NOT_ACTIVE.getCode())); - } - } - - @Test - void should_reject_with_creator_not_active_when_user_never_applied() { - given(creatorMapper.selectOne(any())).willReturn(null); - - assertThatThrownBy(() -> creatorAppService.requireActiveCreator(USER)) - .isInstanceOf(ServiceException.class) - .satisfies(ex -> assertThat(((ServiceException) ex).getCode()) - .isEqualTo(ErrorCode.CREATOR_NOT_ACTIVE.getCode())) - .hasMessage("尚未提交创作者入驻申请"); - } private AtomicReference captureInsert() { AtomicReference saved = new AtomicReference<>(); @@ -155,6 +112,17 @@ class CreatorAppServiceTest { return saved; } + @Test + void should_reject_when_agreement_version_is_not_the_current_one() { + // 协议版本必须等于 /app/config 下发的当前版本(保留用例:apply 的协议校验) + assertThatThrownBy(() -> creatorAppService.apply(USER, req("2026-09-01"), AGREEMENT)) + .isInstanceOf(ServiceException.class) + .satisfies(ex -> assertThat(((ServiceException) ex).getCode()) + .isEqualTo(ErrorCode.BAD_REQUEST.getCode())) + .hasMessageContaining("请先同意最新版协议"); + verify(creatorMapper, never()).insert(any(Creator.class)); + } + private CreatorApplyReqVO req(String agreementVersion) { CreatorApplyReqVO req = new CreatorApplyReqVO(); req.setName("星野工作室"); diff --git a/fruitshorts-common/src/main/java/com/fruitshorts/common/exception/ErrorCode.java b/fruitshorts-common/src/main/java/com/fruitshorts/common/exception/ErrorCode.java index 0a30f80..b7fc80b 100644 --- a/fruitshorts-common/src/main/java/com/fruitshorts/common/exception/ErrorCode.java +++ b/fruitshorts-common/src/main/java/com/fruitshorts/common/exception/ErrorCode.java @@ -37,13 +37,7 @@ public enum ErrorCode { */ CREATOR_NOT_BOUND(200004, "当前账号未绑定创作者主体"), CREATOR_TARGET_REQUIRED(200005, "请先选择要代看的创作者"), - CREATOR_IMPERSONATE_READONLY(200006, "代看模式下只读,不能修改创作者数据"), - - /** - * C 端专用:当前登录账号「不是生效中的创作者」(未申请 / 审核中 / 被驳回 / 已冻结)。 - * 与后台的 {@link #CREATOR_NOT_BOUND} 区分开 —— 客户端要据此决定"引导去申请"还是"提示等待审核"。 - */ - CREATOR_NOT_ACTIVE(200007, "当前账号不是生效中的创作者"); + CREATOR_IMPERSONATE_READONLY(200006, "代看模式下只读,不能修改创作者数据"); private final int code; private final String message; diff --git a/fruitshorts-module-creator/src/main/java/com/fruitshorts/creator/service/CreatorAppService.java b/fruitshorts-module-creator/src/main/java/com/fruitshorts/creator/service/CreatorAppService.java index 49f504d..4b95a92 100644 --- a/fruitshorts-module-creator/src/main/java/com/fruitshorts/creator/service/CreatorAppService.java +++ b/fruitshorts-module-creator/src/main/java/com/fruitshorts/creator/service/CreatorAppService.java @@ -5,13 +5,10 @@ package com.fruitshorts.creator.service; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.fruitshorts.common.exception.ErrorCode; import com.fruitshorts.common.exception.ServiceException; -import com.fruitshorts.common.pojo.PageResult; import com.fruitshorts.creator.dal.entity.Creator; import com.fruitshorts.creator.dal.mapper.CreatorMapper; import com.fruitshorts.creator.vo.CreatorApplyReqVO; import com.fruitshorts.creator.vo.CreatorApplyRespVO; -import com.fruitshorts.creator.vo.CreatorCenterDramaQueryReqVO; -import com.fruitshorts.creator.vo.CreatorCenterDramaVO; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.dao.DuplicateKeyException; @@ -24,10 +21,13 @@ import java.time.LocalDate; import java.time.format.DateTimeFormatter; /** - * C 端创作者入驻与作品(`/app/creator/**`)。 + * C 端创作者入驻(`POST /app/creator/apply`)。 * - *

与管理端创作者中心的关键区别:**归属按 `creator.user_id`(C 端账号)解析**, - * 而管理端按 `creator.admin_user_id`(后台账号绑定)。两条链路互不影响, + *

口径(2026-10-16 变更):**创作者中心由 Web 端承载**,App 侧只保留入驻申请 —— + * 原来的"我的作品 / 数据看板"两条 C 端接口已按新契约撤除。 + * + *

与管理端的关键区别:**归属按 `creator.user_id`(C 端账号)解析**, + * 管理端按 `creator.admin_user_id`(后台账号绑定)。两条链路互不影响, * 这里**没有改动**管理端的入驻审核 / 开号 / 作品管理逻辑。 */ @Slf4j @@ -36,7 +36,6 @@ import java.time.format.DateTimeFormatter; public class CreatorAppService { private final CreatorMapper creatorMapper; - private final CreatorCenterService creatorCenterService; /** * 入驻申请(**幂等**)。 @@ -103,39 +102,6 @@ public class CreatorAppService { .last("limit 1")); } - /** - * 要求当前 C 端账号是**生效中的创作者**,否则按状态给出明确错误(`200007`)。 - * - *

刻意不返回空列表:客户端要能区分"没有作品"和"你还不是创作者"。 - */ - public Creator requireActiveCreator(Long userId) { - Creator creator = findByUserId(userId); - if (creator == null) { - throw new ServiceException(ErrorCode.CREATOR_NOT_ACTIVE, "尚未提交创作者入驻申请"); - } - String status = creator.getStatus() == null ? "" : creator.getStatus(); - switch (status) { - case CreatorService.STATUS_APPROVED -> { - return creator; - } - case CreatorService.STATUS_PENDING -> throw new ServiceException(ErrorCode.CREATOR_NOT_ACTIVE, - "入驻申请审核中,请耐心等待"); - case CreatorService.STATUS_REJECTED -> throw new ServiceException(ErrorCode.CREATOR_NOT_ACTIVE, - "入驻申请未通过:" + (StringUtils.hasText(creator.getAuditNote()) - ? creator.getAuditNote() : "可联系运营了解原因")); - case CreatorService.STATUS_FROZEN -> throw new ServiceException(ErrorCode.CREATOR_NOT_ACTIVE, - "创作者账号已冻结,请联系运营"); - default -> throw new ServiceException(ErrorCode.CREATOR_NOT_ACTIVE, - "当前创作者状态不可用:" + status); - } - } - - /** 我的作品(复用管理端创作者中心的查询与 VO 映射,口径一致)。 */ - public PageResult myWorks(Long userId, CreatorCenterDramaQueryReqVO req) { - Creator creator = requireActiveCreator(userId); - return creatorCenterService.pageMyDramasOf(creator, req); - } - private CreatorApplyRespVO toResp(Creator creator, boolean created) { CreatorApplyRespVO vo = new CreatorApplyRespVO(); vo.setId(creator.getId()); diff --git a/fruitshorts-module-creator/src/main/java/com/fruitshorts/creator/service/CreatorCenterService.java b/fruitshorts-module-creator/src/main/java/com/fruitshorts/creator/service/CreatorCenterService.java index 5278162..93a4c63 100644 --- a/fruitshorts-module-creator/src/main/java/com/fruitshorts/creator/service/CreatorCenterService.java +++ b/fruitshorts-module-creator/src/main/java/com/fruitshorts/creator/service/CreatorCenterService.java @@ -171,25 +171,6 @@ public class CreatorCenterService { return new PageResult<>(result.getTotal(), toDramaVOList(creator.getId(), result.getRecords())); } - /** - * 指定创作者的作品分页(**C 端用**)。 - * - *

与管理端 {@link #pageMyDramas} 共用同一套查询与 VO 映射,区别只在"创作者从哪来": - * 管理端由后台账号绑定关系解析,C 端由 `creator.user_id`(C 端账号)解析。 - * 之所以单独开一个方法而不是把 creatorId 做成参数往上传:**不接受调用方传归属**,避免横向越权入口。 - */ - public PageResult pageMyDramasOf(Creator creator, - CreatorCenterDramaQueryReqVO req) { - Page page = new Page<>(req.getSafePageIndex(), req.getSafePageSize()); - IPage result = dramaMapper.selectPage(page, Wrappers.lambdaQuery() - .eq(Drama::getCreatorId, creator.getId()) - .like(StringUtils.hasText(req.getKeyword()), Drama::getTitle, req.getKeyword()) - .eq(StringUtils.hasText(req.getStatus()), Drama::getStatus, req.getStatus()) - .orderByDesc(Drama::getUpdatedAt) - .orderByDesc(Drama::getId)); - return new PageResult<>(result.getTotal(), toDramaVOList(creator.getId(), result.getRecords())); - } - public CreatorCenterDramaVO myDramaDetail(Long dramaId) { Creator creator = requireCurrentCreator(); Drama drama = requireMyDrama(dramaId);