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
11 changes: 10 additions & 1 deletion src/main/kotlin/busanVibe/busan/BusanApplication.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
package busanVibe.busan

import org.springframework.boot.CommandLineRunner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
import org.springframework.data.jpa.repository.config.EnableJpaAuditing
import java.util.TimeZone

@SpringBootApplication
@EnableJpaAuditing
class BusanApplication
class BusanApplication{
@Bean
fun init() = CommandLineRunner {
// 서버 타임존 설정
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul"))
}
}

fun main(args: Array<String>) {
runApplication<BusanApplication>(*args)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package busanVibe.busan.domain.chat.controller

import busanVibe.busan.domain.chat.dto.websocket.ChatMessageReceiveDTO
import busanVibe.busan.domain.chat.dto.websocket.ChatMessageResponseDTO
import busanVibe.busan.domain.chat.dto.websocket.ChatMessageSendDTO
import busanVibe.busan.domain.chat.service.ChatMongoService
Expand All @@ -24,14 +25,18 @@ class ChatRestController(
@PostMapping("/send")
@Operation(summary = "채팅 전송 API",
description = """
채팅 전송 API 입니다. 해당 API 호출 시, 메시지가 전송되고, 채팅 웹소켓에 연결된 유저들에게 메시지가 전송됩니다.
메시지의 길이는 <b>최대 200글자</b>입니다.
type 유형 : ['CHAT', 'BOT']
- CHAT (구현O): 일반 채팅
- BOT (구현X): 챗봇 기능입니다. 본인에게만 웹소켓 메시지가 전송되고, 채팅방을 나갈 시 다시 볼 수 없습니다.
채팅 전송 API 입니다. 해당 API 호출 시 메시지가 전송되고, 채팅 웹소켓에 연결된 유저들에게 메시지가 전송됩니다.
메시지의 길이는 최대 200글자입니다.

type : ['CHAT', 'BOT']

메시지가 '/' 로 시작하면 챗봇 답변을 응답합니다.
일반 채팅은 웹소켓으로 메시지를 전송하고, 챗봇은 웹소켓 메시지를 전송하지 않습니다.
따라서 일반 채팅은 웹소켓으로 받은 메시지로 활용하고, 챗봇에게 받은 답변은 해당 API의 응답 결과를 활용해주세요.
""")
fun sendMessage(@Valid @RequestBody chatMessage: ChatMessageSendDTO) {
chatMongoService.saveAndPublish(chatMessage)
fun sendMessage(@Valid @RequestBody chatMessage: ChatMessageSendDTO): ApiResponse<ChatMessageReceiveDTO> {
val receiveDTO = chatMongoService.saveAndPublish(chatMessage)
return ApiResponse.onSuccess(receiveDTO)
}

@GetMapping("/history")
Expand Down

This file was deleted.

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

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

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class)
data class WebSearchRequest(
val model: String,
val messages: List<Message>,
// val tools: List<Tool>? = null,
)

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class)
data class Message(
val role: String,
val content: String
)

data class Tool(
val type: String
)

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class)
data class ApproximateLocation(
val country: String,
val city: String,
val region: String
)

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class)
data class WebSearchResponse(
val id: String,
val choices: List<Choice>
)

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class)
data class Choice(
val index: Int,
val message: Message
)
Original file line number Diff line number Diff line change
@@ -1,19 +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 jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.Size

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class)
data class ChatMessageSendDTO(
// var userId: Long? = null,
var type: MessageType? = null, // 메시지 타입

@field:NotBlank(message = "빈 문자열을 전송할 수 없습니다.")
@field:Size(min = 1, max = 200, message = "메세지의 길이는 1이상 200이하입니다.")
var message: String? = null, // 메시지
// var time: LocalDateTime? = null, // 전송 시간
var message: String, // 메시지

){

}
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package busanVibe.busan.domain.chat.repository

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

