Git Product home page Git Product logo

kp3c-backoffice-project's Introduction

Needle은 SNS하지 마라~

📱 스레드와 트위터를 참고하여 SNS 사이트를 만들어보고 백오피스까지 구현해보는 프로젝트


💭 KPT 회고

☝🏻 우리 팀 규칙


✅ 프로젝트 요구 사항

필수 기능

1. 사용자 인증 기능

  • 회원가입

    • username, password는 정해진 형식이 있다.
    • 회원 권한 부여 - ADMIN, USER
  • 로그인 및 로그아웃

2. 프로필 관리

  • 프로필 수정
    • 이름, 한 줄 소개같은 기본적인 정보를 볼 수 있어야 하며 수정할 수 있어야 한다.
    • 비밀번호 수정이 가능해야 함
    • 최근 3번 안에 사용한 비밀번호는 사용할 수 없도록 제한한다.

3. 게시물 CRUD

  • 전체 게시물 조회
  • 인가 개념 적용

4. 댓글 CRUD

  • 전체 댓글 조회
  • 인가 개념 적용
추가 기능 1

1. 소셜 로그인 기능 - 네이버, 카카오

2. 백오피스

  • 회원 관리
    • 회원 조회
    • 회원 정보 수정(권한 수정)
    • 회원 삭제
    • 회원 차단
  • 게시글, 댓글 관리
    • 공지글 등록
    • 모든 게시글 수정
    • 모든 게시글 삭제

3. 프론트엔드

4. 좋아요

  • 게시물 및 댓글 좋아요/좋아요 취소
  • 본인이 작성한 게시물과 댓글에 좋아요 남길 수 없도록 하기

5. 팔로우

  • 특정 사용자를 팔로우/언팔로우
  • 팔로우하는 사용자의 게시물을 볼 수 있도록 하기
추가 기능 2

1. 사진 업로드

  • 사진 저장할 때는 반드시 AWS S3 이용

2. 게시물 멀티미디어 지원 기능 구현

  • 게시물 본문에 사진, 영상 미디어 포함 가능
  • 게시물 수정시 첨부된 미디어 수정 가능
  • 미디어 첨부 시 AWS S3 사용

3. AWS를 이용하여 서비스 배포

  • AWS EC2를 이용해서 배포
    • EC2 역할 이해
    • ubuntu 명령어들을 이해
    • 웹서버, 웹어플리케이션 서버의 차이를 이해
    • Nginx, gunicorn의 역할과 설정파일을 이해

4. HTTP를 HTTPS로 업그레이드 하기


🛠️ 기술 스택

                   
   
       


💁‍♀️ 프로젝트 명세

ERD



API 명세

API 명세


WireFrame


👩‍💻 우리 프로젝트에서 구현한 기능

회원가입, 로그인, 로그아웃

  • username, email 둘 다 로그인 할 수 있게끔 함.
  • 로직 - String usernameOrEmail이 들어오면 먼저 username에서 찾아보고 다음 email에서 찾아본 후 일치하면 로그인 성공
  • 로그인 성공 시 accessToken, refreshToken 발급
  • 로그아웃 기능 구현 - Redis blacklist 이용

팔로우

  • 유저는 다른 유저를 팔로우, 언팔로우할 수 있다.
  • 유저는 자신의 팔로워, 팔로잉 목록을 조회할 수 있다.

글 관련 기능

  • 글에 답글을 남길 수 있다.
  • 홈피드 조회 - 내가 팔로우 한 사람 + 내가 쓴 글 / 마이피드 조회 - 내가 쓴 글 - MySQL 이용한 메서드 & Redis 이용한 메서드
  • 모든 글 계층형으로 조회 가능

백오피스

  • 관리자만 관리자 페이지 접근 가능
  • 전체 회원 / 회원 한 명 조회
  • 회원 정보 수정 - 회원 프로필 수정, 회원 비밀번호 변경, 회원 권한 변경
  • 회원 삭제(강제 탈퇴) - 관리자는 삭제 불가능
  • 공지사항 CRUD
  • 유저가 쓴 글 RUD 가능
  • 페이징 기능
  • 게시글 조회 - 생성 순서를 기준으로
  • 회원 조회 - 가입 순서를 기준으로, 팔로워 수 / 팔로잉 수를 기준으로
  • 회원 차단 / 계정 잠금 기능

