💻구현
사용자가 스트리밍 중인 채널에 들어가면 해당 채널에 들어온 사람들끼리만 채팅이 가능해야 한다.
그러기 위해서는 우선 채널이 필요했다. 오늘도 정아님과 코딩 시작!
회원가입 시에 모든 사용자는 자신만의 채널을 갖게 된다.
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 |