Skip to content

Commit cfb3072

Browse files
authored
release: 2.0.1 (#361)
2 parents 18b7a7f + bdf02ec commit cfb3072

File tree

8 files changed

+201
-3
lines changed

8 files changed

+201
-3
lines changed

src/main/kotlin/org/gitanimals/core/redis/RedisPubSubChannel.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@ object RedisPubSubChannel {
99
const val SLACK_REPLIED = "SLACK_REPLIED"
1010

1111
const val DEAD_LETTER_OCCURRED = "DEAD_LETTER_OCCURRED"
12+
13+
const val NEW_PET_DROP_RATE_DISTRIBUTION = "NEW_PET_DROP_RATE_DISTRIBUTION"
1214
}
Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
package org.gitanimals.render.domain
22

3+
import org.springframework.data.domain.Pageable
4+
import org.springframework.data.domain.Slice
35
import org.springframework.data.jpa.repository.JpaRepository
6+
import org.springframework.data.jpa.repository.Query
7+
import org.springframework.data.repository.query.Param
8+
import java.time.Instant
49

5-
interface PersonaStatisticRepository: JpaRepository<Persona, Long>
10+
interface PersonaStatisticRepository : JpaRepository<Persona, Long> {
11+
12+
@Query("select p from persona p where p.createdAt >= :createdAt")
13+
fun findAllPersonaByCreatedAtAfter(
14+
@Param("createdAt") createdAt: Instant,
15+
pageable: Pageable,
16+
): Slice<Persona>
17+
}

src/main/kotlin/org/gitanimals/render/domain/PersonaStatisticService.kt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package org.gitanimals.render.domain
22

3+
import org.gitanimals.core.PersonaType
4+
import org.gitanimals.render.domain.response.NewPetDropRateDistribution
35
import org.springframework.cache.annotation.Cacheable
6+
import org.springframework.data.domain.PageRequest
47
import org.springframework.stereotype.Service
8+
import java.time.Instant
59

610
@Service
711
class PersonaStatisticService(
@@ -10,4 +14,35 @@ class PersonaStatisticService(
1014

1115
@Cacheable(value = ["total_persona_count_cache"])
1216
fun getTotalPersonaCount() = personaStatisticRepository.count()
17+
18+
fun getAggregatedPersonaDistributionByCreatedAtAfter(createdAt: Instant): List<NewPetDropRateDistribution> {
19+
var pageable = PageRequest.of(0, 100)
20+
var personas = personaStatisticRepository.findAllPersonaByCreatedAtAfter(createdAt, pageable)
21+
22+
val newPetDropRateDistributionMap: MutableMap<Double, Int> =
23+
PersonaType.entries.associateTo(mutableMapOf()) {
24+
it.weight to 0
25+
}
26+
27+
personas.content.forEach {
28+
newPetDropRateDistributionMap.getOrPut(it.type.weight) { 0 }
29+
newPetDropRateDistributionMap[it.type.weight] = (newPetDropRateDistributionMap[it.type.weight] ?: 0) + 1
30+
}
31+
32+
while (personas.hasNext()) {
33+
pageable = pageable.next()
34+
personas = personaStatisticRepository.findAllPersonaByCreatedAtAfter(createdAt, pageable)
35+
personas.content.forEach {
36+
newPetDropRateDistributionMap.getOrPut(it.type.weight) { 0 }
37+
newPetDropRateDistributionMap[it.type.weight] = (newPetDropRateDistributionMap[it.type.weight] ?: 0) + 1
38+
}
39+
}
40+
41+
return newPetDropRateDistributionMap.map {
42+
val dropRate = it.key
43+
val count = it.value
44+
45+
NewPetDropRateDistribution(dropRate, count)
46+
}
47+
}
1348
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package org.gitanimals.render.domain.response
2+
3+
data class NewPetDropRateDistribution(
4+
val dropRate: Double,
5+
val count: Int,
6+
)
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package org.gitanimals.render.infra
2+
3+
import org.gitanimals.core.IdGenerator
4+
import org.gitanimals.core.filter.MDCFilter.Companion.TRACE_ID
5+
import org.gitanimals.core.instant
6+
import org.gitanimals.render.domain.PersonaStatisticService
7+
import org.gitanimals.render.infra.event.NewPetDropRateDistributionEvent
8+
import org.gitanimals.render.infra.event.Type
9+
import org.slf4j.LoggerFactory
10+
import org.slf4j.MDC
11+
import org.springframework.context.ApplicationEventPublisher
12+
import org.springframework.scheduling.annotation.Scheduled
13+
import org.springframework.stereotype.Component
14+
import java.time.temporal.ChronoUnit
15+
16+
@Component
17+
class NewPetDropRateDistributionReport(
18+
private val personaStatisticService: PersonaStatisticService,
19+
private val applicationEventPublisher: ApplicationEventPublisher,
20+
) {
21+
22+
private val logger = LoggerFactory.getLogger(this::class.simpleName)
23+
24+
@Scheduled(cron = EVERY_9AM, zone = "Asia/Seoul")
25+
fun reportDailyNewPetDropRateDistribution() {
26+
runCatching {
27+
publishDropRateDistribution(1, Type.DAILY)
28+
}.also {
29+
MDC.remove(TRACE_ID)
30+
}
31+
}
32+
33+
@Scheduled(cron = EVERY_SUNDAY_9AM, zone = "Asia/Seoul")
34+
fun reportWeeklyNewPetDropRateDistribution() {
35+
runCatching {
36+
publishDropRateDistribution(7, Type.DAILY)
37+
}.also {
38+
MDC.remove(TRACE_ID)
39+
}
40+
}
41+
42+
@Scheduled(cron = EVERY_FIRST_DAY_OF_MONTH_9AM, zone = "Asia/Seoul")
43+
fun reportMonthlyNewPetDropRateDistribution() {
44+
runCatching {
45+
publishDropRateDistribution(30, Type.DAILY)
46+
}.also {
47+
MDC.remove(TRACE_ID)
48+
}
49+
}
50+
51+
private fun publishDropRateDistribution(days: Long, type: Type) {
52+
MDC.put(TRACE_ID, IdGenerator.generate().toString())
53+
54+
logger.info("[NewPetDropRateDistributionReport] Aggregate yesterday days pet distribution drop rate...")
55+
56+
val createdAt = instant().minus(days, ChronoUnit.DAYS).truncatedTo(ChronoUnit.DAYS)
57+
val distributionResponse =
58+
personaStatisticService.getAggregatedPersonaDistributionByCreatedAtAfter(createdAt)
59+
60+
val event = NewPetDropRateDistributionEvent(
61+
type = type,
62+
distributions = distributionResponse.map {
63+
NewPetDropRateDistributionEvent.Distribution(
64+
it.dropRate,
65+
it.count,
66+
)
67+
}
68+
)
69+
70+
applicationEventPublisher.publishEvent(event)
71+
}
72+
73+
companion object {
74+
private const val EVERY_9AM = "0 0 9 * * ?"
75+
private const val EVERY_SUNDAY_9AM = "0 0 9 * * SUN"
76+
private const val EVERY_FIRST_DAY_OF_MONTH_9AM = "0 0 9 1 * *"
77+
}
78+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package org.gitanimals.render.infra.event
2+
3+
import org.gitanimals.core.filter.MDCFilter.Companion.TRACE_ID
4+
import org.gitanimals.core.redis.AsyncRedisPubSubEvent
5+
import org.gitanimals.core.redis.RedisPubSubChannel.NEW_PET_DROP_RATE_DISTRIBUTION
6+
import org.slf4j.MDC
7+
8+
data class NewPetDropRateDistributionEvent(
9+
val type: Type,
10+
val distributions: List<Distribution>,
11+
) : AsyncRedisPubSubEvent(
12+
traceId = MDC.get(TRACE_ID),
13+
channel = NEW_PET_DROP_RATE_DISTRIBUTION,
14+
) {
15+
data class Distribution(
16+
val dropRate: Double,
17+
val count: Int,
18+
)
19+
}
20+
21+
enum class Type {
22+
DAILY,
23+
WEEKLY,
24+
MONTHLY,
25+
}

src/test/kotlin/org/gitanimals/render/domain/PersonaStatisticServiceTest.kt

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ package org.gitanimals.render.domain
22

33
import io.kotest.core.annotation.DisplayName
44
import io.kotest.core.spec.style.DescribeSpec
5+
import io.kotest.matchers.shouldBe
56
import io.kotest.matchers.shouldNotBe
7+
import org.gitanimals.core.instant
68
import org.gitanimals.render.infra.CacheConfigurer
9+
import org.springframework.beans.factory.annotation.Autowired
710
import org.springframework.boot.autoconfigure.domain.EntityScan
811
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
912
import org.springframework.cache.CacheManager
@@ -17,7 +20,7 @@ import org.springframework.test.context.ContextConfiguration
1720
@ContextConfiguration(
1821
classes = [
1922
CacheConfigurer::class,
20-
PersonaStatisticService::class
23+
PersonaStatisticService::class,
2124
]
2225
)
2326
@DisplayName("PersonaStatisticService 클래스의")
@@ -26,6 +29,8 @@ import org.springframework.test.context.ContextConfiguration
2629
internal class PersonaStatisticServiceTest(
2730
private val cacheManager: CacheManager,
2831
private val personaStatisticService: PersonaStatisticService,
32+
private val personaStatisticRepository: PersonaStatisticRepository,
33+
private val userRepository: UserRepository,
2934
) : DescribeSpec({
3035
describe("getTotalPersonaCount 메소드는") {
3136
context("호출되면,") {
@@ -37,4 +42,25 @@ internal class PersonaStatisticServiceTest(
3742
}
3843
}
3944
}
40-
})
45+
46+
describe("getAggregatedPersonaDistributionByCreatedAtAfter 메소드는") {
47+
context("createdAt을 받으면,") {
48+
val user = userRepository.save(user())
49+
val createdAt = instant()
50+
51+
for (i in 0..100) {
52+
personaStatisticRepository.save(persona(user = user))
53+
}
54+
55+
it("createdAt보다 이후에 생성된 모든 persona의 dropRate count를 집계한다") {
56+
val expectedCount = 101
57+
58+
val result = personaStatisticService
59+
.getAggregatedPersonaDistributionByCreatedAtAfter(createdAt)
60+
61+
result.sumOf { it.count } shouldBe expectedCount
62+
}
63+
}
64+
}
65+
}) {
66+
}

src/test/kotlin/org/gitanimals/render/domain/UserFixture.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package org.gitanimals.render.domain
22

33
import org.gitanimals.core.IdGenerator
4+
import org.gitanimals.core.PersonaType
45
import org.gitanimals.render.domain.value.Contribution
6+
import org.gitanimals.render.domain.value.Level
57

68
fun user(
79
id: Long = IdGenerator.generate(),
@@ -20,3 +22,15 @@ fun user(
2022
lastPersonaGivePoint = 0,
2123
)
2224
}
25+
26+
fun persona(
27+
id: Long = IdGenerator.generate(),
28+
type: PersonaType = PersonaType.CAT,
29+
user: User,
30+
) = Persona(
31+
id = id,
32+
type = type,
33+
level = Level(0),
34+
visible = false,
35+
user = user,
36+
)

0 commit comments

Comments
 (0)