Skip to content

놀 줄 아는 노인들을 위한 만남 서비스 경로당

Notifications You must be signed in to change notification settings

prgrms-be-devcourse/NBE2_2_Team01

Repository files navigation

🏡 경로당

경로당은 고령층을 위한 커뮤니티 플랫폼으로, 사용자가 집에서 편하게 동네 친구를 사귀고 자유롭게 소통할 수 있는 SNS 및 채팅 서비스를 제공합니다. MZ세대의 대표 랜덤 채팅 서비스인 **아자르(Azar)**와 고령층 대상 커뮤니티 앱 **시놀(Sinol)**을 벤치마킹하여, 보다 간편한 사용자 인터페이스와 시니어를 위한 맞춤형 기능을 구현하였습니다.


📌 주요 기능 및 특징

🎙️ 오픈 및 화상 채팅 기능

  • 랜덤 오픈 채팅: 다양한 사람들과 랜덤으로 소통할 수 있는 기능
  • 화상 채팅: 영상 통화를 통해 더욱 깊이 있는 소통 경험 제공

🏘️ 동네 친구 모집 커뮤니티 '경로당'

  • 커뮤니티 생성 및 참여: 사용자가 동네 커뮤니티를 쉽게 만들고 모집할 수 있는 기능
  • 실시간 알람 서비스:

⏰ 커스텀 알람 서비스

  • 사용자 정의 알람 설정: 채팅 알람 및 커뮤니티 알림을 사용자가 원하는 대로 설정 가능

📋 설계도

자세히보기

-작성


📚 사용 기술 스택 (Tech Stack)

🛠 Backend

  • Spring Boot Scheduler: 동적 스케줄링 기능 구현

Database

middleware

  • JWT: 인증 및 권한 부여

🎨 Frontend


💻 개발 환경 및 배포

메인 개발 툴

버전 관리 및 소스 코드 저장소


📡 클라이언트 통신

Redis

🗣 커뮤니케이션

📌 팀원 소개

채호정 최근태 도승우 한승희 김강민
채호정 프로필 사진 최근태 프로필 사진 도승우 프로필 사진 한승희 프로필 사진 김강민 프로필 사진
BE 팀장
오픈 채팅
BE
랜덤 화상채팅
1대1 화상채팅
BE
커스텀 알람
게시글/좋아요 알람
BE 깃 관리자
게시판
댓글
마이페이지
BE
OAuth2 로그인
JWT토큰
로그인/회원가입

역할 소개


주요 기능 에러 사항 및 진행 상황

📋 더 자세히 보기

-작성

📋 커뮤니티 서비스

🔍 미리 보기

회원가입

image

  • http://localhost:8080/signup 회원가입 필요
  • blogdb의 users테이블에 OAuth 이메일과 비밀번호가 있어야 다음이 진행됨-> 디비에 레코드가 없어도 돌아가는 방법이 없나?

OAuth로 로그인

image

구글 로그인하기

image

  • users테이블에 있는 구글 이메일로 로그인

image

  • 비밀번호 입력
  • 이때 db에서 비밀번호는 암호화되어 있음

image

  • 계속

image

  • 글 목록

image

  • 글 등록 가능

image

  • 자신이 작성한(같은 이메일) 글이면 수정, 삭제 가능

image

  • 다른 사용자가 작성한 글은 수정, 삭제 불가 ->다른 사용자와 본인을 구분해서 다른 사용자의 글은 수정, 삭제 버튼이 안보이게 할 순 없을까

카카오톡 로그인하기

image

  • users테이블에 있는 카카오톡 이메일로 로그인
  • 이후 구글 로그인과 똑같이 됨

페이징

image

  • 최대 10페이지가 보이게 만듦 (예시를 위해 게시글을 1개씩 보이게 했으나 실제로는 최대 10개가 보임)
  • '다음' 버튼은 아직 표시되지 않은 다음 페이지 그룹이 있을 때만 보임(활성화)
  • '이전' 버튼은 이전 페이지 그룹이 10 이상일 때만 보임(활성화)

image

  • '다음' 버튼을 클릭면 11페이지로 이동함

image

  • 11페이지에서 '다음' 버튼을클릭하면 21페이지로 이동함
  • 표시되지 않은 다음 페이지 그룹이 없으므로 '다음' 버튼이 보이지 않음

image

  • 24페이지에서 '이전' 버튼 클릭하면 20페이지로 이동

image

  • 20페이지에서 '이전' 보튼 클릭하면 10페이지로 이동
  • 표시되지 않은 이전 페이지 그룹이 없으므로 '이전' 버튼이 보이지 않음

