From a9323adb13ffcf3eb5de68cf4138b2fd466fc5d2 Mon Sep 17 00:00:00 2001 From: lili Date: Sun, 21 Jun 2026 10:09:07 -0700 Subject: [PATCH] =?UTF-8?q?fix(content):=20planning=20=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E5=BF=AB=E7=85=A7=E7=89=A9=E7=90=86=E5=88=A0=E9=99=A4,?= =?UTF-8?q?=E4=BF=AE=E4=BA=8C=E6=AC=A1=E4=BF=9D=E5=AD=98=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E9=94=AE=20500?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit captureFieldSnapshot 的 deleteBySectionId 原走 MyBatis-Plus 逻辑删除(@TableLogic): 被删物理行(deleted=TRUE)仍占唯一约束 uk(tenant_id,section_id,field_key)(不含 deleted), 用户二次保存同 section 字段时 INSERT 撞键→DuplicateKeyException→规划保存 500。 改原生 @Delete 物理清行,根治幂等(快照是可重建派生数据,物理删除无审计损失)。 根因:全量 e2e 暴露 planning-edit save 500(work-schema-binding 为并发 flaky,单跑绿)。 验证:planning-edit e2e 真后端 1 passed;连续 2 次 save 同 section code=0(rev 3→4,旧 jar 第 2 次必 500)。 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../dal/mysql/PlanningFieldSnapshotMapper.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/dal/mysql/PlanningFieldSnapshotMapper.java b/muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/dal/mysql/PlanningFieldSnapshotMapper.java index bc62b704..346df2eb 100644 --- a/muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/dal/mysql/PlanningFieldSnapshotMapper.java +++ b/muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/dal/mysql/PlanningFieldSnapshotMapper.java @@ -3,7 +3,9 @@ package cn.iocoder.muse.module.content.dal.mysql; import cn.iocoder.muse.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.muse.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.muse.module.content.dal.dataobject.PlanningFieldSnapshotDO; +import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; import java.util.List; @@ -17,11 +19,16 @@ import java.util.List; @Mapper public interface PlanningFieldSnapshotMapper extends BaseMapperX { - /** 删除某 section 的旧快照(保存 planning 时先删后写,保证重复保存幂等)。 */ - default int deleteBySectionId(Long sectionId) { - return delete(new LambdaQueryWrapperX() - .eq(PlanningFieldSnapshotDO::getSectionId, sectionId)); - } + /** + * 物理删除某 section 的旧快照(保存 planning 时先删后写,保证重复保存幂等)。 + * + *

WHY 物理删除而非 MyBatis-Plus 逻辑删除:本表是字段用量投影(可重建的派生数据、无审计价值), + * 唯一约束 uk(tenant_id, section_id, field_key) 不含 deleted 列。若走 @TableLogic 逻辑删除, + * 被删的物理行(deleted=TRUE)仍占用 uk,再次保存同字段时 INSERT 必撞唯一键→DuplicateKeyException(二次保存即 500)。 + * 故用原生 DELETE 清干净物理行;tenant 插件会对该 DELETE 追加 tenant_id 条件,且 section_id 全局唯一,双重隔离。

+ */ + @Delete("DELETE FROM muse_content_planning_field_snapshot WHERE section_id = #{sectionId}") + int deleteBySectionId(@Param("sectionId") Long sectionId); /** 查某 section 现有快照(测试 / 校验用)。 */ default List selectListBySectionId(Long sectionId) {