86 lines
5.1 KiB
Markdown
86 lines
5.1 KiB
Markdown
# 메뉴 관리 개발 가이드
|
||
|
||
포탈 GNB/마이페이지 메뉴는 `menu.yml` → DB(PTL_MENU_*) → 캐시 → 템플릿 렌더 구조로 동작하며,
|
||
노출/배치 관리는 eapim-admin **포탈메뉴관리**(파트너포탈 > 포탈관리 > 메뉴 관리)에서 수행한다.
|
||
|
||
## 구성 요소
|
||
|
||
| 구성 | 위치 | 역할 |
|
||
|---|---|---|
|
||
| `menu.yml` | `src/main/resources/menu.yml` | 기본 메뉴 정의 (id/노출명/path/권한/기본 배치) |
|
||
| `roles.yml` | `src/main/resources/roles.yml` | 역할 정의 (`portal.portal_security` 이동분) |
|
||
| 엔티티/공유 서비스 | `elink-portal-common` `com.eactive.apim.portal.menu.*` | PTL_MENU_ITEM·PTL_MENU_PLACEMENT·PTL_ROLE(+AUTHORITY), `PortalMenuDataService` |
|
||
| 시더 | `djb/menu/MenuSeeder.java` | 부팅 시 yml→DB 적재 (ApplicationReadyEvent) |
|
||
| 캐시 | `djb/menu/MenuService.java` | role 비의존 트리 스냅샷, TTL 1시간(PTL_PROPERTY) |
|
||
| 렌더 | `djb/menu/MenuModelAdvice.java` → 모델 `menuView` | 요청별 노출(EXPOSE_ROLES) 필터 |
|
||
| 접근 제어 | `djb/menu/MenuAccessInterceptor.java` | ACCESS_ROLES 서버측 집행 (경로 정확 일치) |
|
||
| 내부 API | `djb/menu/MenuInternalController.java` | `POST /internal/menu/reload` (admin 캐시 리로드 수신) |
|
||
|
||
메뉴를 소비하는 템플릿: `fragment/djbank/header_container.html`(데스크톱 nav·마이페이지 드롭다운·모바일 drawer),
|
||
`fragment/djbank/service_sidebar.html`. 모두 `${menuView}` 를 반복 렌더하므로 **메뉴 추가 시 템플릿 수정 불필요**.
|
||
|
||
## menu.yml 스키마
|
||
|
||
```yaml
|
||
portal-menu:
|
||
items:
|
||
- id: support # kebab-case 필수 (^[a-z0-9-]+$). 변경 금지(변경=신규 항목)
|
||
name: "고객지원"
|
||
group: true # 상위 그룹. path 생략 시 클릭 없음(자식 있어야 노출)
|
||
section: GNB # GNB(기본) | MYPAGE. 자식은 부모 섹션 상속
|
||
expose-roles: [] # 생략=전체(익명 포함), AUTHENTICATED=로그인자, 그 외 역할코드 any-of
|
||
children:
|
||
- { id: support-faq, name: "FAQ", path: /faq_list }
|
||
- { id: my-page-webhook, name: "Webhook 관리", path: /webhook, icon: fa-bell,
|
||
expose-roles: [ROLE_WEBHOOK], access-roles: [ROLE_WEBHOOK] }
|
||
```
|
||
|
||
- `expose-roles` = 메뉴 **노출** 조건, `access-roles` = URL **접근** 조건(인터셉터 차단, redirect).
|
||
- `icon` 은 마이페이지 드롭다운 전용(FontAwesome 클래스).
|
||
- 정렬은 yml 나열 순서(기본 배치 sort = index×10).
|
||
|
||
## 시딩 규칙 (MenuSeeder)
|
||
|
||
1. **항목**: id 기준 upsert. yml 값이 바뀌면 DFLT_*(기본값 스냅샷)를 갱신하고,
|
||
**관리자가 수정하지 않은 필드(현재값==구 기본값)만** 새 기본값을 따라간다.
|
||
구조 필드(`group`/`section`/`icon`/`new-window`)는 항상 yml 이 이긴다.
|
||
2. **배치**: `PTL_MENU_PLACEMENT` 가 **비어있을 때만** 기본 배치로 최초 시딩.
|
||
이후 배치는 admin 이 소유한다 — 재배포/재기동에도 보존됨.
|
||
3. yml 에서 항목을 제거해도 DB 는 삭제하지 않고 경고 로그만 남긴다(수동 정리).
|
||
4. 부팅 시딩 주체는 인증 사용자가 없으므로 `CREATED_BY=SYSTEM`.
|
||
|
||
## 캐시와 리로드
|
||
|
||
- 스냅샷 TTL: PTL_PROPERTY `Portal / menu.cache.ttl-seconds` (기본 3600초).
|
||
- 즉시 반영: `curl -X POST http://127.0.0.1:39130/internal/menu/reload`
|
||
(admin 포탈메뉴관리의 [캐시 Reload] 버튼이 동일 호출 수행).
|
||
- 내부 API 가드: `Portal / menu.internal.allow-ips` 허용 IP 목록(기본 loopback)
|
||
+ X-Forwarded-For 동반 요청 거부. CSRF 면제(`/internal/menu/**`).
|
||
- admin 측 호출 URL: `Portal / portal.internal.menu-reload-url`.
|
||
|
||
## 새 메뉴 추가 절차
|
||
|
||
**기본 메뉴(코드 배포와 함께)**
|
||
1. 페이지/라우트 준비 (`portal.pages` 또는 `@GetMapping` — 기존 방식 그대로)
|
||
2. `menu.yml` 에 항목 추가 (필요 시 breadcrumb 용 `page.home` 트리도 갱신 — 별도 체계 유지)
|
||
3. 재기동 → 시딩 로그 확인 → 헤더/드로어 노출 확인
|
||
4. 이미 운영 중인 DB 라면 배치는 자동 추가되지 않음(배치 시딩은 최초 1회) —
|
||
admin 화면에서 미배치 → 원하는 위치로 드래그 후 저장
|
||
|
||
**운영자 임시 메뉴(외부 링크 등)**: admin 포탈메뉴관리 [메뉴 추가] → 미배치 생성 → 드래그 배치 → 저장 → 캐시 Reload.
|
||
커스텀 항목은 미배치 시 삭제된다.
|
||
|
||
## 로컬 개발 주의
|
||
|
||
- `gradle bootRun` 으로 시딩까지 확인하려면 damo-manager 가 classpath 에 필요:
|
||
`JAVA_TOOL_OPTIONS="-Xbootclasspath/a:<...>/apache-tomcat-9.0.115-djb/lib/damo-manager.jar"`
|
||
(미지정 시 감사 컬럼 암호화 컨버터에서 NoClassDefFoundError).
|
||
- 템플릿/메뉴 반영 확인은 서버 재시작 후 curl 로.
|
||
- elink-portal-common 수정 후 Q클래스 duplicate 컴파일 오류 시 각 모듈 `build/generated` 삭제 후 재컴파일.
|
||
|
||
## 역할(roles.yml) 변경
|
||
|
||
- 로그인 권한 확장은 `PortalRolesProperties`(yml 바인딩)를 직접 사용 — DB 미러(PTL_ROLE*)는
|
||
admin 권한 선택 체크박스 소스 전용.
|
||
- 역할 추가 시 `roles.yml` 의 `authority-names` 에 한글 라벨을 함께 등록해야 admin 화면에 표기된다.
|