feat: 07 创作者中心批 2(内容编辑 / 分集 / 提交审核)

为什么:批 1 只给了创作者"看"的能力(工作台、审核进度、通知),内容还是得后台代建。
批 2 补上"改":创作者自己建草稿、编分集、提交审核,后台只管审。落地过程中发现三个必须一起修的问题:

1. 【会卡死创作者】后台驳回提交时不动短剧状态,剧会永远停在 reviewing,
   而批 2 里 reviewing 是只读 —— 创作者既改不了也提不了。现在驳回与撤回都会把剧退回 draft。
2. 【上线了却放不出来】C 端只播 status=published 的分集,但没有任何地方在剧上线时把分集带上线。
   审核通过 PUBLISH 时,仍处于草稿的分集一并上线。
3. 【编辑会误删字段】底层 apply() 对 null 会写空串/默认值:只想改封面的一次调用会把标题、时长、
   排序一起清掉(自测时两集排序都变成 1 才发现)。creator 侧改成"未传字段沿用现值"。

- 新增 CreatorContentService + /admin/creator-center 的 10 个写接口与 2 个读接口,
  一律不接收 creatorId,归属走 requireMyDrama,改别人的作品/分集统一 403
- 状态闸:只有 draft / offline 可直接编辑;reviewing / approved / published 只读并给出下一步动作
- 提交矩阵:draft→CREATE(首次)/UPDATE/PUBLISH,offline→UPDATE/PUBLISH,reviewing/approved→PUBLISH,published→OFFLINE;
  同剧同时只允许一条在审提交;撤回后作品退回草稿,支持撤回重提
- 权限点 creator-center:drama:edit(创作者角色 + 超管),docs/api-phase1.md 2.10 补批 2 接口与规则表
- 分集收费与试看秒数本批不碰(批 3 的收费配置页负责),新增分集默认免费,避免误定价
This commit is contained in:
Vicky committed 2026-10-15 13:58:45 +08:00
1 parent ea875c3e2b
commit 255bcbf02b
11 files changed
+616 -5

No files matched your search

@@ -0,0 +1,121 @@
package com.fruitshorts.admin.controller;
import com.fruitshorts.common.pojo.Result;
import com.fruitshorts.content.vo.EpisodeRespVO;
import com.fruitshorts.content.vo.EpisodeSortReqVO;
import com.fruitshorts.creator.dal.entity.ContentSubmission;
import com.fruitshorts.creator.service.CreatorContentService;
import com.fruitshorts.creator.vo.CreatorDramaEditReqVO;
import com.fruitshorts.creator.vo.CreatorEpisodeReqVO;
import com.fruitshorts.creator.vo.CreatorSubmitReqVO;
import com.fruitshorts.framework.log.OperationLog;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 创作者中心 · 内容编辑(剧信息 / 分集 / 提交审核 / 撤回)。
*
* 与 {@link CreatorCenterController} 一样**不接收 creatorId**:归属由登录态解析,
* 服务层强制 `where creator_id = 当前创作者`,改别人的剧或分集一律 403。
*/
@RestController
@RequestMapping("/admin/creator-center")
@RequiredArgsConstructor
public class CreatorContentController {
private final CreatorContentService creatorContentService;
/** 新建草稿。 */
@PostMapping("/dramas")
@PreAuthorize("@ss.hasPermission('creator-center:drama:edit')")
@OperationLog(module = "创作者中心", action = "新建作品")
public Result<Long> createDrama(@Valid @RequestBody CreatorDramaEditReqVO req) {
return Result.ok(creatorContentService.createDrama(req));
}
/** 编辑作品信息(仅草稿 / 已下线可改)。 */
@PutMapping("/dramas/{id}")
@PreAuthorize("@ss.hasPermission('creator-center:drama:edit')")
@OperationLog(module = "创作者中心", action = "编辑作品")
public Result<Void> updateDrama(@PathVariable Long id, @Valid @RequestBody CreatorDramaEditReqVO req) {
creatorContentService.updateDrama(id, req);
return Result.ok();
}
/** 分集列表。 */
@GetMapping("/dramas/{id}/episodes")
@PreAuthorize("@ss.hasPermission('creator-center:view')")
public Result<List<EpisodeRespVO>> listEpisodes(@PathVariable Long id) {
return Result.ok(creatorContentService.listEpisodes(id));
}
/** 新增分集(集号不传自动排到最后)。 */
@PostMapping("/dramas/{id}/episodes")
@PreAuthorize("@ss.hasPermission('creator-center:drama:edit')")
@OperationLog(module = "创作者中心", action = "新增分集")
public Result<Long> addEpisode(@PathVariable Long id, @RequestBody CreatorEpisodeReqVO req) {
return Result.ok(creatorContentService.addEpisode(id, req));
}
/** 编辑分集(时长 / 封面 / 片源 / 排序)。 */
@PutMapping("/dramas/{id}/episodes/{episodeId}")
@PreAuthorize("@ss.hasPermission('creator-center:drama:edit')")
@OperationLog(module = "创作者中心", action = "编辑分集")
public Result<Void> updateEpisode(@PathVariable Long id, @PathVariable Long episodeId,
@RequestBody CreatorEpisodeReqVO req) {
creatorContentService.updateEpisode(id, episodeId, req);
return Result.ok();
}
/** 删除分集。 */
@DeleteMapping("/dramas/{id}/episodes/{episodeId}")
@PreAuthorize("@ss.hasPermission('creator-center:drama:edit')")
@OperationLog(module = "创作者中心", action = "删除分集")
public Result<Void> deleteEpisode(@PathVariable Long id, @PathVariable Long episodeId) {
creatorContentService.deleteEpisode(id, episodeId);
return Result.ok();
}
/** 批量排序:body 里按新顺序给出分集 id。 */
@PutMapping("/dramas/{id}/episodes/sort")
@PreAuthorize("@ss.hasPermission('creator-center:drama:edit')")
@OperationLog(module = "创作者中心", action = "调整分集顺序")
public Result<Void> sortEpisodes(@PathVariable Long id, @Valid @RequestBody EpisodeSortReqVO req) {
creatorContentService.sortEpisodes(id, req.getIds());
return Result.ok();
}
/** 提交审核:CREATE / UPDATE / PUBLISH / OFFLINE。 */
@PostMapping("/dramas/{id}/submit")
@PreAuthorize("@ss.hasPermission('creator-center:drama:edit')")
@OperationLog(module = "创作者中心", action = "提交审核")
public Result<ContentSubmission> submit(@PathVariable Long id, @Valid @RequestBody CreatorSubmitReqVO req) {
return Result.ok(creatorContentService.submit(id, req.getType()));
}
/** 撤回自己的提交(撤回后作品退回草稿,可继续编辑)。 */
@PostMapping("/submissions/{id}/withdraw")
@PreAuthorize("@ss.hasPermission('creator-center:drama:edit')")
@OperationLog(module = "创作者中心", action = "撤回提交")
public Result<ContentSubmission> withdraw(@PathVariable Long id) {
return Result.ok(creatorContentService.withdraw(id));
}
/** 我的提交历史(审核进度)。 */
@GetMapping("/dramas/{id}/submissions")
@PreAuthorize("@ss.hasPermission('creator-center:view')")
public Result<List<ContentSubmission>> listSubmissions(@PathVariable Long id) {
return Result.ok(creatorContentService.listSubmissions(id));
}
}