Skip to content

Commit

Permalink
Merge pull request #34 from k-twitter/feature/#8-redis-with-chat-service
Browse files Browse the repository at this point in the history
[채팅] 레디스 연동 및 채팅 기능 개발
  • Loading branch information
hyun98 authored Jan 15, 2024
2 parents a2fbf96 + 3b68697 commit f7bfd78
Show file tree
Hide file tree
Showing 67 changed files with 499 additions and 1 deletion.
13 changes: 13 additions & 0 deletions front-test-bed/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Websocket Simple Test

1. ## Click `Edit Configurations...`
![Edit-Configurations](editConfig.png)

2. ## Input `local` in 'Active profiles'
![Alt text](setProfile.png)

> local 프로필로 실행하면 h2 db를 사용해 MySQL의 실행 없이 간단한 테스트가 가능합니다.
3. ## Run!

4. ## Open `front-test-bed/index.html`
69 changes: 69 additions & 0 deletions front-test-bed/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
const stompClient = new StompJs.Client({
brokerURL: 'ws://localhost:8080/ws-stomp'
});

// var channelId = ""

stompClient.onConnect = (frame) => {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/sub/chat/channel/' + $("#channelId").val(), (message) => {
console.log(JSON.parse(message.body))
showMessage(JSON.parse(message.body).content);
});
};

stompClient.onWebSocketError = (error) => {
console.error('Error with websocket', error);
};

stompClient.onStompError = (frame) => {
console.error('Broker reported error: ' + frame.headers['message']);
console.error('Additional details: ' + frame.body);
};

function setConnected(connected) {
$("#connect").prop("disabled", connected);
$("#disconnect").prop("disabled", !connected);
if (connected) {
$("#conversation").show();
}
else {
$("#conversation").hide();
}
$("#greetings").html("");
}

function connect() {
stompClient.activate();
}

function disconnect() {
stompClient.deactivate();
setConnected(false);
console.log("Disconnected");
}

function sendName() {
stompClient.publish({
destination: "/pub/chat/message",
body: JSON.stringify(
{
"channelId": $("#channelId").val(),
"sender" : $("#name").val(),
"content" : $("#content").val()
}
)
});
}

function showMessage(message) {
$("#greetings").append("<tr><td>" + message + "</td></tr>");
}

$(function () {
$("form").on('submit', (e) => e.preventDefault());
$( "#connect" ).click(() => connect());
$( "#disconnect" ).click(() => disconnect());
$( "#send" ).click(() => sendName());
});
Binary file added front-test-bed/editConfig.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
68 changes: 68 additions & 0 deletions front-test-bed/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<!DOCTYPE html>
<html>
<head>
<title>Hello WebSocket</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link href="/main.css" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@stomp/[email protected]/bundles/stomp.umd.min.js"></script>
<script src="app.js"></script>
</head>
<body>
<noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websocket relies on Javascript being
enabled. Please enable
Javascript and reload this page!</h2></noscript>
<div id="main-content" class="container">
<div class="row">
<div class="col-md-6">
<form class="form-inline">
<div class="form-group">
<label for="connect">WebSocket connection:</label>
<button id="connect" class="btn btn-default" type="submit">Connect</button>
<button id="disconnect" class="btn btn-default" type="submit" disabled="disabled">Disconnect
</button>
</div>
</form>
</div>
<div class="col-md-6">
<form class="form-inline">
<div class="form-group">
<label for="name">What is your name?</label>
<input type="text" id="name" class="form-control" placeholder="Your name here...">
</div>
</form>
</div>
<div class="col-md-6">
<form class="form-inline">
<div class="form-group">
<label for="name">Content</label>
<input type="text" id="content" class="form-control" placeholder="Type your message...">
</div>
<button id="send" class="btn btn-default" type="submit">Send</button>
</form>
</div>
<div class="col-md-6">
<form class="form-inline">
<div class="form-group">
<label for="name">Set Channel Id</label>
<input type="text" id="channelId" class="form-control" placeholder="Set temp ChannelId">
</div>
</form>
</div>
</div>
<div class="row">
<div class="col-md-12">
<table id="conversation" class="table table-striped">
<thead>
<tr>
<th>Greetings</th>
</tr>
</thead>
<tbody id="greetings">
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>
Binary file added front-test-bed/setProfile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions sns_service/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,24 @@ repositories {

dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-websocket")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation ("org.springframework.boot:spring-boot-starter-data-redis")
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("io.micrometer:micrometer-registry-prometheus")
implementation ("net.logstash.logback:logstash-logback-encoder:7.3")
implementation ("io.github.microutils:kotlin-logging:3.0.5")
implementation("it.ozimov:embedded-redis:0.7.2")
implementation("org.springframework.kafka:spring-kafka")


compileOnly("org.projectlombok:lombok")
annotationProcessor("org.projectlombok:lombok")
testImplementation("org.springframework.boot:spring-boot-starter-test")
runtimeOnly("com.mysql:mysql-connector-j")
runtimeOnly("com.h2database:h2")
}

