generated from pricelees/issue-pr-template
[#54] 애플리케이션 배포 #55
@ -8,11 +8,13 @@ import com.sangdol.roomescape.admin.infrastructure.persistence.AdminType
|
|||||||
import com.sangdol.roomescape.payment.infrastructure.common.*
|
import com.sangdol.roomescape.payment.infrastructure.common.*
|
||||||
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationStatus
|
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationStatus
|
||||||
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus
|
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus
|
||||||
|
import com.sangdol.roomescape.store.infrastructure.persistence.StoreEntity
|
||||||
import com.sangdol.roomescape.supports.AdminFixture
|
import com.sangdol.roomescape.supports.AdminFixture
|
||||||
import com.sangdol.roomescape.supports.FunSpecSpringbootTest
|
import com.sangdol.roomescape.supports.FunSpecSpringbootTest
|
||||||
import com.sangdol.roomescape.supports.randomPhoneNumber
|
import com.sangdol.roomescape.supports.randomPhoneNumber
|
||||||
import com.sangdol.roomescape.supports.randomString
|
import com.sangdol.roomescape.supports.randomString
|
||||||
import com.sangdol.roomescape.theme.infrastructure.persistence.Difficulty
|
import com.sangdol.roomescape.theme.infrastructure.persistence.Difficulty
|
||||||
|
import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||||
import com.sangdol.roomescape.user.business.SIGNUP
|
import com.sangdol.roomescape.user.business.SIGNUP
|
||||||
import com.sangdol.roomescape.user.infrastructure.persistence.UserEntity
|
import com.sangdol.roomescape.user.infrastructure.persistence.UserEntity
|
||||||
import com.sangdol.roomescape.user.infrastructure.persistence.UserStatus
|
import com.sangdol.roomescape.user.infrastructure.persistence.UserStatus
|
||||||
@ -21,6 +23,7 @@ import io.kotest.core.test.TestCaseOrder
|
|||||||
import jakarta.persistence.EntityManager
|
import jakarta.persistence.EntityManager
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.joinAll
|
import kotlinx.coroutines.joinAll
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Semaphore
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
@ -151,22 +154,22 @@ class DefaultDataInitializer : AbstractDataInitializer() {
|
|||||||
AdminPermissionLevel.READ_SUMMARY to 3
|
AdminPermissionLevel.READ_SUMMARY to 3
|
||||||
)
|
)
|
||||||
|
|
||||||
val storeIds: List<Long> = transactionExecutionUtil.withNewTransaction(isReadOnly = true) {
|
val stores: List<StoreEntity> = transactionExecutionUtil.withNewTransaction(isReadOnly = true) {
|
||||||
entityManager.createQuery(
|
entityManager.createQuery(
|
||||||
"SELECT s.id FROM StoreEntity s",
|
"SELECT s FROM StoreEntity s",
|
||||||
Long::class.java
|
StoreEntity::class.java
|
||||||
).resultList
|
).resultList
|
||||||
}!!.map { it as Long }
|
}!!
|
||||||
|
|
||||||
transactionExecutionUtil.withNewTransaction(isReadOnly = false) {
|
transactionExecutionUtil.withNewTransaction(isReadOnly = false) {
|
||||||
storeIds.forEach { storeId ->
|
stores.forEach { store ->
|
||||||
// StoreManager 1명 생성
|
// StoreManager 1명 생성
|
||||||
val storeManager = AdminFixture.create(
|
val storeManager = AdminFixture.create(
|
||||||
account = "$storeId",
|
account = store.name,
|
||||||
name = randomKoreanName(),
|
name = randomKoreanName(),
|
||||||
phone = randomPhoneNumber(),
|
phone = randomPhoneNumber(),
|
||||||
type = AdminType.STORE,
|
type = AdminType.STORE,
|
||||||
storeId = storeId,
|
storeId = store.id,
|
||||||
permissionLevel = AdminPermissionLevel.FULL_ACCESS
|
permissionLevel = AdminPermissionLevel.FULL_ACCESS
|
||||||
).apply {
|
).apply {
|
||||||
this.createdBy = superHQAdmin.id
|
this.createdBy = superHQAdmin.id
|
||||||
@ -178,11 +181,11 @@ class DefaultDataInitializer : AbstractDataInitializer() {
|
|||||||
storeAdminCountsByPermissionLevel.forEach { (permissionLevel, count) ->
|
storeAdminCountsByPermissionLevel.forEach { (permissionLevel, count) ->
|
||||||
repeat(count) { index ->
|
repeat(count) { index ->
|
||||||
AdminFixture.create(
|
AdminFixture.create(
|
||||||
account = randomString(),
|
account = "${store.name}-${permissionLevel.ordinal}${index}",
|
||||||
name = randomKoreanName(),
|
name = randomKoreanName(),
|
||||||
phone = randomPhoneNumber(),
|
phone = randomPhoneNumber(),
|
||||||
type = AdminType.STORE,
|
type = AdminType.STORE,
|
||||||
storeId = storeId,
|
storeId = store.id,
|
||||||
permissionLevel = permissionLevel
|
permissionLevel = permissionLevel
|
||||||
).apply {
|
).apply {
|
||||||
this.createdBy = storeManager.id
|
this.createdBy = storeManager.id
|
||||||
@ -217,7 +220,7 @@ class DefaultDataInitializer : AbstractDataInitializer() {
|
|||||||
val batchArgs = mutableListOf<Array<Any>>()
|
val batchArgs = mutableListOf<Array<Any>>()
|
||||||
|
|
||||||
repeat(500) { i ->
|
repeat(500) { i ->
|
||||||
val randomDay = if (i <= 9) (1..30).random() else (1..365 * 2).random()
|
val randomDay = if (i <= 9) (7..30).random() else (30..365 * 2).random()
|
||||||
val randomCreatedAt: LocalDateTime = LocalDateTime.now().minusDays(randomDay.toLong())
|
val randomCreatedAt: LocalDateTime = LocalDateTime.now().minusDays(randomDay.toLong())
|
||||||
val randomThemeName =
|
val randomThemeName =
|
||||||
(1..7).random().let { repeat -> (1..repeat).joinToString("") { randomKoreanName() } }
|
(1..7).random().let { repeat -> (1..repeat).joinToString("") { randomKoreanName() } }
|
||||||
@ -417,25 +420,49 @@ class UserDataInitializer : AbstractDataInitializer() {
|
|||||||
class ScheduleDataInitializer : AbstractDataInitializer() {
|
class ScheduleDataInitializer : AbstractDataInitializer() {
|
||||||
init {
|
init {
|
||||||
context("일정 초기 데이터 생성") {
|
context("일정 초기 데이터 생성") {
|
||||||
test("테마 생성일 기준으로 다음 3일차, 매일 5개의 일정을 모든 매장에 생성") {
|
test("테마 생성일 기준으로 다음 3일차, 매일 최대 10개의 일정을 모든 매장에 생성") {
|
||||||
val stores: List<Pair<Long, Long>> = getStoreWithManagers()
|
val stores: List<Pair<Long, Long>> = getStoreWithManagers()
|
||||||
val themes: List<Triple<Long, Short, LocalDateTime>> = getThemes()
|
val themes: List<ThemeEntity> = getThemes()
|
||||||
val maxAvailableMinutes = themes.maxOf { it.second.toInt() }
|
val maxScheduleCountPerDay = 10
|
||||||
val scheduleCountPerDay = 5
|
|
||||||
|
|
||||||
val startTime = LocalTime.of(10, 0)
|
val startTime = LocalTime.of(10, 0)
|
||||||
var lastTime = startTime
|
|
||||||
val times = mutableListOf<LocalTime>()
|
|
||||||
|
|
||||||
repeat(scheduleCountPerDay) {
|
val themeWithTimes: Map<ThemeEntity, List<LocalTime>> = themes.associateWith { theme ->
|
||||||
|
val times = mutableListOf<LocalTime>()
|
||||||
|
val themeAvailableMinutes = theme.availableMinutes
|
||||||
|
var lastTime = startTime
|
||||||
|
|
||||||
|
while (times.size <= maxScheduleCountPerDay && lastTime.hour in (10..23)) {
|
||||||
times.add(lastTime)
|
times.add(lastTime)
|
||||||
lastTime = lastTime.plusMinutes(maxAvailableMinutes.toLong() + 10L)
|
lastTime = lastTime.plusMinutes(themeAvailableMinutes + 10L)
|
||||||
|
}
|
||||||
|
|
||||||
|
times
|
||||||
}
|
}
|
||||||
|
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
themes.forEach { theme ->
|
stores.map { store ->
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
processTheme(theme, stores, times)
|
processTheme(store, themeWithTimes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("내일 ~ 일주일 뒤 까지의 일정 생성") {
|
||||||
|
// val stores: List<Pair<Long, Long>> = getStoreWithManagers()
|
||||||
|
// val availableThemes: List<ThemeEntity> = transactionExecutionUtil.withNewTransaction(isReadOnly = true) {
|
||||||
|
// entityManager.createQuery(
|
||||||
|
// "SELECT t FROM ThemeEntity t WHERE t.isActive = true AND t.createdAt >", ThemeEntity::class.java
|
||||||
|
// ).resultList
|
||||||
|
// }!!.take(10)
|
||||||
|
|
||||||
|
coroutineScope {
|
||||||
|
val jobs = (1..100).map { i ->
|
||||||
|
launch(Dispatchers.IO) {
|
||||||
|
val threadName = Thread.currentThread().name
|
||||||
|
println("[$i] 시작: $threadName")
|
||||||
|
delay(1)
|
||||||
|
println("[$i] 완료: $threadName")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -444,9 +471,8 @@ class ScheduleDataInitializer : AbstractDataInitializer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun processTheme(
|
private suspend fun processTheme(
|
||||||
theme: Triple<Long, Short, LocalDateTime>,
|
store: Pair<Long, Long>,
|
||||||
stores: List<Pair<Long, Long>>,
|
themeWithTimes: Map<ThemeEntity, List<LocalTime>>
|
||||||
times: List<LocalTime>
|
|
||||||
) {
|
) {
|
||||||
val sql = """
|
val sql = """
|
||||||
INSERT INTO schedule (
|
INSERT INTO schedule (
|
||||||
@ -457,24 +483,22 @@ class ScheduleDataInitializer : AbstractDataInitializer() {
|
|||||||
|
|
||||||
val batchArgs = mutableListOf<Array<Any>>()
|
val batchArgs = mutableListOf<Array<Any>>()
|
||||||
|
|
||||||
val now = LocalDateTime.now()
|
val status = ScheduleStatus.RESERVED.name
|
||||||
stores.forEach { (storeId, adminId) ->
|
themeWithTimes.forEach { (theme, times) ->
|
||||||
(1..3).forEach { dayOffset ->
|
val themeCreatedAt = theme.createdAt
|
||||||
val date = theme.third.toLocalDate().plusDays(dayOffset.toLong())
|
(1..3).forEach {
|
||||||
|
val date = themeCreatedAt.toLocalDate().plusDays(it.toLong())
|
||||||
times.forEach { time ->
|
times.forEach { time ->
|
||||||
val scheduledAt = LocalDateTime.of(date, time)
|
val storeId = store.first
|
||||||
val status =
|
val storeAdminId = store.second
|
||||||
if (scheduledAt.isAfter(now)) ScheduleStatus.AVAILABLE.name else ScheduleStatus.RESERVED.name
|
|
||||||
|
|
||||||
batchArgs.add(
|
batchArgs.add(
|
||||||
arrayOf(
|
arrayOf(
|
||||||
idGenerator.create(), storeId, theme.first, date, time,
|
idGenerator.create(), storeId, theme.id, date, time,
|
||||||
status, adminId, adminId, Timestamp.valueOf(now), Timestamp.valueOf(now)
|
status, storeAdminId, storeAdminId, themeCreatedAt.plusHours(1), themeCreatedAt.plusHours(1)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (batchArgs.size >= 500) {
|
if (batchArgs.size >= 300) {
|
||||||
executeBatch(sql, batchArgs).also { batchArgs.clear() }
|
executeBatch(sql, batchArgs).also { batchArgs.clear() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -500,17 +524,13 @@ class ScheduleDataInitializer : AbstractDataInitializer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getThemes(): List<Triple<Long, Short, LocalDateTime>> {
|
private fun getThemes(): List<ThemeEntity> {
|
||||||
return transactionExecutionUtil.withNewTransaction(isReadOnly = true) {
|
return transactionExecutionUtil.withNewTransaction(isReadOnly = true) {
|
||||||
entityManager.createQuery(
|
entityManager.createQuery(
|
||||||
"SELECT t._id, t.availableMinutes, t.createdAt FROM ThemeEntity t",
|
"SELECT t FROM ThemeEntity t",
|
||||||
List::class.java
|
ThemeEntity::class.java
|
||||||
)
|
).resultList
|
||||||
.resultList
|
}!!
|
||||||
}!!.map {
|
|
||||||
val array = it as List<*>
|
|
||||||
Triple(array[0] as Long, array[1] as Short, array[2] as LocalDateTime)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -528,10 +548,10 @@ class ReservationDataInitializer : AbstractDataInitializer() {
|
|||||||
init {
|
init {
|
||||||
context("예약 초기 데이터 생성") {
|
context("예약 초기 데이터 생성") {
|
||||||
test("${ScheduleStatus.RESERVED}인 모든 일정에 예약을 1개씩 배정한다.") {
|
test("${ScheduleStatus.RESERVED}인 모든 일정에 예약을 1개씩 배정한다.") {
|
||||||
val chunkSize = 10_000
|
val chunkSize = 500
|
||||||
|
|
||||||
val chunkedSchedules: List<List<ScheduleWithThemeParticipants>> = entityManager.createQuery(
|
val chunkedSchedules: List<List<ScheduleWithThemeParticipants>> = entityManager.createQuery(
|
||||||
"SELECT new com.sangdol.data.ScheduleWithThemeParticipants(s._id, t.minParticipants, t.maxParticipants) FROM ScheduleEntity s JOIN ThemeEntity t ON s.themeId = t.id WHERE s.status = :status",
|
"SELECT new com.sangdol.roomescape.data.ScheduleWithThemeParticipants(s._id, t.minParticipants, t.maxParticipants) FROM ScheduleEntity s JOIN ThemeEntity t ON s.themeId = t.id WHERE s.status = :status",
|
||||||
ScheduleWithThemeParticipants::class.java
|
ScheduleWithThemeParticipants::class.java
|
||||||
).setParameter("status", ScheduleStatus.RESERVED).resultList.chunked(chunkSize)
|
).setParameter("status", ScheduleStatus.RESERVED).resultList.chunked(chunkSize)
|
||||||
|
|
||||||
@ -587,10 +607,6 @@ class ReservationDataInitializer : AbstractDataInitializer() {
|
|||||||
user.id,
|
user.id,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (batchArgs.size >= 1_000) {
|
|
||||||
executeBatch(sql, batchArgs).also { batchArgs.clear() }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (batchArgs.isNotEmpty()) executeBatch(sql, batchArgs).also { batchArgs.clear() }
|
if (batchArgs.isNotEmpty()) executeBatch(sql, batchArgs).also { batchArgs.clear() }
|
||||||
@ -671,7 +687,7 @@ class PaymentDataInitializer : AbstractDataInitializer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
allReservations.chunked(10_000).forEach { reservations ->
|
allReservations.chunked(500).forEach { reservations ->
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
processPaymentAndDefaultDetail(reservations)
|
processPaymentAndDefaultDetail(reservations)
|
||||||
}
|
}
|
||||||
@ -681,12 +697,12 @@ class PaymentDataInitializer : AbstractDataInitializer() {
|
|||||||
|
|
||||||
test("기존 결제 데이터에 상세 정보(계좌이체, 카드, 간편결제) 데이터를 생성한다.") {
|
test("기존 결제 데이터에 상세 정보(계좌이체, 카드, 간편결제) 데이터를 생성한다.") {
|
||||||
val allPayments: List<PaymentWithMethods> = entityManager.createQuery(
|
val allPayments: List<PaymentWithMethods> = entityManager.createQuery(
|
||||||
"SELECT new com.sangdol.data.PaymentWithMethods(pd._id, p.totalAmount, p.method) FROM PaymentEntity p JOIN PaymentDetailEntity pd ON p._id = pd.paymentId",
|
"SELECT new com.sangdol.roomescape.data.PaymentWithMethods(pd._id, p.totalAmount, p.method) FROM PaymentEntity p JOIN PaymentDetailEntity pd ON p._id = pd.paymentId",
|
||||||
PaymentWithMethods::class.java
|
PaymentWithMethods::class.java
|
||||||
).resultList
|
).resultList
|
||||||
|
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
allPayments.chunked(10_000).forEach { payments ->
|
allPayments.chunked(500).forEach { payments ->
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
processPaymentDetail(payments)
|
processPaymentDetail(payments)
|
||||||
}
|
}
|
||||||
@ -731,9 +747,6 @@ class PaymentDataInitializer : AbstractDataInitializer() {
|
|||||||
approvedAtCache,
|
approvedAtCache,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if (paymentBatchArgs.size >= 1_000) {
|
|
||||||
executeBatch(paymentSql, paymentBatchArgs).also { paymentBatchArgs.clear() }
|
|
||||||
}
|
|
||||||
|
|
||||||
val suppliedAmount: Int = (totalPrice * 0.9).toInt()
|
val suppliedAmount: Int = (totalPrice * 0.9).toInt()
|
||||||
val vat: Int = (totalPrice - suppliedAmount)
|
val vat: Int = (totalPrice - suppliedAmount)
|
||||||
@ -746,10 +759,6 @@ class PaymentDataInitializer : AbstractDataInitializer() {
|
|||||||
vat
|
vat
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (detailBatchArgs.size >= 1_000) {
|
|
||||||
executeBatch(paymentDetailSql, detailBatchArgs).also { detailBatchArgs.clear() }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (paymentBatchArgs.isNotEmpty()) {
|
if (paymentBatchArgs.isNotEmpty()) {
|
||||||
@ -780,9 +789,6 @@ class PaymentDataInitializer : AbstractDataInitializer() {
|
|||||||
settlementStatus
|
settlementStatus
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if (transferBatchArgs.size >= 1_000) {
|
|
||||||
executeBatch(paymentBankTransferDetailSql, transferBatchArgs).also { transferBatchArgs.clear() }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PaymentMethod.EASY_PAY -> {
|
PaymentMethod.EASY_PAY -> {
|
||||||
@ -803,10 +809,6 @@ class PaymentDataInitializer : AbstractDataInitializer() {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (cardBatchArgs.size >= 1_000) {
|
|
||||||
executeBatch(paymentCardDetailSql, cardBatchArgs).also { cardBatchArgs.clear() }
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
easypayPrepaidBatchArgs.add(
|
easypayPrepaidBatchArgs.add(
|
||||||
arrayOf(
|
arrayOf(
|
||||||
@ -816,10 +818,6 @@ class PaymentDataInitializer : AbstractDataInitializer() {
|
|||||||
randomDiscountAmount,
|
randomDiscountAmount,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (easypayPrepaidBatchArgs.size >= 1_000) {
|
|
||||||
executeBatch(paymentEasypayPrepaidDetailSql, easypayPrepaidBatchArgs).also { easypayPrepaidBatchArgs.clear() }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -839,10 +837,6 @@ class PaymentDataInitializer : AbstractDataInitializer() {
|
|||||||
0,
|
0,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (cardBatchArgs.size >= 1_000) {
|
|
||||||
executeBatch(paymentCardDetailSql, cardBatchArgs).also { cardBatchArgs.clear() }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> return@forEach
|
else -> return@forEach
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user