Skip to content

Commit 597822c

Browse files
authored
Merge pull request #125 from swyp-app-team-4/feat#124-add-naver-static-map-image
[Feat] 사용자 장소 Resolve 시 정적 지도(Static Map) 썸네일 자동 생성 및 저장
2 parents 7b01bc1 + 0195dc1 commit 597822c

11 files changed

Lines changed: 290 additions & 115 deletions

File tree

src/main/java/boombimapi/domain/place/command/api/dto/request/ResolveMemberPlaceRequest.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,22 @@ public record ResolveMemberPlaceRequest(
55
String name,
66
String address,
77
Double latitude,
8-
Double longitude,
9-
String imageUrl
8+
Double longitude
109
) {
1110

1211
public static ResolveMemberPlaceRequest of(
1312
String uuid,
1413
String name,
1514
String address,
1615
Double latitude,
17-
Double longitude,
18-
String imageUrl
19-
) {
16+
Double longitude
17+
) {
2018
return new ResolveMemberPlaceRequest(
2119
uuid,
2220
name,
2321
address,
2422
latitude,
25-
longitude,
26-
imageUrl
23+
longitude
2724
);
2825
}
2926
}

src/main/java/boombimapi/domain/place/command/entity/MemberPlace.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,32 +52,28 @@ private MemberPlace(
5252
String name,
5353
String address,
5454
Double latitude,
55-
Double longitude,
56-
String imageUrl
55+
Double longitude
5756
) {
5857
this.uuid = uuid;
5958
this.name = name;
6059
this.address = address;
6160
this.latitude = latitude;
6261
this.longitude = longitude;
63-
this.imageUrl = imageUrl;
6462
}
6563

6664
public static MemberPlace of(
6765
String uuid,
6866
String name,
6967
String address,
7068
Double latitude,
71-
Double longitude,
72-
String imageUrl
69+
Double longitude
7370
) {
7471
return MemberPlace.builder()
7572
.uuid(uuid)
7673
.name(name)
7774
.address(address)
7875
.latitude(latitude)
7976
.longitude(longitude)
80-
.imageUrl(imageUrl)
8177
.build();
8278
}
8379

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package boombimapi.domain.place.command.infrastructure;
2+
3+
import boombimapi.global.properties.StaticMapProperties;
4+
import org.springframework.beans.factory.annotation.Qualifier;
5+
import org.springframework.http.HttpStatusCode;
6+
import org.springframework.stereotype.Component;
7+
import org.springframework.web.reactive.function.client.WebClient;
8+
import reactor.core.publisher.Mono;
9+
10+
@Component
11+
public class NaverStaticMapWebClient {
12+
13+
private final WebClient webClient;
14+
private final StaticMapProperties properties;
15+
16+
public NaverStaticMapWebClient(
17+
@Qualifier("naverStaticMapHttpClient") WebClient webClient,
18+
StaticMapProperties properties
19+
) {
20+
this.webClient = webClient;
21+
this.properties = properties;
22+
}
23+
24+
public byte[] fetchStaticMapImage(
25+
double latitude,
26+
double longitude
27+
) {
28+
String center = longitude + "," + latitude;
29+
String markers = properties.markerStyle()
30+
+ "|pos:" + longitude + " " + latitude
31+
+ "|viewSizeRatio:" + properties.viewSizeRatio();
32+
33+
return webClient.get()
34+
.uri(uriBuilder -> uriBuilder.path("/map-static/v2/raster")
35+
.queryParam("w", properties.width())
36+
.queryParam("h", properties.height())
37+
.queryParam("level", properties.level())
38+
.queryParam("scale", properties.scale())
39+
.queryParam("center", center)
40+
.queryParam("markers", markers)
41+
.build())
42+
.retrieve()
43+
.onStatus(HttpStatusCode::isError, res ->
44+
res.bodyToMono(String.class)
45+
.flatMap(error -> Mono.error(new RuntimeException(
46+
"Naver StaticMap error: " + res.statusCode() + " / " + error))))
47+
.bodyToMono(byte[].class)
48+
.block();
49+
50+
}
51+
52+
}

src/main/java/boombimapi/domain/place/command/repository/MemberPlaceRepository.java

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
import boombimapi.domain.search.presentation.dto.PlaceNameProjection;
88
import org.springframework.data.domain.Pageable;
99
import org.springframework.data.jpa.repository.JpaRepository;
10+
import org.springframework.data.jpa.repository.Modifying;
1011
import org.springframework.data.jpa.repository.Query;
12+
import org.springframework.data.repository.query.Param;
1113

