1️⃣  롤링페이퍼 작성이 한번만 되는 에러

  • 원인) 작성시 닉네임과 롤링페이퍼 내용이 requestDto로 설정되어 있는데, 닉네임이 유니크키로 설정되었기 때문에 하나 이상의 롤링페이퍼를 작성할 수 없었음.
  • 해결) 닉네임 유니크키 해제 후 정상 작동

기본키와 유니크키 차이점

더보기
  • 기본키는 즉, 프라이머리 키는 해당 테이블의 식별자 역할을 합니다. 바로 이 제약조건으로 테이블에 하나만 지정할 수 있습니다. 예를 들면, 사람을 식별할 때는 사람의 이름 대신 주민등록번호를 사용합니다. 그 이유는, 바로 유일한 성질을 가지고 있기 떄문입니다. 이렇게 중복성이 없는 유일성을 가진 성질을 의미합니다. 그 중 사용자가 선택한 것을 기본키라고 할 수 있습니다.
  • 유니크 키는 유일성을 가지기 위해 설정한 것입니다. 따라서 지정이 되면 중복이 되는 것을 제어하는 역할을 하게 됩니다. 에를 들면 회원 이름을 중복으로 설정하지 않게 하는 것입니다. 중복되는 이름의 경우에는 뒤에 숫자를 붙여서 최초의 한 사람만 기입할 수 있도록 하는 것입니다. 결국, 프라이머리 키는 유니크 키의 성질을 포함하는 것을 알 수 있습니다. 그 중에, 설계자가 기본적으로 선택한 키라고 할 수 있습니다. 유니크키는 하나의 테이블에 각각 컬럼마다 지정이 가능합니다. 그러나 프라이머리키는 오직 하나만 설정할 수 있습니다.  

 

2️⃣  댓글 좋아요 수만 오르고 좋아요 취소는 되지 않는 에러*

  • 원인) 쿼리문에서 findByUserIdAndUsername 이라고 명시했지만, ()안 매개변수값 순서를 거꾸로 입력
  • 해결) 올바르게 순서 정렬 후 정상 작동

 

3️⃣  유효성 검사가 정상적으로 수행되지않지만 에러없이 통과*

  • 원인) Build.gradle 파일 내부 dependencies 설정에서 상위버전 valid dependency와 하위버전이 동시에 존재할 경우 dependency간 충돌이 있어 애플리케이션은 에러는 없지만 유효성 검사가 진행되지 않음
  • 해결) 사용되지 않거나 중복으로 설정되어진 dependency를 주석처리 또는 삭제하는 것으로 정상적으로 유효성 검사 수행

 

4️⃣  AWS S3 서버 이미지 업로드 불가

  • 문제) S3에 이미지가 로컬에만 저장되고 S3로 convert 되지 않음
  • 원인) 추측 - ACL(액세스 제어 목록) 권한설정 문제
  • 해결) 추측 - 모든 사람(퍼블릭 액세스) 부분에 객체에 나열 권한을 부여

 

5️⃣  예외처리 부분*

  • 문제) 모든 에러의 상태코드가 200번으로 반환됨
  • 원인) MsgResponseDto로 customException 부분의 메세지부분만 return이 되고 Httpstatus 코드는 반환이 안되고 있었기 때문에 발생함
  • 해결) 반환타입을 ResponseEntity로 감싸고 HttpStatus 상태코드도 같이 반환하는 방식으로 변경

 

6️⃣  CORS 에러*

  • 문제) api사용시 cors 에러
  • 발생원인) CORS설정을 하지않아 프론트 3000포트에서 백 8080포트의 자원요청 불가
  • 해결) 서버에서 CorsConfigurationSource를 통한 CORS 설정으로 해결

 

(추가 해결 방안)

  • @CrossOrigin 어노테이션 사용 컨트롤러 또는 사용할 메소드에 어노테이션을 추가
  • WebMvcConfigurer 설정스프링 프로젝트를 생성하면 @SpringBootApplication 어노테이션이 적용된main함수에서WebMvcConfigurer 을 @Bean으로 등록하여 Cors매핑을 추가

 

7️⃣  토큰이 삭제되지 않아 토큰 만료 메세지가 계속 뜨는 에러

  • 문제)만료된 토큰을 계속 들고 있어서 만료되었습니다 메세지가 계속 뜸
  • 원인)토큰 값이 만료되면 자동적으로 토큰 값을 삭제 해줘 야 되는데 계속 남아있음
  • 해결)localStorage.removeItem("token")을 이용하여 status 401 이 뜰 때 if문을 사용하여 제거함

 

8️⃣  json 에러

  • 문제)서버로 데이터를 전송할 때 json문자열로 보내야 하는데 변환되지 않는 에러
  • 원인)"Content-Type": "application/json”이 담긴 config를 헤더 값에 넣어주지 못함
  • 해결)config 만 해준 곳에 {headers : config} 를 넣어 직접 명시를 해줌으로써 해결

프론트엔드 리엑트 코드 부분

 

※ 그 외 트러블슈팅 해결 방법 - 프론트엔드 리엑트 코드는 잘 몰라서, 아래와 같이 스프링에 System.out.pringln 을 사용하여 들어가는 값을 확인해 주면서 에러 체크를 하기도 했다.

 

※ 아쉬웠던 부분 / 정리 💡

  • 협업은 의사소통이 정말 중요하다. 특히 API 명세를 잘 작성하고 수정이 될 때마다 업데이트를 해줘야 한다.
  • 각 파트가 서로의 기능이 완성이 될 때마다 공유를 하며 맞춰가는 과정이 필요했는데 나중에 한꺼번에 합치려다보니 더 힘들었던 것 같다. 완성된 기능끼리는 바로바로 합치는 과정이 필요하다.

사전 세팅

  • 먼저 AWS에서 S3 버킷을 만들고 IAM에서 사용자를 만들어 access key와 secret ket를 발급받는 작업이 필요하다. 
  • 참고 링크 : https://jojoldu.tistory.com/300
 

SpringBoot & AWS S3 연동하기

안녕하세요? 이번 시간엔 SpringBoot & AWS S3 연동하기 예제를 진행해보려고 합니다. 모든 코드는 Github에 있기 때문에 함께 보시면 더 이해하기 쉬우실 것 같습니다. (공부한 내용을 정리하는 Github와

jojoldu.tistory.com

 

구현 코드 (1) S3 부분

먼저, S3 라는 패키지를 만들어 이미지를 S3에 업로드하고 삭제하는 부분은 이곳에 구현을 했다.

 

1. AmazonS3Config 파일

@Configuration
public class AmazonS3Config {

    @Value("${cloud.aws.credentials.accessKey}")                        //S3 accessKey
    private String accessKey;

    @Value("${cloud.aws.credentials.secretKey}")                        //S3 secretKey
    private String secretKey;

    @Value("${cloud.aws.region.static}")                                //S3 region
    private String region;

    @Bean
    public AmazonS3Client amazonS3Client() {
        BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);

        return (AmazonS3Client) AmazonS3ClientBuilder.standard()
                .withRegion(region)
                .withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
                .build();
    }
}

 

2. S3Controller 파일

사실 이 파일은 전체 서비스와 합칠 때 필요한 부분은 아니지만, 그 전에 업로드가 되는지 보기위해 테스트용으로 있는 컨트롤러이다.

@RequiredArgsConstructor
@RestController
@RequestMapping("/api")
public class S3Controller {
    private final S3Service s3Uploader;

    @GetMapping("/images")
    public String getImage(@AuthenticationPrincipal UserDetailsImpl userDetails)throws IOException{
        return s3Uploader.getThumbnailPath("img.png");
    }

    @PostMapping("/images")
    public String delete(@RequestParam(required = true) String fileName){
         s3Uploader.deleteFile(fileName);
        return "success";
    }
}

 

3. S3Service 부분

@Slf4j
@Component
@RequiredArgsConstructor
@Service
public class S3Service {

    private final AmazonS3Client amazonS3Client;

    private final ImageFileRepository imageFileRepository;
    @Value("${cloud.aws.s3.bucket}")                                                        //bucket 이름
    public String bucket;

    public void upload(MultipartFile multipartFile, String dirName, Board board, User user) throws IOException {
        File uploadFile = convert(multipartFile).orElseThrow(() -> new IllegalArgumentException("파일 전환 실패"));

        ImageFile imageFile = new ImageFile(upload(uploadFile, dirName), user, board);
        imageFileRepository.save(imageFile);
//        return upload(uploadFile, dirName);
    }

    // S3로 파일 업로드하기
    private String upload(File uploadFile, String dirName) {
        String fileName = dirName + "/" + UUID.randomUUID(); // S3에 저장된 파일 이름
        String uploadImageUrl = putS3(uploadFile, fileName); // s3로 업로드
        removeNewFile(uploadFile);
        return uploadImageUrl;
    }

    // S3로 업로드
    private String putS3(File uploadFile, String fileName) {
        amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, uploadFile).withCannedAcl(CannedAccessControlList.PublicRead));
        return amazonS3Client.getUrl(bucket, fileName).toString();
    }

    // 로컬에 저장된 이미지 지우기
    private void removeNewFile(File targetFile) {
        if (targetFile.delete()) {
            log.info("File delete success");
            return;
        }
        log.info("File delete fail");
    }

    private Optional<File> convert(MultipartFile multipartFile) throws IOException {
        File convertFile = new File(System.getProperty("user.dir") + "/" + multipartFile.getOriginalFilename());
        // 바로 위에서 지정한 경로에 File이 생성됨 (경로가 잘못되었다면 생성 불가능)
        if (convertFile.createNewFile()) {
            try (FileOutputStream fos = new FileOutputStream(convertFile)) { // FileOutputStream 데이터를 파일에 바이트 스트림으로 저장하기 위함
                fos.write(multipartFile.getBytes());
            }
            return Optional.of(convertFile);
        }

        return Optional.empty();
    }

    // find image from s3
    public String getThumbnailPath(String path) {
        return amazonS3Client.getUrl(bucket, path).toString();
    }

    //remove s3 object
    public void deleteFile(String fileName){
        DeleteObjectRequest request = new DeleteObjectRequest(bucket, fileName);
        amazonS3Client.deleteObject(request);
    }
}

 

 

코드 구현 (2) 게시판 코드에 적용하기

1. ImageFile 엔티티를 추가해 주었다.

@Getter
@Entity
@NoArgsConstructor
public class ImageFile {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;                    // id

    @Column(nullable = false)           // image 경로
    private String path;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "userid", nullable = false)
    private User user;                  // userid

    @OneToOne
    @JoinColumn(name = "boardid", nullable = false)
    private Board board;

    public ImageFile(String path, User user, Board board){
        this.path = path;
        this.user = user;
        this.board = board;
    }
}

 

 

2. BoardController

  • formdata 형식이기 때문에 POST 부분을 @RequestBody 가 아니라 @RequestPart 로 바꿔주어야 한다.
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/api")
public class BoardController {

    // Connect to boardservice
    private final BoardService boardService;

    // DB save
    @PostMapping(value = "/boards", consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE})
    public ResponseEntity<BoardResponseDto> createBoard(@RequestPart BoardRequestDto requestDto,
                                                        @RequestPart(required = false) MultipartFile image,
                                                        @AuthenticationPrincipal UserDetailsImpl userDetails) throws IOException{
        return ResponseEntity.ok(boardService.createBoard(requestDto, userDetails.getUser(), image));
    }

    // DB select all
    @GetMapping("/boards")
    public ResponseEntity<List<BoardResponseDto>> getBoards(){return ResponseEntity.ok(boardService.getBoards());}

    // DB select one
    @GetMapping("/boards/{id}")
    public ResponseEntity<BoardResponseDto> getBoard(@PathVariable long id){return ResponseEntity.ok(boardService.getBoard(id));}

    // DB update
    @PatchMapping("/boards/{id}")
    public ResponseEntity<BoardResponseDto> updateBoard(@PathVariable Long id,
                                                        @RequestBody BoardRequestDto requestDto,
                                                        @AuthenticationPrincipal UserDetailsImpl userDetails){
        return ResponseEntity.ok(boardService.update(id,requestDto, userDetails.getUser()));
    }

    // DB delete
    @DeleteMapping("/boards/{id}")
    public ResponseEntity<BoardDeleteResponseDto> deleteBoard(@PathVariable Long id,
                                                              @AuthenticationPrincipal UserDetailsImpl userDetails) {
        return ResponseEntity.ok(boardService.deleteBoard(id, userDetails.getUser()));
    }
}

 

3. BoardService 

 



@Slf4j
@Service
@RequiredArgsConstructor
public class BoardService {

    private final BoardRepository boardRepository;                                                      // board repo connect
    private final CommentRepository commentRepository;                                                  // comment repo connect
    private final CommentLikeRepository commentLikeRepository;                                          // commentLike repo connect
    private final BoardLikeRepository boardLikeRepository;                                              // boardLike repo connect
    private final ImageFileRepository imageFileRepository;                                              // ImageFile repo connect
    private final S3Service s3Uploader;

    @Value("${cloud.aws.s3.bucket}")
    public String bucket;

    // Board Create function
    public BoardResponseDto createBoard(BoardRequestDto requestDto, User user,MultipartFile image) throws IOException {

        // 1. create board Object and insert DB
        Board board = new Board(requestDto, user);                                                      // DTO -> Entity
        boardRepository.save(board);

//        if(!image.isEmpty()) {
        if (image != null){
            s3Uploader.upload(image, "static", board, user);
        }

        return new BoardResponseDto(board,user.getUsername(), "");                                          // return Response  Entity -> DTO

    }

    // Get Boards from DB (all)
    public List<BoardResponseDto> getBoards() {
        // 1. find boardList
        List<Board> BoardList = boardRepository.findAllByOrderByModifiedAtAsc();                            // Select All
        List<BoardResponseDto> BoardResponseDtoList = new ArrayList<>();

        // 2. find commentList and add ResponseDto
        for(Board board: BoardList){
            List<Comment> comments = commentRepository.findAllByBoardOrderByIdAsc(board);
            List<CommentResponseDto> commentList = new ArrayList<>();

            Optional<ImageFile> Img = imageFileRepository.findByBoard(board);
            String Img_path = "";

            if(Img.isEmpty()){
                Img_path = "";
            }
            else{
                Img_path = Img.get().getPath();
            }

            for (Comment comment : comments){
                commentList.add(new CommentResponseDto(comment));
            }

            if(comments.isEmpty()){
                BoardResponseDtoList.add(new BoardResponseDto(board,board.getUser().getUsername(), Img_path));
            }else{
                BoardResponseDtoList.add(new BoardResponseDto(board,board.getUser().getUsername(), commentList, Img_path));
            }
        }
        return BoardResponseDtoList;
    }

    // Get memo from DB (one)
    public BoardResponseDto getBoard(long id){
        // 1. find board
        Board board = boardRepository.findById(id).orElseThrow(()->                                        // Select one
                new CustomException(ErrorCode.NO_POST_FOUND)
        );

        // 2. find comment
        List<Comment> comments = commentRepository.findAllByBoardOrderByIdAsc(board);
        List<CommentResponseDto> commentList = new ArrayList<>();

        Optional<ImageFile> Img = imageFileRepository.findByBoard(board);
        String Img_path = "";

        if(Img.isEmpty()){
            Img_path = "";
        }
        else{
            Img_path = Img.get().getPath();
        }

        // 3. add ResponseDto
        for (Comment comment : comments){
            commentList.add(new CommentResponseDto(comment));
        }
        if(comments.isEmpty()){
            return new BoardResponseDto(board,board.getUser().getUsername(), Img_path);                               // Entity -> DTO
        }else{
            return new BoardResponseDto(board, board.getUser().getUsername(),commentList, Img_path);
        }
    }

