diff --git a/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/controller/admin/contentnode/ContentNodeAuthoringController.java b/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/controller/admin/contentnode/ContentNodeAuthoringController.java index 52e0a880..d4d743df 100644 --- a/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/controller/admin/contentnode/ContentNodeAuthoringController.java +++ b/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/controller/admin/contentnode/ContentNodeAuthoringController.java @@ -51,7 +51,7 @@ public class ContentNodeAuthoringController { private ContentNodeAuthoringCommand toCommand(ContentNodeDraftReqVO request, Integer expectedVersion) { return new ContentNodeAuthoringCommand(request.getEntryId(), request.getParentId(), request.getName(), - request.getTitle(), request.getNodeType(), request.getMarkerType(), request.getLeaf(), - request.getSelectable(), request.getHidden(), request.getSortOrder(), request.getMetadata(), expectedVersion); + request.getTitle(), request.getNodeType(), request.getMarkerType(), request.getSelectable(), + request.getSortOrder(), request.getMetadata(), expectedVersion); } } diff --git a/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/controller/admin/contentnode/vo/ContentNodeDraftReqVO.java b/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/controller/admin/contentnode/vo/ContentNodeDraftReqVO.java index 697b8105..0a9d3b49 100644 --- a/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/controller/admin/contentnode/vo/ContentNodeDraftReqVO.java +++ b/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/controller/admin/contentnode/vo/ContentNodeDraftReqVO.java @@ -15,9 +15,7 @@ public class ContentNodeDraftReqVO { @Size(max = 200) private String title; @NotBlank @Size(max = 50) private String nodeType; @Size(max = 50) private String markerType; - @NotNull private Boolean leaf; @NotNull private Boolean selectable; - @NotNull private Boolean hidden; private Integer sortOrder; private String metadata; } diff --git a/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/mysql/catalog/ContentNodeMapper.java b/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/mysql/catalog/ContentNodeMapper.java index cd38a04c..0481ecf8 100644 --- a/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/mysql/catalog/ContentNodeMapper.java +++ b/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/mysql/catalog/ContentNodeMapper.java @@ -65,6 +65,7 @@ public interface ContentNodeMapper extends BaseMapperX { .eq(ContentNodeDO::getAuthoringVersion, expectedVersion) .set(ContentNodeDO::getPublicationStatus, targetStatus) .set(ContentNodeDO::getIsActive, active) + .set(ContentNodeDO::getIsHidden, !active) .set(ContentNodeDO::getAuthoringVersion, expectedVersion + 1)); } diff --git a/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/contentnode/authoring/ContentNodeAuthoringCommand.java b/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/contentnode/authoring/ContentNodeAuthoringCommand.java index 8b83f31a..17425c80 100644 --- a/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/contentnode/authoring/ContentNodeAuthoringCommand.java +++ b/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/contentnode/authoring/ContentNodeAuthoringCommand.java @@ -1,7 +1,7 @@ package cn.iocoder.yudao.module.education.service.contentnode.authoring; public record ContentNodeAuthoringCommand(Long entryId, Long parentId, String name, String title, - String nodeType, String markerType, Boolean leaf, - Boolean selectable, Boolean hidden, Integer sortOrder, - String metadata, Integer expectedAuthoringVersion) { + String nodeType, String markerType, Boolean selectable, + Integer sortOrder, String metadata, + Integer expectedAuthoringVersion) { } diff --git a/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/contentnode/authoring/ContentNodeAuthoringServiceImpl.java b/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/contentnode/authoring/ContentNodeAuthoringServiceImpl.java index 8adefdbe..9e911709 100644 --- a/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/contentnode/authoring/ContentNodeAuthoringServiceImpl.java +++ b/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/contentnode/authoring/ContentNodeAuthoringServiceImpl.java @@ -38,7 +38,8 @@ public class ContentNodeAuthoringServiceImpl implements ContentNodeAuthoringServ ContentNodeDO node = new ContentNodeDO(); apply(node, command); node.setTenantId(tenantId); node.setScope(TENANT_OWNED); - node.setPublicationStatus("DRAFT"); node.setAuthoringVersion(0); node.setIsActive(false); + node.setPublicationStatus("DRAFT"); node.setAuthoringVersion(0); + node.setIsActive(false); node.setIsHidden(true); node.setIsLeaf(true); nodeMapper.insert(node); return node.getId(); } @@ -105,7 +106,7 @@ public class ContentNodeAuthoringServiceImpl implements ContentNodeAuthoringServ private void apply(ContentNodeDO node, ContentNodeAuthoringCommand command) { node.setEntryId(command.entryId()); node.setParentId(command.parentId()); node.setName(command.name()); node.setTitle(command.title()); node.setNodeType(command.nodeType()); node.setMarkerType(command.markerType()); - node.setIsLeaf(command.leaf()); node.setIsSelectable(command.selectable()); node.setIsHidden(command.hidden()); + node.setIsSelectable(command.selectable()); node.setSortOrder(command.sortOrder() != null ? command.sortOrder() : 0); node.setMetadata(command.metadata()); } diff --git a/yudao-module-education/src/main/resources/db/migration/education/V4100__add_content_node_authoring_lifecycle.sql b/yudao-module-education/src/main/resources/db/migration/education/V4100__add_content_node_authoring_lifecycle.sql index cc4a1a74..a49ce894 100644 --- a/yudao-module-education/src/main/resources/db/migration/education/V4100__add_content_node_authoring_lifecycle.sql +++ b/yudao-module-education/src/main/resources/db/migration/education/V4100__add_content_node_authoring_lifecycle.sql @@ -6,14 +6,18 @@ ALTER TABLE education_content_node ADD COLUMN authoring_version INTEGER NOT NULL DEFAULT 0; UPDATE education_content_node - SET publication_status = CASE WHEN is_active THEN 'ACTIVE' ELSE 'ARCHIVED' END; + SET publication_status = CASE WHEN is_active AND NOT is_hidden THEN 'ACTIVE' ELSE 'ARCHIVED' END, + is_active = is_active AND NOT is_hidden, + is_hidden = NOT (is_active AND NOT is_hidden); ALTER TABLE education_content_node ADD CONSTRAINT ck_education_content_node_publication_status CHECK (publication_status IN ('DRAFT', 'ACTIVE', 'ARCHIVED')), ADD CONSTRAINT ck_education_content_node_authoring_version CHECK (authoring_version >= 0), - ADD CONSTRAINT ck_education_content_node_lifecycle_consistent - CHECK (is_active = (publication_status = 'ACTIVE')); + ADD CONSTRAINT ck_education_content_node_lifecycle_consistent CHECK ( + (publication_status = 'ACTIVE' AND is_active AND NOT is_hidden) OR + (publication_status IN ('DRAFT', 'ARCHIVED') AND NOT is_active AND is_hidden) + ); COMMENT ON COLUMN education_content_node.publication_status IS '内容节点发布状态:DRAFT、ACTIVE、ARCHIVED'; COMMENT ON COLUMN education_content_node.authoring_version IS '内容节点创作和生命周期共用的乐观锁版本'; @@ -47,11 +51,13 @@ RETURNS TRIGGER LANGUAGE plpgsql SET search_path = pg_catalog, pg_temp AS $$ BEGIN IF TG_OP = 'INSERT' THEN IF NEW.scope = 'PUBLIC' THEN - IF NEW.publication_status <> 'ACTIVE' OR NOT NEW.is_active OR NEW.authoring_version <> 0 THEN + IF NEW.publication_status <> 'ACTIVE' OR NOT NEW.is_active OR NEW.is_hidden + OR NEW.authoring_version <> 0 THEN RAISE EXCEPTION 'PUBLIC content node writes are not managed by tenant authoring' USING ERRCODE = '23514'; END IF; ELSIF NEW.scope = 'TENANT_OWNED' AND - (NEW.publication_status <> 'DRAFT' OR NEW.is_active OR NEW.authoring_version <> 0) THEN + (NEW.publication_status <> 'DRAFT' OR NEW.is_active OR NOT NEW.is_hidden + OR NEW.authoring_version <> 0) THEN RAISE EXCEPTION 'tenant content nodes must start in DRAFT' USING ERRCODE = '23514'; END IF; RETURN NEW; diff --git a/yudao-module-education/src/test/java/cn/iocoder/yudao/module/education/controller/admin/contentnode/ContentNodeAuthoringControllerContractTest.java b/yudao-module-education/src/test/java/cn/iocoder/yudao/module/education/controller/admin/contentnode/ContentNodeAuthoringControllerContractTest.java index 1d585d59..c5e1ff30 100644 --- a/yudao-module-education/src/test/java/cn/iocoder/yudao/module/education/controller/admin/contentnode/ContentNodeAuthoringControllerContractTest.java +++ b/yudao-module-education/src/test/java/cn/iocoder/yudao/module/education/controller/admin/contentnode/ContentNodeAuthoringControllerContractTest.java @@ -57,7 +57,7 @@ class ContentNodeAuthoringControllerContractTest { security.permissions.add("education:content-node:author"); ContentNodeReviseReqVO request = new ContentNodeReviseReqVO(); request.setExpectedAuthoringVersion(2); request.setName("Revised"); request.setNodeType("category"); - request.setEntryId(100L); request.setSelectable(true); request.setHidden(false); request.setLeaf(true); + request.setEntryId(100L); request.setSelectable(true); assertEquals(3, controller.revise(101L, request).getData()); assertEquals(2, service.command.expectedAuthoringVersion()); } @@ -65,7 +65,7 @@ class ContentNodeAuthoringControllerContractTest { private ContentNodeDraftReqVO request() { ContentNodeDraftReqVO request = new ContentNodeDraftReqVO(); request.setEntryId(100L); request.setName("Algebra"); request.setNodeType("category"); - request.setSelectable(true); request.setHidden(false); request.setLeaf(true); + request.setSelectable(true); return request; } diff --git a/yudao-module-education/src/test/java/cn/iocoder/yudao/module/education/service/contentnode/authoring/ContentNodeAuthoringPostgreSqlIntegrationTest.java b/yudao-module-education/src/test/java/cn/iocoder/yudao/module/education/service/contentnode/authoring/ContentNodeAuthoringPostgreSqlIntegrationTest.java index 1aadfe87..f5e6a885 100644 --- a/yudao-module-education/src/test/java/cn/iocoder/yudao/module/education/service/contentnode/authoring/ContentNodeAuthoringPostgreSqlIntegrationTest.java +++ b/yudao-module-education/src/test/java/cn/iocoder/yudao/module/education/service/contentnode/authoring/ContentNodeAuthoringPostgreSqlIntegrationTest.java @@ -38,15 +38,25 @@ class ContentNodeAuthoringPostgreSqlIntegrationTest extends PostgreSqlDbIntegrat @Test void draftActivateArchiveControlsStudentDiscoveryAndPlacement() { Long id = service.createDraft(command(null, null, "Algebra", null)); + ContentNodeDO draft = nodeMapper.selectTenantOwnedById(10L, id); + assertFalse(draft.getIsActive()); + assertTrue(draft.getIsHidden()); + assertTrue(draft.getIsLeaf()); assertEquals(0, nodeMapper.selectAllByEntryId(10L, 100L, false, null).size()); assertNull(nodeMapper.selectAvailablePlacementTarget(10L, id)); assertEquals(1, service.reviseDraft(id, command(null, null, "Algebra revised", 0))); assertEquals(2, service.activate(id, 1, 7L)); + ContentNodeDO active = nodeMapper.selectTenantOwnedById(10L, id); + assertTrue(active.getIsActive()); + assertFalse(active.getIsHidden()); assertEquals(1, nodeMapper.selectAllByEntryId(10L, 100L, false, null).size()); assertNotNull(nodeMapper.selectAvailablePlacementTarget(10L, id)); assertEquals(3, service.archive(id, 2, 7L)); + ContentNodeDO archived = nodeMapper.selectTenantOwnedById(10L, id); + assertFalse(archived.getIsActive()); + assertTrue(archived.getIsHidden()); assertEquals(0, nodeMapper.selectAllByEntryId(10L, 100L, false, null).size()); assertNull(nodeMapper.selectAvailablePlacementTarget(10L, id)); assertEquals(2L, auditMapper.selectCount()); @@ -74,6 +84,6 @@ class ContentNodeAuthoringPostgreSqlIntegrationTest extends PostgreSqlDbIntegrat private ContentNodeAuthoringCommand command(Long entryId, Long parentId, String name, Integer version) { return new ContentNodeAuthoringCommand(entryId != null ? entryId : 100L, parentId, name, null, - "category", null, true, true, false, 0, null, version); + "category", null, true, 0, null, version); } } diff --git a/yudao-module-education/src/test/java/cn/iocoder/yudao/module/education/test/EducationFlywayMigrationIntegrationTest.java b/yudao-module-education/src/test/java/cn/iocoder/yudao/module/education/test/EducationFlywayMigrationIntegrationTest.java index 4420a5e0..21e848d1 100644 --- a/yudao-module-education/src/test/java/cn/iocoder/yudao/module/education/test/EducationFlywayMigrationIntegrationTest.java +++ b/yudao-module-education/src/test/java/cn/iocoder/yudao/module/education/test/EducationFlywayMigrationIntegrationTest.java @@ -484,12 +484,12 @@ class EducationFlywayMigrationIntegrationTest { (id, tenant_id, scope, entry_key, name, entry_type) VALUES (100, 10, 'TENANT_OWNED', 'questions', 'Questions', 'question'); INSERT INTO education_content_node - (id, tenant_id, scope, entry_id, name, node_type, publication_status, is_active) - VALUES (200, 10, 'TENANT_OWNED', 100, 'Draft', 'category', 'DRAFT', false); + (id, tenant_id, scope, entry_id, name, node_type, publication_status, is_active, is_hidden) + VALUES (200, 10, 'TENANT_OWNED', 100, 'Draft', 'category', 'DRAFT', false, true); """); assertThatThrownBy(() -> execute(schema, """ UPDATE education_content_node - SET publication_status = 'ACTIVE', is_active = true, authoring_version = 1 + SET publication_status = 'ACTIVE', is_active = true, is_hidden = false, authoring_version = 1 WHERE id = 200; """)).hasMessageContaining("content node lifecycle audit must accompany its transition"); assertThatThrownBy(() -> execute(schema, """