Skip to content

Commit 2b754b9

Browse files
committed
perf: stream multipart event sync in batches
1 parent 91f9c11 commit 2b754b9

2 files changed

Lines changed: 63 additions & 4 deletions

File tree

hangsha/src/main/kotlin/com/team1/hangsha/config/SecurityConfig.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,8 @@ class SecurityConfig(
6363
// 주최 기관
6464
"/api/v1/category-groups/**",
6565
"/api/v1/categories/**",
66-
// @TODO: 자동 크롤링 시 삭제 필요
67-
"/admin/events/sync",
68-
"/admin/events/delete",
66+
// admin
67+
"/admin/**",
6968
// 파일 업로드
7069
"/static/**",
7170
"/api/v1/uploads/oci/**",

hangsha/src/main/kotlin/com/team1/hangsha/event/controller/EventSyncController.kt

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,83 @@
11
package com.team1.hangsha.event.controller
22

3+
import com.fasterxml.jackson.core.JsonToken
4+
import com.fasterxml.jackson.databind.ObjectMapper
35
import com.team1.hangsha.event.dto.core.CrawledProgramEvent
46
import com.team1.hangsha.event.dto.request.EventPatchRequest
57
import com.team1.hangsha.event.repository.EventRepository
68
import com.team1.hangsha.event.service.EventSyncService
9+
import org.springframework.http.HttpStatus
710
import org.springframework.web.bind.annotation.DeleteMapping
8-
import org.springframework.web.bind.annotation.PostMapping
911
import org.springframework.web.bind.annotation.PatchMapping
1012
import org.springframework.web.bind.annotation.PathVariable
13+
import org.springframework.web.bind.annotation.PostMapping
1114
import org.springframework.web.bind.annotation.RequestBody
15+
import org.springframework.web.bind.annotation.RequestParam
1216
import org.springframework.web.bind.annotation.RequestMapping
1317
import org.springframework.web.bind.annotation.RestController
18+
import org.springframework.web.server.ResponseStatusException
19+
import org.springframework.web.multipart.MultipartFile
1420

1521
@RestController
1622
@RequestMapping("/admin/events")
1723
class EventSyncController(
1824
private val eventSyncService: EventSyncService,
1925
private val eventRepository: EventRepository,
26+
private val objectMapper: ObjectMapper,
2027
) {
2128
@PostMapping("/sync")
2229
fun sync(
2330
@RequestBody events: List<CrawledProgramEvent>,
2431
): Map<String, Any> {
32+
return runSync(events)
33+
}
34+
35+
@PostMapping("/sync", consumes = ["multipart/form-data"])
36+
fun syncByFile(
37+
@RequestParam("file") file: MultipartFile,
38+
): Map<String, Any> {
39+
file.inputStream.use { input ->
40+
objectMapper.factory.createParser(input).use { parser ->
41+
if (parser.nextToken() != JsonToken.START_ARRAY) {
42+
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "file must be a JSON array")
43+
}
44+
45+
var total = 0
46+
var upserted = 0
47+
var skipped = 0
48+
val buffer = ArrayList<CrawledProgramEvent>(SYNC_BATCH_SIZE)
49+
50+
while (parser.nextToken() != JsonToken.END_ARRAY) {
51+
val event = objectMapper.readValue(parser, CrawledProgramEvent::class.java)
52+
buffer.add(event)
53+
54+
if (buffer.size >= SYNC_BATCH_SIZE) {
55+
val result = eventSyncService.sync(buffer)
56+
total += result.total
57+
upserted += result.upserted
58+
skipped += result.skipped
59+
buffer.clear()
60+
}
61+
}
62+
63+
if (buffer.isNotEmpty()) {
64+
val result = eventSyncService.sync(buffer)
65+
total += result.total
66+
upserted += result.upserted
67+
skipped += result.skipped
68+
}
69+
70+
return mapOf(
71+
"ok" to true,
72+
"total" to total,
73+
"upserted" to upserted,
74+
"skipped" to skipped,
75+
)
76+
}
77+
}
78+
}
79+
80+
private fun runSync(events: List<CrawledProgramEvent>): Map<String, Any> {
2581
val result = eventSyncService.sync(events)
2682
return mapOf(
2783
"ok" to true,
@@ -47,4 +103,8 @@ class EventSyncController(
47103
fun deleteEvent(
48104
@PathVariable eventId: Long,
49105
): Map<String, Any> = eventSyncService.deleteEvent(eventId)
106+
107+
companion object {
108+
private const val SYNC_BATCH_SIZE = 500
109+
}
50110
}

0 commit comments

Comments
 (0)