Создайте приложение для оповещения о криптовалюте с помощью Kotlin и Go: Часть 2. Серверная часть

Вам понадобится Android Studio 3+ и Go 1.10.2+, установленные на вашем компьютере. Вы должны иметь некоторое представление о разработке для Android и языке Kotlin.

В первой части этой статьи мы начали создавать наш сервис с создания нашего приложения для Android. Однако для правильной работы приложения требуется серверная часть. Итак, в этой части мы будем создавать серверную часть приложения.

Мы будем использовать Go для создания серверной части приложения. Фреймворк в Go, который мы будем использовать, это Эхо.

В качестве резюме, вот запись экрана того, что мы создадим, когда закончим:

Предпосылки

Чтобы следовать, вам нужно:

Создание нашего API Go

Настройка нашего проекта

Для начала создайте новый каталог проекта для вашего приложения. Мы создадим один с именем backend. Рекомендуется создать его в своем $GOPATH однако это не является обязательным требованием.

В каталоге проекта создайте три новых каталога:

  1. база данных
  2. уведомление
  3. маршруты

в 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 пакет, который мы импортировали. Маршруты:

  1. GET /fetch-values — получает текущие цены всех поддерживаемых валют и возвращает ответ JSON.
  2. POST /btc-pref — сохраняет минимальную и максимальную цену, которую BTC должен превысить для устройства перед получением уведомления, и возвращает ответ JSON.
  3. POST /eth-pref — сохраняет минимальную и максимальную цену ETH, которую должно превысить устройство перед получением уведомления, и возвращает ответ JSON.
  4. 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 здесь.

Исходный код приложения, созданного в этой статье, доступен на Гитхаб.

Этот пост впервые появился на Блог пушера.

Похожие записи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *