1월 12일 개발일지

2024. 1. 12. 17:35스파르타/개발일지

 

 

 

 

오늘 팀프로젝트 마무리를 하는 단계이다.

 

 

 

 

우리 팀은 개발 초보들의 배움을 공유하는 공간인 Co-Ha 뉴스피드 프로젝트를 기획했다.

 

 

 

 

 

팀 과제에서 필수 구현 기능 중에 하나인 게시물 CRUD 기능은 팀원들과 하나씩 분담해서 구현하기로 했다.

 

 

 

마찬가지로 댓글 CRUD 기능도 팀원들과 분담해서 각자 역할을 따로 두었다.

 

 

 

뉴스피드 상세 기획으로는 메인페이지와 전체조회 페이지를 나누었으며,

메인 페이지에는 최신 게시글 5개씩 보이도록 하고,

디테일 페이지는 게시글의 내용과 댓글을 볼 수 있는 페이지를 만들기로 기획했다.

 

 

 

 

추가 구현 기능에는 이렇게 하기로 예시를 만들어 두었다.

- 사용자 인증/ 인가 기능

- 좋아요 기능

- 사진 업로드 기능

세 가지를 먼저 구현해보기로 했다.

좋아요 같은 경우 조회수 기능 구현을 하기로 변경되었다.

 

 

 

기획단계에서 다음 단계로 넘어와서 API 설계를 작성해주었다.

 

 

 

 

게시글 CRUD, 댓글 CRUD 기능에 대한 API 설계이다.

 

 

 

추가 기능에 대한 API 명세 작성을 했다.

 

 

 

 

ERD 작성도 했다.

ERD를 설계하기 위해서는 아래에 해당되는 속성을 정의하고 ERD 작성을 해야한다.

E(Entity. 개체) 서비스에서 필요로 하는 데이터를 담을 개채를 식별하도록 한다

ex. 사용자, 게시물, 댓글 등이 될 수 있음

A(Attribute. 속성) 각 객체가 가지고 있는 속성을 정의할 수 있다.

ex. 사용자 개체는 이름, 로그인 아이디, 비밀번호 등의 속성을 가질 수 있다.

R(Relationship. 관계) 개체들 사이의 관계를 정의하도록 한다.

ex. 사용자와 게시물 사이에는 작성하다라는 관계가 있다.

 

 

 

와이어프레임도 작성해주었다.

와이어프레임을 그리면서 UI/UX 설계를 하고, 기능 목록에 빠진 기능은 없는지 체크하고

어떠한 기능이 있으면 더 좋을 것 같은지에 대해 구상을 해볼 수 있다.

 

 

 

 

 

내가 작성한 코드에 대해서 설명을 하려고 한다.

 

 

 

PostController

fun createPost(
   @RequestPart data: CreatePostRequest, // 게시물 생성에 필요한 데이터를 나타내는 객체
   @RequestPart("image") image: MultipartFile? //게시물에 첨부될 이미지 파일
): ResponseEntity<Boolean> {

    // postservice를 사용하여 게시물을 생성하는 메소드를 호출함
    // 해당 메소드는 data와 image를 파라미터로 받아서 게시물을 생성하고 저장할 수 있음
    postService.createPost(data,image)

    //ResponseEntity를 사용해서 HTTP 응답을 생성
    //Http.Status.CREATED 는 HTTP 상태코드 201 Created를 나타냄
    //body에는 true를 담아 클라이언트에게 성공적으로 처리되었음을 보여줌
    return  ResponseEntity.status(HttpStatus.CREATED).body(true)
}

 

createPost에서 게시글 생성에 필요한 데이터를 나타내는 객체와

첨부될 이미지 파일에 관련된 코드를 넣었다.

 

fun deletePost(@PathVariable postId: Long): ResponseEntity<String> {

    // postService를 사용하여 주어진 postId에 해당하는 게시글을 삭제하는 메소드 호출
    postService.deletePost(postId)
    // 게시글 삭제가 성공하면 "게시글이 성공적으로 삭제되었습니다."라는 메세지 생성
    val deletePostSuccessMessage = "게시글이 성공적으로 삭제되었습니다."
    
    //ResponseEntity를 사용해서 HTTP 응답을 생성
    //HttpStatus.OK는 HTTP 상태코드 200을 나타냄
    //body에는 삭제 성공 메세지를 담아 클라이언트에게 보여줌
    return ResponseEntity
            .status(HttpStatus.OK)
            .body(deletePostSuccessMessage)
}

 