멀티미디어

  • 유저는 자신의 프로필을 수정할 수 있다. (프로필 이미지도 수정 가능)
  • 유저는 회원가입 시, 자신의 프로필 사진을 설정할 수 있다.
  • 프로필, 회원가입 부분 프론트엔드 구현
  • 유저는 글에 멀티미디어를 올릴 수 있다.(AWS S3 이용)

카카오 로그인

  • 카카오 로그인 구현 - 로그인 성공시 accessToken, refreshToken 발급

Contact

👩‍💻 김은비 - Blog / Github
👩‍💻 최정은 - Blog / Github
👨‍💻 최종우 - Blog / Github
👩‍💻 최혜원 - Blog / Github
👩‍💻 표지수 - Blog / Github

kp3c-backoffice-project's People

Contributors

jisoopyo avatar jungeun5-choi avatar eunb1 avatar hyewon218 avatar jonggae avatar

Stargazers

 avatar

Watchers

 avatar

Forkers

jonggae hyewon218

kp3c-backoffice-project's Issues

작성된 게시글(post) Redis에 캐싱하기

오늘의 주제: 작성된 게시글(post) Redis에 캐싱하기

관련 이슈 #47

  • 내가 작성한 게시글 캐싱
    • 그냥 단순하게 내가 게시글을 작성할 때마다 저장해주면 된다
    • 물론 좀 더 깊게 들어가면 저장 개수 제한이나, 저장 기간에 제한을 둘 수 있겠다

  • 내가 팔로우한 사람의 게시글 캐싱
    • 메시징을 사용해야할까?

참고

[Java / Spring] Redis - Pub/Sub

Redis를 활용한 JWT 토큰 관리

카카오 소셜 로그인 로직 톺아보고 Redis 사용해서 jwt access token, refresh token 구현해보겠습니다.
+) 시간이 된다면 기타 플랫폼 (구글, 깃허브 등) 소셜 로그인까지 도전해볼게요!

Redis 공부하고 @jungeun5-choi 님께 의견 공유 예정.

Admin(관리자) 기능: SNS 백오피스에 대해

예시에 적혀있던 관리자 권한

회원 관리

  1. 회원 조회하기
  2. 사이트 회원 정보 수정하기
  3. 사이트 회원 삭제하기
  4. 회원 운영진으로 승격
  5. 회원 차단하기

게시글 관리

  1. 공지글 등록하기
  2. 모든 게시글 수정하기
  3. 모든 게시글 삭제하기

프로필 수정 기능

기존 기능 +) password - history 기능 추가


  1. 프로필 수정 기능
    • 이름, 한 줄 소개와 같은 기본적인 정보를 볼 수 있어야 하며 수정할 수 있어야 합니다.
    • 비밀번호 수정 시에는 비밀번호를 한 번 더 입력받는 과정이 필요합니다.
    • 최근 3번안에 사용한 비밀번호는 사용할 수 없도록 제한합니다.

회원 계정 차단/잠금

Redis를 활용해 회원 계정을 차단
Redis를 활용해 회원 계정을 차단 → 다시 살아남

  • 회원 계정을 차단
  • 로그인이 제대로 시도되는지 확인
  • 토큰으로 접속하지 못하도록 막기

7월 24일 발표

안녕하세요. Oauth망한 최종우입니다.

발표는 그래도 망하지 않으려고 노력중이니
혹시 코딩하다가 신의 계시를 느꼈던 부분이나, 프로젝트나 이후의 공부에서 도움이 될 것 같은 부분,
다른사람들에게 꼭 보여주고 싶은 부분들을 인용하거나 적어주시면 발표 자료 작성에 큰 도움이 될것같습니다~ ^^

이 이슈 만들기 이전 받은 것으로는

  • 혜원님의 시연 영상, 제가 PR을 보면서 적당히 보고 있었고

지수님께서 말씀해주신

  • 53번 PR 정은님이 해주신 관리자 기능 에서 트러블 슈팅 1번 부분 한 번 짚어 주시면 좋을 거 같아요 저는 querydsl에 대해서 한 번 짚고 넘어갔으면 좋겠어서 그 부분에 대한 설명을 추가해놓겠습니다!

  • PR을 이용하여 프로젝트를 진행했던 9조의 과정 등이 있습니다! (협업 자랑)

발표 자료

1인당 1개씩 발표에 쓸 자료를 작성해봅시다

Admin(관리자) 기능: 전체 조회 시 페이징 기능

페이징 기능에 대한 구상

