- GwClientSyncService 분리 및 트랜잭션 관리 강화 - 승인 화면에 GW 반영 필수 정보 노출 추가 - JSP 동적 행 노출 및 승인 데이터 검증 로직 적용
This commit is contained in:
@@ -246,6 +246,10 @@ public class PortalApprovalManService extends BaseService {
|
||||
appApprovalUI.setClientName(appRequest.getClientName());
|
||||
// 신청 사유는 상세 내용과 별개 항목으로도 내려준다(해지 신청 화면에서 "해지 사유"로 표기)
|
||||
appApprovalUI.setRequestReason(appRequest.getReason());
|
||||
// 승인 시 GW 클라이언트로 그대로 반영되는 항목이므로 승인자가 검토할 수 있게 함께 내려준다.
|
||||
appApprovalUI.setIpWhitelist(appRequest.getIpWhitelist());
|
||||
appApprovalUI.setCallbackUrl(appRequest.getCallbackUrl());
|
||||
appApprovalUI.setAppDescription(appRequest.getAppDescription());
|
||||
|
||||
appApprovalUI.getApprovers().forEach(approver -> {
|
||||
if (approver.getUser() == null) {
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.eactive.eai.rms.onl.apim.approval.app;
|
||||
|
||||
import com.eactive.apim.portal.apprequest.entity.AppRequest;
|
||||
import com.eactive.apim.portal.apprequest.entity.AppRequestType;
|
||||
import com.eactive.apim.portal.apprequest.entity.GwAction;
|
||||
import com.eactive.apim.portal.approval.service.ApprovalDeployException;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceType;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
|
||||
import com.eactive.eai.rms.onl.apim.approval.credential.CredentialUI;
|
||||
import com.eactive.eai.rms.onl.apim.approval.credential.CredentialUIMapper;
|
||||
import com.eactive.eai.rms.onl.manage.authserver.client.ClientManService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* APP 승인 시 GW 인증 클라이언트(APIGW 스키마 TSEAIAU01) 반영을 <b>승인 트랜잭션과 분리된 새 트랜잭션</b>에서
|
||||
* 수행하고 커밋까지 끝낸다.
|
||||
*
|
||||
* <p>분리가 필요한 이유: GW 는 RELOAD_CLIENT 명령을 받으면 자기 커넥션으로 DB 를 다시 읽어 메모리에 적재한다
|
||||
* ({@code OAuth2Manager#reloadClient} 는 캐시에서 제거 후 {@code getClientById} 결과가 있을 때만 다시 넣는다).
|
||||
* 승인 트랜잭션 안에서 INSERT 한 직후 broadcast 하면 그 행이 아직 커밋되지 않아 GW 가 찾지 못하고,
|
||||
* 신규(NEW) 클라이언트는 캐시에 올라가지 못한 채 리로드가 무의미해진다. 변경(MODIFY)도 커밋 전 옛 값이
|
||||
* 다시 적재된다. 따라서 broadcast 는 반드시 이 메서드가 반환(=커밋)된 뒤에 수행해야 한다.
|
||||
*
|
||||
* <p>트랜잭션 매니저는 APIGW 스키마를 라우팅하는 멀티테넌트 EMF 쪽({@code transactionManager})을 사용한다.
|
||||
* Hibernate 는 Session 을 열 때 테넌트를 한 번만 해석하므로, 새 트랜잭션을 여는 이 시점에
|
||||
* {@link DataSourceContextHolder} 가 APIGW 로 설정돼 있어야 한다.
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class GwClientSyncService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(GwClientSyncService.class);
|
||||
|
||||
private final ClientManService clientManService;
|
||||
private final CredentialUIMapper credentialUIMapper;
|
||||
|
||||
@Transactional(transactionManager = "transactionManager",
|
||||
propagation = Propagation.REQUIRES_NEW,
|
||||
rollbackFor = ApprovalDeployException.class)
|
||||
public void apply(AppRequest appRequest, CredentialUI credentialUI) throws ApprovalDeployException {
|
||||
// 새 Session 이 열리는 시점의 테넌트가 APIGW 여야 TSEAIAU01 에 접근할 수 있다.
|
||||
DataSourceType type = DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW);
|
||||
DataSourceContextHolder.setDataSourceType(type);
|
||||
|
||||
AppRequestType action = appRequest.getType();
|
||||
if (AppRequestType.NEW.equals(action)) {
|
||||
clientManService.insert(credentialUIMapper.mapClient(credentialUI));
|
||||
} else if (AppRequestType.MODIFY.equals(action)) {
|
||||
clientManService.update(credentialUIMapper.mapClient(credentialUI));
|
||||
} else if (AppRequestType.DELETE.equals(action)) {
|
||||
applyGwActionForDelete(appRequest.getGwAction(), credentialUI.getClientid());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* API 이용해지(DELETE) 승인 시 GW 클라이언트(TSEAIAU01) 처리.
|
||||
* 승인 시점에 관리자가 선택해 PTL_APP_REQUEST.GW_ACTION 에 영속화된 값을 따른다.
|
||||
* - DELETE: 행 완전 삭제 / - BLOCK 또는 null(미지정): 차단(appstatus='0')
|
||||
* GW 에 행이 이미 없으면(선삭제·재처리) 목적이 달성된 상태이므로 통과시키고,
|
||||
* 그 외 오류는 ApprovalDeployException 으로 던져 FAILED(재처리 가능) 상태를 만든다.
|
||||
*/
|
||||
private void applyGwActionForDelete(GwAction gwAction, String clientId) throws ApprovalDeployException {
|
||||
try {
|
||||
if (GwAction.DELETE.equals(gwAction)) {
|
||||
clientManService.delete(clientId);
|
||||
logger.info("GW 클라이언트 완전 삭제 - clientId: {}", clientId);
|
||||
} else {
|
||||
clientManService.blockClient(clientId);
|
||||
logger.info("GW 클라이언트 차단(appstatus=0) - clientId: {}", clientId);
|
||||
}
|
||||
} catch (org.springframework.dao.EmptyResultDataAccessException | javax.persistence.EntityNotFoundException e) {
|
||||
logger.warn("GW 클라이언트가 이미 존재하지 않아 처리 생략 (재처리 통과) - clientId: {}", clientId);
|
||||
} catch (RuntimeException e) {
|
||||
logger.error("GW 클라이언트 해지 처리 실패 - clientId: {}, gwAction: {}", clientId, gwAction, e);
|
||||
throw new ApprovalDeployException("GW 클라이언트 해지 처리에 실패했습니다: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
-40
@@ -2,7 +2,6 @@ package com.eactive.eai.rms.onl.apim.approval.app;
|
||||
|
||||
import com.eactive.apim.portal.apprequest.entity.AppRequest;
|
||||
import com.eactive.apim.portal.apprequest.entity.AppRequestType;
|
||||
import com.eactive.apim.portal.apprequest.entity.GwAction;
|
||||
import com.eactive.apim.portal.apprequest.repository.AppRequestRepository;
|
||||
import com.eactive.apim.portal.approval.entity.Approval;
|
||||
import com.eactive.apim.portal.approval.service.ApprovalDeployException;
|
||||
@@ -10,6 +9,7 @@ import com.eactive.apim.portal.approval.statemachine.listener.ApprovalListener;
|
||||
import com.eactive.apim.portal.template.entity.MessageCode;
|
||||
import com.eactive.apim.portal.template.service.MessageRecipient;
|
||||
import com.eactive.apim.portal.template.service.MessageSendService;
|
||||
import com.eactive.eai.agent.command.CommandResult;
|
||||
import com.eactive.eai.agent.command.CommonCommand;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceType;
|
||||
@@ -18,9 +18,7 @@ import com.eactive.eai.rms.data.entity.onl.apim.apigroup.ApiGroupService;
|
||||
import com.eactive.eai.rms.onl.apim.approval.RandomStringGenerator;
|
||||
import com.eactive.eai.rms.onl.apim.approval.credential.CredentialManService;
|
||||
import com.eactive.eai.rms.onl.apim.approval.credential.CredentialUI;
|
||||
import com.eactive.eai.rms.onl.apim.approval.credential.CredentialUIMapper;
|
||||
import com.eactive.eai.rms.onl.common.service.AgentUtilService;
|
||||
import com.eactive.eai.rms.onl.manage.authserver.client.ClientManService;
|
||||
import com.eactive.eai.rms.onl.transaction.apim.ui.ApiSpecInfoUI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -55,9 +53,8 @@ public class PortalAppApprovalListener implements ApprovalListener {
|
||||
private final ApiGroupService apiGroupService;
|
||||
private final AppRequestRepository appRequestRepository;
|
||||
private final CredentialManService credentialManService;
|
||||
private final CredentialUIMapper credentialUIMapper;
|
||||
private final ClientManService clientManService;
|
||||
private final MessageSendService messageSendService;
|
||||
private final GwClientSyncService gwClientSyncService;
|
||||
|
||||
|
||||
@Override
|
||||
@@ -127,47 +124,23 @@ public class PortalAppApprovalListener implements ApprovalListener {
|
||||
|
||||
private void syncTargetServer(AppRequest appRequest, CredentialUI credentialUI) throws ApprovalDeployException {
|
||||
|
||||
AppRequestType action = appRequest.getType();
|
||||
|
||||
DataSourceType type = DataSourceTypeManager.getDataSourceType("APIGW");
|
||||
// TSEAIAU01/TSEAISY02 는 APIGW 스키마에 있으므로 라우팅을 먼저 설정한다.
|
||||
// (GW 반영은 새 트랜잭션에서, 이어지는 broadcast 의 서버목록 조회도 이 테넌트로 수행된다)
|
||||
DataSourceType type = DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW);
|
||||
DataSourceContextHolder.setDataSourceType(type);
|
||||
|
||||
if (action.equals(AppRequestType.NEW)) {
|
||||
clientManService.insert(credentialUIMapper.mapClient(credentialUI));
|
||||
} else if (action.equals(AppRequestType.MODIFY)) {
|
||||
clientManService.update(credentialUIMapper.mapClient(credentialUI));
|
||||
} else if (action.equals(AppRequestType.DELETE)) {
|
||||
applyGwActionForDelete(appRequest.getGwAction(), credentialUI.getClientid());
|
||||
}
|
||||
// GW 반영은 별도 트랜잭션에서 커밋까지 끝낸다. GW 는 리로드 명령을 받고 DB 를 다시 읽으므로
|
||||
// 커밋 전에 broadcast 하면 신규 클라이언트를 찾지 못해 리로드가 무의미해진다.
|
||||
gwClientSyncService.apply(appRequest, credentialUI);
|
||||
|
||||
CommonCommand.builder()
|
||||
// broadcast 는 서버별 전송 실패를 예외로 던지지 않고 결과 맵에 담아 정상 반환한다.
|
||||
// 결과를 버리면 리로드 실패가 아무 흔적 없이 묻히므로 서버별 응답을 반드시 남긴다.
|
||||
CommandResult reloadResult = CommonCommand.builder()
|
||||
.name(CommonCommand.RELOAD_CLIENT_COMMAND)
|
||||
.args(credentialUI.getClientid())
|
||||
.build().broadcast(agentUtilService);
|
||||
}
|
||||
|
||||
/**
|
||||
* API 이용해지 승인 시 GW 클라이언트(TSEAIAU01) 처리.
|
||||
* 승인 시점에 관리자가 선택해 PTL_APP_REQUEST.GW_ACTION 에 영속화된 값을 따른다.
|
||||
* - DELETE: 행 완전 삭제 / - BLOCK 또는 null(미지정): 차단(appstatus='0')
|
||||
* GW 에 행이 이미 없으면(선삭제·재처리) 목적이 달성된 상태이므로 통과시키고,
|
||||
* 그 외 오류는 ApprovalDeployException 으로 던져 FAILED(재처리 가능) 상태를 만든다.
|
||||
*/
|
||||
private void applyGwActionForDelete(GwAction gwAction, String clientId) throws ApprovalDeployException {
|
||||
try {
|
||||
if (GwAction.DELETE.equals(gwAction)) {
|
||||
clientManService.delete(clientId);
|
||||
logger.info("GW 클라이언트 완전 삭제 - clientId: {}", clientId);
|
||||
} else {
|
||||
clientManService.blockClient(clientId);
|
||||
logger.info("GW 클라이언트 차단(appstatus=0) - clientId: {}", clientId);
|
||||
}
|
||||
} catch (org.springframework.dao.EmptyResultDataAccessException | javax.persistence.EntityNotFoundException e) {
|
||||
logger.warn("GW 클라이언트가 이미 존재하지 않아 처리 생략 (재처리 통과) - clientId: {}", clientId);
|
||||
} catch (RuntimeException e) {
|
||||
logger.error("GW 클라이언트 해지 처리 실패 - clientId: {}, gwAction: {}", clientId, gwAction, e);
|
||||
throw new ApprovalDeployException("GW 클라이언트 해지 처리에 실패했습니다: " + e.getMessage());
|
||||
}
|
||||
logger.info("GW 클라이언트 리로드 broadcast - clientId: {}, 서버별 응답: {}",
|
||||
credentialUI.getClientid(), reloadResult.getMessage(false));
|
||||
}
|
||||
|
||||
private CredentialUI modifyClientUI(AppRequest appRequest) {
|
||||
|
||||
@@ -33,4 +33,9 @@ public class PortalAppApprovalUI extends PortalApprovalUI {
|
||||
|
||||
//신청자가 입력한 사유(PTL_APP_REQUEST.REASON). 해지 신청은 사유가 필수라 별도 항목으로 노출한다.
|
||||
private String requestReason;
|
||||
|
||||
//신청서(PTL_APP_REQUEST)에 담긴 Client 등록 정보. 승인 시 GW(TSEAIAU01)로 그대로 반영되므로 승인 화면에 노출한다.
|
||||
private String ipWhitelist;
|
||||
private String callbackUrl;
|
||||
private String appDescription;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user