deletePost에는 게시글을 삭제하는 메소드를 호출하고

게시글 삭제가 성공하면 클라이어트에게 생성된 메세지가 보임

 

 

 

CreatePostRequest

// 게시글 생성 요청에 사용되는 데이터 클래스인 CreatePostRequest 정의
data class CreatePostRequest(
    val title: String,
    val name: String,
    val content: String,
    // val image: MultipartFile? //이미지를 받아올 필드
) {
    fun toPost(url:String, currentUser:String): Post { //request받아온 걸 post로 변환
        //post로 객체를 생성하고 반환
        return Post(
            title = title,
            name = name,
            content = content,
            author = currentUser,
            view = 0,
            imagePath = url
        )
    }
}

 

 

 

FileStorageService

//파일 저장 및 로드 서비스를 정의하는 인터페이스인 FileStorageService
interface FileStorageService {

    // MultipartFile을 받아 파일을 저장하고, 저장된 파일의 경로를 반환하는 메소드
    fun storeFile(file: MultipartFile): String
    // 주어진 파일 이름을 기반으로 파일을 로드하고 Resource 형태로 반환하는 메소드
    fun loadFile(filename: String): Resource
}

 

 

 

FileStorageServiceImpl

 

@Service
class FileStorageServiceImpl(
        @Value("\${file.upload-dir}") //파일 업로드 디렉토리 경로를 외부 설정 파일(application.yml)에서 읽어와서 uploadDir 변수에 주입함
        private val uploadDir: String
) : FileStorageService {

    // 생성자에서 초기화 수행
    init {
        //업로드 디렉토리가 없으면 생성
        //업로드 디렉토리의 경로를 나타내는 객체를 생성
        val uploadPath = Paths.get(uploadDir)
        //업로드 디렉토리가 존재하지 않는다면
        if (!Files.exists(uploadPath)) {
            // 업로드 디렉토리를 생성함
            Files.createDirectories(uploadPath)
        }
    }

 

 

파일 업로드 디렉토리를 설정하고,

업로드 디렉토리가 없으면 생성하는 초기화 로직이 포함되어 있다.

val uploadPath = Paths.get(uploadDir) : 업로드 디렉토리의 경로를 Path 객체에 생성

if ~ : 업로드 디렉토리가 존재하지 않으면 해당 디렉토리를 생성하는 조건문

 

 

//MultipartFile을 받아서 파일을 저장하고, 저장된 파일의 이름을 반환하는 메소드
override fun storeFile(file: MultipartFile): String {
    //파일이름을 생성하고 저장경로를 설정
    val fileName = generateFileName(file.originalFilename!!)
    //저장될 파일의 전체 경로를 나타내는 객체 생성
    val targetLocation = Paths.get(uploadDir, fileName)

    //파일을 지정된 경로로 복사
    //file.inputStream은 업로드된 파일의 내용을 읽어오는 InputStream을 제공
    //StandardCopyOption.REPLACE_EXISITING은 동일한 이름의 파일이 이미 존재하면 덮어쓰기 옵션
    Files.copy(file.inputStream, targetLocation, StandardCopyOption.REPLACE_EXISTING)
    //저장된 파일의 이름을 반환
    return fileName
}

 

 

이 메소드는 주어진 MultipartFile을 받아서 파일을 업로드 디렉토리에 저장하고,

파일의 이름을 반환하는 역할을 한다

val fileName = generateFileName(file.originalFilename!!) : 업로드된 파일의 원래 이름에서 새로운 파일 이름을 생성하기 위해 generateFileName 함수를 호출한다

val targetLocation = Paths.get(uploadDir, fileName : 저장된 파일의 전체 경로를 나타내는 Path 객체를 생성, uploadDir은 업로드 디렉토리의 경로이고 fileName은 새로 생성된 파일의 이름이다.

Files.copy(file.inputStream, targetLocation, StandardCopyOption.REPLACE_EXISTING) : Files.copy 메소드를 사용해서

업로드된 파일의 내용을 지정된 경로로 복사한다.

file.inputStream은 업로드된 파일의 내용을 읽어오는 InputStream을 제공하고,

targetLocation은 파일이 실제로 저장될 경로이다.

StandardCopyOption.REPLACE_EXISTING은 동일한 이름의 파일이 존재하면 덮어쓰기 옵션이다.

return fileName은 저장된 파일의 이름을 반환한다.

 

 

 

//주어진 파일 이름을 기반으로 파일을 로드하고 Resource 형태로 반환하는 메소드
override fun loadFile(filename: String): Resource {
    //파일의 전체 경로를 나타내는 객체로 생성
    val filePath = Paths.get(uploadDir).resolve(filename).normalize()
    // UrlResource를 사용하여 파일의 경로를 URI로 변환한 후 Resource 형태로 생성
    val resource: Resource = UrlResource(filePath.toUri())

    //로드한 파일이 존재하면 해당 Resource를 반환, 없으면 예외처리
    if (resource.exists()) {
        return resource
    } else {
        //파일이 존재하지 않는 경우 RuntimeException 예외를 던짐
        throw RuntimeException("File not found : $filename")
    }
}

 

val filePath = Paths.get(uploadDir).resolve(filename).normalize()은 로드할 파일의 전체 경로를 나타내는 Path 객체를 생성한다. uploadDir은 업로드 디렉토리의 경로이고, filename은 로드할 파일의 이름이다.

resolve 함수는 경로를 결합하고, normalize 함수는 경로를 정규화한다.

val resource: Resource = UrlResource(filePath.toUri())은 UrlResource를 사용해서 파일의 경로를 URI로 변환한 후 Resource 형태로 생성한다. 이를 통해 파일에 접근할 수 있는 Resource 객체를 얻는다.

if~ : 로드한 파일이 존재하면 해당 Resource를 반환하고 파일이 존재하지 않으면 RuntimeException 예외를 던진다.

파일이 존재하지 않는 경우 예외 처리를 통해 상황을 알 수 있다.

 

 

 

 

 

 

 

ReplyController

fun creatReply(

        //요청 본문에 있는 데이터를 나타내는 createReplyRequest 객체를 받음
        @RequestBody createReplyRequest: CreateReplyRequest,
        //현재 인증된 사용자 정보를 나타내는 Principal 객체를 받음
        principal: Principal
): ResponseEntity<ReplyResponse> {
    // ReplyService를 사용하여 댓글 생성 메소드를 호출하고 결과값을 받음
    val result = replyService.creatReply(createReplyRequest)

    //HTTP 응답을 생성하여 클라이언트에게 결과를 반환함
    return ResponseEntity
            .status(HttpStatus.CREATED)
            .body(replyService.creatReply(createReplyRequest))

 

ReplyService를 사용해서 댓글 생성 메소드를 호출하고 결과값을 반환한다.

 

 

 

fun deleteReply(
        @PathVariable replyId: Long
): ResponseEntity<String> {

    //ReplyService를 사용하여 주어진 댓글 ID에 해당하는 댓글을 삭제
    replyService.deleteReply(replyId)
    // 댓글 삭제를 성공했을 때 알리는 메세지
    val deleteReplySuccessMessage = "댓글이 성공적으로 삭제되었습니다."

    //HTTP 응답을 생성하여 클라이언트에게 결과 메세지를 반환
    return ResponseEntity
            .status(HttpStatus.OK)
            .body(deleteReplySuccessMessage)
}

 

deleteReply에서는 ReplyService를 사용해서 replyId에 해당하는 댓글을 삭제하고

댓글 삭제를 성공했을 경우 클라이언트에게 결과 메세지를 반환함

 

 

 

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "post_id")
var post: Post,

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

'스파르타 > 개발일지' 카테고리의 다른 글

1월 16일 개발일지  (0) 2024.01.16
1월 15일 개발일지  (0) 2024.01.15
1월 11일 개발일지  (3) 2024.01.11
1월 10일 개발일지  (1) 2024.01.10
1월 9일 개발일지  (3) 2024.01.09