Jsoup + Open AI
2025. 2. 21. 19:17ㆍTIL
사용자로부터 til keyword를 얻어 db에 리스트 형태로 저장하기 위한 db 설계 til keyword를 얻어 db에 리스트 형태로 저장하기 위한 db 설계
package hjp.nextil.domain.til.entity
import hjp.nextil.domain.member.entity.MemberEntity
import jakarta.persistence.*
@Entity
class TilEntity(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? =null,
@ManyToOne
@JoinColumn(name = "member_id", nullable = false)
val memberId: MemberEntity,
@ElementCollection
@CollectionTable(name = "til_keywords", joinColumns = [JoinColumn(name = "til_id")])
@Column(name = "keyword")
var tilKeyword: List<String> = mutableListOf()
) {
}
사용자가 보낸 URL의 HTML을 가져오기 위해서 Jsoup 라이브러리 사용
dependencies {
implementation("org.jsoup:jsoup:1.15.3")
}
사용자가 url을 입력하면 해당 url에서 html파일을 가져오는 로직 구현
package hjp.nextil.domain.til.service
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.springframework.stereotype.Service
@Service
class TilService {
fun getBodyText(url: String): String{
return extractMainContent(fetchHtmlFromUrl(url))
}
fun fetchHtmlFromUrl(url: String): Document {
val cleanUrl = url.replace("\"", "").replace("'", "").trim()
return Jsoup.connect(cleanUrl)
.userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36") // 크롤링 방지 우회
.timeout(5000)
.get()
}
fun extractMainContent(document: Document): String {
return document.select("body").text()
}
}
크롤링을 통해서 html 파일을 가져왔으니 해당 html 본문 내용을 읽고 키워드를 1개에서 3개 사이로 추리는 작업이 시행되어야 한다.
Open AI
API KEY 생성
https://platform.openai.com/api-keys
Open AI 의존성 추가
dependencies {
implementation("org.springframework.ai:spring-ai-openai-spring-boot-starter:1.0.0-M6")
}
application.yml 추가
ai:
openai:
api-key: sk-로 시작하는 내가 발급받은 API키
chat:
options:
model: gpt-4o-mini-2024-07-18
openAIService 작성
package hjp.nextil.domain.ai
import hjp.nextil.domain.til.entity.TilEntity
import hjp.nextil.domain.til.repository.TilRepository
import hjp.nextil.security.jwt.UserPrincipal
import jakarta.transaction.Transactional
import org.springframework.ai.chat.client.ChatClient
import org.springframework.ai.chat.messages.SystemMessage
import org.springframework.ai.chat.messages.UserMessage
import org.springframework.ai.chat.prompt.Prompt
import org.springframework.stereotype.Service
import kotlinx.serialization.json.Json
@Service
class OpenAIService(
private val chatClient: ChatClient,
private val tilRepository: TilRepository,
) {
@Transactional
fun extractKeywordsFromText(text: String, user: UserPrincipal): List<String> {
val systemMessage = SystemMessage("너는 키워드 추출 전문가야. 다음 텍스트의 핵심 개발 키워드를 1~3개 추출해서 JSON 배열로 반환해줘.")
val userMessage = UserMessage("본문:\n$text")
// OpenAI API 호출
val response = chatClient.prompt(Prompt(listOf(systemMessage, userMessage))).call()
val cleanedJson = response.content()?.removeSurrounding("```json", "```")?.trim() ?: ""
val newKeywords = parseJsonKeywords(cleanedJson)
val existingTilEntity = tilRepository.findByMemberId(user.memberId)
val myKeywords = if (existingTilEntity == null) {
val newTilEntity = TilEntity(
memberId = user.memberId,
tilKeyword = newKeywords
)
tilRepository.save(newTilEntity)
newKeywords.toMutableList()
} else {
existingTilEntity.flatMap { it.tilKeyword }.toMutableList()
}
tilRepository.deleteAllByMemberId(user.memberId)
val updatedKeywords = (myKeywords + newKeywords).toSet().toList()
tilRepository.save(
TilEntity(
memberId = user.memberId,
tilKeyword = updatedKeywords,
)
)
return parseJsonKeywords(cleanedJson)
}
private fun parseJsonKeywords(json: String): List<String> {
return Json.decodeFromString<List<String>>(json)
}
}
Open AI Controller 작성
package hjp.nextil.domain.ai
import hjp.nextil.security.jwt.UserPrincipal
import org.springframework.http.ResponseEntity
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.*
@RestController
@RequestMapping("/api/openai")
class OpenAIController(
private val openAIService: OpenAIService,
) {
@PostMapping("/extract")
fun extract(@RequestBody text: UserText, @AuthenticationPrincipal user: UserPrincipal): ResponseEntity<List<String>> {
return ResponseEntity.ok().body(openAIService.extractKeywordsFromText(text = text.tilText, user= user))
}
}
테스트:
테스트 영상: https://www.youtube.com/watch?v=ihcNmnUpkRg
'TIL' 카테고리의 다른 글
HTTP/3 VS HTTP/2 (0) | 2025.02.25 |
---|---|
OPEN AI를 이용한 TIL 추천 서비스 NEXTIL (0) | 2025.02.24 |
open ai 설정 오류 해결 (1) | 2025.02.20 |
java.net.MalformedURLException: no protocol 오류 해결 (0) | 2025.02.19 |
NEXTIL 프로젝트 kakao 로그인 구현 (1) | 2025.02.18 |