문제상황

  1. nickname 컬럼명이 겹치면 안됨
    • 만약에 사용자가 구글, 카카오톡 로그인을 시도할때 이름(ex. 홍길동)이 같으므로 nickname에 본인 이름이 들어간다.
 //OAuth관련키 저장
   @Column(name="nickname",unique = true)
   private String nickname;

   //생성자에 nickname 추가
   @Builder
   public User(String email, String password,String nickname) {
       this.email = email;
       this.password = password;
       this.nickname = nickname;
   }

   //사용자 이름 변경
   public User update(String nickname) {
       this.nickname = nickname;
       return this;
   }
  • 위 코드는 User 클래스 일부분
 private User savedOrUpdate(OAuth2User oAuth2User) {
        Map<String, Object> attributes = oAuth2User.getAttributes(); //OAuth2 사용자 정보(email, name 등)를 속성 맵으로 가져온다.

        // OAuth2User의 속성 출력
        String email;
        String name;

        if (attributes.containsKey("kakao_account")) {
            email = (String) ((Map<String, Object>) attributes.get("kakao_account")).get("email");
            System.out.println("Email from Kakao: " + email);
            name = (String) ((Map<String, Object>) attributes.get("properties")).get("nickname");

        } else {
            // 구글 사용자 정보 처리
            email = (String) attributes.get("email");
            name = (String) attributes.get("name");
        }

        User user = userRepository.findByEmail(email)
                .map(entity -> {
                    // 로그 추가
                    System.out.println("User found, updating: " + entity);
                    return entity.update(name);
                })
                ...
;
        return userRepository.save(user); //새로운 사용자 정보를 저장하거나 업데이트된 사용자 정보를 저장한다.
    }
  • 위 코드는 Oauth2UserCustomService 클래스 일부분

image

  • 같은 nickname으로 로그인시 에러

  • 결론: 같은 사용자가 구글계정에서 카카오톡 계정으로 로그인하기 위해 구글 계정 로그아웃할때, nickname이 본인 이름이 아닌 null로 업데이트되게 만들고 싶음(실패)


  1. blogdb의 users테이블에 OAuth 이메일과 비밀번호가 있어야함
  • 디비에 없는 계정으로 구글 로그인 시,이메일은 삽입되나 비밀번호가 디비에 삽입이 안됨
  • 이거 어짜피 구글 로그인만 성공해야 글을 작성,수정,삭제할 수 있으니까 신경 안써도 될까? (몰라)

  1. 로그아웃하면 세션 및 쿠키 삭제, 토큰 무효화되어야 함
    • 로그아웃되도 직전 로그인방식으로 들어가면 토큰이 살아있음...
        http.logout(logout -> logout
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET"))
                .addLogoutHandler(new CustomLogoutHandler(oAuth2AuthorizationRequestBasedOnCookieRepository()))
                .logoutSuccessUrl("/login"));
  • 위 코드는 WebOAuthSecurityConfig클래스 일부분
@Override
    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        HttpSession session = request.getSession(false);
        if (session != null) {
            session.invalidate(); // 세션 무효화
            System.out.println("Session invalidated.");
        }
        authorizationRequestRepository.removeAuthorizationRequestCookies(request, response);
        System.out.println("Authorization request cookies removed.");
        if (authentication != null) {
            SecurityContextHolder.clearContext();
            System.out.println("Security context cleared.");
        }
    }
  • 위 코드는 CustomLogoutHandler 클래스 일부분

  • 결론:

  • 크롬 설정-> 개인 정보 보호 및 보안 -> 인터넷 사용 기록 삭제를 해야지만 토큰이 사라짐

  • 로그아웃만으로 토큰 무효화, 쿠키 및 세션 삭제하고 싶다....


  1. 일반 로그인으로 로그인하면 글 등록,수정,삭제가 안됨
  • OAuthAndSessionSecurityConfig 클래스를 주석 해제하고 WebOAuthSecurityConfig 주석처리 후
<div class = "mb-4">
                    <form action="/login" method="POST">
                        <input type="hidden" th:name="${_csrf?.parameterName}" th:value="${_csrf?.token}" />
                        <div class="mb-3">
                            <label class="form-label text-white">Email address</label>
                            <input type="email" class="form-control" name="username">
                        </div>
                        <div class="mb-3">
                            <label class="form-label text-white">Password</label>
                            <input type="password" class="form-control" name="password">
                        </div>
                        <button type="submit" class="btn btn-primary">Submit</button>
                    </form>

                    <button type="button" class="btn btn-secondary mt-3" onclick="location.href='/signup'">회원가입</button>
                </div>
  • oauth.html에 위 코드 주석 해제 image

  • 일반 로그인을 하면 글 목록이 보이나 글 등록, 수정,삭제를 할 수 없음

  • 결론: 일반 로그인도 액세스 토큰, 리프레시 토큰을 발급하게 만들고 싶음(실패)


