Skip to content

Commit

Permalink
[FEAT] 카카오 map api 연동
Browse files Browse the repository at this point in the history
  • Loading branch information
ohksj77 committed Aug 16, 2023
1 parent e75de87 commit a9b75fa
Show file tree
Hide file tree
Showing 24 changed files with 435 additions and 15 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.DS_Store
.DS_Store
backend/src/main/resources/application-env.yml
1 change: 1 addition & 0 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ dependencies {
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'
runtimeOnly 'com.h2database:h2:1.4.200'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.projectreactor:reactor-test'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.twtw.backend.config.client;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient;

@Configuration
@RequiredArgsConstructor
public class WebClientConfig {
private static final String HEADER_PREFIX = "KakaoAK ";
private final ObjectMapper objectMapper;

@Bean
public WebClient webClient(
@Value("${kakao-map.url}") final String url,
@Value("${kakao-map.key}") final String authHeader) {
final ExchangeStrategies exchangeStrategies = ExchangeStrategies.builder()
.codecs(configurer -> {
configurer.defaultCodecs().maxInMemorySize(-1);
configurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(objectMapper));
}).build();

return WebClient.builder()
.exchangeStrategies(exchangeStrategies)
.baseUrl(url)
.defaultHeader(HttpHeaders.AUTHORIZATION, HEADER_PREFIX + authHeader)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.twtw.backend.config.mapper;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ObjectMapperConfig {
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true)
.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public SecurityFilterChain configure(HttpSecurity http) throws Exception {
"/v3/api-docs/**",
"/swagger-ui/**",
"/swagger-ui.html",
"auth/**")
"auth/**", "/plans/**")
.permitAll())
.authorizeHttpRequests(
x -> x.requestMatchers("/test/**").permitAll().anyRequest().authenticated())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.twtw.backend.domain.plan.client;

import com.twtw.backend.domain.plan.dto.client.SearchDestinationRequest;
import com.twtw.backend.domain.plan.dto.client.SearchDestinationResponse;
import com.twtw.backend.domain.plan.entity.CategoryGroupCode;
import com.twtw.backend.global.client.MapClient;
import com.twtw.backend.global.exception.WebClientResponseException;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriBuilder;

import java.net.URI;
import java.nio.charset.StandardCharsets;

