Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebSocket 수정 #161

Merged
merged 15 commits into from
May 12, 2024
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,14 @@ repositories {

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-websocket'
implementation 'org.webjars:sockjs-client:1.1.2'
implementation 'org.webjars:stomp-websocket:2.3.3-1'

implementation 'io.jsonwebtoken:jjwt:0.9.1'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
Expand Down
1 change: 0 additions & 1 deletion src/main/java/com/timcooki/jnuwiki/JnuwikiApplication.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.timcooki.jnuwiki;

import java.util.Date;
import java.util.TimeZone;
import javax.annotation.PostConstruct;
import org.springframework.boot.SpringApplication;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.timcooki.jnuwiki.configuration.stomp;

import com.timcooki.jnuwiki.domain.docsRequest.error.DocsErrorHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.ChannelRegistration;
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;

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

private final DocsPreHandler docsPreHandler;
private final DocsErrorHandler docsErrorHandler;

@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/queue");
config.setApplicationDestinationPrefixes("/app");
}

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/api/connect")
.setAllowedOriginPatterns("*")
.withSockJS();
// registry.setErrorHandler(docsErrorHandler);
}

@Override
public void configureClientInboundChannel(ChannelRegistration registration){
registration.interceptors(docsPreHandler);
}



}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.timcooki.jnuwiki.configuration.stomp;

import com.timcooki.jnuwiki.domain.security.config.JwtProvider;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.MalformedJwtException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageDeliveryException;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.stereotype.Component;