내림차순 혹은 오름차순 정렬

  • 게시글 (공지사항 포함)

    1. 생성 순서를 기준으로
    2. 조회수를 기준으로
      • 엔티티에 조회수 필드 추가 필요
    3. 리트윗 수를 기준으로
    4. 좋아요 수를 기준으로
  • 회원

    1. 가입 순서를 기준으로
    2. 접속 시간을 기준으로
      - 엔티티에 접속 시간 필드 추가 필요
    3. 팔로워 수를 기준으로
    4. 팔로잉 수를 기준으로

User 클래스 API에 대해서(follow, 소셜 로그인, 회원가입, 로그인)

현재

http.authorizeHttpRequests((authorizeHttpRequests) ->
	authorizeHttpRequests
			.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() // resources 접근 허용 설정
			.requestMatchers("/kp3c/user/**").permitAll()
			.anyRequest().authenticated() // 그 외 모든 요청 인증처리
		);

로 되어 있어서 '/api/user/'로 시작하는 요청 모두 접근 허가가 되어 있는 상태인데

@RequestMapping("/kp3c/user")
public class UserController {

    ...

    @GetMapping("/{username}/followers")
        public ResponseEntity<List<ProfileDto>> getFollowers(@PathVariable String username) {
            ...
        }
}

로 되어 있어서 팔로워의 목록을 보여주는 메서드, 팔로잉의 목록을 보여주는 메서드, 팔로우를 하는 메서드, 팔로우을 취소하는 메서드
가 인증, 인가를 받지 않은채 통과가 되고 있습니다.

이를 해결하려면

  1. 팔로워, 팔로잉 메서드의 api를 바꿔주거나
  2. 회원가입, 로그인, 소셜 로그인 api를 바꿔주거나
  3. requestMapping()에서 다른 조건을 찾아보거나

중에 선택해야 할 거 같습니다!
어떻게 하는게 좋을까요?

Kakao 소셜 로그인 보강 (소셜 회원가입까지?) + 로직 이해하기 위한 주석달기

우선 소셜로그인을 할때 뭐가 더 필요한지 알아보겠습니다.

다른 분들이 만드시는 것들이 필요하다면 기다려야겠죠.. 다들 열심히 하셔서 저도 도움이 많이 되네요.

테스트도 해보고싶고 해서 우선 로직 이해하는 것부터 해보겠습니다. 설명할 수 있게 주석도 달아볼게요..

프론트를 간단하게 만들어볼까하는데 이건 될지 안될지 모르겠습니다! (JS에서 망할것 같아서 ㅋㅋ/ 안할 수도 있음)

Post(게시물) Entity

게시물 CRUD 기능

  • 게시물 작성, 조회, 수정, 삭제 기능
    • 게시물 조회를 제외한 나머지 기능들은 전부 인가(Authorization) 개념이 적용되어야 하며 이는 JWT와 같은 토큰으로 검증이 되어야 할 것입니다.
    • 전체 게시글 정보를 조회하는 기능도 필요합니다.

Redis 도입해보기

jpa connection -> redis 연결이 쉬움
(redis는 따지고보면 DB가 아니라서)

redis와 session 관리는 찰떡이다!

  • 예를 들면 토큰 만료시간 조절에 자주 쓰임
  • redis는 인메모리DB이기 때문에 저장하는 데이터 크기가 큰 것은 좋지 않다
    • 데이터 저장 시 고려할 점
      • 엔티티 자체는 저장x
        • redis class에 엔티티 자체를 저장하면 stackoverflowError가 난다고 함

      • 필요한 데이터만 저장하는게 바람직하다
      • redis를 사용하는 이유는 DB 부하를 줄이기 위해서(정보를 DB를 통해서 가져오지 않아도 되도록)
      • 리프레시 전략? TTL: time to leave을 제대로 명시

redis 서버와 AWS 웹서버 배포를 동시에 진행하는 경우

  • 서로 다른 브랜치에서 각각의 배포 작업을 하고 merge해도 문제는 없을 것
    • 물론 아래와 같은 문제는 고려해보아야한다 (기본적)
  • AWS로 웹서버 배포시 보안 설정 등 고려해야함
    • redis 서버라는 또 다른 서버에서 데이터를 가져오는 것이기 때문에
  • AWS에서 redis를 띄우는 두 가지 방법
    1. redis - elasticache 사용하는 방법
    2. Amazon EC2 인스턴스 (싼 걸로...) 하나 만들어서 이 안에서 redis를 띄우는 방법

