Merge branch 'jenkins_with_weblogic' into master

This commit is contained in:
curry772
2026-03-11 10:18:55 +09:00
27 changed files with 634 additions and 241 deletions
+10 -1
View File
@@ -14,6 +14,15 @@ idea {
def nexusUrl = "https://nexus.eactive.synology.me:8090"
allprojects {
repositories {
maven {
url "${nexusUrl}/repository/maven-public/"
allowInsecureProtocol = true
}
}
}
def springVersion = "5.3.27"
//def quartzVersion = "2.2.1"
def queryDslVersion = "5.0.0"
@@ -72,7 +81,7 @@ tasks.withType(JavaCompile) {
}
dependencies {
api project(':kjb-safedb')
// api project(':kjb-safedb')
api "com.querydsl:querydsl-jpa:${queryDslVersion}"
api "com.querydsl:querydsl-apt:${queryDslVersion}"
+19
View File
@@ -0,0 +1,19 @@
-- 1. Add fields to ptl_app_request table
ALTER TABLE ptl_app_request ADD app_description VARCHAR2(500);
COMMENT ON COLUMN ptl_app_request.app_description IS '앱 설명';
ALTER TABLE ptl_app_request ADD callback_url VARCHAR2(255);
COMMENT ON COLUMN ptl_app_request.callback_url IS '콜백 URL';
ALTER TABLE ptl_app_request ADD ip_whitelist VARCHAR2(1000);
COMMENT ON COLUMN ptl_app_request.ip_whitelist IS 'IP 화이트리스트 (comma-separated)';
ALTER TABLE ptl_app_request ADD app_icon_file_id VARCHAR2(38);
COMMENT ON COLUMN ptl_app_request.app_icon_file_id IS '앱 아이콘 파일 ID (ptl_file 참조)';
-- 2. Add fields to ptl_credential table
ALTER TABLE ptl_credential ADD app_description VARCHAR2(500);
COMMENT ON COLUMN ptl_credential.app_description IS '앱 설명';
ALTER TABLE ptl_credential ADD app_icon_file_id VARCHAR2(38);
COMMENT ON COLUMN ptl_credential.app_icon_file_id IS '앱 아이콘 파일 ID (ptl_file 참조)';
@@ -61,4 +61,8 @@ public class Agreements extends Auditable {
@Column(name = "SCHEDULED_ON")
@Convert(converter = LocalDateTimeToStringConverter14.class)
private LocalDateTime scheduledOn;
@Column(name = "ATTACHED_FILE_ID", length = 36)
@Comment("첨부파일 ID (PTL_FILE.FILE_ID)")
private String attachedFileId;
}
@@ -111,6 +111,14 @@ public class Credential extends AbstractEntity<String> implements Serializable {
@Comment("기관이름")
private String orgname;
@Column(name = "app_description", length = 500)
@Comment("앱 설명")
private String appDescription;
@Column(name = "app_icon_file_id", length = 38)
@Comment("앱 아이콘 파일 ID")
private String appIconFileId;
@Comment("일별 토큰 제한 수 (0:무제한)")
private int dailytokenlimit;
@@ -1,121 +0,0 @@
package com.eactive.apim.portal.app.entity;
import com.eactive.apim.portal.apispec.entity.ApiSpecInfo;
import com.eactive.eai.data.converter.LocalDateTimeToStringConverter14;
import com.eactive.eai.data.entity.AbstractEntity;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import lombok.Data;
import lombok.NonNull;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
@Entity
@Data
@Table(name = "ptl_prod_client")
@org.hibernate.annotations.Table(appliesTo = "ptl_prod_client", comment = "사용자 정보")
public class ProdClient extends AbstractEntity<String> implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@Column(unique = true, nullable = false, length = 256)
@Comment("client id")
private String clientid;
@Column(length = 11)
@Comment("Access Token 유효 기간(초)")
private String accesstokenvalidityseconds;
@Column(length = 256)
@Comment("허용된 IP")
private String allowedips;
@Column(length = 256)
@Comment("권한")
private String authorities;
@Column(length = 256)
@Comment("자동 승인 스코프")
private String autoapprove;
@Column(length = 40)
@Comment("client명")
private String clientname;
@Column(length = 512)
@Comment("Client 비밀번호")
private String clientsecret;
@Column(length = 256)
@Comment("권한타입")
private String granttypes;
@Column(length = 8)
@Comment("수정인")
@LastModifiedBy
private String modifiedby;
@Column(length = 14)
@Comment("수정일")
@Convert(converter = LocalDateTimeToStringConverter14.class)
@LastModifiedDate
private LocalDateTime modifiedon;
@Column(length = 256)
@Comment("Redirect URI")
private String redirecturi;
@Column(length = 11)
@Comment("Refresh Token 유효 기간(초)")
private String refreshtokenvalidityseconds;
@Column(length = 4000)
@Comment("리소스 id")
private String resourceids;
@Column(length = 1024)
@Comment("scope")
private String scope;
@Column(length = 4000)
@Comment("암호key")
private String securitykey;
@Column(length = 38)
@Comment("기관ID")
private String orgid;
@Comment("APP 상태")
private String appstatus; //0: 차단, 1: 정상
@Column(length = 255)
@Comment("기관이름")
private String orgname;
@Comment("일별 토큰 제한 수 (0:무제한)")
private int dailytokenlimit;
@Override
public @NonNull String getId() {
return clientid;
}
@ManyToMany
@LazyCollection(LazyCollectionOption.FALSE)
@JoinTable(name = "ptl_prod_client_api", joinColumns = @JoinColumn(name = "client_id"), inverseJoinColumns = @JoinColumn(name = "eaisvcname"))
private List<ApiSpecInfo> apiList = new ArrayList<>();
}
@@ -7,15 +7,39 @@ import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
@EMSDataSource
public interface CredentialRepository extends BaseRepository<Credential, String> {
Page<Credential> findAllByOrgidAndServer(Pageable pageable, String id, String server);
Page<Credential> findAllByOrgid(Pageable pageable, String id);
int countAllByOrgidAndServer(String orgid, String server);
int countAllByOrgid(String orgid);
List<Credential> findAllByOrgidAndServer(String orgid, String server);
List<Credential> findAllByOrgid(String orgid);
Optional<Credential> findByClientidAndOrgidAndServer(String clientid, String orgid, String server);
Optional<Credential> findByClientidAndOrgid(String clientid, String orgid);
/**
* 특정 기관 ID 목록에 속하고 이용 가능 상태(appstatus='1')인 앱의 수를 조회
*/
@Query("SELECT COUNT(c) FROM Credential c WHERE c.appstatus = '1' AND c.orgid IN :orgIds")
long countActiveAppsByOrgIds(@Param("orgIds") List<String> orgIds);
/**
* 특정 기관 ID 목록에 속하고 이용 가능 상태(appstatus='1')인 앱 목록 조회
*/
@Query("SELECT c FROM Credential c WHERE c.appstatus = '1' AND c.orgid IN :orgIds")
List<Credential> findActiveAppsByOrgIds(@Param("orgIds") List<String> orgIds);
/**
* 이용 가능 상태인 모든 앱의 수를 조회
*/
long countByAppstatus(String appstatus);
/**
* 이용 가능 상태인 모든 앱 목록 조회
*/
List<Credential> findByAppstatus(String appstatus);
}
@@ -1,18 +0,0 @@
package com.eactive.apim.portal.app.repository;
import com.eactive.apim.portal.app.entity.ProdClient;
import com.eactive.apim.portal.approval.entity.Approval;
import com.eactive.apim.portal.approval.entity.ApprovalType;
import com.eactive.eai.data.jpa.BaseRepository;
import com.eactive.eai.rms.data.EMSDataSource;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
@EMSDataSource
public interface ProdClientRepository extends BaseRepository<ProdClient, String> {
Page<ProdClient> findAllByOrgid(Pageable pageable, String id);
Optional<ProdClient> findByClientidAndOrgid(String clientid, String orgid);
}
@@ -12,6 +12,8 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction;
@Data
@EqualsAndHashCode
@@ -27,7 +29,8 @@ public class AppRequest implements com.eactive.eai.data.Data {
private String id;
@ManyToOne
@JoinColumn(name = "org_id")
@JoinColumn(name = "org_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
@NotFound(action = NotFoundAction.IGNORE)
private PortalOrg org;
@Enumerated(EnumType.STRING)
@@ -37,7 +40,8 @@ public class AppRequest implements com.eactive.eai.data.Data {
private AppRequestType type;
@OneToOne
@JoinColumn(name = "approval_id")
@JoinColumn(name = "approval_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
@NotFound(action = NotFoundAction.IGNORE)
private Approval approval;
@Column(name = "api_list")
@@ -64,6 +68,18 @@ public class AppRequest implements com.eactive.eai.data.Data {
@Column(name = "reason")
private String reason;
@Column(name = "app_description", length = 500)
private String appDescription;
@Column(name = "callback_url", length = 255)
private String callbackUrl;
@Column(name = "ip_whitelist", length = 1000)
private String ipWhitelist;
@Column(name = "app_icon_file_id", length = 38)
private String appIconFileId;
@Column(length = 17, name = "created_date")
@Convert(converter = LocalDateTimeToStringConverter17.class)
@CreationTimestamp
@@ -3,10 +3,7 @@ package com.eactive.apim.portal.apprequest.entity;
public enum AppRequestType {
NEW("API키 신규"),
MODIFY("API 변경"),
DELETE("API키 삭제"),
PROD_NEW("운영 API키 신규"),
PROD_MODIFY("운영 API 변경"),
PROD_DELETE("운영 API키 삭제");
DELETE("API키 삭제");
private final String description;
@@ -8,6 +8,7 @@ import com.eactive.apim.portal.approval.statemachine.ApprovalState;
import com.eactive.apim.portal.portalorg.entity.PortalOrg;
import com.eactive.eai.data.jpa.BaseRepository;
import com.eactive.eai.rms.data.EMSDataSource;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Page;
@@ -20,6 +21,8 @@ public interface AppRequestRepository extends BaseRepository<AppRequest, String>
Optional<AppRequest> findAppRequestByApproval(Approval approval);
List<AppRequest> findAllByOrgAndTypeIsInAndApproval_ApprovalStatusIn(PortalOrg org, List<AppRequestType> types, List<ApprovalState> approvalStates);
int countAllByOrgAndTypeIsInAndApproval_ApprovalStatusIn(PortalOrg org, List<AppRequestType> types, List<ApprovalState> approvalStates);
Optional<AppRequest> findByIdAndOrg(String id, PortalOrg org);
@@ -29,4 +32,12 @@ public interface AppRequestRepository extends BaseRepository<AppRequest, String>
List<AppRequest> findAllByClientIdsContainsAndTypeIsIn(String clientId, List<AppRequestType> types);
List<AppRequest> findAllByOrgAndStatus(PortalOrg org, ApprovalStatus status);
/**
* org.id 목록으로 AppRequest 조회 (ObpGwMetric 집계용)
*
* @param orgIds 기관 ID 목록
* @return AppRequest 목록
*/
List<AppRequest> findByOrg_IdIn(Collection<String> orgIds);
}
@@ -12,6 +12,7 @@ import org.hibernate.annotations.*;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.ForeignKey;
import javax.persistence.Table;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
@@ -50,9 +51,9 @@ public class Approval implements Serializable, com.eactive.eai.data.Data {
@Column(name = "target_id", nullable = false)
private String targetId; //승인하는 대상 객체의 ID
@NotNull
@ManyToOne
@JoinColumn(name = "REQUESTER_ID", nullable = false)
@JoinColumn(name = "REQUESTER_ID", nullable = false, foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
@NotFound(action = NotFoundAction.IGNORE)
private PortalUser requester;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "approval")
@@ -29,6 +29,30 @@ public class FileService {
private final BinaryStorageService binaryStorageService;
private final FileTypeDetector fileTypeDetector;
private static final ThreadLocal<Boolean> internalUserContext = new ThreadLocal<>();
/**
* 현재 스레드에 내부 사용자 컨텍스트 설정
*/
public static void setInternalUserContext(boolean isInternal) {
internalUserContext.set(isInternal);
}
/**
* 내부 사용자 컨텍스트 조회
*/
public static boolean isInternalUserContext() {
Boolean isInternal = internalUserContext.get();
return isInternal != null && isInternal;
}
/**
* 내부 사용자 컨텍스트 정리
*/
public static void clearInternalUserContext() {
internalUserContext.remove();
}
/**
* 특정 파일 ID를 기반으로 파일 정보를 검색합니다.
*
@@ -71,8 +95,15 @@ public class FileService {
String type = fileTypeDetector.detectFileType(file.getBytes());
log.debug("upload file type:{}", type);
if(type.equalsIgnoreCase("unknown")) {
throw new InvalidFileException("허용된 파일 형식이 아닙니다. <br>문서파일과 이미지 파일만 등록 가능합니다. <br>(pdf, doc, docx, xls, xlsx, ppt, pptx, hwp, gif, jpg, png)");
boolean isValidType = !type.equalsIgnoreCase("unknown");
if (!isValidType) {
if (isInternalUserContext()) {
log.warn("Internal user bypassed file validation - File: {}, Type: {}",
file.getOriginalFilename(), type);
} else {
throw new InvalidFileException("허용된 파일 형식이 아닙니다. <br>문서파일과 이미지 파일만 등록 가능합니다. <br>(pdf, doc, docx, xls, xlsx, ppt, pptx, hwp, gif, jpg, png)");
}
}
if (StringUtils.isNotBlank(file.getOriginalFilename()) && file.getSize() > 0) {
@@ -82,7 +113,13 @@ public class FileService {
fileDetail.setFileSn(fileSn);
fileDetail.setFileSize((int) file.getSize());
fileDetail.setOriginalFileName(FilenameUtils.getBaseName(file.getOriginalFilename()));
fileDetail.setFileExtension(type);
if (isValidType) {
fileDetail.setFileExtension(type);
} else {
fileDetail.setFileExtension(FilenameUtils.getExtension(file.getOriginalFilename()));
}
String contentReference = binaryStorageService.store(file.getBytes());
fileDetail.setFileContents(contentReference);
fileInfo.getFileDetails().add(fileDetail);
@@ -232,12 +269,17 @@ public class FileService {
String type = fileTypeDetector.detectFileType(file.getBytes());
log.debug("upload file type:{}", type);
if (isCheckMime) {
if(type.equalsIgnoreCase("unknown")) {
throw new InvalidFileException("허용된 파일 형식이 아닙니다. <br>문서파일과 이미지 파일만 등록 가능합니다. <br>(pdf, doc, docx, xls, xlsx, ppt, pptx, hwp, gif, jpg, png)");
}
} else {
log.debug("파일 체크 옵션 해제 - FileType : {}", type);
boolean isValidType = !type.equalsIgnoreCase("unknown");
if (isCheckMime && !isValidType) {
if (isInternalUserContext()) {
log.warn("Internal user bypassed file validation - File: {}, Type: {}",
fileName, type);
} else {
throw new InvalidFileException("허용된 파일 형식이 아닙니다. <br>문서파일과 이미지 파일만 등록 가능합니다. <br>(pdf, doc, docx, xls, xlsx, ppt, pptx, hwp, gif, jpg, png)");
}
} else if (!isCheckMime) {
log.debug("파일 체크 옵션 해제 - FileType : {}", type);
}
FileDetail fileDetail = new FileDetail();
@@ -252,6 +294,100 @@ public class FileService {
return fileDetail;
}
/**
* byte[] 데이터로 단일 파일을 생성합니다.
*
* @param fileName 파일 이름 (확장자 포함)
* @param contentType 파일 MIME 타입
* @param fileData 파일 바이트 데이터
* @return 파일 정보
* @throws IOException 파일 처리 중 오류 발생 시
*/
public FileInfo createSingleFileFromBytes(String fileName, String contentType, byte[] fileData) throws IOException {
// 파일 타입 검증
String type = fileTypeDetector.detectFileType(fileData);
log.debug("upload file type from bytes: {}", type);
if (type.equalsIgnoreCase("unknown")) {
throw new InvalidFileException("허용된 파일 형식이 아닙니다. <br>문서파일과 이미지 파일만 등록 가능합니다. <br>(pdf, doc, docx, xls, xlsx, ppt, pptx, hwp, gif, jpg, png)");
}
// FileInfo 생성
FileInfo fileInfo = new FileInfo();
String fileId = UUID.randomUUID().toString();
fileInfo.setFileId(fileId);
fileInfo.setCreatedDate(LocalDateTime.now());
fileInfo.setUseYn("Y");
// FileDetail 생성
FileDetail fileDetail = new FileDetail();
fileDetail.setFileId(fileId);
fileDetail.setFileSn(1);
fileDetail.setFileSize(fileData.length);
fileDetail.setOriginalFileName(FilenameUtils.getBaseName(fileName));
fileDetail.setFileExtension(FilenameUtils.getExtension(fileName));
// 파일 내용 저장
String contentReference = binaryStorageService.store(fileData);
fileDetail.setFileContents(contentReference);
// FileInfo에 FileDetail 추가
fileInfo.getFileDetails().add(fileDetail);
// 저장 및 반환
return fileRepository.save(fileInfo);
}
/**
* byte[] 데이터로 기존 파일을 업데이트하거나 새로 생성합니다.
*
* @param existingFileId 기존 파일 ID (null이면 새로 생성)
* @param fileName 파일 이름 (확장자 포함)
* @param contentType 파일 MIME 타입
* @param fileData 파일 바이트 데이터
* @return 파일 정보
* @throws IOException 파일 처리 중 오류 발생 시
*/
public FileInfo createOrUpdateSingleFileFromBytes(String existingFileId, String fileName, String contentType, byte[] fileData) throws IOException {
// 신규 등록 또는 기존 파일 정보 조회
FileInfo fileInfo = Optional.ofNullable(existingFileId)
.filter(StringUtils::isNotBlank)
.map(this::findById)
.orElseGet(() -> {
FileInfo newFile = new FileInfo();
newFile.setFileId(UUID.randomUUID().toString());
newFile.setCreatedDate(LocalDateTime.now());
newFile.setUseYn("Y");
return newFile;
});
// 파일 타입 검증
String type = fileTypeDetector.detectFileType(fileData);
log.debug("upload file type from bytes: {}", type);
if (type.equalsIgnoreCase("unknown")) {
throw new InvalidFileException("허용된 파일 형식이 아닙니다. <br>문서파일과 이미지 파일만 등록 가능합니다. <br>(pdf, doc, docx, xls, xlsx, ppt, pptx, hwp, gif, jpg, png)");
}
// FileDetail 생성
FileDetail fileDetail = new FileDetail();
fileDetail.setFileId(fileInfo.getFileId());
fileDetail.setFileSn(1);
fileDetail.setFileSize(fileData.length);
fileDetail.setOriginalFileName(FilenameUtils.getBaseName(fileName));
fileDetail.setFileExtension(FilenameUtils.getExtension(fileName));
// 파일 내용 저장
String contentReference = binaryStorageService.store(fileData);
fileDetail.setFileContents(contentReference);
// 기존 파일 상세 정보 제거 후 새로운 정보 추가
fileInfo.getFileDetails().clear();
fileInfo.getFileDetails().add(fileDetail);
return fileRepository.save(fileInfo);
}
}
@@ -21,10 +21,11 @@ public class UserInvitationEvent implements MessageEventHandler {
@Override
public MessageSendEvent createEvent(Object source, MessageRecipient recipient, Map<String, Object> params) {
Map<String, String> requestParams = new HashMap<>();
requestParams.put("company", params.get("company").toString());
requestParams.put("manager", params.get("manager").toString());
requestParams.put("corpName", params.get("corpName").toString());
requestParams.put("managerName", params.get("managerName").toString());
requestParams.put("url", params.get("url").toString());
requestParams.put("userName",params.get("company").toString() + " 이용자");
requestParams.put("loginId",params.get("corpName").toString() + " 이용자");
requestParams.put("authNumber", params.get("authNumber").toString());
return new MessageSendEvent(source, KEY, recipient, requestParams);
}
@@ -18,4 +18,8 @@ public interface UserInvitationRepository extends JpaRepository<UserInvitation,
List<UserInvitation> findByStatus(InvitationStatus status);
Optional<UserInvitation> findFirstByInvitationEmailAndStatus(String email, InvitationStatus status);
Optional<UserInvitation> findFirstByInvitationEmailAndOrgIdAndStatus(String email, String orgId, InvitationStatus status);
Optional<UserInvitation> findFirstByInvitationEmailAndOrgId(String email, String orgId);
}
@@ -0,0 +1,151 @@
package com.eactive.apim.portal.obp.entity;
import com.eactive.eai.data.converter.LocalDateTimeToStringConverter17;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;
import javax.persistence.*;
import java.time.LocalDateTime;
@Data
@EqualsAndHashCode(callSuper = false)
@Entity
@Table(name = "ptl_obp_gw_metric")
public class ObpGwMetric {
@Id
@GeneratedValue(generator = "uuid-v1")
@GenericGenerator(
name = "uuid-v1",
strategy = "org.hibernate.id.UUIDGenerator",
parameters = {
@Parameter(
name = "uuid_gen_strategy_class",
value = "org.hibernate.id.uuid.CustomVersionOneStrategy"
)
}
)
@Column(length = 36)
@Comment("id")
private String id;
// json : appKey
@Column(length = 256, name = "client_id")
@Comment("클라이언트 ID")
private String clientId;
// json : clusterHostname
@Column(length = 50, name = "hostname")
@Comment("호스트 이름")
private String hostname;
// json : timeslice
@Column(length = 17, name = "timeslice")
@Convert(converter = LocalDateTimeToStringConverter17.class)
@Comment("타임슬라이스 (yyyyMMddHHmmssSSS)")
private LocalDateTime timeslice;
// json : apiName
@Column(name = "api_name", length = 255)
@Comment("API 이름")
private String apiName;
// json : apiRoutingUri
@Column(length = 255, name = "uri")
@Comment("URI")
private String uri;
// HTTP 메서드 (GET, POST, PUT, DELETE 등)
@Column(length = 10, name = "method")
@Comment("HTTP 메서드 (GET, POST, PUT, DELETE 등)")
private String method;
// json : attemptedCount
@Column(name = "attempted_count")
@Comment("시도 횟수")
private Long attemptedCount;
// json : completedCount
@Column(name = "completed_count")
@Comment("완료 횟수")
private Long completedCount;
// registeredOn
@Column(name = "created_at", nullable = false)
@Comment("생성 날짜와 시간. 레코드가 생성된 시간")
private LocalDateTime createdAt;
// registeredBy
@Column(name = "created_by", nullable = false, length = 50)
@Comment("생성한 사용자")
private String createdBy = "GwMetricHourJob";
// modifiedOn
@Column(name = "updated_at")
@Comment("최종 업데이트 날짜와 시간. 레코드가 마지막으로 업데이트된 시간")
private LocalDateTime updatedAt;
// modifiedBy
@Column(name = "updated_by", length = 50)
@Comment("최종 업데이트한 사용자")
private String updatedBy;
// eapim ID
// json : apiIdByEapim
@Column(length = 40, name = "api_id")
@Comment("API ID(EAPIM) - EAISVCNAME 최대 40자")
private String apiId;
// json : appIdByEapim
@Column(length = 36, name = "app_id")
private String appId;
// json : orgIdByEapim
@Column(length = 36, name = "org_id")
private String orgId;
// AS-IS 호환용 필드
// json : appId
@Column(length = 50, name = "app_id_by_ca_portal")
@Comment("애플리케이션 ID(CA 포탈)")
private String appIdByCaPortal;
// json : portalOrgId
@Column(length = 50, name = "org_id_by_ca_portal")
@Comment("기관 ID(CA 포탈)")
private String orgIdByCaPortal;
// json : portalApiId
@Column(length = 50, name = "api_id_by_ca_portal")
@Comment("API ID(CA 포탈)")
private String apiIdByCaPortal;
// json : partnerCode
@Column(length = 20, name = "partner_code")
@Comment("파트너 코드(OBP)")
private String partnerCode;
// json : apiId
@Column(name = "app_id_by_ca", length = 36)
@Comment("API ID(CA)")
private String apiIdByCa;
@Column(name = "update_count")
@Comment("업데이트(보정) 횟수")
private int updateCount;
// 더미
// requestUri
@Transient
private String requestUri = "-1";
}
@@ -0,0 +1,82 @@
package com.eactive.apim.portal.obp.repository;
import com.eactive.apim.portal.obp.entity.ObpGwMetric;
import com.eactive.eai.data.jpa.BaseRepository;
import com.eactive.eai.rms.data.EMSDataSource;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
/**
* ObpGwMetric Repository
* API Gateway 시간별 거래량 지표 조회/저장
*/
@EMSDataSource
public interface ObpGwMetricRepository extends BaseRepository<ObpGwMetric, String> {
/**
* UPSERT 판단을 위한 기존 데이터 조회 (NULL 안전)
* Unique Key: (timeslice, api_id, client_id, hostname, org_id)
*
* <p>Spring Data JPA의 메서드 이름 기반 쿼리는 NULL 비교를 제대로 처리하지 못함.
* SQL에서 {@code column = NULL}은 항상 false이므로, NULL 값이 있는 레코드를 찾지 못함.
* 이 쿼리는 {@code (column = :param OR (column IS NULL AND :param IS NULL))} 패턴으로 NULL을 안전하게 처리함.</p>
*/
@Query("SELECT m FROM ObpGwMetric m WHERE m.timeslice = :timeslice " +
"AND (m.apiId = :apiId OR (m.apiId IS NULL AND :apiId IS NULL)) " +
"AND (m.clientId = :clientId OR (m.clientId IS NULL AND :clientId IS NULL)) " +
"AND (m.hostname = :hostname OR (m.hostname IS NULL AND :hostname IS NULL)) " +
"AND (m.orgId = :orgId OR (m.orgId IS NULL AND :orgId IS NULL))")
Optional<ObpGwMetric> findByUniqueKeyNullSafe(
@Param("timeslice") LocalDateTime timeslice,
@Param("apiId") String apiId,
@Param("clientId") String clientId,
@Param("hostname") String hostname,
@Param("orgId") String orgId
);
/**
* @deprecated NULL 비교 문제로 {@link #findByUniqueKeyNullSafe} 사용 권장
*/
@Deprecated
Optional<ObpGwMetric> findByTimesliceAndApiIdAndClientIdAndHostnameAndOrgId(
LocalDateTime timeslice,
String apiId,
String clientId,
String hostname,
String orgId
);
/**
* 특정 시간대의 모든 지표 조회 (REST API 용)
*/
List<ObpGwMetric> findByTimeslice(LocalDateTime timeslice);
/**
* 특정 시간대 범위의 지표 조회
*/
List<ObpGwMetric> findByTimesliceBetween(LocalDateTime startTime, LocalDateTime endTime);
/**
* API ID로 지표 조회
*/
List<ObpGwMetric> findByApiIdAndTimeslice(String apiId, LocalDateTime timeslice);
/**
* 특정 시간대 이후의 모든 지표 조회 (REST API 용)
*/
List<ObpGwMetric> findByTimesliceGreaterThanEqual(LocalDateTime timeslice);
/**
* 특정 시간대 이후의 지표 조회 (페이징 지원)
*
* @param timeslice 조회 시작 시간대
* @param pageable 페이징 및 정렬 정보
* @return 조회된 지표 목록
*/
List<ObpGwMetric> findByTimesliceGreaterThanEqual(LocalDateTime timeslice, Pageable pageable);
}
@@ -26,7 +26,6 @@ public class PartnershipApplication extends Auditable {
@Comment("비즈니스제목")
private String bizSubject;
@Lob
@Column(name = "biz_detail", nullable = false)
@Comment("비즈니스내용")
private String bizDetail;
@@ -12,6 +12,10 @@ import java.sql.ResultSet;
import java.sql.SQLException;
/**
* 25.11.26 - 광주은행에서는 기관코드를 OBP 파트너코드와 매칭 시킴. 즉, 자동 생성 사용안함
*/
@Deprecated
@Slf4j
public class OrgCodeGenerator implements ValueGenerator<String> {
private static final String TABLE_NAME = "PTL_ID";
@@ -3,18 +3,12 @@ package com.eactive.apim.portal.portalorg.entity;
import com.eactive.apim.portal.common.entity.Auditable;
import com.eactive.apim.portal.portalorg.entity.PortalOrgEnums.ApprovalStatus;
import com.eactive.apim.portal.portalorg.entity.PortalOrgEnums.OrgStatus;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.GenerationTime;
import org.hibernate.annotations.GeneratorType;
import javax.persistence.*;
import java.io.Serializable;
@Data
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = true)
@@ -40,8 +34,8 @@ public class PortalOrg extends Auditable implements Serializable, com.eactive.ea
private String orgName;
@Column(name = "org_code")
@Comment("제휴기관코드")
@GeneratorType(type = OrgCodeGenerator.class, when = GenerationTime.INSERT)
@Comment("제휴기관코드 - OBP 파트너코드")
// @GeneratorType(type = OrgCodeGenerator.class, when = GenerationTime.INSERT)
private String orgCode;
@Column(name = "org_desc", length = 255)
@@ -2,21 +2,27 @@ package com.eactive.apim.portal.portalproperty.service;
import com.eactive.apim.portal.portalproperty.entity.PortalProperty;
import com.eactive.apim.portal.portalproperty.entity.PortalPropertyGroup;
import com.eactive.apim.portal.portalproperty.entity.PortalPropertyId;
import com.eactive.apim.portal.portalproperty.repository.PortalPropertyGroupRepository;
import com.eactive.apim.portal.portalproperty.repository.PortalPropertyRepository;
import com.eactive.eai.data.jpa.AbstractDataService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional
public class PortalPropertyService extends AbstractDataService<PortalPropertyGroup, String, PortalPropertyGroupRepository> {
private final PortalPropertyGroupRepository portalPropertyGroupRepository;
private final PortalPropertyRepository portalPropertyRepository;
@Transactional(readOnly = true)
public Map<String, String> getPortalPropertiesAsMap(String propertyGroupName) {
@@ -31,5 +37,54 @@ public class PortalPropertyService extends AbstractDataService<PortalPropertyGro
));
}
/**
* 프로퍼티 값을 조회하고, 없으면 기본값을 DB에 저장 후 반환
*
* @param groupName 프로퍼티 그룹명
* @param propertyName 프로퍼티명
* @param defaultValue 기본값
* @param description 프로퍼티 설명 (신규 생성 시 사용)
* @return 프로퍼티 값
*/
public String getOrCreateProperty(String groupName, String propertyName, String defaultValue, String description) {
PortalPropertyId propertyId = new PortalPropertyId(groupName, propertyName);
Optional<PortalProperty> existingProperty = portalPropertyRepository.findById(propertyId);
if (existingProperty.isPresent()) {
return existingProperty.get().getPropertyValue();
}
// 프로퍼티가 없으면 기본값으로 생성
return createDefaultProperty(groupName, propertyName, defaultValue, description);
}
/**
* 기본 프로퍼티를 DB에 생성
*/
private String createDefaultProperty(String groupName, String propertyName, String defaultValue, String description) {
try {
// 그룹이 존재하는지 확인
PortalPropertyGroup group = portalPropertyGroupRepository.findById(groupName)
.orElse(null);
if (group == null) {
log.warn("프로퍼티 그룹 '{}'이 존재하지 않아 기본값 저장을 건너뜁니다.", groupName);
return defaultValue;
}
// 새 프로퍼티 생성
PortalProperty newProperty = new PortalProperty();
newProperty.setId(new PortalPropertyId(groupName, propertyName));
newProperty.setPropertyValue(defaultValue);
newProperty.setPropertyDesc(description);
portalPropertyRepository.save(newProperty);
log.info("기본 프로퍼티 생성 완료 - 그룹: {}, 키: {}, 값: {}", groupName, propertyName, defaultValue);
return defaultValue;
} catch (Exception e) {
log.error("기본 프로퍼티 생성 실패 - 그룹: {}, 키: {}", groupName, propertyName, e);
return defaultValue;
}
}
}
@@ -12,6 +12,8 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction;
import javax.persistence.*;
import java.time.LocalDateTime;
@@ -60,6 +62,7 @@ public class PortalUser extends Auditable implements Serializable, com.eactive.e
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "org_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
@NotFound(action = NotFoundAction.IGNORE)
private PortalOrg portalOrg;
@Enumerated(EnumType.STRING)
@@ -3,6 +3,7 @@ package com.eactive.apim.portal.portaluser.entity;
public class PortalUserEnums {
public enum UserStatus {
PENDING("대기"),
READY("준비"),
ACTIVE("정상"),
PW_FAIL("비밀번호 5회 실패"),
@@ -10,17 +10,20 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
import java.util.Optional;
@EMSDataSource
public interface PortalUserRepository extends BaseRepository<PortalUser, String> {
Optional<PortalUser> findByLoginId(String userName);
Optional<PortalUser> findByLoginId(String String);
Optional<PortalUser> findPortalUserByEmailAddr(String loginId);
PortalUser findByUserNameAndMobileNumber(String userName, String mobileNumber);
List<PortalUser> findAllByUserNameAndMobileNumber(String userName, String mobileNumber);
Optional<PortalUser> findByLoginIdAndUserNameAndMobileNumber(String loginId, String userName, String mobileNumber);
boolean existsByLoginId(String loginId);
@@ -8,6 +8,8 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction;
import javax.persistence.*;
import java.time.LocalDateTime;
@@ -55,7 +57,8 @@ public class Inquiry extends Auditable {
* 문의자
*/
@ManyToOne
@JoinColumn(name = "INQUIRER_ID")
@JoinColumn(name = "INQUIRER_ID", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
@NotFound(action = NotFoundAction.IGNORE)
private PortalUser inquirer;
/**
@@ -9,6 +9,8 @@ import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction;
import javax.persistence.*;
import java.io.Serializable;
@@ -59,6 +61,7 @@ public class MessageRequest implements Serializable {
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "user_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
@NotFound(action = NotFoundAction.IGNORE)
@Comment("발송 대상 사용자")
private PortalUser user;
@@ -18,6 +18,7 @@ import org.springframework.data.annotation.LastModifiedDate;
import com.eactive.eai.data.converter.LocalDateTimeToStringConverter14;
import com.eactive.eai.data.converter.StringTrimConverter;
import com.eactive.eai.data.entity.AbstractEntity;
import kjb.safedb.KjbNotRnnoJpaAttributeConverter;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -53,6 +54,7 @@ public class UserInfo extends AbstractEntity<String> implements Serializable {
@Column(length = 40)
@Comment("휴대폰 번호")
@Convert(converter = KjbNotRnnoJpaAttributeConverter.class)
private String cphnno;
@Column(length = 40)
@@ -61,6 +63,7 @@ public class UserInfo extends AbstractEntity<String> implements Serializable {
@Column(length = 100)
@Comment("이메일 주소")
@Convert(converter = KjbNotRnnoJpaAttributeConverter.class)
private String emad;
@Column(length = 3)
@@ -1,8 +1,8 @@
package kjb.safedb;
import com.eactive.ext.kjb.safedb.KjbSafedbWrapper;
import com.eactive.ext.kjb.safedb.SafeDBColumns;
import com.eactive.ext.kjb.safedb.Utils;
//import com.eactive.ext.kjb.safedb.KjbSafedbWrapper;
//import com.eactive.ext.kjb.safedb.SafeDBColumns;
//import com.eactive.ext.kjb.safedb.Utils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
@@ -25,26 +25,26 @@ public class KjbNotRnnoJpaAttributeConverter implements AttributeConverter<Strin
public String convertToDatabaseColumn(String attribute) {
// log.debug("DB 암호화 #1 - {} / {}", attribute, SafeDBColumns.NOT_RNNO);
String originalAttribute = attribute;
if (StringUtils.isNotEmpty(attribute)) {
KjbSafedbWrapper wrapper = KjbSafedbWrapper.getInstance();
attribute = wrapper.encryptNotRnnoString(attribute);
if (log.isDebugEnabled()) {
// originalAttribute 홀수 글자 마스킹
StringBuilder sb = new StringBuilder();
for (int i = 0; i < originalAttribute.length(); i++) {
if (i % 2 == 0) {
sb.append(originalAttribute.charAt(i));
} else {
sb.append("*");
}
}
originalAttribute = sb.toString();
log.debug("DB 암호화 #2 : {} -> {}", originalAttribute, attribute);
}
}
// String originalAttribute = attribute;
// if (StringUtils.isNotEmpty(attribute)) {
// KjbSafedbWrapper wrapper = KjbSafedbWrapper.getInstance();
// attribute = wrapper.encryptNotRnnoString(attribute);
//
// if (log.isDebugEnabled()) {
// // originalAttribute 홀수 글자 마스킹
// StringBuilder sb = new StringBuilder();
// for (int i = 0; i < originalAttribute.length(); i++) {
// if (i % 2 == 0) {
// sb.append(originalAttribute.charAt(i));
// } else {
// sb.append("*");
// }
// }
//
// originalAttribute = sb.toString();
// log.debug("DB 암호화 #2 : {} -> {}", originalAttribute, attribute);
// }
// }
return attribute;
}
@@ -59,23 +59,23 @@ public class KjbNotRnnoJpaAttributeConverter implements AttributeConverter<Strin
public String convertToEntityAttribute(String dbData) {
// log.debug("DB 복호화 #1 - {} / {}", dbData, SafeDBColumns.NOT_RNNO);
String dbDataOriginal = dbData;
if (StringUtils.isNotEmpty(dbData)) {
if (this.isEncrypted(dbData)) {
KjbSafedbWrapper safeDBWrapper = KjbSafedbWrapper.getInstance();
try {
dbData = safeDBWrapper.decryptNotRnno(dbData);
} catch (Exception e) {
// 복호화 실패시 원본 반환
String logData = dbData.length() <= 3 ? dbData : dbData.substring(0, 3) + "...";
log.warn("DB 복호화 실패: 복호화하지 않고 원본 반환. dbData={} (Length : {})", logData, dbData.length());
needFixLogger.warn("DB 복호화 실패: 복호화하지 않고 원본 반환. dbData={} (Length : {})", logData, dbData.length(), e);
return dbData;
}
} else {
log.debug("DB 복호화 경고: Base64 형식이 아님. 복호화하지 않고 원본 반환. dbData={}", dbData);
}
}
// String dbDataOriginal = dbData;
// if (StringUtils.isNotEmpty(dbData)) {
// if (this.isEncrypted(dbData)) {
// KjbSafedbWrapper safeDBWrapper = KjbSafedbWrapper.getInstance();
// try {
// dbData = safeDBWrapper.decryptNotRnno(dbData);
// } catch (Exception e) {
// // 복호화 실패시 원본 반환
// String logData = dbData.length() <= 3 ? dbData : dbData.substring(0, 3) + "...";
// log.warn("DB 복호화 실패: 복호화하지 않고 원본 반환. dbData={} (Length : {})", logData, dbData.length());
// needFixLogger.warn("DB 복호화 실패: 복호화하지 않고 원본 반환. dbData={} (Length : {})", logData, dbData.length(), e);
// return dbData;
// }
// } else {
// log.debug("DB 복호화 경고: Base64 형식이 아님. 복호화하지 않고 원본 반환. dbData={}", dbData);
// }
// }
// log.debug("DB 복호화 #2 : {} -> {}", dbDataOriginal, dbData);
@@ -88,28 +88,29 @@ public class KjbNotRnnoJpaAttributeConverter implements AttributeConverter<Strin
* @return
*/
private boolean isEncrypted(String data) {
if (StringUtils.isEmpty(data)) {
return false;
}
// 숫자, - 로만 구성된 경우 암호화 되지 않은 걸로 판단 (ex: 연락처)
if (data.matches("^[0-9-]+$")) {
return false;
}
data = data.trim();
// Base64 형식 체크
if (!Utils.isBase64(data)) {
return false;
}
// Base64 길이 체크 (4의 배수)
if (data.length() % 4 != 0) {
return false;
}
return true;
// if (StringUtils.isEmpty(data)) {
// return false;
// }
//
// // 숫자, - 로만 구성된 경우 암호화 되지 않은 걸로 판단 (ex: 연락처)
// if (data.matches("^[0-9-]+$")) {
// return false;
// }
//
// data = data.trim();
//
// // Base64 형식 체크
// if (!Utils.isBase64(data)) {
// return false;
// }
//
// // Base64 길이 체크 (4의 배수)
// if (data.length() % 4 != 0) {
// return false;
// }
//
//
// return true;
return false;
}
}