My Note

自己理解のためのブログ

Echo ( GoWebフレームワーク ) on GAE で仮想通貨の価格を返すAPIを作成した

はじめに

今回は自分のAPIサーバで仮想通貨の現在価格情報を返す機能が欲しかったので取引所のAPIを使って実装しました。

データの取得先

価格を取得するために、GMOコインとCoincheck を利用しました。

APIについて

  • リクエストについて

    • ${ENDPOINT}/crypto/${exchage}\?symbol=${トークン名}
      • クエリパラメータに トークン名 { BTC, ETH等々.. }
  • レスポンスについて

■ レスポンス結果(例)

{
  "data": [
    {
      "symbol": "BTC_JPY",
      "last": "4554436",
      "timestamp": "2022-03-11T07:22:19.507Z"
    }
  ]
}
{
  "symbol": "btc_jpy",
  "last": 4518231,
  "time": "2022/02/21-21:25:42"
}

ソースコード

├── README.md
├── api
│   ├── base.go
│   ├── client.go
│   ├── crypto_coincheck.go
│   ├── crypto_coincheck_test.go
│   ├── crypto_gmo.go
│   ├── crypto_gmo_test.go
│   ├── healthcheck.go
│   ├── investment_trust.go
│   ├── metal.go
│   └── staticcheck.conf
├── app.yaml
├── authentication
│   └── basic_auth.go
├── go.mod
├── go.sum
├── main.go
└── secret.yaml

■ main.go

package main

import (
    "net/http"

    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
    "google.golang.org/appengine"

    "github.com/yhidetoshi/apiEchoGAE-private/api"
    "github.com/yhidetoshi/apiEchoGAE-private/authentication"
)

var e = createMux()

func createMux() *echo.Echo {
    e := echo.New()
    return e
}

func main() {
    // Echoインスタンス作成 //
    http.Handle("/", e)
    e.Use(middleware.Logger())
    e.Use(middleware.Recover())
    e.Use(middleware.Gzip())
    e.Use(authentication.BasicAuth()) // Basic Auth

    cGmo := api.ClientCryptoGMO{}
    cGmo.NewClientCryptoGMO()
    cCc := api.ClientCryptoCoincheck{}
    cCc.NewClientCryptoCoincheck()
    // ルーティング //

    // ヘルスチェック
    e.GET("/api/healthcheck", api.Healthcheck)

    // 貴金属の価格を取得
    e.GET("/api/metal", api.FetchMetal)

    // ISINコードを引数に基準価格を取得
    // curl -sS localhost:1323/investment-trust/JP90C000GKC6
    e.GET("/api/investment-trust/:isin", api.FetchInvestTrust)

    // 仮想通貨の価格を取得
    e.GET("/api/crypto/gmo", cGmo.FetchCryptoGMO)
    e.GET("/api/crypto/coincheck", cCc.FetchCryptoCoincheck)

    // LocalとGAEの切り替え
    appengine.Main() // GAEの場合に有効化
    //e.Start(":1323") // Localの場合に有効化
}

■ client.go

package api

var (
    defaultURLCryptoGMO       = "https://api.coin.z.com/public/v1/ticker?symbol="
    defaultURLCryptoCoincheck = "https://coincheck.com/api/ticker?symbol="
)

type ClientCryptoCoincheck struct {
    URL *string
}
type ClientCryptoGMO struct {
    URL *string
}

func (c *ClientCryptoGMO) NewClientCryptoGMO() {
    c.URL = &defaultURLCryptoGMO
}

func (c *ClientCryptoCoincheck) NewClientCryptoCoincheck() {
    c.URL = &defaultURLCryptoCoincheck
}

■ crypto_gmo.go

package api

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"

    "github.com/labstack/echo/v4"
)

type CryptoInfoGMO struct {
    DataGMO []DataGMO `json:"data"`
}

type DataGMO struct {
    Symbol    string `json:"symbol"`
    Last      string `json:"last"` // 最後の取引価格
    Timestamp string `json:"timestamp"`
}

type ResponseCryptoGMO struct {
    ResponseDataGMO []ResponseDataGMO `json:"data"`
}

