비즈니스 친화적 eventId 및 jobId 생성 로직 구현
- EventIdGenerator 추가: EVT-{storeId}-{yyyyMMddHHmmss}-{random8} 형식
- JobIdGenerator 추가: JOB-{type}-{timestamp}-{random8} 형식
- EventService, JobService에 Generator 주입 및 사용
- AIJobKafkaProducer에 eventId 및 메시지 필드 추가
- AIEventGenerationJobMessage DTO 필드 확장
- Javadoc에서 UUID 표현 제거 및 실제 형식 명시
- Event.java의 UUID 백업 생성 로직 제거
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
34291e1613
commit
b71d27aa8b
@ -32,6 +32,36 @@ public class AIEventGenerationJobMessage {
|
||||
@JsonProperty("user_id")
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 이벤트 ID
|
||||
*/
|
||||
@JsonProperty("event_id")
|
||||
private String eventId;
|
||||
|
||||
/**
|
||||
* 매장명
|
||||
*/
|
||||
@JsonProperty("store_name")
|
||||
private String storeName;
|
||||
|
||||
/**
|
||||
* 매장 업종
|
||||
*/
|
||||
@JsonProperty("store_category")
|
||||
private String storeCategory;
|
||||
|
||||
/**
|
||||
* 매장 설명
|
||||
*/
|
||||
@JsonProperty("store_description")
|
||||
private String storeDescription;
|
||||
|
||||
/**
|
||||
* 이벤트 목적
|
||||
*/
|
||||
@JsonProperty("objective")
|
||||
private String objective;
|
||||
|
||||
/**
|
||||
* 작업 상태 (PENDING, PROCESSING, COMPLETED, FAILED)
|
||||
*/
|
||||
|
||||
@ -0,0 +1,112 @@
|
||||
package com.kt.event.eventservice.application.service;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 이벤트 ID 생성기
|
||||
*
|
||||
* 비즈니스 친화적인 eventId를 생성합니다.
|
||||
* 형식: EVT-{storeId}-{yyyyMMddHHmmss}-{random8}
|
||||
* 예시: EVT-store123-20251029143025-a1b2c3d4
|
||||
*
|
||||
* VARCHAR(50) 길이 제약사항을 고려하여 설계되었습니다.
|
||||
*/
|
||||
@Component
|
||||
public class EventIdGenerator {
|
||||
|
||||
private static final String PREFIX = "EVT";
|
||||
private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
|
||||
private static final int RANDOM_LENGTH = 8;
|
||||
|
||||
/**
|
||||
* 이벤트 ID 생성
|
||||
*
|
||||
* @param storeId 상점 ID (최대 15자 권장)
|
||||
* @return 생성된 이벤트 ID
|
||||
* @throws IllegalArgumentException storeId가 null이거나 비어있는 경우
|
||||
*/
|
||||
public String generate(String storeId) {
|
||||
if (storeId == null || storeId.isBlank()) {
|
||||
throw new IllegalArgumentException("storeId는 필수입니다");
|
||||
}
|
||||
|
||||
// storeId 길이 검증 (전체 길이 50자 제한)
|
||||
if (storeId.length() > 15) {
|
||||
throw new IllegalArgumentException("storeId는 15자 이하여야 합니다");
|
||||
}
|
||||
|
||||
String timestamp = LocalDateTime.now().format(TIMESTAMP_FORMATTER);
|
||||
String randomPart = generateRandomPart();
|
||||
|
||||
// 형식: EVT-{storeId}-{timestamp}-{random}
|
||||
// 예상 길이: 3 + 1 + 15 + 1 + 14 + 1 + 8 = 43자 (최대)
|
||||
String eventId = String.format("%s-%s-%s-%s", PREFIX, storeId, timestamp, randomPart);
|
||||
|
||||
// 길이 검증
|
||||
if (eventId.length() > 50) {
|
||||
throw new IllegalStateException(
|
||||
String.format("생성된 eventId 길이(%d)가 50자를 초과했습니다: %s",
|
||||
eventId.length(), eventId)
|
||||
);
|
||||
}
|
||||
|
||||
return eventId;
|
||||
}
|
||||
|
||||
/**
|
||||
* UUID 기반 랜덤 문자열 생성
|
||||
*
|
||||
* @return 8자리 랜덤 문자열 (소문자 영숫자)
|
||||
*/
|
||||
private String generateRandomPart() {
|
||||
return UUID.randomUUID()
|
||||
.toString()
|
||||
.replace("-", "")
|
||||
.substring(0, RANDOM_LENGTH)
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* eventId 형식 검증
|
||||
*
|
||||
* @param eventId 검증할 이벤트 ID
|
||||
* @return 유효하면 true, 아니면 false
|
||||
*/
|
||||
public boolean isValid(String eventId) {
|
||||
if (eventId == null || eventId.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// EVT-로 시작하는지 확인
|
||||
if (!eventId.startsWith(PREFIX + "-")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 길이 검증
|
||||
if (eventId.length() > 50) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 형식 검증: EVT-{storeId}-{14자리숫자}-{8자리영숫자}
|
||||
String[] parts = eventId.split("-");
|
||||
if (parts.length != 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// timestamp 부분이 14자리 숫자인지 확인
|
||||
if (parts[2].length() != 14 || !parts[2].matches("\\d{14}")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// random 부분이 8자리 영숫자인지 확인
|
||||
if (parts[3].length() != 8 || !parts[3].matches("[a-z0-9]{8}")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -47,6 +47,8 @@ public class EventService {
|
||||
private final AIJobKafkaProducer aiJobKafkaProducer;
|
||||
private final ImageJobKafkaProducer imageJobKafkaProducer;
|
||||
private final EventKafkaProducer eventKafkaProducer;
|
||||
private final EventIdGenerator eventIdGenerator;
|
||||
private final JobIdGenerator jobIdGenerator;
|
||||
|
||||
/**
|
||||
* 이벤트 생성 (Step 1: 목적 선택)
|
||||
@ -61,8 +63,13 @@ public class EventService {
|
||||
log.info("이벤트 생성 시작 - userId: {}, storeId: {}, objective: {}",
|
||||
userId, storeId, request.getObjective());
|
||||
|
||||
// eventId 생성
|
||||
String eventId = eventIdGenerator.generate(storeId);
|
||||
log.info("생성된 eventId: {}", eventId);
|
||||
|
||||
// 이벤트 엔티티 생성
|
||||
Event event = Event.builder()
|
||||
.eventId(eventId)
|
||||
.userId(userId)
|
||||
.storeId(storeId)
|
||||
.objective(request.getObjective())
|
||||
@ -235,7 +242,11 @@ public class EventService {
|
||||
String.join(", ", request.getPlatforms()));
|
||||
|
||||
// Job 엔티티 생성
|
||||
String jobId = jobIdGenerator.generate(JobType.IMAGE_GENERATION);
|
||||
log.info("생성된 jobId: {}", jobId);
|
||||
|
||||
Job job = Job.builder()
|
||||
.jobId(jobId)
|
||||
.eventId(eventId)
|
||||
.jobType(JobType.IMAGE_GENERATION)
|
||||
.build();
|
||||
@ -312,7 +323,11 @@ public class EventService {
|
||||
}
|
||||
|
||||
// Job 엔티티 생성
|
||||
String jobId = jobIdGenerator.generate(JobType.AI_RECOMMENDATION);
|
||||
log.info("생성된 jobId: {}", jobId);
|
||||
|
||||
Job job = Job.builder()
|
||||
.jobId(jobId)
|
||||
.eventId(eventId)
|
||||
.jobType(JobType.AI_RECOMMENDATION)
|
||||
.build();
|
||||
|
||||
@ -0,0 +1,123 @@
|
||||
package com.kt.event.eventservice.application.service;
|
||||
|
||||
import com.kt.event.eventservice.domain.enums.JobType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Job ID 생성기
|
||||
*
|
||||
* 비즈니스 친화적인 jobId를 생성합니다.
|
||||
* 형식: JOB-{jobType}-{timestamp}-{random8}
|
||||
* 예시: JOB-AI-20251029143025-a1b2c3d4
|
||||
*
|
||||
* VARCHAR(50) 길이 제약사항을 고려하여 설계되었습니다.
|
||||
*/
|
||||
@Component
|
||||
public class JobIdGenerator {
|
||||
|
||||
private static final String PREFIX = "JOB";
|
||||
private static final int RANDOM_LENGTH = 8;
|
||||
|
||||
/**
|
||||
* Job ID 생성
|
||||
*
|
||||
* @param jobType Job 타입
|
||||
* @return 생성된 Job ID
|
||||
* @throws IllegalArgumentException jobType이 null인 경우
|
||||
*/
|
||||
public String generate(JobType jobType) {
|
||||
if (jobType == null) {
|
||||
throw new IllegalArgumentException("jobType은 필수입니다");
|
||||
}
|
||||
|
||||
String typeCode = getTypeCode(jobType);
|
||||
String timestamp = String.valueOf(System.currentTimeMillis());
|
||||
String randomPart = generateRandomPart();
|
||||
|
||||
// 형식: JOB-{type}-{timestamp}-{random}
|
||||
// 예상 길이: 3 + 1 + 5 + 1 + 13 + 1 + 8 = 32자 (최대)
|
||||
String jobId = String.format("%s-%s-%s-%s", PREFIX, typeCode, timestamp, randomPart);
|
||||
|
||||
// 길이 검증
|
||||
if (jobId.length() > 50) {
|
||||
throw new IllegalStateException(
|
||||
String.format("생성된 jobId 길이(%d)가 50자를 초과했습니다: %s",
|
||||
jobId.length(), jobId)
|
||||
);
|
||||
}
|
||||
|
||||
return jobId;
|
||||
}
|
||||
|
||||
/**
|
||||
* JobType을 짧은 코드로 변환
|
||||
*
|
||||
* @param jobType Job 타입
|
||||
* @return 타입 코드
|
||||
*/
|
||||
private String getTypeCode(JobType jobType) {
|
||||
switch (jobType) {
|
||||
case AI_RECOMMENDATION:
|
||||
return "AI";
|
||||
case IMAGE_GENERATION:
|
||||
return "IMG";
|
||||
default:
|
||||
return jobType.name().substring(0, Math.min(5, jobType.name().length()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* UUID 기반 랜덤 문자열 생성
|
||||
*
|
||||
* @return 8자리 랜덤 문자열 (소문자 영숫자)
|
||||
*/
|
||||
private String generateRandomPart() {
|
||||
return UUID.randomUUID()
|
||||
.toString()
|
||||
.replace("-", "")
|
||||
.substring(0, RANDOM_LENGTH)
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* jobId 형식 검증
|
||||
*
|
||||
* @param jobId 검증할 Job ID
|
||||
* @return 유효하면 true, 아니면 false
|
||||
*/
|
||||
public boolean isValid(String jobId) {
|
||||
if (jobId == null || jobId.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// JOB-로 시작하는지 확인
|
||||
if (!jobId.startsWith(PREFIX + "-")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 길이 검증
|
||||
if (jobId.length() > 50) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 형식 검증: JOB-{type}-{timestamp}-{8자리영숫자}
|
||||
String[] parts = jobId.split("-");
|
||||
if (parts.length != 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// timestamp 부분이 숫자인지 확인
|
||||
if (!parts[2].matches("\\d+")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// random 부분이 8자리 영숫자인지 확인
|
||||
if (parts[3].length() != 8 || !parts[3].matches("[a-z0-9]{8}")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -27,6 +27,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
public class JobService {
|
||||
|
||||
private final JobRepository jobRepository;
|
||||
private final JobIdGenerator jobIdGenerator;
|
||||
|
||||
/**
|
||||
* Job 생성
|
||||
@ -39,7 +40,12 @@ public class JobService {
|
||||
public Job createJob(String eventId, JobType jobType) {
|
||||
log.info("Job 생성 - eventId: {}, jobType: {}", eventId, jobType);
|
||||
|
||||
// jobId 생성
|
||||
String jobId = jobIdGenerator.generate(jobType);
|
||||
log.info("생성된 jobId: {}", jobId);
|
||||
|
||||
Job job = Job.builder()
|
||||
.jobId(jobId)
|
||||
.eventId(eventId)
|
||||
.jobType(jobType)
|
||||
.build();
|
||||
|
||||
@ -35,9 +35,9 @@ public class AIJobKafkaProducer {
|
||||
/**
|
||||
* AI 이벤트 생성 작업 메시지 발행
|
||||
*
|
||||
* @param jobId 작업 ID (UUID String)
|
||||
* @param userId 사용자 ID (UUID String)
|
||||
* @param eventId 이벤트 ID (UUID String)
|
||||
* @param jobId 작업 ID (JOB-{type}-{timestamp}-{random8})
|
||||
* @param userId 사용자 ID
|
||||
* @param eventId 이벤트 ID (EVT-{storeId}-{yyyyMMddHHmmss}-{random8})
|
||||
* @param storeName 매장명
|
||||
* @param storeCategory 매장 업종
|
||||
* @param storeDescription 매장 설명
|
||||
@ -55,6 +55,11 @@ public class AIJobKafkaProducer {
|
||||
AIEventGenerationJobMessage message = AIEventGenerationJobMessage.builder()
|
||||
.jobId(jobId)
|
||||
.userId(userId)
|
||||
.eventId(eventId)
|
||||
.storeName(storeName)
|
||||
.storeCategory(storeCategory)
|
||||
.storeDescription(storeDescription)
|
||||
.objective(objective)
|
||||
.status("PENDING")
|
||||
.createdAt(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
@ -35,9 +35,9 @@ public class ImageJobKafkaProducer {
|
||||
/**
|
||||
* 이미지 생성 작업 메시지 발행
|
||||
*
|
||||
* @param jobId 작업 ID (UUID)
|
||||
* @param userId 사용자 ID (UUID)
|
||||
* @param eventId 이벤트 ID (UUID)
|
||||
* @param jobId 작업 ID (JOB-{type}-{timestamp}-{random8})
|
||||
* @param userId 사용자 ID
|
||||
* @param eventId 이벤트 ID (EVT-{storeId}-{yyyyMMddHHmmss}-{random8})
|
||||
* @param prompt 이미지 생성 프롬프트
|
||||
*/
|
||||
public void publishImageGenerationJob(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user