diff --git a/src/main/java/com/eactive/apim/portal/menu/entity/PortalMenuItem.java b/src/main/java/com/eactive/apim/portal/menu/entity/PortalMenuItem.java new file mode 100644 index 0000000..2975022 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/menu/entity/PortalMenuItem.java @@ -0,0 +1,113 @@ +package com.eactive.apim.portal.menu.entity; + +import com.eactive.apim.portal.common.entity.Auditable; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Comment; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import java.io.Serializable; + +/** + * 포탈 메뉴 항목 마스터. + * + *
PK 는 menu.yml 의 kebab-case 자연키(예: {@code service-intro}). + * PORTAL(기본) 항목은 eapim-portal 부팅 시 menu.yml 로 upsert 되고, + * ADMIN(커스텀) 항목은 eapim-admin 에서 등록/삭제한다. + * DFLT_* 컬럼은 최종 yml 시딩값 스냅샷으로, 관리자 수정과의 충돌 판정과 + * "메뉴 초기화"의 원본으로 사용한다. + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Entity +@Table(name = "PTL_MENU_ITEM") +public class PortalMenuItem extends Auditable implements Serializable { + private static final long serialVersionUID = 1L; + + public static final String SOURCE_PORTAL = "PORTAL"; + public static final String SOURCE_ADMIN = "ADMIN"; + public static final String SECTION_GNB = "GNB"; + public static final String SECTION_MYPAGE = "MYPAGE"; + /** EXPOSE/ACCESS_ROLES 특수값: 로그인 사용자 전체 */ + public static final String ROLE_AUTHENTICATED = "AUTHENTICATED"; + + @Id + @Column(name = "MENU_ID", length = 100, nullable = false) + @Comment("kebab-case 자연키 (menu.yml id)") + private String menuId; + + @Column(name = "MENU_NAME", length = 200, nullable = false) + @Comment("노출명 (관리자 수정 가능)") + private String menuName; + + @Column(name = "MENU_PATH", length = 500) + @Comment("이동 경로 (NULL=클릭 없는 그룹)") + private String menuPath; + + @Column(name = "GROUP_YN", length = 1, nullable = false) + @Comment("그룹(상위 메뉴) 여부 - 구조 필드, menu.yml 소유") + private String groupYn = "N"; + + @Column(name = "MENU_SECTION", length = 20, nullable = false) + @Comment("섹션 (GNB=상단 글로벌 네비 / MYPAGE=마이페이지 드롭다운)") + private String menuSection = SECTION_GNB; + + @Column(name = "NEW_WINDOW_YN", length = 1, nullable = false) + @Comment("새 창 열기 여부 (관리자 추가 외부링크용)") + private String newWindowYn = "N"; + + @Column(name = "ICON_CLASS", length = 100) + @Comment("아이콘 클래스 (FontAwesome, 마이페이지 드롭다운 표기)") + private String iconClass; + + @Column(name = "EXPOSE_ROLES", length = 500) + @Comment("노출 권한 CSV (NULL=전체, AUTHENTICATED=로그인자, 그 외 역할코드 any-of)") + private String exposeRoles; + + @Column(name = "ACCESS_ROLES", length = 500) + @Comment("접근 권한 CSV (NULL=제한 없음, 서버 인터셉터 집행)") + private String accessRoles; + + @Column(name = "SOURCE_TYPE", length = 10, nullable = false) + @Comment("항목 출처 (PORTAL=menu.yml 기본 / ADMIN=관리자 커스텀)") + private String sourceType = SOURCE_PORTAL; + + @Column(name = "DFLT_MENU_NAME", length = 200) + @Comment("기본 노출명 (최종 yml 시딩값)") + private String dfltMenuName; + + @Column(name = "DFLT_MENU_PATH", length = 500) + @Comment("기본 경로 (최종 yml 시딩값)") + private String dfltMenuPath; + + @Column(name = "DFLT_EXPOSE_ROLES", length = 500) + @Comment("기본 노출 권한 CSV (최종 yml 시딩값)") + private String dfltExposeRoles; + + @Column(name = "DFLT_ACCESS_ROLES", length = 500) + @Comment("기본 접근 권한 CSV (최종 yml 시딩값)") + private String dfltAccessRoles; + + @Column(name = "DFLT_PARENT_ID", length = 100) + @Comment("기본 배치 부모 (MENU_ID 논리 참조, NULL=최상위)") + private String dfltParentId; + + @Column(name = "DFLT_SORT_ORDER") + @Comment("기본 배치 정렬 순서") + private Integer dfltSortOrder; + + @Column(name = "DFLT_VISIBLE_YN", length = 1, nullable = false) + @Comment("기본 노출 여부") + private String dfltVisibleYn = "Y"; + + public boolean isGroup() { + return "Y".equals(groupYn); + } + + public boolean isPortalSource() { + return SOURCE_PORTAL.equals(sourceType); + } +} diff --git a/src/main/java/com/eactive/apim/portal/menu/entity/PortalMenuPlacement.java b/src/main/java/com/eactive/apim/portal/menu/entity/PortalMenuPlacement.java new file mode 100644 index 0000000..eb54015 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/menu/entity/PortalMenuPlacement.java @@ -0,0 +1,48 @@ +package com.eactive.apim.portal.menu.entity; + +import com.eactive.apim.portal.common.entity.Auditable; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Comment; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import java.io.Serializable; + +/** + * 포탈 메뉴 현재 배치. + * + *
row 없음 = 미배치. eapim-portal 부팅 시 테이블이 비어있을 때만 + * {@link PortalMenuItem} 의 DFLT_* 값으로 최초 시딩하며, 이후 배치 관리는 + * eapim-admin 이 수행한다(일괄 replace-all 저장). + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Entity +@Table(name = "PTL_MENU_PLACEMENT") +public class PortalMenuPlacement extends Auditable implements Serializable { + private static final long serialVersionUID = 1L; + + @Id + @Column(name = "MENU_ID", length = 100, nullable = false) + @Comment("PTL_MENU_ITEM.MENU_ID 논리 참조 (항목당 배치 최대 1건)") + private String menuId; + + @Column(name = "PARENT_ID", length = 100) + @Comment("부모 메뉴 ID (NULL=최상위/레인)") + private String parentId; + + @Column(name = "SORT_ORDER", nullable = false) + @Comment("동일 부모 내 정렬 순서") + private Integer sortOrder; + + @Column(name = "VISIBLE_YN", length = 1, nullable = false) + @Comment("노출 여부 (N=배치 유지한 채 숨김)") + private String visibleYn = "Y"; + + public boolean isVisible() { + return "Y".equals(visibleYn); + } +} diff --git a/src/main/java/com/eactive/apim/portal/menu/entity/PortalRole.java b/src/main/java/com/eactive/apim/portal/menu/entity/PortalRole.java new file mode 100644 index 0000000..87791be --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/menu/entity/PortalRole.java @@ -0,0 +1,47 @@ +package com.eactive.apim.portal.menu.entity; + +import com.eactive.apim.portal.common.entity.Auditable; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Comment; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import java.io.Serializable; + +/** + * 포탈 역할 사전 (roles.yml 미러). + * + *
eapim-portal 부팅 시 roles.yml 로 upsert 된다. 로그인 시 권한 확장은
+ * yml 바인딩을 직접 사용하며, 본 테이블은 eapim-admin 의 메뉴 권한 선택
+ * UI(체크박스) 소스로만 소비된다.
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Entity
+@Table(name = "PTL_ROLE")
+public class PortalRole extends Auditable implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ public static final String TYPE_BASE = "BASE";
+ public static final String TYPE_AUTHORITY = "AUTHORITY";
+
+ @Id
+ @Column(name = "ROLE_CODE", length = 50, nullable = false)
+ @Comment("역할 코드 (예: ROLE_CORP_MANAGER)")
+ private String roleCode;
+
+ @Column(name = "ROLE_NAME", length = 200, nullable = false)
+ @Comment("역할 한글명")
+ private String roleName;
+
+ @Column(name = "ROLE_TYPE", length = 10, nullable = false)
+ @Comment("역할 유형 (BASE=기본 역할 / AUTHORITY=파생 권한)")
+ private String roleType;
+
+ @Column(name = "SORT_ORDER", nullable = false)
+ @Comment("표시 정렬 순서")
+ private Integer sortOrder;
+}
diff --git a/src/main/java/com/eactive/apim/portal/menu/entity/PortalRoleAuthority.java b/src/main/java/com/eactive/apim/portal/menu/entity/PortalRoleAuthority.java
new file mode 100644
index 0000000..4d43016
--- /dev/null
+++ b/src/main/java/com/eactive/apim/portal/menu/entity/PortalRoleAuthority.java
@@ -0,0 +1,24 @@
+package com.eactive.apim.portal.menu.entity;
+
+import com.eactive.apim.portal.common.entity.Auditable;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import javax.persistence.EmbeddedId;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import java.io.Serializable;
+
+/**
+ * 기본 역할 → 파생 권한 매핑 (roles.yml 미러, eapim-admin 조회 전용).
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Entity
+@Table(name = "PTL_ROLE_AUTHORITY")
+public class PortalRoleAuthority extends Auditable implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @EmbeddedId
+ private PortalRoleAuthorityId id;
+}
diff --git a/src/main/java/com/eactive/apim/portal/menu/entity/PortalRoleAuthorityId.java b/src/main/java/com/eactive/apim/portal/menu/entity/PortalRoleAuthorityId.java
new file mode 100644
index 0000000..e604d58
--- /dev/null
+++ b/src/main/java/com/eactive/apim/portal/menu/entity/PortalRoleAuthorityId.java
@@ -0,0 +1,26 @@
+package com.eactive.apim.portal.menu.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.annotations.Comment;
+
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+import java.io.Serializable;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Embeddable
+public class PortalRoleAuthorityId implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @Column(name = "ROLE_CODE", length = 50, nullable = false)
+ @Comment("기본 역할 코드 (PTL_ROLE.ROLE_CODE 논리 참조)")
+ private String roleCode;
+
+ @Column(name = "AUTHORITY_CODE", length = 50, nullable = false)
+ @Comment("부여되는 파생 권한 코드")
+ private String authorityCode;
+}
diff --git a/src/main/java/com/eactive/apim/portal/menu/repository/PortalMenuItemRepository.java b/src/main/java/com/eactive/apim/portal/menu/repository/PortalMenuItemRepository.java
new file mode 100644
index 0000000..c450b80
--- /dev/null
+++ b/src/main/java/com/eactive/apim/portal/menu/repository/PortalMenuItemRepository.java
@@ -0,0 +1,13 @@
+package com.eactive.apim.portal.menu.repository;
+
+import com.eactive.apim.portal.menu.entity.PortalMenuItem;
+import com.eactive.eai.data.jpa.BaseRepository;
+import com.eactive.eai.rms.data.EMSDataSource;
+
+import java.util.List;
+
+@EMSDataSource
+public interface PortalMenuItemRepository extends BaseRepository 트랜잭션 경계: {@code PortalPropertyService} 와 동일하게 기본 @Transactional 을 사용한다.
+ * eapim-admin 에서는 호출측 ManService 가 {@code @Transactional("transactionManagerForEMS")}
+ * 로 감싸 EMS 트랜잭션에 참여시킨다.
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+@Transactional
+public class PortalMenuDataService {
+
+ private final PortalMenuItemRepository menuItemRepository;
+ private final PortalMenuPlacementRepository menuPlacementRepository;
+
+ @Transactional(readOnly = true)
+ public List
+ *
+ *
+ * @return true 이면 항목까지 삭제됨(커스텀), false 이면 미배치 전환만 수행(기본)
+ */
+ public boolean deleteMenu(String menuId) {
+ PortalMenuItem item = menuItemRepository.findById(menuId)
+ .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 메뉴: " + menuId));
+
+ menuPlacementRepository.findById(menuId).ifPresent(menuPlacementRepository::delete);
+
+ if (item.isPortalSource()) {
+ log.info("기본 메뉴 항목 미배치 전환: {}", menuId);
+ return false;
+ }
+ menuItemRepository.delete(item);
+ log.info("커스텀 메뉴 항목 삭제: {}", menuId);
+ return true;
+ }
+
+ /**
+ * 메뉴 초기화 — eapim-portal 이 시딩한 기본값으로 되돌린다.
+ *
+ *
+ */
+ public void resetToDefaults() {
+ List