본문 바로가기
대외활동/UMC 8기

너디너리 해커톤 O조라고~ 냉집사! cors의 지옥편

by 피스타0204 2025. 5. 18.

 

안녕하세요 이번에는 1박2일 해커톤을 처음 참여해보게 되었는데요.

열심히 몬스터를 마시면서 아이디어 1시간만에 낸 기획 예진님 최고! 쏘큣 쏘쿨~ 고퀄의 디자인을 뚝딱 만들어낸 디자인 한솔님! erd작성, foodRegister api와 food inquiry api 를 작성한 큐티 mz 리치님~! 백엔드 최고! jwt와 유저 정보를 맡아 너무 고생하시고ㅠㅠ정말 열심히 힘내주신 sunset님! 

엄청난 속도로 작업해주신 뚜루미님! 민수님!

 

너무너무 즐거웠습니다~

 

 


배운 것)

jwt과 interupt에 대해

~인증인가가 필요한 타이밍에 interupt를 일으키는 handler를 통해 유지보수를 간편이 하고 보안을 향상시킵니다. 

 

resource의 파일 불러오기

package nerdinary.hackathon.domain.rate.rateService;

import static nerdinary.hackathon.global.exception.ErrorCode.*;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import nerdinary.hackathon.domain.food.repository.FoodRegisterRepository;
import nerdinary.hackathon.domain.rate.dto.RateResponse;
import nerdinary.hackathon.domain.login.service.UserException;
import nerdinary.hackathon.domain.user.User;
import nerdinary.hackathon.domain.user.UserRepository;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.InputStream;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import org.springframework.core.io.Resource;

@Component
@RequiredArgsConstructor
public class RateService {

    private final UserRepository userRepository;
    private final FoodRegisterRepository foodRegisterRepository;

    // JSON 데이터를 담을 리스트 (캐싱용)
    private List<Map<String, Object>> rateDataList;

    // 초기화 시 JSON 파일을 읽어서 rateDataList에 저장
    @PostConstruct
    public void init() {
       try {
          ObjectMapper mapper = new ObjectMapper();

          // ClassPathResource를 사용해서 resources 폴더 내부 파일 읽기
          Resource resource = new ClassPathResource("rateJson/rate-date.json");
          InputStream inputStream = resource.getInputStream();

          rateDataList = mapper.readValue(inputStream, new TypeReference<>() {});
          System.out.println("rate-data.json 로드 완료!");
       } catch (Exception e) {
          throw new RuntimeException("rate-data.json 파일 로드 실패", e);
       }
    }

    public RateResponse calculateUserRate(Long userId) {
       User user = userRepository.findById(userId)
             .orElseThrow(() -> new UserException(USER_NOT_FOUND));

       long usedCount = foodRegisterRepository.countByUserAndFoodStatus(user, "사용");
       long totalCount = foodRegisterRepository.countByUser(user);

       int consumptionRatePercent = 0;
       if (totalCount > 0) {
          consumptionRatePercent = (int) ((double) usedCount / totalCount * 100);
       }
       String consumptionRate = consumptionRatePercent + "%";

       // rateDataList에서 조건에 맞는 항목 찾기
       Map<String, Object> matchedRate = findMatchingRate(consumptionRatePercent);

       if (matchedRate == null) {
          // 조건에 맞는 데이터가 없으면 빈 값 반환
          return RateResponse.builder()
                .consumptionRate(consumptionRate)
                .fridgeComment("")
                .nearExpiredCount(0)
                .level(0)
                .typeName("")
                .foodBTI("")
                .foodBTIDetail("")
                .description("")
                .build();
       }

       // 각 필드 안전하게 추출
       String fridgeComment = (String) matchedRate.getOrDefault("fridgeComment", "");
       int level = (int) ((Number) matchedRate.getOrDefault("level", 0)).intValue();
       String typeName = (String) matchedRate.getOrDefault("typeName", "");
       String foodBTI = (String) matchedRate.getOrDefault("foodBTI", "");
       String foodBTIDetail = (String) matchedRate.getOrDefault("foodBTIDetail", "");
       String description = (String) matchedRate.getOrDefault("description", "");

       long expiredCount = foodRegisterRepository
             .countByUserAndExpirationDateBeforeAndFoodStatus(user, LocalDate.now(), "보관");

       return RateResponse.builder()
             .consumptionRate(consumptionRate)
             .fridgeComment(fridgeComment)
             .nearExpiredCount(expiredCount) // 현재 로직에 근거 없음
             .level(level)
             .typeName(typeName)
             .foodBTI(foodBTI)
             .foodBTIDetail(foodBTIDetail)
             .description(description)
             .build();
    }

    /**
     * consumptionRatePercent에 맞는 rateData 항목을 찾는 메서드
     */
    private Map<String, Object> findMatchingRate(int consumptionRatePercent) {
       for (Map<String, Object> rate : rateDataList) {
          Map<String, Object> conditions = (Map<String, Object>) rate.get("conditions");

          Integer min = (Integer) conditions.getOrDefault("consumptionRateMin", null);
          Integer max = (Integer) conditions.getOrDefault("consumptionRateMax", null);

          boolean minOk = min == null || consumptionRatePercent >= min;
          boolean maxOk = max == null || consumptionRatePercent <= max;

          if (minOk && maxOk) {
             return rate;
          }
       }
       return null;
    }
}

 


devOps만 열심히 한 것 같지만 같이 개발하고 역경을 헤쳐나가는 매우 즐거운 시간이었습니다. CORS오류가 해결되지 않았을 때는 식겁했지만 임기응변으로 어떻게 할 수 있어 다행이었씁니다.

같은 팀의 모두 덕분에 24시간만에 완성도 높은 작품을 만들어 낼 수 있었습니다. 모두 너무 너무 감사했습니다~~!!

 

https://github.com/orgs/nerdinary-hackathon-8th/repositories

 

nerdinary hackathon 8th

너디너리 해커톤 8th O조라고팀. nerdinary hackathon 8th has 2 repositories available. Follow their code on GitHub.

github.com