메뉴 기능 추가 - menu.yml GNB 정의 - 접근 인터셉터/뷰 주입/내부 API 구현
eapim-portal CI (from elink-portal-common) / build (push) Has been cancelled
eapim-portal CI (from elink-portal-common) / build (push) Has been cancelled
This commit is contained in:
@@ -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;
|
||||
|
||||
/**
|
||||
* 포탈 메뉴 항목 마스터.
|
||||
*
|
||||
* <p>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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
* 포탈 메뉴 현재 배치.
|
||||
*
|
||||
* <p>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);
|
||||
}
|
||||
}
|
||||
@@ -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 미러).
|
||||
*
|
||||
* <p>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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<PortalMenuItem, String> {
|
||||
|
||||
List<PortalMenuItem> findAllBySourceType(String sourceType);
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package com.eactive.apim.portal.menu.repository;
|
||||
|
||||
import com.eactive.apim.portal.menu.entity.PortalMenuPlacement;
|
||||
import com.eactive.eai.data.jpa.BaseRepository;
|
||||
import com.eactive.eai.rms.data.EMSDataSource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@EMSDataSource
|
||||
public interface PortalMenuPlacementRepository extends BaseRepository<PortalMenuPlacement, String> {
|
||||
|
||||
List<PortalMenuPlacement> findAllByOrderByParentIdAscSortOrderAsc();
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.eactive.apim.portal.menu.repository;
|
||||
|
||||
import com.eactive.apim.portal.menu.entity.PortalRoleAuthority;
|
||||
import com.eactive.apim.portal.menu.entity.PortalRoleAuthorityId;
|
||||
import com.eactive.eai.data.jpa.BaseRepository;
|
||||
import com.eactive.eai.rms.data.EMSDataSource;
|
||||
|
||||
@EMSDataSource
|
||||
public interface PortalRoleAuthorityRepository extends BaseRepository<PortalRoleAuthority, PortalRoleAuthorityId> {
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.eactive.apim.portal.menu.repository;
|
||||
|
||||
import com.eactive.apim.portal.menu.entity.PortalRole;
|
||||
import com.eactive.eai.data.jpa.BaseRepository;
|
||||
import com.eactive.eai.rms.data.EMSDataSource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@EMSDataSource
|
||||
public interface PortalRoleRepository extends BaseRepository<PortalRole, String> {
|
||||
|
||||
List<PortalRole> findAllByOrderBySortOrderAsc();
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package com.eactive.apim.portal.menu.service;
|
||||
|
||||
import com.eactive.apim.portal.menu.entity.PortalMenuItem;
|
||||
import com.eactive.apim.portal.menu.entity.PortalMenuPlacement;
|
||||
import com.eactive.apim.portal.menu.repository.PortalMenuItemRepository;
|
||||
import com.eactive.apim.portal.menu.repository.PortalMenuPlacementRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 메뉴 항목/배치 공유 데이터 서비스 (eapim-portal · eapim-admin 공용).
|
||||
*
|
||||
* <p>트랜잭션 경계: {@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<PortalMenuItem> loadAllItems() {
|
||||
return menuItemRepository.findAll();
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<PortalMenuPlacement> loadAllPlacements() {
|
||||
return menuPlacementRepository.findAllByOrderByParentIdAscSortOrderAsc();
|
||||
}
|
||||
|
||||
/**
|
||||
* 현재 배치를 통째로 교체한다(일괄 저장). 스윔레인 "저장" 버튼과
|
||||
* 메뉴 초기화가 공용으로 사용한다.
|
||||
*/
|
||||
public void replacePlacements(List<PortalMenuPlacement> placements) {
|
||||
menuPlacementRepository.deleteAllInBatch();
|
||||
menuPlacementRepository.flush();
|
||||
menuPlacementRepository.saveAll(placements);
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 삭제 가드.
|
||||
* <ul>
|
||||
* <li>PORTAL(기본) 항목: 실제 삭제 금지 — 배치(placement)만 제거해 미배치로 전환</li>
|
||||
* <li>ADMIN(커스텀) 항목: 배치 제거 + 항목 하드 삭제</li>
|
||||
* </ul>
|
||||
*
|
||||
* @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 이 시딩한 기본값으로 되돌린다.
|
||||
* <ul>
|
||||
* <li>PORTAL 항목: 현재 필드를 DFLT_* 값으로 복원</li>
|
||||
* <li>ADMIN 항목: 전부 삭제</li>
|
||||
* <li>배치: PORTAL 항목의 DFLT_PARENT_ID/DFLT_SORT_ORDER/DFLT_VISIBLE_YN 로 재구축</li>
|
||||
* </ul>
|
||||
*/
|
||||
public void resetToDefaults() {
|
||||
List<PortalMenuItem> allItems = menuItemRepository.findAll();
|
||||
|
||||
List<PortalMenuItem> adminItems = allItems.stream()
|
||||
.filter(item -> !item.isPortalSource())
|
||||
.collect(Collectors.toList());
|
||||
menuItemRepository.deleteAll(adminItems);
|
||||
|
||||
List<PortalMenuItem> portalItems = allItems.stream()
|
||||
.filter(PortalMenuItem::isPortalSource)
|
||||
.collect(Collectors.toList());
|
||||
for (PortalMenuItem item : portalItems) {
|
||||
item.setMenuName(item.getDfltMenuName());
|
||||
item.setMenuPath(item.getDfltMenuPath());
|
||||
item.setExposeRoles(item.getDfltExposeRoles());
|
||||
item.setAccessRoles(item.getDfltAccessRoles());
|
||||
}
|
||||
menuItemRepository.saveAll(portalItems);
|
||||
|
||||
replacePlacements(buildDefaultPlacements(portalItems));
|
||||
log.info("메뉴 초기화 완료 - 기본 항목 {}건 복원, 커스텀 항목 {}건 삭제",
|
||||
portalItems.size(), adminItems.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* PORTAL 항목의 기본 위치 정보(DFLT_*)로 배치 목록을 생성한다.
|
||||
* eapim-portal 최초 시딩과 메뉴 초기화가 공용으로 사용한다.
|
||||
*/
|
||||
public List<PortalMenuPlacement> buildDefaultPlacements(List<PortalMenuItem> portalItems) {
|
||||
return portalItems.stream()
|
||||
.filter(item -> item.getDfltSortOrder() != null)
|
||||
.map(item -> {
|
||||
PortalMenuPlacement placement = new PortalMenuPlacement();
|
||||
placement.setMenuId(item.getMenuId());
|
||||
placement.setParentId(item.getDfltParentId());
|
||||
placement.setSortOrder(item.getDfltSortOrder());
|
||||
placement.setVisibleYn(item.getDfltVisibleYn());
|
||||
return placement;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user