프로젝트

blog 제작프로젝트

작성자: nam | 조회수: 69 | 작성일: 2025-09-09 13:34 | 수정일: 2025-10-11 06:30

blog 제작프로젝트





1. 프로젝트 개요


2. 프로젝트 목적 및 담당역할

프로젝트 목적

이 프로젝트는 DDD(도메인 주도 설계) 기반의 패키지 구조를 채택하여 개발된 개인 블로그 프로젝트입니다. 사용자 인증 및 권한 관리, 게시글 CRUD, 파일 업로드/관리, 댓글 기능, 그리고 배포 자동화까지 포괄하는 풀스택 웹 애플리케이션입니다.

기획의도 및 목표

  • 실사용을 위한 블로그제작 : 앞으로 계속 발전시키면서 실제로 사용할 블로그를 만드는것을 목표로 했습니다
  • 실전 개발 경험 축적: Spring Boot, Spring Security, JPA 등 핵심 프레임워크 기술을 깊이 이해하고 실제 프로젝트에 적용하는 것을 목표로 했습니다.
  • 지속 가능한 코드 구조: DDD 원칙을 도입하여 도메인별로 책임이 명확한 패키지 구조를 설계하고, 유지보수와 확장성을 고려한 아키텍처를 구축하는 데 집중했습니다.
  • CI/CD 환경 구축: 개발부터 배포까지의 전 과정을 자동화하여, 효율적인 개발 파이프라인을 구축하고 실제 서비스 배포 경험을 쌓고자 했습니다.



3. 개발환경 및 기술 스택


분류 내용
운영체제 Windows 11, Linux(Ubuntu)
서버 Nginx,Apache Tomcat
데이터베이스 MySQL
파일 스토리지 MiniO(S3 호환 로컬 스토리지)
개발 툴 IntelliJ IDEA,DBeaver,MySql WorkBench
협업/디자인/문서 GitHub, Notion, Google Drive
언어 Java 17, HTML5, CSS, JavaScript, SQL
백엔드 프레임워크 Spring Boot, Spring Web, Spring Security, Spring Boot Devtools,Spring Boot Validation, Spring Data JPA
라이브러리 Summernote
데이터/빌드/유틸 Gradle, Gson, Lombok,Commons IO
프론트엔드 Thymeleaf, jQuery, Ajax, Bootstrap
통신/외부 API RESTful API
인프라 Docker,Docker Compose,GitHub Actions


새롭게 사용해본 기술

  • MiniO
    • AWS S3 API 와 호환되는 로컬 파일스토리지인 MiniO를 사용해서 파일서버를 구축하고 파일 업로드,조회,삭제기능을 구현했습니다
  • Summernote
    • 오픈소스 WYSIWYG 에디터인 Summernote 에디터를 사용해서 글 작성기능과 이미지를 첨부하는 기능 개발시간을 단축했습니다


프로젝트 아키텍쳐

ERD



API 명세서

메소드URL설명
GET/join회원가입 페이지
GET/login로그인 페이지
POST/user/join회원가입 처리
GET/user/update/{email}회원정보 수정 페이지
POST/user/update/{email}회원정보 수정 처리
GET/post/{categoryName}카테고리별 게시글 목록
GET/post/{categoryName}/createpost글 작성 페이지
POST/post/{categoryName}/createpost글 작성 처리
POST/post/{categoryName}/deletePost/{id}글 삭제 처리
GET/post/{categoryName}/updatepost/{id}글 수정 페이지
POST/post/{categoryName}/updatepost/{id}글 수정 처리
POST/post/{categoryName}/updatepost/{id}/verify비회원 글 수정 비밀번호 확인
GET/post/{categoryName}/{id}특정 게시글 조회
GET/post/{categoryName}/{id}/delete-verify비회원 글 삭제 비밀번호 확인 페이지
POST/post/{categoryName}/{id}/delete-verify비회원 글 삭제 처리
POST/api/comments댓글 작성
GET/api/comments/count/{postId}댓글 개수 조회
DELETE/api/comments/guest/{commentId}비회원 댓글 삭제
GET/api/comments/post/{postId}특정 게시글의 댓글 조회
PUT/api/comments/{commentId}댓글 수정
DELETE/api/comments/{commentId}댓글 삭제 (회원)
POST/uploadSummernoteImageFileSummernote 에디터 이미지 업로드
POST/deleteSummernoteImageFileSummernote 에디터 이미지 삭제
GET/메인 페이지
/error전역 에러 처리 페이지