    // DB update function
    @Transactional
    public BoardResponseDto update(Long id, BoardRequestDto requestDto, User user) {
        // 1. 유저 권한 GET
        UserRoleEnum userRoleEnum = user.getRole();
        Board board;

        // 2. 유저 권한에 따른 동작 방식 결정
        if(userRoleEnum == UserRoleEnum.USER){
            board = boardRepository.findById(id).orElseThrow(                                               // find memo
                    () -> new CustomException(ErrorCode.NO_POST_FOUND)
            );

            if(board.getUser().getId().equals(user.getId())){                                               // 자기 자신이 작성한 게시물이면
                board.update(requestDto);                                                                   // DB Update
            }else{
                throw new CustomException(ErrorCode.NO_MODIFY_POST);
            }
        }else{                                                                                              // 관리자 권한일 때,
            board = boardRepository.findById(id).orElseThrow(                                               // find board
                    () -> new CustomException(ErrorCode.NO_POST_FOUND)
            );
            board.update(requestDto);
        }
        return new BoardResponseDto(board,user.getUsername(), "");
    }

    // DB delete function (data delete)
    public BoardDeleteResponseDto deleteBoard(Long id, User user) {
        // 1. 유저 권한 GET
        UserRoleEnum userRoleEnum = user.getRole();
        Board board;

        // 2. 유저 권한에 따른 동작 방식 결정
        if(userRoleEnum == UserRoleEnum.USER){
            board = boardRepository.findById(id).orElseThrow(                                               // find memo
                    () -> new CustomException(ErrorCode.NO_ROLLING_FOUND)
            );

            if(board.getUser().getId().equals(user.getId())){                                               // 자기 자신이 작성한 게시물이면
                boardLikeRepository.deleteAllByBoard(board);                                                // DB DELETE (선행)
                List<Comment> commentList = commentRepository.findAllByBoardOrderByIdAsc(board);
                for(Comment comment: commentList){
                    commentLikeRepository.deleteAllByComment(comment);
                }

                Optional<ImageFile> imageFile = imageFileRepository.findByBoard(board);

                if (imageFile.isPresent()){
                    String path =  imageFile.get().getPath(); //이미지 파일 path값 가져오기
                    String filename = path.substring(58); // path값에서 키값 추출
                    s3Uploader.deleteFile(filename); //키값으로 s3 삭제

                    imageFileRepository.deleteAllByBoard(board);
                }

                commentRepository.deleteAllByBoard(board);
                boardRepository.deleteById(id);                                                             // 해당 게시물 삭제
            }else{
                System.out.println("Error");
                throw new CustomException(ErrorCode.NO_DELETE_POST);

            }
        }else{                                                                                              // 관리자 권한일 떄
            board = boardRepository.findById(id).orElseThrow(                                               // find board
                    () -> new CustomException(ErrorCode.NO_POST_FOUND)
            );
            boardLikeRepository.deleteAllByBoard(board);
            List<Comment> commentList = commentRepository.findAllByBoardOrderByIdAsc(board);
            for(Comment comment: commentList){
                commentLikeRepository.deleteAllByComment(comment);
            }

            commentRepository.deleteAllByBoard(board);
            boardRepository.deleteById(id);
        }
        return  new BoardDeleteResponseDto("삭제 성공", HttpStatus.OK.value());
    }

}

 

 

오늘 크리스마스인데 젭인거 실화소니 ...?

 

 

 

1. 어려웠던 부분 : 첫 협업 프로젝트로 프론트엔드 3명, 백엔드 3명이 팀이 되었다. 그런데 프로젝트 시작 후 프론트엔드 두 분이 갑자기 하차하시게 되면서 처음에 기획했던 스코프를 축소해야 하는 상황이 되었다. 어려운 상황 속에서 하는 데까지 협업을 경험할 수 있는 걸로 이번주 목표를 변경했다. 백엔드끼리의 협업은 이미 지난주차에 경험한 터라 크게 어려운 부분이 없었는데, 프론트엔드와 붙이는 과정에서 생각지 못한 에러도 많이 터지고 특히 API명세와 다르게 작성된 코드들 때문에 그 부분을 수정하느라 고생을 많이 했다. 

 

2. 느낀 점 : 어느 분야나 커뮤니케이션이 정말 중요하다는 것을 느낀 한 주 였다. 조금 과하다 싶어도 좋으니 자주 팀 회의를 통해 서로의 작업 상황을 확인하고, 수정된 부분을 바로바로 업데이트하는 게 정말 중요한 것 같다.

 