Admin(관리자) 기능: 관리자 페이지

관리자 페이지

  1. 회원 종류를 일반 회원과 관리자 회원으로 분리
  2. 회원 관리 또는 게시글 관리 화면 추가 / 혹은 동일 화면이라 하더라도 더 많은 버튼과 기능을 사용할 수 있도록

유의할 점

  • 유저 전체 목록을 조회하고 권한을 수정/삭제하며 관리
  • 게시글, 댓글 전체 목록을 조회하고 수정/삭제하며 관리

Username과 Email 두 가지로 로그인하는 방법

Username과 Email 두 가지로 로그인하는 방법

  • 제약조건: 한 필드에 username과 email, 둘 중 하나만을 입력해야한다.

Kakao 로그인 기능이 추가되어, usernameemail 두 가지 방법으로 로그인 할 필요가 생겼다.
이에 세가지 방법을 고려해보았다.

1. 내부에서 `username`과 `email` 두 가지를 검사하도록 하기
2. `UserDetailsService` 클래스를 상속 받는 클래스를 하나 더 만들고, 두 가지를 입력받도록 하기
3. 로그인 방식을 하나로 합치기

여기서 1번 UserRepository 내부에서 usernameemail 두 가지를 사용하여 User를 찾도록 하는 방법을 선택하였다.

수정 전, 기존 메서드
UserDetailsImpl.java

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    User user = userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("Not Found " + username));
    return new UserDetailsImpl(user);
}

수정 후

1. UserRepository에 findByUsernameOrEmail 메서드 추가

Optional<User> findByUsernameOrEmail(String username, String email);

2. UserDetailsImpl.loadUserByUsername 수정

@Override
public UserDetails loadUserByUsername(String usernameOrEmail) throws UsernameNotFoundException {
    User user = userRepository.findByUsernameOrEmail(usernameOrEmail, usernameOrEmail)
                .orElseThrow(() -> new UsernameNotFoundException("Not Found " + usernameOrEmail));
    return new UserDetailsImpl(user);
}

Kakao Social Login 구현

로그인 과정을 소셜 로그인으로 할 수 있는 기능 만들기

먼저 카카오 로그인을 진행하고, 이후에 다른 플랫폼을 이용해보기로 한다.

(네이버, 구글, 페이스북, 인스타그램, 등등)
개발가이드 참고하여 소셜 로그인을 구현해봅시다.

포스트 좋아요 기능

  • 사용자는 선택한 게시글에 '좋아요'를 할 수 있다.
  • 사용자가 이미 '좋아요'한 게시글에 다시 '좋아요' 요청을 하면 '좋아요'를 했던 기록이 취소된다.
  • 요청이 성공되면 Client로 성공했다는 메시지, 상태코드를 반환한다.

+) 게시글 조회 시 좋아요 개수도 반환한다.

ProfileDto에 대한 수정!

은비님이랑 의견 나눠볼 것.

  1. ProfileDto를 써야 하는지
  2. 쓴다면 어디 쓰는지 확실히 목적 정하기
    (ex. 팔로워들의 목록을 보여준다면 username은 안 보여줘도 되니까 필드 빼기)
  3. 쓴다면 ProfileRequestDto 나, ProfileResponseDto 로 클래스명으로 목적이 보이게끔 분리.
  4. 정은님이 구현하시는 ProfileRequestDto와 충돌 안나게 하기

회원가입, 로그인 (User Entity), Security

  1. 회원가입 기능

    • username, password, nickname, introduction, email을 Client에서 전달받기
    • username은 최소 4자 이상, 10자 이하이며 알파벳 소문자(a~z), 숫자(0~9)로 구성되어야 한다.
    • password는 최소 8자 이상, 15자 이하이며 알파벳 대소문자(a~z, A~Z), 숫자(0~9), 특수문자로 구성되어야 한다.
    • DB에 중복된 username이 없다면 회원을 저장하고 Client 로 성공했다는 메시지, 상태코드 반환하기
    • 회원 권한 부여하기 (ADMIN, USER) - ADMIN 회원은 모든 게시글, 댓글 수정 / 삭제 가능
  2. 로그인 및 로그아웃 기능

    • username, password를 Client에서 전달받기
    • DB에서 username을 사용하여 저장된 회원의 유무를 확인하고 있다면 password 비교하기
    • 로그인 성공 시, 로그인에 성공한 유저의 정보와 JWT를 활용하여 토큰을 발급하고,
      발급한 토큰을 Header에 추가하고 성공했다는 메시지, 상태코드 와 함께 Client에 반환하기
  3. Jwt, Security 관련 클래스 추가
    로그인과 Security과 연결되어 있어서 같이..