4.주요기능

회원기능

  • 세션 기반 회원가입
  • 회원의 비밀번호,닉네임 수정기능

게시판기능

  • 회원게시판, 비회원게시판 분리
  • 목록조회,CRUD 기능
  • 게시글 세션기반 조회수 중복 방지
  • 글 작성시 이미지 첨부 기능

댓글기능

  • AJAX를 사용한 회원/비회원 댓글 CRUD



5.구현


회원

  • UserService는 회원가입 시 중복 이메일을 검사하고, 비밀번호를 BCrypt로 암호화하여 저장합니다
  • Spring Security의 UserDetailsService를 구현해 로그인 시 사용자 정보를 로드하고 역할(ROLE_USER, ROLE_ADMIN)을 부여합니다
  • UserController는 /join/login/user/update/{email} 경로에 대한 가입·로그인·정보수정 페이지를 제공하며, 수정 시 권한 검사를 통해 본인 또는 관리자만 접근 가능하게 합니다
  • SecurityConfig는 CSRF 토큰을 사용하고, 비밀번호 인코더 및 역할 계층(RoleHierarchy)을 등록하여 관리자→회원 순 권한 상속을 구성합니다

게시판

  • 게시글은 PostEntity가 사용자·비회원·카테고리와의 연관관계를 갖고 있으며, 댓글과 조회수 필드를 포함합니다
  • PostController는 카테고리 기반 URL(/post/{categoryName})을 사용해 목록·상세·작성·수정·삭제를 제공하며, 권한에 따라 글 작성 가능 여부를 제한합니다
  • PostService는 카테고리별 조회, 비회원·회원 구분 게시글 작성, 조회수 중복 방지(ViewedPostsHolder) 등을 처리합니다
  • PostRepository는 게시글별 댓글 수를 조회하는 커스텀 쿼리를 제공하여 목록 조회 시 댓글 개수를 함께 보여줍니다

글작성

  • 글 작성 시 Summernote 에디터에서 업로드된 이미지를 임시 폴더에서 MinIO로 이동시키며, 게시글 본문의 <img> URL을 MinIO 경로로 교체합니다
  • 비회원 글은 닉네임·비밀번호를 별도의 GuestUserEntity에 저장하고 게시글과 연관지어 비밀번호 확인 후 수정·삭제가 가능하도록합니다

댓글기능

  • CommentService는 회원·비회원 댓글을 모두 지원하며, 비회원 댓글은 닉네임·비밀번호를 저장 후 수정·삭제 시 비밀번호 검증을 수행합니다
  • 댓글 CRUD와 댓글 개수 조회를 제공합니다

이미지처리

  • FileController는 Summernote 이미지 업로드를 임시 디렉터리에 저장하고,글 작성중 삭제 요청 시 임시 파일을 제거합니다
  • MinioService는 MinIO 버킷에 이미지를 업로드·삭제하며 URL을 반환합니다
  • FileService의 스케줄러는 24시간 이상 지난 임시 이미지 파일을 매일 03시에 정리합니다




6.배포 및 인프라구축

  • CI/CD
    • 개발자가 ‘Main’ 브랜치에 푸시
    • GitHub Action Self-Hosted Runner 에서 작동
    • GitHub에 올려진 코드로 그래들 빌드
    • 그래들 빌드된 JAR를 도커 이미지로 빌드후 도커 허브에 푸시
    • SSH로 서버에서 도커 허브 이미지 풀
    • GitHub Secrets 에 있는 application.yml,docker-compose.yml을 서버에서 생성
    • 도커 컴포즈 실행
  • 보안 관리: application.yml 파일의 민감한 정보(DB 패스워드 등)는 GitHub Actions Secrets로 관리하여 보안을 강화했습니다. 애플리케이션 그래들 빌드할때와 도커 이미지,도커 허브에 보안사항이 포함되지 않도록 애플리케이션 실행시에 외부참조방식으로 진행했습니다
  • Nginx리버스 프록시로 설정하여 HTTP/HTTPS 요청을 애플리케이션 컨테이너로 전달하며, 서비스는 서브도메인으로 구분하여 운영합니다.
  • Let's EncryptCertbot을 이용해 SSL/TLS 인증서 자동 발급 및 갱신합니다