3. 새로 알게 된 내용 : CORS해결 방법, 프로젝트 기획은 어떻게 진행되어야 하는지 ( 주제선정 - ERD/API - 기능분배 - 합쳐보기 - refactoring - 프론트엔드와 합치기 ... ), 그리고 프론트엔드와 기능별 작업이 완성되는 대로 미리미리 붙이는 작업을 해야 한다는 것을 배운 한 주였다. 

 

4. 이번주 잘한 일 : 이번주에 총 다섯 분이 하차하면서 41명으로 시작했던 b반이 23명이 되었다. 내가 나를 끊임없이 의심하고 다시 멘탈을 잡고 그렇게 끌고 왔던 49일. 항해 99를 시작으로 제로베이스로 시작해 지금까지 살아남은 사람들이 점점 줄어드는 걸 보면서, 이번엔 마음이 진짜 씨게 흔들렸다. 그래서 기술매니저님과 면담을 했다. 답정너지만 잘하고 있다는 말을 듣기 위해 찾아갔다. 스스로 의심이 들 때마다 지난주의 나와 이번주 나를 비교하면서 멘탈을 잘 잡자.

 

5. 다음주 할 일 : 클론 프로젝트 마무리 ( 소셜로그인, 페이징처리 정리하기)


[ 이번주 공부한 부분] 

 

< 미니 프로젝트 >

[01] 프로젝트 기획

[02] S3로 이미지 업로드 / 조회 / 삭제 구현하기

[03] 트러블 슈팅 / 아쉬웠던 부분

 

<JAVA>

[28] JAVA 멀티스레드

[29] JAVA 스레드 제어

[30] JAVA 컬렉션 프레임워크

[31] JAVA LIFO와 FIFO 컬렉션

 

 

 

1. 어려웠던 부분 : 오늘은 내가 맡은 기본 CRUD작업을 마무리하고 S3 업로드 후 수정하는 작업을 맡아서 진행했다. 오늘 점심부터 몸 컨디션이 급격하게 좋지 않아 자리에 앉아있는 것 자체가 너무 힘들었다. 팀원들에게 양해를 구하고 한시간 정도 쉬고 약먹고 다시 맡은 부분을 진행했다. 몸이 안좋으니 S3를 이용해 사진을 여러장 수정하는 코드를 짜는게 더 어렵게 느껴졌다. 결국 나중엔 팀원들의 도움으로 같이 화면공유를 하면서 코드를 완성 할 수 있었다.

 

2. 느낀 점 : 확실히 팀원들이랑 같이하면 막힌 문제도 답이 보이는 것 같다. 내가 놓치고 있던 부분을 다른 팀원들이 봐줄 수 있어서 좋은 것 같다.

 

3. 새로 알게 된 내용 : 지난 프로젝트땐 S3로 이미지를 한장만 업로드하는 작업을 했는데, 이번엔 여러장 등록하는 작업으로 확장했다. 개별 수정까지는 구현 못했지만, List<>를 이용해 다중 이미지 업로드하는 법을 배웠다. 업로드 부분을 다른 팀워분이 작업해 주셨는데, 덕분에 어떻게 구현해야 하는지 배울 수 있었다. 

 

4. 셀프칭찬 (오늘 잘한 일) : 몸 컨디션이 안좋았지만, 그래도 맡은 부분을 마무리한 나 칭찬한다.

 

5. 내일 할 일 : 지난주 미니 프로젝트 정리하기. (복습)


[오늘 공부한 부분] 

  • JAVA chap13 컬렉션
  • 클론 프로젝트 기본 CRUD 구현 완료, S3 부분 공부

[30] JAVA 컬렉션 프레임워크

 

[30] JAVA 컬렉션 프레임워크

컬렉션 프레임워크(Collection Framework) :자료구조를 사용해서 객체들을 효율적으로 추가, 삭제, 검색할 수 있도록 인터페이스와 구현 클래스를 java.util 패키지에서 제공하는 것 컬렉션 : 객체의 저

leejincha.tistory.com

[31] JAVA LIFO와 FIFO 컬렉션

 

[31] JAVA LIFO와 FIFO 컬렉션

