Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,20 @@ dependencies {

implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6:3.1.1.RELEASE'

// redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

// web socket
implementation 'org.springframework.boot:spring-boot-starter-websocket'

//mongo
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'

// 직렬화
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
implementation "com.fasterxml.jackson.module:jackson-module-kotlin"


}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package busanVibe.busan.domain.chat.controller

import busanVibe.busan.domain.chat.dto.websocket.ChatMessageResponseDTO
import busanVibe.busan.domain.chat.dto.websocket.ChatMessageSendDTO
import busanVibe.busan.domain.chat.service.ChatMongoService
import busanVibe.busan.global.apiPayload.exception.ApiResponse
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@Tag(name = "채팅 관련 API")
@RestController
@RequestMapping("/chat")
class ChatRestController(
private val chatMongoService: ChatMongoService,
) {

val log: Logger = LoggerFactory.getLogger(ChatRestController::class.java)

@PostMapping("/send")
fun sendMessage(@RequestBody chatMessage: ChatMessageSendDTO) {
log.info("POST /chat/send - 메시지 수신: $chatMessage")
chatMongoService.saveAndPublish(chatMessage)
}

@GetMapping("/history")
@Operation(summary = "채팅 조회 API", description = "채팅 목록을 조회합니다.")
fun getHistory(): ApiResponse<ChatMessageResponseDTO.ListDto> {
val chatList = chatMongoService.getChatHistory()
return ApiResponse.onSuccess(chatList)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package busanVibe.busan.domain.chat.controller

import busanVibe.busan.domain.chat.dto.websocket.ChatMessageSendDTO
import busanVibe.busan.domain.chat.service.ChatMongoService
import busanVibe.busan.domain.user.data.User
import org.springframework.messaging.handler.annotation.Header
import org.springframework.messaging.handler.annotation.MessageMapping
import org.springframework.messaging.handler.annotation.Payload
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.Authentication
import org.springframework.stereotype.Controller
import java.security.Principal

@Controller
class ChatWebSocketController(
private val chatMongoService: ChatMongoService,
) {

/**
* websocket '/pub/chat/message' 로 들어오는 메시징 처리
*/
// @MessageMapping("/chat/message")
// fun handleMessage(@Payload chatMessage: ChatMessageSendDTO, authentication: Authentication) {
// val user = authentication.principal as User
// chatMongoService.saveAndPublish(chatMessage, user)
// }

}
21 changes: 21 additions & 0 deletions src/main/kotlin/busanVibe/busan/domain/chat/domain/ChatMessage.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package busanVibe.busan.domain.chat.domain

import busanVibe.busan.domain.chat.enums.MessageType
import org.springframework.data.annotation.Id
import org.springframework.data.mongodb.core.mapping.Document
import java.time.LocalDateTime

@Document(collection = "chat_messages")
data class ChatMessage(
@Id
val id: String? = null,

val type: MessageType,

val userId: Long?,

val message: String,

val time: LocalDateTime,

)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package busanVibe.busan.domain.chat.dto.openai

class ChatRequest(
val model: String,
val messages: List<ChatMessageDTO>,
val n: Int,
val max_tokens: Int
) {
constructor(model: String, prompt: String) : this(
model = model,
messages = listOf(ChatMessageDTO("user", prompt)),
n = 1,
max_tokens = 100
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package busanVibe.busan.domain.chat.dto.openai

data class ChatMessageDTO(
val role: String,
val content: String
)

data class ChatRequestDto(
val model: String,
val messages: List<ChatMessageDTO>,
val n: Int = 1
) {
constructor(model: String, prompt: String): this(
model = model,
messages = listOf(ChatMessageDTO("user", prompt)),
n = 1
)
}

data class ChatResponse(
val choices: List<Choice>
) {
data class Choice(
val index: Int,
val message: ChatMessageDTO
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package busanVibe.busan.domain.chat.dto.websocket

import busanVibe.busan.domain.chat.enums.MessageType
import com.fasterxml.jackson.databind.PropertyNamingStrategies
import com.fasterxml.jackson.databind.annotation.JsonNaming
import java.time.LocalDateTime

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class)
data class ChatMessageReceiveDTO(
var userId: Long? = null,
var name: String,
var imageUrl: String? = null,
var message: String,
var time: LocalDateTime,
var type: MessageType? = null,
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package busanVibe.busan.domain.chat.dto.websocket

import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.PropertyNamingStrategies
import com.fasterxml.jackson.databind.annotation.JsonNaming
import java.time.LocalDateTime

class ChatMessageResponseDTO {

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class)
data class ListDto(
val chatList: List<ChatInfoDto>
)

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class)
data class ChatInfoDto(
val userName: String,
val userImage: String?,
val content: String,
val dateTime: LocalDateTime?,
@get:JsonProperty("is_my")
val isMy: Boolean
)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package busanVibe.busan.domain.chat.dto.websocket

import busanVibe.busan.domain.chat.enums.MessageType
import com.fasterxml.jackson.databind.PropertyNamingStrategies
import com.fasterxml.jackson.databind.annotation.JsonNaming
import java.time.LocalDateTime

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class)
data class ChatMessageSendDTO(
// var userId: Long? = null,
var type: MessageType? = null, // 메시지 타입
var message: String? = null, // 메시지
var time: LocalDateTime? = null, // 전송 시간
){

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package busanVibe.busan.domain.chat.dto.websocket

import com.fasterxml.jackson.databind.PropertyNamingStrategies
import com.fasterxml.jackson.databind.annotation.JsonNaming

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class)
class UserInfoResponse(
val userId: Long
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package busanVibe.busan.domain.chat.dto.websocket

import com.fasterxml.jackson.databind.PropertyNamingStrategies
import com.fasterxml.jackson.databind.annotation.JsonNaming

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class)
data class WebSocketErrorDTO(
val errorCode: String,
val errorMessage: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package busanVibe.busan.domain.chat.enums

enum class MessageType {
CHAT, BOT
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package busanVibe.busan.domain.chat.repository

import busanVibe.busan.domain.chat.domain.ChatMessage
import org.springframework.data.mongodb.repository.MongoRepository

interface ChatMongoRepository: MongoRepository<ChatMessage, String> {
fun findAllByOrderByTime(): List<ChatMessage>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package busanVibe.busan.domain.chat.service

import busanVibe.busan.domain.chat.dto.openai.ChatRequest
import busanVibe.busan.domain.chat.dto.openai.ChatResponse
import busanVibe.busan.global.config.openai.ChatGPTConfig
import org.springframework.beans.factory.annotation.Value
import org.springframework.http.HttpEntity
import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate

@Service
class ChatGPTService(
private val chatGPTConfig: ChatGPTConfig,

@Value("\${openai.model}")
private val model: String,

@Value("\${openai.secret-key}")
private val url: String

) {

fun prompt(prompt: String): String {

// 토큰 정보가 포함된 header 가져오기
val headers = chatGPTConfig.httpHeaders()

// create request
val chatRequest = ChatRequest(model, prompt)

// 통신을 위한 RestTemplate 구성
val requestEntity = HttpEntity(chatRequest, headers)

val restTemplate = RestTemplate()
val response: ChatResponse? = restTemplate.postForObject(url, requestEntity, ChatResponse::class.java)

if (response?.choices.isNullOrEmpty()) {
throw RuntimeException("No choices returned from ChatGPT.")
}

return response!!.choices[0].message.content
}
}
Loading