JavaRush /בלוג Java /Random-HE /יצירת מערכת לניטור מחירי כרטיסי טיסה: מדריך שלב אחר שלב [...
Roman Beekeeper
רָמָה

יצירת מערכת לניטור מחירי כרטיסי טיסה: מדריך שלב אחר שלב [חלק 1]

פורסם בקבוצה

תוֹכֶן:

יצירת מערכת לניטור מחירי כרטיסי טיסה: מדריך שלב אחר שלב [חלק 1] - 1שלום לכולם, קהילת JavaRush! היום נדבר על איך לכתוב אפליקציית Spring Boot לניטור מחירי כרטיסי טיסה צעד אחר צעד. המאמר מיועד לאנשים שיש להם רעיון לגבי:
  • REST וכיצד בנויות נקודות קצה REST;
  • מסדי נתונים יחסיים;
  • עבודתו של מייבן (בפרט, מהי תלות);
  • אובייקט JSON;
  • עקרונות רישום.
התנהגות מצופה:
  1. ניתן לבחור טיסה לתאריך מסוים ולעקוב אחר המחיר עבורה. המשתמש מזוהה באמצעות כתובת המייל. ברגע שנעשה מנוי לשינוי מחיר, המשתמש מקבל הודעה במייל.
  2. כל 30 דקות (מרווח זה מוגדר דרך application.properties) המחיר המינימלי לטיסה מחושב מחדש עבור כל המנויים. אם אחד מהערכים הפך נמוך יותר, המשתמש יקבל הודעה בדוא"ל.
  3. כל המנויים עם תאריך טיסה מיושן יימחקו.
  4. באמצעות REST API אתה יכול:
    • ליצור מנוי;
    • לַעֲרוֹך;
    • לקבל את כל המנויים במייל;
    • למחוק מנוי.

תוכנית פעולה להשגת המטרה

אתה צריך להתחיל עם העובדה שמידע על טיסות צריך להילקח מאיפשהו. בדרך כלל, אתרי אינטרנט מספקים REST API פתוח שדרכו ניתן לאחזר מידע.

API (ממשק תכנות יישומים) הוא ממשק שדרכו ניתן ליצור אינטראקציה עם אפליקציה. מכאן נוכל לבנות גשר למה שזה REST API.

REST API הוא ממשק של בקשות REST שניתן להשתמש בו כדי לתקשר עם יישום אינטרנט.

לשם כך, נשתמש ב-Skyscanner , או ליתר דיוק, ב-API (באתר Rakuten API ). לאחר מכן, עליך לבחור את המסגרת הנכונה כבסיס הבסיסי. הפופולרי והמבוקש ביותר הוא המערכת האקולוגית של האביב וכתר יצירתם - Spring Boot. אתה יכול ללכת לאתר הרשמי שלהם, או שאתה יכול לקרוא את המאמר על Habré . כדי לאחסן מנויי משתמשים נשתמש במסד הנתונים המובנה של H2 . כדי לקרוא מ-JSON לשיעורים ובחזרה, נשתמש בפרויקט ג'קסון ( הנה הקישור במשאב שלנו ). נשתמש ב- spring-boot-starter-mail כדי לשלוח הודעות למשתמשים . כדי שהאפליקציה תחשב מחדש את המחיר המינימלי בתדירות נתונה, נשתמש ב- Spring Scheduler . כדי ליצור ממשק API של REST נשתמש ב- spring-boot-starter-web . כדי לא לכתוב קוד מושאל (מקבלים, קובעים, override equals ו-hashcode, toString() עבור אובייקטים), נשתמש ב- Project Lombok . כדי להרגיש ולראות את REST API, נשתמש ב- Swagger 2 ומיד ב- Swagger UI (ממשק משתמש) למעקב בזמן אמת. כך זה נראה כעת: יצירת מערכת לניטור מחירי כרטיסי טיסה: מדריך שלב אחר שלב [חלק 1] - 2היכן שיש 4 שאילתות מנוחה המתאימות ליצירה, עריכה, קבלת ומחיקת מנויים.

חקור את ה-API של Skyscanner

בואו נעקוב אחר הקישור ל- rakuten api . ראשית עליך להירשם. יצירת מערכת לניטור מחירי כרטיסי טיסה: מדריך שלב אחר שלב [חלק 1] - 3כל זה נחוץ כדי לקבל מפתח ייחודי לשימוש באתר שלהם ולבצע בקשות לממשקי ה-API הציבוריים שמתפרסמים בו. אחד מממשקי ה-API האלה הוא חיפוש הטיסות של Skyscanner שאנחנו צריכים . עכשיו בואו נבין איך זה עובד. בוא נמצא את הבקשה GET List Places. התמונה מראה שצריך למלא את הנתונים ולהתחיל את Test Endpoint , כתוצאה מכך אנו מקבלים תגובה בצורת אובייקט JSON בצד ימין: יצירת מערכת לניטור מחירי כרטיסי טיסה: מדריך שלב אחר שלב [חלק 1] - 4והבקשה תיווצר כך:

https://skyscanner-skyscanner-flight-search-v1.p.rapidapi.com/apiservices/autosuggest/v1.0/{country}/{currency}/{locale}/?query={query}
וכל הפרמטרים יוחלפו בנוסחה הזו, נקבל:

https://skyscanner-skyscanner-flight-search-v1.p.rapidapi.com/apiservices/autosuggest/v1.0/UK/GBP/en-GB/?query=Stockholm
ושתי כותרות יועברו לבקשות אלו:

.header("x-rapidapi-host", "skyscanner-skyscanner-flight-search-v1.p.rapidapi.com")
.header("x-rapidapi-key", "sing-up-for-key"),
היכן sign-up-for-keyהוא מונפק לאחר הרישום. כדי לעקוב אחר ירידת המחיר, נצטרך את נקודת הקצה עיון בהצעות מחיר. מצא את זה בעצמך :)

