From b8adfc66e27c9d7fb76bb8c1556f4482583ebf44 Mon Sep 17 00:00:00 2001 From: MaysWind Date: Mon, 8 Mar 2021 23:08:25 +0800 Subject: [PATCH] exchange rate data source supports requesting multi url --- pkg/api/exchange_rates.go | 69 +++++++++++++++---- .../euro_central_bank_datasource.go | 24 +++---- .../exchange_rates_datasource.go | 4 +- pkg/models/exchange_rate.go | 30 ++++++-- 4 files changed, 94 insertions(+), 33 deletions(-) diff --git a/pkg/api/exchange_rates.go b/pkg/api/exchange_rates.go index 5bcef04b..d3a25a63 100644 --- a/pkg/api/exchange_rates.go +++ b/pkg/api/exchange_rates.go @@ -3,12 +3,14 @@ package api import ( "io/ioutil" "net/http" + "sort" "time" "github.com/mayswind/lab/pkg/core" "github.com/mayswind/lab/pkg/errs" "github.com/mayswind/lab/pkg/exchangerates" "github.com/mayswind/lab/pkg/log" + "github.com/mayswind/lab/pkg/models" "github.com/mayswind/lab/pkg/settings" ) @@ -34,26 +36,65 @@ func (a *ExchangeRatesApi) LatestExchangeRateHandler(c *core.Context) (interface Timeout: time.Duration(settings.Container.Current.ExchangeRatesRequestTimeout) * time.Millisecond, } - resp, err := client.Get(dataSource.GetRequestUrl()) + urls := dataSource.GetRequestUrls() + exchangeRateResps := make([]*models.LatestExchangeRateResponse, 0, len(urls)) - if err != nil { - log.ErrorfWithRequestId(c, "[exchange_rates.LatestExchangeRateHandler] failed to request latest exchange rate data for user \"uid:%d\", because %s", uid, err.Error()) - return nil, errs.ErrFailedToRequestRemoteApi + for i := 0; i < len(urls); i++ { + resp, err := client.Get(urls[i]) + + if err != nil { + log.ErrorfWithRequestId(c, "[exchange_rates.LatestExchangeRateHandler] failed to request latest exchange rate data for user \"uid:%d\", because %s", uid, err.Error()) + return nil, errs.ErrFailedToRequestRemoteApi + } + + if resp.StatusCode != 200 { + log.ErrorfWithRequestId(c, "[exchange_rates.LatestExchangeRateHandler] failed to get latest exchange rate data response for user \"uid:%d\", because response code is not 200", uid) + return nil, errs.ErrFailedToRequestRemoteApi + } + + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + exchangeRateResp, err := dataSource.Parse(c, body) + + if err != nil { + log.ErrorfWithRequestId(c, "[exchange_rates.LatestExchangeRateHandler] failed to parse response for user \"uid:%d\", because %s", uid, err.Error()) + return nil, errs.Or(err, errs.ErrFailedToRequestRemoteApi) + } + + exchangeRateResps = append(exchangeRateResps, exchangeRateResp) } - if resp.StatusCode != 200 { - log.ErrorfWithRequestId(c, "[exchange_rates.LatestExchangeRateHandler] failed to get latest exchange rate data response for user \"uid:%d\", because response code is not 200", uid) - return nil, errs.ErrFailedToRequestRemoteApi + lastExchangeRateResponse := exchangeRateResps[len(exchangeRateResps)-1] + allExchangeRatesMap := make(map[string]string) + + for i := 0; i < len(exchangeRateResps); i++ { + exchangeRateResp := exchangeRateResps[i] + + for j := 0; j < len(exchangeRateResp.ExchangeRates); j++ { + exchangeRate := exchangeRateResp.ExchangeRates[j] + allExchangeRatesMap[exchangeRate.Currency] = exchangeRate.Rate + } } - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - latestExchangeRateResponse, err := dataSource.Parse(c, body) + allExchangeRatesMap[lastExchangeRateResponse.BaseCurrency] = "1" + allExchangeRates := make(models.LatestExchangeRateSlice, 0, len(allExchangeRatesMap)) - if err != nil { - log.ErrorfWithRequestId(c, "[exchange_rates.LatestExchangeRateHandler] failed to parse response for user \"uid:%d\", because %s", uid, err.Error()) - return nil, errs.Or(err, errs.ErrFailedToRequestRemoteApi) + for currency, rate := range allExchangeRatesMap { + allExchangeRates = append(allExchangeRates, &models.LatestExchangeRate{ + Currency: currency, + Rate: rate, + }) } - return latestExchangeRateResponse, nil + sort.Sort(allExchangeRates) + + finalExchangeRateResponse := &models.LatestExchangeRateResponse{ + DataSource: lastExchangeRateResponse.DataSource, + ReferenceUrl: lastExchangeRateResponse.ReferenceUrl, + UpdateTime: lastExchangeRateResponse.UpdateTime, + BaseCurrency: lastExchangeRateResponse.BaseCurrency, + ExchangeRates: allExchangeRates, + } + + return finalExchangeRateResponse, nil } diff --git a/pkg/exchangerates/euro_central_bank_datasource.go b/pkg/exchangerates/euro_central_bank_datasource.go index 9483d1a4..79c30943 100644 --- a/pkg/exchangerates/euro_central_bank_datasource.go +++ b/pkg/exchangerates/euro_central_bank_datasource.go @@ -42,26 +42,30 @@ type EuroCentralBankExchangeRate struct { } // ToLatestExchangeRateResponse returns a view-object according to original data from euro central bank -func (e *EuroCentralBankExchangeRateData) ToLatestExchangeRateResponse() *models.LatestExchangeRateResponse { +func (e *EuroCentralBankExchangeRateData) ToLatestExchangeRateResponse(c *core.Context) *models.LatestExchangeRateResponse { if len(e.AllExchangeRates) < 1 { + log.ErrorfWithRequestId(c, "[euro_central_bank_datasource.ToLatestExchangeRateResponse] all exchange rates is empty") return nil } latestEuroCentralBankExchangeRate := e.AllExchangeRates[0] if len(latestEuroCentralBankExchangeRate.ExchangeRates) < 1 { + log.ErrorfWithRequestId(c, "[euro_central_bank_datasource.ToLatestExchangeRateResponse] exchange rates is empty") return nil } - exchangeRates := make([]*models.LatestExchangeRate, len(latestEuroCentralBankExchangeRate.ExchangeRates)) + exchangeRates := make(models.LatestExchangeRateSlice, 0, len(latestEuroCentralBankExchangeRate.ExchangeRates)) for i := 0; i < len(latestEuroCentralBankExchangeRate.ExchangeRates); i++ { - exchangeRates[i] = latestEuroCentralBankExchangeRate.ExchangeRates[i].ToLatestExchangeRate() + exchangeRate := latestEuroCentralBankExchangeRate.ExchangeRates[i] + exchangeRates = append(exchangeRates, exchangeRate.ToLatestExchangeRate()) } timezone, err := time.LoadLocation(euroCentralBankDataUpdateDateTimezone) if err != nil { + log.ErrorfWithRequestId(c, "[euro_central_bank_datasource.ToLatestExchangeRateResponse] failed to get timezone, timezone name is %s", euroCentralBankDataUpdateDateTimezone) return nil } @@ -69,6 +73,7 @@ func (e *EuroCentralBankExchangeRateData) ToLatestExchangeRateResponse() *models updateTime, err := time.ParseInLocation(euroCentralBankDataUpdateDateFormat, updateDateTime, timezone) if err != nil { + log.ErrorfWithRequestId(c, "[euro_central_bank_datasource.ToLatestExchangeRateResponse] failed to parse update date, datetime is %s", updateDateTime) return nil } @@ -91,9 +96,9 @@ func (e *EuroCentralBankExchangeRate) ToLatestExchangeRate() *models.LatestExcha } } -// GetRequestUrl returns the euro central bank data source url -func (e *EuroCentralBankDataSource) GetRequestUrl() string { - return euroCentralBankExchangeRateUrl +// GetRequestUrls returns the euro central bank data source urls +func (e *EuroCentralBankDataSource) GetRequestUrls() []string { + return []string{euroCentralBankExchangeRateUrl} } // Parse returns the common response entity according to the euro central bank data source raw response @@ -106,17 +111,12 @@ func (e *EuroCentralBankDataSource) Parse(c *core.Context, content []byte) (*mod return nil, errs.ErrFailedToRequestRemoteApi } - latestExchangeRateResponse := euroCentralBankData.ToLatestExchangeRateResponse() + latestExchangeRateResponse := euroCentralBankData.ToLatestExchangeRateResponse(c) if latestExchangeRateResponse == nil { log.ErrorfWithRequestId(c, "[euro_central_bank_datasource.Parse] failed to parse latest exchange rate data, content is %s", string(content)) return nil, errs.ErrFailedToRequestRemoteApi } - latestExchangeRateResponse.ExchangeRates = append(latestExchangeRateResponse.ExchangeRates, &models.LatestExchangeRate{ - Currency: euroCentralBankBaseCurrency, - Rate: "1", - }) - return latestExchangeRateResponse, nil } diff --git a/pkg/exchangerates/exchange_rates_datasource.go b/pkg/exchangerates/exchange_rates_datasource.go index ceb68a79..f78f19de 100644 --- a/pkg/exchangerates/exchange_rates_datasource.go +++ b/pkg/exchangerates/exchange_rates_datasource.go @@ -7,8 +7,8 @@ import ( // ExchangeRatesDataSource defines the structure of exchange rates data source type ExchangeRatesDataSource interface { - // GetRequestUrl returns the data source url - GetRequestUrl() string + // GetRequestUrl returns the data source urls + GetRequestUrls() []string // Parse returns the common response entity according to the data source raw response Parse(c *core.Context, content []byte) (*models.LatestExchangeRateResponse, error) diff --git a/pkg/models/exchange_rate.go b/pkg/models/exchange_rate.go index 28319fbd..443f6ad1 100644 --- a/pkg/models/exchange_rate.go +++ b/pkg/models/exchange_rate.go @@ -1,12 +1,14 @@ package models +import "strings" + // LatestExchangeRateResponse returns a view-object which contains latest exchange rate type LatestExchangeRateResponse struct { - DataSource string `json:"dataSource"` - ReferenceUrl string `json:"referenceUrl"` - UpdateTime int64 `json:"updateTime"` - BaseCurrency string `json:"baseCurrency"` - ExchangeRates []*LatestExchangeRate `json:"exchangeRates"` + DataSource string `json:"dataSource"` + ReferenceUrl string `json:"referenceUrl"` + UpdateTime int64 `json:"updateTime"` + BaseCurrency string `json:"baseCurrency"` + ExchangeRates LatestExchangeRateSlice `json:"exchangeRates"` } // LatestExchangeRate represents a data pair of currency and exchange rate @@ -14,3 +16,21 @@ type LatestExchangeRate struct { Currency string `json:"currency"` Rate string `json:"rate"` } + +// LatestExchangeRateSlice represents the slice data structure of LatestExchangeRate +type LatestExchangeRateSlice []*LatestExchangeRate + +// Len returns the count of items +func (c LatestExchangeRateSlice) Len() int { + return len(c) +} + +// Swap swaps two items +func (c LatestExchangeRateSlice) Swap(i, j int) { + c[i], c[j] = c[j], c[i] +} + +// Less reports whether the first item is less than the second one +func (c LatestExchangeRateSlice) Less(i, j int) bool { + return strings.Compare(c[i].Currency, c[j].Currency) < 0 +}