interface ChatMongoRepository: MongoRepository<ChatMessage, String> {
fun findAllByOrderByTimeDesc(pageable: Pageable): List<ChatMessage>
fun findByIdLessThanOrderByTimeDesc(id: String, pageable: Pageable): List<ChatMessage>
fun findAllByTypeOrderByTimeDesc(type: MessageType, pageable: Pageable): List<ChatMessage>
fun findByIdLessThanAndTypeOrderByTimeDesc(id: String, type: MessageType, pageable: Pageable): List<ChatMessage>

}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -24,37 +24,86 @@ class ChatMongoService(
private val redisPublisher: RedisPublisher,
private val topic: ChannelTopic,
private val userRepository: UserRepository,
private val openAiService: OpenAiService
) {

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

fun saveAndPublish(chatMessage: ChatMessageSendDTO) {

// 현재 유저 조회
/**
*
* 일반 채팅의 경우
* 메시지 전송 - 메시지를 DB에 저장 - dto 생성 - dto를 웹소켓 전송 - 클라이언트 갱신 - dto를 반환(응답)
*
* 챗봇의 경우
* 메시지 전송 - openai 요청 후 메시지 받음 - dto 생성 - dto를 반환(응답)
*
*/
fun saveAndPublish(chatMessage: ChatMessageSendDTO): ChatMessageReceiveDTO {

// 필요한 정보 미리 정의
val currentUser = AuthService().getCurrentUser()
val now = LocalDateTime.now()
val message = chatMessage.message.trim()

// 받은 메세지로 타입 구분
// '/'로 시작하면 챗봇(BOT)
val type: MessageType = if(message[0] == '/'){
MessageType.BOT
}else{
MessageType.CHAT
}

// 채팅 로그 생성
log.info("[$type] userId: ${currentUser.id}, /POST/chat/send, 메시지 전송: $chatMessage")

// 채팅 객체 생성
val document = ChatMessage(
type = chatMessage.type?: MessageType.CHAT,
// ChatMessage 객체 생성
val chat = ChatMessage(
type = type,
userId = currentUser.id,
message = chatMessage.message?:"",
time = LocalDateTime.now(),
message = message,
time = now
)

// 채팅 저장
chatMongoRepository.save(document)
// 일반 채팅일 경우 채팅 기록 저장
chatMongoRepository.save(chat)

val receiveDto = ChatMessageReceiveDTO(
name = currentUser.nickname,
imageUrl = currentUser.profileImageUrl,
message = document.message,
time = document.time,
type = document.type,
userId = currentUser.id
)
// 유저들에게 웹소켓으로 전달할 메시지의 DTO 생성
val receiveDto = buildReceiveDto(type, currentUser, chat, now)

// 일반 채팅일 경우에만 유저들에게 웹소켓 메시지 보냄
if(type == MessageType.CHAT) {
redisPublisher.publish(topic, receiveDto)
}
return receiveDto
}

log.info("[CHAT] /POST/chat/send, 메시지 전송: $chatMessage")
redisPublisher.publish(topic, receiveDto)
// ChatMessageReceiveDTO 생성
private fun buildReceiveDto(
type: MessageType,
currentUser: User,
chat: ChatMessage,
timestamp: LocalDateTime
): ChatMessageReceiveDTO {
return if (type == MessageType.CHAT) {
ChatMessageReceiveDTO(
name = currentUser.nickname,
imageUrl = currentUser.profileImageUrl,
message = chat.message,
time = timestamp,
type = type,
userId = currentUser.id
)
} else {
val message = openAiService.chatToOpenAI(chat.message)
ChatMessageReceiveDTO(
name = "챗봇",
imageUrl = null,
message = message,
time = timestamp,
type = type,
userId = -1
)
}
}

fun getChatHistory(cursorId: String?, pageSize: Int = 15): ChatMessageResponseDTO.ListDto {
Expand All @@ -70,9 +119,9 @@ class ChatMongoService(

// 조회 -> List<ChatMessage> 변수 선언 및 초기화
val chatHistory: List<ChatMessage> = if (cursorId != null) { // 처음이면 cursorId 없이 조회
chatMongoRepository.findByIdLessThanOrderByTimeDesc(cursorId, Pageable.ofSize(pageSize))
chatMongoRepository.findByIdLessThanAndTypeOrderByTimeDesc(cursorId, MessageType.CHAT, Pageable.ofSize(pageSize))
} else { // 처음 아니면 cursorId로 조회
chatMongoRepository.findAllByOrderByTimeDesc(Pageable.ofSize(pageSize))
chatMongoRepository.findAllByTypeOrderByTimeDesc(MessageType.CHAT, Pageable.ofSize(pageSize))
}

// userId List 저장. 바로 뒤에 userId로 User 정보들을 먼저 찾고, 그 뒤에 DTO 변환
Expand Down
Loading