5 Commits

Author SHA1 Message Date
Rinjae(gf63) 2cdb08a26f - 약관 동의서 노출 및 동작 방식 관리 기능 추가
eapim-portal CI (from elink-portal-common) / build (push) Has been cancelled
- '전체 동의' 및 약관 항목별 활성화 설정 반영
- 사용자 타입 및 약관 페이지 구성에 따른 렌더링 로직 개선
2026-09-15 13:42:27 +09:00
Rinjae(gf63) a1be6d2b9c 알림톡 템플릿 수정 - 템플릿 코드 변경
eapim-portal CI (from elink-portal-common) / build (push) Has been cancelled
2026-09-11 10:05:57 +09:00
Rinjae 3c9c757962 Merge Gitea master with GitLab UMS changes, retaining GitLab verification template ID
eapim-portal CI (from elink-portal-common) / build (push) Has been cancelled
2026-09-11 10:03:52 +09:00
Rinjae(gf63) 4749a570e7 알림톡 템플릿 적용 2026-09-10 15:04:11 +09:00
eastargh a4976fdfa5 UMS 카톡 템플릿 적용 2026-09-10 13:20:13 +09:00
4 changed files with 175 additions and 20 deletions
@@ -0,0 +1,146 @@
package com.eactive.apim.portal.agreements.service;
import com.eactive.apim.portal.agreements.entity.AgreementType;
import com.eactive.apim.portal.portalproperty.service.PortalPropertyService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 약관 종류 운영 설정 조회. PTL_PROPERTY(그룹 {@code Portal})로 관리하며 admin·portal 이 같은 값을 본다.
*
* <table>
* <caption>PTL_PROPERTY (group = Portal)</caption>
* <tr><th>{@code portal.terms.enabled-types}</th>
* <td>시스템에서 사용할 약관 종류. 쉼표 구분, 순서 무의미</td></tr>
* <tr><th>{@code portal.terms.display-types}</th>
* <td>약관 페이지에 노출할 약관 종류. 쉼표 구분, <b>기재 순서가 노출 순서</b></td></tr>
* </table>
*
* <p>값은 관리 콘솔의 '약관 목록 &gt; 약관 종류 관리' 화면에서 설정한다. 프로퍼티가 없으면
* {@link #DEFAULT_TYPES} 를 사용하며 <b>조회만으로 행을 만들지는 않는다</b>
* (PTL_PROPERTY 는 (그룹, 이름) 유니크 제약이 없어 동시 요청이 중복 행을 만들 수 있다).
* 알 수 없는 코드는 무시하므로 enum 항목이 빠져도 화면이 깨지지 않는다.
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class AgreementTypeConfigService {
public static final String PROPERTY_GROUP = "Portal";
public static final String ENABLED_TYPES_KEY = "portal.terms.enabled-types";
public static final String DISPLAY_TYPES_KEY = "portal.terms.display-types";
public static final String AGREE_ALL_ENABLED_KEY = "portal.terms.agree-all-enabled";
/** '전체 동의' 기본값 — 미설정 시 항목별로 끝까지 읽고 개별 동의하는 방식 */
public static final boolean DEFAULT_AGREE_ALL_ENABLED = false;
/** 프로퍼티 미설정 시 기본값 — 현재 운영중인 약관 종류 */
public static final List<AgreementType> DEFAULT_TYPES = Collections.unmodifiableList(Arrays.asList(
AgreementType.TERMS_OF_USE,
AgreementType.PRIVACY_COLLECT,
AgreementType.NOTIFICATION_CONSENT));
private final PortalPropertyService portalPropertyService;
/** 시스템에서 사용하는 약관 종류 */
public Set<AgreementType> getEnabledTypes() {
Set<AgreementType> enabled = EnumSet.noneOf(AgreementType.class);
enabled.addAll(readTypes(readGroupProperties(), ENABLED_TYPES_KEY));
return enabled;
}
/** 약관 페이지에 노출할 약관 종류(노출 순서대로). 사용하지 않는 종류는 제외된다. */
public List<AgreementType> getDisplayTypes() {
Map<String, String> properties = readGroupProperties();
List<AgreementType> enabled = readTypes(properties, ENABLED_TYPES_KEY);
List<AgreementType> display = new ArrayList<>();
for (AgreementType type : readTypes(properties, DISPLAY_TYPES_KEY)) {
if (enabled.contains(type)) {
display.add(type);
}
}
return display;
}
/** 시스템에서 사용하는 약관 종류인지 여부 */
public boolean isEnabled(AgreementType type) {
return getEnabledTypes().contains(type);
}
/** 약관 페이지에 노출(배치)된 약관 종류인지 여부. 사용하지 않는 종류는 항상 {@code false}. */
public boolean isDisplayed(AgreementType type) {
return getDisplayTypes().contains(type);
}
/**
* 회원가입 약관 동의 화면에서 '전체 동의'를 허용할지 여부.
*
* <ul>
* <li>{@code false} — 항목을 펼쳐 끝까지 읽어야 개별 동의할 수 있고, '전체 동의' 체크박스는 노출하지 않는다</li>
* <li>{@code true} — '전체 동의' 체크박스를 노출하고, 항목별 스크롤 없이 바로 동의할 수 있다</li>
* </ul>
*/
public boolean isAgreeAllEnabled() {
return parseBoolean(readGroupProperties().get(AGREE_ALL_ENABLED_KEY));
}
/** {@code true/false} 외에 레거시 {@code Y/N}, {@code 1/0} 표기도 허용한다. */
private boolean parseBoolean(String value) {
if (value == null || value.trim().isEmpty()) {
return DEFAULT_AGREE_ALL_ENABLED;
}
String trimmed = value.trim();
if ("true".equalsIgnoreCase(trimmed) || "Y".equalsIgnoreCase(trimmed) || "1".equals(trimmed)) {
return true;
}
if ("false".equalsIgnoreCase(trimmed) || "N".equalsIgnoreCase(trimmed) || "0".equals(trimmed)) {
return false;
}
log.warn("PTL_PROPERTY {}/{} 값을 해석할 수 없어 기본값({})을 사용합니다. : {}",
PROPERTY_GROUP, AGREE_ALL_ENABLED_KEY, DEFAULT_AGREE_ALL_ENABLED, trimmed);
return DEFAULT_AGREE_ALL_ENABLED;
}
/**
* {@code Portal} 그룹 프로퍼티 전체를 읽는다.
*
* <p>단건 조회({@code findById})는 중복 행에서 예외가 나므로 그룹 단위로 읽는다.
*/
private Map<String, String> readGroupProperties() {
return portalPropertyService.getPortalPropertiesAsMap(PROPERTY_GROUP);
}
/** 프로퍼티 값을 약관 종류 목록으로 변환. 미설정이면 기본값, 알 수 없는 코드는 무시한다. */
private List<AgreementType> readTypes(Map<String, String> properties, String propertyName) {
String value = properties.get(propertyName);
if (value == null) {
return new ArrayList<>(DEFAULT_TYPES);
}
List<AgreementType> types = new ArrayList<>();
for (String code : value.split(",")) {
String trimmed = code.trim();
if (trimmed.isEmpty()) {
continue;
}
try {
types.add(AgreementType.valueOf(trimmed));
} catch (IllegalArgumentException e) {
log.warn("PTL_PROPERTY {}/{} 에 알 수 없는 약관 종류 코드가 있어 무시합니다. : {}",
PROPERTY_GROUP, propertyName, trimmed);
}
}
return new ArrayList<>(new LinkedHashSet<>(types));
}
}
@@ -11,23 +11,22 @@ public enum MessageCode {
// 사용자 이벤트 영역
USER_VERIFICATION_EMAIL("이메일 주소 인증", null, Channels.EMAIL),
USER_VERIFICATION_MOBILEPHONE("휴대폰 번호 인증", null, Channels.SMS),
USER_VERIFICATION_MOBILEPHONE("휴대폰 번호 인증", "JJB_85959019001", Channels.SMS),
USER_PASSWORD_RESET("비밀번호 재설정", null, Channels.SMS),
USER_PASSWORD_CHANGED("사용자 비밀번호 변경", null, Channels.SMS),
USER_PASSWORD_CHANGED("사용자 비밀번호 변경", "JJB_85959019003", Channels.SMS),
USER_ACCOUNT_LOCKED("계정 잠금 알림", null, Channels.SMS),
USER_ACCOUNT_LOCKED("계정 잠금 알림", "JJB_85959019002", Channels.SMS),
// 법인관리자 이벤트 영역
USER_INVITATION("법인 이용자 이메일 초대", null, Channels.SMS),
USER_INVITATION_CANCELED("법인 이용자 이메일 초대 취소", null, Channels.SMS),
USER_INVITATION("법인 이용자 이메일 초대", "JJB_85959019006", Channels.SMS),
USER_INVITATION_CANCELED("법인 이용자 이메일 초대 취소", "JJB_85959019007", Channels.SMS),
MANAGER_WITH_ORG_REGISTER_APPROVED("법인관리자 및 법인 등록 승인", null, Channels.SMS),
MANAGER_WITH_ORG_REGISTER_REJECTED("법인관리자 및 법인 등록 거절", null, Channels.SMS),
MANAGER_WITH_ORG_REGISTER_APPROVED("법인관리자 및 법인 등록 승인", "JJB_85959019004", Channels.SMS),
MANAGER_WITH_ORG_REGISTER_REJECTED("법인관리자 및 법인 등록 거절", "JJB_85959019005", Channels.SMS),
// 관리자 영역
APP_APPROVE("앱 사용 승인", null, Channels.SMS),
APP_REJECTED("앱 사용 거절", null, Channels.SMS),
APP_APPROVE("앱 사용 승인", "JJB_85959019008", Channels.SMS),
APP_REJECTED("앱 사용 거절", "JJB_85959019009", Channels.SMS),
// Q&A 등록/댓글 등록 알림 (DJBank 커스텀 — portal-admin 수신)
INQUIRY_CREATED("Q&A 등록 알림", null, Channels.SWING),
@@ -37,9 +36,10 @@ public enum MessageCode {
// 제주은행
API_STATUS_CHANGED("API 상태 변화", null, Channels.SWING),
INFLOW_TOKEN_FAILED("유량제어 토큰 획득 실패", null, Channels.SWING),
ADMIN_VERIFICATION_MOBILEPHONE("관리자포탈 휴대폰 번호 인증", null, Channels.SMS),
ADMIN_VERIFICATION_MOBILEPHONE("관리자포탈 휴대폰 번호 인증", "JJB_85999019001", Channels.SMS),
// 레거시/미사용 — Event 핸들러 클래스 호환을 위해 유지 (실제 발송 없음)
USER_PASSWORD_RESET("비밀번호 재설정", null, Channels.SMS),
API_APPROVED("API 승인 알림", null, Channels.NONE),
APP_REGISTER_REQUEST("앱 등록 요청", null, Channels.NONE),
APP_REGISTER_REJECTED("앱 등록 거절", null, Channels.NONE),
@@ -86,6 +86,9 @@ public class MessageRequest implements Serializable {
@Column(name = "eai_interface_id")
private String eaiInterfaceId;
@Column(name = "eai_tx_id")
private String eaiTxId;
@Column(name = "service_id")
private String serviceId;
@@ -38,21 +38,21 @@ public class MessageSendService {
@Transactional
public List<MessageRequest> sendMessage(MessageCode messageCode, MessageRecipient recipient, Map<String, String> messageParams) {
return processMessage(messageCode.name(), recipient, messageParams);
return processMessage(messageCode, recipient, messageParams);
}
private List<MessageRequest> processMessage(String messageCode, MessageRecipient recipient, Map<String, String> messageParams) {
private List<MessageRequest> processMessage(MessageCode messageCode, MessageRecipient recipient, Map<String, String> messageParams) {
Optional<MessageTemplate> template = messageTemplateRepository.findById(messageCode.toUpperCase());
Optional<MessageTemplate> template = messageTemplateRepository.findById(messageCode.name().toUpperCase());
List<MessageRequest> created = new ArrayList<>();
if (template.isPresent()) {
List<MessageRecipient> recipients = prepareRecipients(template.get(), recipient);
for (MessageRecipient user : recipients) {
created.addAll(processSingleRecipient(user, template.get(), messageParams));
created.addAll(processSingleRecipient(user, template.get(), messageParams, messageCode));
}
} else {
log.warn("메세지 템플릿이 존재 하지 않음 - {}", messageCode);
log.warn("메세지 템플릿이 존재 하지 않음 - {}", messageCode.name());
}
return created;
}
@@ -72,7 +72,7 @@ public class MessageSendService {
}
public List<MessageRequest> processSingleRecipient(MessageRecipient user, MessageTemplate template, Map<String, String> messageParams) {
public List<MessageRequest> processSingleRecipient(MessageRecipient user, MessageTemplate template, Map<String, String> messageParams, MessageCode messageCode) {
Map<String, String> portalProperties = portalPropertyService.getPortalPropertiesAsMap("Portal");
updateMessageParams(user, messageParams);
@@ -83,6 +83,7 @@ public class MessageSendService {
if (template.getEnableSms().equalsIgnoreCase("Y")) {
String smsEaiInterfaceId = portalProperties.get("djb.ums.sms.if_id");
String smsEaiTxId = portalProperties.get("djb.ums.sms.tx_id");
MessageRequest smsRequest = new MessageRequest();
smsRequest.setMessageCode(MessageCode.valueOf(template.getMessageCode().toUpperCase()));
smsRequest.setSubject(subject);
@@ -92,7 +93,8 @@ public class MessageSendService {
smsRequest.setUsername(user.getUsername());
smsRequest.setPhone(user.getPhone());
smsRequest.setEaiInterfaceId(smsEaiInterfaceId);
smsRequest.setServiceId(portalProperties.get("djb.ums.sms.tx_id"));
smsRequest.setEaiTxId(smsEaiTxId);
smsRequest.setServiceId(messageCode.getServiceId());
smsRequest.setUserId(user.getUserId());
smsRequest.setMessageType("SMS");
created.add(messageRequestRepository.save(smsRequest));
@@ -101,6 +103,7 @@ public class MessageSendService {
if (template.getEnableEmail().equalsIgnoreCase("Y")) {
String mailEaiInterfaceId = portalProperties.get("djb.ums.email.if_id");
String mailEaiTxId = portalProperties.get("djb.ums.email.tx_id");
MessageRequest emailRequest = new MessageRequest();
emailRequest.setMessageCode(MessageCode.valueOf(template.getMessageCode().toUpperCase()));
emailRequest.setSubject(subject);
@@ -110,7 +113,8 @@ public class MessageSendService {
emailRequest.setUsername(resolveEmailUsername(user));
emailRequest.setEmail(user.getUserId());
emailRequest.setEaiInterfaceId(mailEaiInterfaceId);
emailRequest.setServiceId(portalProperties.get("djb.ums.email.tx_id"));
emailRequest.setEaiTxId(mailEaiTxId);
emailRequest.setServiceId(messageCode.getServiceId());
emailRequest.setMessageType("EMAIL");
emailRequest.setUserId(user.getUserId());
created.add(messageRequestRepository.save(emailRequest));
@@ -119,6 +123,7 @@ public class MessageSendService {
if (template.getEnableMessenger().equalsIgnoreCase("Y")) {
String swingEaiInterfaceId = portalProperties.get("djb.ums.messenger.if_id");
String swingEaiTxId = portalProperties.get("djb.ums.messenger.tx_id");
MessageRequest messengerRequest = new MessageRequest();
messengerRequest.setMessageCode(MessageCode.valueOf(template.getMessageCode()));
messengerRequest.setSubject(subject);
@@ -128,7 +133,8 @@ public class MessageSendService {
messengerRequest.setUsername(user.getUsername());
messengerRequest.setMessengerId(user.getMessengerId());
messengerRequest.setEaiInterfaceId(swingEaiInterfaceId);
messengerRequest.setServiceId(portalProperties.get("djb.ums.messenger.tx_id"));
messengerRequest.setEaiTxId(swingEaiTxId);
messengerRequest.setServiceId(messageCode.getServiceId());
messengerRequest.setMessageType("MESSENGER");
messengerRequest.setUserId(user.getUserId());
created.add(messageRequestRepository.save(messengerRequest));