Post 무한 대댓글 + HomeFeed + MyFeed 구현

Todo List

1. Post CUD 기능

  • Create
  • Update
  • Delete

2. Post Read - HomeFeed, MyFeed 구현

  • 홈피드를 불러오는 모든 글의 조회 기능(자기가 팔로우한 사람+내가 쓴 글)

추가 아이디어

  1. 자기가 팔로우 한 사람의 글 + 자기가 쓴 글 + 자기가 답글을 달았던 글(+ 랜덤 글)
  2. 자기가 팔로우 한 사람의 글 + 자기가 쓴 글 + 자기가 답글을 달았던 글 + 랜덤 글

  • 자기 피드 글 조회 기능(내가 쓴 글)

추가 아이디어

  • 자기 피드 조회할 때, 자기가 쓴 글 + 자기가 답글을 달았던 글 조회할 수 있게 하기

  • 모든 글을 계층형으로 조회하는 기능.
    꼭 필요할거라고 생각했는데 구현하고 나서 생각해 보니 트위터를 보면 답글까지 싹 나오는 형태가 아니다. parentId과 일치하는 답글만 나오면 되었는데 너무 어렵게 생각했다. 근데 구현해버려서 아까워서 지우지도 못함. 그냥 언젠가 쓸지도 모른다고 생각하고 내비둠.

  • 특정 글에 대한 답글 조회 기능

계층형 조회 기능 & id가 1인 포스트에 대한 답글 조회

https://kukekyakya.tistory.com/9

정은님이 찾아주신 링크를 참고하여 구현.

프로젝트 피드백 - 작성자: 이성국 튜터

Overview

  • 프로젝트 전반적으로 매우 훌륭합니다 디테일한 부분들만 조금 더 다듬고 다른 팀원 분들이 짠 로직까지 직접 구현해보고 이론적인 내용들까지 완벽히 숙지한다면 취업 준비하실 때 도움이 될 것 같습니다

  • 전반적으로 문서화가 너무 잘되어있습니다 정말 열심히하신게 보이고 실력도 많이 성장하신 것 같아서 보기 너무 좋았습니다

  • AWS S3 파일업로드, 레디스 사용, 카카오 로그인, 테스트코드등 추가적인 기능까지 구현해주셨는데 이러한 시도 정말 좋습니다. 이렇게 직접 구현하는 과정에서 실력도 많이 성장하셨을 것 같네요

  • PR 에서 어떠한 작업을 했는지, 어떻게 테스트했는지 팀원분들끼리 코드리뷰 해주신 내용 정말 좋습니다 훌륭하네요

  • restful api 에 대한 이해도가 많이늘어서 전반적으로 설계가 좋습니다. 아쉬운 점은 단수형보단 복수형으로 사용하는 것이 관례이기는 합니다 posts -> post, comments -> comment

  • 하나 조회, 전체조회 때문에 단수형 복수형으로 나눠서 사용하신 것 같은데 포스트 전체 조회의 경우 GET /posts 포스트 하나 조회의 경우 GET /posts/{postId}로 사용하면 의미 전달이 충분히 될 수 있습니다

  • {post_id} -> {postId} 카멜케이스로 보통 사용합니다

  • ERD 같은 경우는 로그인하면 보이지 않네요. 선호에 따라 다르겠지만 저는 Readme에 바로 스크린샷형태로 보이는 것을 선호합니다. (한 뎁스 더 들어가야 하면 보지 않는 분도 계실 수 있을 것 같습니다)

  • 어떠한 기능을 어떠한 기술을 사용해서 구현했는지 적어주셔서 좋습니다. 나중에 취업준비하실 때는 이 내용을 조금 더 상세히 기술해서 포트폴리오를 적는 것이 좋습니다. ex) 이 기능을 구현하는데 왜 레디스를 사용했고 다른 기술을 고려한 것은 무엇이있는지 성능상 어떠한 이점이 있어서 이것을 사용했는지 등등) 나중에 튜터실에 오시면 어떤식으로 쓰면 더 좋을지 자세히 말씀드릴게요

