feat(content): 新增运营分类与剧单分类管理
This commit is contained in:
1 parent
758c7521bb
commit
7b360ddccd
26 files changed
+1106
-3
No files matched your search
+115
@@ -0,0 +1,115 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
package com.fruitshorts.admin.controller;
|
||||
|
||||
import com.fruitshorts.common.pojo.PageResult;
|
||||
import com.fruitshorts.common.pojo.Result;
|
||||
import com.fruitshorts.content.service.DramaClassificationService;
|
||||
import com.fruitshorts.content.vo.DramaBriefVO;
|
||||
import com.fruitshorts.content.vo.DramaClassificationRespVO;
|
||||
import com.fruitshorts.content.vo.DramaClassificationSaveReqVO;
|
||||
import com.fruitshorts.content.vo.DramaIdsReqVO;
|
||||
import com.fruitshorts.framework.log.OperationLog;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
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.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 运营分类管理。分类与短剧类型(drama-categories)是两套独立概念,
|
||||
* 分类可自定义增删,并关联任意多部短剧。
|
||||
*/
|
||||
@Tag(name = "后台-运营分类")
|
||||
@RestController
|
||||
@RequestMapping("/admin/drama-classifications")
|
||||
@RequiredArgsConstructor
|
||||
public class DramaClassificationController {
|
||||
|
||||
private final DramaClassificationService classificationService;
|
||||
|
||||
@Operation(summary = "分类列表(带关联数量)")
|
||||
@GetMapping
|
||||
@PreAuthorize("@ss.hasPermission('drama:list')")
|
||||
public Result<List<DramaClassificationRespVO>> list() {
|
||||
return Result.ok(classificationService.list());
|
||||
}
|
||||
|
||||
@Operation(summary = "新建分类")
|
||||
@PostMapping
|
||||
@PreAuthorize("@ss.hasPermission('drama:create')")
|
||||
@OperationLog(module = "内容管理", action = "新增分类")
|
||||
public Result<Long> create(@Valid @RequestBody DramaClassificationSaveReqVO req) {
|
||||
return Result.ok(classificationService.create(req));
|
||||
}
|
||||
|
||||
@Operation(summary = "编辑分类")
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("@ss.hasPermission('drama:update')")
|
||||
@OperationLog(module = "内容管理", action = "编辑分类")
|
||||
public Result<Void> update(@PathVariable Long id,
|
||||
@Valid @RequestBody DramaClassificationSaveReqVO req) {
|
||||
classificationService.update(id, req);
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
@Operation(summary = "删除分类")
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("@ss.hasPermission('drama:delete')")
|
||||
@OperationLog(module = "内容管理", action = "删除分类")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
classificationService.delete(id);
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
@Operation(summary = "分类下已关联的短剧")
|
||||
@GetMapping("/{id}/dramas")
|
||||
@PreAuthorize("@ss.hasPermission('drama:list')")
|
||||
public Result<PageResult<DramaBriefVO>> dramas(
|
||||
@PathVariable Long id,
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(defaultValue = "1") int pageIndex,
|
||||
@RequestParam(defaultValue = "10") int pageSize) {
|
||||
return Result.ok(classificationService.pageDramas(id, keyword, pageIndex, pageSize));
|
||||
}
|
||||
|
||||
@Operation(summary = "尚未关联到该分类的短剧")
|
||||
@GetMapping("/{id}/dramas/candidates")
|
||||
@PreAuthorize("@ss.hasPermission('drama:list')")
|
||||
public Result<PageResult<DramaBriefVO>> candidates(
|
||||
@PathVariable Long id,
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(defaultValue = "1") int pageIndex,
|
||||
@RequestParam(defaultValue = "10") int pageSize) {
|
||||
return Result.ok(classificationService.pageCandidates(id, keyword, pageIndex, pageSize));
|
||||
}
|
||||
|
||||
@Operation(summary = "批量关联短剧")
|
||||
@PostMapping("/{id}/dramas")
|
||||
@PreAuthorize("@ss.hasPermission('drama:update')")
|
||||
@OperationLog(module = "内容管理", action = "分类关联短剧")
|
||||
public Result<Integer> bindDramas(@PathVariable Long id,
|
||||
@Valid @RequestBody DramaIdsReqVO req) {
|
||||
return Result.ok(classificationService.bindDramas(id, req.getDramaIds()));
|
||||
}
|
||||
|
||||
@Operation(summary = "移除分类下的短剧")
|
||||
@DeleteMapping("/{id}/dramas/{dramaId}")
|
||||
@PreAuthorize("@ss.hasPermission('drama:update')")
|
||||
@OperationLog(module = "内容管理", action = "分类移除短剧")
|
||||
public Result<Void> unbindDrama(@PathVariable Long id, @PathVariable Long dramaId) {
|
||||
classificationService.unbindDrama(id, dramaId);
|
||||
return Result.ok();
|
||||
}
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
package com.fruitshorts.admin.controller;
|
||||
|
||||
import com.fruitshorts.common.pojo.PageResult;
|
||||
import com.fruitshorts.common.pojo.Result;
|
||||
import com.fruitshorts.content.service.DramaPlaylistCategoryService;
|
||||
import com.fruitshorts.content.vo.DramaBriefVO;
|
||||
import com.fruitshorts.content.vo.DramaIdsReqVO;
|
||||
import com.fruitshorts.content.vo.DramaPlaylistCategoryRespVO;
|
||||
import com.fruitshorts.content.vo.DramaPlaylistCategorySaveReqVO;
|
||||
import com.fruitshorts.framework.log.OperationLog;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
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.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 剧单分类管理:分类直接关联短剧,没有「剧单」这一层。
|
||||
* 与「类型」「分类管理」均无关。
|
||||
*/
|
||||
@Tag(name = "后台-剧单分类")
|
||||
@RestController
|
||||
@RequestMapping("/admin/drama-playlist-categories")
|
||||
@RequiredArgsConstructor
|
||||
public class DramaPlaylistCategoryController {
|
||||
|
||||
private final DramaPlaylistCategoryService categoryService;
|
||||
|
||||
@Operation(summary = "剧单分类列表")
|
||||
@GetMapping
|
||||
@PreAuthorize("@ss.hasPermission('drama:list')")
|
||||
public Result<List<DramaPlaylistCategoryRespVO>> list() {
|
||||
return Result.ok(categoryService.list());
|
||||
}
|
||||
|
||||
@Operation(summary = "新建剧单分类")
|
||||
@PostMapping
|
||||
@PreAuthorize("@ss.hasPermission('drama:create')")
|
||||
@OperationLog(module = "内容管理", action = "新增剧单分类")
|
||||
public Result<Long> create(@Valid @RequestBody DramaPlaylistCategorySaveReqVO req) {
|
||||
return Result.ok(categoryService.create(req));
|
||||
}
|
||||
|
||||
@Operation(summary = "编辑剧单分类")
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("@ss.hasPermission('drama:update')")
|
||||
@OperationLog(module = "内容管理", action = "编辑剧单分类")
|
||||
public Result<Void> update(@PathVariable Long id,
|
||||
@Valid @RequestBody DramaPlaylistCategorySaveReqVO req) {
|
||||
categoryService.update(id, req);
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
@Operation(summary = "删除剧单分类")
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("@ss.hasPermission('drama:delete')")
|
||||
@OperationLog(module = "内容管理", action = "删除剧单分类")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
categoryService.delete(id);
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
@Operation(summary = "分类下已关联的短剧")
|
||||
@GetMapping("/{id}/dramas")
|
||||
@PreAuthorize("@ss.hasPermission('drama:list')")
|
||||
public Result<PageResult<DramaBriefVO>> dramas(
|
||||
@PathVariable Long id,
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(defaultValue = "1") int pageIndex,
|
||||
@RequestParam(defaultValue = "10") int pageSize) {
|
||||
return Result.ok(categoryService.pageDramas(id, keyword, pageIndex, pageSize));
|
||||
}
|
||||
|
||||
@Operation(summary = "尚未关联到该分类的短剧")
|
||||
@GetMapping("/{id}/dramas/candidates")
|
||||
@PreAuthorize("@ss.hasPermission('drama:list')")
|
||||
public Result<PageResult<DramaBriefVO>> candidates(
|
||||
@PathVariable Long id,
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(defaultValue = "1") int pageIndex,
|
||||
@RequestParam(defaultValue = "10") int pageSize) {
|
||||
return Result.ok(categoryService.pageCandidates(id, keyword, pageIndex, pageSize));
|
||||
}
|
||||
|
||||
@Operation(summary = "批量关联短剧")
|
||||
@PostMapping("/{id}/dramas")
|
||||
@PreAuthorize("@ss.hasPermission('drama:update')")
|
||||
@OperationLog(module = "内容管理", action = "剧单分类关联短剧")
|
||||
public Result<Integer> bindDramas(@PathVariable Long id,
|
||||
@Valid @RequestBody DramaIdsReqVO req) {
|
||||
return Result.ok(categoryService.bindDramas(id, req.getDramaIds()));
|
||||
}
|
||||
|
||||
@Operation(summary = "移除分类下的短剧")
|
||||
@DeleteMapping("/{id}/dramas/{dramaId}")
|
||||
@PreAuthorize("@ss.hasPermission('drama:update')")
|
||||
@OperationLog(module = "内容管理", action = "剧单分类移除短剧")
|
||||
public Result<Void> unbindDrama(@PathVariable Long id, @PathVariable Long dramaId) {
|
||||
categoryService.unbindDrama(id, dramaId);
|
||||
return Result.ok();
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
package com.fruitshorts.content.dal.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/** 运营分类:与短剧类型(drama_category)是两套独立概念。 */
|
||||
@Data
|
||||
@TableName("drama_classification")
|
||||
public class DramaClassification {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
private String name;
|
||||
private Integer sort;
|
||||
private Integer status;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
package com.fruitshorts.content.dal.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/** 分类与短剧的关联关系(多对多)。 */
|
||||
@Data
|
||||
@TableName("drama_classification_drama")
|
||||
public class DramaClassificationDrama {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
private Long classificationId;
|
||||
private Long dramaId;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
package com.fruitshorts.content.dal.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/** 剧单专属分类:只服务于剧单分组,与运营分类(drama_classification)无关。 */
|
||||
@Data
|
||||
@TableName("drama_playlist_category")
|
||||
public class DramaPlaylistCategory {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
private String name;
|
||||
/** 分类描述,便于运营说明该分类的用途 */
|
||||
private String description;
|
||||
private Integer sort;
|
||||
private Integer status;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
package com.fruitshorts.content.dal.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/** 剧单分类与短剧的关联关系(分类直接挂短剧,没有剧单层)。 */
|
||||
@Data
|
||||
@TableName("drama_playlist_category_drama")
|
||||
public class DramaPlaylistCategoryDrama {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
private Long categoryId;
|
||||
private Long dramaId;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
package com.fruitshorts.content.dal.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.fruitshorts.content.dal.entity.DramaClassificationDrama;
|
||||
|
||||
public interface DramaClassificationDramaMapper extends BaseMapper<DramaClassificationDrama> {
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
package com.fruitshorts.content.dal.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.fruitshorts.content.dal.entity.DramaClassification;
|
||||
|
||||
public interface DramaClassificationMapper extends BaseMapper<DramaClassification> {
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
package com.fruitshorts.content.dal.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.fruitshorts.content.dal.entity.DramaPlaylistCategoryDrama;
|
||||
|
||||
public interface DramaPlaylistCategoryDramaMapper extends BaseMapper<DramaPlaylistCategoryDrama> {
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
package com.fruitshorts.content.dal.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.fruitshorts.content.dal.entity.DramaPlaylistCategory;
|
||||
|
||||
public interface DramaPlaylistCategoryMapper extends BaseMapper<DramaPlaylistCategory> {
|
||||
}
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
package com.fruitshorts.content.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
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.entity.DramaClassification;
|
||||
import com.fruitshorts.content.dal.entity.DramaClassificationDrama;
|
||||
import com.fruitshorts.content.dal.mapper.DramaClassificationDramaMapper;
|
||||
import com.fruitshorts.content.dal.mapper.DramaClassificationMapper;
|
||||
import com.fruitshorts.content.dal.mapper.DramaMapper;
|
||||
import com.fruitshorts.content.vo.DramaBriefVO;
|
||||
import com.fruitshorts.content.vo.DramaClassificationRespVO;
|
||||
import com.fruitshorts.content.vo.DramaClassificationSaveReqVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 运营分类服务。分类是独立于「短剧类型(drama_category)」的一套概念,
|
||||
* 由运营自定义增删,并通过关联表挂载任意多部短剧。
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DramaClassificationService {
|
||||
|
||||
private final DramaClassificationMapper classificationMapper;
|
||||
private final DramaClassificationDramaMapper classificationDramaMapper;
|
||||
private final DramaMapper dramaMapper;
|
||||
|
||||
public List<DramaClassificationRespVO> list() {
|
||||
Map<Long, Integer> counts = countDramas();
|
||||
return classificationMapper.selectList(Wrappers.<DramaClassification>lambdaQuery()
|
||||
.orderByAsc(DramaClassification::getSort)
|
||||
.orderByDesc(DramaClassification::getId))
|
||||
.stream().map(item -> {
|
||||
DramaClassificationRespVO vo = new DramaClassificationRespVO();
|
||||
vo.setId(item.getId());
|
||||
vo.setName(item.getName());
|
||||
vo.setSort(item.getSort());
|
||||
vo.setStatus(item.getStatus());
|
||||
vo.setUpdatedAt(item.getUpdatedAt());
|
||||
vo.setDramaCount(counts.getOrDefault(item.getId(), 0));
|
||||
return vo;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
public Long create(DramaClassificationSaveReqVO req) {
|
||||
ensureNameAvailable(req.getName(), null);
|
||||
DramaClassification entity = new DramaClassification();
|
||||
entity.setName(req.getName().trim());
|
||||
Long count = classificationMapper.selectCount(null);
|
||||
entity.setSort(req.getSort() == null
|
||||
? (count == null ? 0 : count.intValue()) + 1
|
||||
: req.getSort());
|
||||
entity.setStatus(req.getStatus() == null ? 1 : req.getStatus());
|
||||
classificationMapper.insert(entity);
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
public void update(Long id, DramaClassificationSaveReqVO req) {
|
||||
DramaClassification entity = getExisting(id);
|
||||
ensureNameAvailable(req.getName(), id);
|
||||
entity.setName(req.getName().trim());
|
||||
if (req.getSort() != null) {
|
||||
entity.setSort(req.getSort());
|
||||
}
|
||||
if (req.getStatus() != null) {
|
||||
entity.setStatus(req.getStatus());
|
||||
}
|
||||
classificationMapper.updateById(entity);
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id) {
|
||||
getExisting(id);
|
||||
classificationDramaMapper.delete(Wrappers.<DramaClassificationDrama>lambdaQuery()
|
||||
.eq(DramaClassificationDrama::getClassificationId, id));
|
||||
classificationMapper.deleteById(id);
|
||||
}
|
||||
|
||||
/** 分类下已关联的短剧(分页)。 */
|
||||
public PageResult<DramaBriefVO> pageDramas(Long id, String keyword,
|
||||
int pageIndex, int pageSize) {
|
||||
return pageDramas(id, keyword, pageIndex, pageSize, true);
|
||||
}
|
||||
|
||||
/** 尚未关联到该分类的短剧(分页),供「添加短剧」多选。 */
|
||||
public PageResult<DramaBriefVO> pageCandidates(Long id, String keyword,
|
||||
int pageIndex, int pageSize) {
|
||||
return pageDramas(id, keyword, pageIndex, pageSize, false);
|
||||
}
|
||||
|
||||
/** 批量关联短剧;已关联的自动跳过。 */
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int bindDramas(Long id, List<Long> dramaIds) {
|
||||
getExisting(id);
|
||||
if (dramaIds == null || dramaIds.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
int added = 0;
|
||||
for (Long dramaId : dramaIds) {
|
||||
if (dramaId == null || dramaMapper.selectById(dramaId) == null) {
|
||||
continue;
|
||||
}
|
||||
Long exists = classificationDramaMapper.selectCount(
|
||||
Wrappers.<DramaClassificationDrama>lambdaQuery()
|
||||
.eq(DramaClassificationDrama::getClassificationId, id)
|
||||
.eq(DramaClassificationDrama::getDramaId, dramaId));
|
||||
if (exists != null && exists > 0) {
|
||||
continue;
|
||||
}
|
||||
DramaClassificationDrama relation = new DramaClassificationDrama();
|
||||
relation.setClassificationId(id);
|
||||
relation.setDramaId(dramaId);
|
||||
classificationDramaMapper.insert(relation);
|
||||
added++;
|
||||
}
|
||||
return added;
|
||||
}
|
||||
|
||||
/** 移出分类;短剧本身不受影响。 */
|
||||
public void unbindDrama(Long id, Long dramaId) {
|
||||
getExisting(id);
|
||||
classificationDramaMapper.delete(Wrappers.<DramaClassificationDrama>lambdaQuery()
|
||||
.eq(DramaClassificationDrama::getClassificationId, id)
|
||||
.eq(DramaClassificationDrama::getDramaId, dramaId));
|
||||
}
|
||||
|
||||
private PageResult<DramaBriefVO> pageDramas(Long id, String keyword,
|
||||
int pageIndex, int pageSize, boolean bound) {
|
||||
getExisting(id);
|
||||
Page<Drama> page = new Page<>(Math.max(1, pageIndex), Math.max(1, Math.min(pageSize, 100)));
|
||||
LambdaQueryWrapper<Drama> wrapper = Wrappers.<Drama>lambdaQuery()
|
||||
.like(StringUtils.hasText(keyword), Drama::getTitle, keyword)
|
||||
.orderByDesc(Drama::getSort)
|
||||
.orderByDesc(Drama::getId);
|
||||
String subQuery = "SELECT drama_id FROM drama_classification_drama WHERE classification_id = " + id;
|
||||
if (bound) {
|
||||
wrapper.inSql(Drama::getId, subQuery);
|
||||
} else {
|
||||
wrapper.notInSql(Drama::getId, subQuery);
|
||||
}
|
||||
IPage<Drama> result = dramaMapper.selectPage(page, wrapper);
|
||||
return new PageResult<>(result.getTotal(),
|
||||
result.getRecords().stream().map(this::toBrief).toList());
|
||||
}
|
||||
|
||||
private Map<Long, Integer> countDramas() {
|
||||
Map<Long, Integer> counts = new HashMap<>();
|
||||
classificationDramaMapper.selectMaps(new QueryWrapper<DramaClassificationDrama>()
|
||||
.select("classification_id AS cid", "COUNT(*) AS cnt")
|
||||
.groupBy("classification_id"))
|
||||
.forEach(row -> {
|
||||
Object cid = row.get("cid");
|
||||
Object cnt = row.get("cnt");
|
||||
if (cid instanceof Number c && cnt instanceof Number n) {
|
||||
counts.put(c.longValue(), n.intValue());
|
||||
}
|
||||
});
|
||||
return counts;
|
||||
}
|
||||
|
||||
private DramaClassification getExisting(Long id) {
|
||||
DramaClassification entity = id == null ? null : classificationMapper.selectById(id);
|
||||
if (entity == null) {
|
||||
throw new ServiceException(ErrorCode.DATA_NOT_FOUND, "分类不存在");
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
private void ensureNameAvailable(String name, Long excludeId) {
|
||||
Long count = classificationMapper.selectCount(Wrappers.<DramaClassification>lambdaQuery()
|
||||
.eq(DramaClassification::getName, name.trim())
|
||||
.ne(excludeId != null, DramaClassification::getId, excludeId));
|
||||
if (count != null && count > 0) {
|
||||
throw new ServiceException(ErrorCode.DATA_EXISTS, "分类名称已存在");
|
||||
}
|
||||
}
|
||||
|
||||
private DramaBriefVO toBrief(Drama drama) {
|
||||
DramaBriefVO vo = new DramaBriefVO();
|
||||
vo.setId(drama.getId());
|
||||
vo.setTitle(drama.getTitle());
|
||||
vo.setCover(drama.getCoverUrl());
|
||||
vo.setStatus(drama.getStatus());
|
||||
vo.setTotalEpisodes(drama.getTotalEpisodes());
|
||||
vo.setSort(drama.getSort());
|
||||
vo.setUpdatedAt(drama.getUpdatedAt());
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
+208
@@ -0,0 +1,208 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
package com.fruitshorts.content.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
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.entity.DramaPlaylistCategory;
|
||||
import com.fruitshorts.content.dal.entity.DramaPlaylistCategoryDrama;
|
||||
import com.fruitshorts.content.dal.mapper.DramaMapper;
|
||||
import com.fruitshorts.content.dal.mapper.DramaPlaylistCategoryDramaMapper;
|
||||
import com.fruitshorts.content.dal.mapper.DramaPlaylistCategoryMapper;
|
||||
import com.fruitshorts.content.vo.DramaBriefVO;
|
||||
import com.fruitshorts.content.vo.DramaPlaylistCategoryRespVO;
|
||||
import com.fruitshorts.content.vo.DramaPlaylistCategorySaveReqVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 剧单分类服务:分类直接关联短剧,没有「剧单」这一层。
|
||||
* 这套分类与「类型」「分类管理」都不是同一套数据。
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DramaPlaylistCategoryService {
|
||||
|
||||
private final DramaPlaylistCategoryMapper categoryMapper;
|
||||
private final DramaPlaylistCategoryDramaMapper categoryDramaMapper;
|
||||
private final DramaMapper dramaMapper;
|
||||
|
||||
public List<DramaPlaylistCategoryRespVO> list() {
|
||||
Map<Long, Integer> counts = countDramas();
|
||||
return categoryMapper.selectList(Wrappers.<DramaPlaylistCategory>lambdaQuery()
|
||||
.orderByAsc(DramaPlaylistCategory::getSort)
|
||||
.orderByDesc(DramaPlaylistCategory::getId))
|
||||
.stream().map(item -> {
|
||||
DramaPlaylistCategoryRespVO vo = new DramaPlaylistCategoryRespVO();
|
||||
vo.setId(item.getId());
|
||||
vo.setName(item.getName());
|
||||
vo.setDescription(item.getDescription());
|
||||
vo.setSort(item.getSort());
|
||||
vo.setStatus(item.getStatus());
|
||||
vo.setDramaCount(counts.getOrDefault(item.getId(), 0));
|
||||
return vo;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
public Long create(DramaPlaylistCategorySaveReqVO req) {
|
||||
ensureNameAvailable(req.getName(), null);
|
||||
DramaPlaylistCategory entity = new DramaPlaylistCategory();
|
||||
entity.setName(req.getName().trim());
|
||||
entity.setDescription(req.getDescription() == null ? "" : req.getDescription().trim());
|
||||
Long count = categoryMapper.selectCount(null);
|
||||
entity.setSort(req.getSort() == null
|
||||
? (count == null ? 0 : count.intValue()) + 1
|
||||
: req.getSort());
|
||||
entity.setStatus(req.getStatus() == null ? 1 : req.getStatus());
|
||||
categoryMapper.insert(entity);
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
public void update(Long id, DramaPlaylistCategorySaveReqVO req) {
|
||||
DramaPlaylistCategory entity = getExisting(id);
|
||||
ensureNameAvailable(req.getName(), id);
|
||||
entity.setName(req.getName().trim());
|
||||
if (req.getDescription() != null) {
|
||||
entity.setDescription(req.getDescription().trim());
|
||||
}
|
||||
if (req.getSort() != null) {
|
||||
entity.setSort(req.getSort());
|
||||
}
|
||||
if (req.getStatus() != null) {
|
||||
entity.setStatus(req.getStatus());
|
||||
}
|
||||
categoryMapper.updateById(entity);
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id) {
|
||||
getExisting(id);
|
||||
categoryDramaMapper.delete(Wrappers.<DramaPlaylistCategoryDrama>lambdaQuery()
|
||||
.eq(DramaPlaylistCategoryDrama::getCategoryId, id));
|
||||
categoryMapper.deleteById(id);
|
||||
}
|
||||
|
||||
/** 分类下已关联的短剧(分页)。 */
|
||||
public PageResult<DramaBriefVO> pageDramas(Long id, String keyword,
|
||||
int pageIndex, int pageSize) {
|
||||
return pageDramas(id, keyword, pageIndex, pageSize, true);
|
||||
}
|
||||
|
||||
/** 尚未关联到该分类的短剧(分页),供「选择短剧」多选。 */
|
||||
public PageResult<DramaBriefVO> pageCandidates(Long id, String keyword,
|
||||
int pageIndex, int pageSize) {
|
||||
return pageDramas(id, keyword, pageIndex, pageSize, false);
|
||||
}
|
||||
|
||||
/** 批量把短剧加入分类;已关联的自动跳过。 */
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int bindDramas(Long id, List<Long> dramaIds) {
|
||||
getExisting(id);
|
||||
if (dramaIds == null || dramaIds.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
int added = 0;
|
||||
for (Long dramaId : dramaIds) {
|
||||
if (dramaId == null || dramaMapper.selectById(dramaId) == null) {
|
||||
continue;
|
||||
}
|
||||
Long exists = categoryDramaMapper.selectCount(
|
||||
Wrappers.<DramaPlaylistCategoryDrama>lambdaQuery()
|
||||
.eq(DramaPlaylistCategoryDrama::getCategoryId, id)
|
||||
.eq(DramaPlaylistCategoryDrama::getDramaId, dramaId));
|
||||
if (exists != null && exists > 0) {
|
||||
continue;
|
||||
}
|
||||
DramaPlaylistCategoryDrama relation = new DramaPlaylistCategoryDrama();
|
||||
relation.setCategoryId(id);
|
||||
relation.setDramaId(dramaId);
|
||||
categoryDramaMapper.insert(relation);
|
||||
added++;
|
||||
}
|
||||
return added;
|
||||
}
|
||||
|
||||
/** 把短剧移出分类;短剧本身不受影响。 */
|
||||
public void unbindDrama(Long id, Long dramaId) {
|
||||
getExisting(id);
|
||||
categoryDramaMapper.delete(Wrappers.<DramaPlaylistCategoryDrama>lambdaQuery()
|
||||
.eq(DramaPlaylistCategoryDrama::getCategoryId, id)
|
||||
.eq(DramaPlaylistCategoryDrama::getDramaId, dramaId));
|
||||
}
|
||||
|
||||
private PageResult<DramaBriefVO> pageDramas(Long id, String keyword,
|
||||
int pageIndex, int pageSize, boolean bound) {
|
||||
getExisting(id);
|
||||
Page<Drama> page = new Page<>(Math.max(1, pageIndex), Math.max(1, Math.min(pageSize, 100)));
|
||||
LambdaQueryWrapper<Drama> wrapper = Wrappers.<Drama>lambdaQuery()
|
||||
.like(StringUtils.hasText(keyword), Drama::getTitle, keyword)
|
||||
.orderByDesc(Drama::getSort)
|
||||
.orderByDesc(Drama::getId);
|
||||
String subQuery = "SELECT drama_id FROM drama_playlist_category_drama WHERE category_id = " + id;
|
||||
if (bound) {
|
||||
wrapper.inSql(Drama::getId, subQuery);
|
||||
} else {
|
||||
wrapper.notInSql(Drama::getId, subQuery);
|
||||
}
|
||||
IPage<Drama> result = dramaMapper.selectPage(page, wrapper);
|
||||
return new PageResult<>(result.getTotal(),
|
||||
result.getRecords().stream().map(this::toBrief).toList());
|
||||
}
|
||||
|
||||
private Map<Long, Integer> countDramas() {
|
||||
Map<Long, Integer> counts = new HashMap<>();
|
||||
categoryDramaMapper.selectMaps(new QueryWrapper<DramaPlaylistCategoryDrama>()
|
||||
.select("category_id AS cid", "COUNT(*) AS cnt")
|
||||
.groupBy("category_id"))
|
||||
.forEach(row -> {
|
||||
Object cid = row.get("cid");
|
||||
Object cnt = row.get("cnt");
|
||||
if (cid instanceof Number c && cnt instanceof Number n) {
|
||||
counts.put(c.longValue(), n.intValue());
|
||||
}
|
||||
});
|
||||
return counts;
|
||||
}
|
||||
|
||||
private DramaPlaylistCategory getExisting(Long id) {
|
||||
DramaPlaylistCategory entity = id == null ? null : categoryMapper.selectById(id);
|
||||
if (entity == null) {
|
||||
throw new ServiceException(ErrorCode.DATA_NOT_FOUND, "剧单分类不存在");
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
private void ensureNameAvailable(String name, Long excludeId) {
|
||||
Long count = categoryMapper.selectCount(Wrappers.<DramaPlaylistCategory>lambdaQuery()
|
||||
.eq(DramaPlaylistCategory::getName, name.trim())
|
||||
.ne(excludeId != null, DramaPlaylistCategory::getId, excludeId));
|
||||
if (count != null && count > 0) {
|
||||
throw new ServiceException(ErrorCode.DATA_EXISTS, "剧单分类名称已存在");
|
||||
}
|
||||
}
|
||||
|
||||
private DramaBriefVO toBrief(Drama drama) {
|
||||
DramaBriefVO vo = new DramaBriefVO();
|
||||
vo.setId(drama.getId());
|
||||
vo.setTitle(drama.getTitle());
|
||||
vo.setCover(drama.getCoverUrl());
|
||||
vo.setStatus(drama.getStatus());
|
||||
vo.setTotalEpisodes(drama.getTotalEpisodes());
|
||||
vo.setSort(drama.getSort());
|
||||
vo.setUpdatedAt(drama.getUpdatedAt());
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
package com.fruitshorts.content.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/** 短剧精简信息:分类关联页列表用。 */
|
||||
@Data
|
||||
public class DramaBriefVO {
|
||||
|
||||
private Long id;
|
||||
private String title;
|
||||
private String cover;
|
||||
private String status;
|
||||
private Integer totalEpisodes;
|
||||
private Integer sort;
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
package com.fruitshorts.content.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/** 分类列表项:带关联短剧数量。 */
|
||||
@Data
|
||||
public class DramaClassificationRespVO {
|
||||
|
||||
private Long id;
|
||||
private String name;
|
||||
private Integer sort;
|
||||
private Integer status;
|
||||
private Integer dramaCount;
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
package com.fruitshorts.content.vo;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
/** 分类新增/编辑请求体。 */
|
||||
@Data
|
||||
public class DramaClassificationSaveReqVO {
|
||||
|
||||
@NotBlank(message = "请填写分类名称")
|
||||
private String name;
|
||||
|
||||
private Integer sort;
|
||||
private Integer status;
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
package com.fruitshorts.content.vo;
|
||||
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** 批量操作短剧 id 的通用请求体。 */
|
||||
@Data
|
||||
public class DramaIdsReqVO {
|
||||
|
||||
@NotEmpty(message = "请选择短剧")
|
||||
private List<Long> dramaIds;
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
package com.fruitshorts.content.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/** 剧单分类列表项:带该分类下的短剧数量。 */
|
||||
@Data
|
||||
public class DramaPlaylistCategoryRespVO {
|
||||
|
||||
private Long id;
|
||||
private String name;
|
||||
private String description;
|
||||
private Integer sort;
|
||||
private Integer status;
|
||||
/** 该分类下已关联的短剧数量 */
|
||||
private Integer dramaCount;
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
package com.fruitshorts.content.vo;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
/** 剧单分类新增/编辑请求体。 */
|
||||
@Data
|
||||
public class DramaPlaylistCategorySaveReqVO {
|
||||
|
||||
@NotBlank(message = "请填写剧单分类名称")
|
||||
private String name;
|
||||
|
||||
@Size(max = 255, message = "描述最多 255 个字符")
|
||||
private String description;
|
||||
|
||||
private Integer sort;
|
||||
private Integer status;
|
||||
}
|
||||
+12
-2
@@ -57,6 +57,8 @@ INSERT INTO sys_menu (id, parent_id, title, route_path, route_name, component, r
|
||||
(5, 3, '剧集管理', 'episodes', 'episodes', 'views/contentManagement/EpisodeManagementView', '', '', 'episode:list', 'MENU', 0, 0, 2),
|
||||
(12, 3, '轮播图管理', 'banners', 'banners', 'views/contentManagement/BannerManagementView', '', '', 'banner:list', 'MENU', 0, 0, 3),
|
||||
(13, 3, '类型管理', 'categories', 'categories', 'views/contentManagement/CategoryManagementView','', '', 'drama:list', 'MENU', 0, 0, 4),
|
||||
(36, 3, '分类管理', 'category-dramas','category-dramas','views/contentManagement/CategoryDramaView', '', '', 'drama:list', 'MENU', 0, 0, 5),
|
||||
(37, 3, '剧单管理', 'drama-playlists','drama-playlists','views/contentManagement/DramaPlaylistView', '', '', 'drama:list', 'MENU', 0, 0, 6),
|
||||
(6, 0, '后台管理', '/system', 'System', '#', '/system/access', 'material-symbols:settings-rounded', 'system:role', 'CATALOG', 1, 0, 3),
|
||||
(7, 6, '角色权限配置', 'access', 'access', 'views/permission/RoleManagementView', '', '', 'system:role', 'MENU', 0, 0, 1),
|
||||
(8, 6, '管理员用户列表','admin-users','admin-users', 'views/userManagement/AdminUsersView', '', '', 'system:admin-user','MENU', 0, 0, 2),
|
||||
@@ -77,9 +79,17 @@ ON CONFLICT (id) DO UPDATE SET parent_id = EXCLUDED.parent_id, title = EXCLUDED.
|
||||
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_menu (id, parent_id, title, route_path, route_name, component, redirect, icon,
|
||||
permission_code, type, always_show, affix, sort) VALUES
|
||||
(35, 6, '转码设置', 'transcode-settings', 'transcode-settings', 'views/systemManagement/TranscodeSettingsView', '', '', 'episode:upload', 'MENU', 0, 0, 7)
|
||||
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;
|
||||
|
||||
-- K23:二级菜单统一只显示文字,历史图标值也一并清空。
|
||||
UPDATE sys_menu SET icon = '' WHERE type = 'MENU';
|
||||
|
||||
-- ---------- 角色-权限 ----------
|
||||
INSERT INTO sys_role_permission (id, role_id, permission_id)
|
||||
SELECT r.id * 1000000 + p.id, r.id, p.id FROM sys_role r CROSS JOIN sys_permission p WHERE r.code = 'super_admin'
|
||||
@@ -112,7 +122,7 @@ ON CONFLICT DO NOTHING;
|
||||
|
||||
INSERT INTO sys_role_menu (id, role_id, menu_id)
|
||||
SELECT r.id * 1000000 + m.id, r.id, m.id FROM sys_role r CROSS JOIN sys_menu m
|
||||
WHERE r.code IN ('content_operator','content_reviewer','data_analyst') AND m.id IN (1,2,3,4,5,12,13)
|
||||
WHERE r.code IN ('content_operator','content_reviewer','data_analyst') AND m.id IN (1,2,3,4,5,12,13,36,37)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- ---------- 管理员(密码均为 admin123) ----------
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
-- Author: fruit.com.cn
|
||||
-- 后台「分类管理」菜单与分类-短剧关联页(幂等,仅提交文件,由发布流程执行)
|
||||
|
||||
INSERT INTO sys_menu (id, parent_id, title, route_path, route_name, component, redirect, icon,
|
||||
permission_code, type, always_show, affix, sort) VALUES
|
||||
(36, 3, '分类管理', 'category-dramas', 'category-dramas',
|
||||
'views/contentManagement/CategoryDramaView', '', '',
|
||||
'drama:list', 'MENU', 0, 0, 5)
|
||||
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 (id, role_id, menu_id)
|
||||
SELECT r.id * 1000000 + m.id, r.id, m.id
|
||||
FROM sys_role r
|
||||
CROSS JOIN sys_menu m
|
||||
WHERE r.code IN ('super_admin', 'content_operator', 'content_reviewer', 'data_analyst')
|
||||
AND m.id = 36
|
||||
ON CONFLICT DO NOTHING;
|
||||
@@ -0,0 +1,31 @@
|
||||
-- Author: fruit.com.cn
|
||||
-- 运营分类(与「类型」drama_category 是两个概念):分类可自定义增删,并关联多部短剧
|
||||
-- 幂等,仅提交文件,由发布流程执行
|
||||
|
||||
CREATE TABLE IF NOT EXISTS drama_classification (
|
||||
id BIGINT PRIMARY KEY,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
sort INTEGER NOT NULL DEFAULT 0,
|
||||
status SMALLINT NOT NULL DEFAULT 1,
|
||||
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_drama_classification_name ON drama_classification (name);
|
||||
COMMENT ON TABLE drama_classification IS '运营分类;与短剧类型(drama_category)相互独立';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS drama_classification_drama (
|
||||
id BIGINT PRIMARY KEY,
|
||||
classification_id BIGINT NOT NULL,
|
||||
drama_id BIGINT NOT NULL,
|
||||
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_classification_drama
|
||||
ON drama_classification_drama (classification_id, drama_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_classification_drama_drama
|
||||
ON drama_classification_drama (drama_id);
|
||||
COMMENT ON TABLE drama_classification_drama IS '分类与短剧的关联关系';
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_drama_classification_updated_at ON drama_classification;
|
||||
CREATE TRIGGER trg_drama_classification_updated_at
|
||||
BEFORE UPDATE ON drama_classification
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
@@ -0,0 +1,21 @@
|
||||
-- Author: fruit.com.cn
|
||||
-- 后台「剧单管理」菜单(幂等,仅提交文件,由发布流程执行)
|
||||
|
||||
INSERT INTO sys_menu (id, parent_id, title, route_path, route_name, component, redirect, icon,
|
||||
permission_code, type, always_show, affix, sort) VALUES
|
||||
(37, 3, '剧单管理', 'drama-playlists', 'drama-playlists',
|
||||
'views/contentManagement/DramaPlaylistView', '', '',
|
||||
'drama:list', 'MENU', 0, 0, 6)
|
||||
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 (id, role_id, menu_id)
|
||||
SELECT r.id * 1000000 + m.id, r.id, m.id
|
||||
FROM sys_role r
|
||||
CROSS JOIN sys_menu m
|
||||
WHERE r.code IN ('super_admin', 'content_operator', 'content_reviewer', 'data_analyst')
|
||||
AND m.id = 37
|
||||
ON CONFLICT DO NOTHING;
|
||||
@@ -0,0 +1,31 @@
|
||||
-- Author: fruit.com.cn
|
||||
-- 剧单:运营编排的短剧集合,归属某个分类,并可关联多部短剧
|
||||
-- 幂等,仅提交文件,由发布流程执行
|
||||
|
||||
CREATE TABLE IF NOT EXISTS drama_playlist (
|
||||
id BIGINT PRIMARY KEY,
|
||||
classification_id BIGINT NOT NULL DEFAULT 0,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
sort INTEGER NOT NULL DEFAULT 0,
|
||||
status SMALLINT NOT NULL DEFAULT 1,
|
||||
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_drama_playlist_name ON drama_playlist (name);
|
||||
CREATE INDEX IF NOT EXISTS idx_drama_playlist_classification ON drama_playlist (classification_id);
|
||||
COMMENT ON TABLE drama_playlist IS '剧单;归属分类,可关联多部短剧';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS drama_playlist_drama (
|
||||
id BIGINT PRIMARY KEY,
|
||||
playlist_id BIGINT NOT NULL,
|
||||
drama_id BIGINT NOT NULL,
|
||||
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_playlist_drama ON drama_playlist_drama (playlist_id, drama_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_playlist_drama_drama ON drama_playlist_drama (drama_id);
|
||||
COMMENT ON TABLE drama_playlist_drama IS '剧单与短剧的关联关系';
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_drama_playlist_updated_at ON drama_playlist;
|
||||
CREATE TRIGGER trg_drama_playlist_updated_at
|
||||
BEFORE UPDATE ON drama_playlist
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
@@ -0,0 +1,18 @@
|
||||
-- Author: fruit.com.cn
|
||||
-- 剧单分类直接关联短剧(不再有「剧单」这一层)
|
||||
-- 幂等,仅提交文件,由发布流程执行
|
||||
|
||||
CREATE TABLE IF NOT EXISTS drama_playlist_category_drama (
|
||||
id BIGINT PRIMARY KEY,
|
||||
category_id BIGINT NOT NULL,
|
||||
drama_id BIGINT NOT NULL,
|
||||
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_playlist_category_drama
|
||||
ON drama_playlist_category_drama (category_id, drama_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_playlist_category_drama_drama
|
||||
ON drama_playlist_category_drama (drama_id);
|
||||
COMMENT ON TABLE drama_playlist_category_drama IS '剧单分类与短剧的关联关系';
|
||||
|
||||
DROP TABLE IF EXISTS drama_playlist_drama;
|
||||
DROP TABLE IF EXISTS drama_playlist;
|
||||
@@ -0,0 +1,31 @@
|
||||
-- Author: fruit.com.cn
|
||||
-- 剧单专属分类(与「分类管理」的运营分类无关),并调整剧单归属字段
|
||||
-- 幂等,仅提交文件,由发布流程执行
|
||||
|
||||
CREATE TABLE IF NOT EXISTS drama_playlist_category (
|
||||
id BIGINT PRIMARY KEY,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
sort INTEGER NOT NULL DEFAULT 0,
|
||||
status SMALLINT NOT NULL DEFAULT 1,
|
||||
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_drama_playlist_category_name
|
||||
ON drama_playlist_category (name);
|
||||
COMMENT ON TABLE drama_playlist_category IS '剧单专属分类;与运营分类(drama_classification)相互独立';
|
||||
|
||||
ALTER TABLE drama_playlist DROP COLUMN IF EXISTS classification_id;
|
||||
ALTER TABLE drama_playlist ADD COLUMN IF NOT EXISTS playlist_category_id BIGINT NOT NULL DEFAULT 0;
|
||||
CREATE INDEX IF NOT EXISTS idx_drama_playlist_category
|
||||
ON drama_playlist (playlist_category_id);
|
||||
COMMENT ON COLUMN drama_playlist.playlist_category_id IS '剧单分类;0 表示未分类';
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_drama_playlist_category_updated_at ON drama_playlist_category;
|
||||
CREATE TRIGGER trg_drama_playlist_category_updated_at
|
||||
BEFORE UPDATE ON drama_playlist_category
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
-- Author: fruit.com.cn
|
||||
-- 剧单分类增加描述字段(幂等)
|
||||
|
||||
ALTER TABLE drama_playlist_category ADD COLUMN IF NOT EXISTS description VARCHAR(255) NOT NULL DEFAULT '';
|
||||
COMMENT ON COLUMN drama_playlist_category.description IS '分类描述';
|
||||
+58
-1
@@ -869,9 +869,64 @@ CREATE INDEX IF NOT EXISTS idx_audit_log_biz ON audit_log (biz_type, biz_id, cre
|
||||
COMMENT ON TABLE audit_log IS '业务对象变更审计(支付、权益、定价等敏感操作)';
|
||||
|
||||
-- ============================================================
|
||||
-- 九、updated_at 触发器(与已有表保持一致)
|
||||
-- 八之二、运营分类(与短剧类型 drama_category 相互独立)
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS drama_classification (
|
||||
id BIGINT PRIMARY KEY,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
sort INTEGER NOT NULL DEFAULT 0,
|
||||
status SMALLINT NOT NULL DEFAULT 1,
|
||||
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_drama_classification_name ON drama_classification (name);
|
||||
COMMENT ON TABLE drama_classification IS '运营分类;与短剧类型(drama_category)相互独立';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS drama_classification_drama (
|
||||
id BIGINT PRIMARY KEY,
|
||||
classification_id BIGINT NOT NULL,
|
||||
drama_id BIGINT NOT NULL,
|
||||
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_classification_drama
|
||||
ON drama_classification_drama (classification_id, drama_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_classification_drama_drama
|
||||
ON drama_classification_drama (drama_id);
|
||||
COMMENT ON TABLE drama_classification_drama IS '分类与短剧的关联关系';
|
||||
|
||||
-- ============================================================
|
||||
-- 八之三、剧单(剧单有自己的一套分类,与运营分类无关)
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS drama_playlist_category (
|
||||
id BIGINT PRIMARY KEY,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
description VARCHAR(255) NOT NULL DEFAULT '',
|
||||
sort INTEGER NOT NULL DEFAULT 0,
|
||||
status SMALLINT NOT NULL DEFAULT 1,
|
||||
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_drama_playlist_category_name
|
||||
ON drama_playlist_category (name);
|
||||
COMMENT ON TABLE drama_playlist_category IS '剧单专属分类;与运营分类(drama_classification)相互独立';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS drama_playlist_category_drama (
|
||||
id BIGINT PRIMARY KEY,
|
||||
category_id BIGINT NOT NULL,
|
||||
drama_id BIGINT NOT NULL,
|
||||
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_playlist_category_drama
|
||||
ON drama_playlist_category_drama (category_id, drama_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_playlist_category_drama_drama
|
||||
ON drama_playlist_category_drama (drama_id);
|
||||
COMMENT ON TABLE drama_playlist_category_drama IS '剧单分类与短剧的关联关系';
|
||||
|
||||
-- ============================================================
|
||||
-- 九、updated_at 触发器(与已有表保持一致)
|
||||
-- ============================================================
|
||||
DO $$
|
||||
DECLARE
|
||||
t TEXT;
|
||||
@@ -885,6 +940,8 @@ BEGIN
|
||||
'reconcile_task', 'creator', 'creator_document', 'content_submission',
|
||||
'creator_stat_daily', 'creator_settlement',
|
||||
'transcode_task', 'media_rendition', 'media_thumbnail',
|
||||
'drama_classification',
|
||||
'drama_playlist_category',
|
||||
'ops_notice', 'ops_daily_stat'
|
||||
]
|
||||
LOOP
|
||||
|
||||
Reference in new issue
Block a user