type ResponseDataGMO struct {
    Symbol    string `json:"symbol"`
    Last      string `json:"last"`
    Timestamp string `json:"timestamp"`
}

func (cl *ClientCryptoGMO) FetchCryptoGMO(c echo.Context) error {
    var url string
    cig := &CryptoInfoGMO{}
    symbol := c.QueryParam("symbol") // BTC, ETH..

    // URLを場合別け(テストと外部API)
    if *cl.URL == defaultURLCryptoGMO {
        url = *cl.URL + symbol
    } else {
        url = *cl.URL
    }

    req, err := http.NewRequest("GET", url, nil)
    if err != nil {
        log.Println(err)
    }

    client := new(http.Client)
    resp, err := client.Do(req)
    if err != nil {
        log.Println(err)
    }
    defer resp.Body.Close()

    err = json.NewDecoder(resp.Body).Decode(cig)
    if err != nil {
        log.Println(err)
    }
    responseJSON := []ResponseCryptoGMO{
        {
            ResponseDataGMO: []ResponseDataGMO{
                {
                    Symbol:    cig.DataGMO[0].Symbol,
                    Last:      cig.DataGMO[0].Last,
                    Timestamp: cig.DataGMO[0].Timestamp,
                },
            },
        },
    }
    response, err := json.Marshal(responseJSON[0])
    fmt.Printf("response=%s\n", string(response))

    if err != nil {
        log.Println(err)
    }

    return c.String(http.StatusOK, string(response))
}

■ crypto_coincheck.go

package api

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"

    "github.com/labstack/echo/v4"
)

type CryptoInfoCoincheck struct {
    Last      interface{} `json:"last"`
    Timestamp int64       `json:"timestamp"`
}

type ResponseCryptoCoincheck struct {
    Symbol    string `json:"symbol"`
    Last      string `json:"last"`
    Timestamp int64  `json:"timestamp"`
}

func (cl *ClientCryptoCoincheck) FetchCryptoCoincheck(c echo.Context) error {
    var url, last string
    coincheck := &CryptoInfoCoincheck{}
    symbol := c.QueryParam("symbol") // BTC, ETH..

    // URLを場合別け(テストと外部API)
    if *cl.URL == defaultURLCryptoCoincheck {
        url = *cl.URL + symbol
    } else {
        url = *cl.URL + "/" + symbol
    }
    fmt.Printf("url=%s\n", url)

    req, err := http.NewRequest("GET", url, nil)
    if err != nil {
        log.Println(err)
    }

    client := new(http.Client)
    resp, err := client.Do(req)
    if err != nil {
        log.Println(err)
    }
    defer resp.Body.Close()

    err = json.NewDecoder(resp.Body).Decode(coincheck)
    if err != nil {
        log.Println(err)
    }
    // check type for interface{} value of last
    switch coincheck.Last.(type) {
    case float64:
        last = fmt.Sprintf("%.10g", coincheck.Last) // no use 'exponential(e+)'
    case string:
        last = coincheck.Last.(string)
    }
    responseJSON := ResponseCryptoCoincheck{
        Symbol:    symbol,
        Last:      last,
        Timestamp: coincheck.Timestamp,
    }
    response, err := json.Marshal(responseJSON)

    //fmt.Println(string(response))
    if err != nil {
        log.Println(err)
    }

    return c.String(http.StatusOK, string(response))
}

■ 確認

- GMOコインの場合
curl -sSu ${ID}:${PW} ${ENDPOINT}/api/crypto/gmo\?symbol=BTC | jq 
{
  "data": [
    {
      "symbol": "BTC_JPY",
      "last": "4592081",
      "timestamp": "2022-03-13T05:56:32.907Z"
    }
  ]
}

- Coincheckの場合
curl -sSu ${ID}:${PW} ${ENDPOINT}/api/crypto/coincheck\?symbol=btc_jpy | jq
{
  "symbol": "btc_jpy",
  "last": "4592591",
  "timestamp": 1647151043
}

デプロイについて

今回のGoアプリはGoogle App Engineにデプロイしているので、Github Actionsで自動デプロイするようにしています。 そちらについては以前に記事でまとめています。

yhidetoshi.hatenablog.com

yhidetoshi.hatenablog.com