🎅 오너먼트 프로젝트

이벤트 관리 기능 : Spring 구조 설계 정리

보배 진 2026. 2. 20. 12:09

 

이벤트 컨트롤러 부분 코드를 해석해보려고 한다

이 EventController는 이벤트(프로모션) 관리 API를 모아둔 스프링 컨트롤러입니다

🔹 /api로 시작하는 URL로 들어오는 요청을 받아서

🔹 이벤트 조회/종료/등록을 처리해

 "이 코드가 뭘 하는지 / 어떻게 동작하는지"를 한 번 분석해보겠다


1) 이 클래스가 하는 역할 한 줄 요약

관리자는 이벤트를 등록/종료/전체조회를 할 수 있고,

일반 사용자는 현재 진행 중인 이벤트를 조회할 수 있게 만든 REST API 컨트롤러이다

🔹 @RestController : JSON 응답을 반환하는 컨트롤러

🔹 @RequestMapping("/api") : 이 컨트롤러의 모든 API는 /api로 시작

 

 

 

2) 주입(Autowired)된 것들이 뭐 하는 애들인지

@Autowired private BrevoClient brevoClient;
@Autowired private BrevoService brevoService;
@Autowired private EventService eventService;
@Autowired private ReviewService reviewService;
@Autowired private AccountService accountService;

▪ EventService : 이벤트 DB 조회/등록/수정(종료) 같은 "핵심 비즈니스" 실행

▪ ReviewService : 여기서는 "리뷰"라기보다 이미지 파일 검증/저장 기능을 재사용하고 있음

(파일 크기 체크, 확장자 체크, 저장하고 URL 반환)

▪ AccountService : 이메일 받을 회원 목록 가져오기

▪ BrevoService/BrevoClient : 이벤트 등록되면 메일 발송 (Brevo = 메일 발송 서비스)

 

 

 

3) 설정값(@Value)으로 받는 것들

@Value("${resource.path}")
private String resourcePath;

@Value("${resource.event.prefix}")
private String eventPrefix;

▪ resourcePath : 서버에 이미지 저장할 실제 경로(ex: C:/.../static/ 같은 물리 경로)

▪ eventPrefix : URL prefix (ex:  /images/event/)

즉, 업로드한 파일을 resourcePath에 저장하고, 브라우저에서 접근할 URL은 eventPrefix로 만든다는 뜻

 

 

 

 

4) API 1) 관리자: 이벤트 전체 조회

@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/admin/event/all")
public ResponseEntity<Map<String, Object>> getEvent(EventDTO eventDTO){
    eventDTO.setCondition("SELECT_ALL_EVENT");
    List<EventDTO> list = eventService.getEventList(eventDTO);
    return ResponseEntity.ok(Map.of("eventDatas", list));
}

동작흐름

1. 관리자만 접근 가능(@PreAuthorize("hasRole('ADMIN')"))

2. eventDTO.condition = "SELECT_ALL_EVENT" 

3. eventService.getEventList() 호출 ➡ 전체 이벤트 조회 쿼리 실행

4. 결과를 "eventDatas": list로 JSON 반환

 

{
  "eventDatas": [ ...이벤트목록... ]
}

🔼 반환 예시

 

 

 

 

5) API 2) 관리자: 이벤트 종료 요청

@PreAuthorize("hasRole('ADMIN')")
@PatchMapping("/admin/event/{eventPk}/end")
public ResponseEntity<Map<String, Object>> endEvent(
        @PathVariable Integer eventPk,
        EventDTO eventDTO
)

이 API가 하려는 것

특정 이벤트를 "종료 처리"하는 API이다

URL로 eventPk를 받아서 업데이트

 

흐름

1. 관리자 권한 체크

2. URL의 (eventPk}를 eventPk 변수로 받음

3. DTO에 pk 주입 + condition 지정 🔽

eventDTO.setEventPk(eventPk);
eventDTO.setCondition("UPDATE_END_EVENT");

4. eventService.updateEvent(eventDTO) 실행 : 실패하면 404 + 에러 메시지

5. 성공하면 eventPk랑 eventEndDate를 내려줌

 

 

{
  "code": "VALIDATION_ERROR",
  "message": "이벤트를 찾을 수 없습니다.."
}

 

✅ 실패 응답

 

{
  "eventPk": 3,
  "eventEndDate": "2026-02-18"
}

✅ 성공 응답(현재 코드 기준)

 

 

 

 

6) API 3) 전체: 현재 진행중인 이벤트 조회

@GetMapping("/all/event/in-progress")
public ResponseEntity<?> mainEvent(EventDTO eventDTO){
    eventDTO.setCondition("SELECT_ALL_PROGRESS_EVENT");
    List<EventDTO> list = eventService.getEventList(eventDTO);

    if (list==null || list.isEmpty()) {
        return ResponseEntity.status(404).body(Map.of(
            "code", "NO_ACTIVE_EVENT",
            "message", "현재 진행중인 이벤트가 없습니다."
        ));
    }

    return ResponseEntity.ok(Map.of("eventDatas", list));
}

동작

1. 누구나 접근 가능 (관리자 제한 없음)

2. condition으로 "진행중인 이벤트만 조회"하게 함

3. 결과 없으면 404 + 에러 코드/메시지

4. 있으면 eventData로 반환

여기서 "진행중"이란 보통 DB 기준으로

시작일 <= 오늘

종료일 >= 오늘 (또는 종료일이 null이면 진행중)

같은 조건을 쿼리에서 걸었을 가능성이 크다

 

 

 

 

7) API 4) 관리자: 이벤트 등록 (이미지 업로드 + DB 저장 + 이메일 발송)

@PreAuthorize("hasRole('ADMIN')")
@PostMapping("/admin/event")
public ResponseEntity<Map<String, Object>> insertEvent(
        @RequestPart("eventImage") MultipartFile eventImage,
        @ModelAttribute EventDTO eventDTO
)

이 부분이 제일 핵심이라고 볼 수 있다

요청 형태 

🔹 @RequestPart("eventImage") ➡ multipart/form-data로 파일 업로드 받는다

🔹 @ModelAttribute EventDTO eventDTO ➡  폼 데이터로 넘어온 나머지 필드들을 EventDTO에 바인딩

즉 프론트는 대충 이런 형태로 파일을 보내는 것이다

▪ eventImage 파일

▪ eventName, eventStartDate, eventEndDate, ... : 문자열 폼 필드들

 

흐름 (try 안에서 순서대로)

1) 이미지 존재 체크

없거나 비어있으면 400

{ "code": "MISSING_IMAGE", "message": "이미지를 불러오지 못하였습니다." }

 

 

 

2) 파일 사이즈 체크
reviewService.checkFileSize(eventImage)
실패하면 400 + 최대 허용 크기 안내
⚠️ 메시지에 "kb"라고 적어놨는데, getAllowedImageMaxBytes()가 진짜 bytes면 단위가 안 맞을 수도 있음.

 

 

 

3) 확장자 체크

reviewService.checkFileExtention(eventImage)

실패하면 400 + 허용 확장자 안내

 

 

 

4) 파일 저장 + URL 얻기

String eventImageUrl = reviewService.saveImageAndGetUrl(resourcePath, eventPrefix, eventImage);
eventDTO.setEventImageUrl(eventImageUrl);

▪ 서버 폴더에 이미지 저장

▪ 접근 가능한 URL 만들어서 DTO에 세팅

 

 

 

5) 이벤트 DB INSERT

eventDTO.setCondition("INSERT_EVENT");
if (!eventService.insertEvent(eventDTO)) { ... }

 

 

 

6) (중요) 이메일 발송은 “실패해도” 이벤트 등록 성공으로 처리

try {
    List<AccountDTO> emails = accountService.getEmailDatas();
    brevoService.sendEventMailToAllAgreeAsync(emails, eventDTO);
} catch (Exception e) { e.printStackTrace(); }

▪ 이메일 발송은 별도 try-catch로 감싸서 터져도 무시

▪ sendEventMailToAllAgreeAsync니까 비동기로 보내는 구조

 

 

7) 최근 등록된 eventPk 다시 조회

eventDTO.setCondition("SELECT_ONE_EVENT_PK_RECENT");           
eventDTO = eventService.getEvent(eventDTO);

방금 insert한 이벤트의 PK가 필요해서 “가장 최근 이벤트 PK”를 조회

여기서 주의점

⚠️ “최근” 기준이 애매하면 동시 등록이 있을 때 꼬일 수 있어.

⚠️ 보통은 INSERT 후 생성된 PK를 바로 받는 방식(MyBatis useGeneratedKeys / JdbcTemplate KeyHolder)이 더 안전함.

 

 

 

 

8) 성공 응답

{
  "code": "success",
  "message": "이벤트 등록 성공",
  "eventPk": 123
}

 

 

 

9) 바깥 catch

JSON 변환 실패/파일 저장 실패 같은 예외 → 400 BAD_REQUEST