앱 관리

This commit is contained in:
현성필
2025-11-01 11:49:11 +09:00
parent 6841c9236d
commit 563a3f054c
46 changed files with 9740 additions and 2062 deletions
@@ -4,18 +4,11 @@ package com.eactive.apim.portal.apps.apis.controller;
import com.eactive.apim.portal.apps.apis.dto.ApiSpecInfoDto;
import com.eactive.apim.portal.apps.apis.service.ApiService;
import com.eactive.apim.portal.apps.apiservice.dto.ApiGroupSearch;
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceApiDTO;
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO;
import com.eactive.apim.portal.apps.apiservice.service.ApiServiceService;
import com.eactive.apim.portal.apps.app.dto.ClientDTO;
import com.eactive.apim.portal.apps.app.service.AppServiceFacade;
import com.eactive.apim.portal.common.user.PortalAuthenticatedUser;
import com.eactive.apim.portal.common.util.SecurityUtil;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.GetMapping;
@@ -28,11 +21,8 @@ import org.springframework.web.bind.annotation.RestController;
@RequiredArgsConstructor
public class ApiAjaxController {
private final static String STG_SERVER = "STG";
private final ApiService apiService;
private final ApiServiceService apiServiceService;
private final AppServiceFacade appServiceFacade;
public Map<String, ApiServiceDTO> getMainIconsFromServiceDtos(List<ApiServiceDTO> apiServices) {
Map<String, ApiServiceDTO> mainIconsMap = new HashMap<>();
@@ -54,53 +44,15 @@ public class ApiAjaxController {
Map<String, ApiServiceDTO> mainIconsMap = getMainIconsFromServiceDtos(apiServices);
List<ApiSpecInfoDto> apiSpecInfoDtos = apiService.findApis();
for (ApiSpecInfoDto spec : apiSpecInfoDtos) {
ApiServiceDTO serviceDTO = mainIconsMap.get(spec.getApiId());
if (serviceDTO != null) {
String mainIcon = serviceDTO.getMainIcon();
String service = serviceDTO.getGroupName();
spec.setMainIcon(mainIcon);
spec.setService(service);
}
}
return apiSpecInfoDtos;
}
@GetMapping("/for_request_prod")
@Secured("ROLE_API_KEY_REQUEST")
public List<ApiSpecInfoDto> apiRequestProd(@ModelAttribute ApiGroupSearch search) {
List<ApiServiceDTO> apiServices = apiServiceService.searchApiGroups(search);
Map<String, ApiServiceDTO> mainIconsMap = getMainIconsFromServiceDtos(apiServices);
PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser();
List<ClientDTO> apikeys = appServiceFacade.getApiKeyList(user.getPortalOrg(), STG_SERVER);
List<ApiServiceApiDTO> apis = apikeys.stream()
.flatMap(client -> client.getApiList().stream()) // flatten all apiLists into a single stream of ApiServiceApiDTO
.distinct() // remove duplicates
.collect(Collectors.toList()); // collect to List
List<ApiSpecInfoDto> apiSpecInfoDtos = new ArrayList<>();
apis.forEach(api -> {
ApiSpecInfoDto specInfoDto = new ApiSpecInfoDto();
specInfoDto.setApiId(api.getApiId());
specInfoDto.setApiName(api.getApiDesc());
apiSpecInfoDtos.add(specInfoDto);
});
for (ApiSpecInfoDto spec : apiSpecInfoDtos) {
ApiServiceDTO serviceDTO = mainIconsMap.get(spec.getApiId());
if (serviceDTO != null) {
String mainIcon = serviceDTO.getMainIcon();
String service = serviceDTO.getGroupName();
spec.setMainIcon(mainIcon);
spec.setService(service);
}
}
return apiSpecInfoDtos;
// Filter APIs that have matching service information
return apiSpecInfoDtos.stream()
.filter(spec -> mainIconsMap.containsKey(spec.getApiId()))
.peek(spec -> {
ApiServiceDTO serviceDTO = mainIconsMap.get(spec.getApiId());
spec.setMainIcon(serviceDTO.getMainIcon());
spec.setService(serviceDTO.getGroupName());
})
.collect(java.util.stream.Collectors.toList());
}
}
@@ -1,17 +1,17 @@
package com.eactive.apim.portal.apps.apis.controller;
import com.eactive.apim.portal.apps.apis.dto.ApiSearchData;
import com.eactive.apim.portal.apps.apis.dto.ApiSpecInfoDto;
import com.eactive.apim.portal.apps.apis.service.ApiSearchFacade;
import com.eactive.apim.portal.apps.apis.service.ApiService;
import com.eactive.apim.portal.apps.apiservice.dto.ApiGroupSearch;
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO;
import com.eactive.apim.portal.apps.apiservice.service.ApiServiceService;
import java.util.*;
import com.eactive.apim.portal.common.exception.NotFoundException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
@@ -1,112 +0,0 @@
package com.eactive.apim.portal.apps.app.controller;
import com.eactive.apim.portal.apprequest.entity.AppRequestType;
import com.eactive.apim.portal.apps.apis.dto.ApiSearchData;
import com.eactive.apim.portal.apps.apis.service.ApiSearchFacade;
import com.eactive.apim.portal.apps.app.dto.AppRequestDTO;
import com.eactive.apim.portal.apps.app.dto.ClientDTO;
import com.eactive.apim.portal.apps.app.dto.ClientSimpleDTO;
import com.eactive.apim.portal.apps.app.mapper.CredentialMapper;
import com.eactive.apim.portal.apps.app.service.AppServiceFacade;
import com.eactive.apim.portal.common.dto.ResponseDTO;
import com.eactive.apim.portal.common.user.PortalAuthenticatedUser;
import com.eactive.apim.portal.common.util.EncryptionUtil;
import com.eactive.apim.portal.common.util.SecurityUtil;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.stream.Collectors;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.security.access.annotation.Secured;
import org.springframework.ui.Model;
import org.springframework.validation.ValidationUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/myapikey")
@RequiredArgsConstructor
@Secured("ROLE_API_KEY_REQUEST")
public class ApiKeyRestController {
private final static String PROD_SERVER = "PROD";
private final static String STG_SERVER = "STG";
private final AppServiceFacade appServiceFacade;
private final CredentialMapper credentialMapper;
private final EncryptionUtil encryptionUtil;
private final ApiSearchFacade apiSearchFacade;
@PostMapping("/api_key_request")
public ResponseDTO apiRequest(@RequestBody AppRequestDTO appRequest) {
appRequest.setOrg(SecurityUtil.getUserOrg());
if (appRequest.getType() == null) {
appRequest.setType(AppRequestType.NEW);
}
AppRequestDTO appRequestDTO = appServiceFacade.createAppRequest(appRequest);
appServiceFacade.beginApproval(appRequestDTO.getApproval().getId());
return new ResponseDTO(200, "SUCCESS", appRequestDTO.getType().getDescription() + " 신청이 완료되었습니다.");
}
@PostMapping("/api_key_request_prod")
public ResponseDTO requestProdApiKey(@RequestBody AppRequestDTO appRequest) {
appRequest.setOrg(SecurityUtil.getUserOrg());
if (appRequest.getClientId() != null && !appRequest.getClientId().isEmpty()) {
try {
appRequest.setClientId(encryptionUtil.decrypt(appRequest.getClientId()));
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
return new ResponseDTO(400, "FAILURE", "API 키 정보가 올바르지 않습니다.");
}
appRequest.setType(AppRequestType.PROD_MODIFY);
} else {
appRequest.setType(AppRequestType.PROD_NEW);
}
AppRequestDTO appRequestDTO = appServiceFacade.createAppRequest(appRequest);
appServiceFacade.beginApproval(appRequestDTO.getApproval().getId());
return new ResponseDTO(200, "SUCCESS", appRequestDTO.getType().getDescription() + " 신청이 완료되었습니다.");
}
@GetMapping("/dev")
public List<ClientSimpleDTO> apiKeyList() {
PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser();
List<ClientDTO> apikeys = appServiceFacade.getApiKeyList(user.getPortalOrg(), STG_SERVER);
ApiSearchData apiSearchData = apiSearchFacade.loadAllApiData();
for (ClientDTO apikey : apikeys) {
apikey.getApiList().forEach(api -> {
api.setService(apiSearchData.getServicesByApiId().get(api.getApiId()).getGroupName());
});
}
return apikeys.stream().map(credentialMapper::mapToClientSimpleDTO).collect(Collectors.toList());
}
@PostMapping("/prod")
public ClientSimpleDTO apiKeyProdList(@RequestBody AppRequestDTO appRequest) {
PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser();
try {
ClientDTO apiKey = appServiceFacade.getApiKey(user.getPortalOrg().getId(), encryptionUtil.decrypt(appRequest.getClientId()), PROD_SERVER);
ApiSearchData apiSearchData = apiSearchFacade.loadAllApiData();
apiKey.getApiList().forEach(api -> {
api.setService(apiSearchData.getServicesByApiId().get(api.getApiId()).getGroupName());
});
apiKey.setClientid(encryptionUtil.encrypt(apiKey.getClientid()));
return credentialMapper.mapToClientSimpleDTO(apiKey);
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
return new ClientSimpleDTO();
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,111 @@
package com.eactive.apim.portal.apps.app.dto;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
/**
* API Key Registration Session Data
* Holds data across multiple registration steps
*/
@Data
public class ApiKeyRegistrationDTO implements Serializable {
private static final long serialVersionUID = 1L;
// Client ID for modification operations
private String clientId;
// Request type for tracking operation type
private String requestType; // "NEW" or "MODIFY"
// Existing app icon file ID (for modifications)
private String appIconFileId;
// Step 1: Basic Information
// Transient field for file upload (not serialized in session)
private transient MultipartFile appIcon;
// Persisted file data
private String appIconFileName;
private byte[] appIconData;
private String appIconContentType;
@NotBlank(message = "앱 이름을 입력해주세요.")
@Size(max = 100, message = "앱 이름은 100자를 초과할 수 없습니다.")
private String appName;
@NotBlank(message = "앱 설명을 입력해주세요.")
@Size(max = 500, message = "앱 설명은 500자를 초과할 수 없습니다.")
private String appDescription;
// Optional field - allows empty string or valid URL with http(s):// protocol
@Pattern(regexp = "^$|^https?://.*", message = "올바른 URL 형식이 아닙니다.")
private String callbackUrl;
private List<String> ipWhitelist = new ArrayList<>();
// Step 2: API Selection
private List<String> selectedApis = new ArrayList<>();
// Helper method to add IP to whitelist
public void addIpAddress(String ip) {
if (ip != null && !ip.trim().isEmpty() && !ipWhitelist.contains(ip)) {
ipWhitelist.add(ip);
}
}
// Helper method to set IP whitelist from comma-separated string
public void setIpWhitelistFromString(String ipString) {
ipWhitelist.clear();
if (ipString != null && !ipString.trim().isEmpty()) {
String[] ips = ipString.split(",");
for (String ip : ips) {
String trimmedIp = ip.trim();
if (!trimmedIp.isEmpty()) {
ipWhitelist.add(trimmedIp);
}
}
}
}
// Helper method to get IP whitelist as comma-separated string
public String getIpWhitelistAsString() {
return String.join(",", ipWhitelist);
}
// Helper method to get app icon as base64 data URI for image display
public String getAppIconBase64() {
if (appIconData == null || appIconData.length == 0) {
return null;
}
String base64Data = Base64.getEncoder().encodeToString(appIconData);
String contentType = appIconContentType != null ? appIconContentType : "image/png";
return "data:" + contentType + ";base64," + base64Data;
}
// Validation helpers
public boolean isStep1Complete() {
// callbackUrl is optional, so we only check required fields
// For modifications, appIconFileId indicates an existing icon
boolean hasIcon = (appIconData != null && appIconData.length > 0)
|| (appIconFileId != null && !appIconFileId.trim().isEmpty());
return hasIcon &&
appName != null && !appName.trim().isEmpty() &&
appDescription != null && !appDescription.trim().isEmpty();
}
public boolean isStep2Complete() {
return selectedApis != null && !selectedApis.isEmpty();
}
public boolean isComplete() {
return isStep1Complete() && isStep2Complete();
}
}
@@ -44,6 +44,14 @@ public class AppRequestDTO {
private String reason;
private String appDescription;
private String callbackUrl;
private String ipWhitelist;
private String appIconFileId;
private LocalDateTime createdDate;
private String prevApiList;
@@ -45,6 +45,10 @@ public class ClientDTO {
private String appstatus;
private String appDescription;
private String appIconFileId;
private List<ApiServiceApiDTO> apiList = new ArrayList<>();
}
@@ -4,6 +4,7 @@ package com.eactive.apim.portal.apps.app.mapper;
import com.eactive.apim.portal.apprequest.entity.AppRequest;
import com.eactive.apim.portal.apps.apiservice.mapper.ApiServiceApiMapper;
import com.eactive.apim.portal.apps.app.dto.AppRequestDTO;
import com.eactive.apim.portal.apps.app.dto.ClientDTO;
import com.eactive.apim.portal.apps.approval.mapper.ApprovalMapper;
import com.eactive.apim.portal.apps.user.mapper.PortalOrgMapper;
import com.eactive.eai.data.mapper.BaseMapperConfig;
@@ -13,4 +14,53 @@ import org.mapstruct.Mapper;
@Mapper(config = BaseMapperConfig.class, uses = {PortalOrgMapper.class, ApiServiceApiMapper.class, ApprovalMapper.class})
public interface AppRequestMapper extends GenericMapper<AppRequestDTO, AppRequest> {
/**
* AppRequest를 ClientDTO로 간단하게 매핑합니다.
* 뷰에서 AppRequest를 ClientDTO와 동일한 형식으로 표시하기 위해 사용됩니다.
*
* @param appRequest 앱 요청 엔티티
* @return ClientDTO 표시용 DTO
*/
default ClientDTO mapToClientDTO(AppRequest appRequest) {
if (appRequest == null) {
return null;
}
ClientDTO dto = new ClientDTO();
// 기본 식별 정보
dto.setClientid(appRequest.getId()); // AppRequest ID를 임시 client ID로 사용
dto.setClientname(appRequest.getClientName());
// 앱 상세 정보
dto.setAppDescription(appRequest.getAppDescription());
dto.setAppIconFileId(appRequest.getAppIconFileId());
// 네트워크 설정
dto.setRedirecturi(appRequest.getCallbackUrl());
dto.setAllowedips(appRequest.getIpWhitelist());
// 상태 정보 (ApprovalStatus → appstatus 매핑)
if (appRequest.getStatus() != null) {
switch (appRequest.getStatus()) {
case APPROVED:
dto.setAppstatus("1"); // 승인됨 = 활성화
break;
case DENIED:
dto.setAppstatus("0"); // 거부됨 = 비활성화
break;
default:
dto.setAppstatus("2"); // 대기중/진행중 = 보류
break;
}
}
// 수정 날짜 (createdDate를 modifiedon으로 사용)
dto.setModifiedon(appRequest.getCreatedDate());
// API 목록은 별도 처리 필요 (콤마로 구분된 문자열 → List<ApiServiceApiDTO>)
// 필요시 컨트롤러에서 추가 처리
return dto;
}
}
@@ -1,15 +0,0 @@
package com.eactive.apim.portal.apps.app.mapper;
import com.eactive.apim.portal.app.entity.ProdClient;
import com.eactive.apim.portal.apps.apiservice.mapper.ApiSpecInfoMapper;
import com.eactive.apim.portal.apps.app.dto.ClientDTO;
import com.eactive.eai.data.mapper.BaseMapperConfig;
import com.eactive.eai.data.mapper.GenericMapper;
import org.mapstruct.Mapper;
@Mapper(config = BaseMapperConfig.class, uses = {ApiSpecInfoMapper.class})
public interface ProdClientMapper extends GenericMapper<ClientDTO, ProdClient> {
}
@@ -7,12 +7,14 @@ import com.eactive.apim.portal.apprequest.entity.AppRequest;
import com.eactive.apim.portal.apprequest.entity.AppRequestType;
import com.eactive.apim.portal.apprequest.repository.AppRequestRepository;
import com.eactive.apim.portal.approval.entity.Approval;
import com.eactive.apim.portal.approval.statemachine.ProcessingState;
import com.eactive.apim.portal.approval.statemachine.RequestedState;
import com.eactive.apim.portal.apps.apis.dto.ApiSpecInfoDto;
import com.eactive.apim.portal.apps.apis.service.ApiService;
import com.eactive.apim.portal.apps.apiservice.dto.ApiGroupSearch;
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO;
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceApiDTO;
import com.eactive.apim.portal.apps.apiservice.service.ApiServiceService;
import com.eactive.apim.portal.apps.app.dto.ApiKeyRegistrationDTO;
import com.eactive.apim.portal.apps.app.dto.AppRequestDTO;
import com.eactive.apim.portal.apps.app.dto.ClientDTO;
import com.eactive.apim.portal.apps.app.mapper.AppRequestMapper;
@@ -21,16 +23,24 @@ import com.eactive.apim.portal.apps.approval.service.ApprovalService;
import com.eactive.apim.portal.apps.user.dto.PortalOrgDTO;
import com.eactive.apim.portal.apps.user.mapper.PortalOrgMapper;
import com.eactive.apim.portal.common.exception.NotFoundException;
import com.eactive.apim.portal.common.user.PortalAuthenticatedUser;
import com.eactive.apim.portal.common.util.ApiServiceHelper;
import com.eactive.apim.portal.file.entity.FileInfo;
import com.eactive.apim.portal.file.service.FileService;
import com.eactive.apim.portal.portalorg.entity.PortalOrg;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
@@ -47,53 +57,64 @@ public class AppServiceFacade {
private final AppRequestMapper appRequestMapper;
private final PortalOrgMapper portalOrgMapper;
private final ApiServiceHelper apiServiceHelper;
private final FileService fileService;
private final PasswordEncoder passwordEncoder;
public Page<ClientDTO> getApiKeyPageForServer(Pageable pageable, PortalOrg portalOrg, String server) {
Page<Credential> clients = credentialRepository.findAllByOrgidAndServer(pageable, portalOrg.getId(), server);
return clients.map(credentialMapper::toVo);
}
public List<ClientDTO> getApikeyList(PortalOrg portalOrg) {
public List<ClientDTO> getApiKeyList(PortalOrg portalOrg, String server) {
List<Credential> clients = credentialRepository.findAllByOrgidAndServer(portalOrg.getId(), server);
List<Credential> clients = credentialRepository.findAllByOrgid(portalOrg.getId());
return clients.stream().map(credentialMapper::toVo).collect(Collectors.toList());
}
public ClientDTO getApiKey(String orgid, String clientId, String server) {
return credentialRepository.findByClientidAndOrgidAndServer(clientId, orgid, server).map(credentialMapper::toVo).orElse(null);
public List<AppRequest> getPendingApiKeyList(PortalOrg portalOrg) {
List<AppRequest> appRequests = appRequestRepository.findAllByOrgAndTypeIsInAndApproval_ApprovalStatusIn(portalOrg, Arrays.asList(AppRequestType.NEW, AppRequestType.MODIFY, AppRequestType.DELETE),
Arrays.asList(new ProcessingState(), new RequestedState()));
return appRequests;
}
public ClientDTO getApiKey(String orgid, String clientId) {
return credentialRepository.findByClientidAndOrgid(clientId, orgid).map(credentialMapper::toVo).orElse(null);
}
/**
* AppRequest ID와 조직 정보로 AppRequest를 조회합니다.
* API 그룹 및 API 목록 정보를 함께 처리하여 반환합니다.
*
* @param id AppRequest ID
* @param portalOrg 포털 조직
* @return AppRequest DTO (없으면 null)
*/
public AppRequestDTO getAppRequestById(String id, PortalOrg portalOrg) {
return getApiRequestByIdAndPortalOrg(id, portalOrg);
}
public Page<AppRequestDTO> getApiRequestHistory(Pageable pageable, PortalOrgDTO portalOrg) {
return appRequestRepository.findAllByOrgAndTypeIsIn(pageable, portalOrgMapper.toEntity(portalOrg), Arrays.asList(AppRequestType.NEW,AppRequestType.MODIFY, AppRequestType.DELETE)).map(appRequestMapper::toVo);
}
public Page<AppRequestDTO> getApiRequestProdHistory(Pageable pageable, PortalOrgDTO portalOrg) {
return appRequestRepository.findAllByOrgAndTypeIsIn(pageable, portalOrgMapper.toEntity(portalOrg), Arrays.asList(AppRequestType.PROD_NEW,AppRequestType.PROD_MODIFY, AppRequestType.PROD_DELETE)).map(appRequestMapper::toVo);
return appRequestRepository.findAllByOrgAndTypeIsIn(pageable, portalOrgMapper.toEntity(portalOrg), Arrays.asList(AppRequestType.NEW, AppRequestType.MODIFY, AppRequestType.DELETE)).map(appRequestMapper::toVo);
}
public AppRequestDTO createAppRequest(AppRequestDTO appRequest) {
setApiGroupsFromApiList(appRequest);
AppRequest request = appRequestRepository.save(appRequestMapper.toEntity(appRequest));
if (request.getType() != null && (request.getType().equals(AppRequestType.MODIFY) || request.getType().equals(AppRequestType.DELETE) ||
request.getType().equals(AppRequestType.PROD_MODIFY) || request.getType().equals(AppRequestType.PROD_DELETE))) {
if (request.getType() != null && (request.getType().equals(AppRequestType.MODIFY) || request.getType().equals(AppRequestType.DELETE))) {
Optional<Credential> client = credentialRepository.findById(request.getClientId());
if (client.isPresent()) {
request.setClientName(client.get().getClientname());
if(client.get().getApiList() != null) {
if (client.get().getApiList() != null) {
// 기존 API 목록을 저장
String existingApiList = client.get().getApiList().stream()
.map(ApiSpecInfo::getApiId)
.collect(Collectors.joining(","));
.map(ApiSpecInfo::getApiId)
.collect(Collectors.joining(","));
request.setPrevApiList(existingApiList);
}
if(AppRequestType.DELETE.equals(request.getType()) || AppRequestType.PROD_DELETE.equals(request.getType())) {
if (AppRequestType.DELETE.equals(request.getType())) {
request.setApiList(request.getPrevApiList());
}
} else {
@@ -108,22 +129,6 @@ public class AppServiceFacade {
return appRequestMapper.toVo(request);
}
private void setApiGroupsFromApiList(AppRequestDTO appRequest) {
if (StringUtils.isEmpty(appRequest.getApiList())) {
return;
}
Set<String> uniqueGroupIds = Arrays.stream(appRequest.getApiList().split(","))
.map(apiId -> apiServiceService.findApiServiceByApiId(apiId))
.filter(Objects::nonNull)
.flatMap(apiService -> apiService.getApiGroupApiList().stream())
.map(ApiServiceApiDTO::getApiGroupId)
.collect(Collectors.toSet());
appRequest.setApiGroupList(String.join(",", uniqueGroupIds));
}
public void beginApproval(String approvalId) {
approvalService.beginApproval(approvalId);
}
@@ -132,27 +137,11 @@ public class AppServiceFacade {
appRequestRepository.findByIdAndOrg(id, portalOrg).ifPresent(approvalService::cancelAppApproval);
}
public List<AppRequestDTO> getProdApiRequestHistory(String clientid) {
return appRequestRepository.findAllByClientIdsContainsAndTypeIsIn(clientid,Arrays.asList(AppRequestType.PROD_NEW,AppRequestType.PROD_MODIFY, AppRequestType.PROD_DELETE)).stream().map(appRequestMapper::toVo).collect(Collectors.toList());
}
public AppRequestDTO getApiRequestProdByIdAndPortalOrg(String id, PortalOrg portalOrg) {
AppRequestDTO appRequest = appRequestRepository.findByIdAndOrgAndTypeIsIn(id, portalOrg,
Arrays.asList(AppRequestType.PROD_NEW,AppRequestType.PROD_MODIFY, AppRequestType.PROD_DELETE)).map(this::convertToAppRequestDTO).orElse(null);
if(appRequest != null) {
processApiGroups(appRequest);
processApiList(appRequest);
}
return appRequest;
}
public AppRequestDTO getApiRequestByIdAndPortalOrg(String id, PortalOrg org) {
AppRequestDTO appRequest = appRequestRepository.findByIdAndOrg(id, org).map(this::convertToAppRequestDTO).orElse(null);
if(appRequest != null) {
processApiGroups(appRequest);
if (appRequest != null) {
processApiList(appRequest);
}
return appRequest;
@@ -167,16 +156,6 @@ public class AppServiceFacade {
return dto;
}
private void processApiGroups(AppRequestDTO appRequest) {
if (!StringUtils.isEmpty(appRequest.getApiGroupList())) {
String[] apiGroupList = appRequest.getApiGroupList().split(",");
for (String apiGroupId : apiGroupList) {
appRequest.getApiServiceDTOList().add(apiServiceService.getApiGroupById(apiGroupId));
}
}
}
private void processApiList(AppRequestDTO appRequest) {
if (!StringUtils.isEmpty(appRequest.getApiList())) {
@@ -192,4 +171,140 @@ public class AppServiceFacade {
}
}
}
/**
* ApiKeyRegistrationDTO를 기반으로 AppRequest를 생성합니다.
*
* @param registration API Key 등록 정보
* @param portalOrg 포털 조직
* @return 생성된 AppRequest DTO
* @throws IOException 파일 처리 중 오류 발생 시
*/
public AppRequestDTO createAppRequestFromRegistration(ApiKeyRegistrationDTO registration, PortalOrg portalOrg) throws IOException {
// 1. 앱 아이콘 파일 저장
String appIconFileId = null;
if (registration.getAppIconData() != null && registration.getAppIconData().length > 0) {
FileInfo fileInfo = fileService.createSingleFileFromBytes(registration.getAppIconFileName(), registration.getAppIconContentType(), registration.getAppIconData());
appIconFileId = fileInfo.getFileId();
}
// 2. AppRequestDTO 생성 및 필드 매핑
AppRequestDTO appRequestDTO = new AppRequestDTO();
appRequestDTO.setOrg(portalOrgMapper.toVo(portalOrg));
appRequestDTO.setType(AppRequestType.NEW);
// 기본 정보 매핑
appRequestDTO.setClientName(registration.getAppName());
appRequestDTO.setAppDescription(registration.getAppDescription());
appRequestDTO.setCallbackUrl(registration.getCallbackUrl());
appRequestDTO.setAppIconFileId(appIconFileId);
// IP 화이트리스트 매핑 (List<String> → comma-separated String)
if (registration.getIpWhitelist() != null && !registration.getIpWhitelist().isEmpty()) {
appRequestDTO.setIpWhitelist(String.join(",", registration.getIpWhitelist()));
}
// 선택된 API 목록 매핑 (List<String> → comma-separated String)
if (registration.getSelectedApis() != null && !registration.getSelectedApis().isEmpty()) {
appRequestDTO.setApiList(String.join(",", registration.getSelectedApis()));
}
// 3. 기존 createAppRequest 메소드 호출하여 승인 프로세스 시작
return createAppRequest(appRequestDTO);
}
/**
* ApiKeyRegistrationDTO를 기반으로 수정 AppRequest를 생성합니다.
*
* @param registration API Key 수정 정보
* @param portalOrg 포털 조직
* @return 생성된 AppRequest DTO
* @throws IOException 파일 처리 중 오류 발생 시
*/
public AppRequestDTO createModifyRequestFromRegistration(ApiKeyRegistrationDTO registration, PortalOrg portalOrg) throws IOException {
// 1. 기존 Credential 정보 조회
ClientDTO existingClient = getApiKey(portalOrg.getId(), registration.getClientId());
if (existingClient == null) {
throw new NotFoundException("Client not found: " + registration.getClientId());
}
// 2. 앱 아이콘 파일 저장 (변경된 경우)
String appIconFileId = null;
// 새로운 아이콘 데이터가 있으면 저장
if (registration.getAppIconData() != null && registration.getAppIconData().length > 0) {
FileInfo fileInfo = fileService.createSingleFileFromBytes(registration.getAppIconFileName(), registration.getAppIconContentType(), registration.getAppIconData());
appIconFileId = fileInfo.getFileId();
}
// 새로운 아이콘이 없으면 기존 아이콘 ID 사용 (registration에서 가져오거나 기존 클라이언트에서 가져옴)
else if (registration.getAppIconFileId() != null) {
appIconFileId = registration.getAppIconFileId();
} else {
appIconFileId = existingClient.getAppIconFileId();
}
// 3. AppRequestDTO 생성 및 필드 매핑
AppRequestDTO appRequestDTO = new AppRequestDTO();
appRequestDTO.setOrg(portalOrgMapper.toVo(portalOrg));
appRequestDTO.setType(AppRequestType.MODIFY);
appRequestDTO.setClientId(registration.getClientId()); // 수정할 클라이언트 ID 설정
// 기본 정보 매핑
appRequestDTO.setClientName(registration.getAppName());
appRequestDTO.setAppDescription(registration.getAppDescription());
appRequestDTO.setCallbackUrl(registration.getCallbackUrl());
appRequestDTO.setAppIconFileId(appIconFileId);
// IP 화이트리스트 매핑 (List<String> → comma-separated String)
if (registration.getIpWhitelist() != null && !registration.getIpWhitelist().isEmpty()) {
appRequestDTO.setIpWhitelist(String.join(",", registration.getIpWhitelist()));
}
// 선택된 API 목록 매핑 (List<String> → comma-separated String)
if (registration.getSelectedApis() != null && !registration.getSelectedApis().isEmpty()) {
appRequestDTO.setApiList(String.join(",", registration.getSelectedApis()));
}
// 4. 기존 createAppRequest 메소드 호출하여 승인 프로세스 시작
// createAppRequest 내부에서 MODIFY 타입인 경우 기존 API 목록을 prevApiList에 저장함
return createAppRequest(appRequestDTO);
}
/**
* 사용자의 비밀번호를 검증합니다.
* Client Secret 조회 등 민감한 작업 수행 시 본인 확인을 위해 사용됩니다.
*
* @param user 인증된 사용자 정보
* @param password 검증할 비밀번호 (평문)
* @return 비밀번호 일치 여부
*/
public boolean verifyUserPassword(PortalAuthenticatedUser user, String password) {
if (user == null || password == null) {
return false;
}
try {
// PasswordEncoder를 사용하여 평문 비밀번호와 저장된 해시를 비교
return passwordEncoder.matches(password, user.getPassword());
} catch (Exception e) {
// 비밀번호 검증 중 오류 발생 시 false 반환 (보안상 구체적인 오류 노출 방지)
return false;
}
}
/**
* API Key(Credential)를 즉시 삭제합니다.
* 승인 프로세스 없이 바로 삭제 처리됩니다.
*
* @param orgId 조직 ID
* @param clientId 삭제할 클라이언트 ID
* @throws NotFoundException 클라이언트를 찾을 수 없는 경우
*/
public void deleteApp(String orgId, String clientId) {
Credential credential = credentialRepository.findByClientidAndOrgid(clientId, orgId)
.orElseThrow(() -> new NotFoundException("Client not found: " + clientId));
credentialRepository.delete(credential);
}
}
@@ -4,7 +4,6 @@ import com.eactive.apim.portal.apps.dashboard.service.DashboardService;
import com.eactive.apim.portal.common.pagerouter.PageHandler;
import com.eactive.apim.portal.common.util.SecurityUtil;
import com.eactive.apim.portal.portalorg.entity.PortalOrg;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
@@ -22,9 +21,7 @@ public class DashboardHandler implements PageHandler {
if (SecurityUtil.getPortalAuthenticatedUser() != null && SecurityUtil.getPortalAuthenticatedUser().getPortalOrg() != null) {
PortalOrg org = SecurityUtil.getPortalAuthenticatedUser().getPortalOrg();
model.addAttribute("pendingKeyCount", dashboardService.getPendingApiKeyRequestCount(org));
model.addAttribute("testKeyCount", dashboardService.getTestApiKeyCount(org));
model.addAttribute("pendingProdCount", dashboardService.getProdApiKeyRequestCount(org));
model.addAttribute("prodKeyCount", dashboardService.getProdApiKeyCount(org));
model.addAttribute("testKeyCount", dashboardService.getApiKeyCount(org));
model.addAttribute("totalApi", dashboardService.getTotalApi(org));
model.addAttribute("dailyTotal", dashboardService.getDailyTotalApiUsage(org));
@@ -5,24 +5,18 @@ import com.eactive.apim.portal.app.entity.Credential;
import com.eactive.apim.portal.app.repository.CredentialRepository;
import com.eactive.apim.portal.apprequest.entity.AppRequestType;
import com.eactive.apim.portal.apprequest.repository.AppRequestRepository;
import com.eactive.apim.portal.approval.entity.ApprovalStatus;
import com.eactive.apim.portal.approval.statemachine.ProcessingState;
import com.eactive.apim.portal.approval.statemachine.RequestedState;
import com.eactive.apim.portal.apps.approval.service.ApprovalService;
import com.eactive.apim.portal.apps.dashboard.ApiDailyStatistics;
import com.eactive.apim.portal.apps.dashboard.repository.PortalStatisticsRepository;
import com.eactive.apim.portal.apps.dashboard.repository.PortalStatisticsRepository.TopApiUsage;
import com.eactive.apim.portal.common.util.DateUtil;
import com.eactive.apim.portal.portalorg.entity.PortalOrg;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -39,15 +33,12 @@ import org.springframework.transaction.annotation.Transactional;
@Service
public class DashboardService {
private final static String PROD_SERVER = "PROD";
private final static String STG_SERVER = "STG";
private final AppRequestRepository appRequestRepository;
private final CredentialRepository credentialRepository;
private final PortalStatisticsRepository portalStatisticsRepository;
public int getTestApiKeyCount(PortalOrg org) {
return credentialRepository.countAllByOrgidAndServer(org.getId(), STG_SERVER);
public int getApiKeyCount(PortalOrg org) {
return credentialRepository.countAllByOrgid(org.getId());
}
public int getPendingApiKeyRequestCount(PortalOrg org) {
@@ -55,18 +46,9 @@ public class DashboardService {
Arrays.asList(new ProcessingState(), new RequestedState()));
}
public int getProdApiKeyRequestCount(PortalOrg org) {
return appRequestRepository.countAllByOrgAndTypeIsInAndApproval_ApprovalStatusIn(org, Arrays.asList(AppRequestType.PROD_NEW, AppRequestType.PROD_MODIFY, AppRequestType.PROD_DELETE),
Arrays.asList(new ProcessingState(), new RequestedState()));
}
public int getProdApiKeyCount(PortalOrg org) {
return credentialRepository.findAllByOrgidAndServer(org.getId(), PROD_SERVER).size();
}
public int getTotalApi(PortalOrg org) {
List<Credential> credentials = credentialRepository.findAllByOrgidAndServer(org.getId(), PROD_SERVER);
List<Credential> credentials = credentialRepository.findAllByOrgid(org.getId());
return (int) credentials.stream()
.flatMap(credential -> credential.getApiList().stream())
.map(ApiSpecInfo::getApiId)