@RequiredArgsConstructor
@Component
@Slf4j
public class DocsPreHandler implements ChannelInterceptor {

private static final String AUTHORIZATION = "Authorization";
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(message);

String authorizationHeader = String.valueOf(headerAccessor.getNativeHeader(AUTHORIZATION));

if(authorizationHeader == null || authorizationHeader.equals("null")){
throw new MessageDeliveryException("err1 : 인증이 되지 않은 사용자입니다.");
}

String token = authorizationHeader.substring(JwtProvider.PREFIX.length());

Claims claims;
try{
claims = JwtProvider.getClaims(token);
//TODO : spring security 혹은 최소한의 보안만
}catch (MessageDeliveryException e){
log.error("STOMP ERROR : " + e.getMessage());
throw new MessageDeliveryException("err2 : 인증이 되지 않은 사용자입니다.");
}catch (MalformedJwtException e){
log.error("STOMP ERROR : " + e.getMessage());
throw new MessageDeliveryException("err3 : 인증이 되지 않은 사용자입니다.");
}


return message;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.timcooki.jnuwiki.configuration.stomp;

import com.timcooki.jnuwiki.domain.docsRequest.repository.DocsStatusRepository;
import com.timcooki.jnuwiki.domain.security.config.JwtProvider;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.MalformedJwtException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.messaging.MessageDeliveryException;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.messaging.SessionDisconnectEvent;

@Component
@Slf4j
@RequiredArgsConstructor
public class StompEventListener {

private static final String AUTHORIZATION = "Authorization";

private final DocsStatusRepository docsStatusRepository;


@EventListener
public void handleStompDisconnectListener(SessionDisconnectEvent event) {
StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage());

String authorizationHeader = String.valueOf(headerAccessor.getNativeHeader(AUTHORIZATION));

if(authorizationHeader == null || authorizationHeader.equals("null")){
throw new MessageDeliveryException("err1 : 인증이 되지 않은 사용자입니다.");
}

String token = authorizationHeader.substring(JwtProvider.PREFIX.length());

Claims claims;
try{
claims = JwtProvider.getClaims(token);
String email = String.valueOf(claims.get("memberEmail"));
docsStatusRepository.deleteByEmail(email);
}catch (MessageDeliveryException e){
log.error("STOMP ERROR : " + e.getMessage());
throw new MessageDeliveryException("err2 : 인증이 되지 않은 사용자입니다.");
}catch (MalformedJwtException e){
log.error("STOMP ERROR : " + e.getMessage());
throw new MessageDeliveryException("err3 : 인증이 되지 않은 사용자입니다.");
}catch (Exception e){
log.error("ERROR : " + e.getMessage());
}



}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.timcooki.jnuwiki.domain.docsRequest.controller;

import com.timcooki.jnuwiki.domain.docsRequest.dto.DocsMessage;
import com.timcooki.jnuwiki.domain.docsRequest.service.DocsRequestReadService;
import com.timcooki.jnuwiki.domain.docsRequest.service.DocsRequestWriteService;
import com.timcooki.jnuwiki.util.ApiResult;
import com.timcooki.jnuwiki.util.ApiUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.simp.annotation.SendToUser;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@Controller
@RequiredArgsConstructor
public class DocsRequestWebSocketController {
private final DocsRequestWriteService docsRequestWriteService;
private final DocsRequestReadService docsRequestReadService;

@MessageMapping("/info")
@SendToUser("/queue/info")
public ApiResult<String> info(@Payload DocsMessage message) throws Exception{

docsRequestWriteService.createDocsStatus(message);

return ApiUtils.success("success");
}


@GetMapping("/requests/docs/status/{docsId}")
public ResponseEntity<?> getDocsStatus(@PathVariable Long docsId){

return ResponseEntity.ok(ApiUtils.success(docsRequestReadService.getDocsStatus(docsId)));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.timcooki.jnuwiki.domain.docsRequest.dto;

import lombok.Builder;

public record DocsMessage(
Long docsId,
Long memberId
) {
@Builder
public DocsMessage{}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.timcooki.jnuwiki.domain.docsRequest.dto.response;

import lombok.Builder;

public record GetDocsStatusResDTO(
boolean docsStatus
) {
@Builder
public GetDocsStatusResDTO{}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.timcooki.jnuwiki.domain.docsRequest.entity;

import com.timcooki.jnuwiki.domain.docs.entity.Docs;
import com.timcooki.jnuwiki.domain.member.entity.Member;
import lombok.Builder;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import java.time.LocalDateTime;

@Entity
@Getter
@RequiredArgsConstructor
@Table(name = "DOCS_STATUS")
@EntityListeners(AuditingEntityListener.class)
public class DocsStatus {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "docs_status_id")
private Long docsStatusId;

@JoinColumn(name = "docs_id")
@OneToOne(fetch = FetchType.LAZY)
private Docs docs;

@JoinColumn(name = "member_id")
@OneToOne(fetch = FetchType.LAZY)
private Member member;

@CreatedDate
private LocalDateTime createdAt;

@Builder
public DocsStatus(Docs docs, Member member){
this.docs = docs;
this.member = member;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.timcooki.jnuwiki.domain.docsRequest.error;

import org.springframework.stereotype.Component;
import org.springframework.web.socket.messaging.StompSubProtocolErrorHandler;

@Component
public class DocsErrorHandler extends StompSubProtocolErrorHandler {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.timcooki.jnuwiki.domain.docsRequest.repository;

import com.timcooki.jnuwiki.domain.docs.entity.Docs;
import com.timcooki.jnuwiki.domain.docsRequest.entity.DocsStatus;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

@Repository
public interface DocsStatusRepository extends JpaRepository<DocsStatus, Long> {

@Modifying
@Query("delete from DocsStatus ds WHERE ds.member.email = :email")
void deleteByEmail(@Param("email") String email);

boolean existsByDocs(Docs docs);
boolean existsByDocs_DocsId(Long docsId);

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.timcooki.jnuwiki.domain.docsRequest.service;

import com.timcooki.jnuwiki.domain.docsRequest.dto.response.GetDocsStatusResDTO;
import com.timcooki.jnuwiki.domain.docsRequest.entity.DocsRequest;
import com.timcooki.jnuwiki.domain.docsRequest.entity.DocsRequestType;
import com.timcooki.jnuwiki.domain.docsRequest.mapper.DocsRequestMapper;
import com.timcooki.jnuwiki.domain.docsRequest.repository.DocsRequestRepository;
import com.timcooki.jnuwiki.domain.docsRequest.repository.DocsStatusRepository;
import com.timcooki.jnuwiki.domain.member.DTO.response.admin.EditListReadResDTO;
import com.timcooki.jnuwiki.domain.member.DTO.response.admin.EditReadResDTO;
import com.timcooki.jnuwiki.domain.member.DTO.response.admin.NewListReadResDTO;
Expand All @@ -26,6 +28,7 @@
public class DocsRequestReadService {

private final DocsRequestRepository docsRequestRepository;
private final DocsStatusRepository docsStatusRepository;


// 기본정보 수정 요청 목록 조회
Expand Down Expand Up @@ -90,4 +93,12 @@ public NewReadResDTO getOneCreatedRequest(Long docsRequestId) {
NewReadResDTO newReadResDTO = mapper.newEntityToDTO(docsRequest, docsRequest.getDocsRequestCategory().getCategory());
return newReadResDTO;
}

public GetDocsStatusResDTO getDocsStatus(Long docsId){

return GetDocsStatusResDTO.builder()
.docsStatus(docsStatusRepository.existsByDocs_DocsId(docsId))
.build();

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

import com.timcooki.jnuwiki.domain.docs.entity.Docs;
import com.timcooki.jnuwiki.domain.docs.repository.DocsRepository;
import com.timcooki.jnuwiki.domain.docsRequest.dto.DocsMessage;
import com.timcooki.jnuwiki.domain.docsRequest.dto.request.EditWriteReqDTO;
import com.timcooki.jnuwiki.domain.docsRequest.dto.request.NewWriteReqDTO;
import com.timcooki.jnuwiki.domain.docsRequest.entity.DocsCategory;
import com.timcooki.jnuwiki.domain.docsRequest.entity.DocsRequest;
import com.timcooki.jnuwiki.domain.docsRequest.entity.DocsStatus;
import com.timcooki.jnuwiki.domain.docsRequest.mapper.DocsRequestMapper;
import com.timcooki.jnuwiki.domain.docsRequest.repository.DocsRequestRepository;
import com.timcooki.jnuwiki.domain.docsRequest.repository.DocsStatusRepository;
import com.timcooki.jnuwiki.domain.member.entity.Member;
import com.timcooki.jnuwiki.domain.member.repository.MemberRepository;
import com.timcooki.jnuwiki.util.errors.exception.Exception400;
Expand All @@ -23,6 +26,7 @@
@RequiredArgsConstructor
public class DocsRequestWriteService {
private final DocsRequestRepository docsRequestRepository;
private final DocsStatusRepository docsStatusRepository;
private final MemberRepository memberRepository;
private final DocsRepository docsRepository;

Expand Down Expand Up @@ -62,4 +66,21 @@ public void createNewDocsRequest(NewWriteReqDTO newWriteReqDTO) {
.build();
docsRepository.save(docs);
}

@Transactional
public void createDocsStatus(DocsMessage docsMessage){

Docs docs = docsRepository.findById(docsMessage.docsId())
.orElseThrow(() -> new Exception400(Exception400.NOT_FOUND_DOCS));
Member member = memberRepository.findById(docsMessage.memberId())
.orElseThrow(() -> new Exception400(Exception400.NOT_FOUND_MEMBER));

if(!docsStatusRepository.existsByDocs(docs)){
DocsStatus docsStatus = DocsStatus.builder()
.docs(docs)
.member(member)
.build();
docsStatusRepository.save(docsStatus);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public ResponseEntity<?> serverError(Exception500 e){
public ResponseEntity<?> validationError(
MethodArgumentNotValidException e) {
log.error("[VALIDATION ERROR] {}", e.getMessage());
ApiResult<?> apiResult = ApiUtils.error("[VALIDATION ERROR]", HttpStatus.BAD_REQUEST);
ApiResult<?> apiResult = ApiUtils.error(e.getMessage(), HttpStatus.BAD_REQUEST);
return ResponseEntity.badRequest().body(apiResult);
}
@ExceptionHandler(Exception.class)
Expand Down
Loading
Loading