feat: 07 创作者中心批 3(分片上传挂载 + 分集收费与试看)
为什么:创作者传完片子得把产物挂到分集上才能播,而这条链路之前有两个洞:
uploadId 是"凭证",谁拿到都能往别人的会话里塞分片 / 合并;产物挂载也没有归属校验,
拿到别人的 assetId 就能把自己的剧指向别人的素材。定价这边则缺创作者入口与试看秒数配置。
- 上传复用现有 /admin/media/upload/{init,chunk,complete},不重写;新增 GET /admin/media/upload/{uploadId}
做断点续传回显(receivedChunks/totalChunks/percent/status,完成后带 url)
- 上传会话归属校验:chunk / complete / 进度查询都要求会话发起人,别人拿到 uploadId 也操作不了(403)
- 新增 attach 接口:产物必须① 是本次登录的人上传的(media_asset.create_by)② bizType=EPISODE 且状态 ready/uploaded
③ 分集属于自己这部剧且作品可编辑;挂载时把产物时长补到分集上
- 收费配置复用 PriceService:新增创作者侧 GET/PUT /admin/creator-center/dramas/{id}/prices;
PriceSaveReqVO/PriceRespVO 补 previewSeconds(落在已有的 drama_episode.preview_seconds,不新造一套)
- 定价沿用"可编辑状态"口径:draft/offline 可改,已上线要先申请下线;dramaId 以路径为准,别人的 episodeId 被忽略
- 权限点 creator-center:upload / creator-center:price:update;创作者角色同时获得 cover:upload / episode:upload(复用上传接口)
实测:未登录请求付费集返回 {"accessType":"COIN_REQUIRED","unlockCoin":50,"previewSeconds":15},
免费集直接拿到签名播放地址 —— 创作者改的价格确实走到了 C 端付费判定。
This commit is contained in:
1 parent
255bcbf02b
commit
8b8ebf944f
12 files changed
+269
-10
No files matched your search
@@ -27,5 +27,10 @@
|
||||
<groupId>com.fruitshorts</groupId>
|
||||
<artifactId>fruitshorts-module-user</artifactId>
|
||||
</dependency>
|
||||
<!-- 创作者把上传产物挂到自己的分集,要读媒资库;media 只依赖 framework,方向不会成环 -->
|
||||
<dependency>
|
||||
<groupId>com.fruitshorts</groupId>
|
||||
<artifactId>fruitshorts-module-media</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
+68
@@ -12,10 +12,16 @@ import com.fruitshorts.content.service.EpisodeService;
|
||||
import com.fruitshorts.content.vo.DramaSaveReqVO;
|
||||
import com.fruitshorts.content.vo.EpisodeRespVO;
|
||||
import com.fruitshorts.content.vo.EpisodeSaveReqVO;
|
||||
import com.fruitshorts.content.vo.PriceRespVO;
|
||||
import com.fruitshorts.content.vo.PriceSaveReqVO;
|
||||
import com.fruitshorts.content.service.PriceService;
|
||||
import com.fruitshorts.creator.dal.entity.ContentSubmission;
|
||||
import com.fruitshorts.creator.dal.entity.Creator;
|
||||
import com.fruitshorts.creator.vo.CreatorDramaEditReqVO;
|
||||
import com.fruitshorts.creator.vo.CreatorEpisodeReqVO;
|
||||
import com.fruitshorts.framework.security.SecurityUtils;
|
||||
import com.fruitshorts.media.dal.entity.MediaAsset;
|
||||
import com.fruitshorts.media.dal.mapper.MediaAssetMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -38,7 +44,9 @@ public class CreatorContentService {
|
||||
private final ContentSubmissionService submissionService;
|
||||
private final DramaService dramaService;
|
||||
private final EpisodeService episodeService;
|
||||
private final PriceService priceService;
|
||||
private final DramaEpisodeMapper episodeMapper;
|
||||
private final MediaAssetMapper mediaAssetMapper;
|
||||
|
||||
/** 新建草稿:归属写自己,状态固定 draft(是否上线由后续提交审核决定)。 */
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@@ -155,6 +163,66 @@ public class CreatorContentService {
|
||||
return submissionService.listByDrama(creator.getId(), dramaId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 把上传好的媒资挂到分集上(视频源)。
|
||||
*
|
||||
* 三道闸:① 分集必须属于自己这部剧(requireMyDrama + requireMyEpisode);
|
||||
* ② 作品得是可编辑状态;③ **产物必须是这次登录的人自己上传的** ——
|
||||
* 否则拿到别人的 assetId 就能把别人的素材挂到自己的剧上(或反过来污染别人的剧)。
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void attachAsset(Long dramaId, Long episodeId, Long assetId) {
|
||||
Drama drama = centerService.requireMyDrama(dramaId);
|
||||
requireEditable(drama);
|
||||
DramaEpisode episode = requireMyEpisode(dramaId, episodeId);
|
||||
|
||||
MediaAsset asset = assetId == null ? null : mediaAssetMapper.selectById(assetId);
|
||||
if (asset == null) {
|
||||
throw new ServiceException(ErrorCode.DATA_NOT_FOUND, "媒资不存在");
|
||||
}
|
||||
long owner = asset.getCreateBy() == null ? 0L : asset.getCreateBy();
|
||||
long current = SecurityUtils.getUserId() == null ? -1L : SecurityUtils.getUserId();
|
||||
if (owner != current) {
|
||||
throw new ServiceException(ErrorCode.FORBIDDEN, "无权使用该上传产物");
|
||||
}
|
||||
if (!"EPISODE".equals(asset.getBizType())) {
|
||||
throw new ServiceException(ErrorCode.BAD_REQUEST,
|
||||
"只能把视频产物(bizType=EPISODE)挂到分集,当前是 " + asset.getBizType());
|
||||
}
|
||||
String assetStatus = asset.getStatus() == null ? "" : asset.getStatus();
|
||||
if (!"ready".equals(assetStatus) && !"uploaded".equals(assetStatus)) {
|
||||
throw new ServiceException(ErrorCode.BAD_REQUEST, "产物尚未就绪,当前状态:" + assetStatus);
|
||||
}
|
||||
|
||||
episode.setVideoAssetId(asset.getId());
|
||||
episode.setVideoUrl(asset.getMp4Url() == null ? "" : asset.getMp4Url());
|
||||
if ((episode.getDurationSec() == null || episode.getDurationSec() == 0)
|
||||
&& asset.getDurationSec() != null) {
|
||||
episode.setDurationSec(asset.getDurationSec());
|
||||
}
|
||||
episodeMapper.updateById(episode);
|
||||
}
|
||||
|
||||
public PriceRespVO priceDetail(Long dramaId) {
|
||||
centerService.requireMyDrama(dramaId);
|
||||
return priceService.detail(dramaId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存分集收费与试看秒数:复用后台同一套 PriceService(改完立即影响播放/下载的付费判定)。
|
||||
*
|
||||
* 两点约束:dramaId 以路径为准(body 里传别人的 id 无效);作品必须处于可编辑状态 ——
|
||||
* 让创作者改已上线内容的定价风险太大,要改价先申请下线。这条口径写进 docs/api-phase1.md,
|
||||
* 需要放开的话改一行就行。
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public PriceRespVO savePrice(Long dramaId, PriceSaveReqVO req) {
|
||||
Drama drama = centerService.requireMyDrama(dramaId);
|
||||
requireEditable(drama);
|
||||
req.setDramaId(drama.getId());
|
||||
return priceService.save(req);
|
||||
}
|
||||
|
||||
/** 分集归属:分集必须属于这部剧,否则 403(防止拿自己的剧 id 配别人的分集 id)。 */
|
||||
private DramaEpisode requireMyEpisode(Long dramaId, Long episodeId) {
|
||||
DramaEpisode episode = episodeId == null ? null : episodeMapper.selectById(episodeId);
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.fruitshorts.creator.vo;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/** 创作者中心 · 把已上传的媒资挂到自己的分集上。 */
|
||||
@Data
|
||||
public class CreatorAttachReqVO {
|
||||
|
||||
@NotNull(message = "缺少媒资 ID")
|
||||
private Long assetId;
|
||||
}
|
||||
Reference in new issue
Block a user