후입선출(LIFO : Last In First Out) : 나중에 넣은 객체가 먼저 빠져나가는 자료구조 선입선출(FIFO : First In First Out) : 먼저 넣은 객체가 먼저 빠져나가는 자료구조 컬렉션 프레임워크에는 LIFO(리포) 자

leejincha.tistory.com

[01] WebSecurityConfig

 

[01] WebSecurityConfig

※ Config 파일 : 스프링 시큐리티 인증/ 인가를 다루는 파일 import 부분 @EnableGlobalMethodSecurity Use EnableMethodSecurity instead. Enables Spring Security global method security similar to the xml support. More advanced configuration

leejincha.tistory.com

[02] Airbnb Clone Coding (Main CRUD)

 

[02] Airbnb Clone Coding (Main CRUD)

전체 코드 중 주요 기능으로 잡은 에어비엔비 Room CRUD 부분을 정리해보려 한다. + 추가기능 ( 페이징 처리, 검색어 입력, S3, 게시글 좋아요, 비회원처리 ) Dto, Entity 등은 제외하고 Controlle / Repository

leejincha.tistory.com

 

 

 

 

1. 어려웠던 부분 : 클론코딩주차가 시작되었다. 현재 프론트엔드분들의 수가 부족해서 일주일 만에 두 명이 감당할 수 있는 스코프를 잡아야 했다. 기획회의를 통해 카톡과 슬랙과 같은 웹소켓을 이용한 채팅으로 갈 것인지 아니면 오늘의 집이나 에어비엔비, 마켓컬리와 같은 이커머스 서비스로 갈 것인지 고민을 하는데 시간이 좀 걸렸다. 프론트엔드분들의 UI 작업 수고를 덜어드리기 위해선 채팅서비스가 좋지만, 그걸 선택하기엔 백엔드에서 시도할 부분이 너무 없어서 결국 에어비엔비로 클론코딩을 진행하기로 했다.

 

2. 느낀 점 : 확실히 지난주에 머리가 깨져보니 이번주는 지난주보다 훨씬 수월하게 진행이 되는 느낌이다. 지난주의 실수를 반복하지 않기 위해 백엔드에서 먼저 API 명세 기본을 작성하고 다시 한번 프론트엔드 분들과 같이 API 명세를 수정하는 과정을 진행했다. 백엔드팀에선 서로 각자 맡은 부분을 테스트하기 편하게 로그인/회원가입 그리고 시큐리티 부분, 예외처리와 같이 전역적으로 사용되는 부분은 기본 세팅작업을 같이 진행했다. 역시 실수하고 깨져보는 경험이 중요한 것 같다.

 

3. 새로 알게 된 내용 : 클론코딩을 위해 다양한 사이트들을 구경하면서 예전에는 보이지 않던 세세한 부분들이 보이기 시작했다. 예를 들면, 에어비엔비 같은 경우 로그인/회원가입 모달창이 따로 분리되지 않고 로그인 또는 회원가입으로 하나로 진행된다. 그리고 지도를 클릭했을 때 내 주변 숙소 정보가 뜨는데 도대체 어떻게 구현하는 걸까.... 예전엔 생각 없이 편하게 이용하던 서비스들이 알고 보니 복잡하고 견고한 설계로 만들어져 있다. 과연 나도 이런 아키텍처를 고안할 수 있는 날이 올까?

 

4. 셀프칭찬 (오늘 잘한 일) : 오늘 각자 맡을 부분을 나누는데, 사실 마음 같아선 새로운 기능에 도전하고 싶지만 다들 마음이 그러하고 누군가는 지금까지 해왔던 기본 CRUD 작업을 맡아야 했다. 내가 기본 CRUD를 맡게 되었는데, 최대한 빨리 작업을 마치고 새로운 기능을 시도해 보려 한다. 배우려고 하는 나의 자세 칭찬해  ~_~ 

 

5. 내일 할 일 : 내가 맡은 부분 끝내기 + S3 공부하고 맡은 부분 (S3적용 후 글수정) 마무리


[오늘 공부한 부분] 

  • 스타트업 올웨이즈 세션
  • 클론프로젝트 기획 (ERD, API명세 작성, 기능 나누기)

+ Recent posts