יצירת מסגרת אפליקציה המבוססת על Spring Boot

כדי ליצור פרויקט במהירות ובקלות עם Spring Boot, אתה יכול להשתמש ב- Spring Initializr . בחר את האפשרויות הבאות:
  1. פרויקט מייבן
  2. Java
  3. 2.1.10
  4. קבוצה - מה שנחוץ לך לדעתך, למשל ru.javarush
  5. חפץ - בדיוק אותו הדבר, למשל ניטור טיסות
  6. בחיפוש התלות אנו מחפשים את הדברים הבאים:
    • רשת אביב
    • Java Mail Sender
    • Spring Data Jpa
    • מסד נתונים H2
ולאחר מכן לחץ על צור . זהו: הפרויקט המוגמר יוורד כארכיון. אם משהו לא מסתדר, אתה יכול להשתמש בקישור שבו שמרתי את הפרויקט הרצוי . כמובן, עדיף לעשות זאת בעצמך ולהבין איך זה עובד. היישום יהיה מורכב משלוש שכבות:
  • CONTROLLER - התחבר לאפליקציה. ה- REST API יתואר כאן
  • SERVICE הוא רובד היגיון עסקי. כל ההיגיון של היישום יתואר כאן.
  • REPOSITORY - שכבה לעבודה עם מסד הנתונים.
כמו כן, שיעורים הקשורים ללקוח עבור Skyscanner Flight Search API יהיו חבילה נפרדת.

אנו כותבים לקוח עבור בקשות ל-API של Skyscanner Flight Search בפרויקט

Skyscanner סיפקה בחביבות מאמר כיצד להשתמש ב-API שלהם (לא ניצור הפעלה עם בקשה פעילה). מה זה אומר "לכתוב לקוח"? עלינו ליצור בקשה לכתובת URL ספציפית עם פרמטרים מסוימים ולהכין DTO (אובייקט העברת נתונים) עבור הנתונים המועברים אלינו בחזרה. ישנן ארבע קבוצות של בקשות באתר:
  1. חיפוש טיסות חי - לא נראה אותו מיותר כרגע.
  2. מקומות - בואו נכתוב.
  3. עיין במחירי טיסות - נשתמש בבקשה אחת שבה תוכל לקבל את כל המידע.
  4. לוקליזציה - בואו נוסיף אותה כדי שנדע באילו נתונים נתמכים.

צור שירות לקוח עבור בקשת הלוקליזציה:

