diff --git a/realestate-prices/2018_11_10-2019_11_09.xlsx b/realestate-prices/2018_11_10-2019_11_09.xlsx index 509e331..4aa8227 100644 Binary files a/realestate-prices/2018_11_10-2019_11_09.xlsx and b/realestate-prices/2018_11_10-2019_11_09.xlsx differ diff --git a/src/main/java/kancho/realestate/comparingprices/controller/SoaringPricesController.java b/src/main/java/kancho/realestate/comparingprices/controller/SoaringPricesController.java index 6031fe0..ee28743 100644 --- a/src/main/java/kancho/realestate/comparingprices/controller/SoaringPricesController.java +++ b/src/main/java/kancho/realestate/comparingprices/controller/SoaringPricesController.java @@ -9,25 +9,25 @@ import org.springframework.web.bind.annotation.RestController; import kancho.realestate.comparingprices.domain.dto.response.SuccessReponseDto; -import kancho.realestate.comparingprices.domain.model.SoaringPrices; -import kancho.realestate.comparingprices.service.SoaringPricesService; +import kancho.realestate.comparingprices.domain.model.SoaringPrice; +import kancho.realestate.comparingprices.service.SoaringPriceService; import lombok.RequiredArgsConstructor; @RestController @RequiredArgsConstructor @RequestMapping(value = "/soaring-prices", produces = "application/json; charset=utf8") public class SoaringPricesController { - private final SoaringPricesService soaringPricesService; + private final SoaringPriceService soaringPriceService; @GetMapping(value = "/percent") public ResponseEntity percentList() { - List soaringPricesList = soaringPricesService.getSoaringPrices(SoaringPrices.Unit.PERCENT); - return new ResponseEntity<>(new SuccessReponseDto<>("급상승 부동산 가격 조회 성공", soaringPricesList), HttpStatus.OK); + List soaringPriceList = soaringPriceService.getSoaringPrices(SoaringPrice.Unit.PERCENT); + return new ResponseEntity<>(new SuccessReponseDto<>("급상승 부동산 가격 조회 성공", soaringPriceList), HttpStatus.OK); } @GetMapping(value = "/won") public ResponseEntity wonList() { - List soaringPricesList = soaringPricesService.getSoaringPrices(SoaringPrices.Unit.WON); - return new ResponseEntity<>(new SuccessReponseDto<>("급상승 부동산 가격 조회 성공", soaringPricesList), HttpStatus.OK); + List soaringPriceList = soaringPriceService.getSoaringPrices(SoaringPrice.Unit.WON); + return new ResponseEntity<>(new SuccessReponseDto<>("급상승 부동산 가격 조회 성공", soaringPriceList), HttpStatus.OK); } } diff --git a/src/main/java/kancho/realestate/comparingprices/domain/dto/request/RequestApartmentDetailDto.java b/src/main/java/kancho/realestate/comparingprices/domain/dto/request/RequestApartmentDetailDto.java index ed935fd..0400ecd 100644 --- a/src/main/java/kancho/realestate/comparingprices/domain/dto/request/RequestApartmentDetailDto.java +++ b/src/main/java/kancho/realestate/comparingprices/domain/dto/request/RequestApartmentDetailDto.java @@ -6,8 +6,10 @@ import kancho.realestate.comparingprices.exception.InvalidDealYearAndMonthException; import kancho.realestate.comparingprices.exception.InvalidRegionalCodeException; import lombok.Getter; +import lombok.ToString; @Getter +@ToString public class RequestApartmentDetailDto { private String regionalCode; private int dealYear; diff --git a/src/main/java/kancho/realestate/comparingprices/domain/dto/request/RequestSoaringPriceDto.java b/src/main/java/kancho/realestate/comparingprices/domain/dto/request/RequestSoaringPriceDto.java new file mode 100644 index 0000000..e8eac2d --- /dev/null +++ b/src/main/java/kancho/realestate/comparingprices/domain/dto/request/RequestSoaringPriceDto.java @@ -0,0 +1,32 @@ +package kancho.realestate.comparingprices.domain.dto.request; + +import java.time.LocalDate; + +import kancho.realestate.comparingprices.domain.model.SoaringPrice; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +public class RequestSoaringPriceDto { + private long apartmentId; + private String areaForExclusiveUse; + private LocalDate pastDate; + private long pastPrice; + private LocalDate latestDate; + private long latestPrice; + private SoaringPrice.Unit priceDifferenceUnit; // PERCENT, WON + private long priceDifference; + + public RequestSoaringPriceDto(long apartmentId, String areaForExclusiveUse, LocalDate pastDate, long pastPrice, + LocalDate latestDate, long latestPrice, SoaringPrice.Unit priceDifferenceUnit, long priceDifference) { + this.apartmentId = apartmentId; + this.areaForExclusiveUse = areaForExclusiveUse; + this.pastDate = pastDate; + this.pastPrice = pastPrice; + this.latestDate = latestDate; + this.latestPrice = latestPrice; + this.priceDifferenceUnit = priceDifferenceUnit; + this.priceDifference = priceDifference; + } +} diff --git a/src/main/java/kancho/realestate/comparingprices/domain/dto/request/ResponseApartmentPriceDto.java b/src/main/java/kancho/realestate/comparingprices/domain/dto/request/ResponseApartmentPriceDto.java new file mode 100644 index 0000000..98a43eb --- /dev/null +++ b/src/main/java/kancho/realestate/comparingprices/domain/dto/request/ResponseApartmentPriceDto.java @@ -0,0 +1,59 @@ +package kancho.realestate.comparingprices.domain.dto.request; + +import java.math.BigDecimal; +import java.util.Objects; + +import kancho.realestate.comparingprices.domain.model.ApartmentPrice; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +public class ResponseApartmentPriceDto { + private long apartmentId; + private BigDecimal areaForExclusiveUse; + private int dealYear; + private int dealMonth; + private int dealDay; + private int dealAmount; + private int floor; + + public ResponseApartmentPriceDto(long apartmentId, BigDecimal areaForExclusiveUse, int dealYear, int dealMonth, + int dealDay, int dealAmount, int floor) { + this.apartmentId = apartmentId; + this.areaForExclusiveUse = areaForExclusiveUse; + this.dealYear = dealYear; + this.dealMonth = dealMonth; + this.dealDay = dealDay; + this.dealAmount = dealAmount; + this.floor = floor; + } + + public static ResponseApartmentPriceDto from(ApartmentPrice apartmentPrice) { + return new ResponseApartmentPriceDto( + apartmentPrice.getApartmentId(), + apartmentPrice.getAreaForExclusiveUse(), + apartmentPrice.getDealYear(), + apartmentPrice.getDealMonth(), + apartmentPrice.getDealDay(), + apartmentPrice.getDealAmount(), + apartmentPrice.getFloor()); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + ResponseApartmentPriceDto that = (ResponseApartmentPriceDto)o; + return apartmentId == that.apartmentId && dealYear == that.dealYear && dealMonth == that.dealMonth + && dealDay == that.dealDay && dealAmount == that.dealAmount && floor == that.floor + && areaForExclusiveUse.compareTo(that.areaForExclusiveUse) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(apartmentId, areaForExclusiveUse, dealYear, dealMonth, dealDay, dealAmount, floor); + } +} diff --git a/src/main/java/kancho/realestate/comparingprices/domain/dto/response/ResponseApartmentPriceDto.java b/src/main/java/kancho/realestate/comparingprices/domain/dto/response/ResponseApartmentPriceDto.java new file mode 100644 index 0000000..62bb14a --- /dev/null +++ b/src/main/java/kancho/realestate/comparingprices/domain/dto/response/ResponseApartmentPriceDto.java @@ -0,0 +1,60 @@ +package kancho.realestate.comparingprices.domain.dto.response; + +import java.math.BigDecimal; +import java.util.Objects; + +import kancho.realestate.comparingprices.domain.model.ApartmentPrice; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +public class ResponseApartmentPriceDto { + private long apartmentId; + private BigDecimal areaForExclusiveUse; + private int dealYear; + private int dealMonth; + private int dealDay; + private int dealAmount; + private int floor; + + public ResponseApartmentPriceDto(long apartmentId, BigDecimal areaForExclusiveUse, int dealYear, int dealMonth, + int dealDay, int dealAmount, int floor) { + this.apartmentId = apartmentId; + this.areaForExclusiveUse = areaForExclusiveUse; + this.dealYear = dealYear; + this.dealMonth = dealMonth; + this.dealDay = dealDay; + this.dealAmount = dealAmount; + this.floor = floor; + } + + public static ResponseApartmentPriceDto from(ApartmentPrice apartmentPrice) { + return new ResponseApartmentPriceDto( + apartmentPrice.getApartmentId(), + apartmentPrice.getAreaForExclusiveUse(), + apartmentPrice.getDealYear(), + apartmentPrice.getDealMonth(), + apartmentPrice.getDealDay(), + apartmentPrice.getDealAmount(), + apartmentPrice.getFloor()); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + ResponseApartmentPriceDto that = (ResponseApartmentPriceDto)o; + return apartmentId == that.apartmentId && dealYear == that.dealYear && dealMonth == that.dealMonth + && dealDay == that.dealDay && dealAmount == that.dealAmount && floor == that.floor + && areaForExclusiveUse.compareTo(that.areaForExclusiveUse) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(apartmentId, areaForExclusiveUse, dealYear, dealMonth, dealDay, dealAmount, floor); + } +} + diff --git a/src/main/java/kancho/realestate/comparingprices/domain/dto/response/ResponseSoaringPriceDto.java b/src/main/java/kancho/realestate/comparingprices/domain/dto/response/ResponseSoaringPriceDto.java new file mode 100644 index 0000000..59df698 --- /dev/null +++ b/src/main/java/kancho/realestate/comparingprices/domain/dto/response/ResponseSoaringPriceDto.java @@ -0,0 +1,48 @@ +package kancho.realestate.comparingprices.domain.dto.response; + + +import java.time.LocalDate; + +import kancho.realestate.comparingprices.domain.model.SoaringPrice; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +public class ResponseSoaringPriceDto { + private int id; + private long apartmentId; + private String areaForExclusiveUse; + private LocalDate pastDate; + private long pastPrice; + private LocalDate latestDate; + private long latestPrice; + private String priceDifferenceUnit; // PERCENT, WON + private long priceDifference; + + public ResponseSoaringPriceDto(int id, long apartmentId, String areaForExclusiveUse, LocalDate pastDate, + long pastPrice, LocalDate latestDate, long latestPrice, String priceDifferenceUnit, long priceDifference) { + this.id = id; + this.apartmentId = apartmentId; + this.areaForExclusiveUse = areaForExclusiveUse; + this.pastDate = pastDate; + this.pastPrice = pastPrice; + this.latestDate = latestDate; + this.latestPrice = latestPrice; + this.priceDifferenceUnit = priceDifferenceUnit; + this.priceDifference = priceDifference; + } + + public static ResponseSoaringPriceDto from(SoaringPrice soaringPrice) { + return new ResponseSoaringPriceDto( + soaringPrice.getId(), + soaringPrice.getApartment().getId(), + soaringPrice.getAreaForExclusiveUse().toString(), + soaringPrice.getPastDate(), + soaringPrice.getPastPrice(), + soaringPrice.getLatestDate(), + soaringPrice.getLatestPrice(), + soaringPrice.getPriceDifferenceUnit().toString(), + soaringPrice.getPriceDifference()); + } +} diff --git a/src/main/java/kancho/realestate/comparingprices/domain/model/ApartmentPrice.java b/src/main/java/kancho/realestate/comparingprices/domain/model/ApartmentPrice.java index c47a578..34004fd 100644 --- a/src/main/java/kancho/realestate/comparingprices/domain/model/ApartmentPrice.java +++ b/src/main/java/kancho/realestate/comparingprices/domain/model/ApartmentPrice.java @@ -1,6 +1,7 @@ package kancho.realestate.comparingprices.domain.model; import java.math.BigDecimal; +import java.math.RoundingMode; import java.util.Objects; import javax.persistence.Entity; @@ -11,9 +12,11 @@ import kancho.realestate.comparingprices.domain.vo.ApartmentDetail; import kancho.realestate.comparingprices.domain.vo.ApartmentPriceUniqueInfo; import lombok.Getter; +import lombok.ToString; @Getter @Entity +@ToString public class ApartmentPrice extends BaseTimeEntity{ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -46,25 +49,31 @@ public ApartmentPrice(ApartmentDetail detail) { } private int parseDealAmount(String dealAmount) { - dealAmount = dealAmount.trim().replace(",",""); // 공백 제거 - return Integer.parseInt(dealAmount); + dealAmount = dealAmount.trim().replace(",",""); // 공백 제거 + return Integer.parseInt(dealAmount); } public ApartmentPriceUniqueInfo getApartmentPriceUniqueInfo() { return new ApartmentPriceUniqueInfo(this); } + public BigDecimal getAreaForExclusiveUse() { + return BigDecimal.valueOf(areaForExclusiveUse) + .setScale(4, RoundingMode.HALF_EVEN) + .stripTrailingZeros(); + } + @Override public boolean equals(Object o) { if (this == o) return true; - if (!(o instanceof ApartmentPrice)) + if (o == null || getClass() != o.getClass()) return false; ApartmentPrice that = (ApartmentPrice)o; - BigDecimal thisAreaForExclusiveUse = new BigDecimal(that.areaForExclusiveUse); - BigDecimal thatAreaForExclusiveUse = new BigDecimal(areaForExclusiveUse); - + BigDecimal thisAreaForExclusiveUse = this.getAreaForExclusiveUse(); + BigDecimal thatAreaForExclusiveUse = that.getAreaForExclusiveUse(); + return apartmentId == that.apartmentId && thisAreaForExclusiveUse.compareTo(thatAreaForExclusiveUse) == 0 && dealYear == that.dealYear && dealMonth == that.dealMonth && dealDay == that.dealDay && dealAmount == that.dealAmount @@ -73,6 +82,6 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(apartmentId, areaForExclusiveUse, dealYear, dealMonth, dealDay, dealAmount, floor); + return Objects.hash(apartmentId, this.getAreaForExclusiveUse(), dealYear, dealMonth, dealDay, dealAmount, floor); } } diff --git a/src/main/java/kancho/realestate/comparingprices/domain/model/SoaringPrice.java b/src/main/java/kancho/realestate/comparingprices/domain/model/SoaringPrice.java new file mode 100644 index 0000000..8669836 --- /dev/null +++ b/src/main/java/kancho/realestate/comparingprices/domain/model/SoaringPrice.java @@ -0,0 +1,83 @@ +package kancho.realestate.comparingprices.domain.model; + +import java.time.LocalDate; +import java.util.Objects; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Index; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import kancho.realestate.comparingprices.domain.dto.request.RequestSoaringPriceDto; +import lombok.Getter; + +@Getter +@Table(indexes = @Index(name = "idx_created_date", columnList = "createdDate")) +@Entity +public class SoaringPrice extends BaseTimeEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; + + @ManyToOne + private Apartment apartment; + + private String areaForExclusiveUse; + + private LocalDate pastDate; + + private long pastPrice; + + private LocalDate latestDate; + + private long latestPrice; + + private String priceDifferenceUnit; // PERCENT, WON + + private long priceDifference; + + public SoaringPrice(Apartment apartment, String areaForExclusiveUse, LocalDate pastDate, long pastPrice, LocalDate latestDate, + long latestPrice, String priceDifferenceUnit, long priceDifference) { + this.apartment = apartment; + this.areaForExclusiveUse = areaForExclusiveUse; + this.pastDate = pastDate; + this.pastPrice = pastPrice; + this.latestDate = latestDate; + this.latestPrice = latestPrice; + this.priceDifferenceUnit = priceDifferenceUnit; + this.priceDifference = priceDifference; + } + + public enum Unit{ + PERCENT { // 퍼센트(%) + public long calculatePriceDifference(long latestPrice, long pastPrice){ + return latestPrice * 100 / pastPrice - 100; + } + }, + WON{ // 원(₩) + public long calculatePriceDifference(long latestPrice, long pastPrice){ + return latestPrice - pastPrice; + } + }; + + public abstract long calculatePriceDifference(long latestPrice, long pastPrice); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof SoaringPrice)) + return false; + SoaringPrice that = (SoaringPrice)o; + return getId() == that.getId(); + } + + @Override + public int hashCode() { + return Objects.hash(getId()); + } +} diff --git a/src/main/java/kancho/realestate/comparingprices/domain/model/SoaringPrices.java b/src/main/java/kancho/realestate/comparingprices/domain/model/SoaringPrices.java deleted file mode 100644 index 6df8660..0000000 --- a/src/main/java/kancho/realestate/comparingprices/domain/model/SoaringPrices.java +++ /dev/null @@ -1,64 +0,0 @@ -package kancho.realestate.comparingprices.domain.model; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.Objects; - -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Index; -import javax.persistence.ManyToOne; -import javax.persistence.Table; - -import lombok.Getter; - -@Getter -@Table(indexes = @Index(name = "idx_created_date", columnList = "createdDate")) -@Entity -public class SoaringPrices extends BaseTimeEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private int id; - - @ManyToOne - private Apartment apartment; - - private LocalDate pastDate; - - private long pastPrice; - - private LocalDate latestDate; - - private long latestPrice; - - private String priceDifferenceUnit; // PERCENT, WON - - private long priceDifference; - - protected SoaringPrices() { - } - - public enum Unit { - PERCENT, // 퍼센트(%) - WON // 원(₩) - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (!(o instanceof SoaringPrices)) - return false; - SoaringPrices that = (SoaringPrices)o; - return getId() == that.getId(); - } - - @Override - public int hashCode() { - return Objects.hash(getId()); - } -} diff --git a/src/main/java/kancho/realestate/comparingprices/domain/vo/ApartmentDetail.java b/src/main/java/kancho/realestate/comparingprices/domain/vo/ApartmentDetail.java index 3ddea97..7b1c6dd 100644 --- a/src/main/java/kancho/realestate/comparingprices/domain/vo/ApartmentDetail.java +++ b/src/main/java/kancho/realestate/comparingprices/domain/vo/ApartmentDetail.java @@ -50,6 +50,13 @@ public class ApartmentDetail { @XmlElement(name = "층") private int floor; + public boolean isCorrectData(){ + if(regionalCode == null || dong == null || jibun == null || bonbun == null || bubun == null || apartmentName == null || bubun == null) { + return false; + } + return true; + } + public ApartmentPrice getApartmentPrice() { return new ApartmentPrice(this); } diff --git a/src/main/java/kancho/realestate/comparingprices/repository/ApartmentPriceRepository.java b/src/main/java/kancho/realestate/comparingprices/repository/ApartmentPriceRepository.java index 3f4aacc..8f49f9f 100644 --- a/src/main/java/kancho/realestate/comparingprices/repository/ApartmentPriceRepository.java +++ b/src/main/java/kancho/realestate/comparingprices/repository/ApartmentPriceRepository.java @@ -1,8 +1,11 @@ package kancho.realestate.comparingprices.repository; +import java.util.List; + import org.springframework.data.jpa.repository.JpaRepository; import kancho.realestate.comparingprices.domain.model.ApartmentPrice; public interface ApartmentPriceRepository extends JpaRepository { + List findByDealYearBetween(int start, int end); } diff --git a/src/main/java/kancho/realestate/comparingprices/repository/SoaringPricesRepository.java b/src/main/java/kancho/realestate/comparingprices/repository/SoaringPricesRepository.java index 6c62835..d437dae 100644 --- a/src/main/java/kancho/realestate/comparingprices/repository/SoaringPricesRepository.java +++ b/src/main/java/kancho/realestate/comparingprices/repository/SoaringPricesRepository.java @@ -4,11 +4,11 @@ import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; -import kancho.realestate.comparingprices.domain.model.SoaringPrices; +import kancho.realestate.comparingprices.domain.model.SoaringPrice; -public interface SoaringPricesRepository extends JpaRepository { +public interface SoaringPricesRepository extends JpaRepository { // count 쿼리는 필요없으므로 Slice로 받음. jpql에서는 limit 사용 못함. Pageable 사용해야함 - Slice findSoaringPricesByPriceDifferenceUnit(String priceDifferenceUnit, + Slice findSoaringPricesByPriceDifferenceUnit(String priceDifferenceUnit, Pageable pageable); } diff --git a/src/main/java/kancho/realestate/comparingprices/scheduler/ApartmentUpdater.java b/src/main/java/kancho/realestate/comparingprices/scheduler/ApartmentUpdater.java new file mode 100644 index 0000000..066a8a9 --- /dev/null +++ b/src/main/java/kancho/realestate/comparingprices/scheduler/ApartmentUpdater.java @@ -0,0 +1,45 @@ +package kancho.realestate.comparingprices.scheduler; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import javax.xml.bind.JAXBException; + +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import kancho.realestate.comparingprices.domain.dto.request.RequestSoaringPriceDto; +import kancho.realestate.comparingprices.domain.dto.response.ResponseApartmentPriceDto; +import kancho.realestate.comparingprices.domain.model.SoaringPrice; +import kancho.realestate.comparingprices.scheduler.util.ApartmentStorer; +import kancho.realestate.comparingprices.scheduler.util.SoaringPriceStorer; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Component +public class ApartmentUpdater { + + private final ApartmentStorer apartmentStorer; + private final SoaringPriceStorer soaringPriceStorer; + + // 매일 밤 12시 마다 실행 + @Scheduled(cron = "0 0 0 * * *") + public void update() throws JAXBException, IOException, InterruptedException { + updateApartmentInfos(); + updateSoaringPrices(); + } + + private void updateApartmentInfos() throws JAXBException, IOException, InterruptedException { + apartmentStorer.saveAllOfApartmentInfo(1); + } + + private void updateSoaringPrices() { + List apartmentPriceList = soaringPriceStorer.getApartmenstPriceFromLastyear(); + Map latestPriceMap = soaringPriceStorer.getLatestApartmentPrices(apartmentPriceList); + Map cheapestPriceMap = soaringPriceStorer.getCheapestPriceListFromLastYear(apartmentPriceList); + List soaringPriceByWonList = soaringPriceStorer.getSoaringPriceList(latestPriceMap, cheapestPriceMap, SoaringPrice.Unit.WON); + List soaringPriceByPercentList = soaringPriceStorer.getSoaringPriceList(latestPriceMap, cheapestPriceMap, SoaringPrice.Unit.PERCENT); + soaringPriceStorer.saveSoaringPriceList(soaringPriceByWonList, soaringPriceByPercentList); + } +} \ No newline at end of file diff --git a/src/main/java/kancho/realestate/utils/api/storeaprtment/ApartmentApiClient.java b/src/main/java/kancho/realestate/comparingprices/scheduler/util/ApartmentApiClient.java similarity index 76% rename from src/main/java/kancho/realestate/utils/api/storeaprtment/ApartmentApiClient.java rename to src/main/java/kancho/realestate/comparingprices/scheduler/util/ApartmentApiClient.java index f7d9b19..2a3e4f8 100644 --- a/src/main/java/kancho/realestate/utils/api/storeaprtment/ApartmentApiClient.java +++ b/src/main/java/kancho/realestate/comparingprices/scheduler/util/ApartmentApiClient.java @@ -1,4 +1,4 @@ -package kancho.realestate.utils.api.storeaprtment; +package kancho.realestate.comparingprices.scheduler.util; import static java.net.URLEncoder.*; @@ -17,7 +17,7 @@ import kancho.realestate.comparingprices.domain.dto.request.RequestApartmentDetailDto; import kancho.realestate.comparingprices.domain.vo.ApartmentDetail; -import kancho.realestate.utils.api.storeaprtment.mapper.ApartmentXmlMapper; +import kancho.realestate.comparingprices.exception.InvalidApartmentXmlException; @Component public class ApartmentApiClient { @@ -32,14 +32,15 @@ public class ApartmentApiClient { private final String pageNo = "1"; // 페이지번호 private final String numOfRows = "1000"; // 한 페이지 결과 수 - public List getApartmentDetails(RequestApartmentDetailDto requestDto) throws IOException, JAXBException { - URL url = new URL(makeRequestUrl(requestDto)); - String responseAsXmlString = request(url); // 공공 API에 데이터 요청 - return new ApartmentXmlMapper().apartmentXmlToList(responseAsXmlString); + public List getApartmentDetails(RequestApartmentDetailDto requestDto) throws IOException, JAXBException, + InvalidApartmentXmlException { + URL url = new URL(makeRequestUrl(requestDto)); + String responseAsXmlString = request(url); // 공공 API에 데이터 요청 + return new ApartmentXmlMapper().apartmentXmlToList(responseAsXmlString); } private String makeRequestUrl(RequestApartmentDetailDto requestDto) throws UnsupportedEncodingException { - String dealYearAndMonth = Integer.toString(requestDto.getDealYear()) + Integer.toString(requestDto.getDealMonth()); + String dealYearAndMonth = makeDealYearAndMonth(requestDto); StringBuilder urlBuilder = new StringBuilder(); return urlBuilder.append(domainName).append(path) .append("?").append(encode("serviceKey", encoder)).append(accessKey) @@ -50,6 +51,15 @@ private String makeRequestUrl(RequestApartmentDetailDto requestDto) throws Unsup .toString(); } + private String makeDealYearAndMonth(RequestApartmentDetailDto requestDto) { + StringBuilder dealYearAndMonth = new StringBuilder(); + dealYearAndMonth.append(requestDto.getDealYear()); + if (requestDto.getDealMonth() < 10){ + dealYearAndMonth.append(0); + } + return dealYearAndMonth.append(requestDto.getDealMonth()).toString(); + } + private String request(URL url) throws IOException { HttpURLConnection httpURLConnection = getHttpURLConnection(url); String response = getHttpResponse(httpURLConnection); @@ -75,4 +85,4 @@ private String getHttpResponse(HttpURLConnection httpURLConnection) throws IOExc return response.toString(); } -} +} \ No newline at end of file diff --git a/src/main/java/kancho/realestate/utils/api/storeaprtment/ApartmentInfoStore.java b/src/main/java/kancho/realestate/comparingprices/scheduler/util/ApartmentStorer.java similarity index 60% rename from src/main/java/kancho/realestate/utils/api/storeaprtment/ApartmentInfoStore.java rename to src/main/java/kancho/realestate/comparingprices/scheduler/util/ApartmentStorer.java index 7fd317b..13c9a92 100644 --- a/src/main/java/kancho/realestate/utils/api/storeaprtment/ApartmentInfoStore.java +++ b/src/main/java/kancho/realestate/comparingprices/scheduler/util/ApartmentStorer.java @@ -1,4 +1,4 @@ -package kancho.realestate.utils.api.storeaprtment; +package kancho.realestate.comparingprices.scheduler.util; import java.io.IOException; import java.time.LocalDateTime; @@ -10,8 +10,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.boot.ApplicationArguments; -import org.springframework.boot.ApplicationRunner; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; @@ -22,65 +21,69 @@ import kancho.realestate.comparingprices.domain.vo.ApartmentPriceUniqueInfo; import kancho.realestate.comparingprices.domain.vo.ApartmentUniqueInfo; import kancho.realestate.comparingprices.domain.vo.Gu; -import kancho.realestate.utils.api.storeaprtment.service.ApartmentApiService; -import lombok.RequiredArgsConstructor; +import kancho.realestate.comparingprices.exception.InvalidApartmentXmlException; +import kancho.realestate.comparingprices.service.ApartmentService; -@Profile("!test") // 테스트시 실행 x -@RequiredArgsConstructor @Component -public class ApartmentInfoStore implements ApplicationRunner { +public class ApartmentStorer { private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final ApartmentApiClient apartmentApiClient; - private final ApartmentApiService apartmentApiService; + private final ApartmentService apartmentService; - private final int numOfMonth = 1; // 데이터로 가져올 개월 수 + private Map existingApartmentData; + private Map existingApartmentPriceData; - Map existingApartmentData; - Map existingApartmentPriceData; - - @Override - public void run(ApplicationArguments args) throws Exception { + public ApartmentStorer(ApartmentApiClient apartmentApiClient, ApartmentService apartmentService) { + this.apartmentApiClient = apartmentApiClient; + this.apartmentService = apartmentService; // 데이터베이스에 저장된 기존 아파트, 아파트 가격 정보 받아오기 existingApartmentData = getExistingApartments(); existingApartmentPriceData = getExistingApartmentPrices(); - // 과거 부터 현재까지 모든 아파트 정보 데이터 요청하여 저장하기 - saveAllOfApartmentInfo(); } - private void saveAllOfApartmentInfo() throws JAXBException, IOException, InterruptedException { + /* + * @param numOfMonth: 데이터를 가져올 개월수 + */ + public void saveAllOfApartmentInfo(int numOfMonth) throws JAXBException, IOException, InterruptedException { // 오늘 날짜 정보 가져오기 LocalDateTime now = LocalDateTime.now(); // 각 월 마다 거래된 구별 아파트 정보 가져오기 - for (int minusMonths = 0; minusMonths < numOfMonth; minusMonths++) { + for (int minusMonths = numOfMonth - 1; minusMonths >= 0; minusMonths--) { for (Gu gu : Gu.values()) { - int dealYear = now.minusMonths(numOfMonth).getYear(); - int dealMonth = now.minusMonths(numOfMonth).getMonthValue(); - List details = apartmentApiClient. - getApartmentDetails(new RequestApartmentDetailDto(gu.getReginalCode(), dealYear, dealMonth)); // 공공 데이터 api로 데이터를 받아서 - saveApartmentDetails(details); // 데이터베이스에 저장 - Thread.sleep(2000); + int dealYear = now.minusMonths(minusMonths).getYear(); + int dealMonth = now.minusMonths(minusMonths).getMonthValue(); + try { + List details = apartmentApiClient. + getApartmentDetails(new RequestApartmentDetailDto(gu.getReginalCode(), dealYear, dealMonth)); // 공공 데이터 api로 데이터를 받아서 + saveApartmentDetails(details); // 데이터베이스에 저장 + Thread.sleep(2000); + } catch (InvalidApartmentXmlException e) { + logger.info("아파트 정보 파싱 실패 = {}", e); + } } } } private void saveApartmentDetails(List details) { for (ApartmentDetail apartmentDetail : details) { - // 아파트 정보 저장 - long apartmentId = saveApartment(apartmentDetail); - apartmentDetail.setApartmentId(apartmentId); - // 아파트 가격 정보 저장 - saveApartmentPrice(apartmentDetail); + if (apartmentDetail.isCorrectData()){ + // 아파트 정보 저장 + long apartmentId = saveApartment(apartmentDetail); + apartmentDetail.setApartmentId(apartmentId); + // 아파트 가격 정보 저장 + saveApartmentPrice(apartmentDetail); + } } } private Map getExistingApartments() { - return apartmentApiService.findAllApartments().stream() // 기존 아파트 정보를 찾아서, + return apartmentService.findAllApartments().stream() // 기존 아파트 정보를 찾아서, .collect(Collectors.toMap(Apartment::getApartmentUniqueInfo, Apartment::getId)); // Map 자료구조로 매핑 } private Map getExistingApartmentPrices() { - return apartmentApiService.findAllApartmentsPrice().stream() // 기존 아파트 가격 정보를 찾아서, + return apartmentService.findAllApartmentsPrice().stream() // 기존 아파트 가격 정보를 찾아서, .collect( Collectors.toMap(ApartmentPrice::getApartmentPriceUniqueInfo, ApartmentPrice::getId)); // Map 자료구조로 매핑 } @@ -91,14 +94,14 @@ private long saveApartment(ApartmentDetail apartmentDetail) { Apartment apartment = apartmentDetail.getApartment(); if (isNew(existingApartmentData, apartment)) { - apartmentId = apartmentApiService.save(apartment).getId(); + Apartment saved = apartmentService.save(apartment); + apartmentId = saved.getId(); existingApartmentData.put(new ApartmentUniqueInfo(apartment), apartmentId); logger.info("아파트 정보 저장 성공 = {}", apartment); } else { apartmentId = existingApartmentData.get(new ApartmentUniqueInfo(apartment)); logger.info("아파트 정보 저장 실패, 중복된 데이터 = {}", apartment); } - return apartmentId; } @@ -106,8 +109,8 @@ private void saveApartmentPrice(ApartmentDetail apartmentDetail) { ApartmentPrice apartmentPrice = apartmentDetail.getApartmentPrice(); if (isNew(existingApartmentPriceData, apartmentPrice)) { - long id = apartmentApiService.save(apartmentPrice).getApartmentId(); - existingApartmentPriceData.put(new ApartmentPriceUniqueInfo(apartmentPrice), id); + ApartmentPrice saved = apartmentService.save(apartmentPrice); + existingApartmentPriceData.put(new ApartmentPriceUniqueInfo(apartmentPrice), saved.getId()); logger.info("아파트 가격 정보 저장 성공 = {}", apartmentPrice); } else { logger.info("아파트 가격 정보 저장 실패, 중복된 데이터 = {}", apartmentPrice); diff --git a/src/main/java/kancho/realestate/utils/api/storeaprtment/mapper/ApartmentXmlMapper.java b/src/main/java/kancho/realestate/comparingprices/scheduler/util/ApartmentXmlMapper.java similarity index 96% rename from src/main/java/kancho/realestate/utils/api/storeaprtment/mapper/ApartmentXmlMapper.java rename to src/main/java/kancho/realestate/comparingprices/scheduler/util/ApartmentXmlMapper.java index b69c3ab..1095a8b 100644 --- a/src/main/java/kancho/realestate/utils/api/storeaprtment/mapper/ApartmentXmlMapper.java +++ b/src/main/java/kancho/realestate/comparingprices/scheduler/util/ApartmentXmlMapper.java @@ -1,4 +1,4 @@ -package kancho.realestate.utils.api.storeaprtment.mapper; +package kancho.realestate.comparingprices.scheduler.util; import java.io.StringReader; import java.util.List; diff --git a/src/main/java/kancho/realestate/comparingprices/scheduler/util/SoaringPriceStorer.java b/src/main/java/kancho/realestate/comparingprices/scheduler/util/SoaringPriceStorer.java new file mode 100644 index 0000000..15c043c --- /dev/null +++ b/src/main/java/kancho/realestate/comparingprices/scheduler/util/SoaringPriceStorer.java @@ -0,0 +1,129 @@ +package kancho.realestate.comparingprices.scheduler.util; + + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.stereotype.Component; + +import kancho.realestate.comparingprices.domain.dto.request.RequestSoaringPriceDto; +import kancho.realestate.comparingprices.domain.dto.response.ResponseApartmentPriceDto; +import kancho.realestate.comparingprices.domain.model.SoaringPrice; +import kancho.realestate.comparingprices.service.ApartmentService; +import kancho.realestate.comparingprices.service.SoaringPriceService; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class SoaringPriceStorer { + private final ApartmentService apartmentService; + private final SoaringPriceService soaringPriceService; + + public List getApartmenstPriceFromLastyear() { + int end = LocalDateTime.now().getYear(); + int start = end - 1; + return apartmentService.findApartmentPriceByDealYearBetween(start, end); + } + + public Map getLatestApartmentPrices( + List apartmentPriceList) { + var sortedApartmentPriceList = getSortedByDateApartmentPriceList(apartmentPriceList); + var latestPriceMap = new HashMap(); + for (ResponseApartmentPriceDto apartmentPrice : sortedApartmentPriceList) { + latestPriceMap.put(makeApartmentUniqueId(apartmentPrice), apartmentPrice); + } + return latestPriceMap; + } + + // 계약일의 역순으로 정렬 + private List getSortedByDateApartmentPriceList( + List apartmentPriceList) { + apartmentPriceList.sort((a,b) -> { + LocalDate localDateA = LocalDate.of(a.getDealYear(), a.getDealMonth(), a.getDealDay()); + LocalDate localDateB = LocalDate.of(b.getDealYear(), b.getDealMonth(), b.getDealDay()); + return localDateA.compareTo(localDateB); + }); + return apartmentPriceList; + } + + public Map getCheapestPriceListFromLastYear( + List apartmentPriceList) { + var cheapestPriceMap = new HashMap(); + int lastYear = LocalDateTime.now().getYear() - 1; + for (ResponseApartmentPriceDto apartmentPrice : apartmentPriceList) { + // 작년에 거래되었고, + if (apartmentPrice.getDealYear() != lastYear) + continue; + // 가장 낮은 금액으로 거래된 매물만 가져오기 + String id = makeApartmentUniqueId(apartmentPrice); + if (cheapestPriceMap.get(id) == null) { + cheapestPriceMap.put(id, apartmentPrice); + } else { + ResponseApartmentPriceDto saved = cheapestPriceMap.get(id); + if (saved.getDealAmount() > apartmentPrice.getDealAmount()) { + cheapestPriceMap.put(id, apartmentPrice); + } + } + } + return cheapestPriceMap; + } + + public List getSoaringPriceList(Map latestPriceMap, + Map cheapestPriceMap, SoaringPrice.Unit soaringPriceUnit) { + var soaringPriceList = makeSoaringPriceList(latestPriceMap, cheapestPriceMap, soaringPriceUnit); + return filterTopLevel(soaringPriceList); // 급상승 가격정보 중 최상위 10개만 추출하여 반환한다. + } + + public List saveSoaringPriceList(List soaringPriceByWonList, List soaringPriceByPercentList) { + List savedWonList = soaringPriceService.save(soaringPriceByWonList); + List savedPercentList = soaringPriceService.save(soaringPriceByPercentList); + savedWonList.addAll(savedPercentList); + return savedWonList; + } + + private String makeApartmentUniqueId(ResponseApartmentPriceDto apartmentPrice) { + StringBuilder sb = new StringBuilder(); + return sb.append(apartmentPrice.getApartmentId()) + .append("-") + .append(apartmentPrice.getAreaForExclusiveUse()) + .toString(); + } + + private List filterTopLevel(List soaringPriceList) { + soaringPriceList.sort((a,b)->(int)(b.getPriceDifference() - a.getPriceDifference())); + if (soaringPriceList.size() > 10) { + return soaringPriceList.subList(0, 10); + } + return soaringPriceList; + } + + private List makeSoaringPriceList(Map latestPriceMap, + Map cheapestPriceMap, SoaringPrice.Unit soaringPriceUnit) { + var soaringPriceList = new ArrayList(); + for (Map.Entry latestPriceEntry : latestPriceMap.entrySet()) { + String id = latestPriceEntry.getKey(); + ResponseApartmentPriceDto latestPrice = latestPriceEntry.getValue(); + if (cheapestPriceMap.containsKey(id)) { + ResponseApartmentPriceDto cheapestPrice = cheapestPriceMap.get(id); + soaringPriceList.add(makeSoaringPrice(latestPrice, cheapestPrice, soaringPriceUnit)); + } + } + return soaringPriceList; + } + + private RequestSoaringPriceDto makeSoaringPrice(ResponseApartmentPriceDto latestPrice, + ResponseApartmentPriceDto pastPrice, SoaringPrice.Unit unit) { + return new RequestSoaringPriceDto( + pastPrice.getApartmentId(), pastPrice.getAreaForExclusiveUse().toString(), + LocalDate.of(pastPrice.getDealYear(), pastPrice.getDealMonth(), pastPrice.getDealDay()), + pastPrice.getDealAmount(), + LocalDate.of(latestPrice.getDealYear(), latestPrice.getDealMonth(), latestPrice.getDealDay()), + latestPrice.getDealAmount(), + unit, unit.calculatePriceDifference(latestPrice.getDealAmount(), pastPrice.getDealAmount()) + ); + } +} diff --git a/src/main/java/kancho/realestate/comparingprices/service/ApartmentService.java b/src/main/java/kancho/realestate/comparingprices/service/ApartmentService.java index 4d1b5f0..4b4edbd 100644 --- a/src/main/java/kancho/realestate/comparingprices/service/ApartmentService.java +++ b/src/main/java/kancho/realestate/comparingprices/service/ApartmentService.java @@ -9,7 +9,10 @@ import kancho.realestate.comparingprices.domain.dto.request.RequestApartmentDto; import kancho.realestate.comparingprices.domain.dto.response.ResponseApartmentDto; +import kancho.realestate.comparingprices.domain.dto.response.ResponseApartmentPriceDto; import kancho.realestate.comparingprices.domain.model.Apartment; +import kancho.realestate.comparingprices.domain.model.ApartmentPrice; +import kancho.realestate.comparingprices.repository.ApartmentPriceRepository; import kancho.realestate.comparingprices.repository.ApartmentRepository; import lombok.RequiredArgsConstructor; @@ -19,6 +22,15 @@ public class ApartmentService { private final ApartmentRepository apartmentRepository; + private final ApartmentPriceRepository apartmentPriceRepository; + + public List findAllApartments() { + return apartmentRepository.findAll(); + } + + public List findAllApartmentsPrice() { + return apartmentPriceRepository.findAll(); + } public List findApartmentDtosWithPaging(Pageable pageable) { return apartmentRepository.findAll(pageable) @@ -27,6 +39,13 @@ public List findApartmentDtosWithPaging(Pageable pageable) .collect(Collectors.toList()); } + public List findApartmentPriceByDealYearBetween(int start, int end) { + return apartmentPriceRepository.findByDealYearBetween(start, end) + .stream() + .map(ResponseApartmentPriceDto::from) + .collect(Collectors.toList()); + } + public List findApartmentDtos() { return apartmentRepository.findAll() .stream() @@ -40,4 +59,15 @@ public ResponseApartmentDto save(RequestApartmentDto requestApartmentDto) { apartmentRepository.save(apartment); return ResponseApartmentDto.from(apartment); } + + @Transactional + public Apartment save(Apartment apartment) { + return apartmentRepository.save(apartment); + } + + @Transactional + public ApartmentPrice save(ApartmentPrice apartmentPrice) { + return apartmentPriceRepository.save(apartmentPrice); + } + } diff --git a/src/main/java/kancho/realestate/comparingprices/service/SoaringPriceService.java b/src/main/java/kancho/realestate/comparingprices/service/SoaringPriceService.java new file mode 100644 index 0000000..05d8907 --- /dev/null +++ b/src/main/java/kancho/realestate/comparingprices/service/SoaringPriceService.java @@ -0,0 +1,62 @@ +package kancho.realestate.comparingprices.service; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; + +import kancho.realestate.comparingprices.domain.dto.request.RequestSoaringPriceDto; +import kancho.realestate.comparingprices.domain.model.Apartment; +import kancho.realestate.comparingprices.domain.model.ApartmentPrice; +import kancho.realestate.comparingprices.domain.model.SoaringPrice; +import kancho.realestate.comparingprices.repository.ApartmentPriceRepository; +import kancho.realestate.comparingprices.repository.ApartmentRepository; +import kancho.realestate.comparingprices.repository.SoaringPricesRepository; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class SoaringPriceService { + + private final SoaringPricesRepository soaringPricesRepository; + private final ApartmentRepository apartmentRepository; + + // 매일 12시에 새로 상위 10개를 insert + private static final PageRequest pageRequest = PageRequest.of(0, 10, Sort.by(Sort.Direction.DESC, "createdDate")); + + // 금액 상승률이 가장 높은 부동산 정보 가져오기 + public List getSoaringPrices(SoaringPrice.Unit priceDifferenceUnit) { + return soaringPricesRepository.findSoaringPricesByPriceDifferenceUnit(priceDifferenceUnit.name(), + pageRequest).getContent(); + } + + public List save(List requestSoaringPriceDtoList) { + // 아파트 객체 리스트 가져오기 + List apartmentIds = getApartmentIds(requestSoaringPriceDtoList); + List apartmentList = apartmentRepository.findAllById(apartmentIds); + // 아파트 객체 리스트와 Dto 리스트로 SoaringPrice 객체 리스트 생성 + List soaringPriceList = getSoaringPrices(apartmentList, requestSoaringPriceDtoList); + // 저장 + return soaringPricesRepository.saveAll(soaringPriceList); + } + + private List getApartmentIds(List requestSoaringPriceDtoList) { + return requestSoaringPriceDtoList.stream() + .map(dto -> dto.getApartmentId()) + .collect(Collectors.toList()); + } + + private List getSoaringPrices(List apartmentList, List requestSoaringPriceDtoList) { + List soaringPriceList = new ArrayList<>(); + for (int i = 0; i < apartmentList.size(); i++) { + RequestSoaringPriceDto dto = requestSoaringPriceDtoList.get(i); + SoaringPrice soaringPrice = new SoaringPrice(apartmentList.get(i), dto.getAreaForExclusiveUse(), dto.getPastDate(), dto.getPastPrice(), dto.getLatestDate(), dto.getLatestPrice(), dto.getPriceDifferenceUnit().toString(), dto.getPriceDifference()); + soaringPriceList.add(soaringPrice); + } + return soaringPriceList; + } + +} diff --git a/src/main/java/kancho/realestate/comparingprices/service/SoaringPricesService.java b/src/main/java/kancho/realestate/comparingprices/service/SoaringPricesService.java deleted file mode 100644 index f9c3a99..0000000 --- a/src/main/java/kancho/realestate/comparingprices/service/SoaringPricesService.java +++ /dev/null @@ -1,26 +0,0 @@ -package kancho.realestate.comparingprices.service; - -import java.util.List; - -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Sort; -import org.springframework.stereotype.Service; - -import kancho.realestate.comparingprices.domain.model.SoaringPrices; -import kancho.realestate.comparingprices.repository.SoaringPricesRepository; -import lombok.RequiredArgsConstructor; - -@Service -@RequiredArgsConstructor -public class SoaringPricesService { - - private final SoaringPricesRepository soaringPricesRepository; - - // 매일 12시에 새로 상위 10개를 insert - private static final PageRequest pageRequest = PageRequest.of(0, 10, Sort.by(Sort.Direction.DESC, "createdDate")); - // 금액 상승률이 가장 높은 부동산 정보 가져오기 - public List getSoaringPrices(SoaringPrices.Unit priceDifferenceUnit) { - return soaringPricesRepository.findSoaringPricesByPriceDifferenceUnit(priceDifferenceUnit.name(), - pageRequest).getContent(); - } -} diff --git a/src/main/java/kancho/realestate/utils/api/StorePricesMain.java b/src/main/java/kancho/realestate/utils/api/StorePricesMain.java index 7f749c8..70b7dc3 100644 --- a/src/main/java/kancho/realestate/utils/api/StorePricesMain.java +++ b/src/main/java/kancho/realestate/utils/api/StorePricesMain.java @@ -4,7 +4,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; -@SpringBootApplication(scanBasePackages = {"kancho.realestate"}) +@SpringBootApplication(scanBasePackages = {"kancho.realestate.comparingprices","kancho.realestate.utils.api" }) public class StorePricesMain { public static void main(String[] args){ diff --git a/src/main/java/kancho/realestate/utils/api/storeaprtment/ApartmentStoreRunner.java b/src/main/java/kancho/realestate/utils/api/storeaprtment/ApartmentStoreRunner.java new file mode 100644 index 0000000..e5b281f --- /dev/null +++ b/src/main/java/kancho/realestate/utils/api/storeaprtment/ApartmentStoreRunner.java @@ -0,0 +1,24 @@ +package kancho.realestate.utils.api.storeaprtment; + +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +import kancho.realestate.comparingprices.scheduler.util.ApartmentStorer; +import lombok.RequiredArgsConstructor; + +@Profile("!test") // 테스트시 실행 x +@Component +@RequiredArgsConstructor +public class ApartmentStoreRunner implements ApplicationRunner { + + private final ApartmentStorer apartmentStorer; + + @Override + public void run(ApplicationArguments args) throws Exception { + // 저장할 아파트 정보의 개월 수를 매개변수로 호출 ex) 3 -> 최근 3개월 아파트 거래건들 저장 + apartmentStorer.saveAllOfApartmentInfo(1); + } +} + diff --git a/src/main/java/kancho/realestate/utils/api/storeaprtment/service/ApartmentApiService.java b/src/main/java/kancho/realestate/utils/api/storeaprtment/service/ApartmentApiService.java deleted file mode 100644 index 8ac6c3a..0000000 --- a/src/main/java/kancho/realestate/utils/api/storeaprtment/service/ApartmentApiService.java +++ /dev/null @@ -1,34 +0,0 @@ -package kancho.realestate.utils.api.storeaprtment.service; - -import java.util.List; - -import org.springframework.stereotype.Service; - -import kancho.realestate.comparingprices.domain.model.Apartment; -import kancho.realestate.comparingprices.domain.model.ApartmentPrice; -import kancho.realestate.comparingprices.repository.ApartmentPriceRepository; -import kancho.realestate.comparingprices.repository.ApartmentRepository; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -@Service -public class ApartmentApiService { - private final ApartmentRepository apartmentRepository; - private final ApartmentPriceRepository apartmentPriceRepository; - - public List findAllApartments() { - return apartmentRepository.findAll(); - } - - public List findAllApartmentsPrice() { - return apartmentPriceRepository.findAll(); - } - - public Apartment save(Apartment apartment) { - return apartmentRepository.save(apartment); - } - - public ApartmentPrice save(ApartmentPrice apartmentPrice) { - return apartmentPriceRepository.save(apartmentPrice); - } -} diff --git a/src/test/java/kancho/realestate/utils/api/storeaprtment/model/GuTest.java b/src/test/java/kancho/realestate/comparingprices/domain/vo/GuTest.java similarity index 91% rename from src/test/java/kancho/realestate/utils/api/storeaprtment/model/GuTest.java rename to src/test/java/kancho/realestate/comparingprices/domain/vo/GuTest.java index d158183..b16eb56 100644 --- a/src/test/java/kancho/realestate/utils/api/storeaprtment/model/GuTest.java +++ b/src/test/java/kancho/realestate/comparingprices/domain/vo/GuTest.java @@ -1,4 +1,4 @@ -package kancho.realestate.utils.api.storeaprtment.model; +package kancho.realestate.comparingprices.domain.vo; import static org.assertj.core.api.Assertions.*; import org.junit.jupiter.api.Test; diff --git a/src/test/java/kancho/realestate/utils/api/storeaprtment/ApartmentApiClientTest.java b/src/test/java/kancho/realestate/comparingprices/scheduler/util/ApartmentApiClientTest.java similarity index 95% rename from src/test/java/kancho/realestate/utils/api/storeaprtment/ApartmentApiClientTest.java rename to src/test/java/kancho/realestate/comparingprices/scheduler/util/ApartmentApiClientTest.java index b9f8bdc..ca6cb25 100644 --- a/src/test/java/kancho/realestate/utils/api/storeaprtment/ApartmentApiClientTest.java +++ b/src/test/java/kancho/realestate/comparingprices/scheduler/util/ApartmentApiClientTest.java @@ -1,4 +1,4 @@ -package kancho.realestate.utils.api.storeaprtment; +package kancho.realestate.comparingprices.scheduler.util; import static org.assertj.core.api.Assertions.*; @@ -16,8 +16,8 @@ import kancho.realestate.comparingprices.domain.vo.ApartmentDetail; import kancho.realestate.comparingprices.exception.InvalidDealYearAndMonthException; import kancho.realestate.comparingprices.exception.InvalidRegionalCodeException; +import kancho.realestate.comparingprices.scheduler.util.ApartmentApiClient; -@ActiveProfiles("test") @SpringBootTest class ApartmentApiClientTest { diff --git a/src/test/java/kancho/realestate/utils/api/storeaprtment/ApartmentXmlMapperTest.java b/src/test/java/kancho/realestate/comparingprices/scheduler/util/ApartmentXmlMapperTest.java similarity index 90% rename from src/test/java/kancho/realestate/utils/api/storeaprtment/ApartmentXmlMapperTest.java rename to src/test/java/kancho/realestate/comparingprices/scheduler/util/ApartmentXmlMapperTest.java index 5d28ba1..72f92e0 100644 --- a/src/test/java/kancho/realestate/utils/api/storeaprtment/ApartmentXmlMapperTest.java +++ b/src/test/java/kancho/realestate/comparingprices/scheduler/util/ApartmentXmlMapperTest.java @@ -1,4 +1,4 @@ -package kancho.realestate.utils.api.storeaprtment; +package kancho.realestate.comparingprices.scheduler.util; import java.util.List; import javax.xml.bind.JAXBException; @@ -8,7 +8,7 @@ import kancho.realestate.comparingprices.domain.vo.ApartmentDetail; import kancho.realestate.comparingprices.exception.InvalidApartmentXmlException; -import kancho.realestate.utils.api.storeaprtment.mapper.ApartmentXmlMapper; +import kancho.realestate.comparingprices.scheduler.util.ApartmentXmlMapper; class ApartmentXmlMapperTest { private final ApartmentXmlMapper apartmentXmlMapper = new ApartmentXmlMapper(); diff --git a/src/test/java/kancho/realestate/comparingprices/scheduler/util/SoaringPriceStorerTest.java b/src/test/java/kancho/realestate/comparingprices/scheduler/util/SoaringPriceStorerTest.java new file mode 100644 index 0000000..7d1f50e --- /dev/null +++ b/src/test/java/kancho/realestate/comparingprices/scheduler/util/SoaringPriceStorerTest.java @@ -0,0 +1,151 @@ +package kancho.realestate.comparingprices.scheduler.util; + +import static org.assertj.core.api.Assertions.*; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.IntStream; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +import kancho.realestate.comparingprices.domain.dto.request.RequestSoaringPriceDto; +import kancho.realestate.comparingprices.domain.dto.response.ResponseApartmentPriceDto; +import kancho.realestate.comparingprices.domain.model.SoaringPrice; + +@SpringBootTest +@Transactional +class SoaringPriceStorerTest { + + @Autowired + SoaringPriceStorer soaringPriceStorer; + + static ResponseApartmentPriceDto latestApartmnetPrice; + static ResponseApartmentPriceDto oldApartmentPrice; + static ResponseApartmentPriceDto otherLatestApartmnetPrice; + + static ResponseApartmentPriceDto cheapestApartmentPriceFromLastYear; + static ResponseApartmentPriceDto expensiveApartmentPriceFromLastYear; + + static List apartmentPriceList; + + static Map latestPriceMap; + static Map cheapestPriceMap; + + @BeforeAll + public static void setUp() { + int dealYear = LocalDate.now().getYear(); + latestApartmnetPrice = new ResponseApartmentPriceDto(1, new BigDecimal("1.1"), dealYear, 12, 31, 100, 10); + oldApartmentPrice = new ResponseApartmentPriceDto(1, new BigDecimal("1.1"), dealYear, 11, 30, 100, 10); + + cheapestApartmentPriceFromLastYear = new ResponseApartmentPriceDto(1, new BigDecimal("1.1"), dealYear - 1, 11, + 20, 1, 10); + expensiveApartmentPriceFromLastYear = new ResponseApartmentPriceDto(1, new BigDecimal("1.1"), dealYear - 1, 11, + 30, 999, 10); + + apartmentPriceList = new ArrayList<>(); + apartmentPriceList.add(latestApartmnetPrice); + apartmentPriceList.add(oldApartmentPrice); + apartmentPriceList.add(cheapestApartmentPriceFromLastYear); + apartmentPriceList.add(expensiveApartmentPriceFromLastYear); + + otherLatestApartmnetPrice = new ResponseApartmentPriceDto(1, new BigDecimal("44.4"), dealYear - 1, 11, 30, 999, + 10); + + latestPriceMap = new HashMap<>(); + latestPriceMap.put(makeApartmentUniqueId(latestApartmnetPrice), latestApartmnetPrice); + latestPriceMap.put(makeApartmentUniqueId(otherLatestApartmnetPrice), otherLatestApartmnetPrice); + + cheapestPriceMap = new HashMap<>(); + cheapestPriceMap.put(makeApartmentUniqueId(cheapestApartmentPriceFromLastYear), + cheapestApartmentPriceFromLastYear); + } + + private static String makeApartmentUniqueId(ResponseApartmentPriceDto apartmentPrice) { + StringBuilder sb = new StringBuilder(); + return sb.append(apartmentPrice.getApartmentId()) + .append("-") + .append(apartmentPrice.getAreaForExclusiveUse()) + .toString(); + } + + @Test + void 작년부터_올해까지_가격_반환() { + List apartmentPriceList = soaringPriceStorer.getApartmenstPriceFromLastyear(); + assertThat(apartmentPriceList).isNotEmpty(); + } + + @Test + void 최신_가격을_추출하면_최신_가격이_포함() { + Map latestPriceMap = soaringPriceStorer.getLatestApartmentPrices( + apartmentPriceList); + String latestPriceId = makeApartmentUniqueId(latestApartmnetPrice); + assertThat(latestPriceMap).contains(entry(latestPriceId, latestApartmnetPrice)); + } + + @Test + void 최저가_가격을_추출하면_최저가_가격이_포함() { + Map cheapestPriceMap = soaringPriceStorer.getCheapestPriceListFromLastYear( + apartmentPriceList); + String cheapestPriceId = makeApartmentUniqueId(cheapestApartmentPriceFromLastYear); + assertThat(cheapestPriceMap).contains(entry(cheapestPriceId, cheapestApartmentPriceFromLastYear)); + } + + @Test + void 최저가_가격을_추출하면_최저가_보다_높은_가격은_불포함() { + Map cheapestPriceMap = soaringPriceStorer.getCheapestPriceListFromLastYear( + apartmentPriceList); + String expensivePriceId = makeApartmentUniqueId(expensiveApartmentPriceFromLastYear); + assertThat(cheapestPriceMap).doesNotContain(entry(expensivePriceId, expensiveApartmentPriceFromLastYear)); + } + + @Test + void 가격_상승률을_구하면_최고_상승률_10가지만_반환() { + //given + IntStream.rangeClosed(1, 10).forEach(i -> { + ResponseApartmentPriceDto latestPriceDto = new ResponseApartmentPriceDto(i, new BigDecimal("1.1"), 2022, 1, + i + 1, 1000, 10); + ResponseApartmentPriceDto cheapestPriceDto = new ResponseApartmentPriceDto(i, new BigDecimal("1.1"), 2022, + 1, i, 10, 10); + latestPriceMap.put(makeApartmentUniqueId(latestPriceDto), latestPriceDto); + cheapestPriceMap.put(makeApartmentUniqueId(cheapestPriceDto), cheapestPriceDto); + }); + + //when + List soaringPriceByWonList = soaringPriceStorer.getSoaringPriceList(latestPriceMap, + cheapestPriceMap, SoaringPrice.Unit.WON); + List soaringPriceByPercentList = soaringPriceStorer.getSoaringPriceList(latestPriceMap, + cheapestPriceMap, SoaringPrice.Unit.PERCENT); + + //then + assertThat(soaringPriceByWonList.size()). + isEqualTo(soaringPriceByPercentList.size()) + .isEqualTo(10); + } + + @Test + void 급상승_가격정보를_저장하면_저장한_데이터_수만큼_반환() { + //given + List soaringPriceByWonList = new ArrayList<>(); + List soaringPriceByPercentList = new ArrayList<>(); + IntStream.rangeClosed(1, 10).forEach(i -> { + soaringPriceByWonList.add(new RequestSoaringPriceDto(i, "1,1", LocalDate.of(2022, 1, 24), + 100, LocalDate.of(2022, 1, 25), 120, SoaringPrice.Unit.WON, 20)); + soaringPriceByPercentList.add(new RequestSoaringPriceDto(i, "1,1", LocalDate.of(2022, 1, 24), + 100, LocalDate.of(2022, 1, 25), 120, SoaringPrice.Unit.PERCENT, 120)); + }); + + //when + List saved = soaringPriceStorer.saveSoaringPriceList(soaringPriceByWonList, soaringPriceByPercentList); + + //then + assertThat(saved.size()).isEqualTo(20); + } +} diff --git a/src/test/java/kancho/realestate/utils/api/storeaprtment/service/ApartmentApiServiceTest.java b/src/test/java/kancho/realestate/comparingprices/service/ApartmentServiceTest.java similarity index 87% rename from src/test/java/kancho/realestate/utils/api/storeaprtment/service/ApartmentApiServiceTest.java rename to src/test/java/kancho/realestate/comparingprices/service/ApartmentServiceTest.java index 6549e07..18b8f7d 100644 --- a/src/test/java/kancho/realestate/utils/api/storeaprtment/service/ApartmentApiServiceTest.java +++ b/src/test/java/kancho/realestate/comparingprices/service/ApartmentServiceTest.java @@ -1,4 +1,4 @@ -package kancho.realestate.utils.api.storeaprtment.service; +package kancho.realestate.comparingprices.service; import static org.assertj.core.api.Assertions.*; @@ -12,14 +12,15 @@ import kancho.realestate.comparingprices.domain.model.Apartment; import kancho.realestate.comparingprices.domain.model.ApartmentPrice; +import kancho.realestate.comparingprices.service.ApartmentService; @ActiveProfiles("test") @SpringBootTest @Transactional -class ApartmentApiServiceTest { +class ApartmentServiceTest { @Autowired - ApartmentApiService apartmentService; + ApartmentService apartmentService; @Test void 모든_아파트_정보를_조회하면_값이_존재함() { diff --git a/src/test/java/kancho/realestate/comparingprices/service/SoaringPriceServiceTest.java b/src/test/java/kancho/realestate/comparingprices/service/SoaringPriceServiceTest.java new file mode 100644 index 0000000..603726d --- /dev/null +++ b/src/test/java/kancho/realestate/comparingprices/service/SoaringPriceServiceTest.java @@ -0,0 +1,27 @@ +package kancho.realestate.comparingprices.service; + +import static org.assertj.core.api.Assertions.*; + +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import kancho.realestate.comparingprices.domain.model.SoaringPrice; + +class SoaringPriceServiceTest extends ServiceTest { + + @Autowired + SoaringPriceService soaringPriceService; + + @Test + void 상승률이_높은_부동산_조회(){ + List soaringPriceList = soaringPriceService.getSoaringPrices(SoaringPrice.Unit.PERCENT); + assertThat(soaringPriceList).isNotNull(); // 값이 존재 + } + + @Test + void 상승금액이_높은_부동산_조회(){ + List soaringPriceList = soaringPriceService.getSoaringPrices(SoaringPrice.Unit.WON); + assertThat(soaringPriceList).isNotNull(); // 값이 존재 + } +} diff --git a/src/test/java/kancho/realestate/comparingprices/service/SoaringPricesServiceTest.java b/src/test/java/kancho/realestate/comparingprices/service/SoaringPricesServiceTest.java deleted file mode 100644 index c0934dc..0000000 --- a/src/test/java/kancho/realestate/comparingprices/service/SoaringPricesServiceTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package kancho.realestate.comparingprices.service; - -import static org.assertj.core.api.Assertions.*; - -import java.util.List; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; - -import kancho.realestate.comparingprices.domain.model.SoaringPrices; - -class SoaringPricesServiceTest extends ServiceTest { - - @Autowired - SoaringPricesService soaringPricesService; - - @Test - void 상승률이_높은_부동산_조회(){ - List soaringPricesList = soaringPricesService.getSoaringPrices(SoaringPrices.Unit.PERCENT); - assertThat(soaringPricesList).isNotNull(); // 값이 존재 - } - - @Test - void 상승금액이_높은_부동산_조회(){ - List soaringPricesList = soaringPricesService.getSoaringPrices(SoaringPrices.Unit.WON); - assertThat(soaringPricesList).isNotNull(); // 값이 존재 - } -}