- 投稿日:2020-06-24T23:19:25+09:00
Goでint型のスライスを昇順と降順でソートする方法
はじめに
intのスライスをソートする方法が分からなかったので調べてみました。
今回使用するスライスは、こちらs := []int{4, -1, 12, -26, 5}昇順(小さいから大きい)でソート
sort.Ints(s) fmt.Println(s) // [-26 -1 4 5 12]降順(大きいから小さい)でソート
sort.Sort(sort.Reverse(sort.IntSlice(s))) fmt.Println(s) //[12 5 4 -1 -26]おまけ
スライスの中から一番大きい数字や小さい数字を見つけるコードも書いてみます。
一番大きい数字を出力
sort.Ints(s) //合計の要素数から-1(5-1=4番目の数字) fmt.Println(s[len(s)-1]) //12一番小さい数字を出力
sort.Ints(s) //スライスの0番目 fmt.Println(s[0]) //-26さいごに
調べてみると、比較演算子を使うやり方も出てきましたが、個人的に
<>
を使うのが苦手なので(こんがらがる)、パッケージを使った方法を使っていきたいと思います。参考にした記事など
sort - The Go Programming Language
配列 - ソートしたスライスの中身の数値だけ([]なし)で取り出す方法
- 投稿日:2020-06-24T23:19:25+09:00
Goでintのスライスを昇順と降順でソートする方法
はじめに
intのスライスをソートする方法が分からなかったので調べてみました。
今回使用するスライスは、こちらs := []int{4, -1, 12, -26, 5}昇順(小さいから大きい)でソート
sort.Ints(s) fmt.Println(s) // [-26 -1 4 5 12]降順(大きいから小さい)でソート
sort.Sort(sort.Reverse(sort.IntSlice(s))) fmt.Println(s) //[12 5 4 -1 -26]おまけ
スライスの中から一番大きい数字や小さい数字を見つけるコードも書いてみます。
一番大きい数字を出力
sort.Ints(s) //合計の要素数から-1(5-1=4番目の数字) fmt.Println(s[len(s)-1]) //12一番小さい数字を出力
sort.Ints(s) //スライスの0番目 fmt.Println(s[0]) //-26さいごに
調べてみると、比較演算子を使うやり方も出てきましたが、個人的に
<>
を使うのが苦手なので(こんがらがる)、パッケージを使った方法を使っていきたいと思います。参考にした記事など
sort - The Go Programming Language
配列 - ソートしたスライスの中身の数値だけ([]なし)で取り出す方法
- 投稿日:2020-06-24T22:21:42+09:00
Hyperledger FabricのKeyゾンビ化を防ぐ(ソースコード掲載)
Hyperledger FabricでState DBのKeyを再利用させない(実はできない)
Hyperledger Fabric(以下HF)で一度削除(
DelState
)したKeyで再登録(PutState
)したらどうなるんだろう?という疑問から、次のようなシナリオを考えました。またミラ・キータ(Mira Qiita)に登場してもらいます。
- Mira QiitaをKey="JMYMIRAGINO200302"で登録(
PutState
)- 1のKeyでクエリ(
GetState
)- 1のKeyで削除(
DelState
)- 1のKeyでクエリ(
GetState
)- 1のKeyで登録(
PutState
)- 1のKeyで履歴を参照(
GetHistoryForKey
)どうなるんだろう?
あと、この投稿にてGoで書いたソースコードやシェルスクリプトを掲載します。
環境について
動作環境については次の通りです。
- Ubuntu 18.04.4 LTS
- docker-compose 1.26.0
- docker 19.03.11
- HF 2.1.1
- go 1.14.4
- PostgreSQL(Dockerイメージ) 12.3(latest)
実際にやってみる
# ./CreateAsset.sh <----- 初期登録 2020-06-24 17:50:15.434 JST [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200 # ./QueryAsset.sh <----- 一度クエリ {"year":"2003","month":"02","mileage":43871,"battery":100,"Location":"QIITA東京販売"} # ./DeleteAsset.sh <----- 削除 2020-06-24 17:53:11.100 JST [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200 # ./QueryAsset.sh <----- 削除後のクエリ Error: endorsement failure during query. response: status:500 message:"JMYMIRAGINO200302 does not exist" # ./CreateAsset.sh <----- 同じKeyで登録 2020-06-24 17:53:26.301 JST [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200 # ./QueryAsset.sh <----- またクエリ {"year":"2003","month":"02","mileage":43871,"battery":100,"Location":"QIITA東京販売"} #Oh...
削除後のクエリではdoes not exist
が返ってきますが、削除したKeyで再登録ができてクエリもできちゃいました。履歴はどうなっているのでしょう?
見てみましょう!# ./GetHistoryOfAsset.sh | jq [ { "TxId": "fed9df47d31bb4c6cec4be6c9bf154f8655f13d994eebfd709e564e36e011945", "Timestamp": "2020-06-24 08:53:26 +0000 UTC", "IsDelete": false, "Record": { "year": "2003", "month": "02", "mileage": 43871, "battery": 100, "Location": "QIITA東京販売" } }, { "TxId": "7d2853ede65564e082ab81c56e7a12331493c841993caea4ad88261706f6da9c", "Timestamp": "2020-06-24 08:53:11 +0000 UTC", "IsDelete": true, "Record": { "year": "", "month": "", "mileage": 0, "battery": 0, "Location": "" } }, { "TxId": "854ce2f67ecb11aba6ae103841da9cbf9ddb17ed412df661a8d76abc4b39a35c", "Timestamp": "2020-06-24 08:50:15 +0000 UTC", "IsDelete": false, "Record": { "year": "2003", "month": "02", "mileage": 43871, "battery": 100, "Location": "QIITA東京販売" } } ] #Oh...
履歴は下から読んでください。
廃車(DelState
)前からの履歴が残って出力されています。
Keyやidの桁数が足りなくて、削除後に一定期間が過ぎると再利用というのはモノによってはあり(携帯電話の番号がそうでしたね)ですが、ブロックチェーンでは思想的になしだと思います。対策を探してみる(なかった…)
残念ながらHFのAPIで解決できるものではなさそうです。調べた中でベストプラクティスは
DelState
を使わないです。
同じ疑問を持った人がstackoverflow
で質問していて、その回答が意外なものでした。
つまり、レコードを削除しないで「削除したフラグ」をレコードに持つ、ということですね。ちょっとだけ目からウロコ。HFがバージョンアップしたらできるようになるかもです。
もしかしたら調査不足かもしれません。偉い人教えて下さい:-)ここまでのソースコード
先ずはHFのサンプルコードをインストールします。こちらにインストール方法が書いてあります。現時点でv2.1.1は最新版なので
curl -sSL https://bit.ly/2ysbOFE | bash -s
でインストールできます。
ディレクトリfabric-samples/
に任意な名前(今回はasset
で作っています)のディレクトリを作って、そこを作業場所にします。そこへfabcar/
からnetworkDown.sh
とstartFabric.sh
をコピーしてください。その他の環境については以前の投稿に書いてあります。
- startFabric.sh: docker-composeを使って必要なサービスを起動してくれます
- networkDown.sh: 全てを無にしてくれますシェルスクリプトについて
invoke
系の機能とquery
系の機能で呼び出し方が違います。CreateAsset.sh
とCreateAsset.sh
があれば、他の機能を呼び出すシェルもコピペで作れます。function名と引数の違いだけです。
invoke
系代表CreateAsset.sh
CreateAsset.sh#!/bin/bash pushd ../test-network > /dev/null export PATH=${PWD}/../bin:$PATH export FABRIC_CFG_PATH=$PWD/../config export CORE_PEER_TLS_ENABLED=true export CORE_PEER_LOCALMSPID="Org1MSP" export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp export CORE_PEER_ADDRESS=localhost:7051 key='"JMYMIRAGINO200302"' year='"2003"' month='"02"' mileage='"43871"' battery='"100"' location='"QIITA東京販売"' args=${key},${year},${month},${mileage},${battery},${location} peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n asset --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"function":"CreateAsset","Args":['${args}']}' popd > /dev/null
query
系代表QueryAsset.sh
QueryAsset.sh#!/bin/bash pushd ../test-network > /dev/null export PATH=${PWD}/../bin:$PATH export FABRIC_CFG_PATH=$PWD/../config export CORE_PEER_TLS_ENABLED=true export CORE_PEER_LOCALMSPID="Org1MSP" export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp export CORE_PEER_ADDRESS=localhost:7051 export CHANNEL_NAME=mychannel key='"JMYMIRAGINO20030Z"' peer chaincode query -C $CHANNEL_NAME -n asset -c '{"Args":["QueryAsset",'${key}']}' popd > /dev/null肝は必ずカレントディレクトリを
test-network/
にすることです。pushdで移動してpopdで戻っています。goソースコードについて
もともとはHF1.4の時に書いたコードでHF2.1.1にあわせて書き直しました。祖先はHF1.4の
fabcar.go
です。以下に掲載します。長いです。。
DBの初期化はInitLedger
からInitDB
に移動しています。chaincodeのバージョンアップで毎回DBの初期化が走るのも嫌なので。。
UpdateAsset
とResetAsset
が似ているようで機能を分けています。UpdateAsset
には資産価値が上がるような変更にガードを入れています。ResetAsset
は「工場でバッテリーを新品に交換する」みたいなシナリオを想定したものです。コード中にコメントがなくてスミマセン:-(
個々でやってることは大したことないです。基本的には受け取った引数をHFのAPIへスルーパスしてるだけです。メソッド一覧です。引数は全て文字列で渡します。
メソッド 引数 機能 InitLedger なし HFの初期化 InitDB なし State DBの初期登録。初期データはPostgre SQLから読み込んでいます QueryAsset key keyからState DBの内容を検索・出力します QueryAssetWithOwner key QueryAssetの出力に加え、Postgre SQLからの個人情報も検索・出力します CreateAsset key, year, month, mileage, battery, location 新しいassetを作成・登録します UpdateAsset key, mileage, battery, location assetの内容を更新します。値によっては更新できません ResetAsset key, mileage, battery, location assetの内容を入力の値で強制的に更新します QueryAllAssets なし State DB内の全レコードを検索します QueryRangeAssets pagesize, key keyから始まるレコードをpagesize数分検索・出力します DeleteAsset key keyのレコードを削除します。履歴は残ります GetHistoryOfAsset key keyの取引履歴を検索・出力します asset.gopackage main import ( "encoding/json" "fmt" "strconv" "time" "github.com/hyperledger/fabric-contract-api-go/contractapi" "database/sql" _ "github.com/lib/pq" ) // Define the Smart Contract structure type SmartContract struct { contractapi.Contract } // データ構造の定義 type Asset struct { Year string `json:"year"` // 初度登録年 Month string `json:"month"` // 初度登録月 Mileage int `json:"mileage"` // 走行距離(km) Battery int `json:"battery"` // バッテリーライフ(%) Location string `jasn:"location"` // 位置 } type AssetWithOwner struct { Name string // 名前 Country string // 国 City string // 都道府県 Addr string // 市区町村 Record *Asset } // クエリ結果(レーコード検索) type QueryResult struct { Key string // レコードID(VINコード) Record *Asset } // クエリ結果(履歴検索) type GetHisResult struct { TxId string // トランザクションID Timestamp string // タイムスタンプ IsDelete bool // 削除(廃車)フラグ Record *Asset } func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error { fmt.Println("InitLedger") return nil } func (s *SmartContract) InitDB(ctx contractapi.TransactionContextInterface) error { fmt.Println("InitDB") db, err := sql.Open("postgres", "host=pgsql port=5432 user=postgres password=secret dbname=asset sslmode=disable") defer db.Close() if err != nil { return fmt.Errorf("sql.Open: %s", err.Error()) } rows, err := db.Query("SELECT * FROM asset;") if err != nil { return fmt.Errorf("sql.Query: %s", err.Error()) } var id string for rows.Next() { var asset Asset rows.Scan(&id, &asset.Year, &asset.Month, &asset.Mileage, &asset.Battery, &asset.Location) assetAsBytes, _ := json.Marshal(asset) ctx.GetStub().PutState(id, assetAsBytes) } return nil } func (s *SmartContract) QueryAsset(ctx contractapi.TransactionContextInterface, key string) (*Asset, error) { fmt.Println("QueryAsset") assetAsBytes, err := ctx.GetStub().GetState(key) if err != nil { return nil, fmt.Errorf("Failed to read from world state. %s", err.Error()) } if assetAsBytes == nil { return nil, fmt.Errorf("%s does not exist", key) } asset := new(Asset) _ = json.Unmarshal(assetAsBytes, asset) return asset, nil } func (s *SmartContract) QueryAssetWithOwner(ctx contractapi.TransactionContextInterface, key string) (*AssetWithOwner, error) { fmt.Println("QueryAssetWithOwner") assetAsBytes, err := ctx.GetStub().GetState(key) if err != nil { return nil, fmt.Errorf("Failed to read from world state. %s", err.Error()) } if assetAsBytes == nil { return nil, fmt.Errorf("%s does not exist", key) } asset := new(Asset) _ = json.Unmarshal(assetAsBytes, asset) db, err := sql.Open("postgres", "host=pgsql port=5432 user=postgres password=secret dbname=asset sslmode=disable") defer db.Close() if err != nil { return nil, fmt.Errorf("sql.Open: %s", err.Error()) } sql := "SELECT * FROM owner WHERE id = '" + key + "';" rows, err := db.Query(sql) if err != nil { return nil, fmt.Errorf("db.Query: %s", err.Error()) } var id string awo := new(AssetWithOwner) for rows.Next() { rows.Scan(&id, &awo.Name, &awo.Country, &awo.City, &awo.Addr) awo.Record = asset } return awo, nil } func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, key string, year string, month string, sMileage string, sBattery string, location string) error { fmt.Println("CreateAsset") mileage, _ :=strconv.Atoi(sMileage) battery, _ :=strconv.Atoi(sBattery) asset := Asset{ Year: year, Month: month, Mileage: mileage, Battery: battery, Location: location, } assetAsBytes, _ := json.Marshal(asset) return ctx.GetStub().PutState(key, assetAsBytes) } func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, key string, sMileage string, sBattery string, location string) error { fmt.Println("UpdateAsset") assetAsBytes, _ := ctx.GetStub().GetState(key) asset := new(Asset) json.Unmarshal(assetAsBytes, &asset) if sMileage != "" { mileage, _ := strconv.Atoi(sMileage) // input mileage (km) if mileage < asset.Mileage { return fmt.Errorf("Invalid argument.") } asset.Mileage = mileage } if sBattery != "" { battery, _ := strconv.Atoi(sBattery) // input battery (%) if battery > (asset.Battery + 5) { return fmt.Errorf("Invalid argument.") } asset.Battery = battery } if location != "" { asset.Location = location } assetAsBytes, _ = json.Marshal(asset) return ctx.GetStub().PutState(key, assetAsBytes) } func (s *SmartContract) ResetAsset(ctx contractapi.TransactionContextInterface, key string, sMileage string, sBattery string, location string) error { fmt.Println("ResetAsset") assetAsBytes, _ := ctx.GetStub().GetState(key) asset := new(Asset) json.Unmarshal(assetAsBytes, &asset) if sMileage != "" { mileage, _ := strconv.Atoi(sMileage) // input mileage (km) asset.Mileage = mileage } if sBattery != "" { battery, _ := strconv.Atoi(sBattery) // input battery (%) asset.Battery = battery } if location != "" { asset.Location = location } assetAsBytes, _ = json.Marshal(asset) return ctx.GetStub().PutState(key, assetAsBytes) } func (s *SmartContract) QueryAllAssets(ctx contractapi.TransactionContextInterface) ([]QueryResult, error) { fmt.Println("QueryAllAssets") startKey := "" endKey := "" resultsIterator, err := ctx.GetStub().GetStateByRange(startKey, endKey) if err != nil { return nil, fmt.Errorf("ctx.GetStub().GetStateByRange: %s", err.Error()) } defer resultsIterator.Close() results := []QueryResult{} for resultsIterator.HasNext() { queryResponse, err := resultsIterator.Next() if err != nil { return nil, err } asset := new(Asset) _ = json.Unmarshal(queryResponse.Value, asset) queryResult := QueryResult{Key: queryResponse.Key, Record: asset} results = append(results, queryResult) } return results, nil } func (s *SmartContract) QueryRangeAssets(ctx contractapi.TransactionContextInterface, sPageSize string, bookmark string) ([]QueryResult, error) { fmt.Println("QueryRangeAssets") startKey := "" endKey := "" var pageSize int32 tmpSize, _ := strconv.Atoi(sPageSize) pageSize = int32(tmpSize) resultsIterator, _, err := ctx.GetStub().GetStateByRangeWithPagination(startKey, endKey, pageSize, bookmark) if err != nil { return nil, fmt.Errorf("ctx.GetStub().GetStateByRangeWithPagination: %s", err.Error()) } defer resultsIterator.Close() results := []QueryResult{} for resultsIterator.HasNext() { queryResponse, err := resultsIterator.Next() if err != nil { return nil, err } asset := new(Asset) _ = json.Unmarshal(queryResponse.Value, asset) queryResult := QueryResult{Key: queryResponse.Key, Record: asset} results = append(results, queryResult) } return results, nil } func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, key string) error { fmt.Println("DeleteAsset") return ctx.GetStub().DelState(key) } func (s *SmartContract) GetHistoryOfAsset(ctx contractapi.TransactionContextInterface, key string) ([]GetHisResult, error) { fmt.Println("GetHistoryOfAsset") resultsIterator, err := ctx.GetStub().GetHistoryForKey(key) if err != nil { return nil, fmt.Errorf("ctx.GetStub().GetHistoryForKey: %s", err.Error()) } defer resultsIterator.Close() results := []GetHisResult{} for resultsIterator.HasNext() { queryResponse, err := resultsIterator.Next() if err != nil { return nil, err } asset := new(Asset) _ = json.Unmarshal(queryResponse.Value, asset) t := time.Unix(queryResponse.Timestamp.Seconds, 0).String() //t := time.Unix(queryResponse.Timestamp.Seconds, int64(queryResponse.Timestamp.Nanos)).String() queryResult := GetHisResult{TxId:queryResponse.TxId, Timestamp:t, IsDelete:queryResponse.IsDelete, Record: asset} results = append(results, queryResult) } return results, nil } func main() { chaincode, err := contractapi.NewChaincode(new(SmartContract)) if err != nil { fmt.Printf("Error create asset chaincode: %s", err.Error()) return } if err := chaincode.Start(); err != nil { fmt.Printf("Error starting asset chaincode: %s", err.Error()) } }最後に
HFが1.4から2.1.1になってコードがシンプルに書きやすくなりました。1.4までは文字列バッファに自分でゴリゴリとデータを詰めていたのですが、その辺りがスマートになっています。引数も1つの文字列配列から引数ごとに分離しました。
参考になれば幸いです。
- 投稿日:2020-06-24T22:21:42+09:00
Hyperledger FabricのKeyゾンビ化を防ぐ(全ソースコード掲載)
Hyperledger FabricでState DBのKeyを再利用させない(実はできない)
Hyperledger Fabric(以下HF)で一度削除(
DelState
)したKeyで再登録(PutState
)したらどうなるんだろう?という疑問から、次のようなシナリオを考えました。またミラ・キータ(Mira Qiita)に登場してもらいます。
- Mira QiitaをKey="JMYMIRAGINO200302"で登録(
PutState
)- 1のKeyでクエリ(
GetState
)- 1のKeyで削除(
DelState
)- 1のKeyでクエリ(
GetState
)- 1のKeyで登録(
PutState
)- 1のKeyで履歴を参照(
GetHistoryForKey
)どうなるんだろう?
あと、この投稿にてGoで書いたソースコードやシェルスクリプトを掲載します。
環境について
動作環境については次の通りです。
- Ubuntu 18.04.4 LTS
- docker-compose 1.26.0
- docker 19.03.11
- HF 2.1.1
- go 1.14.4
- PostgreSQL(Dockerイメージ) 12.3(latest)
実際にやってみる
# ./CreateAsset.sh <----- 初期登録 2020-06-24 17:50:15.434 JST [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200 # ./QueryAsset.sh <----- 一度クエリ {"year":"2003","month":"02","mileage":43871,"battery":100,"Location":"QIITA東京販売"} # ./DeleteAsset.sh <----- 削除 2020-06-24 17:53:11.100 JST [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200 # ./QueryAsset.sh <----- 削除後のクエリ Error: endorsement failure during query. response: status:500 message:"JMYMIRAGINO200302 does not exist" # ./CreateAsset.sh <----- 同じKeyで登録 2020-06-24 17:53:26.301 JST [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200 # ./QueryAsset.sh <----- またクエリ {"year":"2003","month":"02","mileage":43871,"battery":100,"Location":"QIITA東京販売"} #Oh...
削除後のクエリではdoes not exist
が返ってきますが、削除したKeyで再登録ができてクエリもできちゃいました。履歴はどうなっているのでしょう?
見てみましょう!# ./GetHistoryOfAsset.sh | jq [ { "TxId": "fed9df47d31bb4c6cec4be6c9bf154f8655f13d994eebfd709e564e36e011945", "Timestamp": "2020-06-24 08:53:26 +0000 UTC", "IsDelete": false, "Record": { "year": "2003", "month": "02", "mileage": 43871, "battery": 100, "Location": "QIITA東京販売" } }, { "TxId": "7d2853ede65564e082ab81c56e7a12331493c841993caea4ad88261706f6da9c", "Timestamp": "2020-06-24 08:53:11 +0000 UTC", "IsDelete": true, "Record": { "year": "", "month": "", "mileage": 0, "battery": 0, "Location": "" } }, { "TxId": "854ce2f67ecb11aba6ae103841da9cbf9ddb17ed412df661a8d76abc4b39a35c", "Timestamp": "2020-06-24 08:50:15 +0000 UTC", "IsDelete": false, "Record": { "year": "2003", "month": "02", "mileage": 43871, "battery": 100, "Location": "QIITA東京販売" } } ] #Oh...
履歴は下から読んでください。
廃車(DelState
)前からの履歴が残って出力されています。
Keyやidの桁数が足りなくて、削除後に一定期間が過ぎると再利用というのはモノによってはあり(携帯電話の番号がそうでしたね)ですが、ブロックチェーンでは思想的になしだと思います。対策を探してみる(なかった…)
残念ながらHFのAPIで解決できるものではなさそうです。調べた中でベストプラクティスは
DelState
を使わないです。
同じ疑問を持った人がstackoverflow
で質問していて、その回答が意外なものでした。
つまり、レコードを削除しないで「削除したフラグ」をレコードに持つ、ということですね。ちょっとだけ目からウロコ。HFがバージョンアップしたらできるようになるかもです。
もしかしたら調査不足かもしれません。偉い人教えて下さい:-)ここまでのソースコード
先ずはHFのサンプルコードをインストールします。こちらにインストール方法が書いてあります。現時点でv2.1.1は最新版なので
curl -sSL https://bit.ly/2ysbOFE | bash -s
でインストールできます。
ディレクトリfabric-samples/
に任意な名前(今回はasset
で作っています)のディレクトリを作って、そこを作業場所にします。そこへfabcar/
からnetworkDown.sh
とstartFabric.sh
をコピーしてください。その他の環境については以前の投稿に書いてあります。
- startFabric.sh: docker-composeを使って必要なサービスを起動してくれます
- networkDown.sh: 全てを無にしてくれますシェルスクリプトについて
invoke
系の機能とquery
系の機能で呼び出し方が違います。CreateAsset.sh
とCreateAsset.sh
があれば、他の機能を呼び出すシェルもコピペで作れます。function名と引数の違いだけです。
invoke
系代表CreateAsset.sh
CreateAsset.sh#!/bin/bash pushd ../test-network > /dev/null export PATH=${PWD}/../bin:$PATH export FABRIC_CFG_PATH=$PWD/../config export CORE_PEER_TLS_ENABLED=true export CORE_PEER_LOCALMSPID="Org1MSP" export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp export CORE_PEER_ADDRESS=localhost:7051 key='"JMYMIRAGINO200302"' year='"2003"' month='"02"' mileage='"43871"' battery='"100"' location='"QIITA東京販売"' args=${key},${year},${month},${mileage},${battery},${location} peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n asset --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"function":"CreateAsset","Args":['${args}']}' popd > /dev/null
query
系代表QueryAsset.sh
QueryAsset.sh#!/bin/bash pushd ../test-network > /dev/null export PATH=${PWD}/../bin:$PATH export FABRIC_CFG_PATH=$PWD/../config export CORE_PEER_TLS_ENABLED=true export CORE_PEER_LOCALMSPID="Org1MSP" export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp export CORE_PEER_ADDRESS=localhost:7051 export CHANNEL_NAME=mychannel key='"JMYMIRAGINO20030Z"' peer chaincode query -C $CHANNEL_NAME -n asset -c '{"Args":["QueryAsset",'${key}']}' popd > /dev/null肝は必ずカレントディレクトリを
test-network/
にすることです。pushdで移動してpopdで戻っています。goソースコードについて
もともとはHF1.4の時に書いたコードでHF2.1.1にあわせて書き直しました。祖先はHF1.4の
fabcar.go
です。以下に掲載します。長いです。。
DBの初期化はInitLedger
からInitDB
に移動しています。chaincodeのバージョンアップで毎回DBの初期化が走るのも嫌なので。。
UpdateAsset
とResetAsset
が似ているようで機能を分けています。UpdateAsset
には資産価値が上がるような変更にガードを入れています。ResetAsset
は「工場でバッテリーを新品に交換する」みたいなシナリオを想定したものです。コード中にコメントがなくてスミマセン:-(
個々でやってることは大したことないです。基本的には受け取った引数をHFのAPIへスルーパスしてるだけです。メソッド一覧です。引数は全て文字列で渡します。
メソッド 引数 機能 InitLedger なし HFの初期化 InitDB なし State DBの初期登録。初期データはPostgre SQLから読み込んでいます QueryAsset key keyからState DBの内容を検索・出力します QueryAssetWithOwner key QueryAssetの出力に加え、Postgre SQLからの個人情報も検索・出力します CreateAsset key, year, month, mileage, battery, location 新しいassetを作成・登録します UpdateAsset key, mileage, battery, location assetの内容を更新します。値によっては更新できません ResetAsset key, mileage, battery, location assetの内容を入力の値で強制的に更新します QueryAllAssets なし State DB内の全レコードを検索します QueryRangeAssets pagesize, key keyから始まるレコードをpagesize数分検索・出力します DeleteAsset key keyのレコードを削除します。履歴は残ります GetHistoryOfAsset key keyの取引履歴を検索・出力します asset.gopackage main import ( "encoding/json" "fmt" "strconv" "time" "github.com/hyperledger/fabric-contract-api-go/contractapi" "database/sql" _ "github.com/lib/pq" ) // Define the Smart Contract structure type SmartContract struct { contractapi.Contract } // データ構造の定義 type Asset struct { Year string `json:"year"` // 初度登録年 Month string `json:"month"` // 初度登録月 Mileage int `json:"mileage"` // 走行距離(km) Battery int `json:"battery"` // バッテリーライフ(%) Location string `jasn:"location"` // 位置 } type AssetWithOwner struct { Name string // 名前 Country string // 国 City string // 都道府県 Addr string // 市区町村 Record *Asset } // クエリ結果(レーコード検索) type QueryResult struct { Key string // レコードID(VINコード) Record *Asset } // クエリ結果(履歴検索) type GetHisResult struct { TxId string // トランザクションID Timestamp string // タイムスタンプ IsDelete bool // 削除(廃車)フラグ Record *Asset } func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error { fmt.Println("InitLedger") return nil } func (s *SmartContract) InitDB(ctx contractapi.TransactionContextInterface) error { fmt.Println("InitDB") db, err := sql.Open("postgres", "host=pgsql port=5432 user=postgres password=secret dbname=asset sslmode=disable") defer db.Close() if err != nil { return fmt.Errorf("sql.Open: %s", err.Error()) } rows, err := db.Query("SELECT * FROM asset;") if err != nil { return fmt.Errorf("sql.Query: %s", err.Error()) } var id string for rows.Next() { var asset Asset rows.Scan(&id, &asset.Year, &asset.Month, &asset.Mileage, &asset.Battery, &asset.Location) assetAsBytes, _ := json.Marshal(asset) ctx.GetStub().PutState(id, assetAsBytes) } return nil } func (s *SmartContract) QueryAsset(ctx contractapi.TransactionContextInterface, key string) (*Asset, error) { fmt.Println("QueryAsset") assetAsBytes, err := ctx.GetStub().GetState(key) if err != nil { return nil, fmt.Errorf("Failed to read from world state. %s", err.Error()) } if assetAsBytes == nil { return nil, fmt.Errorf("%s does not exist", key) } asset := new(Asset) _ = json.Unmarshal(assetAsBytes, asset) return asset, nil } func (s *SmartContract) QueryAssetWithOwner(ctx contractapi.TransactionContextInterface, key string) (*AssetWithOwner, error) { fmt.Println("QueryAssetWithOwner") assetAsBytes, err := ctx.GetStub().GetState(key) if err != nil { return nil, fmt.Errorf("Failed to read from world state. %s", err.Error()) } if assetAsBytes == nil { return nil, fmt.Errorf("%s does not exist", key) } asset := new(Asset) _ = json.Unmarshal(assetAsBytes, asset) db, err := sql.Open("postgres", "host=pgsql port=5432 user=postgres password=secret dbname=asset sslmode=disable") defer db.Close() if err != nil { return nil, fmt.Errorf("sql.Open: %s", err.Error()) } sql := "SELECT * FROM owner WHERE id = '" + key + "';" rows, err := db.Query(sql) if err != nil { return nil, fmt.Errorf("db.Query: %s", err.Error()) } var id string awo := new(AssetWithOwner) for rows.Next() { rows.Scan(&id, &awo.Name, &awo.Country, &awo.City, &awo.Addr) awo.Record = asset } return awo, nil } func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, key string, year string, month string, sMileage string, sBattery string, location string) error { fmt.Println("CreateAsset") mileage, _ :=strconv.Atoi(sMileage) battery, _ :=strconv.Atoi(sBattery) asset := Asset{ Year: year, Month: month, Mileage: mileage, Battery: battery, Location: location, } assetAsBytes, _ := json.Marshal(asset) return ctx.GetStub().PutState(key, assetAsBytes) } func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, key string, sMileage string, sBattery string, location string) error { fmt.Println("UpdateAsset") assetAsBytes, _ := ctx.GetStub().GetState(key) asset := new(Asset) json.Unmarshal(assetAsBytes, &asset) if sMileage != "" { mileage, _ := strconv.Atoi(sMileage) // input mileage (km) if mileage < asset.Mileage { return fmt.Errorf("Invalid argument.") } asset.Mileage = mileage } if sBattery != "" { battery, _ := strconv.Atoi(sBattery) // input battery (%) if battery > (asset.Battery + 5) { return fmt.Errorf("Invalid argument.") } asset.Battery = battery } if location != "" { asset.Location = location } assetAsBytes, _ = json.Marshal(asset) return ctx.GetStub().PutState(key, assetAsBytes) } func (s *SmartContract) ResetAsset(ctx contractapi.TransactionContextInterface, key string, sMileage string, sBattery string, location string) error { fmt.Println("ResetAsset") assetAsBytes, _ := ctx.GetStub().GetState(key) asset := new(Asset) json.Unmarshal(assetAsBytes, &asset) if sMileage != "" { mileage, _ := strconv.Atoi(sMileage) // input mileage (km) asset.Mileage = mileage } if sBattery != "" { battery, _ := strconv.Atoi(sBattery) // input battery (%) asset.Battery = battery } if location != "" { asset.Location = location } assetAsBytes, _ = json.Marshal(asset) return ctx.GetStub().PutState(key, assetAsBytes) } func (s *SmartContract) QueryAllAssets(ctx contractapi.TransactionContextInterface) ([]QueryResult, error) { fmt.Println("QueryAllAssets") startKey := "" endKey := "" resultsIterator, err := ctx.GetStub().GetStateByRange(startKey, endKey) if err != nil { return nil, fmt.Errorf("ctx.GetStub().GetStateByRange: %s", err.Error()) } defer resultsIterator.Close() results := []QueryResult{} for resultsIterator.HasNext() { queryResponse, err := resultsIterator.Next() if err != nil { return nil, err } asset := new(Asset) _ = json.Unmarshal(queryResponse.Value, asset) queryResult := QueryResult{Key: queryResponse.Key, Record: asset} results = append(results, queryResult) } return results, nil } func (s *SmartContract) QueryRangeAssets(ctx contractapi.TransactionContextInterface, sPageSize string, bookmark string) ([]QueryResult, error) { fmt.Println("QueryRangeAssets") startKey := "" endKey := "" var pageSize int32 tmpSize, _ := strconv.Atoi(sPageSize) pageSize = int32(tmpSize) resultsIterator, _, err := ctx.GetStub().GetStateByRangeWithPagination(startKey, endKey, pageSize, bookmark) if err != nil { return nil, fmt.Errorf("ctx.GetStub().GetStateByRangeWithPagination: %s", err.Error()) } defer resultsIterator.Close() results := []QueryResult{} for resultsIterator.HasNext() { queryResponse, err := resultsIterator.Next() if err != nil { return nil, err } asset := new(Asset) _ = json.Unmarshal(queryResponse.Value, asset) queryResult := QueryResult{Key: queryResponse.Key, Record: asset} results = append(results, queryResult) } return results, nil } func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, key string) error { fmt.Println("DeleteAsset") return ctx.GetStub().DelState(key) } func (s *SmartContract) GetHistoryOfAsset(ctx contractapi.TransactionContextInterface, key string) ([]GetHisResult, error) { fmt.Println("GetHistoryOfAsset") resultsIterator, err := ctx.GetStub().GetHistoryForKey(key) if err != nil { return nil, fmt.Errorf("ctx.GetStub().GetHistoryForKey: %s", err.Error()) } defer resultsIterator.Close() results := []GetHisResult{} for resultsIterator.HasNext() { queryResponse, err := resultsIterator.Next() if err != nil { return nil, err } asset := new(Asset) _ = json.Unmarshal(queryResponse.Value, asset) t := time.Unix(queryResponse.Timestamp.Seconds, 0).String() //t := time.Unix(queryResponse.Timestamp.Seconds, int64(queryResponse.Timestamp.Nanos)).String() queryResult := GetHisResult{TxId:queryResponse.TxId, Timestamp:t, IsDelete:queryResponse.IsDelete, Record: asset} results = append(results, queryResult) } return results, nil } func main() { chaincode, err := contractapi.NewChaincode(new(SmartContract)) if err != nil { fmt.Printf("Error create asset chaincode: %s", err.Error()) return } if err := chaincode.Start(); err != nil { fmt.Printf("Error starting asset chaincode: %s", err.Error()) } }最後に
HFが1.4から2.1.1になってコードがシンプルに書きやすくなりました。1.4までは文字列バッファに自分でゴリゴリとデータを詰めていたのですが、その辺りがスマートになっています。引数も1つの文字列配列から引数ごとに分離しました。
参考になれば幸いです。
- 投稿日:2020-06-24T21:36:26+09:00
Go のオレオレ linter を golang.org/x/tools/go/analysis 使って作成する
ゴール
x/tools/go/analysis
package を使って、linter を実装できるようになる背景
Go は言語本体の簡潔さ、強力さはもちろんのことですが、周辺のツール/環境が充実していることでも知られています。
linter についても、標準でgo vet
や、標準の linter の runnner として、golangci-lint
というサードパーティのツールも利用されています。さらに、Go ではそのような linter 、及び静的解析を実装するためのライブラリが準標準 (
golang.org./x
以下)で公開されています。
今回は、そのライブラリを使ってユーザ独自の linter を作る方法を紹介します。環境
- go1.14.4
- skeleton v1.2.1
gostaticanalysis/skeleton で linter のひな形を作る
go/analysis
package を使った linter の作成は、以下のツールでスケルトンを作成することができます。
https://github.com/gostaticanalysis/skeletonskeleton が出力するスケルトンの内容
以下のようにファイル一式が出力されます。
. │ hogefuga.go │ hogefuga_test.go │ ├─cmd │ └─hogefuga │ main.go │ varinit.exe │ ├─plugin │ └─hogefuga │ main.go │ └─testdata └─src └─a a.go
hogefuga.go/hogefuga_test.go
へ、linter の実装とテストを記載するcmd
以下はgo vet
等から利用する実行体を作成するコマンドplugin
以下はgolangci-lint
から利用するプラグインを作成するコマンドtestdata
以下へhogefuga_test.go
から参照されるテストデータを作成するこれらで、linter を作るための基本的な材料は全てそろっていて、
すぐに linter の作成を始めることができます。独自 linter を実装する
というわけで、早速 linter を作っていきます。
今回は簡単なサンプルとして、識別子に hoge/fuga のような意味のない名前を与えている場合、
それを linter の指摘として出力するというものを作ってみます。テストデータを作成する
まず、linter の指摘を受けるような Go のコードをテストデータとして用意します。
go/analysis
package は、go/analysis/analysistest
package を使って、テストができます。
そのテスト package では、// want "期待値"
というコメントで、 その行にどんな linter の指摘を期待するかを記載してテストすることができます。a.gopackage a import "fmt" func a() { var hoge string = "hoge" // want "identifier is meaningless" fuga(hoge) // want "identifier is meaningless" "identifier is meaningless" } func fuga(s string) { // want "identifier is meaningless" fmt.Println(s) }なお、テストコード側は以下のようになっており、複数 package を試すなどの場合除いては、
スケルトンの出力したコードからの変更は不要です。hogefuga_test.gopackage varinit_test import ( "testing" "github.com/akif999/varinit" "golang.org/x/tools/go/analysis/analysistest" ) // TestAnalyzer is a test for Analyzer. func TestAnalyzer(t *testing.T) { testdata := analysistest.TestData() analysistest.Run(t, testdata, varinit.Analyzer, "a") }linter の処理を実装する
以下のように実装しました。
ユーザで書いているのは、ast node
を*ast.Ident
(識別子) へ絞り込むフィルタを作成していることと、
そのフィルタで ast を走査して、各*ast.Ident
(識別子) の要素に対して、指摘を出力するかを判定します。ちなみに、本記事では取り上げていませんが、指摘を出力するだけではなく suggest fix (= 修正案を提示) したり、
lint すると同時に修正したりということもできるようです。hogefuga.gopackage varinit import ( "go/ast" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/ast/inspector" ) const doc = "varinit is ..." // Analyzer is ... var Analyzer = &analysis.Analyzer{ Name: "varinit", Doc: doc, Run: run, Requires: []*analysis.Analyzer{ inspect.Analyzer, }, } func run(pass *analysis.Pass) (interface{}, error) { inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) nodeFilter := []ast.Node{ (*ast.Ident)(nil), // nodefileter の定義 } inspect.Preorder(nodeFilter, func(n ast.Node) { switch n := n.(type) { case *ast.Ident: // 識別子に対して処理を記載する if isHoge(n.Name) || isFuga(n.Name) { // hoge、fuga に識別子が該当するかを確認する pass.Reportf(n.Pos(), "identifier is meaningless") // 指摘ありの場合、指摘を出力する API をコールする } } }) return nil, nil } func isHoge(s string) bool { return s == "hoge" } func isFuga(s string) bool { return s == "fuga" }独自 linter の動かし方
独自 linter は、 go vet から動かすことができます。
golangci-lint
の plugin を作ることができるようになっていますが、
記事執筆時点では、Windows/amd64 環境では build ができませんでした。ちなみに、
go vet
本体の linter も、ある時点でgo/analysis
package を使ってリライトされています。C:\Users\aki01\work\src\github.com\akif999\hogefuga\cmd\hogefuga>go vet --vettool=hogefuga.exe ..\..\testdata\src\a # _/C_/Users/aki01/work/src/github.com/akif999/hogefuga/testdata/src/a ..\..\testdata\src\a\a.go:6:6: identifier is meaningless ..\..\testdata\src\a\a.go:8:2: identifier is meaningless ..\..\testdata\src\a\a.go:8:7: identifier is meaningless ..\..\testdata\src\a\a.go:11:6: identifier is meaninglessまとめ
go/analysis package
を使うと、他の linter と共通の I/F で linter をつくることができる- linter の作成/テストをライブラリが支援してくれるので作るのが簡単
gostaticanalysis/skeleton
を使うとスケルトンを出力してくれて、更に作りやすい- 現場独自の linter を作って、自分たちのプロダクトの品質とスピードの向上に貢献しませんか??
- 投稿日:2020-06-24T21:02:21+09:00
CORS(preflight request)にハマったけど解決した話
CORS(preflight request)にハマったので、解決方法を備忘録として残しておきます。
エラーが起きた場面
異なるドメインからHttpリクエストを送る場合は、CORSに注意だよなぁ。
サーバー側のレスポンスで、ヘッダーをつけてあげれば良いんだろう。
簡単じゃん。クライアント側
何かしらのデータをjasonでPOSTする。
var xmlhttp = new XMLHttpRequest(); xmlhttp.open("POST", API_ENDPOINT); xmlhttp.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); xmlhttp.send(JSON.stringify(data));APIサーバー側
サーバー側では、レスポンスのヘッダーを付けてあげる。
func (a *API) HandleFunc(w http.ResponseWriter, r *http.Request) { //略... w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE") w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") //略... fmt.Fprint(w, "ok") }結果、エラー...!
当然、結果が正常に返ってくると思いきや
プリフライトリクエスト?がどうのこうのといったエラーが出た。。。なんやこれ。。。Access to XMLHttpRequest at 'http://localhost:8080/api' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.CORS(preflight request)エラー解決
preflight requestとは?
リクエストによっては CORS プリフライトを引き起こさないものがあります。これをこの記事では「単純リクエスト」と呼んでいますが、 (CORS を定義している) Fetch 仕様書ではこの用語を使用していません。 「単純リクエスト」は、以下のすべての条件を満たすものです。
・許可されているメソッドのうちの一つであること。
GET
HEAD
POST
・ユーザーエージェントによって自動的に設定されたヘッダー (たとえば Connection、 User-Agent、 または Fetch 仕様書で「禁止ヘッダー名」として定義されているヘッダー) を除いて、手動で設定できるヘッダーは、 Fetch 仕様書で「CORS セーフリストリクエストヘッダー」として定義されている以下のヘッダーだけです。
Accept
Accept-Language
Content-Language
Content-Type (但し、下記の要件を満たすもの)
DPR
Downlink
Save-Data
Viewport-Width
Width
・Content-Type ヘッダーでは以下の値のみが許可されています。
application/x-www-form-urlencoded
multipart/form-data
text/plain
・リクエストに使用されるどの XMLHttpRequestUpload にもイベントリスナーが登録されていないこと。これらは正しく XMLHttpRequest.upload を使用してアクセスされます。
・リクエストに ReadableStream オブジェクトが使用されていないこと。
https://developer.mozilla.org/ja/docs/Web/HTTP/CORS#Preflighted_requests上記の条件を満たさないものはpreflight requestがメインのリクエストの前に行われます。
今回は、POST
ですが、"Content-Type", "application/json"
をヘッダーに付けているので、プリフライトリクエストが起きました。
画像の最初のリクエストがpreflight requestです。
ここでは、実際にはOPTIONS
メソッドがpreflight requestとして走ります。
解決方法
OPTIONS
メソッドの時は、http.StatusOK
を返すようにしたら解決しました!func (a *API) HandleFunc(w http.ResponseWriter, r *http.Request) { //略... w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE") w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") if r.Method == "OPTIONS" { w.WriteHeader(http.StatusOK) return } //略... fmt.Fprint(w, "ok") }
- 投稿日:2020-06-24T09:18:50+09:00
go修行10日目 embeded
embeded
- Go言語には継承がなくてその代わり
- VertexをVertex3Dがembededしている
package main import "fmt" type Vertex struct{ x, y int } func (v Vertex) Area() int{ return v.x * v.y } func (v *Vertex) Scale(i int){ v.x = v.x * i v.y = v.y * i } type Vertex3D struct{ Vertex z int } func (v Vertex3D) Area3D() int{ return v.x * v.y * v.z } func (v *Vertex3D) Scale3D(i int){ v.x = v.x * i v.y = v.y * i v.z = v.z * i } func New(x, y, z int) *Vertex3D{ return &Vertex3D{Vertex{x, y}, z} } func main() { v := New(3,4,5) v.Scale(10) fmt.Println(v.Area()) fmt.Println(v.Area3D()) }1200 6000
- 投稿日:2020-06-24T07:44:51+09:00
fish shell + anyenvでのGo開発環境構築
fish shell + anyenvというパターンでのGoの環境構築の記事がなかったので、備忘録としてまとめます。
実行バージョン
-fish 3.0.2
-anyenv 1.1.1
anyenvでのgoenvのインストールと設定
まずanyenvでGoのバージョン管理システムのgoenvをインストールします。
$ anyenv instal goenv
そして、goenv用の環境変数やaliasの設定をfish shellの設定ファイル
~/.config/fish/config.fish
に追記します。この部分がzshやbashとは違うので注意です。set -x GOENV_ROOT "$HOME/.anyenv/envs/goenv" set -x PATH $PATH "$GOENV_ROOT/bin" set -gx PATH "$GOENV_ROOT/shims" $PATH set -gx GOENV_SHELL fish source "$GOENV_ROOT/libexec/../completions/goenv.fish" command goenv rehash 2>/dev/null function goenv set command $argv[1] set -e argv[1] switch "$command" case rehash shell source (goenv "sh-$command" $argv|psub) case '*' command goenv "$command" $argv end endこれで準備は出来たのでfish shellを再起動して、goの指定バージョンをインストールしましょう。
# インストール可能なバージョンを取得 $ goenv install --list # 指定バージョンのインストール $ goenv install 1.14.3 # デフォルト利用バージョンを指定 $ goenv global 1.14.3一応
go env
で環境変数を出力して各種設定が正しく出来ていることを確認しましょう。$ go env無事出力されれば完了です。
VSCodeでの設定
VSCodeでのGoを開発する場合は以下エクステンションをインストールします。
anyenv利用の場合は環境変数が通常と異なり、このままでは動作しないのでVSCodeの
setting.json
に以下を追記します。
go.gopath
はgo env | grep GOPATH
の値、
go.goroot
はgo env | grep GOPATH
の値です。"go.gopath": "/Users/<ユーザー名>/go/<Goバージョン名>", "go.goroot": "/Users/<ユーザー名>/.anyenv/envs/goenv/versions/<Goバージョン名>"最後go関連の各種ツールをインストールします。
補完やデバッグ関連に必要です。
command + shift + p
でコントロールパネルを開き、Go: Install/Update tools
を選択します。
ダイアログが開くので全てチェックを入れてOKでインストールが始まります。VSCodeでのGo開発環境構築も完了です。
参考
https://gist.github.com/patorash/b5a1033c08d2c4df103457866b2dcaa2