go-ginチュートリアル — Part3

GinのBlog API’s 作成 — DB

第2回の続き、DB周りのモジュールを作ります。サンプルコードがこちらでご参照ください。githubのtagは part03 となります。

TL;DR

Setting 改善

DBモジュールを作る前、改めて main.go のコードをみてみましょ。

...

func main() {
config, err := setting.NewConfig()
if err != nil {
log.Fatal("Can init Config with Environment: %v", err)
}
router := routers.InitRouter(config.AppConfig)

s := &http.Server{
Addr: fmt.Sprintf(":%d", config.HTTPPort),
Handler: router,
ReadTimeout: config.ReadTimeout,
WriteTimeout: config.WriteTimeout,
MaxHeaderBytes: 1 << 20,
}

s.ListenAndServe()
}

このconfig, err := setting.NewConfig() 部分です、 setting の設定を関数の中に使うものですから、 init に変更します。

package setting

import (
"log"
"time"

"github.com/kelseyhightower/envconfig"
)

type DBConfig struct {
DBType string `envconfig:"DBType"`
DBHost string `envconfig:"DBHost"`
DBPort string `envconfig:"DBPort"`
DBUser string `envconfig:"DBUser"`
DBPassword string `envconfig:"DBPassword"`
DBName string `envconfig:"DBName"`
TablePrefix string `envconfig:"TABLE_PREFIX"`
}

type ServerConfig struct {
HTTPPort int `envconfig:"PORT"`
ReadTimeout time.Duration `envconfig:"READTIMEOUT"`
WriteTimeout time.Duration `envconfig:"WRITETIMEOUT"`
}

type AppConfig struct {
RunMode string `envconfig:"RUN_MODE"`
PageSize int `envconfig:"PAGESIZE"`
JwtSecret string `envconfig:"JWTSECRET`
}

type config struct {
DBConfig
ServerConfig
AppConfig
}

var Config = config{}

func init() {
err := envconfig.Process("", &Config)
if err != nil {
log.Fatalf("Fail to load config wiht env : %v", err)
}
}

main.go ファイルを修正します。

package main

import (
"fmt"
"net/http"
"github.com/gavinzhou/hello-gin/pkg/setting"
"github.com/gavinzhou/hello-gin/routers"
)

func main() {
config := setting.Config.ServerConfig
router := routers.InitRouter()

s := &http.Server{
Addr: fmt.Sprintf(":%d", config.HTTPPort),
Handler: router,
ReadTimeout: config.ReadTimeout,
WriteTimeout: config.WriteTimeout,
MaxHeaderBytes: 1 << 20,
}
s.ListenAndServe()
}

pageination.go にも修正が必要です。

package util

import (
"github.com/Unknwon/com"
"github.com/gin-gonic/gin"

"github.com/gavinzhou/hello-gin/pkg/setting"
)

func GetPage(c *gin.Context) int {
result := 0
page, _ := com.StrTo(c.Query("page")).Int()
if page > 0 {
result = (page - 1) * setting.Config.PageSize
}

return result
}

第2回に書いたテスト方法と同じ、テストしてみてください。

これから、 setting の関数を別の関数の中にも使えます。


新規Routersを追加

Blogはよく便利の機能はTagですから、最初Tagの部分を作ります。

いつもコードを書く前、機能の部分を整理しましょ。検索、追加、削除、修正の部分があります。下記のような定義します!

  • Get Tagリスト: GET("/tags")
  • 新規Tag追加: POST("/tags")
  • 指定Tag更新: PUT("/tags/:id")
  • 指定Tag削除: DELETE("/tags/:id")

routersフォルダに新規apiフォルダを追加します、今回のAPIは最初 v1として、名前は v1のフォルダを作ります。その v1フォルダにtag.goフィアルを作ります。下記のコード通り。

package v1

import (
"github.com/gin-gonic/gin"
)

func GetTags(c *gin.Context) {
}

func AddTag(c *gin.Context) {
}

func EditTag(c *gin.Context) {
}

func DeleteTag(c *gin.Context) {
}

routers.go に新規作った v1 を登録します。

package routers

import (
"github.com/gin-gonic/gin"
"github.com/gavinzhou/hello-gin/pkg/setting"
"github.com/gavinzhou/hello-gin/routers/api/v1"
)

func InitRouter() *gin.Engine {
r := gin.New()
r.Use(gin.Logger())
r.Use(gin.Recovery())
gin.SetMode(setting.RunMode)

apiv1 := r.Group("/api/v1")
{
apiv1.GET("/tags", v1.GetTags)
apiv1.POST("/tags", v1.AddTag)
apiv1.PUT("/tags/:id", v1.EditTag)
apiv1.DELETE("/tags/:id", v1.DeleteTag)
}
return r
}

プロジェクトディレクトリが下記の形になりました。

.
├── README.md
├── conf
├── main.go
├── middleware
├── models
├── pkg
│ ├── e
│ │ ├── code.go
│ │ └── msg.go
│ ├── setting
│ │ └── setting.go
│ └── util
│ └── pageination.go
├── routers
│ ├── api
│ │ └── v1
│ │ └── tag.go
│ └── routers.go
└── runtime

新規追加 v1 Routerを確認します。下記のような追加したRouterを表示します。

❯❯❯ eval $(cat .env)
❯❯❯ go run main.go
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET    /api/v1/tags              --> github.com/gavinzhou/hello-gin/routers/api/v1.GetTags (3 handlers)
[GIN-debug] POST /api/v1/tags --> github.com/gavinzhou/hello-gin/routers/api/v1.AddTag (3 handlers)
[GIN-debug] PUT /api/v1/tags/:id --> github.com/gavinzhou/hello-gin/routers/api/v1.EditTag (3 handlers)
[GIN-debug] DELETE /api/v1/tags/:id --> github.com/gavinzhou/hello-gin/routers/api/v1.DeleteTag (3 handlers)

