Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
@AllArgsConstructor
public enum MemberErrorCode implements BaseErrorCode {

NOT_FOUND(HttpStatus.NOT_FOUND, "ARTICLE404_0", "해당 게시글을 찾을 수 없습니다."),
NOT_FOUND(HttpStatus.NOT_FOUND, "MEMBER404_0", "해당 사용자를 찾을 수 없습니다."),
OAUTH_USER_INFO_FAIL(HttpStatus.NOT_FOUND, "MEMBER404_2", "사용자 정보 조회 실패"),
OAUTH_TOKEN_FAIL(HttpStatus.BAD_REQUEST, "MEMBER400_1", "토큰 변환 실패"),
OAUTH_EMAIL_NOT_FOUND(HttpStatus.BAD_REQUEST, "MEMBER400_2", "이메일 정보를 찾을 수 없습니다."),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example.umc7th.domain.openApi.component;

import org.springframework.web.reactive.function.client.WebClient;

public interface OpenApiWebClient {
// 한국 관광정보를 가져올 수 있는 WebClient를 반환하는 메소드 정의
WebClient getTourWebClient(String language);
// 아래에 메소드를 추가하면서 확장해나갈 수 있겠죠?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.example.umc7th.domain.openApi.component;

import com.example.umc7th.domain.openApi.exception.OpenApiErrorCode;
import com.example.umc7th.domain.openApi.exception.OpenApiException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.DefaultUriBuilderFactory;
import reactor.netty.http.client.HttpClient;

import java.time.Duration;

@Component
@Slf4j
public class OpenApiWebClientImpl implements OpenApiWebClient {

@Override
public WebClient getTourWebClient(String language) {
if (language.equals("korean")) {
return getWebClient("https://apis.data.go.kr/B551011/KorService1");
}
else if (language.equals("english")) {
return getWebClient("https://apis.data.go.kr/B551011/EngService1");
}
else {
throw new OpenApiException(OpenApiErrorCode.UNSUPPORTED_LANGUAGE);
}
}

// 영문 API를 추가한 경우
// @Override
// public WebClient getEnglishTourWebClient() {
// return getWebClient("https://apis.data.go.kr/B551011/EngService1");
// }

private WebClient getWebClient(String baseUrl) {
HttpClient httpClient = HttpClient.create()
.responseTimeout(Duration.ofMillis(20000));

// Uri를 build하는 factory 생성 (baseUrl을 WebClient 대신 여기에 포함하도록)
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
// Uri factory에 인코딩 모드를 NONE으로 바꾸어 인코딩하지 않도록해줍니다.
factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.NONE);

return WebClient.builder()
.uriBuilderFactory(factory)
.clientConnector(new ReactorClientHttpConnector(httpClient))
.filter((request, next) -> {
log.info("Web Client Request: "+ request.url());
return next.exchange(request);
})
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.example.umc7th.domain.openApi.controller;

import com.example.umc7th.domain.openApi.dto.response.OpenApiResDto;
import com.example.umc7th.domain.openApi.service.query.OpenApiQueryService;
import com.example.umc7th.global.apiPayload.CustomResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api")
public class OpenApiController {

private final OpenApiQueryService openApiQueryService;

@GetMapping("/searchStay")
public CustomResponse<OpenApiResDto.SearchStayResponseListDto> controller(@RequestParam(name = "arrange", defaultValue = "A") String arrange,
@RequestParam(name = "page", defaultValue = "1") int page,
@RequestParam(name = "offset", defaultValue = "10") int offset) {
return CustomResponse.onSuccess(openApiQueryService.searchStay(arrange, page, offset));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.example.umc7th.domain.openApi.dto.response;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Builder;

import java.util.List;

public class OpenApiResDto {

@Builder
@JsonIgnoreProperties(ignoreUnknown = true) // JSON 파싱 시 필드가 없는 경우 무시
public record SearchStayResponseDto(
String addr1,
String title,
String tel,
String contentid,
String contenttypeid,
String createdtime,
String firstimage,
String firstimage2,
String mapx,
String mapy
) {}

public record SearchStayResponseListDto(
List<SearchStayResponseDto> item
) {
public static SearchStayResponseListDto from(List<SearchStayResponseDto> list) {
return new SearchStayResponseListDto(list);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.example.umc7th.domain.openApi.exception;

import com.example.umc7th.global.apiPayload.code.BaseErrorCode;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
@AllArgsConstructor
public enum OpenApiErrorCode implements BaseErrorCode {
UNSUPPORTED_LANGUAGE(HttpStatus.NOT_FOUND, "OPENAPI400", "제공하지 않는 언어입니다.");

private final HttpStatus status;
private final String code;
private final String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.example.umc7th.domain.openApi.exception;


import com.example.umc7th.global.apiPayload.exception.GeneralException;

public class OpenApiException extends GeneralException {
public OpenApiException(OpenApiErrorCode code){
super(code);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.example.umc7th.domain.openApi.service.query;

import com.example.umc7th.domain.openApi.dto.response.OpenApiResDto;

public interface OpenApiQueryService {
OpenApiResDto.SearchStayResponseListDto searchStay(String arrange, int page, int offset);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.example.umc7th.domain.openApi.service.query;

import com.example.umc7th.domain.openApi.component.OpenApiWebClient;
import com.example.umc7th.domain.openApi.dto.response.OpenApiResDto;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.List;

@Slf4j
@Service
@RequiredArgsConstructor
public class OpenApiQueryServiceImpl implements OpenApiQueryService {

private final OpenApiWebClient openApiWebClient;

@Value("${openapi.tour.serviceKey}")
private String serviceKey;


@Override
public OpenApiResDto.SearchStayResponseListDto searchStay(String arrange, int page, int offset) {
// Web Client 가져오기
WebClient webClient = openApiWebClient.getTourWebClient("korean");
Mono<OpenApiResDto.SearchStayResponseListDto> mono = webClient.get() // get method 사용
// UriBuilder를 이용하여 Endpoint와 Query Param 설정
.uri(uri -> uri
.path("/searchStay1")
.queryParam("numOfRows", offset)
.queryParam("pageNo", page)
.queryParam("MobileOS", "ETC")
.queryParam("MobileApp", "AppTest")
.queryParam("_type", "json")
.queryParam("arrange", arrange)
.queryParam("serviceKey", serviceKey)
.build())
// 응답을 가져오기 위한 method (.onStatus()를 이용해서 Http 상태코드에 따라 다르게 처리해줄 수 있음)
.retrieve()
// 응답에서 body만 String 타입으로 가져오기 (ResponseEntity<Object> 중 Object만 String 형식으로 가져오기)
.bodyToMono(String.class)
// String 값을 메소드로 매핑하여 OpenApiResponseDTO.SearchStayResponseListDTO로 변경하기
.map(this::toSearchStayResponseListDTO)
// 에러가 발생한 경우 log를 찍도록
.doOnError(e -> log.error("Open Api 에러 발생: " + e.getMessage()))
// 성공한 경우에도 log를 찍도록
.doOnSuccess(s -> log.info("관광 정보를 가져오는데 성공했습니다."));

// block()을 사용해서 응답을 바로 가져오도록
return mono.block();
}

private OpenApiResDto.SearchStayResponseListDto toSearchStayResponseListDTO(String response) {
ObjectMapper objectMapper = new ObjectMapper();
try {
// item으로 담을 list 선언
List<OpenApiResDto.SearchStayResponseDto> list = new ArrayList<>();
// JsonNode 형식으로 응답을 읽고 item이 담긴 배열만 읽고 싶기에 item이 있는 배열까지 들어가기
JsonNode jsonNode = objectMapper.readTree(response).path("response").path("body").path("items").path("item");
// item 하나씩 처리
for (JsonNode node : jsonNode) {
// item 하나씩 읽어서 OpenApiResponseDTO.SearchStayResponseDTO로 변경해서 List에 추가
list.add(objectMapper.convertValue(node, OpenApiResDto.SearchStayResponseDto.class));
}
// 응답을 만들어서 반환
return OpenApiResDto.SearchStayResponseListDto.from(list);
} catch (Exception e) {
// 에러 처리
e.fillInStackTrace();
}
return OpenApiResDto.SearchStayResponseListDto.from(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.example.umc7th.domain.member.repository.MemberRepository;
import com.example.umc7th.global.jwt.exception.JwtAccessDeniedHandler;
import com.example.umc7th.global.jwt.exception.JwtAuthenticationEntryPoint;
import com.example.umc7th.global.jwt.filter.JwtFilter;
import com.example.umc7th.global.jwt.util.JwtProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
Expand All @@ -16,13 +17,14 @@
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

private final AuthenticationConfiguration authenticationConfiguration;
private final MemberRepository memberRepository;
private final JwtProvider jwtProvider;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
Expand Down Expand Up @@ -90,6 +92,10 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
.accessDeniedHandler(jwtAccessDeniedHandler)
);

http.addFilterBefore(
new JwtFilter(jwtProvider, memberRepository),
UsernamePasswordAuthenticationFilter.class
);

return http.build();
}
Expand Down
37 changes: 37 additions & 0 deletions src/main/java/com/example/umc7th/global/config/WebConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.example.umc7th.global.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.netty.http.client.HttpClient;

import java.time.Duration;

@Configuration
@Slf4j
public class WebConfig {

@Bean
public WebClient webClient() {
// 연결 설정
// TCP 연결 시 응답 시간 초과 값을 설정
HttpClient httpClient = HttpClient.create()
.responseTimeout(Duration.ofMillis(20000));

// WebClient 생성
return WebClient.builder()
// Base URL 설정
.baseUrl("https://apis.data.go.kr/B551011/KorService1")
// 만들었던 연결 설정 넣어주기
.clientConnector(new ReactorClientHttpConnector(httpClient))
// filter를 사용해서 요청을 보낼 때 로그가 찍히도록
.filter((request, next) -> {
log.info("Web Client Request: "+ request.url());
return next.exchange(request);
})
// build로 객체 생성
.build();
}
}