From 47dfbc6ba81144429089457c552ae7f30abfbc0b Mon Sep 17 00:00:00 2001 From: mino Date: Sat, 8 Nov 2025 23:54:17 +0900 Subject: [PATCH] =?UTF-8?q?feat=20:=20=EB=B8=94=EB=A1=9C=EA=B7=B8=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=A1=B0=ED=9A=8C=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B5=AC=ED=98=84=20(#27)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 27 ++++++++++ .../blog/controller/BlogController.java | 53 +++++++++++++++++++ .../blog/dto/response/BlogResponse.java | 43 +++++++++++++++ .../domain/blog/entity/BlogAuthor.java | 22 ++++++++ .../website/domain/blog/entity/BlogPost.java | 35 ++++++++++++ .../website/domain/blog/entity/Category.java | 16 ++++++ .../website/domain/blog/entity/Position.java | 16 ++++++ .../repository/BlogPostQueryRepository.java | 11 ++++ .../BlogPostQueryRepositoryImpl.java | 46 ++++++++++++++++ .../blog/repository/BlogPostRepository.java | 7 +++ .../domain/blog/service/BlogService.java | 33 ++++++++++++ .../website/global/config/QuerydslConfig.java | 19 +++++++ 12 files changed, 328 insertions(+) create mode 100644 src/main/java/com/kusitms/website/domain/blog/controller/BlogController.java create mode 100644 src/main/java/com/kusitms/website/domain/blog/dto/response/BlogResponse.java create mode 100644 src/main/java/com/kusitms/website/domain/blog/entity/BlogAuthor.java create mode 100644 src/main/java/com/kusitms/website/domain/blog/entity/BlogPost.java create mode 100644 src/main/java/com/kusitms/website/domain/blog/entity/Category.java create mode 100644 src/main/java/com/kusitms/website/domain/blog/entity/Position.java create mode 100644 src/main/java/com/kusitms/website/domain/blog/repository/BlogPostQueryRepository.java create mode 100644 src/main/java/com/kusitms/website/domain/blog/repository/BlogPostQueryRepositoryImpl.java create mode 100644 src/main/java/com/kusitms/website/domain/blog/repository/BlogPostRepository.java create mode 100644 src/main/java/com/kusitms/website/domain/blog/service/BlogService.java create mode 100644 src/main/java/com/kusitms/website/global/config/QuerydslConfig.java diff --git a/build.gradle b/build.gradle index 84489a8..0938979 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,13 @@ +buildscript { + ext { + queryDslVersion = "5.0.0" + } +} plugins { id 'java' id 'org.springframework.boot' version '2.7.8' id 'io.spring.dependency-management' version '1.1.0' + id "com.ewerk.gradle.plugins.querydsl" version "1.0.10" } group = 'com.kusitms' @@ -43,8 +49,29 @@ dependencies { //json dependency implementation group: 'org.json', name: 'json', version: '20220320' implementation group: 'com.googlecode.json-simple', name: 'json-simple', version: '1.1.1' + + //queryDSL 의존성 추가 + implementation "com.querydsl:querydsl-jpa:${queryDslVersion}" + implementation "com.querydsl:querydsl-apt:${queryDslVersion}" + } +//--------------------queryDSL 관련 추가---------------------------- +def querydslDir = "$buildDir/generated/querydsl" + +querydsl { + jpa = true + querydslSourcesDir = querydslDir +} +sourceSets { + main.java.srcDir querydslDir +} +configurations { + querydsl.extendsFrom compileClasspath +} +compileQuerydsl { + options.annotationProcessorPath = configurations.querydsl +} tasks.named('test') { useJUnitPlatform() diff --git a/src/main/java/com/kusitms/website/domain/blog/controller/BlogController.java b/src/main/java/com/kusitms/website/domain/blog/controller/BlogController.java new file mode 100644 index 0000000..e6237e5 --- /dev/null +++ b/src/main/java/com/kusitms/website/domain/blog/controller/BlogController.java @@ -0,0 +1,53 @@ +package com.kusitms.website.domain.blog.controller; + +import com.kusitms.website.domain.blog.dto.response.BlogResponse; +import com.kusitms.website.domain.blog.entity.BlogPost; +import com.kusitms.website.domain.blog.entity.Category; +import com.kusitms.website.domain.blog.entity.Position; +//import com.kusitms.website.domain.blog.service.BlogService; +import com.kusitms.website.domain.blog.service.BlogService; +import com.kusitms.website.domain.project.dto.response.MeetupDetailResponse; +import com.kusitms.website.domain.project.dto.response.MeetupResponse; +import com.kusitms.website.global.common.BaseResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping("/api/blogs") +@RequiredArgsConstructor +@Tag(name = "Blog", description = "블로그 API Document") +public class BlogController { + + private final BlogService blogService; + + @GetMapping("") + @Operation(summary = "블로그 리스트 조회", description = "기수, 파트, 카테고리 조건으로 블로그 리스트를 조회합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "조회 성공", + content = @Content(schema = @Schema(implementation = MeetupResponse.class))), + @ApiResponse(responseCode = "200", description = "조회 성공", + content = @Content(schema = @Schema(implementation = MeetupDetailResponse.class))) + }) + public ResponseEntity getBlogList( + @RequestParam(required = false) Integer generation, + @RequestParam(required = false) Position position, + @RequestParam(required = false) Category category, + @RequestParam(required = false) Long lastId, + @RequestParam(defaultValue = "10") int size + ) { + List result = blogService.getFilteredPostsWithPaging(generation, position, category, lastId, size); + return ResponseEntity.ok(new BaseResponse(result)); + } +} diff --git a/src/main/java/com/kusitms/website/domain/blog/dto/response/BlogResponse.java b/src/main/java/com/kusitms/website/domain/blog/dto/response/BlogResponse.java new file mode 100644 index 0000000..fa5c722 --- /dev/null +++ b/src/main/java/com/kusitms/website/domain/blog/dto/response/BlogResponse.java @@ -0,0 +1,43 @@ +package com.kusitms.website.domain.blog.dto.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.kusitms.website.domain.blog.entity.BlogAuthor; +import com.kusitms.website.domain.blog.entity.BlogPost; +import com.kusitms.website.domain.blog.entity.Category; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +import javax.persistence.*; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class BlogResponse { + @JsonProperty("blog_post_id") + @Schema(description = "블로그 글 아이디") + private Long id; + @Schema(description = "블로그 글 제목") + private String title; + @Schema(description = "블로그 카테고리 이름") + private Category category; + @Schema(description = "블로그 주소") + private String address; + @Schema(description = "블로그 이미지 주소") + private String imageAddress; + @Schema(description = "블로그 글 내용") + private String content; + + public static BlogResponse fromEntity(BlogPost post) { + return BlogResponse.builder() + .id(post.getId()) + .title(post.getTitle()) + .category(post.getCategory()) + .address(post.getAddress()) + .imageAddress(post.getImageAddress()) + .content(post.getContent()) + .build(); + } + + +} diff --git a/src/main/java/com/kusitms/website/domain/blog/entity/BlogAuthor.java b/src/main/java/com/kusitms/website/domain/blog/entity/BlogAuthor.java new file mode 100644 index 0000000..4ec503d --- /dev/null +++ b/src/main/java/com/kusitms/website/domain/blog/entity/BlogAuthor.java @@ -0,0 +1,22 @@ +package com.kusitms.website.domain.blog.entity; + +import lombok.Getter; + +import javax.persistence.*; + +@Entity +@Getter +public class BlogAuthor { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "blog_author_id", nullable = false) + private Long id; + + @Column(length = 20) + private String name; + + private Integer generation; + + @Enumerated(EnumType.STRING) + private Position position; + +} diff --git a/src/main/java/com/kusitms/website/domain/blog/entity/BlogPost.java b/src/main/java/com/kusitms/website/domain/blog/entity/BlogPost.java new file mode 100644 index 0000000..bf9fcd8 --- /dev/null +++ b/src/main/java/com/kusitms/website/domain/blog/entity/BlogPost.java @@ -0,0 +1,35 @@ + package com.kusitms.website.domain.blog.entity; + + import lombok.Getter; + + import javax.persistence.*; + import java.time.LocalDateTime; + + @Entity + @Getter + public class BlogPost { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "blog_post_id", nullable = false) + private Long id; + + @ManyToOne + @JoinColumn(name = "blog_author_id") + private BlogAuthor blogAuthor; + + @Column(length = 100) + private String title; + + @Enumerated(EnumType.STRING) + private Category category; + + @Column(length = 512) + private String address; + + @Column(length = 512) + private String imageAddress; + + @Column(columnDefinition = "TEXT") + private String content; + + } diff --git a/src/main/java/com/kusitms/website/domain/blog/entity/Category.java b/src/main/java/com/kusitms/website/domain/blog/entity/Category.java new file mode 100644 index 0000000..5fcbae8 --- /dev/null +++ b/src/main/java/com/kusitms/website/domain/blog/entity/Category.java @@ -0,0 +1,16 @@ +package com.kusitms.website.domain.blog.entity; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum Category { + DOCUMENT("서류 후기"), + INTERVIEW("면접 후기"), + GIFT("기프 후기"), + MEETUP("밋업 후기"), + GROUP_TF("소모임/TF 후기"); + + private final String description; +} diff --git a/src/main/java/com/kusitms/website/domain/blog/entity/Position.java b/src/main/java/com/kusitms/website/domain/blog/entity/Position.java new file mode 100644 index 0000000..03b77f7 --- /dev/null +++ b/src/main/java/com/kusitms/website/domain/blog/entity/Position.java @@ -0,0 +1,16 @@ +package com.kusitms.website.domain.blog.entity; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum Position { + FRONTEND("프론트엔드"), + BACKEND("백엔드"), + PLAN("기획"), + DESIGNER("디자이너"); + + private final String description; + +} diff --git a/src/main/java/com/kusitms/website/domain/blog/repository/BlogPostQueryRepository.java b/src/main/java/com/kusitms/website/domain/blog/repository/BlogPostQueryRepository.java new file mode 100644 index 0000000..7829916 --- /dev/null +++ b/src/main/java/com/kusitms/website/domain/blog/repository/BlogPostQueryRepository.java @@ -0,0 +1,11 @@ +package com.kusitms.website.domain.blog.repository; + +import com.kusitms.website.domain.blog.entity.BlogPost; +import com.kusitms.website.domain.blog.entity.Category; +import com.kusitms.website.domain.blog.entity.Position; + +import java.util.List; + +public interface BlogPostQueryRepository { + List findByFiltersWithPaging(Integer generation, Position position, Category category, Long lastId, int size); +} diff --git a/src/main/java/com/kusitms/website/domain/blog/repository/BlogPostQueryRepositoryImpl.java b/src/main/java/com/kusitms/website/domain/blog/repository/BlogPostQueryRepositoryImpl.java new file mode 100644 index 0000000..075d644 --- /dev/null +++ b/src/main/java/com/kusitms/website/domain/blog/repository/BlogPostQueryRepositoryImpl.java @@ -0,0 +1,46 @@ +package com.kusitms.website.domain.blog.repository; + +import com.kusitms.website.domain.blog.entity.*; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@RequiredArgsConstructor +public class BlogPostQueryRepositoryImpl implements BlogPostQueryRepository { + private final JPAQueryFactory queryFactory; + private final QBlogPost blogPost = QBlogPost.blogPost; + private final QBlogAuthor blogAuthor = QBlogAuthor.blogAuthor; + + @Override + public List findByFiltersWithPaging(Integer generation, Position position, Category category, Long lastId, int size) { + + BooleanBuilder builder = new BooleanBuilder(); + + if (generation != null) { + builder.and(blogAuthor.generation.eq(generation)); + } + + if (position != null) { + builder.and(blogAuthor.position.eq(position)); + } + + if (category != null) { + builder.and(blogPost.category.eq(category)); + } + + if (lastId != null) { + builder.and(blogPost.id.lt(lastId)); + } + + return queryFactory + .selectFrom(blogPost) + .join(blogPost.blogAuthor, blogAuthor) + .where(builder) + .orderBy(blogPost.id.desc()) + .limit(size) + .fetch(); + } +} diff --git a/src/main/java/com/kusitms/website/domain/blog/repository/BlogPostRepository.java b/src/main/java/com/kusitms/website/domain/blog/repository/BlogPostRepository.java new file mode 100644 index 0000000..c3d65fd --- /dev/null +++ b/src/main/java/com/kusitms/website/domain/blog/repository/BlogPostRepository.java @@ -0,0 +1,7 @@ +package com.kusitms.website.domain.blog.repository; + +import com.kusitms.website.domain.blog.entity.BlogPost; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface BlogPostRepository extends JpaRepository, BlogPostQueryRepository { +} diff --git a/src/main/java/com/kusitms/website/domain/blog/service/BlogService.java b/src/main/java/com/kusitms/website/domain/blog/service/BlogService.java new file mode 100644 index 0000000..cba8365 --- /dev/null +++ b/src/main/java/com/kusitms/website/domain/blog/service/BlogService.java @@ -0,0 +1,33 @@ +package com.kusitms.website.domain.blog.service; + +import com.kusitms.website.domain.blog.dto.response.BlogResponse; +import com.kusitms.website.domain.blog.entity.BlogPost; +import com.kusitms.website.domain.blog.entity.Category; +import com.kusitms.website.domain.blog.entity.Position; +import com.kusitms.website.domain.blog.repository.BlogPostQueryRepository; +import com.kusitms.website.domain.blog.repository.BlogPostRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class BlogService { + + private final BlogPostRepository blogPostRepository; + + public List getFilteredPostsWithPaging( + Integer generation, + Position position, + Category category, + Long lastId, + int size + ) { + return blogPostRepository.findByFiltersWithPaging(generation, position, category, lastId, size) + .stream() + .map(BlogResponse::fromEntity) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/kusitms/website/global/config/QuerydslConfig.java b/src/main/java/com/kusitms/website/global/config/QuerydslConfig.java new file mode 100644 index 0000000..516eb87 --- /dev/null +++ b/src/main/java/com/kusitms/website/global/config/QuerydslConfig.java @@ -0,0 +1,19 @@ +package com.kusitms.website.global.config; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + +@Configuration +public class QuerydslConfig { + @PersistenceContext + private EntityManager em; + + @Bean + public JPAQueryFactory jpaQueryFactory() { + return new JPAQueryFactory(em); + } +}