GoのWebフレームワーク Echoでの開発記録 (GoのORM( GORM )でPaginationを利用する)
はじめに
個人開発中のWebアプリでPaginationのページを作成する機会があったので調べながらやってみました。 開発環境は Go1.6 + Echo + MySQLコンテナ + Redisコンテナで、ORマッパーに "gorm" を利用し、htmlのページは Goでレンダリングしています。
■ GORMのドキュメント
ページングの利用方法について説明されています。 gorm.io
コード
■ ディレクトリ構造
├── README.md ├── api │ ├── crypto │ │ ├── bat │ │ │ └── bat.go │ │ ├── coincheck │ │ │ └── coincheck.go │ │ └── gmo │ │ └── gmo.go │ ├── healthcheck │ │ ├── healthcheck.go │ │ └── healthcheck_test.go │ ├── metal │ │ └── metal.go │ └── stock │ └── investment_trust.go ├── app.yaml ├── conf │ └── config.go ├── docker │ ├── mysql │ │ ├── Dockerfile │ │ ├── data │ │ └── my.cnf │ └── redis │ └── data ├── docker-compose.yml ├── go.mod ├── go.sum ├── handler │ ├── auth │ │ └── auth.go │ ├── crypto │ │ └── crypto.go │ ├── graph │ │ └── crypto.go │ ├── hash │ │ └── hash.go │ └── top.go ├── main.go ├── model │ ├── base.go │ ├── crypto │ │ ├── daily_rate │ │ │ └── daily_rate.go │ │ ├── exchange │ │ │ └── exchange.go │ │ ├── exchange_result │ │ │ └── result.go │ │ ├── token │ │ │ └── token.go │ │ ├── token_result │ │ │ └── result.go │ │ └── trade_history │ │ └── trade_history.go │ ├── date │ │ └── date.go │ ├── stock │ │ └── investment_trust │ │ └── investment_trust.go │ └── user │ └── user.go ├── public │ └── assets │ └── css │ ├── crypto_register.css │ ├── crypto_summary.css │ ├── crypto_trade_history.css │ ├── login.css │ ├── mypage.css │ └── signup.css ├── script │ └── setup_local_env.sh ├── secret.yaml ├── staticcheck.conf └── view ├── crypto_register.html ├── crypto_summary.html ├── crypto_trade_history.html ├── login.html ├── mypage.html ├── signup.html └── top.html
■ 利用するDBのテーブルは以下。
mysql> SELECT id,user_id, token_id, exchange_id,trade,date,volume,rate,price FROM crypto_trade_histories LIMIT 1; +----+---------+----------+-------------+-------+-------------------------+-----------+---------+-------+ | id | user_id | token_id | exchange_id | trade | date | volume | rate | price | +----+---------+----------+-------------+-------+-------------------------+-----------+---------+-------+ | 1 | 1 | 1 | 3 | buy | 2021-07-03 00:00:00.000 | 0.0026152 | 3823799 | 10000 | +----+---------+----------+-------------+-------+-------------------------+-----------+---------+-------+ mysql> SELECT id, token FROM tokens; +----+-------+ | id | token | +----+-------+ | 1 | BTC | | 2 | ETH | +----+-------+ mysql> SELECT id, exchange FROM exchanges; +----+-----------+ | id | exchange | +----+-----------+ | 1 | gmo | | 2 | rakuten | | 3 | coincheck | +----+-----------+
■ model/crypto/trade_history/trade_history.go
(一部抜粋)
- 構造体配列に結果を渡して、
Paginate()
にて LIMIT句とOFFSET句を利用して結果を分割してページネーションで表示させる。 GetTradeHistories()
はページ番号とそのページの検索結果を返す
type TradeHistory struct { Exchange string Token string Trade string Volume float64 Rate int Price int Date string } func GetTradeHistories(c echo.Context, userID int) ([]TradeHistory, int) { var tradeHistory []TradeHistory //db.Raw("SELECT exchange, token, trade, rate, price, DATE_FORMAT(date, '%Y-%m-%d') AS date FROM crypto_trade_histories INNER JOIN exchanges on crypto_trade_histories.exchange_id = exchanges.id INNER JOIN tokens on crypto_trade_histories.token_id = tokens.id ORDER BY date desc"). db.Scopes(Paginate(c.Request())). Table("crypto_trade_histories"). Select("exchange, token, trade, volume, rate, price, DATE_FORMAT(date, '%Y-%m-%d') AS date"). Joins("inner join exchanges on crypto_trade_histories.exchange_id = exchanges.id"). Joins("inner join tokens on crypto_trade_histories.token_id = tokens.id"). Where("user_id = ?", userID). Order("date desc"). Scan(&tradeHistory) q := c.Request().URL.Query() page, _ := strconv.Atoi(q.Get("page")) return tradeHistory, page } func Paginate(r *http.Request) func(db *gorm.DB) *gorm.DB { return func(db *gorm.DB) *gorm.DB { q := r.URL.Query() page, _ := strconv.Atoi(q.Get("page")) if page == 0 { page = 1 } pageSize, _ := strconv.Atoi(q.Get("page_size")) switch { case pageSize > 100: pageSize = 100 case pageSize <= 0: pageSize = 10 } offset := (page - 1) * pageSize return db.Offset(offset).Limit(pageSize) } }
- htmlにレンダリングするために、マップ(key にstring、value に構造体)を用意して値を展開する
- マップにするデータは、"取引履歴" と "ページURL"
- TopPageは1ページ目に移動
- BackPageは1ページ戻る
- NextPageは次のページに進む
■ handler/crypto.go
(一部抜粋)
type Page struct { TopPage string BackPage string NextPage string } func ShowTradeHistoryPage(c echo.Context) error { session := auth.GetSession(c) if session.Values["auth"] != true { return c.Redirect(301, "/login") } username := session.Values["username"].(string) userID := user.GetUserID(username) tradeHistory, page := trade_history.GetTradeHistories(c, userID) p := make([]Page, 1, 1) p[0].TopPage = "/crypto/trade_history?page=" + strconv.Itoa(1) p[0].BackPage = "/crypto/trade_history?page=" + strconv.Itoa(page-1) p[0].NextPage = "/crypto/trade_history?page=" + strconv.Itoa(page+1) data := map[string]interface{}{ "trade_history": &tradeHistory, "page": &p, } return c.Render(http.StatusOK, "crypto_trade_history", data) }
main.go
(一部抜粋 Staticファイルの設定)
// Staticファイル e.Static("/assets", "public/assets") e.Static("/crypto/assets", "public/assets") e.GET("/crypto/trade_history", crypto.ShowTradeHistoryPage)
func ShowTradeHistoryPage(c echo.Context) error {}
で定義した dataの key名( "trade_history" と "page" ) を指定して、それをrangeで回して valueに 構造体のフィールド名を指定する
■ view/crypto_trade_history.html
(cssに関してはコピペ利用できるサンプルを参照して作成。本記事ではコードは割愛)
{{define "crypto_trade_history"}} <!DOCTYPE html> <html lang="jp"> <head> <link rel="stylesheet" href="assets/css/crypto_trade_history.css"> </head> <header> <p class="logo">Crypto</a><span>取引履歴</span></p> </header> <body> <table class="osare5-table col5t"> <thead> <tr> <th>取引所</th> <th>トークン</th> <th>ボリューム</th> <th>売買</th> <th>レート</th> <th>金額</th> <th>日時</th> </tr> </thead> <tbody> {{range .trade_history}} <tr> <td>{{.Exchange}}</td> <td>{{.Token}}</td> <td>{{.Volume}}</td> <td>{{.Trade}}</td> <td>{{.Rate}}</td> <td>{{.Price}}</td> <td>{{.Date}}</td> </tr> {{end}} </tbody> </table> <p></p> {{range .page}} <a href="{{.TopPage}}" class="button">top</a> <a href="{{.BackPage}}" class="button">back</a> <a href="{{.NextPage}}" class="button">next</a> {{end}} <p></p> <a href="/mypage" class="button">MyPageへ</a> </body> </html> {{end}}
結果
ページURLのクエリパラメータがページ番号になりページネーションで表示させ、遷移する事ができました。