- 投稿日:2020-05-31T21:52:55+09:00
gobot I2CでLチカ
1.はじめに
goとgobotを使って、RaspberryPiのI2Cに接続したIOエキスパンダ(MCP23017)を操作してみます。
公式にMCP23017がサポートされてるので、チュートリアルチャレンジで終わるかと思ったら、なんと、サンプルコードが書かれてなかったので、覚え書きを兼ねて簡単にまとめてみました。
実行環境
ハードウェア Raspberry Pi 4 OS Raspbian Buster go Ver.1.14.3 IOエキスパンダ MCP23017 →これを、RaspberryPiのI2C端子に接続済 $ uname -a Linux raspi4 4.19.97-v7l+ #1294 SMP Thu Jan 30 13:21:14 GMT 2020 armv7l GNU/Linux $ go version go version go1.14.3 linux/arm $ sudo i2cdetect -r -y 1 0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20: 20 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- --2.準備
インストールは別記事で解説しているので、詳細はそちらをご覧下さい。
3.LEDの点滅例
MCP23017のポートAのピン0~7につないだLEDを、流れるように点灯する例です。
こちらのドキュメントを参考12に書いてみました。重要な関数は以下の通りです。
i2c.NewMCP23017Driver
i2c.WithBus(1) ラズパイのi2cは 1
です。(1を指定している例:$ sudo i2cdetect -r -y 1
)i2c.WithAddress(0x20) MCP23017の設定にあわせます。(ここでは0x20)
WriteGPIO(ピン番号, 値, ポート名)
- 値は
0
:消灯、1
:点灯- ポート名は文字列で指定、
"A"
あるいは"B"
raspi_blink_i2c.gopackage main import ( "time" "gobot.io/x/gobot" "gobot.io/x/gobot/drivers/i2c" "gobot.io/x/gobot/platforms/raspi" ) func main() { //初期化 raspiAdaptor := raspi.NewAdaptor() //MCP23017の初期化 //I2Cバス1番、アドレス0x20 i2cexp := i2c.NewMCP23017Driver(raspiAdaptor, i2c.WithBus(1), i2c.WithAddress(0x20)) //LEDを流れるように点灯するための制御変数 //pin: ピン番号0から開始 //val: 点灯1状態から開始 var pin, val uint8 pin, val = 0, 1 //定期実行 work := func() { //100ms毎にループ gobot.Every(100*time.Millisecond, func() { //ポートAのピンを点灯/消灯 i2cexp.WriteGPIO(pin, val, "A") //ピン番号を次に移動 pin++ if pin > 7 { //ピン番号7になったら、ピン番号を0に戻す pin = 0 if val == 1 { //現在が点灯1状態なら、次は消灯0状態に val = 0 } else { //次は点灯1状態に val = 1 } } }) } robot := gobot.NewRobot("bot", []gobot.Connection{raspiAdaptor}, //デバイスに追加 []gobot.Device{i2cexp}, work, ) robot.Start() }実行
コマンドライン$ go run raspi_blink_i2c.go 2020/05/31 20:56:29 Initializing connections... 2020/05/31 20:56:29 Initializing connection RaspberryPi-308BFB50 ... 2020/05/31 20:56:29 Initializing devices... 2020/05/31 20:56:29 Initializing device MCP23017-6301D4B5 ... 2020/05/31 20:56:29 Robot bot initialized. 2020/05/31 20:56:29 Starting Robot bot ... 2020/05/31 20:56:29 Starting connections... 2020/05/31 20:56:29 Starting connection RaspberryPi-308BFB50... 2020/05/31 20:56:29 Starting devices... 2020/05/31 20:56:29 Starting device MCP23017-6301D4B5... 2020/05/31 20:56:29 Starting work... 2020/05/31 20:58:30 Stopping Robot bot ... [Ctrl-C] $下記の動画、下の方の8つのLEDが、左から右へ流れています。
4.おわりに
残課題としては、[Ctrl-C]で止めたときに、IOエキスパンダを全て消灯することです。
現状のままだと、制御が止まった時点の点灯が残ってしまいます。
こちら3を参考に、SIGINT捕捉後にIOエキスパンダのピン番号全てにLを送って消灯するプログラムを試しましたが、どうやらwork := func() {
の実行が生きている間に消灯処理を入れないとダメなようです。(今の所、解決できてません)参考資料
- 投稿日:2020-05-31T19:50:23+09:00
aws-sdk-goを通してlocal-kmsを使う
この記事を書いた経緯
aws-sdk-goを用いた案件ソース中にkmsを用いた暗号・復号化の実装があり
kmsという概念すらわからなかった私が、ローカル環境での検証に四苦八苦した時の覚書を認めたものですコマンドだけ知りたい人へ
local-kmsコンテナを立ち上げる
そのままdev環境・本番環境のKMSに作られている鍵を使おうとしても、IAMの認証をかけられてしまうので
権限のないローカル環境では暗号化・復号操作を行うことができません。ローカル環境においてkmsを用いた処理をデバッグしたい場合、local-kmsというdocker imageを用いて実現することが可能です。
他コンテナとポートが重複する場合は、環境変数PORT
を用いることで競合を回避できます$ docker run -p 8080:8081 -e PORT=8081 nsmithuk/local-kmsaws configureの作成
local-kmsを使用する際も通常のインスタンスを用いる場合と同様にクレデンシャル情報が参照されますが
適当なダミーデータで問題ありません
すでに何かしらのconfigureがローカルに作成されていればスルーで問題ないと思います$ aws configure AWS Access Key ID [None]: dummy AWS Secret Access Key [None]: dummy Default region name [None]: ap-northeast-1 Default output format [None]:create-key コマンドによりCMKを作成する
暗号・復号化に必要な、いわゆるCMK(Customer Master Key)と呼ばれるものを作成します。
この時出力されるAccountID・KeyIDなどは適当なダミー値になります$ aws kms create-key --endpoint-url http://localhost:8080 { "KeyMetadata": { "AWSAccountId": "111122223333", "KeyId": "23aeebbd-cb83-43a3-8870-a6f9ee5a4ace", "Arn": "arn:aws:kms:eu-west-2:111122223333:key/23aeebbd-cb83-43a3-8870-a6f9ee5a4ace", "CreationDate": 1590853469, "Enabled": true, "KeyUsage": "ENCRYPT_DECRYPT", "KeyState": "Enabled", "Origin": "AWS_KMS", "KeyManager": "CUSTOMER", "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT", "EncryptionAlgorithms": [ "SYMMETRIC_DEFAULT" ] } }ここで出力された
KeyID
を用いて、kms encryptを実行しますaws-sdk-goからlocal-kmsを介してkms encryptとdecryptを使う
aws-sdk-goのこちらのドキュメントに記載されているサンプルソースを拝借し、若干加筆しました。
WithEndPoint()
へlocal-kmsのコンテナのアドレスを持たせ、WithDisableSSL(true)
を使ってhttpへのアクセスを前提とさせたものを
aws.NewConfig()へメソッドチェーンさせることで、local-kmsを用いた暗号・復号化を可能としますlocalkms.gopackage main import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/kms" "fmt" "os" ) func main() { sess, err := session.NewSession() if err != nil { fmt.Println("aws newsession error: ", err) os.Exit(1) } svc := kms.New(sess, aws.NewConfig().WithRegion("ap-northeast-1").WithEndpoint("localhost:8080").WithDisableSSL(true)) keyId := "arn:aws:kms:eu-west-2:111122223333:key/23aeebbd-cb83-43a3-8870-a6f9ee5a4ace" text := "1234567890" //kms encrypt encResult, err := svc.Encrypt(&kms.EncryptInput{ KeyId: aws.String(keyId), Plaintext: []byte(text), }) if err != nil { fmt.Println("got error encrypting data: ", err) os.Exit(1) } //kms decrypt decResult, err := svc.Decrypt(&kms.DecryptInput{ CiphertextBlob: encResult.CiphertextBlob, }) if err != nil { fmt.Println("got error decrypting data: ", err) os.Exit(1) } fmt.Println("decoded Plaintext:") fmt.Println(string(decResult.Plaintext)) }ソースのビルド
$ go build localkms.goソースの実行
$ go run localkms.go decoded Plaintext: 1234567890Dockerfileでgoのソースをビルドしたい場合
このような階層の簡素なプロジェクトがあることを想定します
tree -L 3 localkms/ localkms/ ├── Dockerfile └── src ├── go.mod ├── go.sum ├── localkms ├── localkms.go └── vendor ├── github.com └── modules.txt
DockerFile
マルチステージビルドの書き方が望ましいですが、とりあえず簡素に作ってみます
DockerfileFROM golang:1.13.0 ADD ./src /go/src/local-kms WORKDIR /go/src/local-kms RUN cd /go/src/local-kms ENV AWS_ACCESS_KEY_ID=dummy ENV AWS_SECRET_ACCESS_KEY=dummy RUN go build -mod=vendor localkms.go ENTRYPOINT ["./localkms"]go.mod
ファイルを作成後、go mod vendorを実行し、srcディレクトリの下にvendorディレクトリを作成します
go.modmodule src/localkms go 1.13 require github.com/aws/aws-sdk-go v1.25.14-0.20200528180948-645efefb5bceソースの修正
コンテナの中からは
localhost
でlocal-kmsを参照できなくなるので
WithEndPointの記述を下記のように変更します(コンテナ名を記載することでそのコンテナのIPアドレスにリクエストが飛ぶようになります)19行目- svc := kms.New(sess, aws.NewConfig().WithRegion("ap-northeast-1").WithEndpoint("localhost:8080").WithDisableSSL(true)) + svc := kms.New(sess, aws.NewConfig().WithRegion("ap-northeast-1").WithEndpoint("local-kms:8081").WithDisableSSL(true))ソースのビルド
$ docker build ./ -t go-containerソースの実行
local-kmsコンテナと連携するため
--link
オプションを利用します$ docker run --link local-kms:local-kms go-container decoded Plaintext: 1234567890
ここから参考文献など
aws cliのインストール
aws kms コマンドの実行にはaws cliのインストールが必要となります
AWS CLI バージョン 1 のインストールkmsとは?
データの暗号化に利用されるCMK(Customer Master Key)の作成と管理を容易にするマネージド型サービスです。
kms encryot
CMKのKeyIDを用いて平文を暗号化します
encryptコマンドリファレンス暗号化されたテキストは、
EncryptionAlgorithm
でのエンクリプションを行った結果ががさらに
base64エンコードされて出力されます。local-kmsを使ったencryptの一例$ aws kms encrypt --key-id arn:aws:kms:eu-west-2:111122223333:key/23aeebbd-cb83-43a3-8870-a6f9ee5a4ace --plaintext "hoge" --endpoint-url http://localhost:8080 { "CiphertextBlob": "S2Fybjphd3M6a21zOmV1LXdlc3QtMjoxMTExMjIyMjMzMzM6a2V5LzIzYWVlYmJkLWNiODMtNDNhMy04ODcwLWE2ZjllZTVhNGFjZQAAAADmlRx1OQCCTk5LQoIkNseoCofCRhLXo3iPpQ2lwx8E5A==", "KeyId": "arn:aws:kms:eu-west-2:111122223333:key/23aeebbd-cb83-43a3-8870-a6f9ee5a4ace", "EncryptionAlgorithm": "SYMMETRIC_DEFAULT" }kms decrypt
CMKにより暗号化されたCiphertextを復号します。
encryptコマンドと異なり、この時keyIDを指定する必要はありません
(ciphertextblobに含まれているメタデータからこの情報を取り込むため、明示的に指定する必要がありません)また、この時の復号結果の出力である
Plaintext
はbase64エンコードされて出てくるため
本来の平文を得るためにはbase64デコードする必要がありますlocal-kmsを使ったdecryptの一例$ aws kms decrypt --ciphertext-blob fileb://<(echo 'S2Fybjphd3M6a21zOmV1LXdlc3QtMjoxMTExMjIyMjMzMzM6a2V5LzIzYWVlYmJkLWNiODMtNDNhMy04ODcwLWE2ZjllZTVhNGFjZQAAAADmlRx1OQCCTk5LQoIkNseoCofCRhLXo3iPpQ2lwx8E5A==' | base64 --decode) --endpoint-url http://localhost:8080 | jq .Plaintext --raw-output | base64 --decode hoge
- 投稿日:2020-05-31T15:14:22+09:00
git で日記を管理したい
要約
git で日記をいい感じに管理したいという思いを込めて日記管理用の cli ツールを作成いたしました。
https://github.com/komem3/go-diary背景
バージョン管理やリモートリポジトリを使って日記・日報を管理したいという思いが有りましたが、いかんせんディレクトリが汚くなってしまうのが問題でした。定期的にディレクトリを綺麗に整理する根気があれば良いのですが、三日坊主になりやすい私なんかはそれだけで日記を書くのが嫌になってしまいます。
そこで、日記のファイルをいい感じのディレクトリ構造で管理するツールを作成しました。インストール
go で作成したツールのため、以下のようにダウンロードします。
go get github.com/komem3/go-diary/cmd/diary使い方
$ diary -h Diary is a CLI libray for managing your diary. This application can format your diary directory, and make index file. Usage: diary [command] Available Commands: format Format directory help Help about any command init Initialize directory new Generate new diary Flags: -h, --help help for diary -v, --version version for diary Use "diary [command] --help" for more information about a command.今回の作成したツールには以下の3つのコマンドがあるため、それぞれ解説していきます。
init
ツールをインストールした後、日記を管理したいディレクトリで最初に実行するコマンドです。
このコマンドを実行することで、他コマンドで使われるテンプレートファイルが生成されます。$ ls $ diary init $ tree tree . └── template ├── diary.template.md └── top.template.mdこのコマンドで生成したテンプレートファイルを編集することで後程紹介するコマンドの出力を変更することができます。
new
本日の日記を生成するコマンドです。デフォルトでは本日の日付が先頭に書かれたファイルが生成されます。この時生成されるファイル名の日付情報を元にformatコマンドはフォーマットしていきます。
(記事作成日2020年5月31日)$ diary new $ cat 20200531.md 2020/05/31 (Sunday)また、先程のinitコマンドで生成したテンプレートファイルを編集することで、生成されるファイルの出力内容を変更出来ます。
echo "今日やること" >> template/diary.template.md $ diary new $ cat 20200531.md 2020/05/31 (Sunday) 今日やることformat
日記ファイルをそのファイル名に基づいてフォルダに格納します。格納後、それぞれのファイルがどこにあるか分かりやすいように
README.md
ファイルにリンクを書き込みます。$ ls 20021201.md 20200101.md 20200102.txt template $ diary format . ├── 2002 │ └── 12 │ └── 20021201.md ├── 2020 │ ├── 01 │ │ ├── 20200101.md │ │ └── 20200102.txt │ └── 02 │ └── 20200202.org ├── README.md └── template ├── diary.template.md └── top.template.md出力状態としては、実際のリポジトリを見てみるのが分かりやすいと思います。
https://github.com/komem3/go-diary/tree/master/sampleこちらも先程同様、テンプレートのファイルを変更することで好きな主力内容に変更することができます。
まとめ
git を使った日記管理を行うために、「日記の作成」「日記の整理」「日記の索引」の3つを行えるコマンドを作成いたしました。
go言語はcliツールを作り易かったのですが、日付のフォーマット指定が特殊なのがいまいちでした。そこら辺の go 特有のものや、設定の変更しやすくすることが今後の課題に思っています。
- 投稿日:2020-05-31T14:52:51+09:00
「Golang」 ×「Gorm」でシンプルに「Mysql」を操作する
「
REST API
サーバから受け取ったリクエストパラメータの内容を元にリレーショナルデータベース(Mysql
)を更新する」という流れで、説明を進めています。以下の記事の続編という形で投稿を行っておりますので、仕様などの前提条件はこちらで確認していただけるとありがたいです。
データベース(Mysql)準備
コードを実装する前に、
Mysql
上でデータベースの作成を行います。
※Mysql
自体導入していない場合は、インストールが必要です。詳細は他のサイトで解説されているのでここでは割愛します。# ルートユーザーでログイン mysql -uroot # mysql shell上 CREATE DATABASE { データベース名 }; # ログインユーザー作成 GRANT ALL PRIVILEGES ON { データベース名 }.* TO 'username'@'localhost' IDENTIFIED BY 'password'; # 一度mysqlからログアウトして、再度作成したユーザーでログイン mysql -u username -p # 念の為、データベースが作成されているか確認 SHOW DATABASES;コード実装
バージョン情報
- go 1.14
- github.com/jinzhu/gorm v1.9.12
- gopkg.in/ini.v1 v1.56.0
ORMマッピング
SQL
文でデータベースを操作することも可能ですが、今回は「コードを簡素化する」・「単純なDB操作のみ行う」ためにORM
でマッピングします。
データベースの複雑な操作が必要な場合はSQL
をそのまま書いたほうが、メンテナンスしやすいと思います。
Gorm
を採用した理由としては「スター数が多い」「ドキュメントが充実している」からです。root上でgoモジュールインストールgo get github.com/jinzhu/gormフォルダ構成
フォルダ構成merchandise_control_system ├── config │ └── config.go ├── controllers │ └── webserver.go ├── models │ └── middleware.go ├── config.ini ├── go.mod ├── go.sum └── main.goサーバポート情報・データベース接続情報をconfigに記載する
APIサーバのポート番号、DBの接続情報は一箇所にまとめ読み込むようにすることで可読性が向上します。
root上でgoモジュールインストールgo get gopkg.in/ini.v1config.ini[db] db_driver_name = mysql db_name = { データベース名 } db_user_name = { データベースユーザー名 } db_user_password = { データベースパスワード } db_host = 127.0.0.1 db_port = 3306 [api] server_port = 8080config/config.gopackage config import ( "gopkg.in/ini.v1" "log" "os" ) type ConfigList struct { DbDriverName string DbName string DbUserName string DbUserPassword string DbHost string DbPort string ServerPort int } var Config ConfigList func init() { cfg, err := ini.Load("config.ini") if err != nil { log.Printf("Failed to read file: %v", err) os.Exit(1) } Config = ConfigList{ DbDriverName: cfg.Section("db").Key("db_driver_name").String(), DbName: cfg.Section("db").Key("db_name").String(), DbUserName: cfg.Section("db").Key("db_user_name").String(), DbUserPassword: cfg.Section("db").Key("db_user_password").String(), DbHost: cfg.Section("db").Key("db_host").String(), DbPort: cfg.Section("db").Key("db_port").String(), ServerPort: cfg.Section("api").Key("server_port").MustInt(), } }コード全体
イメージとしては、
main.go
からコントローラを呼び出し、APIサーバを起動。
APIリクエストが行われると、コントローラ内からモデルが呼び出され、データベースの値を参照・更新。
最終的にレスポンスが返されるといった流れになります。main.gopackage main import ( "merchandise_control_system/controllers" ) func main() { controllers.StartWebServer() }controllers/webserver.gopackage controllers import ( "encoding/json" "fmt" "github.com/gorilla/mux" "io/ioutil" "log" "merchandise_control_system/config" "merchandise_control_system/models" "net/http" "strconv" ) type DeleteResponse struct { Id string `json:"id"` } func rootPage(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Welcome to the Go Api Server") fmt.Println("Root endpoint is hooked!") } func fetchAllItems(w http.ResponseWriter, r *http.Request) { var items []models.Item // modelの呼び出し models.GetAllItems(&items) responseBody, err := json.Marshal(items) if err != nil { log.Fatal(err) } w.Header().Set("Content-Type", "application/json") w.Write(responseBody) } func fetchSingleItem(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) id := vars["id"] var item models.Item // modelの呼び出し models.GetSingleItem(&item, id) responseBody, err := json.Marshal(item) if err != nil { log.Fatal(err) } w.Header().Set("Content-Type", "application/json") w.Write(responseBody) } func createItem(w http.ResponseWriter, r *http.Request) { reqBody, _ := ioutil.ReadAll(r.Body) var item models.Item if err := json.Unmarshal(reqBody, &item); err != nil { log.Fatal(err) } // modelの呼び出し models.InsertItem(&item) responseBody, err := json.Marshal(item) if err != nil { log.Fatal(err) } w.Header().Set("Content-Type", "application/json") w.Write(responseBody) } func deleteItem(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) id := vars["id"] // modelの呼び出し models.DeleteItem(id) responseBody, err := json.Marshal(DeleteResponse{Id: id}) if err != nil { log.Fatal(err) } w.Header().Set("Content-Type", "application/json") w.Write(responseBody) } func updateItem(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) id := vars["id"] reqBody, _ := ioutil.ReadAll(r.Body) var updateItem models.Item if err := json.Unmarshal(reqBody, &updateItem); err != nil { log.Fatal(err) } // modelの呼び出し models.UpdateItem(&updateItem, id) convertUintId, _ := strconv.ParseUint(id, 10, 64) updateItem.Model.ID = uint(convertUintId) responseBody, err := json.Marshal(updateItem) if err != nil { log.Fatal(err) } w.Header().Set("Content-Type", "application/json") w.Write(responseBody) } func StartWebServer() error { fmt.Println("Rest API with Mux Routers") router := mux.NewRouter().StrictSlash(true) router.HandleFunc("/", rootPage) router.HandleFunc("/items", fetchAllItems).Methods("GET") router.HandleFunc("/item/{id}", fetchSingleItem).Methods("GET") router.HandleFunc("/item", createItem).Methods("POST") router.HandleFunc("/item/{id}", deleteItem).Methods("DELETE") router.HandleFunc("/item/{id}", updateItem).Methods("PUT") return http.ListenAndServe(fmt.Sprintf(":%d", config.Config.ServerPort), router) }models/middleware.gopackage models import ( "fmt" "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" "log" "merchandise_control_system/config" "time" ) type Model struct { ID uint `gorm:"primary_key" json:"id"` CreatedAt *time.Time `json:"created_at"` UpdatedAt *time.Time `json:"updated_at"` DeletedAt *time.Time `json:"deleted_at"` } type Item struct { Model JanCode string `gorm:"size:255" json:"jan_code,omitempty"` ItemName string `gorm:"size:255" json:"item_name,omitempty"` Price int `json:"price,omitempty"` CategoryId int `json:"category_id,omitempty"` SeriesId int `json:"series_id,omitempty"` Stock int `json:"stock,omitempty"` Discontinued bool `json:"discontinued"` ReleaseDate *time.Time `json:"release_date,omitempty"` } var Db *gorm.DB func GetAllItems(items *[]Item) { Db.Find(&items) } func GetSingleItem(item *Item, key string) { Db.First(&item, key) } func InsertItem(item *Item) { Db.NewRecord(item) Db.Create(&item) } func DeleteItem(key string) { Db.Where("id = ?", key).Delete(&Item{}) } func UpdateItem(item *Item, key string) { Db.Model(&item).Where("id = ?", key).Updates( map[string]interface{}{ "jan_code": item.JanCode, "item_name": item.ItemName, "price": item.Price, "category_id": item.CategoryId, "series_id": item.SeriesId, "stock": item.Stock, "discontinued": item.Discontinued, "release_date": item.ReleaseDate, }) } // データベースの初期化 func init() { var err error dbConnectInfo := fmt.Sprintf( `%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=True&loc=Local`, config.Config.DbUserName, config.Config.DbUserPassword, config.Config.DbHost, config.Config.DbPort, config.Config.DbName, ) // configから読み込んだ情報を元に、データベースに接続します Db, err = gorm.Open(config.Config.DbDriverName, dbConnectInfo) if err != nil { log.Fatalln(err) } else { fmt.Println("Successfully connect database..") } // 接続したデータベースにitemsテーブルを作成します Db.Set("gorm:table_options", "ENGINE = InnoDB").AutoMigrate(&Item{}) if err != nil { fmt.Println(err.Error()) } else { fmt.Println("Successfully created table..") } }デモ
APIサーバを起動します。と同時にデータベース接続、テーブル作成が行われます。
go run main.goPostmanからリクエストを送ってみます。(curlコマンドでもOKです)。
【POST】 /item
最初はデータベースが空のため、データを作成します。
リクエストパラメータ{ "jan_code": "32739028488888", "item_name": "item_1", "price": 1000, "category_id": 1, "series_id": 1, "stock": 1000, "discontinued": true, "release_date": "2020-05-31T07:00:00.660666+09:00" }レスポンスパラメータ{ "id": 1, "created_at": "2020-05-31T13:46:09.932124+09:00", "updated_at": "2020-05-31T13:46:09.932124+09:00", "deleted_at": null, "jan_code": "32739028488888", "item_name": "item_1", "price": 1000, "category_id": 1, "series_id": 1, "stock": 1000, "discontinued": true, "release_date": "2020-05-31T07:00:00.660666+09:00" }DB(Mysql)select * from items; +----+---------------------+---------------------+------------+----------------+-----------+-------+-------------+-----------+-------+--------------+---------------------+ | id | created_at | updated_at | deleted_at | jan_code | item_name | price | category_id | series_id | stock | discontinued | release_date | +----+---------------------+---------------------+------------+----------------+-----------+-------+-------------+-----------+-------+--------------+---------------------+ | 1 | 2020-05-31 13:46:10 | 2020-05-31 13:46:10 | NULL | 32739028488888 | item_1 | 1000 | 1 | 1 | 1000 | 1 | 2020-05-31 07:00:01 | +----+---------------------+---------------------+------------+----------------+-----------+-------+-------------+-----------+-------+--------------+---------------------+ 1 row in set (0.00 sec)【GET】 /item/1
レスポンスパラメータ{ "id": 1, "created_at": "2020-05-31T13:46:10+09:00", "updated_at": "2020-05-31T13:46:10+09:00", "deleted_at": null, "jan_code": "32739028488888", "item_name": "item_1", "price": 1000, "category_id": 1, "series_id": 1, "stock": 1000, "discontinued": true, "release_date": "2020-05-31T07:00:01+09:00" }【GET】 /items
レスポンスパラメータ[ { "id": 1, "created_at": "2020-05-31T13:46:10+09:00", "updated_at": "2020-05-31T13:46:10+09:00", "deleted_at": null, "jan_code": "32739028488888", "item_name": "item_1", "price": 1000, "category_id": 1, "series_id": 1, "stock": 1000, "discontinued": true, "release_date": "2020-05-31T07:00:01+09:00" } ]【PUT】 /item/1
リクエストパラメータ{ "jan_code": "3273902878656", "item_name": "item_1_update", "price": 12000, "category_id": 1, "series_id": 1, "stock": 10, "discontinued": false, "release_date": "2020-05-30T10:30:19.978603+09:00" }レスポンスパラメータ{ "id": 1, "created_at": null, "updated_at": "2020-05-31T14:26:04.165559+09:00", "deleted_at": null, "jan_code": "3273902878656", "item_name": "item_1_update", "price": 12000, "category_id": 1, "series_id": 1, "stock": 10, "discontinued": false, "release_date": "2020-05-30T10:30:19.978603+09:00" }DB(Mysql)select * from items; +----+---------------------+---------------------+------------+---------------+---------------+-------+-------------+-----------+-------+--------------+---------------------+ | id | created_at | updated_at | deleted_at | jan_code | item_name | price | category_id | series_id | stock | discontinued | release_date | +----+---------------------+---------------------+------------+---------------+---------------+-------+-------------+-----------+-------+--------------+---------------------+ | 1 | 2020-05-31 14:25:15 | 2020-05-31 14:26:04 | NULL | 3273902878656 | item_1_update | 12000 | 1 | 1 | 10 | 0 | 2020-05-30 10:30:20 | +----+---------------------+---------------------+------------+---------------+---------------+-------+-------------+-----------+-------+--------------+---------------------+ 1 row in set (0.00 sec)【DELETE】 /item/1
レスポンスパラメータ{ "id": "1" }まとめ
Gorm
を使うと、比較的簡単にデータベース接続、テーブル作成、テーブル操作を行うことができました。
また、ORM
の特徴として、DBを他のDB(例えばPostgreSQL
)に変更したとしてもコードの変更が少なくて済むのもメリットかと思います。細かいコードメンテナンスは必要ですが、一旦最低限の「APIサーバ起動」と「DB接続」が可能になりました。
今後こちらのサーバサイドコードを成長させながら、「ビュー」部分をReactNative
で実装していきたいと思います。
- 投稿日:2020-05-31T09:44:40+09:00
GolangのWebAPIをsupervisorでデーモン化して公開する。
GolangのWebAPIをデーモン化して公開する際に役立った情報の備忘録です。
使用環境
Amazon Linux AMI 2018.03.0 (HVM), SSD Volume Type (無料使用枠)
デーモン化とは
そもそもGolangのWebAPIを公開する方法を知らず、Linux上で"GO RUN main.go"を実行すればよいのかと考えていた。この方法だと、RLoginなどで接続を閉じたタイミングで処理も終了してしまって上手く行かなかった。
調べるうちに、デーモン(UNIX系OSにおいて、常時待機しているプログラムを指す)というものに行きついた。[Golang デーモン]で検索すると多数ヒットしたので、Golangのmain.goを常駐化させる方針を立てた。
supervisor
Golangのバイナリを常駐化させる為のツール。
Nginx同様にconfファイルで設定を行う必要がある。supervisor.confの編集
sudo vi /etc/supervisord.confsupervisor.confの中身
設定項目は複数あるが、基本的に[program:XX]の設定のみで動くはず。
[program:XX] command=/home/ec2-user/hoge/mainhogeフォルダにバイナリ化したgoプログラムを配置した場合の例。
編集したら、下記の手順で再起動を行えば反映される。supervisord.confを変更した場合の再読み込み手順
sudo supervisorctl reread sudo supervisorctl update sudo supervisorctl restart XX
プログラム内で環境変数を利用している場合の注意
筆者の環境だと、supervisorでデーモン化したプログラムから、環境変数がうまく読み込まれなかった。
デーモンが起動しているプロセス上の環境変数を確認する方法としては以下の通り。
①デーモンが起動しているプロセスを確認ps ax | grep main20591 ? Sl 0:00 /home/ec2-user/hoge/main 20632 pts/1 S+ 0:00 grep --color=auto main②プロセス20591上での環境変数を確認
sudo strings /proc/20591/environ出てこない場合、環境変数への追加が必要。
環境変数追加方法
.bashrcなどのファイルに直接書き込む方法
OR
supervisord.confに書き込む方法
の2通り存在する。①~/.bashrcなどに直接書き込み
export DB_USER="sa" export DB_Name="..."②supervisord.confに書き込む方法
[program:XX]のenvironmentにカンマ区切りで環境変数をセットする。[program:XX] ... environment=DB_USER="sa",DB_Name="..."supervisord.confを変更した場合の再読み込み手順を実行すれば、環境変数が反映されるはず。
- 投稿日:2020-05-31T08:34:32+09:00
【Golang】ゴルーチン②チャネル
【Golang】ゴルーチン②チャネル
Golangの基礎学習〜Webアプリケーション作成までの学習を終えたので、復習を兼ねてまとめていく。 基礎〜応用まで。
package main //channel //スレッド同士のデータのやり取り //複数チャネルを作って、処理することも可能 import ( "fmt" ) //sからcへ送信する処理 func goroutin1(s []int, c chan int) { sum := 0 for _, v := range s { sum += v //都度、計算結果を出力 fmt.Println(sum) } //全て処理が終わったら、channelに送信 c <- sum } func main() { /* //var 変数 chan データ型 var ch chan int //送信専用と受信専用の型を指定すると厳密になり、異なる場合、コンパイルエラーになる。 //送受信に制限を設けない方がオススメ。 //受信専用チャネル var ch1 <-chan int //送信専用チャネル var ch2 chan<- int */ /* チャネルの特徴 キュー(待ち行列)の性質を持つ。 バッファサイズは格納できる領域。 キューにはFIFO(先入先出し)という性質がある。 チャネルに送信 ch <- 1 チャネルから受信 i := <- ch */ //スライス作成 s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} //channelを作成 c := make(chan int) //並行で走らせる go goroutin1(s, c) go goroutin1(s, c) //channelからデータを受け取る //データが入るまで待ってくれる。 //goroutine1でsumにデータが入ったらcに入り、xに送る x := <-c fmt.Println(x) y := <-c fmt.Println(y) }