feat(cms): 增强单页与导航栏目的一对一绑定支持

- CmsNavigation 中新增绑定单页的访问路径、标题和状态字段,完善 VO 透传
- CmsPage 新增 navigationId 字段,用于关联导航栏目,支持按导航过滤单页
- Mapper XML 调整,CmsNavigation 查询添加 cms_page 相关字段联表查询
- CmsPage 查询添加左联导航栏目,支持按导航 ID 过滤
- CmsPageParam 新增 navigationId 查询参数支持
- CmsPageServiceImpl 增加保存、更新单页时的导航绑定逻辑,实现单页与导航的唯一绑定关系维护
- 删除单页时,先清理所有指向该单页的导航绑定,确保数据一致性
- bindNavigation 方法实现解绑与换绑逻辑,保证导航与单页保持一对一关系
- 所有单页导航绑定相关操作均添加事务支持,确保操作原子性
This commit is contained in:
2026-07-30 13:54:40 +08:00
parent 207b16942a
commit b2dc6a9a0b
6 changed files with 88 additions and 5 deletions

View File

@@ -121,9 +121,21 @@ public class CmsNavigation implements Serializable {
@TableField(exist = false) @TableField(exist = false)
private Integer parentPosition; private Integer parentPosition;
@Schema(description = "绑定的页面(已废弃)") @Schema(description = "绑定的单页IDcms_page.page_id单页管理页「关联导航」写入与单页一一对应")
private Integer pageId; private Integer pageId;
@Schema(description = "绑定单页的访问路径slugVO 透传,来自 cms_page.path")
@TableField(exist = false)
private String pagePath;
@Schema(description = "绑定单页的标题VO 透传,来自 cms_page.title")
@TableField(exist = false)
private String pageTitle;
@Schema(description = "绑定单页的状态 0草稿1已发布2已下线VO 透传,来自 cms_page.status")
@TableField(exist = false)
private Integer pageStatus;
@Schema(description = "详情页ID") @Schema(description = "详情页ID")
private Integer itemId; private Integer itemId;

View File

@@ -86,6 +86,10 @@ public class CmsPage implements Serializable {
@TableField(exist = false) @TableField(exist = false)
private String statusText; private String statusText;
@Schema(description = "关联的导航栏目IDcms_navigation.navigation_id存于 cms_navigation.page_id本实体不持久化该字段仅作为 VO 透传与写回导航的入参")
@TableField(exist = false)
private Integer navigationId;
public String getStatusText() { public String getStatusText() {
if (this.status == null) return ""; if (this.status == null) return "";
switch (this.status) { switch (this.status) {

View File

@@ -4,11 +4,13 @@
<!-- 关联查询sql --> <!-- 关联查询sql -->
<sql id="selectSql"> <sql id="selectSql">
SELECT a.*, b.title as parentName, b.position as parentPosition, c.name as modelName SELECT a.*, b.title as parentName, b.position as parentPosition, c.name as modelName,
p.path AS pagePath, p.title AS pageTitle, p.status AS pageStatus
FROM cms_navigation a FROM cms_navigation a
LEFT JOIN cms_navigation b ON a.parent_id = b.navigation_id AND b.deleted = 0 AND b.tenant_id = a.tenant_id LEFT JOIN cms_navigation b ON a.parent_id = b.navigation_id AND b.deleted = 0 AND b.tenant_id = a.tenant_id
<!-- c.model = 0 会触发 MySQL 字符串转数字比较,导致几乎所有模型都匹配,从而把导航行成倍放大 --> <!-- c.model = 0 会触发 MySQL 字符串转数字比较,导致几乎所有模型都匹配,从而把导航行成倍放大 -->
LEFT JOIN cms_model c ON a.model = c.model AND c.deleted = 0 AND c.tenant_id = a.tenant_id LEFT JOIN cms_model c ON a.model = c.model AND c.deleted = 0 AND c.tenant_id = a.tenant_id
LEFT JOIN cms_page p ON p.page_id = a.page_id AND p.deleted = 0
<where> <where>
<if test="param.navigationId != null"> <if test="param.navigationId != null">
AND a.navigation_id = #{param.navigationId} AND a.navigation_id = #{param.navigationId}

View File

@@ -4,12 +4,16 @@
<!-- 单页查询sql --> <!-- 单页查询sql -->
<sql id="selectSql"> <sql id="selectSql">
SELECT a.* SELECT a.*, n.navigation_id AS navigationId
FROM cms_page a FROM cms_page a
LEFT JOIN cms_navigation n ON n.page_id = a.page_id AND n.deleted = 0 AND n.tenant_id = a.tenant_id
<where> <where>
<if test="param.pageId != null"> <if test="param.pageId != null">
AND a.page_id = #{param.pageId} AND a.page_id = #{param.pageId}
</if> </if>
<if test="param.navigationId != null">
AND n.navigation_id = #{param.navigationId}
</if>
<if test="param.title != null"> <if test="param.title != null">
AND a.title LIKE CONCAT('%', #{param.title}, '%') AND a.title LIKE CONCAT('%', #{param.title}, '%')
</if> </if>

View File

@@ -49,4 +49,9 @@ public class CmsPageParam extends BaseParam {
@Schema(description = "单页ID集查询") @Schema(description = "单页ID集查询")
@TableField(exist = false) @TableField(exist = false)
private java.util.Set<Integer> pageIds; private java.util.Set<Integer> pageIds;
@Schema(description = "关联的导航栏目IDcms_navigation.navigation_id用于按导航过滤单页")
@TableField(exist = false)
@QueryField(type = QueryType.EQ)
private Integer navigationId;
} }

View File

@@ -4,15 +4,19 @@ import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.gxwebsoft.cms.entity.CmsNavigation;
import com.gxwebsoft.cms.entity.CmsPage; import com.gxwebsoft.cms.entity.CmsPage;
import com.gxwebsoft.cms.mapper.CmsNavigationMapper;
import com.gxwebsoft.cms.mapper.CmsPageMapper; import com.gxwebsoft.cms.mapper.CmsPageMapper;
import com.gxwebsoft.cms.param.CmsPageParam; import com.gxwebsoft.cms.param.CmsPageParam;
import com.gxwebsoft.cms.service.CmsPageService; import com.gxwebsoft.cms.service.CmsPageService;
import com.gxwebsoft.common.core.web.PageParam; import com.gxwebsoft.common.core.web.PageParam;
import com.gxwebsoft.common.core.web.PageResult; import com.gxwebsoft.common.core.web.PageResult;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.io.Serializable;
import java.util.List; import java.util.List;
/** /**
@@ -24,6 +28,9 @@ import java.util.List;
@Service @Service
public class CmsPageServiceImpl extends com.baomidou.mybatisplus.extension.service.impl.ServiceImpl<CmsPageMapper, CmsPage> implements CmsPageService { public class CmsPageServiceImpl extends com.baomidou.mybatisplus.extension.service.impl.ServiceImpl<CmsPageMapper, CmsPage> implements CmsPageService {
@Resource
private CmsNavigationMapper cmsNavigationMapper;
@Override @Override
public PageResult<CmsPage> pageRel(CmsPageParam param) { public PageResult<CmsPage> pageRel(CmsPageParam param) {
PageParam<CmsPage, CmsPageParam> page = new PageParam<>(param); PageParam<CmsPage, CmsPageParam> page = new PageParam<>(param);
@@ -72,6 +79,7 @@ public class CmsPageServiceImpl extends com.baomidou.mybatisplus.extension.servi
} }
@Override @Override
@Transactional(rollbackFor = Exception.class)
public boolean saveRel(CmsPage page) { public boolean saveRel(CmsPage page) {
if (StrUtil.isBlank(page.getPath())) { if (StrUtil.isBlank(page.getPath())) {
return false; return false;
@@ -88,10 +96,15 @@ public class CmsPageServiceImpl extends com.baomidou.mybatisplus.extension.servi
if (page.getViews() == null) { if (page.getViews() == null) {
page.setViews(0); page.setViews(0);
} }
return save(page); boolean saved = save(page);
if (saved && page.getNavigationId() != null) {
bindNavigation(page.getPageId(), page.getNavigationId(), page.getTenantId());
}
return saved;
} }
@Override @Override
@Transactional(rollbackFor = Exception.class)
public boolean updateByIdRel(CmsPage page) { public boolean updateByIdRel(CmsPage page) {
if (page.getPageId() == null) { if (page.getPageId() == null) {
return false; return false;
@@ -102,6 +115,49 @@ public class CmsPageServiceImpl extends com.baomidou.mybatisplus.extension.servi
if (page.getSortNumber() == null) { if (page.getSortNumber() == null) {
page.setSortNumber(0); page.setSortNumber(0);
} }
return updateById(page); boolean updated = updateById(page);
if (updated) {
bindNavigation(page.getPageId(), page.getNavigationId(), page.getTenantId());
}
return updated;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean removeById(Serializable id) {
// 先清空所有指向该单页的导航引用navigation.page_id = NULL再逻辑删除单页
cmsNavigationMapper.update(null, Wrappers.lambdaUpdate(CmsNavigation.class)
.eq(CmsNavigation::getPageId, (Integer) id)
.set(CmsNavigation::getPageId, null));
return super.removeById(id);
}
/**
* 维护「单页 ↔ 导航」的一一对应关系:
* 1) navigationId 为 null → 解绑(清掉所有绑了该 page 的导航引用)
* 2) 否则:先清目标导航此前绑定的其他单页、再清本单页此前绑定的其他导航,最后建立新绑定
* 依赖 UNIQUE(tenant_id, page_id) 保证一个 page 只被一个导航绑;
* navigationId 为单值列(每个导航最多一个 page_id天然保证一个导航只绑一篇单页。
*/
private void bindNavigation(Integer pageId, Integer navigationId, Integer tenantId) {
if (navigationId == null) {
cmsNavigationMapper.update(null, Wrappers.lambdaUpdate(CmsNavigation.class)
.eq(CmsNavigation::getPageId, pageId)
.set(CmsNavigation::getPageId, null));
return;
}
// 清掉目标导航此前绑定的其他单页(保证一个导航只绑一篇单页)
cmsNavigationMapper.update(null, Wrappers.lambdaUpdate(CmsNavigation.class)
.eq(CmsNavigation::getNavigationId, navigationId)
.set(CmsNavigation::getPageId, null));
// 清掉本单页此前绑定的其他导航(换绑场景)
cmsNavigationMapper.update(null, Wrappers.lambdaUpdate(CmsNavigation.class)
.eq(CmsNavigation::getPageId, pageId)
.set(CmsNavigation::getPageId, null));
// 建立新绑定
cmsNavigationMapper.update(null, Wrappers.lambdaUpdate(CmsNavigation.class)
.eq(CmsNavigation::getNavigationId, navigationId)
.eq(CmsNavigation::getTenantId, tenantId)
.set(CmsNavigation::getPageId, pageId));
} }
} }