From 1c16093026f2e57a8d75776e0524dbe198622a86 Mon Sep 17 00:00:00 2001 From: Jihun-Hwang Date: Sat, 20 Jul 2024 15:08:25 +0900 Subject: [PATCH 01/10] =?UTF-8?q?feat:=20ARTICLE=5FVIEW=5FCOUNT=20?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EB=B8=94=20=EC=83=9D=EC=84=B1,=20=EC=95=84?= =?UTF-8?q?=ED=8B=B0=ED=81=B4=20=EC=A1=B0=ED=9A=8C=EC=88=98=20=EC=88=9C=20?= =?UTF-8?q?=EC=A0=95=EB=A0=AC=20=EB=93=B1=EC=97=90=20=EC=82=AC=EC=9A=A9=20?= =?UTF-8?q?=EC=98=88=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entity/V1.00.0.14__manage_article_view_count.sql | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 data/db/migration/entity/V1.00.0.14__manage_article_view_count.sql diff --git a/data/db/migration/entity/V1.00.0.14__manage_article_view_count.sql b/data/db/migration/entity/V1.00.0.14__manage_article_view_count.sql new file mode 100644 index 000000000..cef0a00bb --- /dev/null +++ b/data/db/migration/entity/V1.00.0.14__manage_article_view_count.sql @@ -0,0 +1,11 @@ +-- article 별 조회수 저장 테이블 +CREATE TABLE ARTICLE_VIEW_COUNT +( + article_id BIGINT NOT NULL, + view_count BIGINT NOT NULL, + CONSTRAINT article_view_count_pk PRIMARY KEY (article_id) +); + +-- 조회수 순으로 아티클 조회시 사용하기 위한 인덱스 +-- ex. SELECT * FROM ARTICLE_VIEW_COUNT ORDER BY view_count; +CREATE INDEX article_view_count_idx ON ARTICLE_VIEW_COUNT (view_count); From d8acd58ec59819fcf66b380bb44ba6646c2cbab1 Mon Sep 17 00:00:00 2001 From: Jihun-Hwang Date: Sat, 20 Jul 2024 15:14:06 +0900 Subject: [PATCH 02/10] =?UTF-8?q?fix:=20ARTICLE=5FVIEW=5FCOUNT=20=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=B8=94=20deleted=5Fat=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migration/entity/V1.00.0.14__manage_article_view_count.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/data/db/migration/entity/V1.00.0.14__manage_article_view_count.sql b/data/db/migration/entity/V1.00.0.14__manage_article_view_count.sql index cef0a00bb..5b1c3bce8 100644 --- a/data/db/migration/entity/V1.00.0.14__manage_article_view_count.sql +++ b/data/db/migration/entity/V1.00.0.14__manage_article_view_count.sql @@ -3,6 +3,7 @@ CREATE TABLE ARTICLE_VIEW_COUNT ( article_id BIGINT NOT NULL, view_count BIGINT NOT NULL, + deleted_at TIMESTAMP NULL DEFAULT NULL, CONSTRAINT article_view_count_pk PRIMARY KEY (article_id) ); From 6fca14c6c531896af4d71f699f2355fe779c49dd Mon Sep 17 00:00:00 2001 From: Jihun-Hwang Date: Sat, 20 Jul 2024 15:18:24 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat:=20=EC=95=84=ED=8B=B0=ED=81=B4=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=EC=8B=9C=20article=20view=20his=EA=B0=80=20?= =?UTF-8?q?=EC=95=84=EB=8B=8C=20article=20view=20count=EB=A5=BC=20?= =?UTF-8?q?=EB=B3=B4=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95(SL=EC=88=98?= =?UTF-8?q?=EC=A0=95:=20count=20->=20select)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repo/dao/article/ArticleViewCountDao.kt | 30 +++++++++++++++++++ .../command/ArticleViewCountCommand.kt | 5 ++++ .../article/query/ArticleViewCountQuery.kt | 5 ++++ .../article/usecase/ReadArticleUseCase.kt | 8 ++--- 4 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 api-repo/src/main/kotlin/com/few/api/repo/dao/article/ArticleViewCountDao.kt create mode 100644 api-repo/src/main/kotlin/com/few/api/repo/dao/article/command/ArticleViewCountCommand.kt create mode 100644 api-repo/src/main/kotlin/com/few/api/repo/dao/article/query/ArticleViewCountQuery.kt diff --git a/api-repo/src/main/kotlin/com/few/api/repo/dao/article/ArticleViewCountDao.kt b/api-repo/src/main/kotlin/com/few/api/repo/dao/article/ArticleViewCountDao.kt new file mode 100644 index 000000000..ad47e82ed --- /dev/null +++ b/api-repo/src/main/kotlin/com/few/api/repo/dao/article/ArticleViewCountDao.kt @@ -0,0 +1,30 @@ +package com.few.api.repo.dao.article + +import com.few.api.repo.dao.article.command.ArticleViewCountCommand +import com.few.api.repo.dao.article.query.ArticleViewCountQuery +import jooq.jooq_dsl.Tables.ARTICLE_VIEW_COUNT +import org.jooq.DSLContext +import org.springframework.stereotype.Repository + +@Repository +class ArticleViewCountDao( + private val dslContext: DSLContext, +) { + + fun upsertArticleViewCount(query: ArticleViewCountQuery) { + dslContext.insertInto(ARTICLE_VIEW_COUNT) + .set(ARTICLE_VIEW_COUNT.ARTICLE_ID, query.articleId) + .set(ARTICLE_VIEW_COUNT.VIEW_COUNT, 1) + .onDuplicateKeyUpdate() + .set(ARTICLE_VIEW_COUNT.VIEW_COUNT, ARTICLE_VIEW_COUNT.VIEW_COUNT.plus(1)) + } + + fun selectArticleViewCount(command: ArticleViewCountCommand): Long? { + return dslContext.select( + ARTICLE_VIEW_COUNT.VIEW_COUNT + ).from(ARTICLE_VIEW_COUNT) + .where(ARTICLE_VIEW_COUNT.ARTICLE_ID.eq(command.articleId)) + .and(ARTICLE_VIEW_COUNT.DELETED_AT.isNull) + .fetchOneInto(Long::class.java) + } +} \ No newline at end of file diff --git a/api-repo/src/main/kotlin/com/few/api/repo/dao/article/command/ArticleViewCountCommand.kt b/api-repo/src/main/kotlin/com/few/api/repo/dao/article/command/ArticleViewCountCommand.kt new file mode 100644 index 000000000..1b90d8fa7 --- /dev/null +++ b/api-repo/src/main/kotlin/com/few/api/repo/dao/article/command/ArticleViewCountCommand.kt @@ -0,0 +1,5 @@ +package com.few.api.repo.dao.article.command + +data class ArticleViewCountCommand( + val articleId: Long, +) \ No newline at end of file diff --git a/api-repo/src/main/kotlin/com/few/api/repo/dao/article/query/ArticleViewCountQuery.kt b/api-repo/src/main/kotlin/com/few/api/repo/dao/article/query/ArticleViewCountQuery.kt new file mode 100644 index 000000000..414ecc1d7 --- /dev/null +++ b/api-repo/src/main/kotlin/com/few/api/repo/dao/article/query/ArticleViewCountQuery.kt @@ -0,0 +1,5 @@ +package com.few.api.repo.dao.article.query + +data class ArticleViewCountQuery( + val articleId: Long, +) \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCase.kt b/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCase.kt index fc80437e4..aba45559c 100644 --- a/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCase.kt @@ -10,8 +10,8 @@ import com.few.api.domain.article.service.dto.BrowseArticleProblemIdsInDto import com.few.api.domain.article.service.dto.ReadWriterRecordInDto import com.few.api.exception.common.NotFoundException import com.few.api.repo.dao.article.ArticleDao -import com.few.api.repo.dao.article.ArticleViewHisDao -import com.few.api.repo.dao.article.query.ArticleViewHisCountQuery +import com.few.api.repo.dao.article.ArticleViewCountDao +import com.few.api.repo.dao.article.command.ArticleViewCountCommand import com.few.api.repo.dao.article.query.SelectArticleRecordQuery import com.few.data.common.code.CategoryType import org.springframework.stereotype.Component @@ -22,8 +22,8 @@ class ReadArticleUseCase( private val articleDao: ArticleDao, private val readArticleWriterRecordService: ReadArticleWriterRecordService, private val browseArticleProblemsService: BrowseArticleProblemsService, - private val articleViewHisDao: ArticleViewHisDao, private val articleViewHisAsyncHandler: ArticleViewHisAsyncHandler, + private val articleViewCountDao: ArticleViewCountDao, ) { @Transactional(readOnly = true) @@ -41,7 +41,7 @@ class ReadArticleUseCase( browseArticleProblemsService.execute(query) } - val views = (articleViewHisDao.countArticleViews(ArticleViewHisCountQuery(useCaseIn.articleId)) ?: 0L) + 1L + val views = (articleViewCountDao.selectArticleViewCount(ArticleViewCountCommand(useCaseIn.articleId)) ?: 0L) + 1L articleViewHisAsyncHandler.addArticleViewHis(useCaseIn.articleId, useCaseIn.memberId) From 5e41a3d6bd4634022d5fdaec52fc42fa2221082b Mon Sep 17 00:00:00 2001 From: Jihun-Hwang Date: Sat, 20 Jul 2024 15:21:32 +0900 Subject: [PATCH 04/10] =?UTF-8?q?test:=20ReadArticleUseCaseTest=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../article/usecase/ReadArticleUseCaseTest.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/api/src/test/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCaseTest.kt b/api/src/test/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCaseTest.kt index f333538f4..d4d7861ee 100644 --- a/api/src/test/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCaseTest.kt +++ b/api/src/test/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCaseTest.kt @@ -7,7 +7,7 @@ import com.few.api.domain.article.service.dto.BrowseArticleProblemsOutDto import com.few.api.domain.article.service.dto.ReadWriterOutDto import com.few.api.domain.article.usecase.dto.ReadArticleUseCaseIn import com.few.api.repo.dao.article.ArticleDao -import com.few.api.repo.dao.article.ArticleViewHisDao +import com.few.api.repo.dao.article.ArticleViewCountDao import com.few.api.repo.dao.article.record.SelectArticleRecord import io.github.oshai.kotlinlogging.KotlinLogging import io.kotest.assertions.throwables.shouldThrow @@ -24,22 +24,22 @@ class ReadArticleUseCaseTest : BehaviorSpec({ lateinit var readArticleWriterRecordService: ReadArticleWriterRecordService lateinit var browseArticleProblemsService: BrowseArticleProblemsService lateinit var useCase: ReadArticleUseCase - lateinit var articleViewHisDao: ArticleViewHisDao lateinit var articleViewHisAsyncHandler: ArticleViewHisAsyncHandler + lateinit var articleViewCountDao: ArticleViewCountDao val useCaseIn = ReadArticleUseCaseIn(articleId = 1L, memberId = 1L) beforeContainer { articleDao = mockk() readArticleWriterRecordService = mockk() browseArticleProblemsService = mockk() - articleViewHisDao = mockk() articleViewHisAsyncHandler = mockk() + articleViewCountDao = mockk() useCase = ReadArticleUseCase( articleDao, readArticleWriterRecordService, browseArticleProblemsService, - articleViewHisDao, - articleViewHisAsyncHandler + articleViewHisAsyncHandler, + articleViewCountDao ) } @@ -64,7 +64,7 @@ class ReadArticleUseCaseTest : BehaviorSpec({ every { articleDao.selectArticleRecord(any()) } returns record every { readArticleWriterRecordService.execute(any()) } returns writerSvcOutDto every { browseArticleProblemsService.execute(any()) } returns probSvcOutDto - every { articleViewHisDao.countArticleViews(any()) } returns 1L + every { articleViewCountDao.selectArticleViewCount(any()) } returns 1L every { articleViewHisAsyncHandler.addArticleViewHis(any(), any()) } answers { log.debug { "Inserting article view history asynchronously" } } @@ -75,7 +75,7 @@ class ReadArticleUseCaseTest : BehaviorSpec({ verify(exactly = 1) { articleDao.selectArticleRecord(any()) } verify(exactly = 1) { readArticleWriterRecordService.execute(any()) } verify(exactly = 1) { browseArticleProblemsService.execute(any()) } - verify(exactly = 1) { articleViewHisDao.countArticleViews(any()) } + verify(exactly = 1) { articleViewCountDao.selectArticleViewCount(any()) } verify(exactly = 1) { articleViewHisAsyncHandler.addArticleViewHis(any(), any()) } } } From 13306e6cf6c25b3c588ca1203d5b397b9db6ae63 Mon Sep 17 00:00:00 2001 From: Jihun-Hwang Date: Sat, 20 Jul 2024 15:40:14 +0900 Subject: [PATCH 05/10] =?UTF-8?q?feat:=20ArticleViewCountHandler=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../article/handler/ArticleViewCountHandler.kt | 18 ++++++++++++++++++ .../handler/ArticleViewHisAsyncHandler.kt | 11 ++++++++++- .../article/usecase/ReadArticleUseCase.kt | 9 ++++----- .../article/usecase/ReadArticleUseCaseTest.kt | 12 ++++++------ 4 files changed, 38 insertions(+), 12 deletions(-) create mode 100644 api/src/main/kotlin/com/few/api/domain/article/handler/ArticleViewCountHandler.kt diff --git a/api/src/main/kotlin/com/few/api/domain/article/handler/ArticleViewCountHandler.kt b/api/src/main/kotlin/com/few/api/domain/article/handler/ArticleViewCountHandler.kt new file mode 100644 index 000000000..f8127ef21 --- /dev/null +++ b/api/src/main/kotlin/com/few/api/domain/article/handler/ArticleViewCountHandler.kt @@ -0,0 +1,18 @@ +package com.few.api.domain.article.handler + +import com.few.api.repo.dao.article.ArticleViewCountDao +import com.few.api.repo.dao.article.command.ArticleViewCountCommand +import org.springframework.stereotype.Component +import org.springframework.transaction.annotation.Isolation +import org.springframework.transaction.annotation.Propagation +import org.springframework.transaction.annotation.Transactional + +@Component +class ArticleViewCountHandler( + private val articleViewCountDao: ArticleViewCountDao, +) { + @Transactional(isolation = Isolation.READ_UNCOMMITTED, propagation = Propagation.REQUIRES_NEW) + fun browseArticleViewCount(articleId: Long): Long { + return (articleViewCountDao.selectArticleViewCount(ArticleViewCountCommand(articleId)) ?: 0L) + 1L + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/article/handler/ArticleViewHisAsyncHandler.kt b/api/src/main/kotlin/com/few/api/domain/article/handler/ArticleViewHisAsyncHandler.kt index c8a22ac3f..a12e2c96e 100644 --- a/api/src/main/kotlin/com/few/api/domain/article/handler/ArticleViewHisAsyncHandler.kt +++ b/api/src/main/kotlin/com/few/api/domain/article/handler/ArticleViewHisAsyncHandler.kt @@ -1,8 +1,10 @@ package com.few.api.domain.article.handler import com.few.api.config.DatabaseAccessThreadPoolConfig.Companion.DATABASE_ACCESS_POOL +import com.few.api.repo.dao.article.ArticleViewCountDao import com.few.api.repo.dao.article.ArticleViewHisDao import com.few.api.repo.dao.article.command.ArticleViewHisCommand +import com.few.api.repo.dao.article.query.ArticleViewCountQuery import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.scheduling.annotation.Async import org.springframework.stereotype.Component @@ -11,6 +13,7 @@ import org.springframework.transaction.annotation.Transactional @Component class ArticleViewHisAsyncHandler( private val articleViewHisDao: ArticleViewHisDao, + private val articleViewCountDao: ArticleViewCountDao, ) { private val log = KotlinLogging.logger {} @@ -20,8 +23,14 @@ class ArticleViewHisAsyncHandler( try { articleViewHisDao.insertArticleViewHis(ArticleViewHisCommand(articleId, memberId)) log.debug { "Successfully inserted article view history for articleId: $articleId and memberId: $memberId" } + + articleViewCountDao.upsertArticleViewCount(ArticleViewCountQuery(articleId)) + log.debug { "Successfully upserted article view count for articleId: $articleId and memberId: $memberId" } } catch (e: Exception) { - log.error { "Failed to insert article view history for articleId: $articleId and memberId: $memberId" } + log.error { + "Failed insertion article view history and upsertion article view count " + + "for articleId: $articleId and memberId: $memberId" + } } } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCase.kt b/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCase.kt index aba45559c..ddb3abdf9 100644 --- a/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCase.kt @@ -1,5 +1,6 @@ package com.few.api.domain.article.usecase +import com.few.api.domain.article.handler.ArticleViewCountHandler import com.few.api.domain.article.handler.ArticleViewHisAsyncHandler import com.few.api.domain.article.usecase.dto.ReadArticleUseCaseIn import com.few.api.domain.article.usecase.dto.ReadArticleUseCaseOut @@ -10,8 +11,6 @@ import com.few.api.domain.article.service.dto.BrowseArticleProblemIdsInDto import com.few.api.domain.article.service.dto.ReadWriterRecordInDto import com.few.api.exception.common.NotFoundException import com.few.api.repo.dao.article.ArticleDao -import com.few.api.repo.dao.article.ArticleViewCountDao -import com.few.api.repo.dao.article.command.ArticleViewCountCommand import com.few.api.repo.dao.article.query.SelectArticleRecordQuery import com.few.data.common.code.CategoryType import org.springframework.stereotype.Component @@ -23,7 +22,7 @@ class ReadArticleUseCase( private val readArticleWriterRecordService: ReadArticleWriterRecordService, private val browseArticleProblemsService: BrowseArticleProblemsService, private val articleViewHisAsyncHandler: ArticleViewHisAsyncHandler, - private val articleViewCountDao: ArticleViewCountDao, + private val articleViewCountHandler: ArticleViewCountHandler, ) { @Transactional(readOnly = true) @@ -41,8 +40,8 @@ class ReadArticleUseCase( browseArticleProblemsService.execute(query) } - val views = (articleViewCountDao.selectArticleViewCount(ArticleViewCountCommand(useCaseIn.articleId)) ?: 0L) + 1L - + // ARTICLE VIEW HIS에 저장하기 전에 먼저 VIEW COUNT 조회하는 순서 변경 금지 + val views = articleViewCountHandler.browseArticleViewCount(useCaseIn.articleId) articleViewHisAsyncHandler.addArticleViewHis(useCaseIn.articleId, useCaseIn.memberId) return ReadArticleUseCaseOut( diff --git a/api/src/test/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCaseTest.kt b/api/src/test/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCaseTest.kt index d4d7861ee..e6524f177 100644 --- a/api/src/test/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCaseTest.kt +++ b/api/src/test/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCaseTest.kt @@ -1,5 +1,6 @@ package com.few.api.domain.article.usecase +import com.few.api.domain.article.handler.ArticleViewCountHandler import com.few.api.domain.article.handler.ArticleViewHisAsyncHandler import com.few.api.domain.article.service.BrowseArticleProblemsService import com.few.api.domain.article.service.ReadArticleWriterRecordService @@ -7,7 +8,6 @@ import com.few.api.domain.article.service.dto.BrowseArticleProblemsOutDto import com.few.api.domain.article.service.dto.ReadWriterOutDto import com.few.api.domain.article.usecase.dto.ReadArticleUseCaseIn import com.few.api.repo.dao.article.ArticleDao -import com.few.api.repo.dao.article.ArticleViewCountDao import com.few.api.repo.dao.article.record.SelectArticleRecord import io.github.oshai.kotlinlogging.KotlinLogging import io.kotest.assertions.throwables.shouldThrow @@ -25,7 +25,7 @@ class ReadArticleUseCaseTest : BehaviorSpec({ lateinit var browseArticleProblemsService: BrowseArticleProblemsService lateinit var useCase: ReadArticleUseCase lateinit var articleViewHisAsyncHandler: ArticleViewHisAsyncHandler - lateinit var articleViewCountDao: ArticleViewCountDao + lateinit var articleViewCountHandler: ArticleViewCountHandler val useCaseIn = ReadArticleUseCaseIn(articleId = 1L, memberId = 1L) beforeContainer { @@ -33,13 +33,13 @@ class ReadArticleUseCaseTest : BehaviorSpec({ readArticleWriterRecordService = mockk() browseArticleProblemsService = mockk() articleViewHisAsyncHandler = mockk() - articleViewCountDao = mockk() + articleViewCountHandler = mockk() useCase = ReadArticleUseCase( articleDao, readArticleWriterRecordService, browseArticleProblemsService, articleViewHisAsyncHandler, - articleViewCountDao + articleViewCountHandler ) } @@ -64,7 +64,7 @@ class ReadArticleUseCaseTest : BehaviorSpec({ every { articleDao.selectArticleRecord(any()) } returns record every { readArticleWriterRecordService.execute(any()) } returns writerSvcOutDto every { browseArticleProblemsService.execute(any()) } returns probSvcOutDto - every { articleViewCountDao.selectArticleViewCount(any()) } returns 1L + every { articleViewCountHandler.browseArticleViewCount(any()) } returns 1L every { articleViewHisAsyncHandler.addArticleViewHis(any(), any()) } answers { log.debug { "Inserting article view history asynchronously" } } @@ -75,7 +75,7 @@ class ReadArticleUseCaseTest : BehaviorSpec({ verify(exactly = 1) { articleDao.selectArticleRecord(any()) } verify(exactly = 1) { readArticleWriterRecordService.execute(any()) } verify(exactly = 1) { browseArticleProblemsService.execute(any()) } - verify(exactly = 1) { articleViewCountDao.selectArticleViewCount(any()) } + verify(exactly = 1) { articleViewCountHandler.browseArticleViewCount(any()) } verify(exactly = 1) { articleViewHisAsyncHandler.addArticleViewHis(any(), any()) } } } From 3f6a6888027b54417a03554e83330c5c94080da4 Mon Sep 17 00:00:00 2001 From: Jihun-Hwang Date: Sat, 20 Jul 2024 15:53:09 +0900 Subject: [PATCH 06/10] =?UTF-8?q?fix:=20view=5Fcount=20=EC=BB=AC=EB=9F=BC?= =?UTF-8?q?=20=EA=B8=B0=EC=A4=80=20=EB=82=B4=EB=A6=BC=EC=B0=A8=EC=88=9C=20?= =?UTF-8?q?=EC=A0=95=EB=A0=AC=20=EC=9D=B8=EB=8D=B1=EC=8A=A4=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migration/entity/V1.00.0.14__manage_article_view_count.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/db/migration/entity/V1.00.0.14__manage_article_view_count.sql b/data/db/migration/entity/V1.00.0.14__manage_article_view_count.sql index 5b1c3bce8..316ce7bf0 100644 --- a/data/db/migration/entity/V1.00.0.14__manage_article_view_count.sql +++ b/data/db/migration/entity/V1.00.0.14__manage_article_view_count.sql @@ -9,4 +9,4 @@ CREATE TABLE ARTICLE_VIEW_COUNT -- 조회수 순으로 아티클 조회시 사용하기 위한 인덱스 -- ex. SELECT * FROM ARTICLE_VIEW_COUNT ORDER BY view_count; -CREATE INDEX article_view_count_idx ON ARTICLE_VIEW_COUNT (view_count); +CREATE INDEX article_view_count_desc_idx1 ON ARTICLE_VIEW_COUNT(view_count DESC); From b5f58ce92d4630dac0ca7a433567ca7812f2e5a3 Mon Sep 17 00:00:00 2001 From: Jihun-Hwang Date: Sat, 20 Jul 2024 21:04:45 +0900 Subject: [PATCH 07/10] =?UTF-8?q?feat:=20category=5Fcd=20=EC=BB=AC?= =?UTF-8?q?=EB=9F=BC=20=EC=B6=94=EA=B0=80=20in=20ARTICLE=5FVIEW=5FCOUNT=20?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EB=B8=94=20and=20=EC=9D=B8=EB=8D=B1=EC=8B=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/repo/dao/article/ArticleViewCountDao.kt | 1 + .../dao/article/query/ArticleViewCountQuery.kt | 3 +++ .../article/handler/ArticleViewHisAsyncHandler.kt | 5 +++-- .../domain/article/usecase/ReadArticleUseCase.kt | 6 +++++- .../V1.00.0.14__manage_article_view_count.sql | 14 +++++++++----- 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/api-repo/src/main/kotlin/com/few/api/repo/dao/article/ArticleViewCountDao.kt b/api-repo/src/main/kotlin/com/few/api/repo/dao/article/ArticleViewCountDao.kt index ad47e82ed..3b8542aea 100644 --- a/api-repo/src/main/kotlin/com/few/api/repo/dao/article/ArticleViewCountDao.kt +++ b/api-repo/src/main/kotlin/com/few/api/repo/dao/article/ArticleViewCountDao.kt @@ -15,6 +15,7 @@ class ArticleViewCountDao( dslContext.insertInto(ARTICLE_VIEW_COUNT) .set(ARTICLE_VIEW_COUNT.ARTICLE_ID, query.articleId) .set(ARTICLE_VIEW_COUNT.VIEW_COUNT, 1) + .set(ARTICLE_VIEW_COUNT.CATEGORY_CD, query.categoryType?.code) .onDuplicateKeyUpdate() .set(ARTICLE_VIEW_COUNT.VIEW_COUNT, ARTICLE_VIEW_COUNT.VIEW_COUNT.plus(1)) } diff --git a/api-repo/src/main/kotlin/com/few/api/repo/dao/article/query/ArticleViewCountQuery.kt b/api-repo/src/main/kotlin/com/few/api/repo/dao/article/query/ArticleViewCountQuery.kt index 414ecc1d7..cda228f3a 100644 --- a/api-repo/src/main/kotlin/com/few/api/repo/dao/article/query/ArticleViewCountQuery.kt +++ b/api-repo/src/main/kotlin/com/few/api/repo/dao/article/query/ArticleViewCountQuery.kt @@ -1,5 +1,8 @@ package com.few.api.repo.dao.article.query +import com.few.data.common.code.CategoryType + data class ArticleViewCountQuery( val articleId: Long, + val categoryType: CategoryType?, ) \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/article/handler/ArticleViewHisAsyncHandler.kt b/api/src/main/kotlin/com/few/api/domain/article/handler/ArticleViewHisAsyncHandler.kt index a12e2c96e..c2847c4bf 100644 --- a/api/src/main/kotlin/com/few/api/domain/article/handler/ArticleViewHisAsyncHandler.kt +++ b/api/src/main/kotlin/com/few/api/domain/article/handler/ArticleViewHisAsyncHandler.kt @@ -5,6 +5,7 @@ import com.few.api.repo.dao.article.ArticleViewCountDao import com.few.api.repo.dao.article.ArticleViewHisDao import com.few.api.repo.dao.article.command.ArticleViewHisCommand import com.few.api.repo.dao.article.query.ArticleViewCountQuery +import com.few.data.common.code.CategoryType import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.scheduling.annotation.Async import org.springframework.stereotype.Component @@ -19,12 +20,12 @@ class ArticleViewHisAsyncHandler( @Async(value = DATABASE_ACCESS_POOL) @Transactional - fun addArticleViewHis(articleId: Long, memberId: Long) { + fun addArticleViewHis(articleId: Long, memberId: Long, categoryType: CategoryType?) { try { articleViewHisDao.insertArticleViewHis(ArticleViewHisCommand(articleId, memberId)) log.debug { "Successfully inserted article view history for articleId: $articleId and memberId: $memberId" } - articleViewCountDao.upsertArticleViewCount(ArticleViewCountQuery(articleId)) + articleViewCountDao.upsertArticleViewCount(ArticleViewCountQuery(articleId, categoryType)) log.debug { "Successfully upserted article view count for articleId: $articleId and memberId: $memberId" } } catch (e: Exception) { log.error { diff --git a/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCase.kt b/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCase.kt index ddb3abdf9..6743057dc 100644 --- a/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCase.kt @@ -42,7 +42,11 @@ class ReadArticleUseCase( // ARTICLE VIEW HIS에 저장하기 전에 먼저 VIEW COUNT 조회하는 순서 변경 금지 val views = articleViewCountHandler.browseArticleViewCount(useCaseIn.articleId) - articleViewHisAsyncHandler.addArticleViewHis(useCaseIn.articleId, useCaseIn.memberId) + articleViewHisAsyncHandler.addArticleViewHis( + useCaseIn.articleId, + useCaseIn.memberId, + CategoryType.fromCode(articleRecord.category) + ) return ReadArticleUseCaseOut( id = articleRecord.articleId, diff --git a/data/db/migration/entity/V1.00.0.14__manage_article_view_count.sql b/data/db/migration/entity/V1.00.0.14__manage_article_view_count.sql index 316ce7bf0..2b647e9f8 100644 --- a/data/db/migration/entity/V1.00.0.14__manage_article_view_count.sql +++ b/data/db/migration/entity/V1.00.0.14__manage_article_view_count.sql @@ -1,12 +1,16 @@ -- article 별 조회수 저장 테이블 CREATE TABLE ARTICLE_VIEW_COUNT ( - article_id BIGINT NOT NULL, - view_count BIGINT NOT NULL, - deleted_at TIMESTAMP NULL DEFAULT NULL, + article_id BIGINT NOT NULL, + view_count BIGINT NOT NULL, + category_cd TINYINT NOT NULL, + deleted_at TIMESTAMP NULL DEFAULT NULL, CONSTRAINT article_view_count_pk PRIMARY KEY (article_id) ); -- 조회수 순으로 아티클 조회시 사용하기 위한 인덱스 --- ex. SELECT * FROM ARTICLE_VIEW_COUNT ORDER BY view_count; -CREATE INDEX article_view_count_desc_idx1 ON ARTICLE_VIEW_COUNT(view_count DESC); +-- ex. SELECT * FROM ARTICLE_VIEW_COUNT ORDER BY view_count DESC LIMIT 10; +CREATE INDEX article_view_count_idx1 ON ARTICLE_VIEW_COUNT (view_count DESC); + +-- 카테고리 별 필터링을 위한 인덱스 +CREATE INDEX article_view_count_idx2 ON ARTICLE_VIEW_COUNT (category_cd); From a1eb90aa6ca6f8889eaa81596f323466e83447f5 Mon Sep 17 00:00:00 2001 From: Jihun-Hwang Date: Sat, 20 Jul 2024 21:06:11 +0900 Subject: [PATCH 08/10] =?UTF-8?q?test:=20category=5Fcd=20=EC=BB=AC?= =?UTF-8?q?=EB=9F=BC=20=EC=B6=94=EA=B0=80=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../few/api/domain/article/usecase/ReadArticleUseCaseTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/test/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCaseTest.kt b/api/src/test/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCaseTest.kt index e6524f177..e832feb97 100644 --- a/api/src/test/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCaseTest.kt +++ b/api/src/test/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCaseTest.kt @@ -65,7 +65,7 @@ class ReadArticleUseCaseTest : BehaviorSpec({ every { readArticleWriterRecordService.execute(any()) } returns writerSvcOutDto every { browseArticleProblemsService.execute(any()) } returns probSvcOutDto every { articleViewCountHandler.browseArticleViewCount(any()) } returns 1L - every { articleViewHisAsyncHandler.addArticleViewHis(any(), any()) } answers { + every { articleViewHisAsyncHandler.addArticleViewHis(any(), any(), any()) } answers { log.debug { "Inserting article view history asynchronously" } } @@ -76,7 +76,7 @@ class ReadArticleUseCaseTest : BehaviorSpec({ verify(exactly = 1) { readArticleWriterRecordService.execute(any()) } verify(exactly = 1) { browseArticleProblemsService.execute(any()) } verify(exactly = 1) { articleViewCountHandler.browseArticleViewCount(any()) } - verify(exactly = 1) { articleViewHisAsyncHandler.addArticleViewHis(any(), any()) } + verify(exactly = 1) { articleViewHisAsyncHandler.addArticleViewHis(any(), any(), any()) } } } From 680cf7537c4c6a9cf77c168059d938c1a4dd62d8 Mon Sep 17 00:00:00 2001 From: Jihun-Hwang Date: Sun, 21 Jul 2024 21:52:57 +0900 Subject: [PATCH 09/10] =?UTF-8?q?refactor:=20article=20category=20?= =?UTF-8?q?=EC=9C=A0=ED=9A=A8=ED=95=98=EC=A7=80=20=EC=95=8A=EC=9D=84=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=EC=97=90=20=EB=8C=80=ED=95=9C=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/few/api/repo/dao/article/ArticleViewCountDao.kt | 2 +- .../com/few/api/repo/dao/article/query/ArticleViewCountQuery.kt | 2 +- .../api/domain/article/handler/ArticleViewHisAsyncHandler.kt | 2 +- .../com/few/api/domain/article/usecase/ReadArticleUseCase.kt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api-repo/src/main/kotlin/com/few/api/repo/dao/article/ArticleViewCountDao.kt b/api-repo/src/main/kotlin/com/few/api/repo/dao/article/ArticleViewCountDao.kt index 3b8542aea..d1cd1c406 100644 --- a/api-repo/src/main/kotlin/com/few/api/repo/dao/article/ArticleViewCountDao.kt +++ b/api-repo/src/main/kotlin/com/few/api/repo/dao/article/ArticleViewCountDao.kt @@ -15,7 +15,7 @@ class ArticleViewCountDao( dslContext.insertInto(ARTICLE_VIEW_COUNT) .set(ARTICLE_VIEW_COUNT.ARTICLE_ID, query.articleId) .set(ARTICLE_VIEW_COUNT.VIEW_COUNT, 1) - .set(ARTICLE_VIEW_COUNT.CATEGORY_CD, query.categoryType?.code) + .set(ARTICLE_VIEW_COUNT.CATEGORY_CD, query.categoryType.code) .onDuplicateKeyUpdate() .set(ARTICLE_VIEW_COUNT.VIEW_COUNT, ARTICLE_VIEW_COUNT.VIEW_COUNT.plus(1)) } diff --git a/api-repo/src/main/kotlin/com/few/api/repo/dao/article/query/ArticleViewCountQuery.kt b/api-repo/src/main/kotlin/com/few/api/repo/dao/article/query/ArticleViewCountQuery.kt index cda228f3a..812695f0a 100644 --- a/api-repo/src/main/kotlin/com/few/api/repo/dao/article/query/ArticleViewCountQuery.kt +++ b/api-repo/src/main/kotlin/com/few/api/repo/dao/article/query/ArticleViewCountQuery.kt @@ -4,5 +4,5 @@ import com.few.data.common.code.CategoryType data class ArticleViewCountQuery( val articleId: Long, - val categoryType: CategoryType?, + val categoryType: CategoryType, ) \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/article/handler/ArticleViewHisAsyncHandler.kt b/api/src/main/kotlin/com/few/api/domain/article/handler/ArticleViewHisAsyncHandler.kt index c2847c4bf..020965d16 100644 --- a/api/src/main/kotlin/com/few/api/domain/article/handler/ArticleViewHisAsyncHandler.kt +++ b/api/src/main/kotlin/com/few/api/domain/article/handler/ArticleViewHisAsyncHandler.kt @@ -20,7 +20,7 @@ class ArticleViewHisAsyncHandler( @Async(value = DATABASE_ACCESS_POOL) @Transactional - fun addArticleViewHis(articleId: Long, memberId: Long, categoryType: CategoryType?) { + fun addArticleViewHis(articleId: Long, memberId: Long, categoryType: CategoryType) { try { articleViewHisDao.insertArticleViewHis(ArticleViewHisCommand(articleId, memberId)) log.debug { "Successfully inserted article view history for articleId: $articleId and memberId: $memberId" } diff --git a/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCase.kt b/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCase.kt index 6743057dc..0675d0439 100644 --- a/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCase.kt @@ -45,7 +45,7 @@ class ReadArticleUseCase( articleViewHisAsyncHandler.addArticleViewHis( useCaseIn.articleId, useCaseIn.memberId, - CategoryType.fromCode(articleRecord.category) + CategoryType.fromCode(articleRecord.category) ?: throw RuntimeException("invalid article") ) return ReadArticleUseCaseOut( From c519e607b679b310a68f21b00af80470351f31f7 Mon Sep 17 00:00:00 2001 From: Jihun-Hwang Date: Sun, 21 Jul 2024 21:57:09 +0900 Subject: [PATCH 10/10] =?UTF-8?q?feat:=20article.invalid.category=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/few/api/domain/article/usecase/ReadArticleUseCase.kt | 2 +- api/src/main/resources/messages/article.properties | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCase.kt b/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCase.kt index 0675d0439..7be0bf30f 100644 --- a/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCase.kt @@ -45,7 +45,7 @@ class ReadArticleUseCase( articleViewHisAsyncHandler.addArticleViewHis( useCaseIn.articleId, useCaseIn.memberId, - CategoryType.fromCode(articleRecord.category) ?: throw RuntimeException("invalid article") + CategoryType.fromCode(articleRecord.category) ?: throw NotFoundException("article.invalid.category") ) return ReadArticleUseCaseOut( diff --git a/api/src/main/resources/messages/article.properties b/api/src/main/resources/messages/article.properties index 2d8bfbc17..71025d20e 100644 --- a/api/src/main/resources/messages/article.properties +++ b/api/src/main/resources/messages/article.properties @@ -1,2 +1,3 @@ article.notfound.id=\u0061\u0072\u0074\u0069\u0063\u006c\u0065\u002e\u006e\u006f\u0074\u0066\u006f\u0075\u006e\u0064\u002e\u0069\u0064 article.notfound.articleidworkbookid=\u0061\u0072\u0074\u0069\u0063\u006c\u0065\u002e\u006e\u006f\u0074\u0066\u006f\u0075\u006e\u0064\u002e\u0061\u0072\u0074\u0069\u0063\u006c\u0065\u0069\u0064\u0077\u006f\u0072\u006b\u0062\u006f\u006f\u006b\u0069 +article.invalid.category=\uc874\uc7ac\ud558\uc9c0\u0020\uc54a\ub294\u0020\uc544\ud2f0\ud074\u0020\uce74\ud14c\uace0\ub9ac\uc785\ub2c8\ub2e4\u000d