התוכנית פשוטה כמו לפת מאודה: צרו בקשה, הסתכלו על הפרמטרים, הסתכלו על התגובה. ישנן שתי שאילתות: סמני רשימה ומטבעות. נתחיל עם מטבעות. האיור מראה כי מדובר בבקשה ללא שדות נוספים: יש צורך לקבל מידע על מטבעות נתמכים: יצירת מערכת לניטור מחירי כרטיסי טיסה: מדריך שלב אחר שלב [חלק 1] - 6התגובה היא בצורת אובייקט JSON, המכיל אוסף של אותם אובייקטים, למשל:
{
"Code":"LYD"
"Symbol":"د.ل.‏"
"ThousandsSeparator":","
"DecimalSeparator":"."
"SymbolOnLeft":true
"SpaceBetweenAmountAndSymbol":false
"RoundingCoefficient":0
"DecimalDigits":3
}
בואו ניצור CurrencyDto עבור האובייקט הזה:
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;

/**
* Data transfer object for Currency.
*/
@Data
public class CurrencyDto {

   @JsonProperty("Code")
   private String code;

   @JsonProperty("Symbol")
   private String symbol;

   @JsonProperty("ThousandsSeparator")
   private String thousandsSeparator;

   @JsonProperty("DecimalSeparator")
   private String decimalSeparator;

   @JsonProperty("SymbolOnLeft")
   private boolean symbolOnLeft;

   @JsonProperty("SpaceBetweenAmountAndSymbol")
   private boolean spaceBetweenAmountAndSymbol;

   @JsonProperty("RoundingCoefficient")
   private int roundingCoefficient;

   @JsonProperty("DecimalDigits")
   private int decimalDigits;
}
איפה:
  • @Data הוא הערה מפרויקט Lombok ומייצר את כל המתודות ה-Getters, Setters, עוקפות toString(), equals() ו-hashCode() . מה שמשפר את קריאות הקוד ומזרז את זמן כתיבת אובייקטי POJO ;
  • @JsonProperty("קוד") הוא הערה מפרויקט ג'קסון שמספרת איזה שדה יוקצה למשתנה זה. כלומר, שדה ב-JSON השווה ל-Code יוקצה למשתנה הקוד .
המאמר הרשמי של Skyscanner מציע להשתמש בספריית UniRest עבור בקשות REST . לכן, נכתוב שירות נוסף שיטמיע בקשות דרך REST. זה יהיה UniRestService . כדי לעשות זאת, הוסף תלות חדשה ל-maven:
<dependency>
  <groupId>com.mashape.unirest</groupId>
  <artifactId>unirest-java</artifactId>
  <version>1.4.9</version>
</dependency>
לאחר מכן, נכתוב שירות שיבצע בקשות REST. כמובן שלכל לקוח/שירות ניצור ממשק והטמעתו:
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.JsonNode;

/**
* Service, which is manipulating with Rest calls.
*/
public interface UniRestService {

   /**
   * Create GET request based on provided {@param path} with needed headers.
   *
   * @param path provided path with all the needed data
   * @return {@link HttpResponse<jsonnode>} response object.
   */
   HttpResponse<jsonnode> get(String path);

}
והיישום שלו:
import com.github.romankh3.flightsmonitoring.exception.FlightClientException;
import com.github.romankh3.flightsmonitoring.client.service.UniRestService;
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.JsonNode;
import com.mashape.unirest.http.Unirest;
import com.mashape.unirest.http.exceptions.UnirestException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

/**
* {@inheritDoc}
*/
@Slf4j
@Service
public class UniRestServiceImpl implements UniRestService {

   public static final String HOST = "https://skyscanner-skyscanner-flight-search-v1.p.rapidapi.com";

   public static final String PLACES_FORMAT = "/apiservices/autosuggest/v1.0/%s/%s/%s/?query=%s";
   public static final String CURRENCIES_FORMAT = "/apiservices/reference/v1.0/currencies";
   public static final String COUNTRIES_FORMAT = "/apiservices/reference/v1.0/countries/%s";

   public static final String PLACES_KEY = "Places";
   public static final String CURRENCIES_KEY = "Currencies";
   public static final String COUNTRIES_KEY = "Countries";

   @Value("${x.rapid.api.key}")
   private String xRapidApiKey;

