feat(content): 标签增加语义码并随卡片下发
backend-ci / test (push) Canceled after 0s

标签名称会随语言和运营文案变化,客户端按名称判断板块(如推荐页「上新」
横滑)一改名就失效。content_tag 新增 code 列并在后台维护,
卡片与详情以 tagCode 下发,已有「上新 / 热剧 / 新剧 / 热播」一并回填。
This commit is contained in:
jianghaifeng authored and trae-solo-agent committed 2026-10-29 15:45:34 +08:00
1 parent 4d796089a0
commit 0d4d510aee
9 files changed
+73

No files matched your search

+4
View File
@@ -205,6 +205,10 @@ app.register.max-per-device-per-day
> 卡片文案只放语言表:`titleLocales`、`synopsisLocales`、`types`、`tagLocales`。
> `tagColor` 是标签背景色。中文取各表的 `zh`。
>
> `tagCode` 是标签语义码(如 `NEW_ARRIVAL`),来自 `content_tag.code`。
> 客户端按码判断板块(推荐页「上新」横滑),`tagLocales` 只用于展示;
> 码为空时客户端才退回按文案匹配。后台标签管理可配置该码。
>
> 卡片新增 `dramaType`:`ANIME` 表示漫剧,`LIVE` 表示真人剧。
> 客户端字段仍返回主内容类型;后台多选值保存在 `drama.drama_types`,
> App 类型筛选按逗号列整词匹配,因此 `ANIME,LIVE` 多标签剧可被任一类型命中。
@@ -411,6 +411,7 @@ public class AppCatalogService {
ContentTag tag = tagOf(drama, tags);
Map<String, String> locales = tagLocales(tag);
card.setTagLocales(locales);
card.setTagCode(tagCode(tag));
card.setTagColor(tag == null ? "" : tag.getBgColor());
}
@@ -418,9 +419,18 @@ public class AppCatalogService {
ContentTag tag = tagOf(drama, loadTags(List.of(drama)));
Map<String, String> locales = tagLocales(tag);
detail.setTagLocales(locales);
detail.setTagCode(tagCode(tag));
detail.setTagColor(tag == null ? "" : tag.getBgColor());
}
/** 语义码直接下发,客户端据此判断板块;未配置时给空串,由客户端按文案兜底。 */
private String tagCode(ContentTag tag) {
if (tag == null || tag.getCode() == null) {
return "";
}
return tag.getCode().trim();
}
private ContentTag tagOf(Drama drama, Map<Long, ContentTag> tags) {
if (drama.getTagId() == null || drama.getTagId() <= 0L) {
return null;
@@ -20,6 +20,8 @@ public class AppDramaCardVO {
private List<Map<String, String>> types;
/** 标签语言表,key 为 zh / en / id。 */
private Map<String, String> tagLocales;
/** 标签语义码,如 NEW_ARRIVAL;客户端按码判断板块,文案仅用于展示。 */
private String tagCode;
/** 标签角标背景色,#RRGGBB。 */
private String tagColor;
private Integer totalEpisodes;
@@ -16,6 +16,8 @@ public class AppDramaDetailVO {
private String cover;
private List<Map<String, String>> types;
private Map<String, String> tagLocales;
/** 标签语义码,如 NEW_ARRIVAL;空表示未配置。 */
private String tagCode;
private String tagColor;
private Integer totalEpisodes;
private Integer freeEpisodes;
@@ -24,6 +24,8 @@ public class ContentTag {
private String nameLocales;
/** 角标背景色,#RRGGBB。 */
private String bgColor;
/** 语义码,如 NEW_ARRIVAL;客户端按码判断板块,不解析文案。空表示未配置。 */
private String code;
private Integer sort;
private Integer status;
private LocalDateTime createdAt;
@@ -49,9 +49,12 @@ public class ContentTagService {
public Long create(ContentTagSaveReqVO req) {
Map<String, String> locales = namesOf(req);
ensureNameAvailable(locales.get(ContentLocaleTexts.ZH), null);
String code = normalizeCode(req.getCode());
ensureCodeAvailable(code, null);
ContentTag entity = new ContentTag();
writeNames(entity, locales);
entity.setBgColor(normalizeColor(req.getBgColor()));
entity.setCode(code);
Long count = tagMapper.selectCount(null);
entity.setSort(req.getSort() == null
? (count == null ? 0 : count.intValue()) + 1
@@ -69,6 +72,11 @@ public class ContentTagService {
if (req.getBgColor() != null) {
entity.setBgColor(normalizeColor(req.getBgColor()));
}
if (req.getCode() != null) {
String code = normalizeCode(req.getCode());
ensureCodeAvailable(code, id);
entity.setCode(code);
}
if (req.getSort() != null) {
entity.setSort(req.getSort());
}
@@ -115,6 +123,23 @@ public class ContentTagService {
return color.trim().toUpperCase();
}
/** 语义码统一大写;空字符串表示不配置。 */
private String normalizeCode(String code) {
return code == null ? "" : code.trim().toUpperCase();
}
private void ensureCodeAvailable(String code, Long excludeId) {
if (code.isEmpty()) {
return;
}
Long count = tagMapper.selectCount(Wrappers.<ContentTag>lambdaQuery()
.eq(ContentTag::getCode, code)
.ne(excludeId != null, ContentTag::getId, excludeId));
if (count != null && count > 0) {
throw new ServiceException(ErrorCode.DATA_EXISTS, "语义码已存在");
}
}
private ContentTag getExisting(Long id) {
ContentTag entity = id == null ? null : tagMapper.selectById(id);
if (entity == null) {
@@ -142,6 +167,7 @@ public class ContentTagService {
vo.setNameId(ContentLocaleTexts.value(locales, ContentLocaleTexts.ID));
vo.setLocales(locales);
vo.setBgColor(tag.getBgColor() == null || tag.getBgColor().isBlank() ? "#E5484D" : tag.getBgColor());
vo.setCode(tag.getCode() == null ? "" : tag.getCode());
vo.setSort(tag.getSort());
vo.setStatus(tag.getStatus());
return vo;
@@ -14,6 +14,8 @@ public class ContentTagRespVO {
private String nameId;
private java.util.Map<String, String> locales;
private String bgColor;
/** 语义码,如 NEW_ARRIVAL;空表示未配置。 */
private String code;
private Integer sort;
private Integer status;
}
@@ -21,6 +21,10 @@ public class ContentTagSaveReqVO {
@Pattern(regexp = "^$|^#[0-9A-Fa-f]{6}$", message = "背景色格式应为 #RRGGBB")
private String bgColor;
/** 语义码,如 NEW_ARRIVAL;客户端按码判断板块,留空表示按文案兜底。 */
@Pattern(regexp = "^$|^[A-Z][A-Z0-9_]{0,31}$", message = "语义码需为大写字母、数字或下划线")
private String code;
private Integer sort;
private Integer status;
}
+21
View File
@@ -0,0 +1,21 @@
-- Author: fruit.com.cn
-- 标签语义码:客户端按稳定码判断板块归属(如「上新」横滑),文案可随意改。
ALTER TABLE content_tag ADD COLUMN IF NOT EXISTS code VARCHAR(32) NOT NULL DEFAULT '';
-- 同一个语义码只能给一个标签,空码不参与唯一性约束。
CREATE UNIQUE INDEX IF NOT EXISTS uk_content_tag_code
ON content_tag (code) WHERE code <> '';
-- 已有标签回填语义码(幂等:只补空的)。
UPDATE content_tag SET code = 'NEW_ARRIVAL'
WHERE code = '' AND (name = '上新' OR name_en = 'New Arrival');
UPDATE content_tag SET code = 'HOT'
WHERE code = '' AND (name = '热剧' OR name_en = 'Hot');
UPDATE content_tag SET code = 'NEW_RELEASE'
WHERE code = '' AND (name = '新剧' OR name_en = 'New Movie');
UPDATE content_tag SET code = 'TRENDING'
WHERE code = '' AND (name = '热播' OR name_en = 'Rebo');