Skip to content

타인의 긍정적 평가로 나를 알아갈 수 있는 소셜 미디어 플랫폼, Whokie

Notifications You must be signed in to change notification settings

kakao-tech-campus-2nd-step3/Team6_BE

Repository files navigation

Whokie

"쿠키로 친구에게 칭찬을? 오직 Whokie에서!"


2.mp4

타인의 긍정적 평가로 나를 알아갈 수 있는 소셜 미디어 플랫폼

(혼자 사용하기에 사용 가능한 기능이 별로 없습니다! 최소 2명 이상이 테스트 하시길 권장드립니다!)

Whokie와 함께 진정한 나를 알아가보자!
작은 칭찬이 큰 변화를 만들어내는 긍정의 선순환을 경험해보세요🎀


목차

1. Whokie 소개

  1. 🔒 로그인 및 회원가입
    • 카카오 간편 로그인으로 간단하게 회원가입과 로그인이 가능해요
    • 내 카카오 프로필과 카카오 친구들을 불러올 수 있어요
  2. 🎫 마이페이지로 나를 보여줘요
    • 마이페이지는 로그인 하지 않은 사람들도 볼 수 있어요
  3. 🍪 쿠키 주기로 친구에게 칭찬을 해요
    • 어떤 친구에게 쿠키를 줄 지 모르겠다면 선택지를 Reload↻ 할 수 있어요
    • 질문에 적합한 친구가 없다면 질문을 Skip 할 수 있어요
  4. 🏅 친구에게 랭킹을 자랑해요
    • 내가 쿠키 받은 질문 Top3를 보여줄 수 있어요
    • 친구의 랭킹을 보면서 친구의 장점을 찾아봐요
  5. 💌 실시간 쿠키 알림
    • 쿠키를 받으면 실시간으로 나에게 알림이 와요
  6. 🔑 나에게 쿠키를 준 친구에 대한 힌트를 조회해요
    • 나에게 누가 쿠키를 줬는지 힌트를 볼 수 있어요
    • 힌트는 세 개 까지만 제공돼요
    • 정확한 이름과 정보는 알 수 없어요 힌트로 추측 해볼까요?
  7. 📣 프로필 질문으로 친구들에게 물어봐요
    • 친구들에게 물어보고 싶은 질문은 프로필 질문으로 질문해요
    • 친구들의 프로필 질문에 익명으로 재밌게 대답해요
  8. 🏫 그룹에서 친구들과 함께 즐겨요
    • 함께 쿠키 주기를 즐기고 싶은 친구들과 그룹을 만들어요
    • 그룹 랭킹을 보고 우리 그룹의 쿠키 왕을 노려봐요
  9. 💸 포인트를 차곡차곡 모아봐요
    • 친구에게 쿠키를 주면 포인트를 모을 수 있어요
    • 힌트가 빨리 보고 싶다면 1000원에 100포인트를 구매할 수 있어요

2. 프로젝트 소개

🚀 배포 링크

BE https://whokie.com/api/
FE https://whokie.com/

👋🏻 팀원 소개

BE

권다운 김건 신형진 유승욱
테크리더 리마인더 리액셔너 기획리더

FE

김아진 안희정 정솔빈
조장 테크리더 타임키퍼

🗓️ 개발 기간

  • 2024.08.19 ~ 2024.11.15

🏃 프로젝트 개요

  • “타인의 긍정적 평가로 나를 알아갈 수 있는 소셜 미디어 플랫폼”을 구현

저희 서비스의 기획 목적은 타인의 긍정적 평가와 피드백을 통해 사용자 스스로의 강점을 발견하고 자존감을 높이는 것입니다. 사람들이 자신의 매력을 객관적으로 확인할 수 있는 환경을 제공함으로써, 스스로에 대한 이해를 높이고 긍정적인 정체성을 형성하는 데 도움을 주고자 합니다. 또한. 커뮤니티의 응원과 긍정적 피드백을 통해 건강한 소셜 상호작용을 장려하여 사용자 간 긍정적인 영향력을 확산하는 것이 목표입니다.

3. 프로젝트 중점사항

Command, Model 패턴 적용을 통해 변화에 용이한 코드 구조 작성 image