@Component
@RequiredArgsConstructor
public class SearchDestinationClient implements MapClient<SearchDestinationRequest, SearchDestinationResponse> {
private static final Integer MAX_SIZE_PER_REQUEST = 15;
private static final Integer DEFAULT_DISTANCE_RADIUS = 20000;
private final WebClient webClient;

@Override
public SearchDestinationResponse request(final SearchDestinationRequest request) {
return webClient.get()
.uri(uriBuilder -> getUri(request, uriBuilder))
.accept(MediaType.APPLICATION_JSON)
.acceptCharset(StandardCharsets.UTF_8)
.retrieve()
.bodyToMono(SearchDestinationResponse.class)
.blockOptional()
.orElseThrow(WebClientResponseException::new);
}

private URI getUri(final SearchDestinationRequest request, final UriBuilder uriBuilder) {
final UriBuilder builder = uriBuilder
.path("search/keyword")
.queryParam("query", request.getQuery())
.queryParam("x", request.getX())
.queryParam("y", request.getY())
.queryParam("radius", DEFAULT_DISTANCE_RADIUS)
.queryParam("page", request.getPage())
.queryParam("size", MAX_SIZE_PER_REQUEST);

final CategoryGroupCode categoryGroupCode = request.getCategoryGroupCode();

if (categoryGroupCode == CategoryGroupCode.NONE) {
return builder.build();
}
return builder
.queryParam("category_group_code", categoryGroupCode)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.twtw.backend.domain.plan.controller;

import com.twtw.backend.domain.plan.dto.client.SearchDestinationRequest;
import com.twtw.backend.domain.plan.dto.response.PlanDestinationResponse;
import com.twtw.backend.domain.plan.service.PlanService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("plans")
@RequiredArgsConstructor
public class PlanController {
private final PlanService planService;

@GetMapping("search/destination")
public ResponseEntity<PlanDestinationResponse> searchPlanDestination(@ModelAttribute final SearchDestinationRequest request) {
return ResponseEntity.ok(planService.searchPlanDestination(request));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.twtw.backend.domain.plan.dto.client;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class MetaDetails {
private Boolean isEnd;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.twtw.backend.domain.plan.dto.client;

import com.twtw.backend.domain.plan.entity.CategoryGroupCode;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class PlaceDetails {
private String placeName;
private String distance;
private String placeUrl;
private String categoryName;
private String addressName;
private String roadAddressName;
private CategoryGroupCode categoryGroupCode;
private String x;
private String y;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.twtw.backend.domain.plan.dto.client;

import com.twtw.backend.domain.plan.entity.CategoryGroupCode;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class SearchDestinationRequest {
private String query;
private String x;
private String y;
private Integer page;
private CategoryGroupCode categoryGroupCode;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.twtw.backend.domain.plan.dto.client;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.List;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class SearchDestinationResponse {
private MetaDetails meta;
private List<PlaceDetails> documents;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.twtw.backend.domain.plan.dto.response;

import com.twtw.backend.domain.plan.dto.client.PlaceDetails;
import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.List;

@Getter
@AllArgsConstructor
public class PlanDestinationResponse {
private List<PlaceDetails> results;
private Boolean isLast;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.twtw.backend.domain.plan.entity;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public enum CategoryGroupCode {
MT1("대형마트"),
CS2("편의점"),
PS3("어린이집, 유치원"),
SC4("학교"),
AC5("학원"),
PK6("주차장"),
OL7("주유소, 충전소"),
SW8("지하철역"),
BK9("은행"),
CT1("문화시설"),
AG2("중개업소"),
PO3("공공기관"),
AT4("관광명소"),
AD5("숙박"),
FD6("음식점"),
CE7("카페"),
HP8("병원"),
PM9("약국"),
NONE("");

private final String toKorean;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.twtw.backend.domain.plan.entity;

import com.twtw.backend.domain.member.entity.Member;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.*;

@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Plan {
@Id
@GeneratedValue(generator = "uuid2")
@Column(columnDefinition = "BINARY(16)")
private UUID id;

@Embedded
private PlanPlace planPlace;

@OneToMany(mappedBy = "plan", cascade = CascadeType.PERSIST)
private Set<PlanMember> planMembers = new HashSet<>();

@Builder
public Plan(final PlanPlace planPlace, final List<Member> members) {
this.planPlace = planPlace;
organizePlanMember(members);
}

public void organizePlanMember(final List<Member> members) {
members.stream()
.map(member -> new PlanMember(this, member))
.forEach(this.planMembers::add);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.twtw.backend.domain.plan.entity;

import com.twtw.backend.domain.member.entity.Member;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.UUID;

@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class PlanMember {
@Id
@GeneratedValue(generator = "uuid2")
@Column(columnDefinition = "BINARY(16)")
private UUID id;

@JoinColumn
@ManyToOne(fetch = FetchType.LAZY)
private Plan plan;

@JoinColumn
@ManyToOne(fetch = FetchType.LAZY)
private Member member;

@Builder
public PlanMember(final Plan plan, final Member member) {
this.plan = plan;
this.member = member;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.twtw.backend.domain.plan.entity;

import jakarta.persistence.Embeddable;
import lombok.*;

@Getter
@Embeddable
@AllArgsConstructor
@EqualsAndHashCode(of = {"x", "y"})
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class PlanPlace {
private String placeName;
private String distance;
private String placeUrl;
private String categoryName;
private String addressName;
private String roadAddressName;
private CategoryGroupCode categoryGroupCode;
private String x;
private String y;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.twtw.backend.domain.plan.repository;

import com.twtw.backend.domain.plan.entity.Plan;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.UUID;

public interface PlanRepository extends JpaRepository<Plan, UUID> {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.twtw.backend.domain.plan.service;

import com.twtw.backend.domain.plan.dto.client.SearchDestinationRequest;
import com.twtw.backend.domain.plan.dto.client.SearchDestinationResponse;
import com.twtw.backend.domain.plan.dto.response.PlanDestinationResponse;
import com.twtw.backend.domain.plan.repository.PlanRepository;
import com.twtw.backend.global.client.MapClient;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class PlanService {
private final PlanRepository planRepository;
private final MapClient<SearchDestinationRequest, SearchDestinationResponse> destinationClient;

public PlanDestinationResponse searchPlanDestination(final SearchDestinationRequest request) {
final SearchDestinationResponse response = requestMapClient(request);
return new PlanDestinationResponse(response.getDocuments(), response.getMeta().getIsEnd());
}

private SearchDestinationResponse requestMapClient(final SearchDestinationRequest request) {
return destinationClient.request(request);
}
}
Loading

0 comments on commit a9b75fa

Please sign in to comment.