From 3b7c2e5a8f7c62228be6031feb457006c8314d44 Mon Sep 17 00:00:00 2001 From: Rinjae Date: Wed, 5 Aug 2026 15:23:59 +0900 Subject: [PATCH] =?UTF-8?q?=ED=91=B8=ED=84=B0=20"=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EC=82=AC=EC=9D=B4=ED=8A=B8"=20=EC=A1=B0=ED=9A=8C=20=EB=B0=8F?= =?UTF-8?q?=20=EB=A0=8C=EB=8D=94=EB=A7=81=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80:=20-=20RelatedSite/RelatedSiteService=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=B0=8F=20PortalProperty=20=EC=97=B0=EA=B3=84=20-?= =?UTF-8?q?=20GlobalControllerAdvice=EC=97=90=EC=84=9C=20=EC=85=80?= =?UTF-8?q?=EB=A0=89=ED=8A=B8=20=EB=9D=BC=EB=B2=A8/=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=EC=A3=BC=EC=9E=85=20-=20footer.html=20=ED=85=9C=ED=94=8C?= =?UTF-8?q?=EB=A6=BF=20=EB=B0=8F=20=EA=B4=80=EB=A0=A8=20JS=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../breadcrumb/GlobalControllerAdvice.java | 22 +++ .../apim/portal/djb/footer/RelatedSite.java | 22 +++ .../portal/djb/footer/RelatedSiteService.java | 131 ++++++++++++++++++ .../views/fragment/djbank/footer.html | 25 +++- 4 files changed, 194 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/eactive/apim/portal/djb/footer/RelatedSite.java create mode 100644 src/main/java/com/eactive/apim/portal/djb/footer/RelatedSiteService.java diff --git a/src/main/java/com/eactive/apim/portal/common/breadcrumb/GlobalControllerAdvice.java b/src/main/java/com/eactive/apim/portal/common/breadcrumb/GlobalControllerAdvice.java index 933992b..97d1b63 100644 --- a/src/main/java/com/eactive/apim/portal/common/breadcrumb/GlobalControllerAdvice.java +++ b/src/main/java/com/eactive/apim/portal/common/breadcrumb/GlobalControllerAdvice.java @@ -12,6 +12,8 @@ import com.eactive.apim.portal.config.PortalProperties; import com.eactive.apim.portal.apps.auth.AuthNoticeProperties; import com.eactive.apim.portal.apps.session.service.UserSessionService; import com.eactive.apim.portal.common.security.ClientGuardService; +import com.eactive.apim.portal.djb.footer.RelatedSite; +import com.eactive.apim.portal.djb.footer.RelatedSiteService; import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; @ControllerAdvice @@ -35,6 +37,9 @@ public class GlobalControllerAdvice { @Autowired private AuthNoticeProperties authNoticeProperties; + @Autowired + private RelatedSiteService relatedSiteService; + @Autowired private Environment environment; @@ -111,4 +116,21 @@ public class GlobalControllerAdvice { return portalPropertyService.getOrCreateProperty( "Portal", "customer.center.contact", "1588-3388", "고객센터 연락처"); } + + /** + * 푸터 관련 사이트 셀렉트 라벨. PortalProperty(Portal/footer.related-sites.label)에서 조회. + */ + @ModelAttribute("relatedSitesLabel") + public String relatedSitesLabel() { + return relatedSiteService.getLabel(); + } + + /** + * 푸터 관련 사이트 목록. PortalProperty(Portal/footer.related-sites)의 '이름=URL' 줄 목록을 파싱한 결과. + * 비어 있으면 푸터에서 셀렉트 자체를 렌더하지 않는다. + */ + @ModelAttribute("relatedSites") + public List relatedSites() { + return relatedSiteService.getSites(); + } } diff --git a/src/main/java/com/eactive/apim/portal/djb/footer/RelatedSite.java b/src/main/java/com/eactive/apim/portal/djb/footer/RelatedSite.java new file mode 100644 index 0000000..e29f705 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/footer/RelatedSite.java @@ -0,0 +1,22 @@ +package com.eactive.apim.portal.djb.footer; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; + +/** + * 푸터 "관련 사이트" 셀렉트에 노출되는 사이트 한 건. + * + *

{@link RelatedSiteService} 가 PortalProperty 문자열을 파싱해 만든다.

+ */ +@Getter +@ToString +@AllArgsConstructor +public class RelatedSite { + + /** 셀렉트에 표시되는 이름 (예: 신한은행) */ + private final String name; + + /** 이동 대상 URL (http/https 절대주소 또는 `/` 로 시작하는 사이트 상대주소) */ + private final String url; +} diff --git a/src/main/java/com/eactive/apim/portal/djb/footer/RelatedSiteService.java b/src/main/java/com/eactive/apim/portal/djb/footer/RelatedSiteService.java new file mode 100644 index 0000000..b08495f --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/footer/RelatedSiteService.java @@ -0,0 +1,131 @@ +package com.eactive.apim.portal.djb.footer; + +import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * 푸터 "관련 사이트" 셀렉트의 라벨과 목록을 DB(PortalProperty)에서 조회한다. + * + *