문제점

  1. service 패키지에서 controller안에 있는 dto(request, response)를 알고 있음
  2. controller가 service를 알고 service또한 controller 패키지를 알게되는 상황 발생
  3. request가 client에서 controller로 데이터를 넘겨주는 역할과 controller에서 service로 데이터를 넘겨주는 역할 2가지를 하게됨
  4. request나 response가 변할때 마다 service와 controller 두곳의 코드가 계속 변경됨

실제 상황

  1. front에서 request를 변경해달라는 요청
  2. request를 변경하는 순간 controller 뿐만 아니라 service 코드가 변경
  3. service관련 test 코드 까지 바꿔야 하는 상황 발생

해결방안

  1. service 와 controller 사이에 dto 제작
  2. service 패키지에 dto(command,model) 제작

기대 효과

  1. service가 controller 패키지를 모르게 구현 가능
  2. request, response가 변화할시 controller단 안에서 만 수정하고 service코드는 수정이 필요없음
  3. 변화에 유연하게 대처 가능
Service Layer 분리를 통해 순환 참조 가능성 제거 image

문제점

  1. AService에서 BService가 만든 메서드가 필요한 상황 발생
  2. AService에서 BService의 메서드를 사용하면 순환참조 오류 발생가능성 발생
  3. Service가 Reader,Writer에 관련된 모든 메서드를 갖고있으니 service가 비대해지는 문제 발생
  4. 프로젝트가 커질수록 service가 다른 service를 알면 편해지는 로직이 증가함

해결방안

  1. service를 Reader,Writer Service로 분리
  2. MainService를 두어 MainService는 Reader와 Writer Service를 통해서만 로직 구현
  3. Reader,Writer Service는 본인과 관련된 도메인 repository만 참조

기대효과

  1. ReaderService와 WriterService를 통해 데이터 편집과 관련된 로직을 분리하여 구현
  2. MainService에서 다른 ReaderService,WriterService를 자유롭게 참조해도 순환참조 오류를 방지할 수 있음
  3. MainService의 코드 비대화 문제 해결
  4. 코드 리팩토링시 다른 팀원이 짠 Reader나 Writer Service를 관리하기 편하고 사용할 때 가독성이 좋아짐
nGrinder를 활용한 API별 TPS, MTT 관리 https://velog.io/@momnpa333/%EC%84%B1%EB%8A%A5%ED%85%8C%EC%8A%A4%ED%8A%B8-ngrinder-%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0

실행하고 설치하는 과정은 위에 글을 작성하였다.

도입 계기

프로젝트를 진행하면서 프론트가 실수로 한번에 요청을 몇천번 보내는 일이 잦았고, 그로인해 서버의 cpu가 100퍼를 찍으며 죽는 일이 자주 발생하였다. 서버에 많은 요청이 들어와도 안정성을 높이기 위해 성능테스트가 필요하다고 판단 하였고 ngrinder를 이용하여 성능테스트를 진행하기로 했다.

기대 효과

해당 api별 성능테스트를 통해 어떤 작업에서 리소스를 많이 쓰는지 확인하고, 리소스를 많이 쓰는 작업을 리팩토링 하여 성능을 끌어올렸다. image

예를 들어 image를 저장하는 api의 경우

TPS(초당 처리량)(14.4→20.9)

MTT(응답 대기 시간)(274.9ms→186.5ms)로 성능을 개선함

단위 테스트 및 통합 테스트 작성

문제사항

  1. 4명이서 프로젝트를 진행하다 보니, 남이 짠 코드가 제대로 돌아가는지 매번확인해야할 필요성이 생겼다.
  2. 리팩토링을 하는 과정에서 리팩토링 후에도 제대로 코드가 돌아가는지 확인하는 작업이 필요했다.

해결방안

  1. 테스트 코드를 만들어 안정성을 증가시킨다.

테스트 커버리지를 54%로 끌어올렸다. 가장 많이 쓰이고 있는 api인 answer와 profileanswer의 경우 메서드 기준으로 96%, 100%로 올려 안정성을 더했다. image

PR/머지 시 자동화된 테스트를 통한 코드 안정성 증가