Code review

// ADMIN_TOKEN
private final String ADMIN_TOKEN = "AAABnvxRVklrnYxKZ0aHgTBcXukeZygoC";

  • 토큰 값은 코드에 적지 않습니다. 해킹될 위험이 있어서 vault 같은 곳에 보통 저장합니다. 지금은 적용하기 무리가 있으니 properties로 적용하는것도 방법이겠네요

public UserProfileResponseDto updateUserProfile(Long userId, UserProfileRequestDto requestDto) throws IOException { // 프로필 폼에서 받은 데이터를 넣어주어야 함
UploadFile storeImageFile = fileStore.storeFile(requestDto.getImageFile());
Optional<User> userOptional = userRepository.findById(userId);
if (userOptional.isPresent()) {
User user = userOptional.get();
// 사용자 정보 업데이트
user.setNickname(requestDto.getNickname());
user.setIntroduction(requestDto.getIntroduction());
user.setImageFile(storeImageFile.getStoreFileName()); // 서버용 파일이름으로 저장
User updatedUser = userRepository.save(user);
return new UserProfileResponseDto(updatedUser);
}
return null;

  • 파일업로드 기능 구현 좋습니다. 현재는 파일이름으로 저장하고 있지만 (파일을 서버에 저장하는 방식이라) 보통은 S3에 파일을 업로드하고 AWS 에서 파일에 대한 url을 주면 그 url을 보통 DB에 저장합니다.

@Service
@Transactional
@RequiredArgsConstructor
public class FollowService {

  • @transactional은 보통 메소드 단위로 사용하는게 좋습니다. 왜냐면 getFollowers 같은 메소드는 읽기만 있으므로 @transactional(readOnly = true)로 적용해야하기 때문입니다

public class Follow {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "follower_id", nullable = false)
private User follower;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "followee_id", nullable = false)
private User followee;
public Follow(User follower, User followee) {
this.follower = follower;
this.followee = followee;

  • 팔로워 엔티티 설정 좋습니다 학습내용에 맞게 정석적으로 구현해주셨네요

public List<FollowUserDto> getFollowers(String username) {
User user = findUser(username);
List<Follow> followerList = followRepository.findAllByFollowee(user);
return followerList
.stream()
.map(Follow::getFollower)
.map(FollowUserDto::new)
.toList();
}

  • 로직 구성 학습방향에 맞게 좋습니다
  • 현업에서는 이렇게 사용하면 문제가 될 염려가 있습니다. 왜냐면 인스타그램 팔로워가 많은사람들은 몇천만명 이 넘는 유저들도 있기 때문에 이렇게 한번에 DB를 조회하면 그 내용이 엄청 많아져서 성능에 문제가 될 수 있습니다. 이를해결하려면 어떻게 해야할까요!? 여러방법이 있는데 한번 고민해보시고 검색해보면서 어떻게 처리하면 좋을지 고민해보면 좋을 것 같습니다.
  • 또한 지금처럼 몇천만건의 데이터를 읽을때 Pagination이라는 것을 사용합니다. api를 호출할때 몇천만건을 한번에 불러오는 것은 성능에 문제가 생길 수 있어서 클라이언트에서 100개씩 여러번 호출하는 개념인데요 한번 검색해서 내용 숙지해보시면 좋을 것 같네요

@ColumnDefault("0")
@Column(name = "follower_count")
private Integer followerCount; // 팔로워 수
@ColumnDefault("0")
@Column(name = "following_count")
private Integer followingCount; // 팔로잉 수

  • 성능을 고려해서 반정규화 를 통해 팔로워, 팔로잉 수를 나타내주신 점 좋습니다.

public ResponseEntity<ApiResponseDto> follow(@PathVariable String username, @AuthenticationPrincipal UserDetailsImpl userDetails) {
followService.follow(username, userDetails.getUser());
return ResponseEntity.ok().body(new ApiResponseDto("팔로우 성공", HttpStatus.OK.value()));
}
@PostMapping("/user/unfollow/{username}")
public ResponseEntity<ApiResponseDto> unfollow(@PathVariable String username, @AuthenticationPrincipal UserDetailsImpl userDetails) {
followService.unfollow(username, userDetails.getUser());
return ResponseEntity.ok().body(new ApiResponseDto("언팔로우 성공", HttpStatus.OK.value()));
}

  • ~~ 성공 같은 문구는 나타내주지 않아도 api가 예외를 뱉지않고 성공해서 200 status를 내려준다면 클라이언트는 성공했다는 것을 알 수 있으므로 해당 문구 불필요합니다

public class RedisPostController {
private final RedisPostService redisPostService;
/* 타임라인(피드) 게시글 조회 */
@GetMapping("/my")
public ResponseEntity<List<RedisPostDto>> getMyLastCreatedPosts(
@AuthenticationPrincipal UserDetailsImpl userDetails
) {
List<RedisPostDto> results = redisPostService.getMyLastCreatedPosts(userDetails.getUser());
return ResponseEntity.ok().body(results);
}
}

  • 레디스를 이용한 캐싱을 사용하신 것은 좋으나 Redis만을 위한 Controller는 불필요합니다.
  • 게시글을 조회할 때 레디스에 먼저 게시글이 있나 확인하고 없으면 DB를 조회해서 게시글을 가져와서 응답을 내려주고 이 게시글을 레디스에 저장하는 방식이 일반적인 캐싱 방식입니다
  • https://souljit2.tistory.com/72 이런글 참조해보시면 좋겠네요

public void saveMyLastCreatedPost(User user) {
ListOperations<String, RedisPostDto> listOperations = redisTemplate.opsForList();
// id가 가장 마지막인 post를 찾는다
Post lastCreatedPost = postRepository.findTopByOrderByIdDesc().orElseThrow( () ->
new IllegalArgumentException("작성한 게시글이 없습니다.")
);
// redis에 저장
String key = user.getId().toString();
RedisPostDto value = new RedisPostDto(lastCreatedPost);
listOperations.leftPush(key, value); // 저장
}

  • 해당 방식으로 저장하는 것도 나쁘지 않으나 스프링에서 지원해주는 @Cacheable 같은 것을 사용하면 더 편리하게 캐싱할 수 있습니다
  • 또한 이렇게 따로 저장하기 보단 어떠한 게시글을 조회했을 때 레디스에 있는지 확인하고 없으면 DB를 조회해서 이를 레디스에 저장하면 다음번 다시 조회할 때 레디스에 있는지 확인했을 때 있으면 캐시 히트가 되는 로직이 일반적입니다

public List<RedisPostDto> getMyLastCreatedPosts(User user) {
ListOperations<String, RedisPostDto> listOperations = redisTemplate.opsForList();
String key = user.getId().toString();
long size = listOperations.size(key) == null ? 0 : listOperations.size(key);
return listOperations.range(key, 0, size);
}

  • 이처럼 레디스를 조회하는 방식은 연산도 많이들고 캐시 히트가 될 확률도 적습니다

@Getter @ToString
@NoArgsConstructor
@AllArgsConstructor
@RedisHash("redis_post")
public class RedisPost {
@Id
private String userId; // key
private RedisPostDto redisPostDto; // value

  • 캐싱을 위해서 새로운 엔티티를 만들 필요는 없습니다
  • 기존 엔티티를 이용해서 말씀드린 방식대로 캐싱을 적용해보면 좋겠네요

전반적으로 레디스를 이용한 시도 너무 좋았지만 좀 더 적절한 캐싱방법을 이용해보시면 더욱 좋을 것 같네요

@Value("${kakao-rest-api-key}")
private String CLIENT_ID;

  • 주입방식 좋습니다
  • 전반적으로 카카오 로그인 방식 구성이 괜찮은 것 같네요

private User registerKakaoUserIfNeeded(KakaoUserInfoDto kakaoUserInfo) {
Long kakaoId = kakaoUserInfo.getId();
User kakaoUser = userRepository.findByKakaoId(kakaoId).orElse(null);
if (kakaoUser == null) {

  • 현재는 프로젝트 사이즈가 작아서 User 엔티티에 kakaoId까지 저장하는 것도 괜찮지만 몇천만명이 사용하는 어플리케이션이라면 해당 테이블도 분리되는 것이 좋습니다

public Page<AdminNoticeResponseDto> getNotices(
@RequestParam("page") int page,
@RequestParam("size") int size,
@RequestParam("sortBy") String sortBy,
@RequestParam("isAsc") boolean isAsc,
@AuthenticationPrincipal UserDetailsImpl userDetails) {
return adminNoticeService.getNotices(userDetails.getUser(),
page - 1, size, sortBy, isAsc);
}

  • 페이지네이션을 적용해주셨네요 훌륭합니다. 페이지네이션 방식에는 사용해주신 방식과 커서 페이지네이션 방식이 있습니다. 실제로 현업에서는 커서 페이지네이션 방법을 훨씬 더 많이 사용합니다. 왜냐면 page, size를 이용하면 10번까지 조회한다음에 사용자들이 글을 계속 작성한다면 누락되서 조회하는 경우가 생길 수 있기 때문입니다. 커서 페이지네이션에 대해 검색해서 개념을 익혀보시면 더욱 좋을 것 같네요
  • 로직 구성해주신 걸 보니 왜 페이지네이션을 사용해야하는지 알고 계실 것 같지만 이 부분에 대해서 좀 더 숙지해보면 좋을 것 같습니다 면접에서도 많이 나오는 내용입니다

@Service
@Transactional
@RequiredArgsConstructor
public class AdminPostService {

  • @transactional은 보통 메소드 단위로 사용하는게 좋습니다. 왜냐면 getFollowers 같은 메소드는 읽기만 있으므로 @transactional(readOnly = true)로 적용해야하기 때문입니다

String accessToken = redisUtils.get(findUser.getEmail(), "access_token", String.class);
redisUtils.put(accessToken, "delete forced", JwtUtil.ACCESS_TOKEN_TIME); // blacklist 에 등록
redisUtils.delete(findUser.getEmail(), "refresh_token"); // refresh token 삭제
redisUtils.delete(findUser.getEmail(), "access_token"); // access token 삭제
userRepository.delete(findUser); // 회원 정보 삭제
}

  • 토큰 삭제 좋습니다 레디스에 토큰 저장한 것도 적절합니다. 프로젝트 규모가 커지면 레디스가 아니라 DB에 토큰을 저장하는 경우도 있습니다. 구현하기 나름인데 이 부분도 고민해보면 좋을 것 같네요

// 모든 글 계층형으로 조회(답글까지) - 어디에서 쓰이겠지 뭐..
@GetMapping("/allPosts")
public ResponseEntity<List<PostResponseDto>> getAllPosts(){
List<PostResponseDto> results = postService.getAllPosts();
return ResponseEntity.ok().body(results);
}
//////////////////////////////////////////////////////////////////////////////////
// 홈피드(유저 작성 글 + 유저가 팔로잉한 글)
// http://localhost:8080/kp3c/posts
@GetMapping("/homeFeed")
public ResponseEntity<List<PostResponseDto>> getHomeFeed(@AuthenticationPrincipal UserDetailsImpl userDetails) {
List<PostResponseDto> results = postService.getHomeFeed(userDetails.getUser());
return ResponseEntity.ok().body(results);
}
// 자기피드(유저 작성 글만)
// http://localhost:8080/kp3c/post/1
@GetMapping("/myFeed")
public ResponseEntity<List<PostResponseDto>> getMyFeed(@AuthenticationPrincipal UserDetailsImpl userDetails) {
List<PostResponseDto> results = postService.getMyFeed(userDetails.getUser());
return ResponseEntity.ok().body(results);

  • 전반적으로 페이지네이션 적용 필요합니다. 피드에 수천만개가 한번에 있을 수도 있는 로직이여서 실제 어플리케이션이라면 위험할 수 있습니다.
  • 인스타그램을 예로들면 처음에 100개를 가져오고 우리가 피드를 내리다가 98번째쯤되면 그때 프론트에서 api를 요청해서 다음 100개를 가져오는 방식으로 설계가 되어있습니다. 이는 주로 커서 페이지네이션으로 구현되어있고 이 부분 참고해보시면 좋겠네요

전반적으로 동일한 부분에 대해서는 다시 지적하지는 않았으니 각자 작성한 부분에서 제가 리뷰 남긴 부분이 적용된다면 고쳐보시면 실력향상에 도움이 될 것 같습니다. 프로젝트하느라 고생많으셨습니다. 열심히 하고 계신게 눈에 보이네요. 다른 팀원분들이 작성하신 로직도 보면서 본인 것으로 만들면 더 좋을 것 같네요

Admin(관리자) 기능: 회원 관리

회원 관리

내용은 추후 수정될 수 있음

  1. 회원 조회하기
  2. 사이트 회원 정보 수정하기
  3. 사이트 회원 삭제하기
  4. 회원 운영진으로 승격
  5. 회원 차단하기
    • user모델과 tokenobtainpairview 수정 필요

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.