1214
public interface MemberPlaceRepository extends JpaRepository<MemberPlace, Long> {
1315

@@ -22,18 +24,26 @@ List<MemberPlace> findByLatitudeBetweenAndLongitudeBetween(
2224

2325
// 연관 검색
2426
@Query("""
25-
select p.name as name
26-
from MemberPlace p
27-
where lower(p.name) like lower(concat('%', ?1, '%'))
28-
order by lower(p.name) asc
29-
""")
27+
select p.name as name
28+
from MemberPlace p
29+
where lower(p.name) like lower(concat('%', ?1, '%'))
30+
order by lower(p.name) asc
31+
""")
3032
List<PlaceNameProjection> searchByName(String keyword, Pageable pageable);
3133

3234

33-
3435
// 검색 결과
3536
List<MemberPlace> findEntitiesByNameContainingIgnoreCase(String keyword, Pageable pageable);
3637

37-
38-
38+
@Modifying(clearAutomatically = true, flushAutomatically = true)
39+
@Query(value = """
40+
UPDATE member_places
41+
SET image_url = :imageUrl,
42+
updated_at = NOW()
43+
WHERE id = :memberPlaceId
44+
""", nativeQuery = true)
45+
void updateImageUrl(
46+
@Param("memberPlaceId") Long memberPlaceId,
47+
@Param("imageUrl") String imageUrl
48+
);
3949
}

src/main/java/boombimapi/domain/place/command/service/MemberPlaceService.java

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import boombimapi.domain.member.domain.entity.Member;
1111
import boombimapi.domain.member.domain.repository.MemberRepository;
1212
import boombimapi.domain.place.command.api.dto.request.ResolveMemberPlaceRequest;
13+
import boombimapi.domain.place.command.infrastructure.NaverStaticMapWebClient;
1314
import boombimapi.domain.place.query.api.dto.request.ViewportRequest;
1415
import boombimapi.domain.place.query.api.dto.response.member.GetMemberPlaceDetailResponse;
1516
import boombimapi.domain.place.query.api.dto.response.member.MemberPlaceSummaryResponse;
@@ -19,6 +20,7 @@
1920
import boombimapi.domain.place.query.api.dto.response.node.ViewportPlaceNodeResponse;
2021
import boombimapi.domain.place.command.entity.MemberPlace;
2122
import boombimapi.domain.place.command.repository.MemberPlaceRepository;
23+
import boombimapi.global.infra.s3.presentation.application.S3Service;
2224
import boombimapi.global.vo.Coordinate;
2325
import boombimapi.global.geo.core.ClusterMarker;
2426
import boombimapi.global.geo.core.ClusterPoint;
@@ -39,6 +41,7 @@
3941
import org.springframework.data.domain.Pageable;
4042
import org.springframework.data.domain.Slice;
4143
import org.springframework.stereotype.Service;
44+
import org.springframework.transaction.annotation.Transactional;
4245

4346
@Slf4j
4447
@Service
@@ -52,24 +55,40 @@ public class MemberPlaceService {
5255
private final Clusterer clusterer;
5356
private final FavoriteRepository favoriteRepository;
5457

58+
private final NaverStaticMapWebClient naverStaticMapWebClient;
59+
private final S3Service s3Service;
60+
61+
// TODO: 추후 트랜잭션 분리 시 리팩터링
62+
@Transactional
5563
public ResolveMemberPlaceResponse resolveMemberPlace(
5664
ResolveMemberPlaceRequest request
5765
) {
66+
Optional<MemberPlace> memberPlaceOptional = memberPlaceRepository.findByUuid(request.uuid());
67+
68+
if (memberPlaceOptional.isPresent()) {
69+
return ResolveMemberPlaceResponse.from(memberPlaceOptional.get());
70+
}
71+
72+
MemberPlace created = memberPlaceRepository.save(
73+
MemberPlace.of(
74+
request.uuid(),
75+
request.name(),
76+
request.address(),
77+
request.latitude(),
78+
request.longitude()
79+
)
80+
);
81+
82+
try {
83+
byte[] bytes = naverStaticMapWebClient.fetchStaticMapImage(request.latitude(), request.longitude());
84+
String key = "maps/naver/static-map/%d.png".formatted(created.getId());
85+
String url = s3Service.storeStaticMapImage(key, bytes, "image/png");
86+
memberPlaceRepository.updateImageUrl(created.getId(), url);
87+
} catch (Exception e) {
88+
log.warn("Static map generation failed. placeId={}, err={}", created.getId(), e.toString());
89+
}
5890

59-
// TODO: 레이스 컨디션 처리 필요(동일 uuid 동시 생성 방지)
60-
MemberPlace memberPlace = memberPlaceRepository.findByUuid(request.uuid())
61-
.orElseGet(() -> memberPlaceRepository.save(
62-
MemberPlace.of(
63-
request.uuid(),
64-
request.name(),
65-
request.address(),
66-
request.latitude(),
67-
request.longitude(),
68-
request.imageUrl()
69-
)
70-
));
71-
72-
return ResolveMemberPlaceResponse.from(memberPlace);
91+
return ResolveMemberPlaceResponse.from(created);
7392
}
7493

7594
public GetMemberPlaceDetailResponse getMemberPlaceDetail(

0 commit comments

Comments
 (0)