Open CutTheWire opened 2 months ago
JWT 토큰 기반 인증 미들웨어: TokenAuth.kt
파일에서 JWT 토큰을 처리하여 사용자의 user_id
를 추출하는 미들웨어를 사용. 이 미들웨어는 각 요청에 대해 유저를 인증하고 user_id
를 제공하여 MongoDB 관련 기능과 통합할 수 있도록 처리.
라우팅 및 URL 구조:
nono.me/chatroom/c/캐릭터 고유번호
페이지에서 유저가 채팅을 입력하면 FastAPI 서버에 해당 데이터를 전송하여 MongoDB에 chatbot_log_{user_id}
컬렉션을 생성.nono.me/chatroom/c/캐릭터 고유번호/u/채팅방 고유번호
페이지에서 해당 채팅방 데이터를 로드.nono.me/chatroom/o/gpt
페이지에서 채팅을 입력하면 FastAPI 서버에 해당 데이터를 전송하여 MongoDB에 office_log_{user_id}
컬렉션을 생성.nono.me/chatroom/o/gpt/u/채팅방 고유번호
페이지에서 해당 채팅방 데이터를 로드.컨트롤러 (Controller) 구성
@RestController
@RequestMapping("/server/chatroom")
class ChatController(private val tokenAuth: TokenAuth) {
// 채팅방 생성 (Chatbot)
@PostMapping("/c/{characterId}")
fun createChatbotRoom(
@RequestHeader("Authorization") token: String,
@PathVariable characterId: String
): ResponseEntity<String> {
val userId = tokenAuth.authGuard(token)
// FastAPI 서버로 채팅방 생성 요청을 보내는 로직
// ...
return ResponseEntity.ok("chatroom created with characterId: $characterId")
}
// 채팅방 생성 (Office GPT)
@PostMapping("/o/gpt")
fun createGptRoom(@RequestHeader("Authorization") token: String): ResponseEntity<String> {
val userId = tokenAuth.authGuard(token)
// FastAPI 서버로 GPT 채팅방 생성 요청을 보내는 로직
// ...
return ResponseEntity.ok("GPT chatroom created")
}
// 채팅 로드 (Chatbot)
@GetMapping("/c/{characterId}/u/{chatroomId}")
fun getChatbotLog(
@RequestHeader("Authorization") token: String,
@PathVariable characterId: String,
@PathVariable chatroomId: String
): ResponseEntity<String> {
val userId = tokenAuth.authGuard(token)
// FastAPI 서버로부터 MongoDB에서 데이터를 받아오는 로직
// ...
return ResponseEntity.ok("chat logs for chatbot")
}
// 채팅 로드 (Office GPT)
@GetMapping("/o/gpt/u/{chatroomId}")
fun getGptLog(
@RequestHeader("Authorization") token: String,
@PathVariable chatroomId: String
): ResponseEntity<String> {
val userId = tokenAuth.authGuard(token)
// FastAPI 서버로부터 MongoDB에서 데이터를 받아오는 로직
// ...
return ResponseEntity.ok("chat logs for GPT")
}
}
Feign 클라이언트로 FastAPI 서버 호출:
FeignClient
또는 RestTemplate
을 사용할 수 있습니다. FeignClient
를 사용하면 간결하게 외부 API를 호출할 수 있습니다.@FeignClient(name = "chatService", url = "\${fastapi.server.url}")
interface ChatServiceClient {
@PostMapping("/mongo/chatbot/create")
fun createChatbotRoom(@RequestBody userId: Long): ResponseEntity<String>
@PostMapping("/mongo/office/create")
fun createGptRoom(@RequestBody userId: Long): ResponseEntity<String>
@GetMapping("/mongo/chatbot/load/{chatroomId}")
fun getChatbotLog(@PathVariable chatroomId: String): ResponseEntity<String>
@GetMapping("/mongo/office/load/{chatroomId}")
fun getGptLog(@PathVariable chatroomId: String): ResponseEntity<String>
}
설명:
@FeignClient
를 통해 FastAPI 서버와 통신하며, 채팅방 생성, 로그 저장, 로그 불러오기 등의 작업을 FastAPI에 요청.RestTemplate
을 사용할 경우 더 세부적인 설정이 필요하지만, 그만큼 유연한 커스터마이징이 가능합니다.의존성 주입 및 환경 설정:
application.yml
파일에 FastAPI 서버의 URL을 정의합니다.fastapi:
server:
url: http://localhost:8000 # FastAPI 서버의 주소
예외 처리 (Custom Exceptions):
TokenAuth.kt
에서 정의한 JWT 관련 예외들을 전역적으로 처리하는 방식으로 Spring Boot의 @ControllerAdvice
를 사용해 처리합니다.@ControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(TokenExpiredException::class)
fun handleTokenExpiredException(ex: TokenExpiredException): ResponseEntity<String> {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(ex.message)
}
@ExceptionHandler(TokenMalformedException::class)
fun handleTokenMalformedException(ex: TokenMalformedException): ResponseEntity<String> {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ex.message)
}
// 다른 예외 처리 로직도 동일하게 추가
}
전체적인 코드 개편과 Model 수정으로 인해 위, 이슈의 코드는 사용 중지.
해당 이슈에 대한 코드를 사용.
📦ChatBot_Backend
┣ 📂controller
┃ ┣ 📂config
┃ ┃ ┣ 📜SecurityConfig.kt
┃ ┃ ┗ 📜WebClientConfig.kt
┃ ┣ 📜CharacterController.kt
┃ ┣ 📜ChatroomController.kt
┃ ┗ 📜UserController.kt
┣ 📂exceptions
┃ ┗ 📜TokenAuthException.kt
┣ 📂middleware
┃ ┗ 📜TokenAuth.kt
┣ 📂model
┃ ┣ 📜Character.kt
┃ ┣ 📜Chatroom.kt
┃ ┣ 📜Officeroom.kt
┃ ┗ 📜User.kt
┣ 📂repository
┃ ┣ 📜CharacterRepository.kt
┃ ┣ 📜ChatroomRepository.kt
┃ ┣ 📜OfficeroomRepository.kt
┃ ┗ 📜UserRepository.kt
┣ 📂service
┃ ┣ 📜CharacterService.kt
┃ ┣ 📜ChatroomService.kt
┃ ┗ 📜UserService.kt
┗ 📜ChatBotBackendApplication.kt
package com.TreeNut.ChatBot_Backend.config
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.reactive.function.client.WebClient
@Configuration
class WebClientConfig {
@Bean
fun webClientBuilder(): WebClient.Builder {
return WebClient.builder()
.baseUrl("http://fastapi:8000") // 기본 URL 설정
}
}
package com.TreeNut.ChatBot_Backend.controller
import com.TreeNut.ChatBot_Backend.service.ChatroomService
import com.TreeNut.ChatBot_Backend.middleware.TokenAuth
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import reactor.core.publisher.Mono
@RestController
@RequestMapping("/server/chatroom")
class ChatroomController(
private val chatroomService: ChatroomService,
private val tokenAuth: TokenAuth
) {
@GetMapping("/test")
fun testRoom(
@RequestHeader("Authorization") authorization: String?
): ResponseEntity<Map<Any, Any>> {
val token = authorization
?: return ResponseEntity.badRequest().body(mapOf("status" to 401, "message" to "토큰 없음"))
val userId = tokenAuth.authGuard(token)
?: return ResponseEntity.badRequest().body(mapOf("status" to 401, "message" to "유효한 토큰이 필요합니다."))
return ResponseEntity.ok(mapOf("status" to 200, "user_id" to userId))
}
@GetMapping("/gpt")
fun createGptRoom(
@RequestHeader("Authorization") authorization: String?
): Mono<ResponseEntity<Map<String, Any>>> {
val token = authorization
?: return Mono.just(ResponseEntity.badRequest().body(mapOf("status" to 401, "message" to "토큰 없음")))
val userId = tokenAuth.authGuard(token)
?: return Mono.just(ResponseEntity.badRequest().body(mapOf("status" to 401, "message" to "유효한 토큰이 필요합니다.")))
// FastAPI 서버에 요청하고 결과를 받아 채팅방을 생성하는 로직
return chatroomService.createOfficeroom(userId)
.map { response ->
// 응답을 그대로 반환
ResponseEntity.ok(mapOf(
"status" to 200,
"message" to "채팅방이 성공적으로 생성되었습니다.",
"chatroom" to response // FastAPI에서 받은 응답
))
}
.defaultIfEmpty(
ResponseEntity.status(500).body(mapOf(
"status" to 500,
"message" to "채팅방 생성에 실패했습니다."
))
)
}
}
package com.TreeNut.ChatBot_Backend.model
import jakarta.persistence.*
import java.time.LocalDateTime
@Entity
@Table(name = "chatroom")
data class Chatroom(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "idx")
val idx: Long? = null,
@Column(name = "userid", nullable = false, length = 100)
val userid: String, // 외래 키로 설정될 수 있음
@Column(name = "characters_idx", nullable = false)
val charactersIdx: Int, // 외래 키로 설정될 수 있음
@Column(name = "mongo_chatlog", length = 100)
val mongoChatlog: String? = null,
@Column(name = "created_at", updatable = false)
val createdAt: LocalDateTime = LocalDateTime.now(),
@Column(name = "updated_at")
var updatedAt: LocalDateTime = LocalDateTime.now()
) {
@PreUpdate
fun onUpdate() {
updatedAt = LocalDateTime.now()
}
}
package com.TreeNut.ChatBot_Backend.model
import jakarta.persistence.*
import java.time.LocalDateTime
@Entity
@Table(name = "officeroom")
data class Officeroom(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "idx")
val idx: Long? = null,
@Column(name = "userid", nullable = false, length = 100)
val userid: String, // 외래 키로 설정될 수 있음
@Column(name = "mongo_chatlog", length = 100)
val mongoChatlog: String? = null,
@Column(name = "created_at", updatable = false)
val createdAt: LocalDateTime = LocalDateTime.now(),
@Column(name = "updated_at")
var updatedAt: LocalDateTime = LocalDateTime.now()
) {
@PreUpdate
fun onUpdate() {
updatedAt = LocalDateTime.now()
}
}
package com.TreeNut.ChatBot_Backend.repository
import com.TreeNut.ChatBot_Backend.model.Chatroom
import com.TreeNut.ChatBot_Backend.model.Officeroom
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
@Repository
interface ChatroomRepository : JpaRepository<Chatroom, Long> {
fun findByUserid(userId: String): Chatroom? // 수정된 메소드 이름
}
package com.TreeNut.ChatBot_Backend.repository
import com.TreeNut.ChatBot_Backend.model.Officeroom
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
@Repository
interface OfficeroomRepository : JpaRepository<Officeroom, Long> {
fun findByUserid(userId: String): Officeroom?
}
package com.TreeNut.ChatBot_Backend.service
import com.TreeNut.ChatBot_Backend.model.Chatroom
import com.TreeNut.ChatBot_Backend.model.Officeroom
import com.TreeNut.ChatBot_Backend.repository.ChatroomRepository
import com.TreeNut.ChatBot_Backend.repository.OfficeroomRepository
import org.springframework.http.MediaType
import org.springframework.stereotype.Service
import org.springframework.web.reactive.function.client.WebClient
import reactor.core.publisher.Mono
@Service
class ChatroomService(
private val chatroomRepository: ChatroomRepository,
private val officeroomRepository: OfficeroomRepository, // OfficeroomRepository 추가
private val webClient: WebClient.Builder
) {
fun createOfficeroom(userid: String): Mono<Map<*, *>> {
val requestBody = mapOf(
"user_id" to userid
)
return webClient.build()
.post()
.uri("/mongo/office/create")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(requestBody)
.retrieve()
.bodyToMono(Map::class.java)
}
fun saveOfficeroom(userid: String, documentId: String): Officeroom {
val newOfficeroom = Officeroom(
userid = userid,
mongoChatlog = documentId
)
return officeroomRepository.save(newOfficeroom) // OfficeroomRepository를 사용하여 저장
}
fun saveChatroom(userid: String, charactersIdx: Int = 0, documentId: String): Chatroom {
val newChatroom = Chatroom(
userid = userid,
charactersIdx = charactersIdx,
mongoChatlog = documentId
)
return chatroomRepository.save(newChatroom) // ChatroomRepository를 사용하여 저장
}
}
chatbot(캐릭터 채팅) & office(GPT)의 채팅 생성 방식 설명
chatbot의 경우
nono.me/chatroom/c/캐릭터 고유번호
로 페이지 시작nono.me/chatroom/c/캐릭터 고유번호
페이지에서 채팅을 user가 입력 시 user의 id를 받아서 mongoDB에 컬랙션 생성(chatbotlog{user_id}), 생성된 컬랙션에 id 컬럼에 채팅방 고유번호가 생성됨.nono.me/chatroom/c/캐릭터 고유번호/u/채팅방 고유번호
로 페이지 생성.office의 경우
nono.me/chatroom/o/gpt
로 페이지 시작nono.me/chatroom/o/gpt
페이지에서 채팅을 user가 입력 시 user의 id를 받아서 mongoDB에 컬랙션 생성(officelog{user_id}), 생성된 컬랙션에 id 컬럼에 채팅방 고유번호가 생성됨.nono.me/chatroom/o/gpt/u/채팅방 고유번호
로 페이지 생성.