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:
@@ -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