From c204bbf1068ea096a9d53e634753f005024c16a1 Mon Sep 17 00:00:00 2001 From: Rinjae Date: Thu, 16 Oct 2025 11:53:45 +0900 Subject: [PATCH] init --- .gitignore | 1 + build.gradle | 123 ++++++++++ .../ext/kjb/common/ValidationUtil.java | 48 ++++ .../com/eactive/ext/kjb/ums/EmailJob.java | 225 ++++++++++++++++++ .../eactive/ext/kjb/ums/EmailProperty.java | 26 ++ .../eactive/ext/kjb/ums/EmailSendModule.java | 148 ++++++++++++ .../ext/kjb/ums/EmailValidationResult.java | 39 +++ .../eactive/ext/kjb/ums/UUIDBase62Util.java | 70 ++++++ .../ums/test/KjbEmailJobSendModuleTests.java | 44 ++++ 9 files changed, 724 insertions(+) create mode 100644 .gitignore create mode 100644 build.gradle create mode 100644 src/main/java/com/eactive/ext/kjb/common/ValidationUtil.java create mode 100644 src/main/java/com/eactive/ext/kjb/ums/EmailJob.java create mode 100644 src/main/java/com/eactive/ext/kjb/ums/EmailProperty.java create mode 100644 src/main/java/com/eactive/ext/kjb/ums/EmailSendModule.java create mode 100644 src/main/java/com/eactive/ext/kjb/ums/EmailValidationResult.java create mode 100644 src/main/java/com/eactive/ext/kjb/ums/UUIDBase62Util.java create mode 100644 src/test/java/com/eactive/ext/kjb/ums/test/KjbEmailJobSendModuleTests.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..8cff9c4 --- /dev/null +++ b/build.gradle @@ -0,0 +1,123 @@ +plugins { + id 'java' + id 'java-library' + id 'eclipse-wtp' + id 'idea' +} + +group = 'com.eactive.ext.kjb' +version = '1.0-SNAPSHOT' + +def springVersion = "5.3.27" + + +dependencies { + annotationProcessor "org.projectlombok:lombok:1.18.28" + + api project(':elink-portal-common') + api project(":kjb-safedb") + + api 'com.eactive.elink.common:elink-common-data:4.5.5' + +// api "org.springframework:spring-context:${springVersion}" +// api "org.springframework:spring-webmvc:${springVersion}" +// api "org.springframework.security.oauth:spring-security-oauth2:2.3.8.RELEASE" + + implementation "com.google.code.gson:gson:2.3.1" + + + compileOnly 'org.slf4j:jul-to-slf4j:1.7.35' + compileOnly 'org.slf4j:jcl-over-slf4j:1.7.35' + compileOnly 'org.slf4j:log4j-over-slf4j:1.7.35' + compileOnly 'org.slf4j:slf4j-api:1.7.35' + compileOnly fileTree(dir: "ext-libs/compileOnly", include: ["*.jar"]) + + + + testAnnotationProcessor "org.projectlombok:lombok:1.18.28" + + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' + testImplementation 'ch.qos.logback:logback-classic:1.2.10' + testImplementation 'ch.qos.logback:logback-access:1.2.10' + testImplementation 'ch.qos.logback:logback-core:1.2.10' + testImplementation 'org.slf4j:jul-to-slf4j:1.7.35' + testImplementation 'org.slf4j:jcl-over-slf4j:1.7.35' + testImplementation 'org.slf4j:log4j-over-slf4j:1.7.35' + testImplementation 'org.slf4j:slf4j-api:1.7.35' + + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.8.2' +} + + +// Compile +java { + toolchain { + languageVersion = JavaLanguageVersion.of(8) + } +} + +compileJava { + options.encoding = 'UTF-8' +} + + +// Build +task sourcesJar(type: Jar, dependsOn: classes) { + archiveClassifier = 'sources' + from sourceSets.main.allSource +} + +artifacts { + archives sourcesJar +} + +jar { + archiveFileName = "eapim-admin-kjb.jar" +} + + +// Test +test { + useJUnitPlatform() + + systemProperty "kjb_safedb.mode", project.findProperty("kjb_safedb.mode") ?: "" + + if (project.hasProperty("safedb")) { + systemProperty "kjb_safedb.mode", "real" + systemProperty "kjb_safedb.test.strict_mode", "true" + + classpath += files("/safedb/JavaAPI/config") + classpath += fileTree(dir: "/safedb/JavaAPI/lib", include: ["*.jar"]) + } + + + testLogging { + events "PASSED", "FAILED", "SKIPPED", "STANDARD_OUT", "STANDARD_ERROR" + exceptionFormat "full" + showStandardStreams = true + } +} + + + +configurations.all { + resolutionStrategy { + // 동적 버전 탐색을 10분 캐시 + cacheDynamicVersionsFor 10, 'minutes' + // 변하는 모듈(Changing Module)을 캐시하지 않음 + cacheChangingModulesFor 0, 'seconds' + } +} + +task settingEclipseEncoding { + if (project.file('.settings').exists()) { + File f = file('.settings/org.eclipse.core.resources.prefs') + f.write('eclipse.preferences.version=1\n') + f.append('encoding/=utf-8') + } +} + +eclipse { + synchronizationTasks settingEclipseEncoding +} \ No newline at end of file diff --git a/src/main/java/com/eactive/ext/kjb/common/ValidationUtil.java b/src/main/java/com/eactive/ext/kjb/common/ValidationUtil.java new file mode 100644 index 0000000..29e9662 --- /dev/null +++ b/src/main/java/com/eactive/ext/kjb/common/ValidationUtil.java @@ -0,0 +1,48 @@ +package com.eactive.ext.kjb.common; + +import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator; + +import javax.validation.*; +import java.util.Set; +import java.util.stream.Collectors; + +public class ValidationUtil { + private static final ValidatorFactory factory; + private static final Validator validator; + + static { + factory = Validation.byDefaultProvider() + .configure() + .messageInterpolator(new ParameterMessageInterpolator()) + .buildValidatorFactory(); + + validator = factory.getValidator(); + } + + + private ValidationUtil() { + } + + + public static Set> validate(T target) { + return validator.validate(target); + } + + public static void validateOrThrow(T target) { + Set> violations = validate(target); + if (!violations.isEmpty()) { + String message = violations.stream() + .map(v -> v.getPropertyPath() + " : " + v.getMessage()) + .collect(Collectors.joining(", ")); + throw new ConstraintViolationException("Validation failed: " + message, violations); + } + } + + public static String getViolationMessage(T target) { + Set> violations = validate(target); + if (violations.isEmpty()) return null; + return violations.stream() + .map(v -> v.getPropertyPath() + " : " + v.getMessage()) + .collect(Collectors.joining(", ")); + } +} diff --git a/src/main/java/com/eactive/ext/kjb/ums/EmailJob.java b/src/main/java/com/eactive/ext/kjb/ums/EmailJob.java new file mode 100644 index 0000000..71d0bfe --- /dev/null +++ b/src/main/java/com/eactive/ext/kjb/ums/EmailJob.java @@ -0,0 +1,225 @@ +package com.eactive.ext.kjb.ums; + +import com.eactive.apim.portal.portaluser.entity.PortalUser; +import com.eactive.apim.portal.template.entity.MessageRequest; +import com.eactive.apim.portal.template.repository.MessageRequestRepository; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +import javax.transaction.Transactional; +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.Map; + +@Slf4j +public class EmailJob { + private final EmailProperty prop; + private final MessageRequest messageRequest; + + private final Path tempFilePath; + private final Path eaiFilePath; + private final Path backupFilePath; + private final Path failedFilePath; + private MessageRequestRepository messageRequestRepository; + private final LocalDateTime now = LocalDateTime.now(); + + + + private EmailJob(EmailProperty prop, MessageRequest messageRequest, MessageRequestRepository messageRequestRepository) { + this.messageRequest = messageRequest; + this.prop = prop; + + String filename = messageRequest.getServiceId() + "_" + DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS").format(now); + this.tempFilePath = Paths.get(prop.getEaiBatchSendDir(), filename + ".tmp"); + this.eaiFilePath = Paths.get(prop.getEaiBatchSendDir(), filename); + this.backupFilePath = Paths.get(prop.getEaiBatchBackupDir(), filename); + this.failedFilePath = Paths.get(prop.getEaiBatchBackupDir(), filename + ".failed"); + + this.messageRequestRepository = messageRequestRepository; + + this.doCreateNewFile(); + } + + + public static EmailJob create(EmailProperty prop, MessageRequest messageRequest, MessageRequestRepository repository) { + if (prop == null) { + throw new IllegalArgumentException("Property is null"); + } + if (messageRequest == null) { + throw new IllegalArgumentException("MessageRequest is null"); + } + + validateMessageRequest(messageRequest, false, true); + + if (messageRequest.getUser() != null) { + PortalUser user = messageRequest.getUser(); + messageRequest.setUmsUid(UUIDBase62Util.uuidStringToBase62(user.getId())); + + if (repository != null) { + repository.save(messageRequest); + } + } + + return new EmailJob(prop, messageRequest, repository); + } + + public static EmailValidationResult validateMessageRequest(MessageRequest messageRequest, boolean forSend, boolean isThrow) { + Map errors = new HashMap<>(); + + + if (StringUtils.isEmpty(messageRequest.getId())) { + errors.put("id", "ID is required"); + } + + if (StringUtils.isEmpty(messageRequest.getEmail())) { + errors.put("email", "Email is required"); + } + + if (StringUtils.isEmpty(messageRequest.getUsername())) { + errors.put("username", "Username is required"); + } + + + if (forSend) { + // TODO: 발송용 체크 + if (StringUtils.isEmpty(messageRequest.getServiceId())) { + errors.put("serviceId", "Service ID is required"); + } + } + + + // 결과 리턴 + if (!errors.isEmpty()) { + if (isThrow) { + throw new IllegalArgumentException("Invalid MessageRequest: " + errors); + } else { + return EmailValidationResult.failed(errors); + } + } + + return EmailValidationResult.ok(); + } + + + @Transactional + public void sendEmail() { + if (messageRequestRepository != null) { + PortalUser user = messageRequest.getUser(); + if (user != null) { + messageRequest.setUmsUid(UUIDBase62Util.uuidStringToBase62(user.getId())); + } + + messageRequestRepository.save(messageRequest); + } + + + try { + doSaveFile(); + eaiBatchCall(); + doSuccess(); + } catch (Exception e) { + doFail(); + } finally { + doDeleteEaiFile(); + } + } + + private void doDeleteEaiFile() { + // TODO: EAI 발송용 파일 삭제 + } + + private void doFail() { + // TODO: EAI 발송 실패 처리 + } + + private void eaiBatchCall() { + // TODO: EAI 배치 호출 + } + + private void doSuccess() { + // 백업 디렉토리로 복사 + try { + Files.copy(eaiFilePath, backupFilePath); + log.debug("Copied EAI file to backup: {} -> {}", eaiFilePath.toString(), backupFilePath.toString()); + } catch (IOException e) { + log.error("Cannot copy EAI file to backup: {} -> {}", eaiFilePath.toString(), backupFilePath.toString(), e); + } + } + + + public void doCreateNewFile() { + final String filename = "EMAIL_" + messageRequest.getId() + ".json"; + + if (eaiFilePath.toFile().exists()) { + log.error("Mail File exists for ID: {}", messageRequest.getId()); + throw new IllegalStateException("Mail File exists for ID: " + messageRequest.getId()); + } + + File mailFile = tempFilePath.toFile(); + try { + boolean created = mailFile.createNewFile(); + if (!created) { + log.error("Cannot create mail file for ID: {}", messageRequest.getId()); + throw new IllegalStateException("Cannot create mail file for ID: " + messageRequest.getId()); + } + } catch (Exception ex) { + log.error("Cannot create mail file for ID: {}", messageRequest.getId(), ex); + throw new IllegalStateException("Cannot create mail file for ID: " + messageRequest.getId(), ex); + } + } + + public void doSaveFile() { + String jsonContent = generateJSON(true); + log.debug("Generated JSON: \n{}", jsonContent); + + // 저장은 임시 파일에 euc-kr 인코딩으로 저장 + byte[] data = null; + try { + data = jsonContent.getBytes("euc-kr"); + } catch (UnsupportedEncodingException e) { + log.error("Cannot encode(euc-kr) JSON: \n{}", jsonContent, e); + throw new RuntimeException(e); + } + try { + Files.write(tempFilePath, data); + Files.copy(tempFilePath, eaiFilePath); + Files.delete(tempFilePath); + log.debug("Wrote JSON to file: {}", eaiFilePath.toString()); + } catch (IOException e) { + log.error("Cannot write JSON to file: {}", eaiFilePath.toString(), e); + throw new RuntimeException(e); + } + } + + public String generateJSON(boolean isPretty) { + validateMessageRequest(messageRequest, true, true); + + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("MAKE_DATE", DateTimeFormatter.ofPattern("yyyyMMdd").format(LocalDateTime.now())); + jsonObject.addProperty("MAKE_SEQNO", "0001"); + jsonObject.addProperty("CAMP_ID", messageRequest.getServiceId()); // 전자금융 고객이메일 발송 업무ID (SERVICE_ID) + jsonObject.addProperty("ID", messageRequest.getUmsUid()); // 고객 ID + jsonObject.addProperty("EMAIL", messageRequest.getEmail()); + jsonObject.addProperty("NAME", messageRequest.getUsername()); + jsonObject.addProperty("ENCKEY", ""); // 보안메일용으로 기입 불필요 + + + jsonObject.addProperty("SUBJECT", messageRequest.getSubject()); + jsonObject.addProperty("MESSAGE", messageRequest.getMessage()); + + if (isPretty) { + return new GsonBuilder().setPrettyPrinting().create().toJson(jsonObject); + } else { + return jsonObject.toString(); + } + } +} diff --git a/src/main/java/com/eactive/ext/kjb/ums/EmailProperty.java b/src/main/java/com/eactive/ext/kjb/ums/EmailProperty.java new file mode 100644 index 0000000..99a5338 --- /dev/null +++ b/src/main/java/com/eactive/ext/kjb/ums/EmailProperty.java @@ -0,0 +1,26 @@ +package com.eactive.ext.kjb.ums; + +import lombok.Data; +import org.hibernate.validator.constraints.NotEmpty; + +import javax.validation.constraints.Pattern; + +@Data +public class EmailProperty { + @Pattern(regexp = "^$|^(https?)://[^\\s/$.?#].[^\\s]*$", message = "kjb.ums.eai_batch.url 은 유효한 URL 형식이거나 공백이어야 합니다. 예: http://example.com/api") + private String eaiBatchUrl = ""; + + @NotEmpty(message = "kjb.ums.eai_batch.send_dir 는 필수값입니다.") + @Pattern(regexp = "^(?:[a-zA-Z]:\\\\|\\\\|\\/)(?:[\\w\\-\\.]+[\\\\\\/])*[\\w\\-\\.]+$", + message = "kjb.ums.eai_batch.send_dir 은 절대 경로여야 합니다. 예: /Data/eapim/portal/sendmail/snd 또는 C:\\Data\\eapim\\portal\\sendmail\\snd") + private String eaiBatchSendDir; + + @NotEmpty(message = "kjb.ums.eai_batch.backup_dir 는 필수값입니다.") + @Pattern(regexp = "^(?:[a-zA-Z]:\\\\|\\\\|\\/)(?:[\\w\\-\\.]+[\\\\\\/])*[\\w\\-\\.]+$", + message = "kjb.ums.eai_batch.backup_dir 은 절대 경로여야 합니다. 예: /Data/eapim/portal/sendmail/bak 또는 C:\\Data\\eapim\\portal\\sendmail\\bak") + private String eaiBatchBackupDir; + + + @Pattern(regexp = "^[a-zA-Z0-9]{12}$", message = "kjb.ums.eai_batch.interface_id 는 12자리 영문대소문자 및 숫자 조합이어야 합니다.") + private String eaiBatchInterfaceId; +} diff --git a/src/main/java/com/eactive/ext/kjb/ums/EmailSendModule.java b/src/main/java/com/eactive/ext/kjb/ums/EmailSendModule.java new file mode 100644 index 0000000..320e849 --- /dev/null +++ b/src/main/java/com/eactive/ext/kjb/ums/EmailSendModule.java @@ -0,0 +1,148 @@ +package com.eactive.ext.kjb.ums; + +import com.eactive.apim.portal.template.entity.MessageRequest; +import com.eactive.apim.portal.template.repository.MessageRequestRepository; +import com.eactive.ext.kjb.common.ValidationUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; + +@Slf4j +public class EmailSendModule { + private static EmailSendModule instance = null; + private static EmailProperty prop; + + private final MessageRequestRepository repo; + + + public static synchronized EmailSendModule getInstance(EmailProperty property, + MessageRequestRepository messageRequestRepository) { + + ValidationUtil.validateOrThrow(property); + + if (instance == null) { + instance = new EmailSendModule(property, messageRequestRepository); + } + + return instance; + } + + + public static void environmentCheck() { + if (StringUtils.isEmpty(prop.getEaiBatchUrl())) { + log.warn("EAI_BATCH_URL 가 설정되지 않음. 실제 EAI 배치 연동 없이 동작함"); + } + + log.info("EAI Batch URL: {}", prop.getEaiBatchUrl()); + log.info("EAI Batch Send Dir: {}", prop.getEaiBatchSendDir()); + + + // 디렉토리 검사(쓰기 권한 포함) + Path sendDirPath = Paths.get(prop.getEaiBatchSendDir()); + if (!sendDirPath.toFile().exists()) { + // 생성 시도 + boolean created = sendDirPath.toFile().mkdirs(); + if (!created) { + log.error("EAI_BATCH_SEND_DIR 디렉토리를 생성할 수 없음: {}", prop.getEaiBatchSendDir()); + throw new IllegalStateException("EAI_BATCH_SEND_DIR 디렉토리를 생성할 수 없음: " + prop.getEaiBatchSendDir()); + } + } + + if (!sendDirPath.toFile().isDirectory()) { + log.error("EAI_BATCH_SEND_DIR 가 디렉토리가 아님: {}", prop.getEaiBatchSendDir()); + throw new IllegalStateException("EAI_BATCH_SEND_DIR 가 디렉토리가 아님: " + prop.getEaiBatchSendDir()); + } + + if (!sendDirPath.toFile().canWrite()) { + log.error("EAI_BATCH_SEND_DIR 에 쓰기 권한이 없음: {}", prop.getEaiBatchSendDir()); + throw new IllegalStateException("EAI_BATCH_SEND_DIR 에 쓰기 권한이 없음: " + prop.getEaiBatchSendDir()); + } + + + // 백업 디렉토리 + Path backupDirPath = Paths.get(prop.getEaiBatchBackupDir()); + if (!backupDirPath.toFile().exists()) { + // 생성 시도 + boolean created = backupDirPath.toFile().mkdirs(); + if (!created) { + log.error("EAI_BATCH_BACKUP_DIR 디렉토리를 생성할 수 없음: {}", prop.getEaiBatchBackupDir()); + throw new IllegalStateException("EAI_BATCH_BACKUP_DIR 디렉토리를 생성할 수 없음: " + prop.getEaiBatchBackupDir()); + } + } + + if (!backupDirPath.toFile().isDirectory()) { + log.error("EAI_BATCH_BACKUP_DIR 가 디렉토리가 아님: {}", prop.getEaiBatchBackupDir()); + throw new IllegalStateException("EAI_BATCH_BACKUP_DIR 가 디렉토리가 아님: " + prop.getEaiBatchBackupDir()); + } + + if (!backupDirPath.toFile().canWrite()) { + log.error("EAI_BATCH_BACKUP_DIR 에 쓰기 권한이 없음: {}", prop.getEaiBatchBackupDir()); + throw new IllegalStateException("EAI_BATCH_BACKUP_DIR 에 쓰기 권한이 없음: " + prop.getEaiBatchBackupDir()); + } + + log.info("KjbEmailSendModule - Environment check passed."); + } + + + + private EmailSendModule(EmailProperty property, + MessageRequestRepository repo) { + this.repo = repo; + + if (property != null && prop == null) { + prop = property; + } + + environmentCheck(); + } + + public CompletableFuture sendEmail(MessageRequest messageRequest, BiConsumer callback) { + return CompletableFuture + .supplyAsync(() -> { + // TODO: MESSAGE_CODE를 통해 SERVICE_ID 생성 필요 + String serviceId; + + switch(messageRequest.getMessageCode()) { + + } + + messageRequest.setServiceId("TEMP_SERVICE"); + if (repo != null) { + repo.save(messageRequest); + } + + EmailJob job = EmailJob.create(prop, messageRequest, repo); + job.sendEmail(); + + log.debug("JSON : {}", job.generateJSON(true)); + return true; + }) + .whenComplete(callback); + } + + + private void validateMessageRequest(MessageRequest messageRequest) { + if (messageRequest == null) { + log.warn("MessageRequest is null"); + throw new IllegalArgumentException("MessageRequest is required"); + } + + if (StringUtils.isEmpty(messageRequest.getId())) { + log.warn("MessageRequest ID is empty"); + throw new IllegalArgumentException("MessageRequest ID is required"); + } + + if (StringUtils.isEmpty(messageRequest.getEmail())) { + log.warn("MessageRequest Email is empty for ID: {}", messageRequest.getId()); + throw new IllegalArgumentException("MessageRequest Email is required for ID: " + messageRequest.getId()); + } + } + + private void createMailFile() { + + } +} diff --git a/src/main/java/com/eactive/ext/kjb/ums/EmailValidationResult.java b/src/main/java/com/eactive/ext/kjb/ums/EmailValidationResult.java new file mode 100644 index 0000000..81973d7 --- /dev/null +++ b/src/main/java/com/eactive/ext/kjb/ums/EmailValidationResult.java @@ -0,0 +1,39 @@ +package com.eactive.ext.kjb.ums; + +import java.util.Collections; +import java.util.Map; + +public class EmailValidationResult { + private final boolean success; + private final Map errors; + + + public EmailValidationResult(boolean success, Map errors) { + this.success = success; + this.errors = errors; + } + + public static EmailValidationResult ok() { + return new EmailValidationResult(true, Collections.emptyMap()); + } + + public static EmailValidationResult failed(Map errors) { + return new EmailValidationResult(false, errors); + } + + public boolean isSuccess() { + return success; + } + + public Map getErrors() { + return errors; + } + + @Override + public String toString() { + return "ValidationResult{" + + "success=" + success + + ", errors=" + errors + + '}'; + } +} diff --git a/src/main/java/com/eactive/ext/kjb/ums/UUIDBase62Util.java b/src/main/java/com/eactive/ext/kjb/ums/UUIDBase62Util.java new file mode 100644 index 0000000..047b34b --- /dev/null +++ b/src/main/java/com/eactive/ext/kjb/ums/UUIDBase62Util.java @@ -0,0 +1,70 @@ +package com.eactive.ext.kjb.ums; + +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.UUID; + +public class UUIDBase62Util { + private static final String BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + public static String uuidStringToBase62(String uuidStr) { + UUID uuid = UUID.fromString(uuidStr); + ByteBuffer bb = ByteBuffer.wrap(new byte[16]); + bb.putLong(uuid.getMostSignificantBits()); + bb.putLong(uuid.getLeastSignificantBits()); + BigInteger bigInt = new BigInteger(1, bb.array()); + return toBase62(bigInt); + } + + public static String base62ToUuidString(String base62Str) { + BigInteger bigInt = fromBase62(base62Str); + byte[] bytes = toByteArray(bigInt, 16); + ByteBuffer bb = ByteBuffer.wrap(bytes); + long msb = bb.getLong(); + long lsb = bb.getLong(); + return new UUID(msb, lsb).toString(); + } + + private static String toBase62(BigInteger value) { + StringBuilder sb = new StringBuilder(); + while (value.compareTo(BigInteger.ZERO) > 0) { + BigInteger[] divRem = value.divideAndRemainder(BigInteger.valueOf(62)); + sb.append(BASE62.charAt(divRem[1].intValue())); + value = divRem[0]; + } + // pad to 22 chars for consistent length + while (sb.length() < 22) sb.append('0'); + return sb.reverse().toString(); + } + + private static BigInteger fromBase62(String base62) { + BigInteger result = BigInteger.ZERO; + for (char c : base62.toCharArray()) { + int digit = BASE62.indexOf(c); + if (digit == -1) throw new IllegalArgumentException("Invalid Base62 character: " + c); + result = result.multiply(BigInteger.valueOf(62)).add(BigInteger.valueOf(digit)); + } + return result; + } + + private static byte[] toByteArray(BigInteger bigInt, int size) { + byte[] src = bigInt.toByteArray(); + byte[] dst = new byte[size]; + int srcPos = Math.max(0, src.length - size); + int dstPos = Math.max(0, size - src.length); + int length = Math.min(size, src.length); + System.arraycopy(src, srcPos, dst, dstPos, length); + return dst; + } + + public static void main(String[] args) { + String uuid = UUID.randomUUID().toString(); + String base62 = uuidStringToBase62(uuid); + String decoded = base62ToUuidString(base62); + + System.out.println("UUID : " + uuid); + System.out.println("Base62: " + base62 + " (" + base62.length() + ")"); + System.out.println("Decoded: " + decoded); + System.out.println("Same? " + uuid.equals(decoded)); + } +} diff --git a/src/test/java/com/eactive/ext/kjb/ums/test/KjbEmailJobSendModuleTests.java b/src/test/java/com/eactive/ext/kjb/ums/test/KjbEmailJobSendModuleTests.java new file mode 100644 index 0000000..6bf739e --- /dev/null +++ b/src/test/java/com/eactive/ext/kjb/ums/test/KjbEmailJobSendModuleTests.java @@ -0,0 +1,44 @@ +package com.eactive.ext.kjb.ums.test; + +import com.eactive.apim.portal.template.entity.MessageRequest; +import com.eactive.ext.kjb.ums.EmailProperty; +import com.eactive.ext.kjb.ums.EmailSendModule; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; + +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + + +@Slf4j +public class KjbEmailJobSendModuleTests { + + @Test + public void test() { + EmailProperty prop = new EmailProperty(); + prop.setEaiBatchUrl(null); + prop.setEaiBatchSendDir("D:\\kjb-logs\\sendmail\\snd"); + prop.setEaiBatchBackupDir("D:\\kjb-logs\\sendmail\\bak"); + + EmailSendModule service = EmailSendModule.getInstance(prop, null); + MessageRequest messageRequest = new MessageRequest(); + messageRequest.setId(UUID.randomUUID().toString()); + messageRequest.setEmail("dearmai@dearmai.net"); + messageRequest.setUsername("김광은"); + messageRequest.setSubject("제목 - test"); + messageRequest.setMessage("내용 - test"); + + + CompletableFuture future = service.sendEmail(messageRequest, (result, ex) -> { + if (ex != null) { + log.error("failed", ex); + } else { + log.info("result: {}", result); + } + }); + + future.join(); + + log.info("done"); + } +}