Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion .github/workflows/cd-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ jobs:
sudo docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGE }}

echo "🌱Running new container"
sudo docker run -d -p 8000:9000 --name kuchat ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGE }}
sudo docker run -d -p 9000:9000 --name kuchat ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGE }}

echo "🚮Cleaning up old images"
sudo docker image prune -f
11 changes: 5 additions & 6 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,16 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'

// oauth2
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-security'
// auth
implementation 'javax.xml.bind:jaxb-api:2.3.1' // jwt 생성에 필요한 DatatypeConverter
testImplementation 'org.springframework.security:spring-security-test'
implementation 'io.jsonwebtoken:jjwt:0.9.1'

// chat
implementation 'org.springframework.boot:spring-boot-starter-websocket'
implementation 'org.webjars:sockjs-client:1.1.2'
implementation 'org.webjars:stomp-websocket:2.3.3'
// implementation 'org.webjars:sockjs-client:1.5.1'
implementation 'org.webjars:stomp-websocket:2.3.4'
implementation 'org.springframework:spring-messaging:6.1.4'
implementation 'org.springframework.security:spring-security-messaging:6.2.3'

}

Expand Down
58 changes: 0 additions & 58 deletions src/main/java/kuchat/server/common/config/SecurityConfig.java

This file was deleted.

34 changes: 6 additions & 28 deletions src/main/java/kuchat/server/common/config/WebMvcConfig.java
Original file line number Diff line number Diff line change
@@ -1,45 +1,23 @@
package kuchat.server.common.config;

import kuchat.server.domain.auth.JwtTokenInterceptor;
import kuchat.server.domain.auth.argumentResolver.AuthArgumentResolver;
import kuchat.server.domain.auth.argumentResolver.GuestArgumentResolver;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.servlet.view.MustacheViewResolver;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@Slf4j
@RequiredArgsConstructor
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {

private final AuthArgumentResolver authArgumentResolver;
private final GuestArgumentResolver guestArgumentResolver;
private final JwtTokenInterceptor jwtTokenInterceptor;


@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
MustacheViewResolver viewResolver = new MustacheViewResolver();
viewResolver.setCharset("UTF-8");
viewResolver.setContentType("text/html; charset=utf-8");
viewResolver.setPrefix("classpath:/templates/");
viewResolver.setSuffix(".html");
registry.viewResolver(viewResolver);
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
log.info("[interceptor 등록]");
registry.addInterceptor(jwtTokenInterceptor)
.excludePathPatterns("/member/signup/**")
.addPathPatterns("/member/my-profile", "/friend/**", "/block/**", "/chatroom/**");
}

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
Expand All @@ -51,11 +29,11 @@ public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers)
public void addCorsMappings(CorsRegistry registry) {
log.info("[addCorsMappings] CorsMapping 호출");
registry.addMapping("/**")
.allowedOrigins("https://kuchat.netlify.app")
.allowedOrigins("https://kuchat.netlify.app", "http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")
.exposedHeaders("*")
.allowedHeaders("*")
.allowCredentials(true); // 쿠키 허용
.exposedHeaders("Authorization", "Set-Cookie")
.allowCredentials(true) // 쿠키 허용
.maxAge(3000); // 원하는 시간만큼 pre-flight 리퀘스트를 캐싱
}

}
34 changes: 21 additions & 13 deletions src/main/java/kuchat/server/common/config/WebSocketConfig.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package kuchat.server.common.config;


import kuchat.server.domain.message.WebSocketHandshakeInterceptor;
import kuchat.server.domain.message.WebSocketInterceptor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.ChannelRegistration;
Expand All @@ -13,16 +13,14 @@
import org.springframework.web.socket.config.annotation.WebSocketTransportRegistration;

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

private final WebSocketInterceptor webSocketInterceptor;

@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(webSocketInterceptor);
log.info("✅ WebSocket Inbound Channel Interceptor 활성화");
registration.interceptors(new WebSocketInterceptor());
}

/**
Expand All @@ -32,12 +30,17 @@ public void configureClientInboundChannel(ChannelRegistration registration) {
public void configureMessageBroker(MessageBrokerRegistry mqRegistry) {

// 클라이언트 → 서버 로 오는 요청
// @MessageMapping 메서드가 /msg 으로 시작하는 요청을 처리하도록 한다.
mqRegistry.setApplicationDestinationPrefixes("/ku");
// @MessageMapping 메서드가 /pub 으로 시작하는 요청을 처리하도록 한다.
mqRegistry.setApplicationDestinationPrefixes("/pub");
mqRegistry.enableSimpleBroker("/sub");

// 개인 메시징을 위한 설정 (사실 기본적으로 자동 적용됨)
mqRegistry.setUserDestinationPrefix("/user");

// 서버 → 클라이언트 로 가는 요청
// 브로커가 자동으로 메시지를 전송해준다.
mqRegistry.enableSimpleBroker("/queue", "/topic"); // queue : 개인톡, topic : 단체톡
// mqRegistry.enableSimpleBroker("/queue", "/topic"); // queue : 개인톡, topic : 단체톡
// mqRegistry.enableSimpleBroker("/sub");

// enableSimpleBroker : 스프링이 제공하는 인메모리 브로커를 사용하겠다는 의미
}
Expand All @@ -47,20 +50,25 @@ public void configureMessageBroker(MessageBrokerRegistry mqRegistry) {
*/
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
log.info("[registerStompEndpoints] Registering STOMP endpoint at /stomp");
log.info("[registerStompEndpoints] Registering STOMP endpoint at /ws-connect");

// 클라이언트가 websocket 연결을 맺기 위해 접속해야 하는 http 엔드포인트
registry.addEndpoint("/ws-connect")
.setAllowedOrigins("*");
// .withSockJS(); // 이 옵션 추가하면 오류가 나는 이유가 뭘까?
.setAllowedOriginPatterns("*")
.addInterceptors(new WebSocketHandshakeInterceptor());

// registry.addEndpoint("/ws-connect")
// .setAllowedOriginPatterns("*")
// .addInterceptors(new WebSocketHandshakeInterceptor())
// .withSockJS();
}

/**
* STOMP websocket 전송 속성
*/
@Override
public void configureWebSocketTransport(WebSocketTransportRegistration registry) {
registry.setMessageSizeLimit(32 * 1024); // 메시지 크기 제한 : 디폴트 64KB 에서 32KB 로 변경
registry.setMessageSizeLimit(32 * 1024); // 메시지 크기 제한 : 디폴트 64KB 에서 32KB 로 변경
registry.setTimeToFirstMessage(30 * 1000); // 클라이언트가 websocket을 연결한 후 30초 내에 STOMP 메시지를 보내야 함.
// 보내지 않으면 서버가 클라이언트 연결 종료
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package kuchat.server.common.exception;

import kuchat.server.common.response.BaseResponse;
import kuchat.server.common.response.BaseResponseStatus;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.messaging.converter.MessageConversionException;
import org.springframework.messaging.handler.annotation.MessageExceptionHandler;
import org.springframework.messaging.simp.annotation.SendToUser;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import static kuchat.server.common.response.BaseResponseStatus.MESSAGE_FORMAT_ERROR;

@Slf4j
@RestControllerAdvice
public class MessageControllerAdvice {

@MessageExceptionHandler(MessageConversionException.class)
@SendToUser("/queue/errors")
public BaseResponse handleMessageConversionException(MessageConversionException e) {
log.error("[handleMessageConversionException] 에러 메시지 = {}", e.getMessage());
return new BaseResponse(MESSAGE_FORMAT_ERROR);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,19 @@
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.View;

import static kuchat.server.common.response.BaseResponseStatus.*;

@Slf4j
@RestControllerAdvice
public class RequestControllerAdvice {
private final View error;

public RequestControllerAdvice(View error) {
this.error = error;
}

@ExceptionHandler(MissingServletRequestParameterException.class)
public ResponseEntity<BaseResponse> handleMissingParameterException(MissingServletRequestParameterException e) {
log.error("[handleMissingParameterException] 클라이언트 요청에서 request parameter가 누락된 경우");
Expand All @@ -43,7 +50,7 @@ public ResponseEntity<BaseResponse> handleMissingMultipartException(MultipartExc
}

@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseEntity<BaseResponse> handleMissingPathVariableException(HttpRequestMethodNotSupportedException e){
public ResponseEntity<BaseResponse> handleMissingPathVariableException(HttpRequestMethodNotSupportedException e) {
log.error("[handleMissingPathVariableException] 요청 url에서 path variable이 누락된 경우");
log.error(e.getMessage());
log.error(String.valueOf(e.getHeaders()));
Expand All @@ -53,7 +60,7 @@ public ResponseEntity<BaseResponse> handleMissingPathVariableException(HttpReque
}

@ExceptionHandler(NoHandlerFoundException.class)
public ResponseEntity<BaseResponse> handlerNotFoundException(NoHandlerFoundException e){
public ResponseEntity<BaseResponse> handlerNotFoundException(NoHandlerFoundException e) {
log.error("[handlerNotFoundException] 구현되지 않은 API로 요청을 보낸 경우");
log.error(e.getMessage());
HttpStatus httpStatus = API_NOT_FOUND.getHttpStatus();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,22 +53,16 @@ public enum BaseResponseStatus {
//- 5000번대 : 채팅방(chat) 관련 코드
NOT_FOUND_CHAT(5000, HttpStatus.NOT_FOUND, "존재하지 않는 채팅방입니다."),
ALREADY_CHAT(5001, HttpStatus.OK, "이미 존재하는 채팅방입니다."),
UNAUTHORIZED_CHAT_MEMBER(5002, HttpStatus.UNAUTHORIZED, "채팅방에 접근할 수 있는 회원이 아닙니다."),
NOT_FOUND_CHATMEMBER(5003, HttpStatus.BAD_REQUEST, "채팅방 구성원이 아닙니다."),
UNAUTHORIZED_CHAT_MEMBER(5002, HttpStatus.UNAUTHORIZED, "채팅방에 접근할 수 없는 회원입니다. 권한을 확인해주세요."),
EMPTY_CHAT_MEMBER(5003, HttpStatus.BAD_REQUEST, "채팅방 구성원이 없습니다. 다시 시도해주세요."),
NOT_FOUND_CHAT_MEMBER(5004, HttpStatus.BAD_REQUEST, "채팅방 구성원이 아닙니다."),
ALREADY_LEFT_CHAT(5005, HttpStatus.BAD_REQUEST, "해당 회원은 이미 채팅방을 나갔습니다."),


// EXIT_CHATROOM_FAIL(5001, HttpStatus.INTERNAL_SERVER_ERROR, "채팅방 나가기에 실패했습니다."),
// MALFORMED_CHATROOM_ID(5002, HttpStatus.BAD_REQUEST, "uri의 채팅방 id가 올바르지 않습니다."),
// DUPLICATED_JOIN(5003, HttpStatus.BAD_REQUEST, "이미 참여하고 있는 회원은 초대할 수 없습니다."),
// EMPTY_CHATROOM(5004, HttpStatus.BAD_REQUEST, "채팅방 참여 인원이 없어 채팅방을 만들 수 없습니다."),
// DUPLICATE_CHATROOM_NAME(5005, HttpStatus.BAD_REQUEST, "이미 존재하는 채팅방 이름입니다. 다른 이름으로 시도해주세요."),
// STOMP_ACCESSOR_NULL(5006, HttpStatus.BAD_REQUEST, "받은 요청의 stomp header accessor가 존재하지 않습니다.(null) 다시 시도해주세요."),
// CHATROOM_BAD_REQUEST(5007, HttpStatus.BAD_REQUEST, "채팅방 생성/수정 요청이 올바르지 않습니다. 다시 시도해 주세요."),

//- 6000번대 : 메시지(message)/소켓 관련 코드
WEBSOCKET_CONNECTION_FAIL(6000, HttpStatus.INTERNAL_SERVER_ERROR, "웹소켓 서버 접속에 실패했습니다."),
WEBSOCKET_CLOSE_FAIL(6001, HttpStatus.INTERNAL_SERVER_ERROR, "웹소켓 서버와의 연결 종료에 실패했습니다."),
MESSAGE_FORMAT_ERROR(6002, HttpStatus.BAD_REQUEST, "클라이언트에서 요청한 메시지 json 객체의 형식이 잘못됐습니다."),
MESSAGE_FORMAT_ERROR(6002, HttpStatus.BAD_REQUEST, "메시지에서 하나 이상의 필드가 누락되었습니다. json 데이터를 확인해주세요."),
CONVERT_TO_JSON_FAIL(6003, HttpStatus.INTERNAL_SERVER_ERROR, "메시지를 json 형태로 바꾸는데 실패했습니다"),
CONVERT_TO_OBJECT_FAIL(6004, HttpStatus.INTERNAL_SERVER_ERROR, "json을 메세지 객체 형태로 바꾸는데 실패했습니다"),
MESSAGE_SEND_FAIL(6005, HttpStatus.INTERNAL_SERVER_ERROR, "메시지 전송에 실패했습니다."),
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/kuchat/server/domain/BaseTime.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ public abstract class BaseTime {

@LastModifiedDate
@Column(name = "modified_at", updatable = false)
private LocalDateTime modifedDate;
private LocalDateTime modifiedDate;

}
Loading
Loading