   /**
    * {@inheritDoc}
    */
   @Override
   public HttpResponse<jsonnode> get(String path) {
       HttpResponse<jsonnode> response = null;
       try {
           response = Unirest.get(HOST + path)
                   .header("x-rapidapi-host", "skyscanner-skyscanner-flight-search-v1.p.rapidapi.com")
                   .header("x-rapidapi-key", xRapidApiKey)
                   .asJson();
       } catch (UnirestException e) {
           throw new FlightClientException(String.format("Request failed, path=%s", HOST + path), e);
       }

       log.info("Response from Get request, on path={}, statusCode={}, response={}", path, response.getStatus(), response.getBody().toString());
       return response;
   }
}
המהות שלו היא שכל הבקשות שאנו מעוניינים בהן נוצרות עבור בקשות GET, ושירות זה מקבל בקשה מוכנה ומוסיף את הכותרות הנחוצות כמו:
.header("x-rapidapi-host", "skyscanner-skyscanner-flight-search-v1.p.rapidapi.com")
.header("x-rapidapi-key", xRapidApiKey)
כדי לקחת נתונים מנכסים, השתמש בביאור @Value כפי שמוצג להלן:
@Value("${x.rapid.api.key}")
private String xRapidApiKey;
זה אומר שב-application.properties יהיה מאפיין בשם x.rapid.api.key, שצריך להזריק למשתנה הזה. אנו נפטרים מערכי קשיח וגוזרים את ההגדרה של משתנה זה מקוד התוכנית. יתר על כן, כאשר אני מפרסם את האפליקציה הזו ב- GitHub אני לא מוסיף את הערך של הנכס הזה. זה נעשה מטעמי אבטחה. כתבנו שירות שיעבוד עם בקשות REST, עכשיו הגיע הזמן לשירות ללוקליזציה. אנו בונים אפליקציה המבוססת על OOP, אז אנו יוצרים את ממשק LocalizationClient והטמעתו LocalisationClientImpl :
import com.github.romankh3.flightsmonitoring.client.dto.CountryDto;
import com.github.romankh3.flightsmonitoring.client.dto.CurrencyDto;
import java.io.IOException;
import java.util.List;

/**
* Client for SkyScanner localisation.
*/
public interface LocalisationClient {

   /**
    * Retrieve the market countries that SkyScanner flight search API support. Most suppliers (airlines,
    * travel agents and car hire dealers) set their fares based on the market (or country of purchase).
    * It is therefore necessary to specify the market country in every query.
    *
    * @param locale locale of the response.
    *
    * @return the collection of the {@link CountryDto} objects.
    *
    * @throws IOException
    */
   List<CountryDto> retrieveCountries(String locale);

   /**
    * Retrieve the currencies that we ScyScanner flight search API.
    *
    * @return the collection of the {@link CurrencyDto} objects.
    *
    * @throws IOException
    */
   List<CurrencyDto> retrieveCurrencies();

}
ויישום של LocalizationClientImpl
import static com.github.romankh3.flightsmonitoring.client.service.impl.UniRestServiceImpl.COUNTRIES_FORMAT;
import static com.github.romankh3.flightsmonitoring.client.service.impl.UniRestServiceImpl.COUNTRIES_KEY;
import static com.github.romankh3.flightsmonitoring.client.service.impl.UniRestServiceImpl.CURRENCIES_FORMAT;
import static com.github.romankh3.flightsmonitoring.client.service.impl.UniRestServiceImpl.CURRENCIES_KEY;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.romankh3.flightsmonitoring.client.dto.CountryDto;
import com.github.romankh3.flightsmonitoring.client.dto.CurrencyDto;
import com.github.romankh3.flightsmonitoring.client.service.LocalisationClient;
import com.github.romankh3.flightsmonitoring.client.service.UniRestService;
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.JsonNode;
import com.mashape.unirest.http.exceptions.UnirestException;
import java.io.IOException;
import java.util.List;
import org.apache.http.HttpStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
* {@inheritDoc}
*/
@Component
public class LocalisationClientImpl implements LocalisationClient {

   @Autowired
   private UniRestService uniRestService;

   @Autowired
   private ObjectMapper objectMapper;