tasks.withType<KotlinCompile> {
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package joryu.sns_service.channel.entity

import jakarta.persistence.*
import joryu.sns_service.channel.enums.ChannelType
import java.io.Serializable
import java.util.*


@Table(name = "channel")
@Entity
class Channel(
channelName: String = "",

@Column(name = "channel_type")
val channelType: ChannelType = ChannelType.PERSONAL
) : Serializable {
@Id
@Column(name = "channel_id")
val id: String = UUID.randomUUID().toString()

@OneToMany(mappedBy = "channel")
val channelProfiles: MutableList<ChannelProfile> = mutableListOf()

@Column(name = "channel_name")
var channelName: String = channelName
private set

fun updateChannelName(newName: String) {
channelName = newName
}

fun addProfileToChannel(profile: ChannelProfile) {
channelProfiles.add(profile)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package joryu.sns_service.channel.entity

import jakarta.persistence.*
import joryu.sns_service.profile.entity.Profile
import java.io.Serializable

@Table(name = "channel_profile")
@Entity
class ChannelProfile(
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "channel_id")
val channel: Channel = Channel(),

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "profile_id")
val profile: Profile = Profile()
): Serializable {
@Id
@Column(name = "channel_profile_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package joryu.sns_service.channel.enums

enum class ChannelType {
PERSONAL, GROUP
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package joryu.sns_service.channel.repository

import joryu.sns_service.channel.entity.ChannelProfile
import org.springframework.data.jpa.repository.JpaRepository

interface ChannelProfileRepository: JpaRepository<ChannelProfile, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package joryu.sns_service.channel.repository

import joryu.sns_service.channel.entity.Channel
import org.springframework.data.jpa.repository.JpaRepository

interface ChannelRepository: JpaRepository<Channel, String> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package joryu.sns_service.chat.config

import jakarta.annotation.PostConstruct
import jakarta.annotation.PreDestroy
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Profile
import redis.embedded.RedisServer


@Profile("local") // profile이 local일때만 활성화
@Configuration
class EmbeddedRedisConfig {

@Value("\${spring.data.redis.port}")
private val redisPort = 6379

private var redisServer: RedisServer? = null

@PostConstruct
fun redisServer() {
redisServer = RedisServer(redisPort)
redisServer?.start()
}

@PreDestroy
fun stopRedis() {
redisServer?.stop()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package joryu.sns_service.chat.config

import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Primary
import org.springframework.data.redis.connection.MessageListener
import org.springframework.data.redis.connection.RedisConnectionFactory
import org.springframework.data.redis.connection.RedisStandaloneConfiguration
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.data.redis.listener.ChannelTopic
import org.springframework.data.redis.listener.RedisMessageListenerContainer
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer
import org.springframework.data.redis.serializer.StringRedisSerializer


/**
* 채팅에 사용되는 redis 설정 관리
*/
@Configuration
@EnableRedisRepositories
class RedisConfig(
@Value("\${spring.data.redis.port:6379}")
private val port: Int,

@Value("\${spring.data.redis.host:localhost}")
private val host: String
) {

/**
* ChannelTopic 에 발행된 메시지를 처리하는 Listner 들을 설정한다.
*/
@Bean
@Primary
fun redisMessageListenerContainer(
redisConnectionFactory: RedisConnectionFactory,
chatMessageListenerAdapter: MessageListenerAdapter,
@Qualifier("chatChannelTopic") chatChannelTopic: ChannelTopic
): RedisMessageListenerContainer {
val container = RedisMessageListenerContainer()
container.setConnectionFactory(redisConnectionFactory)
container.addMessageListener(chatMessageListenerAdapter, chatChannelTopic)
return container
}

@Bean
fun redisConnectionFactory(): RedisConnectionFactory {
val redisStandaloneConfiguration = RedisStandaloneConfiguration()
redisStandaloneConfiguration.hostName = host
redisStandaloneConfiguration.port = port
return LettuceConnectionFactory(redisStandaloneConfiguration)
}

/**
* RedisMessageListenerContainer 로부터 메시지를 전달받는다.
* 메시지 처리 비즈니스 로직을 담은 Subscriber Bean 을 추가해준다.
*/
@Bean
fun chatMessageListenerAdapter(listener: MessageListener): MessageListenerAdapter {
return MessageListenerAdapter(listener, "onMessage")
}

/**
* 채팅 채널 토픽을 반환한다.
*/
@Bean(value = ["chatChannelTopic"])
fun chatChannelTopic(): ChannelTopic {
return ChannelTopic("chat")
}

/**
* Redis 데이터에 접근하는 redisTemplate 를 반환한다.
*/
@Bean
fun redisTemplate(connectionFactory: RedisConnectionFactory): RedisTemplate<String, Any> {
val redisTemplate = RedisTemplate<String, Any>()
redisTemplate.connectionFactory = connectionFactory
redisTemplate.keySerializer = StringRedisSerializer()
redisTemplate.valueSerializer = Jackson2JsonRedisSerializer(String::class.java)
return redisTemplate
}
}
Loading

0 comments on commit f7bfd78

Please sign in to comment.