Skip to content

Commit

Permalink
Merge pull request #27 from Project-Catcher/feat-hg-festival-batch
Browse files Browse the repository at this point in the history
축제 페스티벌 예제
  • Loading branch information
dev-khg authored Nov 19, 2023
2 parents c561f4d + 2035f27 commit 01739b1
Show file tree
Hide file tree
Showing 8 changed files with 185 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,16 @@ public class HashCodeGenerator {
public static String hashString(String category, String input) {
return DigestUtils.sha256Hex(category + "-" + input);
}

public static String hashString(String input) {
return DigestUtils.sha256Hex(input);
}

public static String hashString(Object... objects) {
StringBuilder sb = new StringBuilder();
for (Object object : objects) {
sb.append(object.toString());
}
return DigestUtils.sha256Hex(sb.toString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,16 @@ public class CatcherItem extends BaseTimeEntity {

@Column(name = "deleted_at")
private ZonedDateTime deletedAt;

public void changeContents(CatcherItem catcherItem) {
this.category = catcherItem.category;
this.location = catcherItem.location;
this.itemHashValue = catcherItem.itemHashValue;
this.title = catcherItem.title;
this.description = catcherItem.description;
this.thumbnailUrl = catcherItem.thumbnailUrl;
this.resourceUrl = catcherItem.resourceUrl;
this.startAt = catcherItem.startAt;
this.endAt = catcherItem.endAt;
}
}
24 changes: 15 additions & 9 deletions src/main/java/com/catcher/batch/core/dto/ApiResponse.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
package com.catcher.batch.core.dto;

import com.catcher.batch.core.domain.entity.CatcherItem;
import com.catcher.batch.core.domain.entity.Category;
import com.catcher.batch.core.domain.entity.Location;

import java.time.ZoneId;
import java.time.ZonedDateTime;

public interface ApiResponse {
ZoneId zoneId = ZoneId.of("Asia/Seoul");

String getCategory();
String getHashValue();
String getTitle();
String getDescription();
String getThumbnailUrl();
String getResourceUrl();
ZonedDateTime getStartAt();
ZonedDateTime getEndAt();

String getAddress();
String getProvince();
String getCity();

String getHashString();

String getCategory();

CatcherItem toEntity(Location location, Category category);

default boolean isExpired() {
return getEndAt() != null && ZonedDateTime.now(zoneId).isAfter(getEndAt());
}
}
75 changes: 32 additions & 43 deletions src/main/java/com/catcher/batch/core/dto/FestivalApiResponse.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package com.catcher.batch.core.dto;

import com.catcher.batch.annotation.CatcherJson;
import com.catcher.batch.common.utils.HashCodeGenerator;
import com.catcher.batch.core.domain.entity.CatcherItem;
import com.catcher.batch.core.domain.entity.Category;
import com.catcher.batch.core.domain.entity.Location;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.micrometer.common.util.StringUtils;
import lombok.Getter;

import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.List;
Expand All @@ -25,11 +29,16 @@ public class FestivalApiResponse {

@JsonIgnoreProperties(ignoreUnknown = true)
static class FestivalItem implements ApiResponse {
private final static String CATEGORY = "festival";

@JsonProperty("fstvlNm")
private String fetivalName;
private String festivalName;

@JsonProperty("rdnmadr")
private String address;
private String roadAddress;

@JsonProperty("lnmadr")
private String zibunAddress;

@JsonProperty("fstvlCo")
private String description;
Expand All @@ -44,58 +53,38 @@ static class FestivalItem implements ApiResponse {
private Date endDate;

@Override
public String getCategory() {
return "festival";
}

@Override
public String getHashValue() {
return fetivalName;
}

@Override
public String getTitle() {
return fetivalName;
}

@Override
public String getDescription() {
return description;
}

@Override
public String getThumbnailUrl() {
return null;
}

@Override
public String getResourceUrl() {
return resourceUrl;
}

@Override
public ZonedDateTime getStartAt() {
return startDate.toInstant().atZone(zoneId);
public ZonedDateTime getEndAt() {
return endDate == null ? null : endDate.toInstant().atZone(zoneId);
}

@Override
public ZonedDateTime getEndAt() {
return endDate.toInstant().atZone(zoneId);
public String getAddress() {
return StringUtils.isBlank(roadAddress) ? zibunAddress : roadAddress;
}

@Override
public String getAddress() {
return address;
public String getHashString() {
return HashCodeGenerator.hashString(CATEGORY, fetivalName, startDate, endDate);
}

@Override
public String getProvince() {
return null;
public String getCategory() {
return CATEGORY;
}

@Override
public String getCity() {
return null;
public CatcherItem toEntity(Location location, Category category) {
return CatcherItem
.builder()
.title(fetivalName)
.itemHashValue(getHashString())
.startAt(startDate.toInstant().atZone(zoneId))
.resourceUrl(resourceUrl)
.description(description)
.endAt(getEndAt())
.location(location)
.category(category)
.build();
}
}
}
65 changes: 32 additions & 33 deletions src/main/java/com/catcher/batch/core/service/BatchService.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.catcher.batch.core.service;

import com.catcher.batch.common.utils.HashCodeGenerator;
import com.catcher.batch.core.database.CatcherItemRepository;
import com.catcher.batch.core.database.CategoryRepository;
import com.catcher.batch.core.database.LocationRepository;
Expand All @@ -8,6 +9,7 @@
import com.catcher.batch.core.domain.entity.Location;
import com.catcher.batch.core.dto.ApiResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional;

import java.time.ZonedDateTime;
Expand All @@ -18,11 +20,13 @@
import java.util.stream.Collectors;

@RequiredArgsConstructor
@Slf4j
public abstract class BatchService {
private final CatcherItemRepository catcherItemRepository;
private final CategoryRepository categoryRepository;
private final LocationRepository locationRepository;

@Transactional
public void batch(List<? extends ApiResponse> apiResponses) {
Category category = categoryRepository.findByName(apiResponses.get(0).getCategory())
.orElseGet(() -> categoryRepository.save(Category.create(apiResponses.get(0).getCategory())));
Expand All @@ -32,54 +36,62 @@ public void batch(List<? extends ApiResponse> apiResponses) {

List<CatcherItem> deleteItems = new ArrayList<>();
List<CatcherItem> saveItems = new ArrayList<>();

List<CatcherItem> catcherItems = apiResponses.stream()
.filter(apiResponse -> {
String hashKey = hashString(apiResponse);
if (itemMap.containsKey(hashKey)) {
String hashKey = apiResponse.getHashString();
CatcherItem savedCatcherItem = null;

if ((savedCatcherItem = itemMap.get(hashKey)) != null) {
if (isExpired(apiResponse.getEndAt())) {
deleteItems.add(itemMap.get(hashKey));
deleteItems.add(savedCatcherItem);
}
if (isContentChanged(itemMap.get(hashKey), apiResponse)) {
CatcherItem e = itemMap.get(hashKey);
// e.changeContent(dsfasfaf,dadasd); ... 요기에 바꾸는 로직 추가
saveItems.add(e);
CatcherItem receivedCatcherItem = apiResponse.toEntity(getLocation(apiResponse), category);

if (isContentChanged(savedCatcherItem, receivedCatcherItem)) {
savedCatcherItem.changeContents(receivedCatcherItem);
saveItems.add(savedCatcherItem);
}

return false;
}
if (isExpired(apiResponse.getEndAt())) {
return false;
} else {
if (isExpired(apiResponse.getEndAt())) {
return false;
}
return true;
}
return true;
})
.map(apiResponse -> apiResponseToCatcherItem(apiResponse, category, getLocation(apiResponse)))
.map(apiResponse -> apiResponse.toEntity(getLocation(apiResponse), category))
.toList();

if (!saveItems.isEmpty()) {
log.info("변경된 아이템 개수 : {}, 카테고리 = {}", saveItems.size(), category.getName());
catcherItemRepository.saveAll(saveItems);
}

if (!catcherItems.isEmpty()) {
log.info("변경된 아이템 개수 : {}, 카테고리 = {}", catcherItems.size(), category.getName());
catcherItemRepository.saveAll(catcherItems);
}

if (!deleteItems.isEmpty()) {
log.info("삭제된 아이템 개수 : {}, 카테고리 = {}", deleteItems.size(), category.getName());
catcherItemRepository.deleteAll(deleteItems);
}
}

protected abstract String hashString(ApiResponse apiResponse);
protected abstract Location getLocation(ApiResponse apiResponse);

protected abstract boolean isContentChanged(CatcherItem catcherItem, ApiResponse apiResponse);
protected boolean isContentChanged(CatcherItem originCatcherItem, CatcherItem newCatcherItem) {
return false;
}

protected abstract Location getLocation(ApiResponse apiResponse);
protected String hashString(ApiResponse apiResponse) {
return HashCodeGenerator.hashString(apiResponse.getHashString());
}

protected Location getLocation(String province, String city) {
String withoutDo = province.replace("도", "");

return locationRepository.findByDescription(withoutDo, city)
.orElseThrow();
.orElse(null);
}

protected Location getLocation(String address) {
Expand All @@ -92,20 +104,7 @@ protected Location getLocation(String address) {
.orElseThrow();
}

protected CatcherItem apiResponseToCatcherItem(ApiResponse apiResponse, Category category, Location location) {
return CatcherItem.builder()
.category(category)
.thumbnailUrl(apiResponse.getThumbnailUrl())
.location(location)
.itemHashValue(hashString(apiResponse))
.description(apiResponse.getDescription())
.resourceUrl(apiResponse.getResourceUrl())
.endAt(apiResponse.getEndAt())
.startAt(apiResponse.getStartAt())
.build();
}

private boolean isExpired(ZonedDateTime endDateTime) {
return endDateTime != null && ZonedDateTime.now().isAfter(endDateTime);
return endDateTime != null && ZonedDateTime.now(ApiResponse.zoneId).isAfter(endDateTime);
}
}
18 changes: 11 additions & 7 deletions src/main/java/com/catcher/batch/core/service/FestivalService.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,35 @@
import com.catcher.batch.core.domain.entity.CatcherItem;
import com.catcher.batch.core.domain.entity.Location;
import com.catcher.batch.core.dto.ApiResponse;
import org.springframework.stereotype.Service;
import io.micrometer.common.util.StringUtils;

import java.util.Objects;

@Service
public class FestivalService extends BatchService {

public FestivalService(CatcherItemRepository catcherItemRepository, CategoryRepository categoryRepository, LocationRepository locationRepository) {
super(catcherItemRepository, categoryRepository, locationRepository);
}

@Override
protected boolean isContentChanged(CatcherItem catcherItem, ApiResponse apiResponse) {
int responseHash = Objects.hash(catcherItem.getStartAt(), catcherItem.getEndAt(), catcherItem.getTitle());
int catcherHash = Objects.hash(apiResponse.getStartAt(), apiResponse.getEndAt(), apiResponse.getTitle());
protected boolean isContentChanged(CatcherItem originCatcherItem, CatcherItem newCatcherItem) {
int responseHash = Objects.hash(newCatcherItem.getStartAt(), newCatcherItem.getEndAt(), newCatcherItem.getTitle());
int catcherHash = Objects.hash(originCatcherItem.getStartAt(), originCatcherItem.getEndAt(), originCatcherItem.getTitle());
return responseHash != catcherHash;
}

@Override
protected String hashString(ApiResponse apiResponse) {
return HashCodeGenerator.hashString(apiResponse.getCategory(), apiResponse.getTitle());
return HashCodeGenerator.hashString(apiResponse.getHashString());
}

@Override
protected Location getLocation(ApiResponse apiResponse) {
return getLocation(apiResponse.getAddress());
if (!StringUtils.isBlank(apiResponse.getAddress())) {
String[] address = apiResponse.getAddress().split(" ");

return getLocation(address[0], address[1]);
}
return null;
}
}
Loading

0 comments on commit 01739b1

Please sign in to comment.