   /**
    * {@inheritDoc}
    */
   @Override
   public List<CountryDto> retrieveCountries(String locale) throws IOException {
       HttpResponse<JsonNode> response = uniRestService.get(String.format(COUNTRIES_FORMAT, locale));

       if (response.getStatus() != HttpStatus.SC_OK) {
           return null;
       }

       String jsonList = response.getBody().getObject().get(COUNTRIES_KEY).toString();

       return objectMapper.readValue(jsonList, new TypeReference<List<CountryDto>>() {
       });
   }

   /**
    * {@inheritDoc}
    */
   @Override
   public List<CurrencyDto> retrieveCurrencies() throws IOException, UnirestException {

       HttpResponse<JsonNode> response = uniRestService.get(CURRENCIES_FORMAT);
       if (response.getStatus() != HttpStatus.SC_OK) {
           return null;
       }

       String jsonList = response.getBody().getObject().get(CURRENCIES_KEY).toString();

       return objectMapper.readValue(jsonList, new TypeReference<List<CurrencyDto>>() {
       });
   }
}
איפה
  • @Autowired הוא ביאור שאומר שצריך להחדיר אובייקט למחלקה הזו ולהשתמש בו מבלי ליצור אותו, כלומר ללא פעולת האובייקט החדשה;
  • @Component הוא הערה האומרת שיש להוסיף את האובייקט הזה להקשר היישום כדי שניתן יהיה להחדיר אותו מאוחר יותר באמצעות ההערה @Autowired;
  • ObjectMapper objectMapper הוא אובייקט מפרויקט Jackson שמתרגם את כל זה לאובייקטי Java.
  • CurrencyDTO ו-CountryDto:
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;

/**
* Data transfer object for Currency.
*/
@Data
public class CurrencyDto {

   @JsonProperty("Code")
   private String code;

   @JsonProperty("Symbol")
   private String symbol;

   @JsonProperty("ThousandsSeparator")
   private String thousandsSeparator;

   @JsonProperty("DecimalSeparator")
   private String decimalSeparator;

   @JsonProperty("SymbolOnLeft")
   private boolean symbolOnLeft;

   @JsonProperty("SpaceBetweenAmountAndSymbol")
   private boolean spaceBetweenAmountAndSymbol;

   @JsonProperty("RoundingCoefficient")
   private int roundingCoefficient;

   @JsonProperty("DecimalDigits")
   private int decimalDigits;
}
	и
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;

/**
* Data transfer object for Country.
*/
@Data
public class CountryDto {

   @JsonProperty("Code")
   private String code;

   @JsonProperty("Name")
   private String name;
}
כדי להחדיר ObjectMapper לכל חלק של הפרוייקט, הוספתי יצירה והוספתו ל- ApplicationContext באמצעות מחלקת תצורה.
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* {@link Configuration} class.
*/
@Configuration
public class Config {

   @Bean
   public ObjectMapper objectMapper() {
       ObjectMapper objectMapper = new ObjectMapper();
       objectMapper.registerModule(new JavaTimeModule());
       return objectMapper;
   }
}
ההערה @Configuration אומרת ל-Spring שיהיו כמה תצורות במחלקה זו. ובדיוק בשביל זה הוספתי את ObjectMapper. באופן דומה, אנו מוסיפים את PlacesClient ו- PlacesClientImpl:
import com.github.romankh3.flightsmonitoring.client.dto.PlaceDto;
import com.github.romankh3.flightsmonitoring.client.dto.PlacesDto;
import com.mashape.unirest.http.exceptions.UnirestException;
import java.io.IOException;
import java.util.List;

/**
* SkyScanner client.
*/
public interface PlacesClient {

   /**
    * Get a list of places that match a query string based on arguments.
    *
    * @param query the code of the city.
    * @param country the code of the country.
    * @param currency the code of the currency.
    * @param locale the code of the locale.
    * @return the collection of the {@link PlaceDto} objects.
    */
   List<PlacesDto> retrieveListPlaces(String query, String country, String currency, String locale)
           throws IOException, UnirestException;
}
ו
import static com.github.romankh3.flightsmonitoring.client.service.impl.UniRestServiceImpl.PLACES_FORMAT;
import static com.github.romankh3.flightsmonitoring.client.service.impl.UniRestServiceImpl.PLACES_KEY;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.romankh3.flightsmonitoring.client.dto.PlacesDto;
import com.github.romankh3.flightsmonitoring.client.service.PlacesClient;
import com.github.romankh3.flightsmonitoring.client.service.UniRestService;
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.JsonNode;
import com.mashape.unirest.http.exceptions.UnirestException;
import java.io.IOException;
import java.util.List;
import org.apache.http.HttpStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
* {@inheritDoc}
*/
@Service
public class PlacesClientImpl implements PlacesClient {

