Chef.Yeon
Code Cook
Chef.Yeon
전체 방문자
오늘
어제
  • 분류 전체보기 (230)
    • 게임 개발 (1)
      • Unity (1)
    • Android (27)
      • Kotlin (19)
      • 우아한테크코스 5기 (4)
    • Language (11)
      • 파이썬 (3)
      • Java (7)
    • DB (2)
      • SQL (16)
    • Spring (25)
    • 코딩테스트 (56)
    • Git (1)
    • TIL (85)
    • DevOps (6)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 우아한테크코스
  • Wil
  • MariaDB
  • kotlin
  • Docker
  • webflux
  • 파이썬
  • 프리코스
  • 코딩테스트
  • kibana
  • til
  • elasticsearch
  • enum
  • 문자열
  • rsocket
  • java
  • 다이나믹 프로그래밍
  • 코틀린 인 액션
  • spring
  • 내림차순
  • 프로그래머스
  • 코틀린
  • 레포지토리
  • Android
  • 에라토스테네스의 체
  • 안드로이드
  • 백준
  • ec2
  • SQL
  • grafana

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
Chef.Yeon

Code Cook

[TIL - 20230531] webflux 채널 생성/조회
TIL

[TIL - 20230531] webflux 채널 생성/조회

2023. 6. 1. 05:51

 

💻구현

사용자가 스트리밍 중인 채널에 들어가면 해당 채널에 들어온 사람들끼리만 채팅이 가능해야 한다. 

그러기 위해서는 우선 채널이 필요했다. 오늘도 정아님과 코딩 시작!

 

회원가입 시에 모든 사용자는 자신만의 채널을 갖게 된다. 

 

MemberService

    @Transactional
    public Mono<ResponseEntity<String>> signup(SignupRequestDto signupRequestDto) {
        return memberRepository
                .existsByUserId(signupRequestDto.getUserId())
                .flatMap(exists -> {
                    if (exists) return Mono.error(new IllegalArgumentException("중복된 아이디입니다."));
                    else {
                        return memberRepository
                                .save(new Member(signupRequestDto, passwordEncoder.encode(signupRequestDto.getPassword()),
                                        signupRequestDto.getMemberRole().equals("ADMIN") ? MemberRoleEnum.ADMIN : MemberRoleEnum.USER))
                                .onErrorResume(exception -> {
                                    return Mono.error(new RuntimeException("회원 정보 저장 중 오류 발생!"));
                                })
                                .flatMap(member -> {
                                    return channelRepository
                                            .save(new Channel(member.getNickname())) //닉네임으로 채널 생성
                                            .onErrorResume(exception -> {
                                                return Mono.error(new RuntimeException("회원 정보 저장 중 오류 발생!"));
                                            });
                                })
                                .thenReturn(ResponseEntity.ok(signupRequestDto.getNickname() + "님 회원 가입 완료 o(〃^▽^〃)o"));
                    }
                });

    }

 

ChannelController

@RestController
@RequestMapping("broadcasts")
@RequiredArgsConstructor
public class ChannelController {
    private final ChannelService channelService;

	//스트리밍 중인 채널 전체 조회
    @GetMapping
    public Mono<ResponseEntity<Flux<ChannelResponseDto>>> getAllOnAirChannels(){
        return channelService.getAllOnAirChannels();
    }

	//채널 상세 조회
    @GetMapping("/{id}")
    public Mono<ResponseEntity<ChannelDetailResponseDto>> getChannelDetail(@PathVariable("id") Long id){
        return channelService.getChannelDetail(id);
    }

	//스트리밍 시작
    @PostMapping("/{id}/onair")
    public Mono<ResponseEntity<String>> startBroadcast(Mono<Principal> userDetails, @PathVariable("id") Long id, @RequestBody ChannelRequestDto channelRequestDto){
        return userDetails.flatMap(principal-> {
            return channelService.startBroadcast(getMemberFromPrincipal(principal), id, channelRequestDto);
        });
    }

	//스트리밍 종료
    @PostMapping("/{id}/offair")
    public Mono<ResponseEntity<String>> endBroadcast(Mono<Principal> userDetails, @PathVariable("id") Long id){
        return userDetails.flatMap(principal-> {
            return channelService.endBroadcast(getMemberFromPrincipal(principal), id);
        });
    }

    private Member getMemberFromPrincipal(Principal principal){
        if (principal instanceof Authentication) {
            Authentication authentication = (Authentication) principal;
            Object principalObject = authentication.getPrincipal();
            if (principalObject instanceof UserDetailsImpl) {
                UserDetailsImpl userDetails = (UserDetailsImpl) principalObject;
                return userDetails.getMember();
            }
        }
        throw new RuntimeException("당신 누구야! Σ(っ °Д °;)っ");
    }
}

 

