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 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 params = info.getParams(); List 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()); } } } }