周末修订
build / build-push (push) Successful in 3m59s

This commit is contained in:
chenxing
2026-09-15 23:41:34 +08:00
parent e4b843cf00
commit 1cb95225a6
52 changed files with 1250 additions and 3788 deletions
@@ -68,12 +68,11 @@ public class AttachmentService {
/**
* 上传 scene 白名单(B2 附件引用模型):banner=轮播 / link=友链 / site=站点配置 /
* category=单页栏目贴图 / svc_item=事项指南附件(列表可见) / svc_item_asset=事项封面与正文内嵌资产。
* 事项侧拆两个场景的原因:指南附件查询(refType=svc_item, refId=itemId)必须只返回"用户显式上传的附件",
* 封面与正文图片若同场景会被混进指南页附件列表。
* category=单页栏目贴图。svc_item/svc_item_asset 两个事项场景已随事项库迁出办事大厅
* (V2.0,见 docs/19 §九),CMS 侧下线。
*/
private static final List<String> ALLOWED_SCENES =
Arrays.asList("banner", "link", "site", "category", "svc_item", "svc_item_asset");
Arrays.asList("banner", "link", "site", "category");
private final CmsAttachmentRepository attachmentRepository;
private final SiteService siteService;
@@ -85,13 +84,13 @@ public class AttachmentService {
@Value("${FILE_WEB_PATH:/cms/upload/}")
private String fileWebPath;
/** 上传(编辑器图片/附件/轮播图/Logo 复用);articleId 可空;scene 可空(banner/link/site/category/svc_item/svc_item_asset,B2 初步标记) */
/** 上传(编辑器图片/附件/轮播图/Logo 复用);articleId 可空;scene 可空(banner/link/site/category) */
@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/category/svc_item/svc_item_asset)");
"scene 非法:" + scene + "(仅 banner/link/site/category)");
}
if (file == null || file.isEmpty()) {
throw new BizException(ErrorCode.PARAM_ERROR, "上传文件不能为空");
@@ -37,6 +37,9 @@ public class CategoryService {
private static final java.util.Set<String> HOME_STYLE_VALUES =
new java.util.HashSet<>(java.util.Arrays.asList("news_grid", "media_focus"));
/** 祖先链上溯上限(防脏数据自环导致死循环;正常栏目树远低于此深度) */
private static final int MAX_ANCESTOR_WALK = 64;
private final CmsCategoryRepository categoryRepository;
private final CmsArticleRepository articleRepository;
private final com.yunhai.cms.repository.CmsBannerRepository bannerRepository;
@@ -119,9 +122,29 @@ public class CategoryService {
if (!parent.getSiteId().equals(req.getSiteId())) {
throw new BizException(ErrorCode.PARAM_ERROR, "父栏目不属于该站点");
}
if (category.getId() != null && parent.getParentId() != null
&& parent.getParentId().equals(category.getId())) {
throw new BizException(ErrorCode.PARAM_ERROR, "不支持把子栏目设为父栏目(成环)");
requireNoCycle(category, parent);
}
/**
* 防环:从目标父栏目沿<b>祖先链</b>上溯,遇到"正在保存的栏目自身"即为环。
*
* <p>原实现只挡一级父子互换(parent.parentId == 自己),三级及以上的环写得进去;
* 环会让整枝栏目在门户导航与栏目树上消失,故补成任意层级(前端下拉同步放开置灰,
* 改为保存前同口径校验 + 明确提示,前后端口径一致)。
*/
private void requireNoCycle(CmsCategory category, CmsCategory parent) {
if (category.getId() == null) {
return;
}
int guard = 0;
CmsCategory cursor = parent;
while (cursor != null && guard++ <= MAX_ANCESTOR_WALK) {
if (category.getId().equals(cursor.getId())) {
throw new BizException(ErrorCode.PARAM_ERROR,
"不支持把栏目挂到自身或自己的子栏目下(会成环)");
}
Integer next = cursor.getParentId();
cursor = (next == null || next == 0) ? null : categoryRepository.findById(next).orElse(null);
}
}