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:
1 parent
7e730fbbca
commit
1fb8427f28
25 files changed
+935
-3
No files matched your search
+15
-2
@@ -276,7 +276,20 @@ POST /app/auth/register
|
||||
| POST | `/admin/wallets/coins` | `wallet:update` | 人工补币/扣回,必须带幂等键,先校验用户存在 |
|
||||
| POST | `/admin/reconcile/run` · GET `/admin/reconcile/page` | `reconcile:run` / `order:list` | 手动对账与历史(默认每天 04:20 自动跑前一天) |
|
||||
|
||||
### 2.7 创作者审核、数据看板(下一批)
|
||||
### 2.7 创作者与内容审核(已实现)
|
||||
|
||||
| 方法 | 路径 | 权限点 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| GET | `/admin/creators/page` `/admin/creators/{id}` `/{id}/documents` | `creator:list` | 创作者检索、详情、资质文件 |
|
||||
| PUT | `/admin/creators/{id}/audit` | `creator:audit` | 入驻审核:`APPROVE` / `REJECT`(必填原因+原因码)/ `FREEZE` |
|
||||
| GET | `/admin/submissions/page` `/admin/submissions/{id}` | `submission:list` | 内容提交检索与详情 |
|
||||
| PUT | `/admin/submissions/{id}/audit` | `submission:audit` | 审核通过后按 `type` 落到短剧状态机(CREATE/UPDATE 留档、PUBLISH 上线、OFFLINE 下线) |
|
||||
| GET · PUT | `/admin/prices` | `price:update` | 分集价格:整剧 `freeEpisodes`/`unlockCoin` + 逐集覆盖;改价立即影响播放与下载判定 |
|
||||
|
||||
**定时发布**:`DramaPublishJob` 每分钟扫一次,把 `publish_at` 到点的 `approved` 短剧推上线,
|
||||
走的是短剧状态机(不直接改状态字段)。上下架也可通过内容提交的 `OFFLINE` 审核触发。
|
||||
|
||||
### 2.8 数据看板(下一批)
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
| --- | --- | --- |
|
||||
@@ -309,6 +322,6 @@ POST /app/auth/register
|
||||
| 05 记录、收藏与下载 | 已建 | 已冻结 | **已实现**:进度、观看流水、收藏、离线下载授权与任务 |
|
||||
| 06 评论、点赞与分享 | 已建 | 已冻结 | **已实现**:评论(游标翻页/审核/删除)、点赞、举报、敏感词;分享埋点在下一批 |
|
||||
| 07 创作者中心 | 已建 | 已冻结 | 下一批(转码任务与产物记账已实现) |
|
||||
| 08 平台运营后台 | 已建 | 已冻结 | 用户管理、内容治理、**订单与权益已实现**;创作者审核与数据看板下一批 |
|
||||
| 08 平台运营后台 | 已建 | 已冻结 | 用户管理、内容治理、订单与权益、**创作者与内容审核已实现**;数据看板下一批 |
|
||||
| 09 分集收费与支付 | 已建 | 已冻结 | 下一批(权益判定端口已预留,接上即生效) |
|
||||
| 10 质量、数据与上线 | 已建 | 已冻结 | 压测与埋点 下一批 |
|
||||
@@ -43,6 +43,10 @@
|
||||
<groupId>com.fruitshorts</groupId>
|
||||
<artifactId>fruitshorts-module-commerce</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fruitshorts</groupId>
|
||||
<artifactId>fruitshorts-module-creator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
|
||||
+56
@@ -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()));
|
||||
}
|
||||
}
|
||||
+38
@@ -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));
|
||||
}
|
||||
}
|
||||
+46
@@ -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()));
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.fruitshorts.content.service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/** 定时发布:把已到 publish_at 的待发布短剧推上线。 */
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class DramaPublishJob {
|
||||
|
||||
private final DramaService dramaService;
|
||||
|
||||
@Scheduled(cron = "${fruitshorts.content.publish-cron:0 * * * * *}")
|
||||
public void run() {
|
||||
int published = dramaService.publishDue();
|
||||
if (published > 0) {
|
||||
log.info("定时发布:本轮上线 {} 部短剧", published);
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -143,6 +143,24 @@ public class DramaService {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 定时发布:把已到 publish_at 的待发布短剧推上线。
|
||||
* 状态机不允许把 approved 直接写库,所以逐条走 PUBLISH 动作,顺带留下审核记录。
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int publishDue() {
|
||||
List<Drama> due = dramaMapper.selectList(Wrappers.<Drama>lambdaQuery()
|
||||
.eq(Drama::getStatus, ContentStatus.APPROVED.getCode())
|
||||
.isNotNull(Drama::getPublishAt)
|
||||
.le(Drama::getPublishAt, LocalDateTime.now()));
|
||||
int published = 0;
|
||||
for (Drama drama : due) {
|
||||
changeStatus(drama.getId(), "PUBLISH", "定时发布到点", 0L, "system");
|
||||
published++;
|
||||
}
|
||||
return published;
|
||||
}
|
||||
|
||||
public List<DramaReviewRecord> listReviewRecords(Long dramaId) {
|
||||
return dramaReviewRecordMapper.selectList(Wrappers.<DramaReviewRecord>lambdaQuery()
|
||||
.eq(DramaReviewRecord::getBizType, "DRAMA")
|
||||
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
package com.fruitshorts.content.service;
|
||||
|
||||
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.mapper.DramaEpisodeMapper;
|
||||
import com.fruitshorts.content.dal.mapper.DramaMapper;
|
||||
import com.fruitshorts.content.vo.PriceRespVO;
|
||||
import com.fruitshorts.content.vo.PriceSaveReqVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 分集价格与免费集配置。
|
||||
*
|
||||
* <p>价格直接落在 {@code drama_episode.is_free / unlock_coin}(未单独指定时回落到 {@code drama.unlock_coin}),
|
||||
* 播放与下载的付费判定读的就是这两个字段,因此改价**立即生效**;已购用户的权益在 {@code entitlement},
|
||||
* 与价格解耦,改价不会回收或重算已购权益。</p>
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class PriceService {
|
||||
|
||||
private final DramaService dramaService;
|
||||
private final DramaMapper dramaMapper;
|
||||
private final DramaEpisodeMapper dramaEpisodeMapper;
|
||||
|
||||
/** 读取当前价格配置:后台定价页进入时先看现状再改。 */
|
||||
public PriceRespVO detail(Long dramaId) {
|
||||
Drama drama = dramaService.getExisting(dramaId);
|
||||
List<DramaEpisode> episodes = dramaEpisodeMapper.selectList(Wrappers.<DramaEpisode>lambdaQuery()
|
||||
.eq(DramaEpisode::getDramaId, drama.getId())
|
||||
.orderByAsc(DramaEpisode::getEpNo));
|
||||
PriceRespVO vo = new PriceRespVO();
|
||||
vo.setDramaId(drama.getId());
|
||||
vo.setFreeEpisodes(drama.getFreeEpisodes());
|
||||
vo.setUnlockCoin(drama.getUnlockCoin());
|
||||
vo.setEpisodeCount(episodes.size());
|
||||
vo.setChangedCount(0);
|
||||
vo.setEpisodes(episodes.stream().map(episode -> {
|
||||
PriceRespVO.DramaEpisodePriceVO item = new PriceRespVO.DramaEpisodePriceVO();
|
||||
item.setEpisodeId(episode.getId());
|
||||
item.setNumber(episode.getEpNo());
|
||||
item.setTitle(episode.getTitle());
|
||||
item.setFree(episode.getIsFree() != null && episode.getIsFree() == 1);
|
||||
item.setUnlockCoin(episode.getUnlockCoin());
|
||||
return item;
|
||||
}).toList());
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public PriceRespVO save(PriceSaveReqVO req) {
|
||||
Drama drama = dramaService.getExisting(req.getDramaId());
|
||||
int freeEpisodes = req.getFreeEpisodes() != null
|
||||
? Math.max(0, req.getFreeEpisodes())
|
||||
: (drama.getFreeEpisodes() == null ? 0 : drama.getFreeEpisodes());
|
||||
int unlockCoin = req.getUnlockCoin() != null
|
||||
? Math.max(0, req.getUnlockCoin())
|
||||
: (drama.getUnlockCoin() == null ? 0 : drama.getUnlockCoin());
|
||||
|
||||
drama.setFreeEpisodes(freeEpisodes);
|
||||
drama.setUnlockCoin(unlockCoin);
|
||||
dramaMapper.updateById(drama);
|
||||
|
||||
List<DramaEpisode> episodes = dramaEpisodeMapper.selectList(Wrappers.<DramaEpisode>lambdaQuery()
|
||||
.eq(DramaEpisode::getDramaId, drama.getId())
|
||||
.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());
|
||||
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 (!Objects.equals(episode.getIsFree(), isFree) || !Objects.equals(episode.getUnlockCoin(), coin)) {
|
||||
episode.setIsFree(isFree);
|
||||
episode.setUnlockCoin(coin);
|
||||
dramaEpisodeMapper.updateById(episode);
|
||||
changed++;
|
||||
}
|
||||
}
|
||||
|
||||
PriceRespVO vo = new PriceRespVO();
|
||||
vo.setDramaId(drama.getId());
|
||||
vo.setFreeEpisodes(freeEpisodes);
|
||||
vo.setUnlockCoin(unlockCoin);
|
||||
vo.setEpisodeCount(episodes.size());
|
||||
vo.setChangedCount(changed);
|
||||
vo.setEpisodes(episodes.stream().map(episode -> {
|
||||
PriceRespVO.DramaEpisodePriceVO item = new PriceRespVO.DramaEpisodePriceVO();
|
||||
item.setEpisodeId(episode.getId());
|
||||
item.setNumber(episode.getEpNo());
|
||||
item.setTitle(episode.getTitle());
|
||||
item.setFree(episode.getIsFree() != null && episode.getIsFree() == 1);
|
||||
item.setUnlockCoin(episode.getUnlockCoin());
|
||||
return item;
|
||||
}).toList());
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.fruitshorts.content.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** 价格配置结果:便于运营确认改动范围。 */
|
||||
@Data
|
||||
public class PriceRespVO {
|
||||
|
||||
private Long dramaId;
|
||||
private Integer freeEpisodes;
|
||||
private Integer unlockCoin;
|
||||
/** 该剧剧集总数 */
|
||||
private Integer episodeCount;
|
||||
/** 本次实际发生变化的剧集数 */
|
||||
private Integer changedCount;
|
||||
/** 剧集明细:后台定价页要按集展示当前价格与免费状态 */
|
||||
private List<DramaEpisodePriceVO> episodes;
|
||||
|
||||
@Data
|
||||
public static class DramaEpisodePriceVO {
|
||||
private Long episodeId;
|
||||
private Integer number;
|
||||
private String title;
|
||||
private Boolean free;
|
||||
private Integer unlockCoin;
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.fruitshorts.content.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** 分集价格与免费集配置。 */
|
||||
@Data
|
||||
public class PriceSaveReqVO {
|
||||
|
||||
private Long dramaId;
|
||||
/** 整剧免费集数:前 N 集免费,其余按 unlockCoin 收费 */
|
||||
private Integer freeEpisodes;
|
||||
/** 整剧默认解锁金币(逐集未单独指定时生效) */
|
||||
private Integer unlockCoin;
|
||||
/** 逐集覆盖,可空;列表里的集优先于整剧默认值 */
|
||||
private List<EpisodePrice> episodes;
|
||||
|
||||
@Data
|
||||
public static class EpisodePrice {
|
||||
private Long episodeId;
|
||||
/** 1 免费 / 0 收费 */
|
||||
private Integer isFree;
|
||||
private Integer unlockCoin;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.fruitshorts</groupId>
|
||||
<artifactId>fruitshorts</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>fruitshorts-module-creator</artifactId>
|
||||
<name>fruitshorts-module-creator</name>
|
||||
<description>创作者域:入驻资质审核、内容提交审核流转</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.fruitshorts</groupId>
|
||||
<artifactId>fruitshorts-framework</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fruitshorts</groupId>
|
||||
<artifactId>fruitshorts-module-content</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fruitshorts</groupId>
|
||||
<artifactId>fruitshorts-module-user</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package com.fruitshorts.creator.dal.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/** 创作者内容提交与审核流转,对应 content_submission 表。 */
|
||||
@Data
|
||||
@TableName("content_submission")
|
||||
public class ContentSubmission {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
private Long creatorId;
|
||||
private Long dramaId;
|
||||
/** CREATE / UPDATE / PUBLISH / OFFLINE */
|
||||
private String type;
|
||||
/** DRAFT / SUBMITTED / REVIEWING / APPROVED / REJECTED / WITHDRAWN */
|
||||
private String status;
|
||||
private Integer version;
|
||||
private LocalDateTime submitAt;
|
||||
private Long reviewerId;
|
||||
private LocalDateTime reviewAt;
|
||||
private String reviewNote;
|
||||
private String rejectCode;
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@TableField(updateStrategy = FieldStrategy.NEVER)
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package com.fruitshorts.creator.dal.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/** 创作者主体与资质,对应 creator 表。 */
|
||||
@Data
|
||||
@TableName("creator")
|
||||
public class Creator {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
/** 关联 C 端账号 app_user.id */
|
||||
private Long userId;
|
||||
private String code;
|
||||
private String name;
|
||||
/** INDIVIDUAL / COMPANY */
|
||||
private String type;
|
||||
private String contactName;
|
||||
private String contactMobile;
|
||||
private String contactEmail;
|
||||
private String countryCode;
|
||||
/** pending / approved / rejected / frozen */
|
||||
private String status;
|
||||
private String auditNote;
|
||||
private Long auditBy;
|
||||
private LocalDateTime auditAt;
|
||||
private String settlementCurrency;
|
||||
/** 预留:加密存储(第二阶段结算) */
|
||||
private String bankAccount;
|
||||
/** 预留:税号 */
|
||||
private String taxNo;
|
||||
/** 预留:KYC 状态 */
|
||||
private String kycStatus;
|
||||
/** 预留:分成比例(第二阶段) */
|
||||
private BigDecimal shareRate;
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@TableField(updateStrategy = FieldStrategy.NEVER)
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.fruitshorts.creator.dal.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/** 创作者资质文件,对应 creator_document 表。 */
|
||||
@Data
|
||||
@TableName("creator_document")
|
||||
public class CreatorDocument {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
private Long creatorId;
|
||||
/** ID_CARD / BUSINESS_LICENSE / CONTRACT */
|
||||
private String type;
|
||||
private String fileUrl;
|
||||
/** pending / approved / rejected */
|
||||
private String status;
|
||||
private String auditNote;
|
||||
/** 证件有效期 */
|
||||
private LocalDateTime expireAt;
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@TableField(updateStrategy = FieldStrategy.NEVER)
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package com.fruitshorts.creator.dal.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.fruitshorts.creator.dal.entity.ContentSubmission;
|
||||
|
||||
public interface ContentSubmissionMapper extends BaseMapper<ContentSubmission> {
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package com.fruitshorts.creator.dal.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.fruitshorts.creator.dal.entity.CreatorDocument;
|
||||
|
||||
public interface CreatorDocumentMapper extends BaseMapper<CreatorDocument> {
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package com.fruitshorts.creator.dal.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.fruitshorts.creator.dal.entity.Creator;
|
||||
|
||||
public interface CreatorMapper extends BaseMapper<Creator> {
|
||||
}
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
package com.fruitshorts.creator.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
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.ServiceException;
|
||||
import com.fruitshorts.common.pojo.PageResult;
|
||||
import com.fruitshorts.content.dal.entity.Drama;
|
||||
import com.fruitshorts.content.enums.ContentStatus;
|
||||
import com.fruitshorts.content.service.DramaService;
|
||||
import com.fruitshorts.creator.dal.entity.ContentSubmission;
|
||||
import com.fruitshorts.creator.dal.mapper.ContentSubmissionMapper;
|
||||
import com.fruitshorts.creator.vo.SubmissionAuditReqVO;
|
||||
import com.fruitshorts.creator.vo.SubmissionQueryReqVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Set;
|
||||
|
||||
/** 创作者内容提交:后台检索、审核,通过后按 type 落到短剧状态机。 */
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ContentSubmissionService {
|
||||
|
||||
public static final String STATUS_DRAFT = "DRAFT";
|
||||
public static final String STATUS_SUBMITTED = "SUBMITTED";
|
||||
public static final String STATUS_REVIEWING = "REVIEWING";
|
||||
public static final String STATUS_APPROVED = "APPROVED";
|
||||
public static final String STATUS_REJECTED = "REJECTED";
|
||||
public static final String STATUS_WITHDRAWN = "WITHDRAWN";
|
||||
|
||||
private static final Set<String> REVIEWABLE = Set.of(STATUS_SUBMITTED, STATUS_REVIEWING);
|
||||
|
||||
private final ContentSubmissionMapper submissionMapper;
|
||||
private final DramaService dramaService;
|
||||
|
||||
public PageResult<ContentSubmission> page(SubmissionQueryReqVO req) {
|
||||
Page<ContentSubmission> page = new Page<>(req.getSafePageIndex(), req.getSafePageSize());
|
||||
IPage<ContentSubmission> result = submissionMapper.selectPage(page,
|
||||
Wrappers.<ContentSubmission>lambdaQuery()
|
||||
.eq(req.getCreatorId() != null, ContentSubmission::getCreatorId, req.getCreatorId())
|
||||
.eq(req.getDramaId() != null, ContentSubmission::getDramaId, req.getDramaId())
|
||||
.eq(StringUtils.hasText(req.getStatus()), ContentSubmission::getStatus, req.getStatus())
|
||||
.eq(StringUtils.hasText(req.getType()), ContentSubmission::getType, req.getType())
|
||||
.orderByDesc(ContentSubmission::getId));
|
||||
return new PageResult<>(result.getTotal(), result.getRecords());
|
||||
}
|
||||
|
||||
public ContentSubmission getExisting(Long id) {
|
||||
ContentSubmission submission = id == null ? null : submissionMapper.selectById(id);
|
||||
if (submission == null) {
|
||||
throw new ServiceException(ErrorCode.DATA_NOT_FOUND, "内容提交记录不存在");
|
||||
}
|
||||
return submission;
|
||||
}
|
||||
|
||||
/** 审核:通过 / 驳回 / 撤回。 */
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ContentSubmission audit(Long id, SubmissionAuditReqVO req, Long operatorId, String operatorName) {
|
||||
ContentSubmission submission = getExisting(id);
|
||||
String current = submission.getStatus();
|
||||
String action = req.getAction() == null ? "" : req.getAction();
|
||||
|
||||
switch (action) {
|
||||
case "APPROVE" -> {
|
||||
requireState(current, "通过", REVIEWABLE);
|
||||
submission.setStatus(STATUS_APPROVED);
|
||||
applyToDrama(submission, operatorId, operatorName);
|
||||
}
|
||||
case "REJECT" -> {
|
||||
requireState(current, "驳回", REVIEWABLE);
|
||||
if (!StringUtils.hasText(req.getReason())) {
|
||||
throw new ServiceException(ErrorCode.BAD_REQUEST, "驳回必须填写原因");
|
||||
}
|
||||
submission.setStatus(STATUS_REJECTED);
|
||||
}
|
||||
case "WITHDRAW" -> {
|
||||
requireState(current, "撤回", Set.of(STATUS_DRAFT, STATUS_SUBMITTED));
|
||||
submission.setStatus(STATUS_WITHDRAWN);
|
||||
}
|
||||
default -> throw new ServiceException(ErrorCode.BAD_REQUEST, "未知的审核动作: " + action);
|
||||
}
|
||||
|
||||
submission.setReviewerId(operatorId == null ? 0L : operatorId);
|
||||
submission.setReviewAt(LocalDateTime.now());
|
||||
submission.setReviewNote(req.getReason() == null ? "" : req.getReason());
|
||||
submission.setRejectCode(req.getRejectCode() == null ? "" : req.getRejectCode());
|
||||
submissionMapper.updateById(submission);
|
||||
return submission;
|
||||
}
|
||||
|
||||
/**
|
||||
* 审核通过后落到短剧状态机:PUBLISH 上线、OFFLINE 下线,CREATE / UPDATE 只留档不改状态。
|
||||
* 上线时若短剧还停在草稿,按 SUBMIT → APPROVE → PUBLISH 顺推,保证状态机不被绕过。
|
||||
*/
|
||||
private void applyToDrama(ContentSubmission submission, Long operatorId, String operatorName) {
|
||||
String type = submission.getType() == null ? "" : submission.getType();
|
||||
Long dramaId = submission.getDramaId();
|
||||
String reason = "创作者内容审核通过";
|
||||
if ("PUBLISH".equals(type)) {
|
||||
Drama drama = dramaService.getExisting(dramaId);
|
||||
String status = drama.getStatus();
|
||||
if (ContentStatus.PUBLISHED.getCode().equals(status)) {
|
||||
return;
|
||||
}
|
||||
if (ContentStatus.DRAFT.getCode().equals(status)) {
|
||||
dramaService.changeStatus(dramaId, "SUBMIT", reason, operatorId, operatorName);
|
||||
}
|
||||
Drama afterSubmit = dramaService.getExisting(dramaId);
|
||||
if (ContentStatus.REVIEWING.getCode().equals(afterSubmit.getStatus())) {
|
||||
dramaService.changeStatus(dramaId, "APPROVE", reason, operatorId, operatorName);
|
||||
}
|
||||
dramaService.changeStatus(dramaId, "PUBLISH", reason, operatorId, operatorName);
|
||||
} else if ("OFFLINE".equals(type)) {
|
||||
Drama drama = dramaService.getExisting(dramaId);
|
||||
if (ContentStatus.PUBLISHED.getCode().equals(drama.getStatus())) {
|
||||
dramaService.changeStatus(dramaId, "OFFLINE", reason, operatorId, operatorName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void requireState(String current, String action, Set<String> allowed) {
|
||||
if (!allowed.contains(current)) {
|
||||
throw new ServiceException(ErrorCode.OPERATION_NOT_ALLOWED,
|
||||
"当前状态(" + statusText(current) + ")不允许执行「" + action + "」");
|
||||
}
|
||||
}
|
||||
|
||||
public static String statusText(String code) {
|
||||
return switch (code == null ? "" : code) {
|
||||
case STATUS_DRAFT -> "草稿";
|
||||
case STATUS_SUBMITTED -> "已提交";
|
||||
case STATUS_REVIEWING -> "审核中";
|
||||
case STATUS_APPROVED -> "已通过";
|
||||
case STATUS_REJECTED -> "已驳回";
|
||||
case STATUS_WITHDRAWN -> "已撤回";
|
||||
default -> code;
|
||||
};
|
||||
}
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
package com.fruitshorts.creator.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
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.ServiceException;
|
||||
import com.fruitshorts.common.pojo.PageResult;
|
||||
import com.fruitshorts.creator.dal.entity.Creator;
|
||||
import com.fruitshorts.creator.dal.entity.CreatorDocument;
|
||||
import com.fruitshorts.creator.dal.mapper.CreatorDocumentMapper;
|
||||
import com.fruitshorts.creator.dal.mapper.CreatorMapper;
|
||||
import com.fruitshorts.creator.vo.CreatorAuditReqVO;
|
||||
import com.fruitshorts.creator.vo.CreatorQueryReqVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/** 创作者主体与资质:后台检索、详情、入驻审核。 */
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CreatorService {
|
||||
|
||||
public static final String STATUS_PENDING = "pending";
|
||||
public static final String STATUS_APPROVED = "approved";
|
||||
public static final String STATUS_REJECTED = "rejected";
|
||||
public static final String STATUS_FROZEN = "frozen";
|
||||
|
||||
private final CreatorMapper creatorMapper;
|
||||
private final CreatorDocumentMapper creatorDocumentMapper;
|
||||
|
||||
public PageResult<Creator> page(CreatorQueryReqVO req) {
|
||||
Page<Creator> page = new Page<>(req.getSafePageIndex(), req.getSafePageSize());
|
||||
IPage<Creator> result = creatorMapper.selectPage(page, Wrappers.<Creator>lambdaQuery()
|
||||
.and(StringUtils.hasText(req.getKeyword()), w -> w
|
||||
.like(Creator::getName, req.getKeyword())
|
||||
.or().like(Creator::getCode, req.getKeyword())
|
||||
.or().like(Creator::getContactName, req.getKeyword())
|
||||
.or().like(Creator::getContactMobile, req.getKeyword()))
|
||||
.eq(StringUtils.hasText(req.getStatus()), Creator::getStatus, req.getStatus())
|
||||
.eq(StringUtils.hasText(req.getType()), Creator::getType, req.getType())
|
||||
.eq(StringUtils.hasText(req.getCountryCode()), Creator::getCountryCode, req.getCountryCode())
|
||||
.orderByDesc(Creator::getId));
|
||||
return new PageResult<>(result.getTotal(), result.getRecords());
|
||||
}
|
||||
|
||||
public Creator getExisting(Long id) {
|
||||
Creator creator = id == null ? null : creatorMapper.selectById(id);
|
||||
if (creator == null) {
|
||||
throw new ServiceException(ErrorCode.DATA_NOT_FOUND, "创作者不存在");
|
||||
}
|
||||
return creator;
|
||||
}
|
||||
|
||||
/** 资质文件列表:审核时要能看到证件原件。 */
|
||||
public List<CreatorDocument> documents(Long creatorId) {
|
||||
getExisting(creatorId);
|
||||
return creatorDocumentMapper.selectList(Wrappers.<CreatorDocument>lambdaQuery()
|
||||
.eq(CreatorDocument::getCreatorId, creatorId)
|
||||
.orderByAsc(CreatorDocument::getType)
|
||||
.orderByDesc(CreatorDocument::getId));
|
||||
}
|
||||
|
||||
/** 入驻审核:通过 / 驳回 / 冻结。 */
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Creator audit(Long id, CreatorAuditReqVO req, Long operatorId) {
|
||||
Creator creator = getExisting(id);
|
||||
String current = creator.getStatus();
|
||||
String action = req.getAction() == null ? "" : req.getAction();
|
||||
String target = switch (action) {
|
||||
case "APPROVE" -> {
|
||||
requireState(current, "通过", Set.of(STATUS_PENDING, STATUS_REJECTED, STATUS_FROZEN));
|
||||
yield STATUS_APPROVED;
|
||||
}
|
||||
case "REJECT" -> {
|
||||
requireState(current, "驳回", Set.of(STATUS_PENDING));
|
||||
if (!StringUtils.hasText(req.getReason())) {
|
||||
throw new ServiceException(ErrorCode.BAD_REQUEST, "驳回必须填写原因");
|
||||
}
|
||||
yield STATUS_REJECTED;
|
||||
}
|
||||
case "FREEZE" -> {
|
||||
requireState(current, "冻结", Set.of(STATUS_APPROVED));
|
||||
yield STATUS_FROZEN;
|
||||
}
|
||||
default -> throw new ServiceException(ErrorCode.BAD_REQUEST, "未知的审核动作: " + action);
|
||||
};
|
||||
|
||||
creator.setStatus(target);
|
||||
creator.setAuditNote(buildNote(req));
|
||||
creator.setAuditBy(operatorId == null ? 0L : operatorId);
|
||||
creator.setAuditAt(LocalDateTime.now());
|
||||
creatorMapper.updateById(creator);
|
||||
return creator;
|
||||
}
|
||||
|
||||
/** 驳回原因码没有独立列,与说明合并存进 audit_note,保留可追溯性。 */
|
||||
private String buildNote(CreatorAuditReqVO req) {
|
||||
String reason = req.getReason() == null ? "" : req.getReason();
|
||||
return StringUtils.hasText(req.getRejectCode())
|
||||
? "[" + req.getRejectCode() + "] " + reason
|
||||
: reason;
|
||||
}
|
||||
|
||||
private void requireState(String current, String action, Set<String> allowed) {
|
||||
if (!allowed.contains(current)) {
|
||||
throw new ServiceException(ErrorCode.OPERATION_NOT_ALLOWED,
|
||||
"当前状态(" + statusText(current) + ")不允许执行「" + action + "」");
|
||||
}
|
||||
}
|
||||
|
||||
public static String statusText(String code) {
|
||||
return switch (code == null ? "" : code) {
|
||||
case STATUS_PENDING -> "待审核";
|
||||
case STATUS_APPROVED -> "已通过";
|
||||
case STATUS_REJECTED -> "已驳回";
|
||||
case STATUS_FROZEN -> "已冻结";
|
||||
default -> code;
|
||||
};
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.fruitshorts.creator.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/** 创作者入驻审核请求。 */
|
||||
@Data
|
||||
public class CreatorAuditReqVO {
|
||||
|
||||
/** APPROVE / REJECT / FREEZE */
|
||||
private String action;
|
||||
/** 驳回原因码,REJECT 时必填 */
|
||||
private String rejectCode;
|
||||
/** 审核说明 / 驳回原因 */
|
||||
private String reason;
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.fruitshorts.creator.vo;
|
||||
|
||||
import com.fruitshorts.common.pojo.PageParam;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/** 后台创作者检索条件。 */
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class CreatorQueryReqVO extends PageParam {
|
||||
|
||||
/** 名称 / 编号 / 联系人 / 联系方式 关键字 */
|
||||
private String keyword;
|
||||
/** pending / approved / rejected / frozen */
|
||||
private String status;
|
||||
/** INDIVIDUAL / COMPANY */
|
||||
private String type;
|
||||
private String countryCode;
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.fruitshorts.creator.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/** 内容提交审核请求。 */
|
||||
@Data
|
||||
public class SubmissionAuditReqVO {
|
||||
|
||||
/** APPROVE / REJECT / WITHDRAW */
|
||||
private String action;
|
||||
/** 驳回原因码,REJECT 时必填 */
|
||||
private String rejectCode;
|
||||
/** 审核说明 / 驳回原因 */
|
||||
private String reason;
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package com.fruitshorts.creator.vo;
|
||||
|
||||
import com.fruitshorts.common.pojo.PageParam;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/** 后台内容提交审核检索条件。 */
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class SubmissionQueryReqVO extends PageParam {
|
||||
|
||||
private Long creatorId;
|
||||
private Long dramaId;
|
||||
/** DRAFT / SUBMITTED / REVIEWING / APPROVED / REJECTED / WITHDRAWN */
|
||||
private String status;
|
||||
/** CREATE / UPDATE / PUBLISH / OFFLINE */
|
||||
private String type;
|
||||
}
|
||||
@@ -28,6 +28,7 @@
|
||||
<module>fruitshorts-module-playback</module>
|
||||
<module>fruitshorts-module-interaction</module>
|
||||
<module>fruitshorts-module-commerce</module>
|
||||
<module>fruitshorts-module-creator</module>
|
||||
<module>fruitshorts-admin-api</module>
|
||||
<module>fruitshorts-app-api</module>
|
||||
</modules>
|
||||
@@ -90,6 +91,11 @@
|
||||
<artifactId>fruitshorts-module-commerce</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fruitshorts</groupId>
|
||||
<artifactId>fruitshorts-module-creator</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
|
||||
+31
-1
@@ -55,6 +55,14 @@ INSERT INTO sys_permission (code, name, group_name, type, sort) VALUES
|
||||
('reconcile:run', '手动对账', '订单与权益', 'BUTTON', 126)
|
||||
ON CONFLICT (code) DO NOTHING;
|
||||
|
||||
INSERT INTO sys_permission (code, name, group_name, type, sort) VALUES
|
||||
('creator:list', '查看创作者', '创作者管理', 'BUTTON', 131),
|
||||
('creator:audit', '创作者入驻审核','创作者管理', 'BUTTON', 132),
|
||||
('submission:list', '查看内容提交', '创作者管理', 'BUTTON', 133),
|
||||
('submission:audit', '内容提交审核', '创作者管理', 'BUTTON', 134),
|
||||
('price:update', '配置分集价格', '内容管理', 'BUTTON', 135)
|
||||
ON CONFLICT (code) DO NOTHING;
|
||||
|
||||
-- 超管默认拥有全部权限点
|
||||
INSERT INTO sys_role_permission (role_id, permission_id)
|
||||
SELECT r.id, p.id
|
||||
@@ -65,7 +73,29 @@ WHERE r.code = 'super_admin'
|
||||
'comment:list', 'comment:audit', 'report:list', 'report:handle',
|
||||
'sensitive-word:list', 'sensitive-word:update',
|
||||
'order:list', 'order:update', 'entitlement:list', 'entitlement:update',
|
||||
'wallet:update', 'reconcile:run')
|
||||
'wallet:update', 'reconcile:run',
|
||||
'creator:list', 'creator:audit', 'submission:list', 'submission:audit',
|
||||
'price:update')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- ---------- 菜单:创作者管理 ----------
|
||||
-- component 指向 src/views/creator/ 下的页面,前端按这个字符串动态加载。
|
||||
INSERT INTO sys_menu (id, parent_id, title, route_path, route_name, component, redirect, icon,
|
||||
permission_code, type, always_show, affix, sort) VALUES
|
||||
(17, 0, '创作者管理', '/creator', 'CreatorCenter', '#', '/creator/creators', 'ep:user-filled', 'creator:list', 'CATALOG', 1, 0, 5),
|
||||
(18, 17, '创作者入驻', 'creators', 'creators', 'views/creator/CreatorAuditView', '', '', 'creator:list', 'MENU', 0, 0, 1),
|
||||
(19, 17, '内容审核', 'submissions', 'submissions', 'views/creator/SubmissionAuditView', '', '', 'submission:list', 'MENU', 0, 0, 2)
|
||||
ON CONFLICT (id) DO UPDATE SET parent_id = EXCLUDED.parent_id, title = EXCLUDED.title,
|
||||
route_path = EXCLUDED.route_path, route_name = EXCLUDED.route_name, component = EXCLUDED.component,
|
||||
redirect = EXCLUDED.redirect, icon = EXCLUDED.icon, permission_code = EXCLUDED.permission_code,
|
||||
type = EXCLUDED.type, always_show = EXCLUDED.always_show, affix = EXCLUDED.affix, sort = EXCLUDED.sort;
|
||||
|
||||
INSERT INTO sys_role_menu (role_id, menu_id)
|
||||
SELECT r.id, m.id
|
||||
FROM sys_role r
|
||||
CROSS JOIN sys_menu m
|
||||
WHERE r.code = 'super_admin'
|
||||
AND m.id IN (17, 18, 19)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- 第一阶段新增的白名单数据(当前阶段都是免费内容,先把演示商品占位好)
|
||||
|
||||
Reference in new issue
Block a user