feat: 07 创作者中心批 1(账号绑定 + 我的作品 / 审核进度 / 通知)
为什么:创作者要在 Web 端(做进后台管理)看到自己的作品与审核进度,但 creator 模块
此前只有「后台审核视角」的服务,创作者本人没有任何入口;更要紧的是,全项目没有任何
地方把「后台账号」和「创作者主体」关联起来 —— 缺这层映射,横向越权就只能靠每个接口
自觉加 where 过滤,迟早会漏。所以批 1 先把归属判定的地基打好,再给工作台接口。
- creator 增加 admin_user_id(幂等 ALTER + 部分唯一索引,0 = 未绑定,多条 0 不冲突)
- 新增角色 creator 与权限点 creator-center:view(创作者角色 + 超管各授权一条),菜单等页面做好再挂
- 新增 CreatorCenterService:requireCurrentCreator() 是归属判定的唯一入口,requireMyDrama() 供后续批次复用
- 新增 /admin/creator-center 四个接口(profile / dramas/page / dramas/{id} / notices),
一律不接收 creatorId,查询强制 where creator_id = 当前创作者,访问他人作品统一 403
- 通知不新建表:由入驻审核结果 + 内容提交审核结果派生(ops_notice 无产出方,等有公告功能再接)
- 顺手接通 drama.trailer_url:DDL 与实体早就有,但保存 VO / 响应 VO 都没带,预告片存不进也读不出
- ContentSubmissionService 补 typeText + TYPE_* 常量(通知文案与批 2 的提交都要用)
This commit is contained in:
1 parent
8b261b71de
commit
ea875c3e2b
14 files changed
+454
No files matched your search
@@ -354,6 +354,27 @@ POST /app/auth/register
|
||||
菜单、权限点与超管授权由 `sql/data-phase1.sql` 幂等落库(内容治理 / 订单与权益 / 数据看板挂菜单 id 20–25,创作者两条为 17–19)。
|
||||
页面只读态与可操作态按权限点区分:没有 `*:update` / `*:audit` 的角色进入页面时按钮隐藏或显示「只读」。
|
||||
|
||||
### 2.10 创作者中心(批 1 已实现:工作台)
|
||||
|
||||
创作者 Web 端做在后台管理里(`ReelShortWeb`),身份复用 `sys_admin_user`:
|
||||
`creator.admin_user_id` 绑定后台账号(部分唯一索引,0 = 未绑定),角色 `creator` 只拿 `creator-center:*` 权限点。
|
||||
|
||||
| 方法 | 路径 | 权限点 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| GET | `/admin/creator-center/profile` | `creator-center:view` | 当前创作者主体与入驻状态 |
|
||||
| GET | `/admin/creator-center/dramas/page` | `creator-center:view` | 我的作品(含最近一次提交的审核进度:类型 / 状态 / 驳回原因码 / 时间) |
|
||||
| GET | `/admin/creator-center/dramas/{id}` | `creator-center:view` | 我的作品详情 |
|
||||
| GET | `/admin/creator-center/notices` | `creator-center:view` | 我的通知:入驻审核结果 + 我提交内容的审核结果 |
|
||||
|
||||
**契约红线**:以上接口一律**不接收 `creatorId`**,归属由登录态(`sys_admin_user.id` → `creator.admin_user_id`)解析,
|
||||
查询强制带 `where creator_id = 当前创作者`;访问他人作品统一返回 `{"code":403,"message":"无权访问该作品"}`,
|
||||
未绑定创作者主体返回 `{"code":403,"message":"当前账号未绑定创作者主体,无法进入创作者中心"}`。
|
||||
|
||||
通知不新建表:第一阶段由 `creator`(入驻审核)与 `content_submission`(内容提交审核)派生;
|
||||
系统公告 `ops_notice` 目前没有产出方,等有公告功能再接。
|
||||
|
||||
**进度**:批 2(内容编辑与分集、提交审核)、批 3(分片上传、分集收费)、批 4(创作者数据看板)、批 5(前端页面)未开始。
|
||||
|
||||
## 3. 契约与实现状态对照
|
||||
|
||||
| 模块 | 表结构 | 接口 | 代码 |
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package com.fruitshorts.admin.controller;
|
||||
|
||||
import com.fruitshorts.common.pojo.PageResult;
|
||||
import com.fruitshorts.common.pojo.Result;
|
||||
import com.fruitshorts.creator.service.CreatorCenterService;
|
||||
import com.fruitshorts.creator.vo.CreatorCenterDramaQueryReqVO;
|
||||
import com.fruitshorts.creator.vo.CreatorCenterDramaVO;
|
||||
import com.fruitshorts.creator.vo.CreatorCenterNoticeVO;
|
||||
import com.fruitshorts.creator.vo.CreatorCenterProfileVO;
|
||||
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.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 创作者中心(Web 端,挂在后台管理里):创作者看自己的作品、审核进度与通知。
|
||||
*
|
||||
* **契约红线**:本控制器所有接口都**不接收 creatorId**,
|
||||
* 归属由 {@link CreatorCenterService#requireCurrentCreator()} 从登录态解析,
|
||||
* 查询强制带 `where creator_id = 当前创作者`;访问他人作品统一 403。
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/admin/creator-center")
|
||||
@RequiredArgsConstructor
|
||||
public class CreatorCenterController {
|
||||
|
||||
private final CreatorCenterService creatorCenterService;
|
||||
|
||||
/** 当前创作者的主体信息与入驻状态。 */
|
||||
@GetMapping("/profile")
|
||||
@PreAuthorize("@ss.hasPermission('creator-center:view')")
|
||||
public Result<CreatorCenterProfileVO> profile() {
|
||||
return Result.ok(creatorCenterService.profile());
|
||||
}
|
||||
|
||||
/** 我的作品列表(含最近一次提交的审核进度)。 */
|
||||
@GetMapping("/dramas/page")
|
||||
@PreAuthorize("@ss.hasPermission('creator-center:view')")
|
||||
public Result<PageResult<CreatorCenterDramaVO>> pageMyDramas(CreatorCenterDramaQueryReqVO req) {
|
||||
return Result.ok(creatorCenterService.pageMyDramas(req));
|
||||
}
|
||||
|
||||
/** 我的作品详情:id 是别人的作品时返回 403,不区分"不存在"。 */
|
||||
@GetMapping("/dramas/{id}")
|
||||
@PreAuthorize("@ss.hasPermission('creator-center:view')")
|
||||
public Result<CreatorCenterDramaVO> myDrama(@PathVariable Long id) {
|
||||
return Result.ok(creatorCenterService.myDramaDetail(id));
|
||||
}
|
||||
|
||||
/** 我的通知:入驻审核结果 + 我提交内容的审核结果。 */
|
||||
@GetMapping("/notices")
|
||||
@PreAuthorize("@ss.hasPermission('creator-center:view')")
|
||||
public Result<List<CreatorCenterNoticeVO>> notices() {
|
||||
return Result.ok(creatorCenterService.notices());
|
||||
}
|
||||
}
|
||||
+2
@@ -263,6 +263,7 @@ public class DramaService {
|
||||
drama.setGenre(req.getGenre() == null ? "" : req.getGenre());
|
||||
drama.setSynopsis(req.getDescription() == null ? "" : req.getDescription());
|
||||
drama.setCoverUrl(req.getCover() == null ? "" : req.getCover());
|
||||
drama.setTrailerUrl(req.getTrailerUrl() == null ? "" : req.getTrailerUrl());
|
||||
drama.setHorizontalCoverUrl(drama.getHorizontalCoverUrl() == null ? "" : drama.getHorizontalCoverUrl());
|
||||
drama.setLanguage(req.getLanguage() == null ? "zh-CN" : req.getLanguage());
|
||||
drama.setFreeEpisodes(req.getFreeEpisodes() == null ? 3 : req.getFreeEpisodes());
|
||||
@@ -293,6 +294,7 @@ public class DramaService {
|
||||
vo.setGenre(drama.getGenre());
|
||||
vo.setDescription(drama.getSynopsis());
|
||||
vo.setCover(drama.getCoverUrl());
|
||||
vo.setTrailerUrl(drama.getTrailerUrl());
|
||||
vo.setLanguage(drama.getLanguage());
|
||||
vo.setEpisodes(drama.getTotalEpisodes());
|
||||
vo.setFreeEpisodes(drama.getFreeEpisodes());
|
||||
|
||||
@@ -20,6 +20,8 @@ public class DramaRespVO {
|
||||
private String genre;
|
||||
private String description;
|
||||
private String cover;
|
||||
/** 整剧预告片地址 */
|
||||
private String trailerUrl;
|
||||
private String language;
|
||||
private Integer episodes;
|
||||
private Integer freeEpisodes;
|
||||
|
||||
+2
@@ -14,6 +14,8 @@ public class DramaSaveReqVO {
|
||||
private String genre;
|
||||
private String description;
|
||||
private String cover;
|
||||
/** 整剧预告片地址(复用已有的 drama.trailer_url 列) */
|
||||
private String trailerUrl;
|
||||
private String language;
|
||||
private Integer freeEpisodes;
|
||||
private Integer unlockCoin;
|
||||
|
||||
+2
@@ -20,6 +20,8 @@ public class Creator {
|
||||
private Long id;
|
||||
/** 关联 C 端账号 app_user.id */
|
||||
private Long userId;
|
||||
/** 绑定的后台账号 sys_admin_user.id(创作者中心的登录身份),0 表示未绑定 */
|
||||
private Long adminUserId;
|
||||
private String code;
|
||||
private String name;
|
||||
/** INDIVIDUAL / COMPANY */
|
||||
|
||||
+16
@@ -33,6 +33,11 @@ public class ContentSubmissionService {
|
||||
public static final String STATUS_REJECTED = "REJECTED";
|
||||
public static final String STATUS_WITHDRAWN = "WITHDRAWN";
|
||||
|
||||
public static final String TYPE_CREATE = "CREATE";
|
||||
public static final String TYPE_UPDATE = "UPDATE";
|
||||
public static final String TYPE_PUBLISH = "PUBLISH";
|
||||
public static final String TYPE_OFFLINE = "OFFLINE";
|
||||
|
||||
private static final Set<String> REVIEWABLE = Set.of(STATUS_SUBMITTED, STATUS_REVIEWING);
|
||||
|
||||
private final ContentSubmissionMapper submissionMapper;
|
||||
@@ -141,4 +146,15 @@ public class ContentSubmissionService {
|
||||
default -> code;
|
||||
};
|
||||
}
|
||||
|
||||
/** 提交类型文案:创作者看的通知里要写清楚是"新建"还是"申请上线" */
|
||||
public static String typeText(String code) {
|
||||
return switch (code == null ? "" : code) {
|
||||
case TYPE_CREATE -> "新建";
|
||||
case TYPE_UPDATE -> "编辑";
|
||||
case TYPE_PUBLISH -> "申请上线";
|
||||
case TYPE_OFFLINE -> "申请下线";
|
||||
default -> code == null ? "" : code;
|
||||
};
|
||||
}
|
||||
}
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
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.dal.mapper.DramaMapper;
|
||||
import com.fruitshorts.content.enums.ContentStatus;
|
||||
import com.fruitshorts.creator.dal.entity.ContentSubmission;
|
||||
import com.fruitshorts.creator.dal.entity.Creator;
|
||||
import com.fruitshorts.creator.dal.mapper.ContentSubmissionMapper;
|
||||
import com.fruitshorts.creator.dal.mapper.CreatorMapper;
|
||||
import com.fruitshorts.creator.vo.CreatorCenterDramaQueryReqVO;
|
||||
import com.fruitshorts.creator.vo.CreatorCenterDramaVO;
|
||||
import com.fruitshorts.creator.vo.CreatorCenterNoticeVO;
|
||||
import com.fruitshorts.creator.vo.CreatorCenterProfileVO;
|
||||
import com.fruitshorts.framework.security.SecurityUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 创作者中心(Web 端,做在后台管理里):创作者本人视角的只读查询与归属校验。
|
||||
*
|
||||
* **归属判定的唯一入口是 {@link #requireCurrentCreator()}**:
|
||||
* 创作者身份从登录态(sys_admin_user.id)解析,creator-center 的所有接口都**不收 creatorId 参数**,
|
||||
* 查询一律带 `where creator_id = 当前创作者`。所以横向越权不是靠"记得加过滤",
|
||||
* 而是参数层面就没有入口;真被别人猜到 id 时,{@link #requireMyDrama} 会直接 403。
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CreatorCenterService {
|
||||
|
||||
private static final int NOTICE_LIMIT = 50;
|
||||
|
||||
private final CreatorMapper creatorMapper;
|
||||
private final DramaMapper dramaMapper;
|
||||
private final ContentSubmissionMapper submissionMapper;
|
||||
|
||||
/** 当前登录人绑定的创作者主体;没绑定就是 403,不返回"你是管理员"之类的细节。 */
|
||||
public Creator requireCurrentCreator() {
|
||||
Long adminUserId = SecurityUtils.getUserId();
|
||||
if (adminUserId == null) {
|
||||
throw new ServiceException(ErrorCode.UNAUTHORIZED);
|
||||
}
|
||||
Creator creator = creatorMapper.selectOne(Wrappers.<Creator>lambdaQuery()
|
||||
.eq(Creator::getAdminUserId, adminUserId)
|
||||
.last("limit 1"));
|
||||
if (creator == null) {
|
||||
throw new ServiceException(ErrorCode.FORBIDDEN, "当前账号未绑定创作者主体,无法进入创作者中心");
|
||||
}
|
||||
return creator;
|
||||
}
|
||||
|
||||
/**
|
||||
* 作品归属校验:不是自己的作品,不区分"不存在"与"别人的",统一 403。
|
||||
* 后续各批(编辑、上传挂载、收费配置)都复用这个方法,避免各写一份过滤条件。
|
||||
*/
|
||||
public Drama requireMyDrama(Long dramaId) {
|
||||
Creator creator = requireCurrentCreator();
|
||||
Drama drama = dramaId == null ? null : dramaMapper.selectById(dramaId);
|
||||
if (drama == null || !Objects.equals(drama.getCreatorId(), creator.getId())) {
|
||||
throw new ServiceException(ErrorCode.FORBIDDEN, "无权访问该作品");
|
||||
}
|
||||
return drama;
|
||||
}
|
||||
|
||||
public CreatorCenterProfileVO profile() {
|
||||
Creator creator = requireCurrentCreator();
|
||||
CreatorCenterProfileVO vo = new CreatorCenterProfileVO();
|
||||
vo.setId(creator.getId());
|
||||
vo.setCode(creator.getCode());
|
||||
vo.setName(creator.getName());
|
||||
vo.setType(creator.getType());
|
||||
vo.setStatus(creator.getStatus());
|
||||
vo.setStatusText(CreatorService.statusText(creator.getStatus()));
|
||||
vo.setAuditNote(creator.getAuditNote());
|
||||
vo.setAuditAt(creator.getAuditAt());
|
||||
vo.setContactName(creator.getContactName());
|
||||
vo.setContactEmail(creator.getContactEmail());
|
||||
vo.setCountryCode(creator.getCountryCode());
|
||||
vo.setDramaTotal(dramaMapper.selectCount(Wrappers.<Drama>lambdaQuery()
|
||||
.eq(Drama::getCreatorId, creator.getId())));
|
||||
return vo;
|
||||
}
|
||||
|
||||
public PageResult<CreatorCenterDramaVO> pageMyDramas(CreatorCenterDramaQueryReqVO req) {
|
||||
Creator creator = requireCurrentCreator();
|
||||
Page<Drama> page = new Page<>(req.getSafePageIndex(), req.getSafePageSize());
|
||||
IPage<Drama> result = dramaMapper.selectPage(page, Wrappers.<Drama>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);
|
||||
return toDramaVOList(creator.getId(), List.of(drama)).get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 我的通知 = 入驻审核结果 + 我提交内容的审核结果(都从已有表派生,不新建通知表)。
|
||||
* 系统公告(ops_notice)当前没有产出方,等有公告功能再接,避免出现"永远空列表"的入口。
|
||||
*/
|
||||
public List<CreatorCenterNoticeVO> notices() {
|
||||
Creator creator = requireCurrentCreator();
|
||||
List<CreatorCenterNoticeVO> list = new ArrayList<>();
|
||||
|
||||
if (StringUtils.hasText(creator.getStatus()) && !CreatorService.STATUS_PENDING.equals(creator.getStatus())
|
||||
&& creator.getAuditAt() != null) {
|
||||
CreatorCenterNoticeVO notice = new CreatorCenterNoticeVO();
|
||||
notice.setId(creator.getId());
|
||||
notice.setBizType("CREATOR");
|
||||
notice.setTitle("入驻审核结果:" + CreatorService.statusText(creator.getStatus()));
|
||||
notice.setContent(StringUtils.hasText(creator.getAuditNote()) ? creator.getAuditNote() : "—");
|
||||
notice.setResult(creator.getStatus());
|
||||
notice.setTime(creator.getAuditAt());
|
||||
list.add(notice);
|
||||
}
|
||||
|
||||
List<ContentSubmission> reviewed = submissionMapper.selectList(Wrappers.<ContentSubmission>lambdaQuery()
|
||||
.eq(ContentSubmission::getCreatorId, creator.getId())
|
||||
.in(ContentSubmission::getStatus,
|
||||
ContentSubmissionService.STATUS_APPROVED, ContentSubmissionService.STATUS_REJECTED)
|
||||
.orderByDesc(ContentSubmission::getReviewAt)
|
||||
.last("limit " + NOTICE_LIMIT));
|
||||
for (ContentSubmission submission : reviewed) {
|
||||
CreatorCenterNoticeVO notice = new CreatorCenterNoticeVO();
|
||||
notice.setId(submission.getId());
|
||||
notice.setBizType("SUBMISSION");
|
||||
notice.setTitle("作品 ID " + submission.getDramaId() + " 的"
|
||||
+ ContentSubmissionService.typeText(submission.getType()) + "审核结果:"
|
||||
+ ContentSubmissionService.statusText(submission.getStatus()));
|
||||
notice.setContent(StringUtils.hasText(submission.getReviewNote())
|
||||
? submission.getReviewNote() : "—");
|
||||
notice.setResult(submission.getStatus());
|
||||
notice.setTime(submission.getReviewAt());
|
||||
list.add(notice);
|
||||
}
|
||||
|
||||
list.sort(Comparator.comparing(CreatorCenterNoticeVO::getTime,
|
||||
Comparator.nullsLast(Comparator.reverseOrder())));
|
||||
return list;
|
||||
}
|
||||
|
||||
/** 作品 + 最近一次提交:列表页要显示"审核状态与进度",两个表拼一次拿全,避免前端 N+1。 */
|
||||
private List<CreatorCenterDramaVO> toDramaVOList(Long creatorId, List<Drama> dramas) {
|
||||
if (dramas.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
List<Long> dramaIds = dramas.stream().map(Drama::getId).toList();
|
||||
List<ContentSubmission> submissions = submissionMapper.selectList(Wrappers.<ContentSubmission>lambdaQuery()
|
||||
.eq(ContentSubmission::getCreatorId, creatorId)
|
||||
.in(ContentSubmission::getDramaId, dramaIds)
|
||||
.orderByDesc(ContentSubmission::getId));
|
||||
Map<Long, ContentSubmission> latest = new HashMap<>();
|
||||
for (ContentSubmission submission : submissions) {
|
||||
latest.putIfAbsent(submission.getDramaId(), submission);
|
||||
}
|
||||
|
||||
return dramas.stream().map(drama -> {
|
||||
CreatorCenterDramaVO vo = new CreatorCenterDramaVO();
|
||||
vo.setId(drama.getId());
|
||||
vo.setCode(drama.getCode());
|
||||
vo.setTitle(drama.getTitle());
|
||||
vo.setCover(drama.getCoverUrl());
|
||||
vo.setStatus(drama.getStatus());
|
||||
vo.setStatusText(ContentStatus.textOf(drama.getStatus()));
|
||||
vo.setEpisodes(drama.getTotalEpisodes());
|
||||
vo.setViews(drama.getViewCount());
|
||||
vo.setUpdatedAt(drama.getUpdatedAt());
|
||||
|
||||
ContentSubmission submission = latest.get(drama.getId());
|
||||
if (submission != null) {
|
||||
vo.setSubmissionId(submission.getId());
|
||||
vo.setSubmissionType(submission.getType());
|
||||
vo.setSubmissionStatus(submission.getStatus());
|
||||
vo.setSubmissionStatusText(ContentSubmissionService.statusText(submission.getStatus()));
|
||||
vo.setReviewNote(submission.getReviewNote());
|
||||
vo.setRejectCode(submission.getRejectCode());
|
||||
vo.setSubmitAt(submission.getSubmitAt());
|
||||
vo.setReviewAt(submission.getReviewAt());
|
||||
}
|
||||
return vo;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package com.fruitshorts.creator.vo;
|
||||
|
||||
import com.fruitshorts.common.pojo.PageParam;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 创作者中心 · 我的作品检索条件。
|
||||
*
|
||||
* 注意:这里**没有 creatorId** —— 归属一律从登录态解析,前端传不进来,也就没有横向越权的入口。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class CreatorCenterDramaQueryReqVO extends PageParam {
|
||||
|
||||
private String keyword;
|
||||
private String status;
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package com.fruitshorts.creator.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 创作者中心 · 我的作品(作品 + 最近一次提交的审核进度,一个接口给全,避免前端拼两个请求)。
|
||||
*/
|
||||
@Data
|
||||
public class CreatorCenterDramaVO {
|
||||
|
||||
private Long id;
|
||||
private String code;
|
||||
private String title;
|
||||
private String cover;
|
||||
private String status;
|
||||
private String statusText;
|
||||
private Integer episodes;
|
||||
private Long views;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
/** 最近一次内容提交(没有提交过则为空) */
|
||||
private Long submissionId;
|
||||
private String submissionType;
|
||||
private String submissionStatus;
|
||||
private String submissionStatusText;
|
||||
private String reviewNote;
|
||||
private String rejectCode;
|
||||
private LocalDateTime submitAt;
|
||||
private LocalDateTime reviewAt;
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.fruitshorts.creator.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 创作者中心 · 通知。
|
||||
*
|
||||
* 第一阶段的通知来源是「审核结果」:入驻审核(CREATOR)与内容提交审核(SUBMISSION),
|
||||
* 都从已有表派生,不新建通知表;系统公告(ops_notice)目前没有产出方,等有公告功能再接。
|
||||
*/
|
||||
@Data
|
||||
public class CreatorCenterNoticeVO {
|
||||
|
||||
/** 来源单据 id */
|
||||
private Long id;
|
||||
/** CREATOR(入驻审核)/ SUBMISSION(内容提交审核) */
|
||||
private String bizType;
|
||||
private String title;
|
||||
private String content;
|
||||
/** 通过 / 驳回 等结果文案 */
|
||||
private String result;
|
||||
private LocalDateTime time;
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.fruitshorts.creator.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/** 创作者中心 · 当前登录创作者的主体信息(只返回自己的,不含他人数据)。 */
|
||||
@Data
|
||||
public class CreatorCenterProfileVO {
|
||||
|
||||
private Long id;
|
||||
private String code;
|
||||
private String name;
|
||||
/** INDIVIDUAL / COMPANY */
|
||||
private String type;
|
||||
/** pending / approved / rejected / frozen */
|
||||
private String status;
|
||||
private String statusText;
|
||||
/** 入驻审核意见(驳回时是原因) */
|
||||
private String auditNote;
|
||||
private LocalDateTime auditAt;
|
||||
private String contactName;
|
||||
private String contactEmail;
|
||||
private String countryCode;
|
||||
/** 我的作品数(只统计自己的) */
|
||||
private Long dramaTotal;
|
||||
}
|
||||
@@ -163,3 +163,38 @@ CROSS JOIN sys_menu m
|
||||
WHERE r.code = 'super_admin'
|
||||
AND m.id IN (20, 21, 22, 23, 24, 25)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- ---------- 07 创作者中心:角色、权限点、授权 ----------
|
||||
-- 创作者第一阶段直接登后台(复用 sys_admin_user + sys_role + sys_menu),
|
||||
-- 这里只落"身份与权限";菜单等页面做出来再挂 —— 菜单先入库会让前端动态路由指向不存在的组件。
|
||||
INSERT INTO sys_role_category (id, name, description, sort) VALUES
|
||||
(4, '创作者', '外部内容创作者,只能管理自己的作品与数据', 4)
|
||||
ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, description = EXCLUDED.description, sort = EXCLUDED.sort;
|
||||
|
||||
INSERT INTO sys_role (id, name, code, category_id, level, description, status, is_builtin) VALUES
|
||||
(5, '创作者', 'creator', 4, 5, '只能查看与编辑自己的作品与数据', 1, 0)
|
||||
ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, code = EXCLUDED.code,
|
||||
description = EXCLUDED.description, category_id = EXCLUDED.category_id, level = EXCLUDED.level;
|
||||
|
||||
-- 权限点按批补:批 1 只有「进创作者中心」这一个,后面各批用到什么再幂等追加
|
||||
INSERT INTO sys_permission (code, name, group_name, type, sort) VALUES
|
||||
('creator-center:view', '访问创作者中心', '创作者中心', 'BUTTON', 150)
|
||||
ON CONFLICT (code) DO NOTHING;
|
||||
|
||||
-- 创作者角色只拿创作者中心这几个点,不给任何后台运营权限
|
||||
INSERT INTO sys_role_permission (role_id, permission_id)
|
||||
SELECT r.id, p.id
|
||||
FROM sys_role r
|
||||
CROSS JOIN sys_permission p
|
||||
WHERE r.code = 'creator'
|
||||
AND p.code = 'creator-center:view'
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- 超管也留一份,方便排查创作者视角的问题(超管没有绑定创作者主体,进 creator-center 仍会被挡)
|
||||
INSERT INTO sys_role_permission (role_id, permission_id)
|
||||
SELECT r.id, p.id
|
||||
FROM sys_role r
|
||||
CROSS JOIN sys_permission p
|
||||
WHERE r.code = 'super_admin'
|
||||
AND p.code = 'creator-center:view'
|
||||
ON CONFLICT DO NOTHING;
|
||||
@@ -903,3 +903,13 @@ COMMENT ON TABLE download_task IS '离线下载任务与断点续传进度(分
|
||||
DROP TRIGGER IF EXISTS trg_download_task_updated_at ON download_task;
|
||||
CREATE TRIGGER trg_download_task_updated_at BEFORE UPDATE ON download_task
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
-- ============================================================
|
||||
-- 九、07 创作者中心(Web 端):后台账号 ↔ 创作者主体
|
||||
-- ============================================================
|
||||
-- 第一阶段创作者直接登后台,身份复用 sys_admin_user / sys_role / sys_menu,
|
||||
-- 所以 creator 需要一列账号映射;用「部分唯一索引」:
|
||||
-- admin_user_id = 0 表示尚未绑定账号,多条 0 不冲突;绑定之后一账号只能对应一个创作者主体。
|
||||
ALTER TABLE creator ADD COLUMN IF NOT EXISTS admin_user_id BIGINT NOT NULL DEFAULT 0;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_creator_admin_user ON creator (admin_user_id) WHERE admin_user_id > 0;
|
||||
COMMENT ON COLUMN creator.admin_user_id IS '绑定的后台账号 sys_admin_user.id,0 表示未绑定(创作者中心据此判定归属)';
|
||||
Reference in new issue
Block a user