Merge pull request #17 from ktds-dg0501/feature/participation-service

participant_id 중복 생성 문제 수정
This commit is contained in:
kkkd-max 2025-10-28 15:18:01 +09:00 committed by GitHub
commit e807bdbd59
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 207 additions and 12 deletions

View File

@ -171,7 +171,11 @@ public class GlobalExceptionHandler {
*/ */
@ExceptionHandler(DataIntegrityViolationException.class) @ExceptionHandler(DataIntegrityViolationException.class)
public ResponseEntity<ErrorResponse> handleDataIntegrityViolationException(DataIntegrityViolationException ex) { public ResponseEntity<ErrorResponse> handleDataIntegrityViolationException(DataIntegrityViolationException ex) {
log.warn("Data integrity violation: {}", ex.getMessage()); log.error("=== DataIntegrityViolationException 발생 ===");
log.error("Exception type: {}", ex.getClass().getSimpleName());
log.error("Exception message: {}", ex.getMessage());
log.error("Root cause: {}", ex.getRootCause() != null ? ex.getRootCause().getMessage() : "null");
log.error("Stack trace: ", ex);
String message = "데이터 중복 또는 무결성 제약 위반이 발생했습니다"; String message = "데이터 중복 또는 무결성 제약 위반이 발생했습니다";
String details = ex.getMessage(); String details = ex.getMessage();

View File

@ -0,0 +1,14 @@
-- participation-service channel 컬럼 추가 스크립트
-- 실행 방법: psql -h 4.230.72.147 -U eventuser -d participationdb -f add-channel-column.sql
-- channel 컬럼 추가
ALTER TABLE participants
ADD COLUMN IF NOT EXISTS channel VARCHAR(20);
-- 기존 데이터에 기본값 설정
UPDATE participants
SET channel = 'SNS'
WHERE channel IS NULL;
-- 커밋
COMMIT;

View File

@ -44,14 +44,31 @@ public class ParticipationService {
public ParticipationResponse participate(String eventId, ParticipationRequest request) { public ParticipationResponse participate(String eventId, ParticipationRequest request) {
log.info("이벤트 참여 시작 - eventId: {}, phoneNumber: {}", eventId, request.getPhoneNumber()); log.info("이벤트 참여 시작 - eventId: {}, phoneNumber: {}", eventId, request.getPhoneNumber());
// 중복 참여 체크 // 중복 참여 체크 - 상세 디버깅
if (participantRepository.existsByEventIdAndPhoneNumber(eventId, request.getPhoneNumber())) { log.info("중복 참여 체크 시작 - eventId: '{}', phoneNumber: '{}'", eventId, request.getPhoneNumber());
boolean isDuplicate = participantRepository.existsByEventIdAndPhoneNumber(eventId, request.getPhoneNumber());
log.info("중복 참여 체크 결과 - isDuplicate: {}", isDuplicate);
if (isDuplicate) {
log.warn("중복 참여 감지! eventId: '{}', phoneNumber: '{}'", eventId, request.getPhoneNumber());
throw new DuplicateParticipationException(); throw new DuplicateParticipationException();
} }
// 참여자 ID 생성 log.info("중복 참여 체크 통과 - 참여 진행");
Long maxId = participantRepository.findMaxIdByEventId(eventId).orElse(0L);
String participantId = Participant.generateParticipantId(eventId, maxId + 1); // 참여자 ID 생성 - 날짜별 최대 순번 기반
String dateTime;
if (eventId != null && eventId.length() >= 16 && eventId.startsWith("evt_")) {
dateTime = eventId.substring(4, 12); // "20250124"
} else {
dateTime = java.time.LocalDate.now().format(
java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd"));
}
String datePrefix = "prt_" + dateTime + "_";
Integer maxSequence = participantRepository.findMaxSequenceByDatePrefix(datePrefix);
String participantId = String.format("prt_%s_%03d", dateTime, maxSequence + 1);
// 참여자 저장 // 참여자 저장
Participant participant = Participant.builder() Participant participant = Participant.builder()

View File

@ -106,4 +106,16 @@ public interface ParticipantRepository extends JpaRepository<Participant, Long>
* @return 참여자 Optional * @return 참여자 Optional
*/ */
Optional<Participant> findByEventIdAndParticipantId(String eventId, String participantId); Optional<Participant> findByEventIdAndParticipantId(String eventId, String participantId);
/**
* 특정 날짜 패턴의 참여자 ID 최대 순번 조회
*
* @param datePrefix 날짜 접두사 (: "prt_20251028_")
* @return 최대 순번
*/
@Query(value = "SELECT COALESCE(MAX(CAST(SUBSTRING(participant_id FROM LENGTH(?1) + 1) AS INTEGER)), 0) " +
"FROM participants " +
"WHERE participant_id LIKE CONCAT(?1, '%')",
nativeQuery = true)
Integer findMaxSequenceByDatePrefix(@Param("datePrefix") String datePrefix);
} }

View File

@ -0,0 +1,103 @@
package com.kt.event.participation.presentation.controller;
import com.kt.event.participation.domain.participant.Participant;
import com.kt.event.participation.domain.participant.ParticipantRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 디버깅용 컨트롤러
*/
@Slf4j
@RestController
@RequestMapping("/debug")
@RequiredArgsConstructor
public class DebugController {
private final ParticipantRepository participantRepository;
/**
* 중복 참여 체크 테스트
*/
@GetMapping("/exists/{eventId}/{phoneNumber}")
public String testExists(@PathVariable String eventId, @PathVariable String phoneNumber) {
try {
log.info("디버그: 중복 체크 시작 - eventId: {}, phoneNumber: {}", eventId, phoneNumber);
boolean exists = participantRepository.existsByEventIdAndPhoneNumber(eventId, phoneNumber);
log.info("디버그: 중복 체크 결과 - exists: {}", exists);
long totalCount = participantRepository.count();
long eventCount = participantRepository.countByEventId(eventId);
return String.format(
"eventId: %s, phoneNumber: %s, exists: %s, totalCount: %d, eventCount: %d",
eventId, phoneNumber, exists, totalCount, eventCount
);
} catch (Exception e) {
log.error("디버그: 예외 발생", e);
return "ERROR: " + e.getMessage();
}
}
/**
* 모든 참여자 데이터 조회
*/
@GetMapping("/participants")
public String getAllParticipants() {
try {
List<Participant> participants = participantRepository.findAll();
StringBuilder sb = new StringBuilder();
sb.append("Total participants: ").append(participants.size()).append("\n\n");
for (Participant p : participants) {
sb.append(String.format("ID: %s, EventID: %s, Phone: %s, Name: %s\n",
p.getParticipantId(), p.getEventId(), p.getPhoneNumber(), p.getName()));
}
return sb.toString();
} catch (Exception e) {
log.error("디버그: 참여자 조회 예외 발생", e);
return "ERROR: " + e.getMessage();
}
}
/**
* 특정 전화번호의 참여 이력 조회
*/
@GetMapping("/phone/{phoneNumber}")
public String getByPhoneNumber(@PathVariable String phoneNumber) {
try {
List<Participant> participants = participantRepository.findAll();
StringBuilder sb = new StringBuilder();
sb.append("Participants with phone: ").append(phoneNumber).append("\n\n");
int count = 0;
for (Participant p : participants) {
if (phoneNumber.equals(p.getPhoneNumber())) {
sb.append(String.format("ID: %s, EventID: %s, Name: %s\n",
p.getParticipantId(), p.getEventId(), p.getName()));
count++;
}
}
if (count == 0) {
sb.append("No participants found with this phone number.");
}
return sb.toString();
} catch (Exception e) {
log.error("디버그: 전화번호별 조회 예외 발생", e);
return "ERROR: " + e.getMessage();
}
}
}

View File

@ -41,12 +41,21 @@ public class ParticipationController {
@PathVariable String eventId, @PathVariable String eventId,
@Valid @RequestBody ParticipationRequest request) { @Valid @RequestBody ParticipationRequest request) {
log.info("이벤트 참여 요청 - eventId: {}", eventId); log.info("컨트롤러: 이벤트 참여 요청 시작 - eventId: '{}', phoneNumber: '{}'", eventId, request.getPhoneNumber());
ParticipationResponse response = participationService.participate(eventId, request);
return ResponseEntity try {
.status(HttpStatus.CREATED) log.info("컨트롤러: 서비스 호출 전");
.body(ApiResponse.success(response)); ParticipationResponse response = participationService.participate(eventId, request);
log.info("컨트롤러: 서비스 호출 완료 - participantId: {}", response.getParticipantId());
return ResponseEntity
.status(HttpStatus.CREATED)
.body(ApiResponse.success(response));
} catch (Exception e) {
log.error("컨트롤러: 예외 발생 - type: {}, message: {}", e.getClass().getSimpleName(), e.getMessage());
throw e;
}
} }
/** /**

View File

@ -18,7 +18,7 @@ spring:
# JPA 설정 # JPA 설정
jpa: jpa:
hibernate: hibernate:
ddl-auto: ${DDL_AUTO:validate} ddl-auto: ${DDL_AUTO:update}
show-sql: ${SHOW_SQL:true} show-sql: ${SHOW_SQL:true}
properties: properties:
hibernate: hibernate:

View File

@ -0,0 +1,9 @@
{
"name": "기존전화번호테스트",
"phoneNumber": "010-2044-4103",
"email": "test@example.com",
"channel": "SNS",
"storeVisited": false,
"agreeMarketing": true,
"agreePrivacy": true
}

9
test-new-phone.json Normal file
View File

@ -0,0 +1,9 @@
{
"name": "새로운테스트",
"phoneNumber": "010-8888-8888",
"email": "newtest@example.com",
"channel": "SNS",
"storeVisited": false,
"agreeMarketing": true,
"agreePrivacy": true
}

View File

@ -0,0 +1,9 @@
{
"name": "새로운테스트",
"phoneNumber": "010-9999-9999",
"email": "newtest@example.com",
"channel": "SNS",
"storeVisited": false,
"agreeMarketing": true,
"agreePrivacy": true
}

9
test-participate.json Normal file
View File

@ -0,0 +1,9 @@
{
"name": "테스트",
"phoneNumber": "010-2044-4103",
"email": "test@example.com",
"channel": "SNS",
"storeVisited": false,
"agreeMarketing": true,
"agreePrivacy": true
}