   @Autowired
   private UniRestService uniRestService;

   @Autowired
   private ObjectMapper objectMapper;


   /**
    * {@inheritDoc}
    */
   @Override
   public List<PlacesDto> retrieveListPlaces(String query, String country, String currency, String locale)
           throws IOException, UnirestException {
       HttpResponse<JsonNode> response = uniRestService
               .get(String.format(PLACES_FORMAT, country, currency, locale, query));

       if (response.getStatus() != HttpStatus.SC_OK) {
           return null;
       }

       String jsonList = response.getBody().getObject().get(PLACES_KEY).toString();

       return objectMapper.readValue(jsonList, new TypeReference<List<PlacesDto>>() {
       });
   }
}
כאשר ל-PlacesDto יש את הטופס:
import com.fasterxml.jackson.annotation.JsonProperty;
import com.github.romankh3.flightsmonitoring.client.service.PlacesClient;
import lombok.Data;

/**
* Using for {@link PlacesClient}.
*/
@Data
public class PlacesDto {

   @JsonProperty("PlaceId")
   private String placeId;

   @JsonProperty("PlaceName")
   private String placeName;

   @JsonProperty("CountryId")
   private String countryId;

   @JsonProperty("RegionId")
   private String regionId;

   @JsonProperty("CityId")
   private String cityId;

   @JsonProperty("CountryName")
   private String countryName;
}
ולבסוף, שירות לקוחות שעל בסיס הנתונים הדרושים יחזיר את מחיר המינימום לטיסה ואת כל המידע הדרוש: FlightPriceClient ו- FlightPriceClientImpl. אנו ניישם רק בקשה אחת, browseQuotes. FlightPrice Client:
import com.github.romankh3.flightsmonitoring.client.dto.FlightPricesDto;

/**
* Browse flight prices.
*/
public interface FlightPricesClient {

   /**
    * Browse quotes for current flight based on provided arguments. One-way ticket.
    *
    * @param country the country from
    * @param currency the currency to get price
    * @param locale locale for the response
    * @param originPlace origin place
    * @param destinationPlace destination place
    * @param outboundPartialDate outbound date
    * @return {@link FlightPricesDto} object.
    */
   FlightPricesDto browseQuotes(String country, String currency, String locale, String originPlace,
           String destinationPlace, String outboundPartialDate);

   /**
    * Browse quotes for current flight based on provided arguments. Round trip ticket.
    *
    * @param country the country from
    * @param currency the currency to get price
    * @param locale locale for the response
    * @param originPlace origin place
    * @param destinationPlace destination place
    * @param outboundPartialDate outbound date
    * @param inboundPartialDate inbound date
    * @return {@link FlightPricesDto} object.
    */
   FlightPricesDto browseQuotes(String country, String currency, String locale, String originPlace,
           String destinationPlace, String outboundPartialDate, String inboundPartialDate);
}
FlightPriceClientImpl
import static com.github.romankh3.flightsmonitoring.client.service.impl.UniRestServiceImpl.CURRENCIES_KEY;
import static com.github.romankh3.flightsmonitoring.client.service.impl.UniRestServiceImpl.PLACES_KEY;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.romankh3.flightsmonitoring.client.dto.CarrierDto;
import com.github.romankh3.flightsmonitoring.client.dto.CurrencyDto;
import com.github.romankh3.flightsmonitoring.client.dto.FlightPricesDto;
import com.github.romankh3.flightsmonitoring.client.dto.PlaceDto;
import com.github.romankh3.flightsmonitoring.client.dto.QuoteDto;
import com.github.romankh3.flightsmonitoring.client.dto.ValidationErrorDto;
import com.github.romankh3.flightsmonitoring.client.service.FlightPricesClient;
import com.github.romankh3.flightsmonitoring.client.service.UniRestService;
import com.github.romankh3.flightsmonitoring.exception.FlightClientException;
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.JsonNode;
import java.io.IOException;
import java.util.List;
import org.apache.http.HttpStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
* {@inheritDoc}
*/
@Service
public class FlightPricesClientImpl implements FlightPricesClient {

