feat(W1): 皮肤包完整管理-外部主题目录+热注册,zip上传/派生/编辑/删除,ThemePackageValidator收敛,Thymeleaf文件解析器,list增source/inUseCount/editable/usedBySiteNames,T-W1-01..20 全绿299/299
build / build-push (push) Successful in 52s
build / build-push (push) Successful in 52s
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
package com.yunhai.cms.theme;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* 主题包规范校验(W1 重构点,方案 §5.5:校验语义收敛于本类,Registry 与 PackageService 共用,
|
||||
* 杜绝两套校验漂移)。原 ThemeRegistry.validate 的规则原文平移:
|
||||
* specVersion=1、name 规范、name=目录名、必备模板(home/list/detail/single)、
|
||||
* fragments/head.html、param 规范、screenshot 存在。
|
||||
* existsFn 判定资源存在:classpath 包用 ClassPathResource、外部包用 Files.exists(§5.4 步骤 1)。
|
||||
*/
|
||||
public final class ThemePackageValidator {
|
||||
|
||||
private ThemePackageValidator() {
|
||||
}
|
||||
|
||||
/** 校验不通过抛 IllegalArgumentException(bad package 语义,Registry 剔除 + log.warn) */
|
||||
public static void validate(ThemeInfo info, String dirName, Predicate<String> existsFn) {
|
||||
if (info == null) {
|
||||
throw new IllegalArgumentException("清单为空");
|
||||
}
|
||||
if (info.getSpecVersion() == null || info.getSpecVersion() != 1) {
|
||||
throw new IllegalArgumentException("specVersion 必须为 1");
|
||||
}
|
||||
if (!ThemeRegistry.isValidName(info.getName())) {
|
||||
throw new IllegalArgumentException("name 缺失或不符合命名规范:" + info.getName());
|
||||
}
|
||||
if (!info.getName().equals(dirName)) {
|
||||
throw new IllegalArgumentException("name 与目录名不一致:" + dirName + " != " + info.getName());
|
||||
}
|
||||
for (String template : ThemeRegistry.requiredTemplates()) {
|
||||
if (!existsFn.test(template)) {
|
||||
throw new IllegalArgumentException("缺必备模板 " + template);
|
||||
}
|
||||
}
|
||||
// fragments/ 目录存在性以 head.html 片段代替(jar 内目录项不可靠)
|
||||
if (!existsFn.test("fragments/head.html")) {
|
||||
throw new IllegalArgumentException("缺 fragments/head.html 公共片段");
|
||||
}
|
||||
validateParams(info);
|
||||
String shot = info.screenshotPath();
|
||||
if (!existsFn.test(shot)) {
|
||||
throw new IllegalArgumentException("声明的 screenshot 不存在:" + shot);
|
||||
}
|
||||
}
|
||||
|
||||
/** param 规范(key/类型/重复/cssVar 仅 color/select 必带 options);不合法抛 IllegalArgumentException */
|
||||
public static void validateParams(ThemeInfo info) {
|
||||
List<ThemeParam> params = info.getParams();
|
||||
List<String> seen = new java.util.ArrayList<>();
|
||||
for (ThemeParam p : params) {
|
||||
if (p.getKey() == null || !ThemeParam.KEY_PATTERN.matcher(p.getKey()).matches()) {
|
||||
throw new IllegalArgumentException("param key 非法:" + p.getKey());
|
||||
}
|
||||
if (seen.contains(p.getKey())) {
|
||||
throw new IllegalArgumentException("param key 重复:" + p.getKey());
|
||||
}
|
||||
seen.add(p.getKey());
|
||||
String type = p.getType();
|
||||
boolean knownType = ThemeParam.TYPE_COLOR.equals(type) || ThemeParam.TYPE_IMAGE.equals(type)
|
||||
|| ThemeParam.TYPE_SELECT.equals(type) || ThemeParam.TYPE_TEXT.equals(type);
|
||||
if (!knownType) {
|
||||
throw new IllegalArgumentException("param " + p.getKey() + " 类型非法:" + type);
|
||||
}
|
||||
if (p.getCssVar() != null && !p.getCssVar().isEmpty() && !ThemeParam.TYPE_COLOR.equals(type)) {
|
||||
throw new IllegalArgumentException("cssVar 仅允许 color 类型参数:" + p.getKey());
|
||||
}
|
||||
if (ThemeParam.TYPE_SELECT.equals(type)
|
||||
&& (p.getOptions() == null || p.getOptions().isEmpty())) {
|
||||
throw new IllegalArgumentException("select 参数缺 options:" + p.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user