feat(portal): 门户左侧栏目导航+首页视觉升级+主题正名批次-PortalSideNav模型+attachCategoryNavigation注入doCategory/doArticle三件套categoryPath/navTopId/sideNav(站点快照树推导零新增查询,home/search不注入);7主题侧栏两栏markup+窄屏横滚+顶导航祖先高亮+二级下拉当前项高亮+面包屑全路径;cms_category.home_style全链路(news_grid 16条8x2日期前缀/media_focus形态)+PortalListData.Item补source来源徽标四处装配;Banner机制gallery展示位→galleryBanners四列大图轮播(自由字符串零DDL);页脚config 12新键(footer_addresses多校区/qr_*/social_*含URL校验,footerAddressLines由PortalSiteSnapshot.of拆行派生);新增展示型皮肤包campus-glow;fresh-mint2正名fresh-mint(theme.json/头注释同步);AttachmentService scene白名单加category(单页富文本贴图免清孤儿);新增PortalSideNavIT 8+PortalHomeBlocksIT 3,CategoryServiceIT/SiteServiceIT/PortalSsrIT/ThemeContractIT等单类独跑全绿;附sql/clear_cms_data.sql全量清库脚本(清cms_*六表留平台账号/主题包/上传文件,测试数据重走用)
build / build-push (push) Successful in 1m47s
build / build-push (push) Successful in 1m47s
This commit is contained in:
@@ -26,7 +26,7 @@ public class AttachmentController {
|
||||
|
||||
private final AttachmentService attachmentService;
|
||||
|
||||
/** 上传(multipart:file 必填;articleId 可空;scene 可空 banner/link/site,B2 初步标记) */
|
||||
/** 上传(multipart:file 必填;articleId 可空;scene 可空 banner/link/site/category(category=单页栏目贴图),B2 初步标记) */
|
||||
@PostMapping("/upload")
|
||||
public AttachmentVo upload(BoaUser authUserWithDept,
|
||||
@RequestParam("siteId") Integer siteId,
|
||||
|
||||
@@ -133,6 +133,8 @@ public class PortalController {
|
||||
model.addAttribute("navCategories", site.getCategories());
|
||||
model.addAttribute("currentCategoryId", categoryId);
|
||||
model.addAttribute("category", category);
|
||||
// 栏目导航三件套(docs/11):面包屑全路径 + 顶导航祖先高亮 + 左侧兄弟侧栏
|
||||
pageRenderer.attachCategoryNavigation(site, categoryId, model);
|
||||
model.addAttribute("pageTitle", seoTitle(category, site));
|
||||
model.addAttribute("seoDescription", category.getSeoDescription());
|
||||
model.addAttribute("canonical", url.category(categoryId));
|
||||
@@ -198,6 +200,8 @@ public class PortalController {
|
||||
// 面包屑栏目名(视觉精修 D2-2):按栏目树查名,查不到(栏目已删)退"正文"
|
||||
PortalCategory detailCategory = findCategory(site, detail.getCategoryId());
|
||||
model.addAttribute("currentCategoryName", detailCategory == null ? "正文" : detailCategory.getName());
|
||||
// 栏目导航三件套(docs/11):详情页同栏目页规则;栏目已删时 path 空,模板按缺省形态渲染
|
||||
pageRenderer.attachCategoryNavigation(site, detail.getCategoryId(), model);
|
||||
// 正文上传地址渲染期补基准(B-1):详情来自 cms:article 缓存(快照跨两种暴露形态共享),
|
||||
// 拷贝后重写,避免把某一形态的绝对 URL 写回缓存
|
||||
model.addAttribute("article", detail.withContent(
|
||||
@@ -356,6 +360,7 @@ public class PortalController {
|
||||
item.setTitle(a.getTitle());
|
||||
item.setSummary(a.getSummary());
|
||||
item.setCoverImage(a.getCoverImage());
|
||||
item.setSource(a.getSource());
|
||||
item.setPublishTime(a.getPublishTime());
|
||||
item.setTopmost(a.getTopmost());
|
||||
return item;
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.yunhai.cms.enums.LinkTypeEnum;
|
||||
import com.yunhai.cms.model.portal.PortalBanner;
|
||||
import com.yunhai.cms.model.portal.PortalCategory;
|
||||
import com.yunhai.cms.model.portal.PortalListData;
|
||||
import com.yunhai.cms.model.portal.PortalSideNav;
|
||||
import com.yunhai.cms.model.portal.PortalSiteSnapshot;
|
||||
import com.yunhai.cms.repository.CmsArticleRepository;
|
||||
import com.yunhai.cms.utils.PortalUrlHelper;
|
||||
@@ -28,8 +29,9 @@ import java.util.Map;
|
||||
@RequiredArgsConstructor
|
||||
public class PortalPageRenderer {
|
||||
|
||||
/** 首页每个一级栏目取 N 条 */
|
||||
/** 首页每个一级栏目取 N 条(docs/12:双栏新闻卡形态需 16 条铺满 8 行 × 2 列) */
|
||||
private static final int HOME_BLOCK_SIZE = 8;
|
||||
private static final int HOME_BLOCK_GRID_SIZE = 16;
|
||||
/** 首页置顶区取 N 条 */
|
||||
private static final int HOME_TOP_SIZE = 6;
|
||||
|
||||
@@ -46,6 +48,7 @@ public class PortalPageRenderer {
|
||||
model.addAttribute("navCategories", site.getCategories());
|
||||
List<Map<String, Object>> banners = activeBanners(site, "home", url);
|
||||
model.addAttribute("banners", banners);
|
||||
model.addAttribute("galleryBanners", activeBanners(site, "gallery", url));
|
||||
model.addAttribute("topArticles", topArticles(site.getId()));
|
||||
model.addAttribute("focusArticles", focusArticles(site.getId()));
|
||||
model.addAttribute("categoryBlocks", categoryBlocks(site.getId(), site.getCategories(), url));
|
||||
@@ -63,6 +66,71 @@ public class PortalPageRenderer {
|
||||
|
||||
// ===== 内部装配(自 PortalController 原样迁移)=====
|
||||
|
||||
/**
|
||||
* 栏目导航装配(docs/11):栏目页/详情页共用,三条模型属性——
|
||||
* categoryPath(顶级→当前路径,面包屑全路径)、navTopId(顶导航祖先高亮)、
|
||||
* sideNav(模块有二级子栏目时的左侧兄弟导航,无则不挂,模板 th:if 兜底)。
|
||||
* 全部基于站点快照树推导,零新增查询;搜索/首页等无栏目上下文的路由不调用。
|
||||
*/
|
||||
public void attachCategoryNavigation(PortalSiteSnapshot site, Integer currentCategoryId, Model model) {
|
||||
if (currentCategoryId == null) {
|
||||
return;
|
||||
}
|
||||
List<PortalCategory> path = pathFromRoot(site.getCategories(), currentCategoryId);
|
||||
model.addAttribute("categoryPath", path);
|
||||
if (path.isEmpty()) {
|
||||
return; // 栏目已删等兜底(详情页可能挂已删栏目文章),模板按缺省形态渲染
|
||||
}
|
||||
PortalCategory top = path.get(0);
|
||||
model.addAttribute("navTopId", top.getId());
|
||||
if (!top.getChildren().isEmpty()) {
|
||||
PortalSideNav nav = new PortalSideNav();
|
||||
nav.setTop(top);
|
||||
nav.setItems(top.getChildren());
|
||||
nav.setActiveId(resolveActiveId(path, currentCategoryId));
|
||||
model.addAttribute("sideNav", nav);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 侧栏高亮位:当前为二级=当前栏目;一级直点=null(侧栏头即模块名,无高亮项);
|
||||
* 三级及更深=所在二级分支(决策点②:固定列二级层,高亮当前所属分支)。
|
||||
*/
|
||||
private Integer resolveActiveId(List<PortalCategory> path, Integer currentCategoryId) {
|
||||
if (path.size() == 2) {
|
||||
return currentCategoryId;
|
||||
}
|
||||
return path.size() > 2 ? path.get(1).getId() : null;
|
||||
}
|
||||
|
||||
/** 自栏目树求 根→目标 路径(含两端);目标不存在返回空表 */
|
||||
private List<PortalCategory> pathFromRoot(List<PortalCategory> roots, Integer categoryId) {
|
||||
for (PortalCategory root : roots) {
|
||||
List<PortalCategory> hit = pathFromRootDeep(root, categoryId);
|
||||
if (hit != null) {
|
||||
return hit;
|
||||
}
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
private List<PortalCategory> pathFromRootDeep(PortalCategory node, Integer categoryId) {
|
||||
if (node.getId().equals(categoryId)) {
|
||||
List<PortalCategory> path = new ArrayList<>();
|
||||
path.add(node);
|
||||
return path;
|
||||
}
|
||||
for (PortalCategory child : node.getChildren()) {
|
||||
List<PortalCategory> sub = pathFromRootDeep(child, categoryId);
|
||||
if (sub != null) {
|
||||
sub.add(0, node);
|
||||
return sub;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/** 启用中且在时间窗内的指定位轮播,并解析为完整跳转 URL */
|
||||
private List<Map<String, Object>> activeBanners(PortalSiteSnapshot site, String position,
|
||||
PortalUrlHelper url) {
|
||||
@@ -137,6 +205,7 @@ public class PortalPageRenderer {
|
||||
/**
|
||||
* 首页一级栏目区块:每栏目精确取 top N(列表型栏目才出区块)。
|
||||
* D1-2:区块聚合列表型子孙栏目文章(文章通常挂叶子栏目,父栏目区块必须显示汇总)。
|
||||
* docs/12:区块带出栏目 homeStyle,双栏新闻卡(news_grid)取 16 条铺满双栏,其余取 8 条。
|
||||
*/
|
||||
private List<Map<String, Object>> categoryBlocks(Integer siteId, List<PortalCategory> categories,
|
||||
PortalUrlHelper url) {
|
||||
@@ -145,12 +214,14 @@ public class PortalPageRenderer {
|
||||
if (category.getType() == null || category.getType() != CategoryTypeEnum.LIST.getCode()) {
|
||||
continue;
|
||||
}
|
||||
int blockSize = "news_grid".equals(category.getHomeStyle())
|
||||
? HOME_BLOCK_GRID_SIZE : HOME_BLOCK_SIZE;
|
||||
List<Integer> categoryIds = new ArrayList<>();
|
||||
collectListDescendantIds(category, categoryIds);
|
||||
List<com.yunhai.cms.entity.CmsArticle> articles = articleRepository
|
||||
.findBySiteIdAndCategoryIdInAndStatus(siteId, categoryIds,
|
||||
com.yunhai.cms.enums.ArticleStatusEnum.PUBLISHED.getCode(),
|
||||
PageRequest.of(0, HOME_BLOCK_SIZE,
|
||||
PageRequest.of(0, blockSize,
|
||||
Sort.by(Sort.Order.desc("topmost"), Sort.Order.desc("publishTime"))))
|
||||
.getContent();
|
||||
List<PortalListData.Item> items = new ArrayList<>();
|
||||
@@ -159,6 +230,7 @@ public class PortalPageRenderer {
|
||||
}
|
||||
Map<String, Object> block = new HashMap<>();
|
||||
block.put("category", category);
|
||||
block.put("homeStyle", category.getHomeStyle());
|
||||
block.put("items", items);
|
||||
block.put("moreUrl", url.category(category.getId()));
|
||||
blocks.add(block);
|
||||
@@ -182,6 +254,7 @@ public class PortalPageRenderer {
|
||||
item.setTitle(a.getTitle());
|
||||
item.setSummary(a.getSummary());
|
||||
item.setCoverImage(a.getCoverImage());
|
||||
item.setSource(a.getSource());
|
||||
item.setPublishTime(a.getPublishTime());
|
||||
item.setTopmost(a.getTopmost());
|
||||
return item;
|
||||
|
||||
@@ -42,6 +42,13 @@ public class CmsCategory extends BaseEntity {
|
||||
@Column(name = "template", length = 64)
|
||||
private String template;
|
||||
|
||||
/**
|
||||
* 首页展示形态(docs/11,空=主题默认卡):news_grid=双栏新闻卡、media_focus=来源标签列表。
|
||||
* 仅一级列表型栏目在首页生效;旧主题不消费该字段(仅条数外溢 16)。
|
||||
*/
|
||||
@Column(name = "home_style", length = 32)
|
||||
private String homeStyle;
|
||||
|
||||
/**
|
||||
* 单页栏目内容(type=2 时门户直接渲染;经 jsoup 白名单清洗入库)。
|
||||
* 注:方案 §8.1 要求单页直接渲染 content,但 §5.1 DDL 漏列,此处补齐(ddl-auto=update 自动加列)。
|
||||
|
||||
@@ -38,6 +38,10 @@ public class CategorySaveRequest {
|
||||
@Size(max = 64)
|
||||
private String template;
|
||||
|
||||
/** 首页展示形态(docs/11:空=默认卡 / news_grid / media_focus,服务端白名单校验) */
|
||||
@Size(max = 32)
|
||||
private String homeStyle;
|
||||
|
||||
/** 单页栏目内容(type=2 有效,服务端 jsoup 清洗后入库) */
|
||||
private String content;
|
||||
|
||||
|
||||
@@ -25,6 +25,9 @@ public class PortalCategory {
|
||||
|
||||
private String template;
|
||||
|
||||
/** 首页展示形态(docs/11:空=默认卡 / news_grid / media_focus) */
|
||||
private String homeStyle;
|
||||
|
||||
private String seoTitle;
|
||||
|
||||
private String seoDescription;
|
||||
@@ -42,6 +45,7 @@ public class PortalCategory {
|
||||
node.setType(c.getType());
|
||||
node.setLinkUrl(c.getLinkUrl());
|
||||
node.setTemplate(c.getTemplate());
|
||||
node.setHomeStyle(c.getHomeStyle());
|
||||
node.setSeoTitle(c.getSeoTitle());
|
||||
node.setSeoDescription(c.getSeoDescription());
|
||||
node.setContent(c.getContent());
|
||||
@@ -60,6 +64,7 @@ public class PortalCategory {
|
||||
copy.type = type;
|
||||
copy.linkUrl = linkUrl;
|
||||
copy.template = template;
|
||||
copy.homeStyle = homeStyle;
|
||||
copy.seoTitle = seoTitle;
|
||||
copy.seoDescription = seoDescription;
|
||||
copy.content = content;
|
||||
|
||||
@@ -37,6 +37,9 @@ public class PortalListData {
|
||||
|
||||
private String coverImage;
|
||||
|
||||
/** 来源(转载出处,docs/11:媒体聚焦来源徽标用;可空) */
|
||||
private String source;
|
||||
|
||||
private LocalDateTime publishTime;
|
||||
|
||||
private Integer topmost;
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.yunhai.cms.model.portal;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 门户左侧栏目导航(docs/11):模块存在二级子栏目时,栏目页/详情页左侧
|
||||
* 展示"模块名 + 全部二级子栏目",当前位高亮。纯展示模型,由站点快照树推导。
|
||||
*/
|
||||
@Data
|
||||
public class PortalSideNav {
|
||||
|
||||
/** 顶级模块(侧栏头) */
|
||||
private PortalCategory top;
|
||||
|
||||
/** 全部二级子栏目(快照序,列表/单页/外链型均列出) */
|
||||
private List<PortalCategory> items = new ArrayList<>();
|
||||
|
||||
/** 当前高亮栏目 id:当前为二级时=当前栏目;三级及更深时=所在二级分支;一级直点=null */
|
||||
private Integer activeId;
|
||||
}
|
||||
@@ -63,6 +63,12 @@ public class PortalSiteSnapshot {
|
||||
/** 站点级自定义样式(B3:config_json.custom_css,保存时经服务端黑名单校验,存量直读) */
|
||||
private String customCss = "";
|
||||
|
||||
/**
|
||||
* 页脚多行地址(docs/11:config_json.footer_addresses 按 \r?\n 拆分去空行,装配期派生随快照缓存,
|
||||
* 规避模板内拆分的表达式平台差异)。
|
||||
*/
|
||||
private List<String> footerAddressLines = new ArrayList<>();
|
||||
|
||||
public static PortalSiteSnapshot of(CmsSite site) {
|
||||
PortalSiteSnapshot snapshot = new PortalSiteSnapshot();
|
||||
snapshot.setId(site.getId());
|
||||
@@ -78,9 +84,25 @@ public class PortalSiteSnapshot {
|
||||
snapshot.setDomain(ExtJsonUtils.getString(config, "domain"));
|
||||
snapshot.setPublicBaseUrl(ExtJsonUtils.getString(config, "public_base_url"));
|
||||
snapshot.setCustomCss(ExtJsonUtils.getString(config, "custom_css"));
|
||||
snapshot.setFooterAddressLines(splitAddressLines(ExtJsonUtils.getString(config, "footer_addresses")));
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
/** footer_addresses 多行文本拆行(\r?\n 分隔,去空行;空值返回空列表) */
|
||||
private static List<String> splitAddressLines(String raw) {
|
||||
List<String> lines = new ArrayList<>();
|
||||
if (raw == null || raw.trim().isEmpty()) {
|
||||
return lines;
|
||||
}
|
||||
for (String line : raw.split("\r?\n")) {
|
||||
String trimmed = line.trim();
|
||||
if (!trimmed.isEmpty()) {
|
||||
lines.add(trimmed);
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览用浅拷贝(方案 §5.5):集合字段共享只读引用;覆盖 theme/themeParams/themeVarsCss
|
||||
* 的操作由预览方在拷贝上进行,不影响缓存中的原快照。
|
||||
@@ -104,6 +126,7 @@ public class PortalSiteSnapshot {
|
||||
copy.setThemeParams(themeParams);
|
||||
copy.setThemeVarsCss(themeVarsCss);
|
||||
copy.setCustomCss(customCss);
|
||||
copy.setFooterAddressLines(footerAddressLines);
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,9 @@ public class CategoryTreeNode {
|
||||
|
||||
private String template;
|
||||
|
||||
/** 首页展示形态(docs/11:空=默认卡 / news_grid / media_focus) */
|
||||
private String homeStyle;
|
||||
|
||||
/** 单页栏目内容(type=2 时编辑/渲染用) */
|
||||
private String content;
|
||||
|
||||
@@ -50,6 +53,7 @@ public class CategoryTreeNode {
|
||||
node.setType(c.getType());
|
||||
node.setLinkUrl(c.getLinkUrl());
|
||||
node.setTemplate(c.getTemplate());
|
||||
node.setHomeStyle(c.getHomeStyle());
|
||||
node.setContent(c.getContent());
|
||||
node.setSortNo(c.getSortNo());
|
||||
node.setSeoTitle(c.getSeoTitle());
|
||||
|
||||
@@ -56,8 +56,8 @@ public class AttachmentService {
|
||||
private static final List<String> IMAGE_EXTS = Arrays.asList(
|
||||
"jpg", "jpeg", "png", "gif", "webp", "bmp", "ico");
|
||||
|
||||
/** 上传 scene 白名单(B2 附件引用模型):banner=轮播 / link=友链 / site=站点配置 */
|
||||
private static final List<String> ALLOWED_SCENES = Arrays.asList("banner", "link", "site");
|
||||
/** 上传 scene 白名单(B2 附件引用模型):banner=轮播 / link=友链 / site=站点配置 / category=单页栏目贴图 */
|
||||
private static final List<String> ALLOWED_SCENES = Arrays.asList("banner", "link", "site", "category");
|
||||
|
||||
private final CmsAttachmentRepository attachmentRepository;
|
||||
private final SiteService siteService;
|
||||
@@ -69,12 +69,12 @@ public class AttachmentService {
|
||||
@Value("${FILE_WEB_PATH:/cms/upload/}")
|
||||
private String fileWebPath;
|
||||
|
||||
/** 上传(编辑器图片/附件/轮播图/Logo 复用);articleId 可空;scene 可空(banner/link/site,B2 初步标记) */
|
||||
/** 上传(编辑器图片/附件/轮播图/Logo 复用);articleId 可空;scene 可空(banner/link/site/category,B2 初步标记) */
|
||||
@Transactional
|
||||
public AttachmentVo upload(BoaUser user, Integer siteId, Integer articleId, String scene, MultipartFile file) {
|
||||
CmsSite site = siteService.requireManageable(user, siteId);
|
||||
if (StringUtils.hasText(scene) && !ALLOWED_SCENES.contains(scene)) {
|
||||
throw new BizException(ErrorCode.PARAM_ERROR, "scene 非法:" + scene + "(仅 banner/link/site)");
|
||||
throw new BizException(ErrorCode.PARAM_ERROR, "scene 非法:" + scene + "(仅 banner/link/site/category)");
|
||||
}
|
||||
if (file == null || file.isEmpty()) {
|
||||
throw new BizException(ErrorCode.PARAM_ERROR, "上传文件不能为空");
|
||||
|
||||
@@ -33,6 +33,10 @@ import java.util.stream.Collectors;
|
||||
@RequiredArgsConstructor
|
||||
public class CategoryService {
|
||||
|
||||
/** 首页展示形态白名单(docs/11;空=主题默认卡) */
|
||||
private static final java.util.Set<String> HOME_STYLE_VALUES =
|
||||
new java.util.HashSet<>(java.util.Arrays.asList("news_grid", "media_focus"));
|
||||
|
||||
private final CmsCategoryRepository categoryRepository;
|
||||
private final CmsArticleRepository articleRepository;
|
||||
private final com.yunhai.cms.repository.CmsBannerRepository bannerRepository;
|
||||
@@ -70,6 +74,10 @@ public class CategoryService {
|
||||
if (type == CategoryTypeEnum.LINK && !StringUtils.hasText(req.getLinkUrl())) {
|
||||
throw new BizException(ErrorCode.PARAM_ERROR, "外链栏目必须填写外链地址");
|
||||
}
|
||||
if (StringUtils.hasText(req.getHomeStyle()) && !HOME_STYLE_VALUES.contains(req.getHomeStyle())) {
|
||||
throw new BizException(ErrorCode.PARAM_ERROR,
|
||||
"首页展示形态非法:" + req.getHomeStyle() + "(仅支持 news_grid / media_focus)");
|
||||
}
|
||||
CmsCategory category;
|
||||
if (req.getId() == null) {
|
||||
category = new CmsCategory();
|
||||
@@ -87,6 +95,7 @@ public class CategoryService {
|
||||
category.setType(type.getCode());
|
||||
category.setLinkUrl(req.getLinkUrl());
|
||||
category.setTemplate(req.getTemplate());
|
||||
category.setHomeStyle(StringUtils.hasText(req.getHomeStyle()) ? req.getHomeStyle() : null);
|
||||
category.setContent(HtmlSanitizer.clean(req.getContent()));
|
||||
category.setSortNo(req.getSortNo() == null ? 0 : req.getSortNo());
|
||||
category.setSeoTitle(req.getSeoTitle());
|
||||
|
||||
@@ -194,6 +194,7 @@ public class PortalQueryService {
|
||||
item.setTitle(a.getTitle());
|
||||
item.setSummary(a.getSummary());
|
||||
item.setCoverImage(a.getCoverImage());
|
||||
item.setSource(a.getSource());
|
||||
item.setPublishTime(a.getPublishTime());
|
||||
item.setTopmost(a.getTopmost());
|
||||
data.getItems().add(item);
|
||||
@@ -246,6 +247,7 @@ public class PortalQueryService {
|
||||
item.setTitle(a.getTitle());
|
||||
item.setSummary(a.getSummary());
|
||||
item.setCoverImage(a.getCoverImage());
|
||||
item.setSource(a.getSource());
|
||||
item.setPublishTime(a.getPublishTime());
|
||||
item.setTopmost(a.getTopmost());
|
||||
return item;
|
||||
|
||||
@@ -287,7 +287,8 @@ public class SiteService {
|
||||
/**
|
||||
* config_json 校验(docs/09 §3.1,替代旧「空 Map 且长度>2」判定):
|
||||
* ① 必须是 JSON 对象(修复 A-09:带空白空对象误判);② 键名仅字母/数字/下划线;
|
||||
* ③ 已知键按类型校验(domain 域名 / public_base_url 完整 URL / logo、favicon 资产路径)。
|
||||
* ③ 已知键按类型校验(domain 域名 / public_base_url、social_* 完整 URL /
|
||||
* logo、favicon、qr_wechat、qr_weibo、news_bg_image 资产路径,docs/12)。
|
||||
* 系统托管键 theme_params 直通;非字符串值(wechat 等预留对象)不校验原样放行——
|
||||
* 只拦新脏数据,不做严格 schema 拒绝未知键(D6-2)。
|
||||
*/
|
||||
@@ -337,6 +338,10 @@ public class SiteService {
|
||||
}
|
||||
break;
|
||||
case "public_base_url":
|
||||
case "social_wechat":
|
||||
case "social_weibo":
|
||||
case "social_douyin":
|
||||
case "social_bilibili":
|
||||
if (!URL_PATTERN.matcher(text).matches()) {
|
||||
throw new BizException(ErrorCode.PARAM_ERROR,
|
||||
"对外基地址必须是完整的 http(s):// 地址:" + text);
|
||||
@@ -344,6 +349,9 @@ public class SiteService {
|
||||
break;
|
||||
case "logo":
|
||||
case "favicon":
|
||||
case "qr_wechat":
|
||||
case "qr_weibo":
|
||||
case "news_bg_image":
|
||||
if (!ASSET_PATTERN.matcher(text).matches()) {
|
||||
throw new BizException(ErrorCode.PARAM_ERROR,
|
||||
key + " 必须是站点上传路径(/ 开头)或 http(s) 地址");
|
||||
|
||||
Reference in New Issue
Block a user