   public static final String BROWSE_QUOTES_FORMAT = "/apiservices/browsequotes/v1.0/%s/%s/%s/%s/%s/%s";
   public static final String OPTIONAL_BROWSE_QUOTES_FORMAT = BROWSE_QUOTES_FORMAT + "?inboundpartialdate=%s";

   public static final String QUOTES_KEY = "Quotes";
   public static final String ROUTES_KEY = "Routes";
   public static final String DATES_KEY = "Dates";
   public static final String CARRIERS_KEY = "Carriers";
   public static final String VALIDATIONS_KEY = "ValidationErrors";

   @Autowired
   private UniRestService uniRestService;

   @Autowired
   private ObjectMapper objectMapper;

   /**
    * {@inheritDoc}
    */
   @Override
   public FlightPricesDto browseQuotes(String country, String currency, String locale, String originPlace,
           String destinationPlace, String outboundPartialDate) {

       HttpResponse<JsonNode> response = uniRestService.get(String
               .format(BROWSE_QUOTES_FORMAT, country, currency, locale, originPlace, destinationPlace,
                       outboundPartialDate));
       return mapToObject(response);
   }

   public FlightPricesDto browseQuotes(String country, String currency, String locale, String originPlace,
           String destinationPlace, String outboundPartialDate, String inboundPartialDate) {
       HttpResponse<JsonNode> response = uniRestService.get(String
               .format(OPTIONAL_BROWSE_QUOTES_FORMAT, country, currency, locale, originPlace, destinationPlace,
                       outboundPartialDate, inboundPartialDate));
       return mapToObject(response);
   }

   private FlightPricesDto mapToObject(HttpResponse<JsonNode> response) {
       if (response.getStatus() == HttpStatus.SC_OK) {
           FlightPricesDto flightPricesDto = new FlightPricesDto();
           flightPricesDto.setQuotas(readValue(response.getBody().getObject().get(QUOTES_KEY).toString(),
                   new TypeReference<List<QuoteDto>>() {
                   }));
           flightPricesDto.setCarriers(readValue(response.getBody().getObject().get(CARRIERS_KEY).toString(),
                   new TypeReference<List<CarrierDto>>() {
                   }));
           flightPricesDto.setCurrencies(readValue(response.getBody().getObject().get(CURRENCIES_KEY).toString(),
                   new TypeReference<List<CurrencyDto>>() {
                   }));
           flightPricesDto.setPlaces(readValue(response.getBody().getObject().get(PLACES_KEY).toString(),
                   new TypeReference<List<PlaceDto>>() {
                   }));
           return flightPricesDto;
       }
       throw new FlightClientException(String.format("There are validation errors. statusCode = %s", response.getStatus()),
               readValue(response.getBody().getObject().get(VALIDATIONS_KEY).toString(),
                       new TypeReference<List<ValidationErrorDto>>() {
                       }));
   }

   private <T> List<T> readValue(String resultAsString, TypeReference<List<T>> valueTypeRef) {
       List<T> list;
       try {
           list = objectMapper.readValue(resultAsString, valueTypeRef);
       } catch (IOException e) {
           throw new FlightClientException("Object Mapping failure.", e);
       }
       return list;
   }
}
איפה FlightClientException נראה:
import com.github.romankh3.flightsmonitoring.client.dto.ValidationErrorDto;
import java.util.List;

/**
* A {@link RuntimeException} that is thrown in case of an flight monitoring failures.
*/
public final class FlightClientException extends RuntimeException {

   public FlightClientException(String message) {
       super(message);
   }

   public FlightClientException(String message, Throwable throwable) {
       super(message, throwable);
   }

   public FlightClientException(String message, List<ValidationErrorDto> errors) {
       super(message);
       this.validationErrorDtos = errors;
   }

   private List<ValidationErrorDto> validationErrorDtos;
}
כתוצאה מכך, על פי נתוני PlacesCl
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION