Создайте приложение для оповещения о криптовалюте с помощью Kotlin и Go: Часть 2. Серверная часть
Вам понадобится Android Studio 3+ и Go 1.10.2+, установленные на вашем компьютере. Вы должны иметь некоторое представление о разработке для Android и языке Kotlin.
В первой части этой статьи мы начали создавать наш сервис с создания нашего приложения для Android. Однако для правильной работы приложения требуется серверная часть. Итак, в этой части мы будем создавать серверную часть приложения.
Мы будем использовать Go для создания серверной части приложения. Фреймворк в Go, который мы будем использовать, это Эхо.
В качестве резюме, вот запись экрана того, что мы создадим, когда закончим:
Предпосылки
Чтобы следовать, вам нужно:
Создание нашего API Go
Настройка нашего проекта
Для начала создайте новый каталог проекта для вашего приложения. Мы создадим один с именем backend
. Рекомендуется создать его в своем $GOPATH
однако это не является обязательным требованием.
В каталоге проекта создайте три новых каталога:
- база данных
- уведомление
- маршруты
в database
каталог, создайте новый каталог с именем model
. В этом database
каталог, мы будем хранить все, что связано с базой данных, включая файл базы данных SQLite, model
а также database
упаковка.
в notification
каталог, у нас будет пакет, который будет содержать все необходимое для отправки push-уведомлений на устройства.
Наконец, в routes
каталог, у нас будет routes
package, где у нас есть логика для каждого HTTP-запроса.
Теперь приступим к созданию приложения.
Создание нашего основного приложения
Создать новый main.go
файл в корне проекта. В этот файл мы будем добавлять ядро проекта. Мы будем настраивать маршрутизацию, промежуточное ПО и базу данных.
в main.go
файл, вставьте следующий код:
<span class="hljs-comment">// File: ./main.go</span>
<span class="hljs-keyword">package</span> main
<span class="hljs-keyword">import</span> (
<span class="hljs-string">"./database"</span>
<span class="hljs-string">"./routes"</span>
<span class="hljs-string">"github.com/labstack/echo"</span>
<span class="hljs-string">"github.com/labstack/echo/middleware"</span>
)
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
db := database.Initialize(<span class="hljs-string">"./database/db.sqlite"</span>)
database.Migrate(db)
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.GET(<span class="hljs-string">"/fetch-values"</span>, routes.GetPrices())
e.POST(<span class="hljs-string">"/btc-pref"</span>, routes.SaveDeviceSettings(db))
e.POST(<span class="hljs-string">"/eth-pref"</span>, routes.SaveDeviceSettings(db))
e.GET(<span class="hljs-string">"/simulate"</span>, routes.SimulatePriceChanges(db))
e.Start(<span class="hljs-string">":9000"</span>)
}
В приведенном выше коде мы сначала импортировали некоторые пакеты, которые потребуются для работы скрипта Go. Затем мы создаем базу данных с помощью database
подпакет, который мы импортировали. Далее мы запускаем миграцию на db
пример. Это создаст таблицу базы данных, которую необходимо запустить приложению, если она еще не существует.
Далее мы создаем новый экземпляр Echo e
. Затем мы используем экземпляр для регистрации Регистратор промежуточное ПО и Восстанавливаться промежуточное ПО.
ПО промежуточного слоя Logger регистрирует информацию о каждом HTTP-запросе.
ПО промежуточного слоя Recover восстанавливается после паники в любом месте цепочки, распечатывает трассировку стека и передает управление централизованному HTTPErrorHandler.
Затем мы регистрируем наши маршруты и сопоставляем с ними обработчик с помощью routes
пакет, который мы импортировали. Маршруты:
GET /fetch-values
— получает текущие цены всех поддерживаемых валют и возвращает ответ JSON.POST /btc-pref
— сохраняет минимальную и максимальную цену, которую BTC должен превысить для устройства перед получением уведомления, и возвращает ответ JSON.POST /eth-pref
— сохраняет минимальную и максимальную цену ETH, которую должно превысить устройство перед получением уведомления, и возвращает ответ JSON.GET /simulate
— имитирует изменение цен в поддерживаемых валютах.
После маршрутов запускаем сервер на порту 9000.
Вы можете выбрать другой порт, если используется порт 9000, просто не забудьте также изменить его в своем
MainActivity.kt
файл.
Теперь, когда у нас есть main.go
файл, давайте вытащим весь импорт, который нужен сценарию. Откройте терминал и выполните следующие команды:
$ go get github.com/labstack/echo
$ go get github.com/labstack/echo/middleware
Это повлечет за собой пакет Echo и пакет промежуточного программного обеспечения Echo. Для двух других пакетов database
а также routes
, мы создадим их вручную. Давайте сделаем это сейчас.
Создание наших внутренних пакетов Go
Как упоминалось ранее, мы собираемся создать несколько внутренних пакетов, чтобы сделать приложение более модульным, поэтому давайте начнем с database
упаковка.
в database
каталог, создайте новый init.go
файл и вставьте следующий код:
<span class="hljs-comment">// File: ./database/init.go</span>
<span class="hljs-keyword">package</span> database
<span class="hljs-keyword">import</span> (
<span class="hljs-string">"database/sql"</span>
_ <span class="hljs-string">"github.com/mattn/go-sqlite3"</span>
)
<span class="hljs-comment">// Initialize initialises the database</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Initialize</span><span class="hljs-params">(filepath <span class="hljs-keyword">string</span>)</span> *<span class="hljs-title">sql</span>.<span class="hljs-title">DB</span></span> {
db, err := sql.Open(<span class="hljs-string">"sqlite3"</span>, filepath)
<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> || db == <span class="hljs-literal">nil</span> {
<span class="hljs-built_in">panic</span>(<span class="hljs-string">"Error connecting to database"</span>)
}
<span class="hljs-keyword">return</span> db
}
<span class="hljs-comment">// Migrate migrates the database</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Migrate</span><span class="hljs-params">(db *sql.DB)</span></span> {
sql := <span class="hljs-string">`
CREATE TABLE IF NOT EXISTS devices(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
uuid VARCHAR NOT NULL,
btc_min INTEGER,
btc_max INTEGER,
eth_min INTEGER,
eth_max INTEGER
);
`</span>
_, err := db.Exec(sql)
<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
<span class="hljs-built_in">panic</span>(err)
}
}
В приведенном выше файле мы сначала импортируем два пакета: database/sql
который встроен, и mattn/go-sqlite3 пакет, который является драйвером sqlite3 для Go с использованием database/sql
. Чтобы вытащить это, откройте терминал и выполните команду ниже:
$ go get github.com/mattn/go-sqlite3
Далее мы создали функцию с именем Initialize
и в этой функции мы инициализируем нашу базу данных SQLite. Это создаст новый файл базы данных, если он не существует, или использует существующий.
У нас также есть Migrate
функция, в которой мы указываем SQL-запрос, который будет выполняться при инициализации приложения. Как видно из запроса, мы создаем таблицу devices
только если его еще нет.
Это все для init.go
файл.
Создать новый routes.go
файл в routes
каталог и вставьте следующий код:
<span class="hljs-comment">// File: ./routes/routes.go</span>
<span class="hljs-keyword">package</span> routes
<span class="hljs-keyword">import</span> (
<span class="hljs-string">"database/sql"</span>
<span class="hljs-string">"errors"</span>
<span class="hljs-string">"net/http"</span>
<span class="hljs-string">"strconv"</span>
<span class="hljs-string">"../database/model"</span>
<span class="hljs-string">"github.com/labstack/echo"</span>
)
Теперь давайте начнем определять обработчики маршрутов, используемые в main.go
файл.
Во-первых, мы добавим GetPrices
функция. В этот же файл вставьте следующий код внизу:
<span class="hljs-comment">// GetPrices returns the coin prices</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">GetPrices</span><span class="hljs-params">()</span> <span class="hljs-title">echo</span>.<span class="hljs-title">HandlerFunc</span></span> {
<span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(c echo.Context)</span> <span class="hljs-title">error</span></span> {
prices, err := model.GetCoinPrices(<span class="hljs-literal">true</span>)
<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
<span class="hljs-keyword">return</span> c.JSON(http.StatusBadGateway, err)
}
<span class="hljs-keyword">return</span> c.JSON(http.StatusOK, prices)
}
}
Функция выше проста. Мы просто получаем цены от model.GetCoinPrices
функцию и вернуть их в виде ответа JSON.
Обратите внимание, что мы передали логическое значение в GetCoinPrices
функция. Это логическое значение указывает, моделировать ли цены или получать их напрямую из API. Поскольку мы тестируем, мы хотим смоделировать цены, чтобы они часто менялись.
Следующая функция, которую нужно добавить в routes.go
файл это SaveDeviceSettings
функция. В том же файле вставьте следующий код в конец файла:
<span class="hljs-keyword">var</span> postedSettings <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">string</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">formValue</span><span class="hljs-params">(c echo.Context, key <span class="hljs-keyword">string</span>)</span> <span class="hljs-params">(<span class="hljs-keyword">string</span>, error)</span></span> {
<span class="hljs-keyword">if</span> postedSettings == <span class="hljs-literal">nil</span> {
<span class="hljs-keyword">if</span> err := c.Bind(&postedSettings); err != <span class="hljs-literal">nil</span> {
<span class="hljs-keyword">return</span> <span class="hljs-string">""</span>, err
}
}
<span class="hljs-keyword">return</span> postedSettings[key], <span class="hljs-literal">nil</span>
}
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">getCoinValueFromRequest</span><span class="hljs-params">(key <span class="hljs-keyword">string</span>, c echo.Context)</span> <span class="hljs-params">(<span class="hljs-keyword">int64</span>, error)</span></span> {
value, _ := formValue(c, key)
<span class="hljs-keyword">if</span> value != <span class="hljs-string">""</span> {
setting, err := strconv.ParseInt(value, <span class="hljs-number">10</span>, <span class="hljs-number">64</span>)
<span class="hljs-keyword">if</span> err == <span class="hljs-literal">nil</span> {
<span class="hljs-keyword">return</span> setting, <span class="hljs-literal">nil</span>
}
}
<span class="hljs-keyword">return</span> <span class="hljs-number">0</span>, errors.New(<span class="hljs-string">"Invalid or empty key for: "</span> + key)
}
<span class="hljs-comment">// SaveDeviceSettings saves the device settings</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">SaveDeviceSettings</span><span class="hljs-params">(db *sql.DB)</span> <span class="hljs-title">echo</span>.<span class="hljs-title">HandlerFunc</span></span> {
<span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(c echo.Context)</span> <span class="hljs-title">error</span></span> {
uuid, _ := formValue(c, <span class="hljs-string">"uuid"</span>)
field := <span class="hljs-built_in">make</span>(<span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">int64</span>)
<span class="hljs-keyword">if</span> btcmin, err := getCoinValueFromRequest(<span class="hljs-string">"minBTC"</span>, c); err == <span class="hljs-literal">nil</span> {
field[<span class="hljs-string">"btc_min"</span>] = btcmin
}
<span class="hljs-keyword">if</span> btcmax, err := getCoinValueFromRequest(<span class="hljs-string">"maxBTC"</span>, c); err == <span class="hljs-literal">nil</span> {
field[<span class="hljs-string">"btc_max"</span>] = btcmax
}
<span class="hljs-keyword">if</span> ethmin, err := getCoinValueFromRequest(<span class="hljs-string">"minETH"</span>, c); err == <span class="hljs-literal">nil</span> {
field[<span class="hljs-string">"eth_min"</span>] = ethmin
}
<span class="hljs-keyword">if</span> ethmax, err := getCoinValueFromRequest(<span class="hljs-string">"maxETH"</span>, c); err == <span class="hljs-literal">nil</span> {
field[<span class="hljs-string">"eth_max"</span>] = ethmax
}
<span class="hljs-keyword">defer</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> { postedSettings = <span class="hljs-literal">nil</span> }()
device, err := model.SaveSettings(db, uuid, field)
<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
<span class="hljs-keyword">return</span> c.JSON(http.StatusBadRequest, err)
}
<span class="hljs-keyword">return</span> c.JSON(http.StatusOK, device)
}
}
В приведенном выше коде у нас есть три функции. Первые две функции являются вспомогательными. Они нужны нам для получения из запроса значений опубликованной формы.
в SaveDeviceSettings
функцию, мы получаем uuid
для устройства, и условно получить минимальное и максимальное значения для монеты. Мы сохраняем значения в базу данных с помощью model.SaveSettings
функцию и вернуть ответ JSON.
Последней функцией, которую нужно добавить, будет функция Simulate
функция. Добавьте следующий код в конец файла:
<span class="hljs-comment">// SimulatePriceChanges simulates the prices changes</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">SimulatePriceChanges</span><span class="hljs-params">(db *sql.DB)</span> <span class="hljs-title">echo</span>.<span class="hljs-title">HandlerFunc</span></span> {
<span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(c echo.Context)</span> <span class="hljs-title">error</span></span> {
prices, err := model.GetCoinPrices(<span class="hljs-literal">true</span>)
<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
<span class="hljs-built_in">panic</span>(err)
}
devices, err := model.NotifyDevicesOfPriceChange(db, prices)
<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
<span class="hljs-built_in">panic</span>(err)
}
resp := <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">interface</span>{}{
<span class="hljs-string">"prices"</span>: prices,
<span class="hljs-string">"devices"</span>: devices,
<span class="hljs-string">"status"</span>: <span class="hljs-string">"success"</span>,
}
<span class="hljs-keyword">return</span> c.JSON(http.StatusOK, resp)
}
}
В приведенной выше функции мы получаем цены на монеты, а затем отправляем их в model.NotifyDevicesOfPriceChange
функция, которая находит устройства с соответствующими критериями и отправляет им push-уведомление. Затем мы возвращаем ответ JSON prices
, devices
а также status
.
Это все, что касается маршрутов.
Наконец, давайте определим модель. Создать новый models.go
файл в database/model
каталог и вставьте следующий код:
<span class="hljs-comment">// File: ./database/model/models.go</span>
<span class="hljs-keyword">package</span> model
<span class="hljs-keyword">import</span> (
<span class="hljs-string">"database/sql"</span>
<span class="hljs-string">"encoding/json"</span>
<span class="hljs-string">"fmt"</span>
<span class="hljs-string">"io/ioutil"</span>
<span class="hljs-string">"math/big"</span>
<span class="hljs-string">"math/rand"</span>
<span class="hljs-string">"net/http"</span>
<span class="hljs-string">"time"</span>
<span class="hljs-string">"errors"</span>
<span class="hljs-string">"../../notification"</span>
)
Далее давайте определим структуры для ресурсов наших объектов. В том же файле вставьте внизу следующее:
<span class="hljs-comment">// CoinPrice represents a single coin resource</span>
<span class="hljs-keyword">type</span> CoinPrice <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">interface</span>{}
<span class="hljs-comment">// Device represents a single device resource</span>
<span class="hljs-keyword">type</span> Device <span class="hljs-keyword">struct</span> {
ID <span class="hljs-keyword">int64</span> <span class="hljs-string">`json:"id"`</span>
UUID <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"uuid"`</span>
BTCMin <span class="hljs-keyword">int64</span> <span class="hljs-string">`json:"btc_min"`</span>
BTCMax <span class="hljs-keyword">int64</span> <span class="hljs-string">`json:"btc_max"`</span>
ETHMin <span class="hljs-keyword">int64</span> <span class="hljs-string">`json:"eth_min"`</span>
ETHMax <span class="hljs-keyword">int64</span> <span class="hljs-string">`json:"eth_max"`</span>
}
<span class="hljs-comment">// Devices represents a collection of Devices</span>
<span class="hljs-keyword">type</span> Devices <span class="hljs-keyword">struct</span> {
Devices []Device <span class="hljs-string">`json:"items"`</span>
}
Выше у нас есть CoinPrice
карта. Это будет использоваться для обработки ответа от API, который мы будем использовать для нашего приложения. Когда получен ответ от API, мы привязываем его к CoinPrice
карта.
Следующий — это Device
структура. Это представляет ресурс устройства. Она соответствует схеме SQL таблицы, которую мы создали ранее в этой статье. Когда мы хотим создать новый ресурс устройства для хранения в базе данных или извлечения его, мы будем использовать Device
структура.
Наконец, у нас есть Devices
структура, которая представляет собой просто набор нескольких Device
структуры. Мы используем это, если хотим вернуть коллекцию Device
с.
Go не допускает подчеркивания в именах структур, поэтому мы будем использовать
json:``"``key_name``"
format для автоматического преобразования в свойства и обратно с указанными ключами.
Давайте начнем определять функции нашей модели.
В том же файле вставьте следующий код внизу страницы:
<span class="hljs-comment">// CreateSettings creates a new device and saves it to the db</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">CreateSettings</span><span class="hljs-params">(db *sql.DB, uuid <span class="hljs-keyword">string</span>)</span> <span class="hljs-params">(Device, error)</span></span> {
device := Device{UUID: uuid, BTCMin: <span class="hljs-number">0</span>, BTCMax: <span class="hljs-number">0</span>, ETHMin: <span class="hljs-number">0</span>, ETHMax: <span class="hljs-number">0</span>}
stmt, err := db.Prepare(<span class="hljs-string">"INSERT INTO devices (uuid, btc_min, btc_max, eth_min, eth_max) VALUES (?, ?, ?, ?, ?)"</span>)
<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
<span class="hljs-keyword">return</span> device, err
}
res, err := stmt.Exec(device.UUID, device.BTCMin, device.BTCMax, device.ETHMin, device.ETHMax)
<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
<span class="hljs-keyword">return</span> device, err
}
lastID, err := res.LastInsertId()
<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
<span class="hljs-keyword">return</span> device, err
}
device.ID = lastID
<span class="hljs-keyword">return</span> device, <span class="hljs-literal">nil</span>
}
Описанная выше функция используется для создания настроек для нового устройства. В функции новое устройство создается с помощью Device
структура. Затем мы пишем SQL-запрос, который хотим использовать для создания нового устройства.
Мы бежим Exec
в запросе SQL для выполнения запроса. Если ошибки нет, мы получаем последний вставленный идентификатор из запроса и присваиваем его Device
структуру, которую мы создали ранее. Затем мы возвращаем созданный Device
.
Добавим следующую функцию. В этот же файл вставьте следующий код внизу:
<span class="hljs-comment">// GetSettings fetches the settings for a single user from the db</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">GetSettings</span><span class="hljs-params">(db *sql.DB, uuid <span class="hljs-keyword">string</span>)</span> <span class="hljs-params">(Device, error)</span></span> {
device := Device{}
<span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(uuid) <= <span class="hljs-number">0</span> {
<span class="hljs-keyword">return</span> device, errors.New(<span class="hljs-string">"Invalid device UUID"</span>)
}
err := db.QueryRow(<span class="hljs-string">"SELECT * FROM devices WHERE uuid=?"</span>, uuid).Scan(
&device.ID,
&device.UUID,
&device.BTCMin,
&device.BTCMax,
&device.ETHMin,
&device.ETHMax)
<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
<span class="hljs-keyword">return</span> CreateSettings(db, uuid)
}
<span class="hljs-keyword">return</span> device, <span class="hljs-literal">nil</span>
}
в GetSettings
выше, мы создаем пустой Device
структура. Мы запускаем запрос для извлечения устройства из devices
таблица, которая соответствует uuid
. Затем мы используем Scan
метод пакета базы данных для сохранения значений строк в Device
Пример.
Если устройство не найдено, новое создается с помощью CreateSettings
функцию, которую мы создали ранее, иначе возвращается найденное устройство.
Добавим следующую функцию. В этот же файл вставьте следующий код внизу:
<span class="hljs-comment">// SaveSettings saves the devices settings</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">SaveSettings</span><span class="hljs-params">(db *sql.DB, uuid <span class="hljs-keyword">string</span>, field <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">int64</span>)</span> <span class="hljs-params">(Device, error)</span></span> {
device, err := GetSettings(db, uuid)
<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
<span class="hljs-keyword">return</span> Device{}, err
}
<span class="hljs-keyword">if</span> btcmin, isset := field[<span class="hljs-string">"btc_min"</span>]; isset {
device.BTCMin = btcmin
}
<span class="hljs-keyword">if</span> btcmax, isset := field[<span class="hljs-string">"btc_max"</span>]; isset {
device.BTCMax = btcmax
}
<span class="hljs-keyword">if</span> ethmin, isset := field[<span class="hljs-string">"eth_min"</span>]; isset {
device.ETHMin = ethmin
}
<span class="hljs-keyword">if</span> ethmax, isset := field[<span class="hljs-string">"eth_max"</span>]; isset {
device.ETHMax = ethmax
}
stmt, err := db.Prepare(<span class="hljs-string">"UPDATE devices SET btc_min = ?, btc_max = ?, eth_min = ?, eth_max = ? WHERE uuid = ?"</span>)
<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
<span class="hljs-keyword">return</span> Device{}, err
}
_, err = stmt.Exec(device.BTCMin, device.BTCMax, device.ETHMin, device.ETHMax, device.UUID)
<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
<span class="hljs-keyword">return</span> Device{}, err
}
<span class="hljs-keyword">return</span> device, <span class="hljs-literal">nil</span>
}
в SaveSettings
выше, мы получаем существующие настройки, используя функцию GetSettings
функцию, а затем мы условно обновляем существующее значение. Затем мы пишем запрос SQL для обновления базы данных новыми значениями. После этого возвращаем Device
структура.
Добавим следующую функцию. В этот же файл вставьте следующий код внизу:
<span class="hljs-comment">// GetCoinPrices gets the current coin prices</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">GetCoinPrices</span><span class="hljs-params">(simulate <span class="hljs-keyword">bool</span>)</span> <span class="hljs-params">(CoinPrice, error)</span></span> {
coinPrice := <span class="hljs-built_in">make</span>(CoinPrice)
currencies := [<span class="hljs-number">2</span>]<span class="hljs-keyword">string</span>{<span class="hljs-string">"ETH"</span>, <span class="hljs-string">"BTC"</span>}
<span class="hljs-keyword">for</span> _, curr := <span class="hljs-keyword">range</span> currencies {
<span class="hljs-keyword">if</span> simulate == <span class="hljs-literal">true</span> {
min := <span class="hljs-number">1000.0</span>
max := <span class="hljs-number">15000.0</span>
price, _ := big.NewFloat(min + rand.Float64()*(max-min)).SetPrec(<span class="hljs-number">8</span>).Float64()
coinPrice[curr] = <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">interface</span>{}{<span class="hljs-string">"USD"</span>: price}
<span class="hljs-keyword">continue</span>
}
url := fmt.Sprintf(<span class="hljs-string">" curr, time.Now().Unix())
res, err := http.Get(url)
<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
<span class="hljs-keyword">return</span> coinPrice, err
}
<span class="hljs-keyword">defer</span> res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
<span class="hljs-keyword">return</span> coinPrice, err
}
<span class="hljs-keyword">var</span> f <span class="hljs-keyword">interface</span>{}
err = json.Unmarshal([]<span class="hljs-keyword">byte</span>(body), &f)
<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
<span class="hljs-keyword">return</span> coinPrice, err
}
priceMap := f.(<span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">interface</span>{})[curr]
<span class="hljs-keyword">for</span> _, price := <span class="hljs-keyword">range</span> priceMap.(<span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">interface</span>{}) {
coinPrice[curr] = <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">interface</span>{}{<span class="hljs-string">"USD"</span>: price.(<span class="hljs-keyword">float64</span>)}
}
}
<span class="hljs-keyword">return</span> coinPrice, <span class="hljs-literal">nil</span>
}
В приведенной выше функции мы создаем новый экземпляр coinPrice
а затем мы создаем массив из двух валют, которые мы хотим получить, ETH и BTC. Затем мы перебираем валюты, и если simulate
является true
, мы просто возвращаем смоделированные цены на монеты. Если это false
то для каждой из валют делаем следующее:
- Получить цену валюты из API.
- Добавьте цену валюты к
coinPrice
карта.
После того, как мы закончим, мы вернем цены.
Следующая и последняя функция, которую мы хотим добавить, это NotifyDevicesOfPriceChange
функция. Это отвечает за получение устройств, соответствующих минимальному и максимальному порогу, и отправку им push-уведомлений.
В этот же файл вставьте следующий код:
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">minMaxQuery</span><span class="hljs-params">(curr <span class="hljs-keyword">string</span>)</span> <span class="hljs-title">string</span></span> {
<span class="hljs-keyword">return</span> <span class="hljs-string">`(`</span> + curr + <span class="hljs-string">`_min > 0 AND `</span> + curr + <span class="hljs-string">`_min > ?) OR (`</span> + curr + <span class="hljs-string">`_max > 0 AND `</span> + curr + <span class="hljs-string">`_max < ?)`</span>
}
<span class="hljs-comment">// NotifyDevicesOfPriceChange returns the devices that are within the range</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">NotifyDevicesOfPriceChange</span><span class="hljs-params">(db *sql.DB, prices CoinPrice)</span> <span class="hljs-params">(Devices, error)</span></span> {
devices := Devices{}
<span class="hljs-keyword">for</span> currency, price := <span class="hljs-keyword">range</span> prices {
pricing := price.(<span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">interface</span>{})
rows, err := db.Query(<span class="hljs-string">"SELECT * FROM devices WHERE "</span>+minMaxQuery(currency), pricing[<span class="hljs-string">"USD"</span>], pricing[<span class="hljs-string">"USD"</span>])
<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
<span class="hljs-keyword">return</span> devices, err
}
<span class="hljs-keyword">defer</span> rows.Close()
<span class="hljs-keyword">for</span> rows.Next() {
device := Device{}
err = rows.Scan(&device.ID, &device.UUID, &device.BTCMin, &device.BTCMax, &device.ETHMin, &device.ETHMax)
<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
<span class="hljs-keyword">return</span> devices, err
}
devices.Devices = <span class="hljs-built_in">append</span>(devices.Devices, device)
notification.SendNotification(currency, pricing[<span class="hljs-string">"USD"</span>].(<span class="hljs-keyword">float64</span>), device.UUID)
}
}
<span class="hljs-keyword">return</span> devices, <span class="hljs-literal">nil</span>
}
В приведенном выше коде у нас есть две функции, первая minMaxQuery
это вспомогательная функция, которая помогает нам генерировать SQL-запрос для минимального и максимального значения валюты.
Вторая функция – это NotifyDevicesOfPriceChange
функция. Здесь мы перебираем цены в валюте и для каждой цены проверяем базу данных на наличие устройств, которые соответствуют минимальной и максимальной цене.
Когда у нас есть устройства, мы прокручиваем их и отправляем push-уведомление с помощью notification.SendNotification
метод. Затем мы возвращаем устройства, на которые мы отправили уведомление.
Это все для модельного пакета. У нас есть последний пакет, который нужно добавить, и это notification
упаковка. Мы использовали его в приведенном выше коде для отправки push-уведомления, поэтому давайте определим его.
в notifications
каталог, создайте push.go
файл и вставьте следующий код:
<span class="hljs-comment">// File: ./notification/push.go</span>
<span class="hljs-keyword">package</span> notification
<span class="hljs-keyword">import</span> (
<span class="hljs-string">"fmt"</span>
<span class="hljs-string">"strconv"</span>
<span class="hljs-string">"github.com/pusher/push-notifications-go"</span>
)
<span class="hljs-keyword">const</span> (
instanceID = <span class="hljs-string">"PUSHER_BEAMS_INSTANCE_ID"</span>
secretKey = <span class="hljs-string">"PUSHER_BEAMS_SECRET_KEY"</span>
)
<span class="hljs-comment">// SendNotification sends push notification to devices</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">SendNotification</span><span class="hljs-params">(currency <span class="hljs-keyword">string</span>, price <span class="hljs-keyword">float64</span>, uuid <span class="hljs-keyword">string</span>)</span> <span class="hljs-title">error</span></span> {
notifications, err := pushnotifications.New(instanceID, secretKey)
<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
<span class="hljs-keyword">return</span> err
}
publishRequest := <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">interface</span>{}{
<span class="hljs-string">"fcm"</span>: <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">interface</span>{}{
<span class="hljs-string">"notification"</span>: <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">interface</span>{}{
<span class="hljs-string">"title"</span>: currency + <span class="hljs-string">" Price Change"</span>,
<span class="hljs-string">"body"</span>: fmt.Sprintf(<span class="hljs-string">"The price of %s has changed to $%s"</span>, currency, strconv.FormatFloat(price, <span class="hljs-string">'f'</span>, <span class="hljs-number">2</span>, <span class="hljs-number">64</span>)),
},
},
}
interest := fmt.Sprintf(<span class="hljs-string">"%s_%s_changed"</span>, uuid, currency)
_, err = notifications.Publish([]<span class="hljs-keyword">string</span>{interest}, publishRequest)
<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
<span class="hljs-keyword">return</span> err
}
<span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
}
Заменить
PUSHER_BEAMS_*
ключ с учетными данными на панели инструментов Pusher.
В приведенном выше коде у нас есть SendNotification
функция. Там мы создаем новый экземпляр Pusher Beams, используя InstanceID
а также secretKey
определенная выше функция.
Затем мы создаем publishRequest
переменная, которая содержит полезную нагрузку уведомления Android. Эта полезная нагрузка — это то, что мы отправим на серверную часть Pusher Beams, и она будет содержать все необходимое для отправки уведомления на устройство Android.
Далее мы создаем interest
переменная, которая будет интересом, на который мы хотим отправить уведомление. Формат интереса будет соответствовать тому, на который мы подписались в первой части этого руководства. Далее мы вызываем Publish
функция пакета Pusher Beams для отправки уведомления на устройство.
Последнее, что нам нужно сделать, это добавить пакет Pusher Beams в наш $GOPATH
. Откройте терминал и выполните следующую команду:
$ go get github.com/pusher/push-notifications-go
Когда команда выполнена успешно, теперь мы можем запустить приложение.
Запускаем наше приложение
Теперь, когда мы закончили создание приложения, нам нужно запустить как серверную часть, так и приложение Android.
Откройте терминал и выполните следующую команду из корня проекта, чтобы запустить приложение Go:
$ go run main.go
Это должно запустить сервер на порту 9000.
Затем перейдите в Android Studio и запустите свой проект Android. На этом этапе вы можете увидеть приложение. Вы можете установить минимальные и максимальные ограничения для валюты BTC и ETH.
Теперь сверните приложение в симуляторе и откройте центр уведомлений. Посетите URL-адрес для имитации изменения валюты. Вы должны увидеть уведомления, поступающие на устройство, как показано ниже:
Вывод
В этой статье мы смогли увидеть, как вы можете создать приложение для наблюдения за криптовалютой для Android, используя Pusher Beams и Go. Это руководство также доступно для iOS здесь.
Исходный код приложения, созданного в этой статье, доступен на Гитхаб.
Этот пост впервые появился на Блог пушера.