feat(content): 콘텐츠 업로드 후 languageCode가 null이면 naver papago 언어 감지 API 호출 기능 추가
This commit is contained in:
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -332,6 +332,24 @@ class AudioContentService(
|
|||||||
|
|
||||||
audioContent.content = contentPath
|
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!!)
|
return CreateAudioContentResponse(contentId = audioContent.id!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ google:
|
|||||||
webClientId: ${GOOGLE_WEB_CLIENT_ID}
|
webClientId: ${GOOGLE_WEB_CLIENT_ID}
|
||||||
|
|
||||||
cloud:
|
cloud:
|
||||||
|
naver:
|
||||||
|
papagoClientId: ${NCLOUD_PAPAGO_CLIENT_ID}
|
||||||
|
papagoClientSecret: ${NCLOUD_PAPAGO_CLIENT_SECRET}
|
||||||
aws:
|
aws:
|
||||||
credentials:
|
credentials:
|
||||||
accessKey: ${APP_AWS_ACCESS_KEY}
|
accessKey: ${APP_AWS_ACCESS_KEY}
|
||||||
|
|||||||
Reference in New Issue
Block a user