feat(content): 短剧分类和标签支持多语言
This commit is contained in:
1 parent
5c20cf1ab9
commit
e60c47123c
34 files changed
+1071
-53
No files matched your search
+31
@@ -0,0 +1,31 @@
|
||||
// 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("content_tag")
|
||||
public class ContentTag {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
/** 中文名称。 */
|
||||
private String name;
|
||||
private String nameEn;
|
||||
private String nameId;
|
||||
/** 已选语言的名称。 */
|
||||
private String nameLocales;
|
||||
/** 角标背景色,#RRGGBB。 */
|
||||
private String bgColor;
|
||||
private Integer sort;
|
||||
private Integer status;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -26,6 +26,10 @@ public class Drama {
|
||||
private String categoryIds;
|
||||
private String genre;
|
||||
private String synopsis;
|
||||
/** 标题语言表,JSON。 */
|
||||
private String titleLocales;
|
||||
/** 简介语言表,JSON。 */
|
||||
private String synopsisLocales;
|
||||
private String coverUrl;
|
||||
private String horizontalCoverUrl;
|
||||
private String language;
|
||||
@@ -33,6 +37,8 @@ public class Drama {
|
||||
private String dramaType;
|
||||
/** 内容类型多选,逗号分隔(ANIME/LIVE),drama_type 保留第一个 */
|
||||
private String dramaTypes;
|
||||
/** 单选标签,对应 content_tag.id。 */
|
||||
private Long tagId;
|
||||
private Integer totalEpisodes;
|
||||
private Integer freeEpisodes;
|
||||
private Integer unlockCoin;
|
||||
|
||||
+4
@@ -14,6 +14,10 @@ public class DramaCategory {
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
private String name;
|
||||
private String nameEn;
|
||||
private String nameId;
|
||||
/** 已选语言的名称。 */
|
||||
private String nameLocales;
|
||||
private Integer sort;
|
||||
private Integer status;
|
||||
}
|
||||
+5
@@ -16,7 +16,12 @@ public class DramaClassification {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
/** 中文名称。 */
|
||||
private String name;
|
||||
private String nameEn;
|
||||
private String nameId;
|
||||
/** 已选语言的名称。 */
|
||||
private String nameLocales;
|
||||
private Integer sort;
|
||||
private Integer status;
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
+10
-1
@@ -16,9 +16,18 @@ public class DramaPlaylistCategory {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
/** 中文名称,列表和唯一性都用这一列。 */
|
||||
private String name;
|
||||
/** 分类描述,便于运营说明该分类的用途 */
|
||||
private String nameEn;
|
||||
private String nameId;
|
||||
/** 中文描述。 */
|
||||
private String description;
|
||||
private String descriptionEn;
|
||||
private String descriptionId;
|
||||
/** 已选语言的名称。 */
|
||||
private String nameLocales;
|
||||
/** 已选语言的描述。 */
|
||||
private String descriptionLocales;
|
||||
private Integer sort;
|
||||
private Integer status;
|
||||
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.ContentTag;
|
||||
|
||||
public interface ContentTagMapper extends BaseMapper<ContentTag> {
|
||||
}
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
package com.fruitshorts.content.service;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fruitshorts.common.exception.ErrorCode;
|
||||
import com.fruitshorts.common.exception.ServiceException;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/** 内容名称的可选语言。中文必填,英文和印尼语按勾选保存。 */
|
||||
public final class ContentLocaleTexts {
|
||||
|
||||
public static final String ZH = "zh";
|
||||
public static final String EN = "en";
|
||||
public static final String ID = "id";
|
||||
|
||||
private static final int NAME_LIMIT = 64;
|
||||
private static final int TITLE_LIMIT = 80;
|
||||
private static final int DESCRIPTION_LIMIT = 255;
|
||||
private static final int SYNOPSIS_LIMIT = 1000;
|
||||
private static final List<String> CODES = List.of(ZH, EN, ID);
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
private static final TypeReference<Map<String, String>> MAP_TYPE = new TypeReference<>() {
|
||||
};
|
||||
|
||||
private ContentLocaleTexts() {
|
||||
}
|
||||
|
||||
public static Map<String, String> names(Map<String, String> locales, String name,
|
||||
String nameEn, String nameId) {
|
||||
Map<String, String> source = locales == null ? legacy(name, nameEn, nameId) : locales;
|
||||
return normalize(source, NAME_LIMIT, "名称");
|
||||
}
|
||||
|
||||
public static Map<String, String> titles(Map<String, String> locales, String title) {
|
||||
Map<String, String> source = locales == null ? legacy(title, null, null) : locales;
|
||||
return normalize(source, TITLE_LIMIT, "名称");
|
||||
}
|
||||
|
||||
public static Map<String, String> descriptions(Map<String, String> locales, Map<String, String> names,
|
||||
String description, String descriptionEn,
|
||||
String descriptionId) {
|
||||
return optional(locales, names, description, descriptionEn, descriptionId, DESCRIPTION_LIMIT, "描述");
|
||||
}
|
||||
|
||||
public static Map<String, String> synopses(Map<String, String> locales, Map<String, String> titles,
|
||||
String synopsis) {
|
||||
return optional(locales, titles, synopsis, null, null, SYNOPSIS_LIMIT, "简介");
|
||||
}
|
||||
|
||||
private static Map<String, String> optional(Map<String, String> locales, Map<String, String> required,
|
||||
String zh, String en, String id, int limit, String field) {
|
||||
Map<String, String> source = locales == null ? legacy(zh, en, id) : locales;
|
||||
Map<String, String> result = new LinkedHashMap<>();
|
||||
for (String code : required.keySet()) {
|
||||
String value = trim(source.get(code));
|
||||
if (value.length() > limit) {
|
||||
throw tooLong(code, field, limit);
|
||||
}
|
||||
if (!value.isEmpty()) {
|
||||
result.put(code, value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Map<String, String> read(String json, String zh, String en, String id) {
|
||||
Map<String, String> parsed = parse(json);
|
||||
Map<String, String> result = new LinkedHashMap<>();
|
||||
put(result, ZH, first(parsed.get(ZH), zh));
|
||||
put(result, EN, first(parsed.get(EN), en));
|
||||
put(result, ID, first(parsed.get(ID), id));
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String json(Map<String, String> locales) {
|
||||
try {
|
||||
return MAPPER.writeValueAsString(locales == null ? Map.of() : locales);
|
||||
} catch (Exception ex) {
|
||||
throw new ServiceException(ErrorCode.BAD_REQUEST, "语言内容无法保存");
|
||||
}
|
||||
}
|
||||
|
||||
public static String value(Map<String, String> locales, String code) {
|
||||
String text = locales == null ? null : locales.get(code);
|
||||
return text == null ? "" : text;
|
||||
}
|
||||
|
||||
private static Map<String, String> normalize(Map<String, String> source, int limit, String field) {
|
||||
Map<String, String> result = new LinkedHashMap<>();
|
||||
for (String code : CODES) {
|
||||
if (!source.containsKey(code)) {
|
||||
continue;
|
||||
}
|
||||
String value = trim(source.get(code));
|
||||
if (value.isEmpty()) {
|
||||
throw new ServiceException(ErrorCode.BAD_REQUEST, "请填写" + label(code) + field);
|
||||
}
|
||||
if (value.length() > limit) {
|
||||
throw tooLong(code, field, limit);
|
||||
}
|
||||
result.put(code, value);
|
||||
}
|
||||
rejectUnknown(source);
|
||||
if (!result.containsKey(ZH)) {
|
||||
throw new ServiceException(ErrorCode.BAD_REQUEST, "请选择中文并填写中文" + field);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Map<String, String> legacy(String zh, String en, String id) {
|
||||
Map<String, String> source = new LinkedHashMap<>();
|
||||
source.put(ZH, zh);
|
||||
if (!trim(en).isEmpty()) {
|
||||
source.put(EN, en);
|
||||
}
|
||||
if (!trim(id).isEmpty()) {
|
||||
source.put(ID, id);
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
private static void rejectUnknown(Map<String, String> source) {
|
||||
for (String code : source.keySet()) {
|
||||
if (!CODES.contains(code)) {
|
||||
throw new ServiceException(ErrorCode.BAD_REQUEST, "不支持的语言");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, String> parse(String json) {
|
||||
if (json == null || json.isBlank()) {
|
||||
return Map.of();
|
||||
}
|
||||
try {
|
||||
Map<String, String> parsed = MAPPER.readValue(json, MAP_TYPE);
|
||||
return parsed == null ? Map.of() : parsed;
|
||||
} catch (Exception ex) {
|
||||
return Map.of();
|
||||
}
|
||||
}
|
||||
|
||||
private static void put(Map<String, String> target, String code, String value) {
|
||||
String text = trim(value);
|
||||
if (!text.isEmpty()) {
|
||||
target.put(code, text);
|
||||
}
|
||||
}
|
||||
|
||||
private static String first(String preferred, String fallback) {
|
||||
return trim(preferred).isEmpty() ? fallback : preferred;
|
||||
}
|
||||
|
||||
private static String trim(String value) {
|
||||
return value == null ? "" : value.trim();
|
||||
}
|
||||
|
||||
private static String label(String code) {
|
||||
return switch (code) {
|
||||
case EN -> "英文";
|
||||
case ID -> "印尼语";
|
||||
default -> "中文";
|
||||
};
|
||||
}
|
||||
|
||||
private static ServiceException tooLong(String code, String field, int limit) {
|
||||
return new ServiceException(ErrorCode.BAD_REQUEST, label(code) + field + "最多 " + limit + " 个字符");
|
||||
}
|
||||
}
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
package com.fruitshorts.content.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.fruitshorts.common.exception.ErrorCode;
|
||||
import com.fruitshorts.common.exception.ServiceException;
|
||||
import com.fruitshorts.content.dal.entity.ContentTag;
|
||||
import com.fruitshorts.content.dal.entity.Drama;
|
||||
import com.fruitshorts.content.dal.mapper.ContentTagMapper;
|
||||
import com.fruitshorts.content.dal.mapper.DramaMapper;
|
||||
import com.fruitshorts.content.vo.ContentTagRespVO;
|
||||
import com.fruitshorts.content.vo.ContentTagSaveReqVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/** 短剧标签。名称先选语言,中文必填,英文和印尼语按勾选填写。 */
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ContentTagService {
|
||||
|
||||
private final ContentTagMapper tagMapper;
|
||||
private final DramaMapper dramaMapper;
|
||||
|
||||
public List<ContentTagRespVO> list() {
|
||||
return tagMapper.selectList(Wrappers.<ContentTag>lambdaQuery()
|
||||
.orderByAsc(ContentTag::getSort)
|
||||
.orderByDesc(ContentTag::getId))
|
||||
.stream()
|
||||
.map(this::toResp)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public List<ContentTagRespVO> listEnabled() {
|
||||
return tagMapper.selectList(Wrappers.<ContentTag>lambdaQuery()
|
||||
.eq(ContentTag::getStatus, 1)
|
||||
.orderByAsc(ContentTag::getSort)
|
||||
.orderByDesc(ContentTag::getId))
|
||||
.stream()
|
||||
.map(this::toResp)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public Long create(ContentTagSaveReqVO req) {
|
||||
Map<String, String> locales = namesOf(req);
|
||||
ensureNameAvailable(locales.get(ContentLocaleTexts.ZH), null);
|
||||
ContentTag entity = new ContentTag();
|
||||
writeNames(entity, locales);
|
||||
entity.setBgColor(normalizeColor(req.getBgColor()));
|
||||
Long count = tagMapper.selectCount(null);
|
||||
entity.setSort(req.getSort() == null
|
||||
? (count == null ? 0 : count.intValue()) + 1
|
||||
: req.getSort());
|
||||
entity.setStatus(req.getStatus() == null ? 1 : req.getStatus());
|
||||
tagMapper.insert(entity);
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
public void update(Long id, ContentTagSaveReqVO req) {
|
||||
ContentTag entity = getExisting(id);
|
||||
Map<String, String> locales = namesOf(req);
|
||||
ensureNameAvailable(locales.get(ContentLocaleTexts.ZH), id);
|
||||
writeNames(entity, locales);
|
||||
if (req.getBgColor() != null) {
|
||||
entity.setBgColor(normalizeColor(req.getBgColor()));
|
||||
}
|
||||
if (req.getSort() != null) {
|
||||
entity.setSort(req.getSort());
|
||||
}
|
||||
if (req.getStatus() != null) {
|
||||
entity.setStatus(req.getStatus());
|
||||
}
|
||||
tagMapper.updateById(entity);
|
||||
}
|
||||
|
||||
public void delete(Long id) {
|
||||
getExisting(id);
|
||||
dramaMapper.update(null, Wrappers.<Drama>lambdaUpdate()
|
||||
.eq(Drama::getTagId, id)
|
||||
.set(Drama::getTagId, null));
|
||||
tagMapper.deleteById(id);
|
||||
}
|
||||
|
||||
/** 短剧保存时校验标签存在且启用。空 id 表示不选。 */
|
||||
public void requireEnabled(Long id) {
|
||||
if (id == null || id <= 0L) {
|
||||
return;
|
||||
}
|
||||
ContentTag tag = tagMapper.selectById(id);
|
||||
if (tag == null || tag.getStatus() == null || tag.getStatus() != 1) {
|
||||
throw new ServiceException(ErrorCode.DATA_NOT_FOUND, "标签不存在或已停用");
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> namesOf(ContentTagSaveReqVO req) {
|
||||
return ContentLocaleTexts.names(req.getLocales(), req.getName(), req.getNameEn(), req.getNameId());
|
||||
}
|
||||
|
||||
private void writeNames(ContentTag entity, Map<String, String> locales) {
|
||||
entity.setName(locales.get(ContentLocaleTexts.ZH));
|
||||
entity.setNameEn(ContentLocaleTexts.value(locales, ContentLocaleTexts.EN));
|
||||
entity.setNameId(ContentLocaleTexts.value(locales, ContentLocaleTexts.ID));
|
||||
entity.setNameLocales(ContentLocaleTexts.json(locales));
|
||||
}
|
||||
|
||||
private String normalizeColor(String color) {
|
||||
if (color == null || color.isBlank()) {
|
||||
return "#E5484D";
|
||||
}
|
||||
return color.trim().toUpperCase();
|
||||
}
|
||||
|
||||
private ContentTag getExisting(Long id) {
|
||||
ContentTag entity = id == null ? null : tagMapper.selectById(id);
|
||||
if (entity == null) {
|
||||
throw new ServiceException(ErrorCode.DATA_NOT_FOUND, "标签不存在");
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
private void ensureNameAvailable(String name, Long excludeId) {
|
||||
Long count = tagMapper.selectCount(Wrappers.<ContentTag>lambdaQuery()
|
||||
.eq(ContentTag::getName, name.trim())
|
||||
.ne(excludeId != null, ContentTag::getId, excludeId));
|
||||
if (count != null && count > 0) {
|
||||
throw new ServiceException(ErrorCode.DATA_EXISTS, "标签名称已存在");
|
||||
}
|
||||
}
|
||||
|
||||
private ContentTagRespVO toResp(ContentTag tag) {
|
||||
ContentTagRespVO vo = new ContentTagRespVO();
|
||||
vo.setId(tag.getId());
|
||||
Map<String, String> locales = ContentLocaleTexts.read(
|
||||
tag.getNameLocales(), tag.getName(), tag.getNameEn(), tag.getNameId());
|
||||
vo.setName(locales.get(ContentLocaleTexts.ZH));
|
||||
vo.setNameEn(ContentLocaleTexts.value(locales, ContentLocaleTexts.EN));
|
||||
vo.setNameId(ContentLocaleTexts.value(locales, ContentLocaleTexts.ID));
|
||||
vo.setLocales(locales);
|
||||
vo.setBgColor(tag.getBgColor() == null || tag.getBgColor().isBlank() ? "#E5484D" : tag.getBgColor());
|
||||
vo.setSort(tag.getSort());
|
||||
vo.setStatus(tag.getStatus());
|
||||
return vo;
|
||||
}
|
||||
|
||||
/** 按 id 批量取出标签,供短剧列表带名称和背景色。 */
|
||||
public Map<Long, ContentTag> findByIds(Collection<Long> ids) {
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
Map<Long, ContentTag> result = new HashMap<>();
|
||||
for (ContentTag tag : tagMapper.selectBatchIds(ids)) {
|
||||
if (tag != null && tag.getId() != null) {
|
||||
result.put(tag.getId(), tag);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 按 id 批量取出标签语言表,供短剧列表拼卡片。 */
|
||||
public Map<Long, Map<String, String>> localeMap(Collection<Long> ids) {
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
Map<Long, Map<String, String>> result = new HashMap<>();
|
||||
for (ContentTag tag : tagMapper.selectBatchIds(ids)) {
|
||||
if (tag == null || tag.getId() == null) {
|
||||
continue;
|
||||
}
|
||||
result.put(tag.getId(), ContentLocaleTexts.read(
|
||||
tag.getNameLocales(), tag.getName(), tag.getNameEn(), tag.getNameId()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+61
-21
@@ -14,6 +14,7 @@ import com.fruitshorts.content.vo.DramaCategorySaveReqVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -30,21 +31,15 @@ public class DramaCategoryService {
|
||||
Map<Long, Integer> counts = countDramasByCategory();
|
||||
return dramaCategoryMapper.selectList(
|
||||
Wrappers.<DramaCategory>lambdaQuery().orderByAsc(DramaCategory::getSort))
|
||||
.stream().map(category -> {
|
||||
DramaCategoryRespVO vo = new DramaCategoryRespVO();
|
||||
vo.setId(category.getId());
|
||||
vo.setName(category.getName());
|
||||
vo.setSort(category.getSort());
|
||||
vo.setStatus(category.getStatus());
|
||||
vo.setDramaCount(counts.getOrDefault(category.getId(), 0));
|
||||
return vo;
|
||||
}).toList();
|
||||
.stream().map(category -> toResp(category, counts.getOrDefault(category.getId(), 0)))
|
||||
.toList();
|
||||
}
|
||||
|
||||
public Long create(DramaCategorySaveReqVO req) {
|
||||
ensureNameAvailable(req.getName(), null);
|
||||
Map<String, String> locales = namesOf(req);
|
||||
ensureNameAvailable(locales.get(ContentLocaleTexts.ZH), null);
|
||||
DramaCategory category = new DramaCategory();
|
||||
category.setName(req.getName().trim());
|
||||
writeNames(category, locales);
|
||||
Long count = dramaCategoryMapper.selectCount(null);
|
||||
category.setSort(req.getSort() == null
|
||||
? (count == null ? 0 : count.intValue()) + 1
|
||||
@@ -61,20 +56,14 @@ public class DramaCategoryService {
|
||||
public List<DramaCategoryRespVO> listBasic() {
|
||||
return dramaCategoryMapper.selectList(
|
||||
Wrappers.<DramaCategory>lambdaQuery().orderByAsc(DramaCategory::getSort))
|
||||
.stream().map(category -> {
|
||||
DramaCategoryRespVO vo = new DramaCategoryRespVO();
|
||||
vo.setId(category.getId());
|
||||
vo.setName(category.getName());
|
||||
vo.setSort(category.getSort());
|
||||
vo.setStatus(category.getStatus());
|
||||
return vo;
|
||||
}).toList();
|
||||
.stream().map(category -> toResp(category, null)).toList();
|
||||
}
|
||||
|
||||
public void update(Long id, DramaCategorySaveReqVO req) {
|
||||
DramaCategory category = getExisting(id);
|
||||
ensureNameAvailable(req.getName(), id);
|
||||
category.setName(req.getName().trim());
|
||||
Map<String, String> locales = namesOf(req);
|
||||
ensureNameAvailable(locales.get(ContentLocaleTexts.ZH), id);
|
||||
writeNames(category, locales);
|
||||
if (req.getSort() != null) {
|
||||
category.setSort(req.getSort());
|
||||
}
|
||||
@@ -126,4 +115,55 @@ public class DramaCategoryService {
|
||||
return dramaCategoryMapper.selectList(null).stream()
|
||||
.collect(Collectors.toMap(DramaCategory::getId, DramaCategory::getName, (a, b) -> a));
|
||||
}
|
||||
|
||||
/** 类型 id 到已选语言名称。C 端卡片按当前语言取其中一项。 */
|
||||
public Map<Long, Map<String, String>> localeMap() {
|
||||
Map<Long, Map<String, String>> result = new HashMap<>();
|
||||
for (DramaCategory category : dramaCategoryMapper.selectList(null)) {
|
||||
result.put(category.getId(), readLocales(category));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 一部短剧选中的类型,按保存顺序返回语言表。 */
|
||||
public List<Map<String, String>> localesForDrama(Drama drama, Map<Long, Map<String, String>> localesById) {
|
||||
List<Map<String, String>> result = new ArrayList<>();
|
||||
for (Long id : DramaMultiTagSupport.parseCategoryIds(drama)) {
|
||||
Map<String, String> locales = localesById.get(id);
|
||||
if (locales != null && !locales.isEmpty()) {
|
||||
result.add(locales);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private DramaCategoryRespVO toResp(DramaCategory category, Integer dramaCount) {
|
||||
Map<String, String> locales = readLocales(category);
|
||||
DramaCategoryRespVO vo = new DramaCategoryRespVO();
|
||||
vo.setId(category.getId());
|
||||
vo.setName(ContentLocaleTexts.value(locales, ContentLocaleTexts.ZH));
|
||||
vo.setNameEn(ContentLocaleTexts.value(locales, ContentLocaleTexts.EN));
|
||||
vo.setNameId(ContentLocaleTexts.value(locales, ContentLocaleTexts.ID));
|
||||
vo.setLocales(locales);
|
||||
vo.setSort(category.getSort());
|
||||
vo.setStatus(category.getStatus());
|
||||
vo.setDramaCount(dramaCount);
|
||||
return vo;
|
||||
}
|
||||
|
||||
private Map<String, String> namesOf(DramaCategorySaveReqVO req) {
|
||||
return ContentLocaleTexts.names(req.getLocales(), req.getName(), req.getNameEn(), req.getNameId());
|
||||
}
|
||||
|
||||
private void writeNames(DramaCategory category, Map<String, String> locales) {
|
||||
category.setName(locales.get(ContentLocaleTexts.ZH));
|
||||
category.setNameEn(ContentLocaleTexts.value(locales, ContentLocaleTexts.EN));
|
||||
category.setNameId(ContentLocaleTexts.value(locales, ContentLocaleTexts.ID));
|
||||
category.setNameLocales(ContentLocaleTexts.json(locales));
|
||||
}
|
||||
|
||||
private Map<String, String> readLocales(DramaCategory category) {
|
||||
return ContentLocaleTexts.read(
|
||||
category.getNameLocales(), category.getName(), category.getNameEn(), category.getNameId());
|
||||
}
|
||||
}
|
||||
+23
-5
@@ -49,7 +49,12 @@ public class DramaClassificationService {
|
||||
.stream().map(item -> {
|
||||
DramaClassificationRespVO vo = new DramaClassificationRespVO();
|
||||
vo.setId(item.getId());
|
||||
vo.setName(item.getName());
|
||||
Map<String, String> locales = ContentLocaleTexts.read(
|
||||
item.getNameLocales(), item.getName(), item.getNameEn(), item.getNameId());
|
||||
vo.setName(locales.get(ContentLocaleTexts.ZH));
|
||||
vo.setNameEn(ContentLocaleTexts.value(locales, ContentLocaleTexts.EN));
|
||||
vo.setNameId(ContentLocaleTexts.value(locales, ContentLocaleTexts.ID));
|
||||
vo.setLocales(locales);
|
||||
vo.setSort(item.getSort());
|
||||
vo.setStatus(item.getStatus());
|
||||
vo.setUpdatedAt(item.getUpdatedAt());
|
||||
@@ -59,9 +64,10 @@ public class DramaClassificationService {
|
||||
}
|
||||
|
||||
public Long create(DramaClassificationSaveReqVO req) {
|
||||
ensureNameAvailable(req.getName(), null);
|
||||
Map<String, String> locales = namesOf(req);
|
||||
ensureNameAvailable(locales.get(ContentLocaleTexts.ZH), null);
|
||||
DramaClassification entity = new DramaClassification();
|
||||
entity.setName(req.getName().trim());
|
||||
writeNames(entity, locales);
|
||||
Long count = classificationMapper.selectCount(null);
|
||||
entity.setSort(req.getSort() == null
|
||||
? (count == null ? 0 : count.intValue()) + 1
|
||||
@@ -73,8 +79,9 @@ public class DramaClassificationService {
|
||||
|
||||
public void update(Long id, DramaClassificationSaveReqVO req) {
|
||||
DramaClassification entity = getExisting(id);
|
||||
ensureNameAvailable(req.getName(), id);
|
||||
entity.setName(req.getName().trim());
|
||||
Map<String, String> locales = namesOf(req);
|
||||
ensureNameAvailable(locales.get(ContentLocaleTexts.ZH), id);
|
||||
writeNames(entity, locales);
|
||||
if (req.getSort() != null) {
|
||||
entity.setSort(req.getSort());
|
||||
}
|
||||
@@ -182,6 +189,17 @@ public class DramaClassificationService {
|
||||
return entity;
|
||||
}
|
||||
|
||||
private Map<String, String> namesOf(DramaClassificationSaveReqVO req) {
|
||||
return ContentLocaleTexts.names(req.getLocales(), req.getName(), req.getNameEn(), req.getNameId());
|
||||
}
|
||||
|
||||
private void writeNames(DramaClassification entity, Map<String, String> locales) {
|
||||
entity.setName(locales.get(ContentLocaleTexts.ZH));
|
||||
entity.setNameEn(ContentLocaleTexts.value(locales, ContentLocaleTexts.EN));
|
||||
entity.setNameId(ContentLocaleTexts.value(locales, ContentLocaleTexts.ID));
|
||||
entity.setNameLocales(ContentLocaleTexts.json(locales));
|
||||
}
|
||||
|
||||
private void ensureNameAvailable(String name, Long excludeId) {
|
||||
Long count = classificationMapper.selectCount(Wrappers.<DramaClassification>lambdaQuery()
|
||||
.eq(DramaClassification::getName, name.trim())
|
||||
|
||||
+5
@@ -52,6 +52,11 @@ final class DramaMultiTagSupport {
|
||||
drama.setCategoryIds(String.join(",", values.stream().map(String::valueOf).toList()));
|
||||
return;
|
||||
}
|
||||
if (req.getCategoryIds() != null) {
|
||||
drama.setCategoryId(0L);
|
||||
drama.setCategoryIds("");
|
||||
return;
|
||||
}
|
||||
if (drama.getCategoryId() == null) {
|
||||
drama.setCategoryId(0L);
|
||||
}
|
||||
|
||||
+46
-10
@@ -49,8 +49,19 @@ public class DramaPlaylistCategoryService {
|
||||
.stream().map(item -> {
|
||||
DramaPlaylistCategoryRespVO vo = new DramaPlaylistCategoryRespVO();
|
||||
vo.setId(item.getId());
|
||||
vo.setName(item.getName());
|
||||
vo.setDescription(item.getDescription());
|
||||
Map<String, String> locales = ContentLocaleTexts.read(
|
||||
item.getNameLocales(), item.getName(), item.getNameEn(), item.getNameId());
|
||||
Map<String, String> descriptions = ContentLocaleTexts.read(
|
||||
item.getDescriptionLocales(), item.getDescription(),
|
||||
item.getDescriptionEn(), item.getDescriptionId());
|
||||
vo.setName(locales.get(ContentLocaleTexts.ZH));
|
||||
vo.setNameEn(ContentLocaleTexts.value(locales, ContentLocaleTexts.EN));
|
||||
vo.setNameId(ContentLocaleTexts.value(locales, ContentLocaleTexts.ID));
|
||||
vo.setLocales(locales);
|
||||
vo.setDescription(ContentLocaleTexts.value(descriptions, ContentLocaleTexts.ZH));
|
||||
vo.setDescriptionEn(ContentLocaleTexts.value(descriptions, ContentLocaleTexts.EN));
|
||||
vo.setDescriptionId(ContentLocaleTexts.value(descriptions, ContentLocaleTexts.ID));
|
||||
vo.setDescriptionLocales(descriptions);
|
||||
vo.setSort(item.getSort());
|
||||
vo.setStatus(item.getStatus());
|
||||
vo.setDramaCount(counts.getOrDefault(item.getId(), 0));
|
||||
@@ -59,10 +70,10 @@ public class DramaPlaylistCategoryService {
|
||||
}
|
||||
|
||||
public Long create(DramaPlaylistCategorySaveReqVO req) {
|
||||
ensureNameAvailable(req.getName(), null);
|
||||
Map<String, String> locales = namesOf(req);
|
||||
ensureNameAvailable(locales.get(ContentLocaleTexts.ZH), null);
|
||||
DramaPlaylistCategory entity = new DramaPlaylistCategory();
|
||||
entity.setName(req.getName().trim());
|
||||
entity.setDescription(req.getDescription() == null ? "" : req.getDescription().trim());
|
||||
writeText(entity, req, locales);
|
||||
Long count = categoryMapper.selectCount(null);
|
||||
entity.setSort(req.getSort() == null
|
||||
? (count == null ? 0 : count.intValue()) + 1
|
||||
@@ -74,11 +85,9 @@ public class DramaPlaylistCategoryService {
|
||||
|
||||
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());
|
||||
}
|
||||
Map<String, String> locales = namesOf(req);
|
||||
ensureNameAvailable(locales.get(ContentLocaleTexts.ZH), id);
|
||||
writeText(entity, req, locales);
|
||||
if (req.getSort() != null) {
|
||||
entity.setSort(req.getSort());
|
||||
}
|
||||
@@ -178,6 +187,14 @@ public class DramaPlaylistCategoryService {
|
||||
return counts;
|
||||
}
|
||||
|
||||
/** C 端用:已启用剧单,按排序升序、再按 id 倒序。 */
|
||||
public List<DramaPlaylistCategory> listEnabled() {
|
||||
return categoryMapper.selectList(Wrappers.<DramaPlaylistCategory>lambdaQuery()
|
||||
.eq(DramaPlaylistCategory::getStatus, 1)
|
||||
.orderByAsc(DramaPlaylistCategory::getSort)
|
||||
.orderByDesc(DramaPlaylistCategory::getId));
|
||||
}
|
||||
|
||||
/** C 端用:只返回已启用(status=1)的剧单。 */
|
||||
public DramaPlaylistCategory getEnabledById(Long id) {
|
||||
DramaPlaylistCategory entity = getExisting(id);
|
||||
@@ -206,6 +223,25 @@ public class DramaPlaylistCategoryService {
|
||||
return entity;
|
||||
}
|
||||
|
||||
private Map<String, String> namesOf(DramaPlaylistCategorySaveReqVO req) {
|
||||
return ContentLocaleTexts.names(req.getLocales(), req.getName(), req.getNameEn(), req.getNameId());
|
||||
}
|
||||
|
||||
private void writeText(DramaPlaylistCategory entity, DramaPlaylistCategorySaveReqVO req,
|
||||
Map<String, String> locales) {
|
||||
Map<String, String> descriptions = ContentLocaleTexts.descriptions(
|
||||
req.getDescriptionLocales(), locales,
|
||||
req.getDescription(), req.getDescriptionEn(), req.getDescriptionId());
|
||||
entity.setName(locales.get(ContentLocaleTexts.ZH));
|
||||
entity.setNameEn(ContentLocaleTexts.value(locales, ContentLocaleTexts.EN));
|
||||
entity.setNameId(ContentLocaleTexts.value(locales, ContentLocaleTexts.ID));
|
||||
entity.setNameLocales(ContentLocaleTexts.json(locales));
|
||||
entity.setDescription(ContentLocaleTexts.value(descriptions, ContentLocaleTexts.ZH));
|
||||
entity.setDescriptionEn(ContentLocaleTexts.value(descriptions, ContentLocaleTexts.EN));
|
||||
entity.setDescriptionId(ContentLocaleTexts.value(descriptions, ContentLocaleTexts.ID));
|
||||
entity.setDescriptionLocales(ContentLocaleTexts.json(descriptions));
|
||||
}
|
||||
|
||||
private void ensureNameAvailable(String name, Long excludeId) {
|
||||
Long count = categoryMapper.selectCount(Wrappers.<DramaPlaylistCategory>lambdaQuery()
|
||||
.eq(DramaPlaylistCategory::getName, name.trim())
|
||||
|
||||
+122
-8
@@ -10,9 +10,11 @@ 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.ContentTag;
|
||||
import com.fruitshorts.content.dal.entity.Drama;
|
||||
import com.fruitshorts.content.dal.entity.DramaEpisode;
|
||||
import com.fruitshorts.content.dal.entity.DramaReviewRecord;
|
||||
import com.fruitshorts.content.dal.mapper.ContentTagMapper;
|
||||
import com.fruitshorts.content.dal.mapper.DramaEpisodeMapper;
|
||||
import com.fruitshorts.content.dal.mapper.DramaMapper;
|
||||
import com.fruitshorts.content.dal.mapper.DramaReviewRecordMapper;
|
||||
@@ -28,20 +30,27 @@ import org.springframework.util.StringUtils;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DramaService {
|
||||
|
||||
private static final DateTimeFormatter CODE_DATE = DateTimeFormatter.ofPattern("yyyyMMdd");
|
||||
/** 全部短剧单页上限,与分类频道分页一致。 */
|
||||
private static final int PUBLISHED_PAGE_SIZE_MAX = 50;
|
||||
|
||||
private final DramaMapper dramaMapper;
|
||||
private final DramaEpisodeMapper dramaEpisodeMapper;
|
||||
private final DramaReviewRecordMapper dramaReviewRecordMapper;
|
||||
private final DramaCategoryService dramaCategoryService;
|
||||
private final ContentTagMapper contentTagMapper;
|
||||
private final ContentTagService contentTagService;
|
||||
private final EpisodePriceSync episodePriceSync;
|
||||
|
||||
public PageResult<DramaRespVO> page(DramaPageReqVO req) {
|
||||
@@ -122,6 +131,11 @@ public class DramaService {
|
||||
}
|
||||
dramaMapper.updateById(drama);
|
||||
episodePriceSync.syncByDramaRule(drama);
|
||||
if (req.getTagId() != null && req.getTagId() <= 0L) {
|
||||
dramaMapper.update(null, Wrappers.<Drama>lambdaUpdate()
|
||||
.eq(Drama::getId, drama.getId())
|
||||
.set(Drama::getTagId, null));
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@@ -226,10 +240,63 @@ public class DramaService {
|
||||
return dramaMapper.selectPage(page, wrapper).getRecords();
|
||||
}
|
||||
|
||||
/** C 端全部短剧:类型、分类、标签多选。 */
|
||||
public PageResult<Drama> pagePublishedLibrary(List<Long> categoryIds, List<Long> classificationIds,
|
||||
List<Long> tagIds, int pageIndex, int pageSize) {
|
||||
int safeIndex = Math.max(1, pageIndex);
|
||||
int safeSize = Math.max(1, Math.min(pageSize, PUBLISHED_PAGE_SIZE_MAX));
|
||||
Page<Drama> page = new Page<>(safeIndex, safeSize);
|
||||
LambdaQueryWrapper<Drama> wrapper = Wrappers.<Drama>lambdaQuery()
|
||||
.eq(Drama::getStatus, ContentStatus.PUBLISHED.getCode());
|
||||
applyLibraryFilters(wrapper, categoryIds, classificationIds, tagIds);
|
||||
wrapper.orderByDesc(Drama::getSort).orderByDesc(Drama::getId);
|
||||
IPage<Drama> result = dramaMapper.selectPage(page, wrapper);
|
||||
return new PageResult<>(result.getTotal(), result.getRecords());
|
||||
}
|
||||
|
||||
private void applyLibraryFilters(LambdaQueryWrapper<Drama> wrapper, List<Long> categoryIds,
|
||||
List<Long> classificationIds, List<Long> tagIds) {
|
||||
List<Long> types = positiveIds(categoryIds);
|
||||
if (!types.isEmpty()) {
|
||||
DramaMultiTagSupport.applyCategoryIdTokens(wrapper, types);
|
||||
}
|
||||
List<Long> tags = positiveIds(tagIds);
|
||||
if (!tags.isEmpty()) {
|
||||
wrapper.in(Drama::getTagId, tags);
|
||||
}
|
||||
List<Long> classes = positiveIds(classificationIds);
|
||||
if (!classes.isEmpty()) {
|
||||
wrapper.apply(classificationInClause(classes.size()), classes.toArray());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分类关联用占位符,id 不拼进 SQL。
|
||||
*/
|
||||
static String classificationInClause(int size) {
|
||||
StringBuilder sql = new StringBuilder(
|
||||
"id IN (SELECT drama_id FROM drama_classification_drama WHERE classification_id IN (");
|
||||
for (int index = 0; index < size; index++) {
|
||||
if (index > 0) {
|
||||
sql.append(',');
|
||||
}
|
||||
sql.append('{').append(index).append('}');
|
||||
}
|
||||
sql.append("))");
|
||||
return sql.toString();
|
||||
}
|
||||
|
||||
private List<Long> positiveIds(List<Long> ids) {
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
return ids.stream().filter(id -> id != null && id > 0L).distinct().toList();
|
||||
}
|
||||
|
||||
/** C 端分类频道:按分类分页查已上线短剧,含 total。 */
|
||||
public PageResult<Drama> pagePublishedByCategory(Long categoryId, int pageIndex, int pageSize) {
|
||||
int safeIndex = Math.max(1, pageIndex);
|
||||
int safeSize = Math.max(1, Math.min(pageSize, 50));
|
||||
int safeSize = Math.max(1, Math.min(pageSize, PUBLISHED_PAGE_SIZE_MAX));
|
||||
Page<Drama> page = new Page<>(safeIndex, safeSize);
|
||||
LambdaQueryWrapper<Drama> wrapper = Wrappers.<Drama>lambdaQuery()
|
||||
.eq(Drama::getStatus, ContentStatus.PUBLISHED.getCode());
|
||||
@@ -369,7 +436,13 @@ public class DramaService {
|
||||
* 避免编辑表单没带 sort / language / trailerUrl 时把已有数据清掉。
|
||||
*/
|
||||
private void apply(Drama drama, DramaSaveReqVO req, boolean isNew) {
|
||||
drama.setTitle(req.getTitle());
|
||||
if (req.getTitleLocales() != null) {
|
||||
Map<String, String> titles = ContentLocaleTexts.titles(req.getTitleLocales(), req.getTitle());
|
||||
drama.setTitle(titles.get(ContentLocaleTexts.ZH));
|
||||
drama.setTitleLocales(ContentLocaleTexts.json(titles));
|
||||
} else if (isNew || req.getTitle() != null) {
|
||||
drama.setTitle(req.getTitle());
|
||||
}
|
||||
if (isNew || req.getSubtitle() != null) {
|
||||
drama.setSubtitle(req.getSubtitle() == null ? "" : req.getSubtitle());
|
||||
}
|
||||
@@ -379,7 +452,13 @@ public class DramaService {
|
||||
if (isNew || req.getGenre() != null) {
|
||||
drama.setGenre(req.getGenre() == null ? "" : req.getGenre());
|
||||
}
|
||||
if (isNew || req.getDescription() != null) {
|
||||
if (req.getSynopsisLocales() != null) {
|
||||
Map<String, String> titles = ContentLocaleTexts.titles(req.getTitleLocales(), req.getTitle());
|
||||
Map<String, String> synopses = ContentLocaleTexts.synopses(
|
||||
req.getSynopsisLocales(), titles, req.getDescription());
|
||||
drama.setSynopsis(ContentLocaleTexts.value(synopses, ContentLocaleTexts.ZH));
|
||||
drama.setSynopsisLocales(ContentLocaleTexts.json(synopses));
|
||||
} else if (isNew || req.getDescription() != null) {
|
||||
drama.setSynopsis(req.getDescription() == null ? "" : req.getDescription());
|
||||
}
|
||||
if (isNew || req.getCover() != null) {
|
||||
@@ -395,6 +474,10 @@ public class DramaService {
|
||||
if (isNew || req.getDramaTypes() != null || req.getDramaType() != null) {
|
||||
DramaMultiTagSupport.applyDramaTypes(drama, req);
|
||||
}
|
||||
if (isNew || req.getTagId() != null) {
|
||||
contentTagService.requireEnabled(req.getTagId());
|
||||
drama.setTagId(req.getTagId() != null && req.getTagId() > 0L ? req.getTagId() : null);
|
||||
}
|
||||
if (isNew || req.getFreeEpisodes() != null) {
|
||||
drama.setFreeEpisodes(req.getFreeEpisodes() == null ? 3 : req.getFreeEpisodes());
|
||||
}
|
||||
@@ -420,11 +503,10 @@ public class DramaService {
|
||||
return List.of();
|
||||
}
|
||||
Map<Long, String> categories = dramaCategoryService.nameMap();
|
||||
Map<Long, ContentTag> tags = loadTags(dramas);
|
||||
return dramas.stream().map(drama -> {
|
||||
List<Long> categoryIds = DramaMultiTagSupport.parseCategoryIds(drama);
|
||||
Long primaryCategoryId = categoryIds.isEmpty()
|
||||
? (drama.getCategoryId() == null ? 0L : drama.getCategoryId())
|
||||
: categoryIds.get(0);
|
||||
Long primaryCategoryId = categoryIds.isEmpty() ? null : categoryIds.get(0);
|
||||
List<String> categoryNames =
|
||||
DramaMultiTagSupport.categoryNames(drama, categoryIds, categories);
|
||||
List<String> dramaTypes = DramaMultiTagSupport.parseDramaTypes(drama);
|
||||
@@ -432,19 +514,28 @@ public class DramaService {
|
||||
vo.setId(drama.getId());
|
||||
vo.setCode(drama.getCode());
|
||||
vo.setTitle(drama.getTitle());
|
||||
vo.setSubtitle(drama.getSubtitle());
|
||||
Map<String, String> titles = ContentLocaleTexts.read(
|
||||
drama.getTitleLocales(), drama.getTitle(), null, null);
|
||||
Map<String, String> synopses = ContentLocaleTexts.read(
|
||||
drama.getSynopsisLocales(), drama.getSynopsis(), null, null);
|
||||
vo.setTitle(ContentLocaleTexts.value(titles, ContentLocaleTexts.ZH));
|
||||
vo.setTitleLocales(titles);
|
||||
vo.setCategoryId(primaryCategoryId);
|
||||
vo.setCategoryIds(categoryIds);
|
||||
vo.setCategoryNames(categoryNames);
|
||||
vo.setCategory(categories.getOrDefault(primaryCategoryId, ""));
|
||||
vo.setCategory(primaryCategoryId == null ? "" : categories.getOrDefault(primaryCategoryId, ""));
|
||||
vo.setGenre(drama.getGenre());
|
||||
vo.setDescription(drama.getSynopsis());
|
||||
vo.setDescription(ContentLocaleTexts.value(synopses, ContentLocaleTexts.ZH));
|
||||
vo.setSynopsisLocales(synopses);
|
||||
vo.setSubtitle(drama.getSubtitle());
|
||||
vo.setCover(drama.getCoverUrl());
|
||||
vo.setTrailerUrl(drama.getTrailerUrl());
|
||||
vo.setLanguage(drama.getLanguage());
|
||||
vo.setDramaType(dramaTypes.get(0));
|
||||
vo.setDramaTypes(dramaTypes);
|
||||
vo.setEpisodes(drama.getTotalEpisodes());
|
||||
fillTag(vo, drama.getTagId() == null ? null : tags.get(drama.getTagId()));
|
||||
vo.setFreeEpisodes(drama.getFreeEpisodes());
|
||||
vo.setUnlockCoin(drama.getUnlockCoin());
|
||||
vo.setIsHot(drama.getIsHot() != null && drama.getIsHot() == 1);
|
||||
@@ -480,6 +571,29 @@ public class DramaService {
|
||||
}
|
||||
}
|
||||
|
||||
private Map<Long, ContentTag> loadTags(List<Drama> dramas) {
|
||||
List<Long> ids = dramas.stream()
|
||||
.map(Drama::getTagId)
|
||||
.filter(id -> id != null && id > 0L)
|
||||
.distinct()
|
||||
.toList();
|
||||
if (ids.isEmpty()) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
return contentTagMapper.selectBatchIds(ids).stream()
|
||||
.collect(Collectors.toMap(ContentTag::getId, Function.identity(), (left, right) -> left));
|
||||
}
|
||||
|
||||
private void fillTag(DramaRespVO vo, ContentTag tag) {
|
||||
if (tag == null) {
|
||||
return;
|
||||
}
|
||||
vo.setTagId(tag.getId());
|
||||
vo.setTagName(tag.getName());
|
||||
vo.setTagNameEn(tag.getNameEn());
|
||||
vo.setTagNameId(tag.getNameId());
|
||||
}
|
||||
|
||||
private Long nonNegativeOrZero(Long value, String fieldName) {
|
||||
return value == null ? 0L : nonNegative(value, fieldName);
|
||||
}
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
package com.fruitshorts.content.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/** 标签列表项。 */
|
||||
@Data
|
||||
public class ContentTagRespVO {
|
||||
|
||||
private Long id;
|
||||
private String name;
|
||||
private String nameEn;
|
||||
private String nameId;
|
||||
private java.util.Map<String, String> locales;
|
||||
private String bgColor;
|
||||
private Integer sort;
|
||||
private Integer status;
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
package com.fruitshorts.content.vo;
|
||||
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
/** 标签新增或编辑。中文必填,其余语言按 locales 勾选。 */
|
||||
@Data
|
||||
public class ContentTagSaveReqVO {
|
||||
|
||||
@Size(max = 64, message = "中文名称最多 64 个字符")
|
||||
private String name;
|
||||
|
||||
private String nameEn;
|
||||
private String nameId;
|
||||
/** 勾选的语言名称,key 为 zh / en / id。 */
|
||||
private java.util.Map<String, String> locales;
|
||||
|
||||
@Pattern(regexp = "^$|^#[0-9A-Fa-f]{6}$", message = "背景色格式应为 #RRGGBB")
|
||||
private String bgColor;
|
||||
|
||||
private Integer sort;
|
||||
private Integer status;
|
||||
}
|
||||
+5
@@ -4,11 +4,16 @@ package com.fruitshorts.content.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class DramaCategoryRespVO {
|
||||
|
||||
private Long id;
|
||||
private String name;
|
||||
private String nameEn;
|
||||
private String nameId;
|
||||
private Map<String, String> locales;
|
||||
private Integer sort;
|
||||
private Integer status;
|
||||
/** 该类型下的短剧数量 */
|
||||
|
||||
+9
-2
@@ -2,15 +2,22 @@
|
||||
|
||||
package com.fruitshorts.content.vo;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class DramaCategorySaveReqVO {
|
||||
|
||||
@NotBlank(message = "请填写类型名称")
|
||||
@Size(max = 64, message = "中文名称最多 64 个字符")
|
||||
private String name;
|
||||
|
||||
private String nameEn;
|
||||
private String nameId;
|
||||
/** 勾选的语言名称,key 为 zh / en / id。 */
|
||||
private Map<String, String> locales;
|
||||
|
||||
private Integer sort;
|
||||
private Integer status;
|
||||
}
|
||||
+3
@@ -12,6 +12,9 @@ public class DramaClassificationRespVO {
|
||||
|
||||
private Long id;
|
||||
private String name;
|
||||
private String nameEn;
|
||||
private String nameId;
|
||||
private java.util.Map<String, String> locales;
|
||||
private Integer sort;
|
||||
private Integer status;
|
||||
private Integer dramaCount;
|
||||
|
||||
+7
-2
@@ -2,16 +2,21 @@
|
||||
|
||||
package com.fruitshorts.content.vo;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
/** 分类新增/编辑请求体。 */
|
||||
@Data
|
||||
public class DramaClassificationSaveReqVO {
|
||||
|
||||
@NotBlank(message = "请填写分类名称")
|
||||
@Size(max = 64, message = "中文名称最多 64 个字符")
|
||||
private String name;
|
||||
|
||||
private String nameEn;
|
||||
private String nameId;
|
||||
/** 勾选的语言名称,key 为 zh / en / id。 */
|
||||
private java.util.Map<String, String> locales;
|
||||
|
||||
private Integer sort;
|
||||
private Integer status;
|
||||
}
|
||||
+6
@@ -10,7 +10,13 @@ public class DramaPlaylistCategoryRespVO {
|
||||
|
||||
private Long id;
|
||||
private String name;
|
||||
private String nameEn;
|
||||
private String nameId;
|
||||
private String description;
|
||||
private String descriptionEn;
|
||||
private String descriptionId;
|
||||
private java.util.Map<String, String> locales;
|
||||
private java.util.Map<String, String> descriptionLocales;
|
||||
private Integer sort;
|
||||
private Integer status;
|
||||
/** 该分类下已关联的短剧数量 */
|
||||
|
||||
+10
-3
@@ -2,7 +2,6 @@
|
||||
|
||||
package com.fruitshorts.content.vo;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
@@ -10,11 +9,19 @@ import lombok.Data;
|
||||
@Data
|
||||
public class DramaPlaylistCategorySaveReqVO {
|
||||
|
||||
@NotBlank(message = "请填写剧单分类名称")
|
||||
@Size(max = 64, message = "中文名称最多 64 个字符")
|
||||
private String name;
|
||||
|
||||
@Size(max = 255, message = "描述最多 255 个字符")
|
||||
private String nameEn;
|
||||
private String nameId;
|
||||
/** 勾选的语言名称,key 为 zh / en / id。 */
|
||||
private java.util.Map<String, String> locales;
|
||||
|
||||
private String description;
|
||||
private String descriptionEn;
|
||||
private String descriptionId;
|
||||
/** 勾选语言对应的描述。 */
|
||||
private java.util.Map<String, String> descriptionLocales;
|
||||
|
||||
private Integer sort;
|
||||
private Integer status;
|
||||
|
||||
@@ -17,6 +17,7 @@ public class DramaRespVO {
|
||||
private Long id;
|
||||
private String code;
|
||||
private String title;
|
||||
private java.util.Map<String, String> titleLocales;
|
||||
private String subtitle;
|
||||
private Long categoryId;
|
||||
private List<Long> categoryIds;
|
||||
@@ -24,12 +25,17 @@ public class DramaRespVO {
|
||||
private String category;
|
||||
private String genre;
|
||||
private String description;
|
||||
private java.util.Map<String, String> synopsisLocales;
|
||||
private String cover;
|
||||
/** 整剧预告片地址 */
|
||||
private String trailerUrl;
|
||||
private String language;
|
||||
private String dramaType;
|
||||
private List<String> dramaTypes;
|
||||
private Long tagId;
|
||||
private String tagName;
|
||||
private String tagNameEn;
|
||||
private String tagNameId;
|
||||
private Integer episodes;
|
||||
private Integer freeEpisodes;
|
||||
private Integer unlockCoin;
|
||||
|
||||
+6
@@ -14,6 +14,8 @@ public class DramaSaveReqVO {
|
||||
|
||||
@NotBlank(message = "请填写短剧名称")
|
||||
private String title;
|
||||
/** 勾选语言的标题,key 为 zh / en / id。 */
|
||||
private java.util.Map<String, String> titleLocales;
|
||||
|
||||
private String subtitle;
|
||||
private Long categoryId;
|
||||
@@ -21,6 +23,8 @@ public class DramaSaveReqVO {
|
||||
private List<Long> categoryIds;
|
||||
private String genre;
|
||||
private String description;
|
||||
/** 勾选语言的简介。 */
|
||||
private java.util.Map<String, String> synopsisLocales;
|
||||
private String cover;
|
||||
/** 整剧预告片地址(复用已有的 drama.trailer_url 列) */
|
||||
private String trailerUrl;
|
||||
@@ -29,6 +33,8 @@ public class DramaSaveReqVO {
|
||||
private String dramaType;
|
||||
/** 后台多选优先,创作者中心可继续只传 dramaType */
|
||||
private List<String> dramaTypes;
|
||||
/** 单选标签。不传则保持原值。 */
|
||||
private Long tagId;
|
||||
private Integer freeEpisodes;
|
||||
private Integer unlockCoin;
|
||||
@PositiveOrZero(message = "收藏数不能小于 0")
|
||||
|
||||
Reference in new issue
Block a user