問題がなければ、 v1 Routersを書きましょ。


DB初期化

postgresqlを使います。 brew がおすすめです。

~ ❯❯❯ brew install postgresql
~ ❯❯❯ brew services start postgresql
~ ❯❯❯ createdb blog -O gavin -E UTF8 -e

Create tag Table

CREATE SEQUENCE blog_tag_seq;
CREATE TABLE blog_tag (
id int check (id > 0) NOT NULL DEFAULT NEXTVAL ('blog_tag_seq'),
name varchar(100) DEFAULT '' ,
created_on int check (created_on > 0) DEFAULT '0' ,
created_by varchar(100) DEFAULT '' ,
modified_on int check (modified_on > 0) DEFAULT '0' ,
modified_by varchar(100) DEFAULT '' ,
state smallint check (state > 0) DEFAULT '1' ,
PRIMARY KEY (id)
) ;

Create article Table

CREATE SEQUENCE blog_article_seq;
CREATE TABLE blog_article (
id int check (id > 0) NOT NULL DEFAULT NEXTVAL ('blog_article_seq'),
tag_id int check (tag_id > 0) DEFAULT '0' ,
title varchar(100) DEFAULT '' ,
description varchar(255) DEFAULT '',
content text,
created_on int DEFAULT NULL,
created_by varchar(100) DEFAULT '' ,
modified_on int check (modified_on > 0) DEFAULT '0' ,
modified_by varchar(255) DEFAULT '' ,
state smallint check (state > 0) DEFAULT '1' ,
PRIMARY KEY (id)
);

Create auth Table

CREATE SEQUENCE blog_auth_seq;
CREATE TABLE blog_auth (
id int check (id > 0) NOT NULL DEFAULT NEXTVAL ('blog_auth_seq'),
username varchar(50) DEFAULT '' ,
password varchar(50) DEFAULT '' ,
PRIMARY KEY (id)
);

Insert auth User

INSERT INTO blog_auth (username, password) VALUES ('test', 'test123456');

Create models init

DB周りは gorm を使います、最初パッケージをダウンロードします。

go get -u github.com/jinzhu/gorm

postgresを使うため、 postgres のdriverにも

go get -u github.com/go-sql-driver/postgres

gin-blogmodelsフォルダにmodels.goファイルを作ります。modelsの初期化を使います。

続き models フォルダに tag.goファイルを作ります。内容が下記の通ります。

Topic
  1. Gorm用のTag struct{}を定義しました。jsonを付けるとc.JSONの時、使います。
  2. 返す関数を定義したため、そのままreturnを使います。
  3. dbがどこに定義したか?同じmodelsパッケージに定義した、db *gorm.DBをそのまま使えます。

Cteate Router

routers フォルダのv1 バージョンの tag.goGetTags を実装します。

package v1

import (
"net/http"

"github.com/Unknwon/com"
"github.com/gavinzhou/hello-gin/models"
"github.com/gavinzhou/hello-gin/pkg/e"
"github.com/gavinzhou/hello-gin/pkg/setting"
"github.com/gavinzhou/hello-gin/pkg/util"
"github.com/gin-gonic/gin"
)

func GetTags(c *gin.Context) {
name := c.Query("name")

maps := make(map[string]interface{})
data := make(map[string]interface{})

if name != "" {
maps["name"] = name
}

var state int = -1
if arg := c.Query("state"); arg != "" {
state = com.StrTo(arg).MustInt()
maps["state"] = state
}

code := e.SUCCESS

data["lists"] = models.GetTags(util.GetPage(c), setting.Config.PageSize, maps)
data["total"] = models.GetTagTotal(maps)

c.JSON(http.StatusOK, gin.H{
"code": code,
"msg": e.GetMsg(code),
"data": data,
})
}

func AddTag(c *gin.Context) {
}

func EditTag(c *gin.Context) {
}

func DeleteTag(c *gin.Context) {
}
Topic
  1. c.Query?name=test&state=1のクエリーを取得できます。c.DefaultQueryのディフォルト設定値があります。
  2. code変数は最初eのエラーコードを使います。エラーコードが最初から定義するとエラーコードが管理しやすくなります。
  3. util.GetPageは各page処理が統一します。
Test

Run Server

❯❯❯ eval $(cat .env)                                                                                                                                
❯❯❯ go run *.go
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET    /api/v1/tags              --> github.com/gavinzhou/hello-gin/routers/api/v1.GetTags (3 handlers)
[GIN-debug] POST /api/v1/tags --> github.com/gavinzhou/hello-gin/routers/api/v1.AddTag (3 handlers)
[GIN-debug] PUT /api/v1/tags/:id --> github.com/gavinzhou/hello-gin/routers/api/v1.EditTag (3 handlers)
[GIN-debug] DELETE /api/v1/tags/:id --> github.com/gavinzhou/hello-gin/routers/api/v1.DeleteTag (3 handlers)

tag を取得してみます。正常なら、下記の様な感じですが、エラーの場合は gin のエラーメッセージで直してください。

❯❯❯ curl 127.0.0.1:8000/api/v1/tags
{"code":200,"data":{"lists":[],"total":0},"msg":"ok"}

次回、新規と削除の部分を作りましょ。

参考サイト

※問題があった場合は、githubの issue をお願いします。