해결상황

페이징 넘버

image

  • 2페이지를 클릭해도 http://localhost:8080/articles?page=1으로 로딩됨 image

  • 1페이지를 클릭하면 에러 발생

  • 초기 articleList.html 일부 코드

    <ul class="pagination justify-content-center">
        <!-- 이전 버튼 -->
        <li class="page-item" th:classappend="${page.hasPrevious()} ? '' : 'disabled'">
            <a class="page-link" th:href="@{/articles(page=${page.number > 0 ? page.number - 1 : 0}, size=${page.size})}" tabindex="-1">이전</a>
        </li>
    
        <!-- 페이지 번호 반복 -->
        <li class="page-item" th:each="i : ${#numbers.sequence(1, page.totalPages)}"
            th:classappend="${page.number + 1 == i} ? 'active' : ''">
            <a class="page-link" th:href="@{/articles(page=${i - 1}, size=${page.size})}"
               th:text="${i}"></a>
        </li>
    
        <!-- 다음 버튼 -->
        <li class="page-item" th:classappend="${page.hasNext()} ? '' : 'disabled'">
            <a class="page-link" th:href="@{/articles(page=${page.number + 1}, size=${page.size})}">다음</a>
        </li>
    </ul>
    
  1. Spring Data JPA의 페이지 처리
    • Spring Data JPA에서 PageRequest.of(page, size) 메서드를 통해 페이지 요청을 생성할 때, 첫 번째 페이지를 0으로 처리한다.
    • 따라서 사용자가 입력하는 페이지 번호와 실제 페이지 번호 간의 불일치가 발생함
  2. Thymeleaf 템플릿 코드의 오류
    • 페이지 번호 반복 처리에서 인덱스가 잘못 계산되어 페이지 번호가 올바르게 표시되지 않는다.
  3. URL 파라미터 전달
    • 링크에서 페이지 번호를 0으로 설정하는 경우(예: 이전 버튼 클릭 시) page=-1 또는 page=0으로 잘못 전달되어 오류를 유발했다.
  4. 첫번째 페이지 그룹에서의 '이전'버튼과 마지막 페이지 그룹에서의 '다음' 버튼은 비활성화가 됐지만 계속 보인다.
  • 수정된 코드 image

     <nav aria-label="Page navigation">
          <ul class="pagination justify-content-center">
    
              <!-- 이전 그룹 버튼 -->
              <li class="page-item" th:classappend="${page.number >= 10} ? '' : 'd-none'">
                  <a class="page-link" th:href="@{/articles(page=${(page.number/10)*10 }, size=${page.size})}" tabindex="-1">이전</a>
              </li>
    
              <!-- 페이지 번호 반복 -->
              <li class="page-item" th:each="i : ${#numbers.sequence((page.number/10)*10, ((page.number/10)*10 + 9) < page.totalPages ? (page.number/10)*10 + 9 : page.totalPages - 1)}"
                  th:classappend="${page.number == i} ? 'active' : ''">
                  <a class="page-link" th:href="@{/articles(page=${i+1}, size=${page.size})}" th:text="${i+1}"></a> <!-- 1-based 페이지 표시 -->
              </li>
    
              <!-- 다음 그룹 버튼 -->
              <li class="page-item" th:classappend="${(page.number/10)*10 + 10 < page.totalPages} ? '' : 'd-none'">
                  <a class="page-link" th:href="@{/articles(page=${(page.number/10)*10 + 11}, size=${page.size})}">다음</a>
              </li>
    
          </ul>
      </nav>
    
  • n페이지 http://localhost:8080/articles?page=n

  • 페이지네이션 로직을 수정하여 페이지 번호를 올바르게 계산하도록 함

  • 이전, 다음 버튼 클릭 시 올바른 페이지로 이동하도록 th:classappend와 href를 수정함

  • 'd-none' 사용하여 첫번째 페이지 그룹에서의 '이전'버튼과 마지막 페이지 그룹에서의 '다음' 버튼 보이지 않게 함(비활성화)

참고
<script>
    // 페이지 번호를 로그에 출력
    document.addEventListener('DOMContentLoaded', function () {
        // Thymeleaf 변수를 JavaScript로 전달
        var currentPage = [[${page.number}]]; // 0-based 페이지 번호
        var totalPages = [[${page.totalPages}]];

        // 콘솔에 출력
        console.log("Current page number: " + currentPage);
        console.log("Total number of pages: " + totalPages);

    });
</script>
  • 콘솔 로그 찍으면서 pageNumber 확인
📋 더 자세히 보기
📋 더 자세히 보기
📋 더 자세히 보기
📋 더 자세히 보기 (성능 최적화)

📋 프로젝트 구조

About

놀 줄 아는 노인들을 위한 만남 서비스 경로당

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published