(https://velog.io/@momnpa333/github-actionsspring-test-%EC%9E%90%EB%8F%99%ED%99%94)

도입 계기

프로젝트 테크 리더를 수행하면서 팀원들의 코드를 머지하고, 리뷰하는 일이 많아졌다. issue 브랜치를 week별 브랜치에 머지할때 실제로 팀원들의 코드가 제대로 동작하는지 일일이 빌드하기에는 시간요소가 많이 들었다. 또한 테스트 코드가 터지는지 일일이 돌리는 것도 시간이 너무 많이 들어서 해결책을 찾아야 했다.

해결책

github-actions를 통해 배포자동화 수행

기대 효과

테스트를 자동화하면서 팀원간 협업할 때 코드리뷰와 머지할때 드는 리소스를 상당부분 줄일 수 있었다. 특히 팀원의 코드가 제대로 작성이 된것인지 바로바로 알기때문에 안정성 면에서 효과를 많이 보았던 것 같다.

테스트 결과를 PR에 코멘트로 등록 image

테스트 실패 시, 실패한 코드 라인에 Check 코멘트를 등록 image image

테스트가 실패할 시 실패한 코드 라인에 check 코멘트를 달아줆으로서 pr을 날릴때 바로 피드백을 받아볼수 있게 작성

Nginx의 Reversed-Proxy, Certbot을 활용하여 SSL 인증서 관리 관련 이슈: (https://velog.io/@momnpa333/https-nginx-spring-s3-docker-로-배포하기)
JWT기반 로그인 인증/인가

인증: 사용자가 로그인할 때 JWT ACCESS TOKEN을 생성하여 사용자 정보를 담고, 이를 통해 인증 상태를 유지한다. 이후 요청 시 생성된 ACCESS TOKEN은 Authorization 헤더에 BEARER 토큰 형식으로 전달된다. 인터셉터를 통해 인증이 필요한 요청이 들어올 경우, ACCESS TOKEN을 확인하여 유효한 사용자임을 검증한다.

인가: 관리자 페이지 접근 시 JWT 토큰의 Role 정보를 확인하여 ADMIN 권한을 가진 사용자만 접근할 수 있도록 인가 처리를 적용함. 이를 통해 권한이 없는 사용자가 관리자 페이지에 접근하는 것을 방지함.

N+1 문제 해결 통한 조회 성능 최적화

1. 페이징 n+1 문제 해결 이슈

관련 이슈 : https://geonit.tistory.com/71

문제 상황

  • N+1 문제는 연관 관계가 있는 엔티티를 조회할 때 발생하는 성능 이슈
  • Page 형태로 반환하는 레포지토리 메서드에서 Fetch Join 사용 시, JPA가 메모리에서 페이징을 처리하여 메모리 과부화 현상 발생 위험

해결 방안

  • 페이징이 필요한 조회 메서드에 @EntityGraph를 적용
  • 메모리 효율성을 유지하면서도 N+1 문제를 해결하여 쿼리 성능 개선
  • JPA가 메모리에서 페이징 처리하는 것을 방지하고 DB 단에서 페이징이 처리되도록 개선

EntityGraph 성능 테스트 분석

초기 가설 EntityGraph 적용이 성능 향상에 도움이 될 것이라 예상

    @EntityGraph(attributePaths = {"picked"})
    @Query("SELECT p FROM Answer p WHERE p.picked = :user AND p.createdAt BETWEEN :startDate AND :endDate ORDER BY p.createdAt DESC")
    Page<Answer> findAllByPickedAndCreatedAtBetween(Pageable pageable, @Param("user") Users user, @Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate);

테스트 환경

  • Answer 데이터 수: 5,000개
  • 페이지 size: 3,000개
  • 측정 지표: TPS, MTT

테스트 결과

  1. 일반 Join
image
  • TPS(초당 처리량)c: 21.7/sec
  • MTT(하나 처리하는 데 걸리는 시간): 441.3ms
  1. EntityGraph 적용
image
  • TPS(초당 처리량): 15.1/sec (일반 Join보다 약 30% 성능 저하 발생)
  • MTT(하나 처리하는 데 걸리는 시간): 691.1ms(일반 Join보다 약 57% 성능 저하 발생)

원인 분석

   @Transactional(readOnly = true)
    public Page<AnswerModel.Record> getAnswerRecord(Pageable pageable, Long userId, LocalDate date) {
        Users user = userReaderService.getUserById(userId);

        LocalDateTime startDate;
        LocalDateTime endDate;

        if (date == null) {
            startDate = AnswerConstants.DEFAULT_START_DATE;
            endDate = LocalDateTime.now();
        } else {
            startDate = date.withDayOfMonth(1).atStartOfDay();
            endDate = date.withDayOfMonth(date.lengthOfMonth()).atTime(LocalTime.MAX);
        }

        // 지정된 기간 내의 데이터를 조회
        Page<Answer> answers = answerReaderService.getAnswerList(pageable, user, startDate, endDate);
        return answers.map(AnswerModel.Record::from);
    }
  1. @Transactional(readOnly = true) 적용으로 영속성 컨텍스트 유지
  2. 조회 대상 user가 트랜잭션 내에서 고정값으로 사용
  3. EntityGraph로 인한 불필요한 JOIN 연산 발생으로 오히려 성능이 더 느린 현상이 발생한 것으로 예상

결론 영속성 컨텍스트에 캐시된 데이터를 사용하는 상황에서는 EntityGraph 적용이 오히려 성능 저하를 초래할 수 있음을 확인함. 이는 불필요한 JOIN 연산이 추가되기 때문으로 예상함.

  1. 실제 Fetch Join 성능 테스트 이슈

"Fetch Join이 실제로 성능에 얼마나 영향을 미칠까?"라는 궁금증에서 시작한 성능 테스트를 진행하였음. 그룹 멤버 조회 API를 대상으로 일반 Join과 Fetch Join의 성능 차이를 비교 분석했음.

예상 시나리오 (그룹 멤버 10명 기준)

  • 일반 Join: 1(그룹 조회) + 10(멤버별 user 조회) = 총 11번의 쿼리 실행 예상
  • Fetch Join: 단 1번의 쿼리로 모든 데이터 조회 가능

테스트 대상 쿼리

  • 그룹 내 멤버 조회 API (그룹 ID로 멤버 목록 조회)
    @Query("SELECT g FROM GroupMember g JOIN FETCH g.user WHERE g.user.id != :userId AND g.group.id = :groupId")
    List<GroupMember> getGroupMemberJoinFetch(@Param("userId") Long userId, @Param("groupId") Long groupId);
  • user 엔티티와의 연관 관계에서 N+1 문제 발생

성능 테스트 결과

  1. 일반 Join 사용 시
image
  • TPS(초당 처리량): 98.0/sec
  • MTT(하나 처리하는데 걸리는 시간) : 1035.2ms
  • N+1 문제로 인한 추가 쿼리 발생
  1. Fetch Join 적용 시
image
  • TPS(초당 처리량): 282.0/sec(일반 Join에 비해 약 288% 성능 향상)
  • MTT(하나 처리하는데 걸리는 시간) : 345.2ms (일반 Join에 비해 약 300% 시간 감축)
  • 단일 쿼리로 데이터 조회 완료

결론: 실제 테스트 결과, Fetch Join 적용으로 N+1 문제를 해결하여 약 2.9배의 성능 향상을 확인할 수 있었음. 이를 통해 Fetch Join이 실제 서비스에서도 상당한 성능 개선 효과를 가져올 수 있음을 예상함.

관리자 페이지를 통한 사용자, 그룹, 질문 등의 효율적 데이터 관리 image

문제 상황

  • 프론트엔드에서 DB를 직접 조작하는 안티패턴 발생
  • 프론트엔드에서 DB를 직접 알게 되는 문제점 발생

해결 방안

  • 관리자 전용 페이지를 구현하여 DB 조작을 백엔드단에서 처리하는 것으로 제한
  • 관리자 페이지에서 공통 질문을 직접 등록/수정할 수 있는 인터페이스 제공
  • 사용자, 그룹, 유저 목록을 한눈에 볼 수 있는 대시보드 형태의 UI 구현으로 효율적인 데이터 관리 가능
카카오페이 api를 사용하여 결제 기능 구현 관련 이슈 링크 : https://geonit.tistory.com/72

문제 상황

  • 포인트 결제을 위한 간단하고 쉽게 접근할 수 있는 사용자 친화적인 결제 시스템 필요

해결 방안

  • QR코드나 전화번호로 간편하게 결제할 수 있는 카카오페이 API 도입
  • https://developers.kakaopay.com/ 카카오페이 디벨로퍼스 공식 문서를 참조하여 단건 결제 기능을 구현
Redis를 활용하여 조회 성능 최적화 및 DB 접근 최소화

문제점

  • 중복 방문자를 방지하기 위해 프로필 조회 요청마다 방문자 테이블을 조회한다.
  • 이로 인해 RDBMS 디스크에 대한 많은 I/O로 오버헤드 증가 → 응답 속도 저하 및 DB 과부하 발생

해결방안

  • 방문자수 및 방문자 정보를 Redis에서 관리
  • 방문자 정보 데이터를 Redis에서 DB로 하루에 1번 saveAll()
image

기대 효과

  • 메모리 기반의 Redis의 사용으로 응답 속도 개선
  • 방문자 관련 데이터를 Redis에서 관리함으로써 DB 접근 최소화하여 DB에 걸리는 부하 감소

테스트 결과 - 148개의 스레드에서 1분간 Get 요청

  • Redis 미적용
image image
  • TPS(초당 처리량): 107.0/sec
  • MTT(하나 처리하는데 걸리는 시간) : 1364.8ms
  • 요청마다 방문자 정보 조회, 입력, 방문자수 업데이트를 위해 DB에 I/O 발생
  • Redis 적용
image image
  • TPS(초당 처리량): 178.5/sec(Redis 적용 전에 비해 약 66.3% 성능 향상)
  • MTT(하나 처리하는데 걸리는 시간) : 826.0ms (Redis 적용 전에 비해 약 40% 시간 감축)
  • 메모리 기반의 Redis 사용으로 응답 속도 상승

결론: 실제 테스트 결과, Redis 적용으로 응답 속도를 약 1.66배의 성능 향상을 확인할 수 있었음. 이를 통해 메모리 기반의 Redis가 DB 디스크 기반의 MySQL(RDBMS)에 비해 응답 속도를 개선할 수 있음을 확인할 수 있었다.

테스트를 진행할 때 요청을 보내는 스레드를 증가시켜도 실행된 테스트(Executed Tests) 차이가 크지 않은 것을 확인하였다.

해당 요청은 RedissonLock으로 방문자수 증가 시 동시성을 제어하기 때문에 무분별한 DB 접근이 제한되는 것으로 보인다.

SSE를 활용하여 클라이언트에게 실시간 알림 이벤트 발행 관련 이슈: https://velog.io/@hjinshin/웹-알림-구현

이슈 요약

  • 핵심 서비스인 칭찬을 받았을 경우 대상자에게 실시간으로 전달하기를 희망
  • polling, websocket, webhook과 비교하였을 때 SSE가 서버에서 발생하는 이벤트를 클라이언트에게 전달하기에 적합하다고 판단
    • polling: 지속적인 요청으로 서버 리소스 낭비
    • websocket: 단방향 통신만 필요하기에 양방향 통신을 지원하는 websocket은 부적합
    • webhook: 서버에서 post 요청을 보내기 때문에 클라이언트에 endpoint가 존재해야함
비동기 처리를 통해 응답 속도 향상

문제사항: 프로필 배경 이미지를 S3에 업로드하는 과정에서 업로드 시간이 길어지면서 전체 API 응답이 지연됨. 사용자가 프로필 배경을 수정할 때마다 이미지 업로드 시간이 API 응답 시간에 그대로 반영되어 사용자 경험이 저하되는 문제가 발생함

해결 방안: 이미지 업로드 작업을 @Async 어노테이션을 사용해 비동기 처리하도록 구현하여, API의 주요 로직이 완료된 후에도 이미지 업로드가 백그라운드에서 진행되도록 함. 이를 통해 사용자 요청에 대한 API 응답 속도가 향상되며, 업로드 과정이 API 응답을 지연시키지 않도록 하여 보다 빠른 사용자 경험을 제공함.

관련 이슈: https://yso8296.tistory.com/28

image image

image를 저장하는 api의 경우(약 1000번 요청 시도)

TPS(초당 처리량)(14.4→20.9) : 비동기를 적용하기 전 TPS의 경우 14.4의 측정치를 보여주었음. 이후 비동기 방식을 적용하여 TPS를 20.9로 향상시킴

MTT(응답 대기 시간)(274.9ms→186.5ms): 비동기를 적용하기 전 약 274.9ms가 응답시간이 걸리던 api 요청을 비동기 방식을 적용하여 186.5ms 로 응답시간을 감소시킴.

Redisson Lock 및 동시성 제어 **문제사항:** 여러 사용자가 동시에 프로필을 조회할 경우, 일일 방문자 수와 총 방문자 수 증가 로직에서 동시성 문제가 발생하여 조회수가 정확하게 반영되지 않는 문제가 발생함. 이로 인해 실제 방문자 수와 조회된 방문자 수 간의 불일치가 발생함.

해결 방안: Redisson 분산 락을 활용하여 다수의 사용자가 동시에 프로필을 조회할 때도 정확한 조회수 증가가 이루어지도록 동시성 제어를 적용함. 이를 통해 각 조회 요청에 대해 일관된 방문자 수가 반영되며, 조회수 기록의 정확성을 보장할 수 있게 됨.

관련 이슈: https://yso8296.tistory.com/29

동시성 테스트 결과:

동시성 테스트 코드 - 100개의 스레드를 만든 후 조회수 증가 로직에 대한 동시 접근 테스트

@Test
    @DisplayName("동시 방문자 수 증가 테스트")
    void visitProfileConcurrentlyTest() throws InterruptedException {
        // given
        RedisVisitCount redisVisitCount = createVisitCount();
        Long hostId = redisVisitCount.getHostId();
        String visitorIp = "visitorIp";
        int oldDailyVisited = redisVisitCount.getDailyVisited();
        int oldTotalVisited = redisVisitCount.getTotalVisited();

        int threadCount = 100; // 스레드 개수
        ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
        CountDownLatch latch = new CountDownLatch(threadCount);

        // when
        for (int i = 0; i < threadCount; i++) {
            int finalI = i;
            executorService.submit(() -> {
                try {
                    redisVisitService.visitProfile(hostId, visitorIp + finalI);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    latch.countDown();
                }
            });
        }
        latch.await();
        executorService.shutdown();

        // then
        RedisVisitCount actual = redisVisitCountRepository.findById(hostId).orElseThrow();

        assertAll(
            () -> assertThat(actual.getDailyVisited()).isEqualTo(oldDailyVisited + threadCount),
            () -> assertThat(actual.getTotalVisited()).isEqualTo(oldTotalVisited + threadCount)
        );
    }
  1. RedissonLock을 적용한 경우
 @RedissonLock(value = "#hostId")
    public RedisVisitCount visitProfile(Long hostId, String visitorIp) {
        RedisVisitCount redisVisitCount = findVisitCountByHostId(hostId);
        log.info("visitorIp: {}", visitorIp);
        if(!checkVisited(hostId, visitorIp)) {
            redisVisitCount.visit();
            redisVisitCountRepository.save(redisVisitCount);
        }
        // 방문자 로그 기록
        saveVisitor(hostId, visitorIp);

        return redisVisitCount;
    }
image

예상 결과: 일일 방문자 수: 100, 총 방문자 수: 110

실제 결과: 일일 방문자 수: 100, 총 방문자 수: 110

테스트 결과:

여러 사용자가 동시에 프로필에 접근하는 상황을 시뮬레이션한 후, Redisson 분산 락을 이용해 동시성 제어를 적용한 결과, 예상한 대로 일일 방문자 수와 총 방문자 수가 각각 100씩 증가하는 것을 확인할 수 있다. 이를 통해 동시 접근 상황에서도 방문자 수 증가 로직이 안정적으로 작동함을 확인할 수 있었습니다.

  1. RedissonLock을 적용하지 않은 경우
public RedisVisitCount visitProfile(Long hostId, String visitorIp) {
        RedisVisitCount redisVisitCount = findVisitCountByHostId(hostId);
        log.info("visitorIp: {}", visitorIp);
        if(!checkVisited(hostId, visitorIp)) {
            redisVisitCount.visit();
            redisVisitCountRepository.save(redisVisitCount);
        }
        // 방문자 로그 기록
        saveVisitor(hostId, visitorIp);

        return redisVisitCount;
    }
image image

예상 결과: 일일 방문자 수: 100, 총 방문자 수: 110

실제 결과: 일일 방문자 수: 10, 총 방문자 수: 20

테스트 결과:

여러 사용자가 동시에 프로필에 접근하는 상황을 시뮬레이션한 결과, 일부 요청이 누락되거나 중복 처리되어 조회수 증가가 정확히 반영되지 않은 모습을 확인할 수 있다. 이를 통해 현재의 RedissonLock을 적용하지 않은 경우 다수의 동시 접근 상황에서 기대한 만큼 안정적으로 작동하지 않음을 확인할 수 있다.

보안을 위해 CORS 막아두기

CORS 에러가 발생하는 경우를 알아보자.

  1. 출처가 다른 도메인 또는 포트로 리소스를 요청할 때 CORS 정책에 따라, 출처가 다른 도메인이나 포트에서 리소스를 요청하는 경우에는 브라우저에서 CORS 에러가 발생한다. 이 경우에는 서버 측에서 Access-Control-Allow-Origin 헤더를 설정하여 요청을 허용해야 한다.

  2. HTTPS에서 HTTP로 리소스를 요청할 때 보안상의 이유로 HTTPS에서 HTTP로 리소스를 요청하는 경우에도 CORS 에러가 발생할 수 있다. 이 경우에는 HTTPS로 통신하는 서버에서 HTTP로 요청을 전달하는 것이 아니라, HTTPS로 전달해야 한다.

CORS를 열게되면 다른 도메인에서 서버에 요청을 보낼 때에도 응답을 하게 된다. 보안상 CORS를 닫아두는 것이 좋기 때문에 domain을 통합하여 CORS을 닫았다. 다만 dev 서버에서는 프론트와의 원활한 협업을 위해 열어 두었다.

@Configuration
@RequiredArgsConstructor
public class WebMvcConfig implements WebMvcConfigurer {

    private final JwtProvider jwtProvider;

    @Bean
    @Order(1)
    public JwtInterceptor jwtInterceptor() {
        return new JwtInterceptor(jwtProvider);
    }

    @Bean
    @Order(2)
    public VisitorInterceptor visitorInterceptor() {
        return new VisitorInterceptor();
    }

    @Bean
    @Order(3)
    public AdminInterceptor adminInterceptor() {
        return new AdminInterceptor(jwtProvider);
    }

    @Bean
    public LoginUserArgumentResolver loginUserArgumentResolver() {
        return new LoginUserArgumentResolver();
    }

    @Bean
    public VisitorArgumentResolver visitorArgumentResolver() {
        return new VisitorArgumentResolver();
    }

    @Bean
    public TempUserArgumentResolver tempUserArgumentResolver() {
        return new TempUserArgumentResolver();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jwtInterceptor())
            .addPathPatterns("/api/**");
        registry.addInterceptor(visitorInterceptor())
            .addPathPatterns("/api/profile/**");
        registry.addInterceptor(adminInterceptor())
            .addPathPatterns("/admin/**", "/api/admin/**");
    }

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(loginUserArgumentResolver());
        resolvers.add(visitorArgumentResolver());
        resolvers.add(tempUserArgumentResolver());

    }

//    @Override
//    public void addCorsMappings(CorsRegistry registry) {
//        registry.addMapping("/**")
//                .allowedOriginPatterns("*")
//                .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")
//                .allowedHeaders("Authorization", "Content-Type")
//                .allowCredentials(true)
//                .exposedHeaders("Authorization")
//                .maxAge(3600);
//    }
}

4. 프로젝트 이슈

5. 프로젝트 구성

🛠️ 기술 스택

BE

제목 없는 다이어그램 drawio (3) (1)

Java v21 MYSQL v8.0 Spring v3.3.3 Docker v27.3.1 Redis v7.1.0 h2 v2.2.224 redisson v3.33.0 nginx v1.24.0 nGrinder v3.5.5

🏛️ 아키텍처

image (33) (1)

💾 ERD

image (32)


6. 개발 문화

📷 팀 미팅

image image image

7. Whokie를 자세히 알고 싶다면..

About

타인의 긍정적 평가로 나를 알아갈 수 있는 소셜 미디어 플랫폼, Whokie

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published