feat(content): 콘텐츠 업로드 후 languageCode가 null이면 naver papago 언어 감지 API 호출 기능 추가

This commit is contained in:
2025-11-25 15:11:27 +09:00
parent edaea84a5b
commit 93ccb666c4
3 changed files with 142 additions and 0 deletions

View File

@@ -0,0 +1,121 @@
package kr.co.vividnext.sodalive.content
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.http.HttpEntity
import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType
import org.springframework.scheduling.annotation.Async
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional
import org.springframework.transaction.event.TransactionPhase
import org.springframework.transaction.event.TransactionalEventListener
import org.springframework.util.LinkedMultiValueMap
import org.springframework.web.client.RestTemplate
/**
* 오디오 콘텐츠 메타데이터(제목/내용/태그)를 기반으로 파파고 언어 감지를 요청하기 위한 이벤트.
*/
class AudioContentLanguageDetectEvent(
val contentId: Long,
val query: String
)
data class PapagoLanguageDetectResponse(
val langCode: String?
)
@Component
class AudioContentLanguageDetectListener(
private val audioContentRepository: AudioContentRepository,
@Value("\${cloud.naver.papago-client-id}")
private val papagoClientId: String,
@Value("\${cloud.naver.papago-client-secret}")
private val papagoClientSecret: String
) {
private val log = LoggerFactory.getLogger(AudioContentLanguageDetectListener::class.java)
private val restTemplate: RestTemplate = RestTemplate()
private val papagoDetectUrl = "https://papago.apigw.ntruss.com/langs/v1/dect"
@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
@Transactional(propagation = Propagation.REQUIRES_NEW)
fun detectLanguage(event: AudioContentLanguageDetectEvent) {
if (event.query.isBlank()) {
log.debug("[PapagoLanguageDetect] query is blank. Skip language detection. contentId={}", event.contentId)
return
}
val audioContent = audioContentRepository.findById(event.contentId).orElse(null)
if (audioContent == null) {
log.warn("[PapagoLanguageDetect] AudioContent not found. contentId={}", event.contentId)
return
}
// 이미 언어 코드가 설정된 경우 호출하지 않음
if (!audioContent.languageCode.isNullOrBlank()) {
log.debug(
"[PapagoLanguageDetect] languageCode already set. Skip language detection. contentId={}, languageCode={}",
event.contentId,
audioContent.languageCode
)
return
}
try {
val headers = HttpHeaders().apply {
contentType = MediaType.APPLICATION_FORM_URLENCODED
set("X-NCP-APIGW-API-KEY-ID", papagoClientId)
set("X-NCP-APIGW-API-KEY", papagoClientSecret)
}
val body = LinkedMultiValueMap<String, String>().apply {
// 파파고 스펙에 따라 query 파라미터에 제목/내용/태그를 공백으로 구분하여 전달
add("query", event.query)
}
val requestEntity = HttpEntity(body, headers)
val response = restTemplate.postForEntity(
papagoDetectUrl,
requestEntity,
PapagoLanguageDetectResponse::class.java
)
if (!response.statusCode.is2xxSuccessful) {
log.warn(
"[PapagoLanguageDetect] Non-success status from Papago. status={}, contentId={}",
response.statusCode,
event.contentId
)
return
}
val langCode = response.body?.langCode?.takeIf { it.isNotBlank() }
if (langCode == null) {
log.warn("[PapagoLanguageDetect] langCode is null or blank in Papago response. contentId={}", event.contentId)
return
}
audioContent.languageCode = langCode
// REQUIRES_NEW 트랜잭션 내에서 변경 내용을 저장한다.
audioContentRepository.save(audioContent)
log.info(
"[PapagoLanguageDetect] languageCode updated from Papago. contentId={}, langCode={}",
event.contentId,
langCode
)
} catch (ex: Exception) {
// 언어 감지는 부가 기능이므로, 실패 시 예외를 전파하지 않고 로그만 남긴다.
log.error("[PapagoLanguageDetect] Failed to detect language via Papago. contentId={}", event.contentId, ex)
}
}
}

View File

@@ -332,6 +332,24 @@ class AudioContentService(
audioContent.content = contentPath
// 언어 코드가 지정되지 않은 경우, 파파고 언어 감지 API를 통해 비동기로 언어를 식별한다.
if (audioContent.languageCode.isNullOrBlank()) {
val papagoQuery = listOf(
request.title.trim(),
request.detail.trim(),
request.tags.trim()
)
.filter { it.isNotBlank() }
.joinToString(" ")
applicationEventPublisher.publishEvent(
AudioContentLanguageDetectEvent(
contentId = audioContent.id!!,
query = papagoQuery
)
)
}
return CreateAudioContentResponse(contentId = audioContent.id!!)
}

View File

@@ -45,6 +45,9 @@ google:
webClientId: ${GOOGLE_WEB_CLIENT_ID}
cloud:
naver:
papagoClientId: ${NCLOUD_PAPAGO_CLIENT_ID}
papagoClientSecret: ${NCLOUD_PAPAGO_CLIENT_SECRET}
aws:
credentials:
accessKey: ${APP_AWS_ACCESS_KEY}