7.문제 해결

  • 사용자가 글 작성중에 이미지를 첨부하는 순간 바로 이미지 파일서버에 저장되는 부분
    • 사용자가 글작성중에 이미지를 첨부하면 바로 이미지 파일서버로 전송되서 저장되어 버리는 부분을 개선하는 방안이 필요했습니다
    • 사용자가 글작성중에 첨부한 이미지는 애플리케이션 에서 먼저 임시파일로 저장하고 사용자가 글 작성을 완료했을때 이미지가 이미지 파일서버로 전송되도록 구조 를 변경하고 애플리케이션에 있는 임시파일은 03시마다 지워지게 했습니다


  • 조회수가 글을 조회할때마다 증가하는 부분
    • 같은 사용자가 글 조회시 한번만 증가해야하는데 글을 조회할때마다 계속 증가하는 로직을 개선하고싶었습니다
    • 세션에 ViewPostHodler 라는 인스턴스를 생성하고 인스턴스에 사용자가 조회한 게시물의 id를 저장하고 PostService에서 사용자가 조회한 게시물이 인스턴스에 없는경우 조회수를 증가시켜 DB에 저장하도록 했습니다


  • 1MB 넘는 이미지 저장과 조회 불가
    • 1MB 미만 이미지는 저장과 조회가 가능하나 1MB가 초과하는 순간 저장할수없는 문제가 생겼습니다
    • Nginx 설정에서 Body 사이즈가 기본값이 1MB이기때문에 HTTP 요청 본문의 크기가 1MB를 넘어가면 전송이 안되는 문제였습니다
    • Nginx 설정에 client_max_body_size를 입력후 문제 해결했습니다


  • 도커 컨테이너 DNS 해석 문제
    • 전날까지 잘 작동하던 애플리케이션의 이미지 조회와 이미지 첨부 기능에 문제가 생겼습니다
    • 개발환경에서 로컬서버 구동시에는 이미지 조회와 이미지 첨부기능이 정상작동하는것을 확인했습니다
    • 도커 컨테이너 에서 네트워크환경 점검을 해보니 이미지파일서버의 주소를 해석을 못하는것을 확인했습니다
    • 다른 컴퓨터에서 도메인과 DDNS 주소 점검시 정상임을 확인했습니다
    • 도커 컴포즈 실행시에 dns 지정후 실행해보니 주소 해석 실패했습니다
    • 도커 컨테이너 네트워크를 호스트네트워크로 변경후 실행해보니 실패했습니다
    • 서버 네트워크에 reslov.conf 에 dns추가후 실행 해보니 실패했습니다
    • 문득 도커 파일 생성시에 경량화 기반인openjdk:17-jdk-alpine 이미지로 빌드 한다는것이 생각났습니다
    • 찾아보니 Alpine은 musl libc 라는 데비안 계열과는 다른 라이브러리를 사용하는데 구 버젼의 경우 DNS TCP Fallback 문제가 있는것을 알았습니다
    • 데비안 계열 openjdk:17-jdk-slim 으로 도커 이미지 빌드후 문제 해결 완료했습니다



8. 스크린샷

  • 메인화면


  • 프로젝트 게시판

    • 로그인화면


    • 회원가입화면


    • 회원자유게시판


    • 회원 글 작성


    • 회원 글 수정


    • 회원 글 보기


    • 회원 댓글 수정


    • 비회원 자유게시판


    • 비회원 글작성


    • 비회원 글 수정시 비밀번호 검증화면


    • 비회원 글 수정


    • 비회원 글 삭제 검증 화면


    • 비회원 글보기


    • 비회원 댓글작성


    • 비회원 댓글수정



    댓글 작성

    0 / 1000자

    댓글 목록 (0)