group 은 기존 {@code Portal} 을 재사용하여 {@link PortalPropertyService#getOrCreateProperty} + * 의 자동 생성(self-seed)이 동작하도록 한다.

+ * + * + * + *

목록 표기 규칙

+ *
+ * # 주석 (# 으로 시작하는 줄은 무시)
+ * 신한은행=http://www.shinhan.com
+ * 제주은행 = https://www.jejubank.co.kr   ← = 앞뒤 공백 허용
+ * 
+ * + * + *

허용 스킴을 {@code http/https} 와 사이트 상대경로로 제한하는 이유는, 이 값이 관리자 화면에서 + * 편집되어 그대로 앵커/스크립트 이동 대상이 되기 때문이다({@code javascript:} 등 차단).

+ */ +@Slf4j +@Service +@RequiredArgsConstructor +public class RelatedSiteService { + + private static final String GROUP = "Portal"; + + static final String NAME_LABEL = "footer.related-sites.label"; + static final String NAME_SITES = "footer.related-sites"; + + static final String DESC_LABEL = "푸터 관련 사이트 셀렉트 라벨(첫 항목 문구)"; + static final String DESC_SITES = "푸터 관련 사이트 목록. 한 줄에 하나씩 '이름=URL' (빈 줄·# 주석 무시)"; + + static final String DEFAULT_LABEL = "DJBank 관련 사이트"; + + /** 기본값: 신한금융그룹 Family site (shinhangroup.com 하단 목록 기준) */ + static final String DEFAULT_SITES = String.join("\n", + "신한은행=http://www.shinhan.com", + "신한카드=http://www.shinhancard.com", + "신한투자증권=http://www.shinhansec.com", + "신한라이프=http://www.shinhanlife.co.kr", + "신한캐피탈=http://www.shcap.co.kr", + "신한자산운용=https://www.shinhanfund.com", + "제주은행=https://www.jejubank.co.kr", + "신한저축은행=http://www.shinhansavings.co.kr", + "신한자산신탁=http://www.shinhantrust.kr", + "신한DS=http://www.shinhansys.co.kr", + "신한펀드파트너스=https://www.shinhanfundpartners.com", + "신한리츠운용=http://shinhanrem.com", + "신한벤처투자=http://www.shinhanvc.com", + "신한EZ손해보험=http://www.shinhanez.co.kr", + "신한장학재단=http://www.shsf.or.kr", + "신한금융희망재단=http://www.shinhanfoundation.or.kr"); + + private final PortalPropertyService portalPropertyService; + + /** 셀렉트 첫 항목에 노출할 라벨 */ + @Transactional + public String getLabel() { + String label = portalPropertyService.getOrCreateProperty(GROUP, NAME_LABEL, DEFAULT_LABEL, DESC_LABEL); + return (label == null || label.trim().isEmpty()) ? DEFAULT_LABEL : label.trim(); + } + + /** 셀렉트에 노출할 사이트 목록. 파싱 결과가 없으면 빈 리스트(푸터에서 셀렉트 미노출) */ + @Transactional + public List getSites() { + return parse(portalPropertyService.getOrCreateProperty(GROUP, NAME_SITES, DEFAULT_SITES, DESC_SITES)); + } + + /** + * {@code 이름=URL} 줄 목록을 파싱한다. 잘못된 줄은 건너뛴다. + */ + static List parse(String raw) { + if (raw == null || raw.trim().isEmpty()) { + return Collections.emptyList(); + } + + List sites = new ArrayList<>(); + for (String rawLine : raw.split("\\r?\\n")) { + String line = rawLine.trim(); + if (line.isEmpty() || line.startsWith("#")) { + continue; + } + + int sep = line.indexOf('='); + if (sep <= 0) { + log.warn("관련 사이트 설정 형식 오류(= 구분자 없음) - 해당 줄 무시: {}", line); + continue; + } + + String name = line.substring(0, sep).trim(); + String url = line.substring(sep + 1).trim(); + if (name.isEmpty() || url.isEmpty()) { + log.warn("관련 사이트 설정 형식 오류(이름 또는 URL 없음) - 해당 줄 무시: {}", line); + continue; + } + if (!isAllowedUrl(url)) { + log.warn("관련 사이트 설정 URL 스킴 불허(http/https 또는 / 로 시작해야 함) - 해당 줄 무시: {}", line); + continue; + } + + sites.add(new RelatedSite(name, url)); + } + return sites; + } + + private static boolean isAllowedUrl(String url) { + String lower = url.toLowerCase(); + return lower.startsWith("http://") || lower.startsWith("https://") || url.startsWith("/"); + } +} diff --git a/src/main/resources/templates/views/fragment/djbank/footer.html b/src/main/resources/templates/views/fragment/djbank/footer.html index 62cdd33..6eefe23 100644 --- a/src/main/resources/templates/views/fragment/djbank/footer.html +++ b/src/main/resources/templates/views/fragment/djbank/footer.html @@ -24,18 +24,31 @@ + +