Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 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
9 changes: 9 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'

// CHAT
// web socket
implementation 'org.springframework.boot:spring-boot-starter-websocket'
implementation 'org.webjars:webjars-locator-core'
implementation 'org.webjars:sockjs-client:1.5.1'
implementation 'org.webjars:stomp-websocket:2.3.4'
// kafka
implementation 'org.springframework.kafka:spring-kafka'

compileOnly 'org.projectlombok:lombok'

developmentOnly 'org.springframework.boot:spring-boot-devtools'
Expand Down
24 changes: 23 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,26 @@ services:
DB_NAME: ${DB_NAME}
DB_PASSWORD: ${DB_PASSWORD}
SLACK_WEBHOOK_URL: ${SLACK_WEBHOOK_URL}
restart: always # 컨테이너가 중단되었을 때 재시작 설정
restart: always # 컨테이너가 중단되었을 때 재시작 설정

zookeeper:
image: confluentinc/cp-zookeeper:latest
container_name: zookeeper
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ports:
- "2181:2181"

kafka:
image: confluentinc/cp-kafka:latest
container_name: kafka
ports:
- "9092:9092"
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
depends_on:
- zookeeper
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ public CategoryResponse delete(long id) {
// 2. 하위 카테고리 존재 여부 확인
if (categoryJpaRepository.existsByParentId(id)) {
log.debug("하위 카테고리 존재하여 삭제가 불가능 합니다. id : {}", id);
throw new PlayHiveException(RESOURCE_CONFLICT);
// throw new PlayHiveException(RESOURCE_CONFLICT);
}

// 3. DTO 생성 (삭제된 정보 반환)
Expand Down
39 changes: 39 additions & 0 deletions src/main/java/org/myteam/server/chat/config/DataInit.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.myteam.server.chat.config;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.myteam.server.chat.domain.ChatRoom;
import org.myteam.server.chat.repository.ChatRoomRepository;
import org.myteam.server.chat.domain.FilterData;
import org.myteam.server.chat.repository.FilterDataRepository;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@RequiredArgsConstructor
public class DataInit implements CommandLineRunner {

private final ChatRoomRepository repository;
private final FilterDataRepository filterDataRepository;

@Override
public void run(String... args) throws Exception {

ChatRoom chatRoom1 = new ChatRoom("맨유 VS 토트넘");
ChatRoom chatRoom2 = new ChatRoom("아스날 VS 맨시티");
ChatRoom chatRoom3 = new ChatRoom("첼시 VS 리버풀");

repository.save(chatRoom1);
repository.save(chatRoom2);
repository.save(chatRoom3);

FilterData filterData1 = new FilterData("맹구");
FilterData filterData2 = new FilterData("닭트넘");

filterDataRepository.save(filterData1);
filterDataRepository.save(filterData2);

log.info("데이터 초기화 완료");
}
}
98 changes: 98 additions & 0 deletions src/main/java/org/myteam/server/chat/config/KafkaConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package org.myteam.server.chat.config;


import org.apache.kafka.clients.admin.AdminClientConfig;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;
import org.myteam.server.chat.domain.Chat;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.annotation.EnableKafka;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.core.*;
import org.springframework.kafka.support.serializer.JsonDeserializer;
import org.springframework.kafka.support.serializer.JsonSerializer;

import java.util.HashMap;
import java.util.Map;

@EnableKafka
@Configuration
public class KafkaConfig {

private static final String BOOTSTRAP_SERVERS = "kafka:9092";
private static final String DEFAULT_GROUP_ID = "chat-group";

@Bean
public KafkaAdmin kafkaAdmin() {
Map<String, Object> configs = new HashMap<>();
configs.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, BOOTSTRAP_SERVERS);
return new KafkaAdmin(configs);
}

/**
* Kafka ProducerFactory를 생성하는 Bean 메서드
*/
@Bean
public ProducerFactory<String, Chat> producerFactory() {
return new DefaultKafkaProducerFactory<>(producerConfigurations());
}

/**
* Kafka Producer 구성을 위한 설정값들을 포함한 맵을 반환하는 메서드
*/
@Bean
public Map<String, Object> producerConfigurations() {
Map<String, Object> producerConfigurations = new HashMap<>();

producerConfigurations.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, BOOTSTRAP_SERVERS);
producerConfigurations.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
producerConfigurations.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
producerConfigurations.put(JsonSerializer.ADD_TYPE_INFO_HEADERS, false); // JSON 타입 헤더 제거 (선택사항)

return producerConfigurations;
}

/**
* KafkaTemplate을 생성하는 Bean 메서드
*/
@Bean
public KafkaTemplate<String, Chat> kafkaTemplate() {
return new KafkaTemplate<>(producerFactory());
}

/**
* Kafka ConsumerFactory를 생성하는 Bean 메서드
*/
@Bean
public ConsumerFactory<String, Chat> consumerFactory() {
JsonDeserializer<Chat> deserializer = new JsonDeserializer<>(Chat.class);
deserializer.addTrustedPackages("*"); // 모든 패키지 신뢰 (필요 시 제한적으로 변경)

Map<String, Object> consumerConfigurations = Map.of(
ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, BOOTSTRAP_SERVERS,
ConsumerConfig.GROUP_ID_CONFIG, DEFAULT_GROUP_ID,
ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class,
ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, deserializer,
ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest"
);

return new DefaultKafkaConsumerFactory<>(consumerConfigurations, new StringDeserializer(), deserializer);
}

/**
* KafkaListener 컨테이너 팩토리를 생성하는 Bean 메서드
*/
@Bean
public ConcurrentKafkaListenerContainerFactory<String, Chat> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, Chat> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());

factory.setConcurrency(3); // 병렬 처리 설정 (기본값 1)
factory.getContainerProperties().setPollTimeout(3000L); // 폴링 시간 설정 (선택사항)

return factory;
}
}
39 changes: 39 additions & 0 deletions src/main/java/org/myteam/server/chat/config/WebSocketConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.myteam.server.chat.config;


import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketTransportRegistration;

@EnableWebSocketMessageBroker
@Configuration
@RequiredArgsConstructor
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/play-hive");
registry.enableSimpleBroker("/room");
}

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws-stomp")
.setAllowedOrigins("http://localhost:3000")
.withSockJS();
registry.addEndpoint("/ws-stomp")
.setAllowedOrigins("http://localhost:3000");
}

// STOMP에서 64KB 이상의 데이터 전송을 못하는 문제 해결
@Override
public void configureWebSocketTransport(WebSocketTransportRegistration registry) {
registry.setMessageSizeLimit(160 * 64 * 1024);
registry.setSendTimeLimit(100 * 10000);
registry.setSendBufferSizeLimit(3 * 512 * 1024);
}
}
61 changes: 61 additions & 0 deletions src/main/java/org/myteam/server/chat/controller/BanController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package org.myteam.server.chat.controller;

import lombok.RequiredArgsConstructor;
import org.myteam.server.chat.dto.request.BanRequest;
import org.myteam.server.chat.dto.response.BanResponse;
import org.myteam.server.chat.service.BanService;
import org.myteam.server.global.web.response.ResponseDto;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import static org.myteam.server.global.web.response.ResponseStatus.SUCCESS;

/**
* Ban 도메인에 대한 HTTP 요청 처리
*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/bans")
public class BanController {

private final BanService banService;

/**
* 유저 밴하기
*/
@PostMapping
public ResponseEntity<ResponseDto<BanResponse>> banUser(@RequestBody BanRequest request) {
BanResponse response = banService.banUser(request);
return ResponseEntity.ok(new ResponseDto(
SUCCESS.name(),
"Ban Success",
response
));
}

/**
* 유저 밴 해제
*/
@DeleteMapping("/{username}")
public ResponseEntity<ResponseDto<String>> unbanUser(@PathVariable String username) {
String deleteName = banService.unbanUser(username);
return ResponseEntity.ok(new ResponseDto(
SUCCESS.name(),
"Delete Ban Successfully",
deleteName
));
}

/**
* 특정 유저 밴 정보 조회
*/
@GetMapping("/{username}")
public ResponseEntity<ResponseDto<BanResponse>> getBanByUsername(@PathVariable String username) {
BanResponse response = banService.findBanByUsername(username);
return ResponseEntity.ok(new ResponseDto(
SUCCESS.name(),
"Find Ban Reason Successfully",
response
));
}
}
Loading