feat: 08 创作者与内容审核落地(入驻审核 / 内容提交审核 / 定时发布 / 分集定价)

为什么:
1) 内容是创作者供的,必须有一条可管控的供给链路:创作者入驻要审、内容提交要审、
   审核通过才能真正上架 —— 否则平台无法对内容和主体负责。
2) 审核不能绕过状态机。内容提交审核通过后,是顺着短剧状态机
   SUBMIT → APPROVE → PUBLISH 推上去,而不是直接写 status 字段,
   这样审核记录与操作日志都不会缺环。
3) 定时发布(publish_at 到点自动上线)同样走状态机,并且做成幂等任务:
   重复执行不会把已上线的剧再推一次。
4) 定价落在 is_free / unlock_coin 上,与权益表解耦:改价立即影响播放与下载判定,
   但已购用户的权益不受影响,不会因为改价把用户买过的东西收回去。
5) 补齐 PM 草稿未做的部分:5 个权限点 + 3 条后台菜单(含前端组件路径)+ 超管授权(幂等),
   以及后台定价页需要的价格读取接口 GET /admin/prices。
This commit is contained in:
Vicky committed 2026-10-15 12:51:40 +08:00
1 parent 7e730fbbca
commit 1fb8427f28
25 files changed
+935 -3

No files matched your search

@@ -0,0 +1,56 @@
package com.fruitshorts.admin.controller;
import com.fruitshorts.common.pojo.PageResult;
import com.fruitshorts.common.pojo.Result;
import com.fruitshorts.creator.dal.entity.Creator;
import com.fruitshorts.creator.dal.entity.CreatorDocument;
import com.fruitshorts.creator.service.CreatorService;
import com.fruitshorts.creator.vo.CreatorAuditReqVO;
import com.fruitshorts.creator.vo.CreatorQueryReqVO;
import com.fruitshorts.framework.log.OperationLog;
import com.fruitshorts.framework.security.SecurityUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
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;
/** 后台创作者:检索、详情、资质、入驻审核。 */
@RestController
@RequestMapping("/admin/creators")
@RequiredArgsConstructor
public class CreatorAdminController {
private final CreatorService creatorService;
@GetMapping("/page")
@PreAuthorize("@ss.hasPermission('creator:list')")
public Result<PageResult<Creator>> page(CreatorQueryReqVO req) {
return Result.ok(creatorService.page(req));
}
@GetMapping("/{id}")
@PreAuthorize("@ss.hasPermission('creator:list')")
public Result<Creator> detail(@PathVariable Long id) {
return Result.ok(creatorService.getExisting(id));
}
/** 资质文件:审核前要看证件原件。 */
@GetMapping("/{id}/documents")
@PreAuthorize("@ss.hasPermission('creator:list')")
public Result<List<CreatorDocument>> documents(@PathVariable Long id) {
return Result.ok(creatorService.documents(id));
}
@PutMapping("/{id}/audit")
@PreAuthorize("@ss.hasPermission('creator:audit')")
@OperationLog(module = "创作者管理", action = "入驻审核")
public Result<Creator> audit(@PathVariable Long id, @RequestBody CreatorAuditReqVO req) {
return Result.ok(creatorService.audit(id, req, SecurityUtils.getUserId()));
}
}
@@ -0,0 +1,38 @@
package com.fruitshorts.admin.controller;
import com.fruitshorts.common.pojo.Result;
import com.fruitshorts.content.service.PriceService;
import com.fruitshorts.content.vo.PriceRespVO;
import com.fruitshorts.content.vo.PriceSaveReqVO;
import com.fruitshorts.framework.log.OperationLog;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.GetMapping;
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;
/** 后台分集价格与免费集配置。 */
@RestController
@RequestMapping("/admin/prices")
@RequiredArgsConstructor
public class PriceAdminController {
private final PriceService priceService;
/** 当前价格配置:整剧默认 + 分集明细。 */
@GetMapping
@PreAuthorize("@ss.hasPermission('price:update')")
public Result<PriceRespVO> detail(@RequestParam Long dramaId) {
return Result.ok(priceService.detail(dramaId));
}
@PutMapping
@PreAuthorize("@ss.hasPermission('price:update')")
@OperationLog(module = "定价管理", action = "配置分集价格")
public Result<PriceRespVO> save(@RequestBody PriceSaveReqVO req) {
return Result.ok(priceService.save(req));
}
}
@@ -0,0 +1,46 @@
package com.fruitshorts.admin.controller;
import com.fruitshorts.common.pojo.PageResult;
import com.fruitshorts.common.pojo.Result;
import com.fruitshorts.creator.dal.entity.ContentSubmission;
import com.fruitshorts.creator.service.ContentSubmissionService;
import com.fruitshorts.creator.vo.SubmissionAuditReqVO;
import com.fruitshorts.creator.vo.SubmissionQueryReqVO;
import com.fruitshorts.framework.log.OperationLog;
import com.fruitshorts.framework.security.SecurityUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
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;
/** 后台内容提交审核:检索、详情、审核(通过 / 驳回 / 撤回)。 */
@RestController
@RequestMapping("/admin/submissions")
@RequiredArgsConstructor
public class SubmissionAdminController {
private final ContentSubmissionService submissionService;
@GetMapping("/page")
@PreAuthorize("@ss.hasPermission('submission:list')")
public Result<PageResult<ContentSubmission>> page(SubmissionQueryReqVO req) {
return Result.ok(submissionService.page(req));
}
@GetMapping("/{id}")
@PreAuthorize("@ss.hasPermission('submission:list')")
public Result<ContentSubmission> detail(@PathVariable Long id) {
return Result.ok(submissionService.getExisting(id));
}
@PutMapping("/{id}/audit")
@PreAuthorize("@ss.hasPermission('submission:audit')")
@OperationLog(module = "内容审核", action = "提交审核")
public Result<ContentSubmission> audit(@PathVariable Long id, @RequestBody SubmissionAuditReqVO req) {
return Result.ok(submissionService.audit(id, req, SecurityUtils.getUserId(), SecurityUtils.getUsername()));
}
}