ChannelService

@Service
@RequiredArgsConstructor
@Transactional
public class ChannelService {
    private final ChannelRepository channelRepository;

    @Transactional(readOnly = true)
    public Mono<ResponseEntity<Flux<ChannelResponseDto>>> getAllOnAirChannels() {
        Flux<ChannelResponseDto> channelResponseFlux = channelRepository
                .findAllByOnAirTrue()
                .map(ChannelResponseDto::new);

        return Mono.just(ResponseEntity.ok(channelResponseFlux));
    }

    @Transactional(readOnly = true)
    public Mono<ResponseEntity<ChannelDetailResponseDto>> getChannelDetail(Long id) {
        return channelRepository
                .findById(id)
                .switchIfEmpty(Mono.error(new RuntimeException("없는 방송이다! Σ(っ °Д °;)っ")))
                .map(channel -> ResponseEntity.ok(new ChannelDetailResponseDto(channel)));
    }

    public Mono<ResponseEntity<String>> startBroadcast(Member member, Long id, ChannelRequestDto channelRequestDto) {
        return channelRepository
                .findById(id)
                .switchIfEmpty(Mono.error(new RuntimeException("없는 방송이다! Σ(っ °Д °;)っ")))
                .flatMap(channel -> {
                    if (!channel.getStreamer().equals(member.getNickname()))
                        return Mono.error(new RuntimeException("권한이 없잖아! (╬▔皿▔)╯"));
                    
                    // 추후에 streamKey 체크!

                    if (channelRequestDto.getTitle().trim().equals(""))
                        channelRequestDto.updateTitle(member.getNickname() + "님의 방송 (^・ω・^ ) <( Commeow! )");

                    return channelRepository.save(channel.channelOn(channelRequestDto));
                })
                .thenReturn(ResponseEntity.ok("방송 시작 (。・・)ノ <(Hi)"));
    }

    public Mono<ResponseEntity<String>> endBroadcast(Member member, Long id) {
        return channelRepository
                .findById(id)
                .switchIfEmpty(Mono.error(new RuntimeException("없는 방송이다! Σ(っ °Д °;)っ")))
                .flatMap(channel -> {
                    if (!channel.getStreamer().equals(member.getNickname()))
                        return Mono.error(new RuntimeException("권한이 없잖아! (╬▔皿▔)╯"));

                    return channelRepository.save(channel.channelOff());
                })
                .thenReturn(ResponseEntity.ok("방송 종료 (。・・)ノ <(BYE)"));
    }
}

💡느낀점

비동기 논블로킹 방식으로 코드를 작성하는 방식이 아직 익숙하지 않아서 메서드 하나를 작성하면서도 이게.. 맞나? 하면서 작성했다. 

열심히 작성하다가 안되면 GPT한테 물어보기도 하고 ㅎㅎ 혼자했으면 어려웠을텐데 정아님과 함께해서 큰 어려움 없이 채널 부분을 완성시킬 수 있었다! \( ̄︶ ̄*\))

 

슬프게도 JPA는 비동기를 제공하지 않는다. 모처럼 비동기를 위해 Webflux를 사용했는데 JPA를 사용하면 소용이 없어진다. 그래서 Reactive한 R2DBC 사용해야 했다. 그래서 Web MVC처럼 연관관계를 맺을 수 없다는게 참 슬펐다... 연관 데이터를 불러오지 못하니 repository에서 find 를 통해 찾아야 했다. 그래도 크게 불편했던 것 같지는 않다. 아직은 스코프가 작아서... 나중에 좀 커지면 힘들어질 수도...

728x90

'TIL' 카테고리의 다른 글

[TIL - 20230602] RSocket 채팅 전송 시 토큰 검사, 토큰에서 데이터 추출  (0) 2023.06.03
[TIL - 20230531-0601] RSocket을 사용해 채팅방 별 실시간 채팅하기  (2) 2023.06.02
[TIL - 20230530] Webflux + RSocket 채팅  (0) 2023.06.01
[TIL - 20230526] Github Actions Jacoco&Codecov  (0) 2023.05.27
[TIL - 20230525] Prometheus, Grafana 적용, 오류 해결  (0) 2023.05.25
    'TIL' 카테고리의 다른 글
    • [TIL - 20230602] RSocket 채팅 전송 시 토큰 검사, 토큰에서 데이터 추출
    • [TIL - 20230531-0601] RSocket을 사용해 채팅방 별 실시간 채팅하기
    • [TIL - 20230530] Webflux + RSocket 채팅
    • [TIL - 20230526] Github Actions Jacoco&Codecov
    Chef.Yeon
    Chef.Yeon
    보기 좋고 깔끔한 코드를 요리하기 위해 노력하고 있습니다.

    티스토리툴바