iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 15
0
Modern Web

Go into Web!系列 第 15

Day15 | 透過 golang 實作一個簡單的使用者管理 API(二)

  • 分享至 

  • xImage
  •  

昨天我們撰寫了 interface 方法後,今天要來實作他們,就讓我們開始吧!

實作 CRUD 方法

Repository

/module/user 底下建立一個名為 repository 的資料夾,並在裡面建立一個名為 repository.go 的檔案,在此處我們會 implement 前面步驟宣告的 interface,內容如下

package repository

import (
	"github.com/jinzhu/gorm"
	"sample_api/model"
	"sample_api/module/user"
)

type UserRepository struct {
	orm *gorm.DB
}

func NewUserRepository(orm *gorm.DB) user.Repository {
	return &UserRepository{
		orm: orm,
	}
}

func (u *UserRepository) GetUserList(data map[string]interface{}) ([]*model.User, error) {
	var (
		err error
		in  = make([]*model.User, 0)
	)

	err = u.orm.Find(&in, data).Error
	return in, err
}

func (u *UserRepository) GetUser(in *model.User) (*model.User, error) {
	var err error
	err = u.orm.First(&in).Error
	return in, err
}

func (u *UserRepository) CreateUser(in *model.User) (*model.User, error) {
	var err error
	err = u.orm.Create(&in).Error
	return in, err
}

func (u *UserRepository) UpdateUser(in *model.User) (*model.User, error) {
	var err error
	err = u.orm.Save(&in).Error
	return in, err
}

func (u *UserRepository) ModifyUser(in *model.User, data map[string]interface{}) (*model.User, error) {
	var err error
	err = u.orm.Model(&in).Updates(data).Error
	return in, err
}

func (u *UserRepository) DeleteUser(in *model.User) error {
	return u.orm.Delete(&in).Error
}

Service

/module/user 底下建立一個名為 service 的資料夾,並在裡面建立一個名為 service.go 的檔案,在此處我們會 implement 前面步驟宣告的 interface,內容如下

package repository

import (
	"sample_api/model"
	"sample_api/module/user"
)

type UserService struct {
	repo user.Repository
}

func NewUserService(repo user.Repository) user.Service {
	return &UserService{
		repo: repo,
	}
}

func (u *UserService) GetUserList(data map[string]interface{}) ([]*model.User, error) {
	return u.repo.GetUserList(data)
}

func (u *UserService) GetUser(in *model.User) (*model.User, error) {
	return u.repo.GetUser(in)
}

func (u *UserService) CreateUser(in *model.User) (*model.User, error) {
	return u.repo.CreateUser(in)
}

func (u *UserService) UpdateUser(in *model.User) (*model.User, error) {
	return u.repo.UpdateUser(in)
}

func (u *UserService) ModifyUser(in *model.User, data map[string]interface{}) (*model.User, error) {
	return u.repo.ModifyUser(in, data)
}

func (u *UserService) DeleteUser(in *model.User) error {
	return u.repo.DeleteUser(in)
}

Http Handler

/module/user/delivery 底下建立一個名為 http 的資料夾,並在裡面建立一個名為 http.go 的檔案,在此處我們會 implement 前面步驟宣告的 interface,內容如下

package http

import (
	"github.com/astaxie/beego/validation"
	cx "github.com/codingXiang/cxgateway/delivery"
	"github.com/codingXiang/cxgateway/pkg/e"
	"github.com/codingXiang/cxgateway/pkg/i18n"
	"github.com/codingXiang/cxgateway/pkg/util"
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"sample_api/model"
	"sample_api/module/user"
	"sample_api/module/user/delivery"
)

const (
	MODULE          = "user"
)

type UserHttpHandler struct {
	i18nMsg i18n.I18nMessageHandlerInterface
	gateway cx.HttpHandler
	svc     user.Service
}

func NewUserHttpHandler(gateway cx.HttpHandler, svc user.Service) delivery.HttpHandler {
	var handler = &UserHttpHandler{
		i18nMsg: i18n.NewI18nMessageHandler(MODULE),
		gateway: gateway,
		svc:     svc,
	}
	/*
		v1 版本的 User API
	*/
	v1 := gateway.GetApiRoute().Group("/v1/user")
	v1.GET("", e.Wrapper(handler.GetUserList))
	v1.GET("/:id", e.Wrapper(handler.GetUser))
	v1.POST("", e.Wrapper(handler.CreateUser))
	v1.PUT("/:id", e.Wrapper(handler.UpdateUser))
	v1.PATCH("/:id", e.Wrapper(handler.ModifyUser))
	v1.DELETE("/:id", e.Wrapper(handler.DeleteUser))

	return handler
}

func (g *UserHttpHandler) GetUserList(c *gin.Context) error {
	var (
		data = map[string]interface{}{}
	)
	g.i18nMsg.SetModule(MODULE)
	g.i18nMsg.SetCore(util.GetI18nData(c))

	//抓取 query string

	if in, isExist := c.GetQuery("id"); isExist {
		data["id"] = in
	}
	if in, isExist := c.GetQuery("email"); isExist {
		data["email"] = in
	}
	if in, isExist := c.GetQuery("phone"); isExist {
		data["phone"] = in
	}
	if result, err := g.svc.GetUserList(data); err != nil {
		return g.i18nMsg.GetError(err)
	} else {
		c.JSON(g.i18nMsg.GetSuccess(result))
		return nil
	}
}
func (g *UserHttpHandler) GetUser(c *gin.Context) error {
	var (
		data = new(model.User)
	)
	//將 middleware 傳入的 i18n 進行轉換
	g.i18nMsg.SetModule(MODULE)
	g.i18nMsg.SetCore(util.GetI18nData(c))
	data.ID = c.Params.ByName("id")
	if result, err := g.svc.GetUser(data); err != nil {
		return g.i18nMsg.GetError(err)
	} else {
		c.JSON(g.i18nMsg.GetSuccess(result))
	}
	return nil
}
func (g *UserHttpHandler) CreateUser(c *gin.Context) error {
	var (
		valid = new(validation.Validation)
		data  = new(model.User)
	)
	//將 middleware 傳入的 i18n 進行轉換
	g.i18nMsg.SetModule(MODULE)
	g.i18nMsg.SetCore(util.GetI18nData(c))
	//綁定參數
	var err = c.ShouldBindWith(&data, binding.JSON)
	if err != nil || data == nil {
		return g.i18nMsg.ParameterFormatError()
	}

	//驗證表單資訊是否填寫充足
	valid.Required(&data.ID, "id")
	valid.Required(&data.Email, "email")

	if err := util.NewRequestHandler().ValidValidation(valid); err != nil {
		return err
	}

	if result, err := g.svc.CreateUser(data); err != nil {
		return g.i18nMsg.CreateError(err)
	} else {
		c.JSON(g.i18nMsg.CreateSuccess(result))
		return nil
	}
}
func (g *UserHttpHandler) UpdateUser(c *gin.Context) error {
	var (
		data = new(model.User)
		err  error
	)
	//將 middleware 傳入的 i18n 進行轉換
	g.i18nMsg.SetModule(MODULE)
	g.i18nMsg.SetCore(util.GetI18nData(c))
	data.ID = c.Params.ByName("id")
	//取得 tenant
	if data, err = g.svc.GetUser(data); err != nil {
		return g.i18nMsg.GetError(err)
	}

	//綁定參數
	err = c.ShouldBindWith(data, binding.JSON)
	if err != nil || data == nil {
		return g.i18nMsg.ParameterFormatError()
	}

	//更新 tenant
	if result, err := g.svc.UpdateUser(data); err != nil {
		return g.i18nMsg.UpdateError(err)
	} else {
		c.JSON(g.i18nMsg.UpdateSuccess(result))
		return nil
	}
}
func (g *UserHttpHandler) ModifyUser(c *gin.Context) error {
	var (
		data       = new(model.User)
		updateData = new(map[string]interface{})
	)
	//將 middleware 傳入的 i18n 進行轉換
	g.i18nMsg.SetModule(MODULE)
	g.i18nMsg.SetCore(util.GetI18nData(c))
	data.ID = c.Params.ByName("id")

	//綁定參數
	err := c.ShouldBindWith(&updateData, binding.JSON)
	if err != nil || data == nil {
		return g.i18nMsg.ParameterFormatError()
	}

	if result, err := g.svc.ModifyUser(data, *updateData); err != nil {
		return g.i18nMsg.ModifyError(err)
	} else {
		c.JSON(g.i18nMsg.ModifySuccess(result))
		return nil
	}
}
func (g *UserHttpHandler) DeleteUser(c *gin.Context) error {
	var (
		data = new(model.User)
	)
	//將 middleware 傳入的 i18n 進行轉換
	g.i18nMsg.SetModule(MODULE)
	g.i18nMsg.SetCore(util.GetI18nData(c))
	data.ID = c.Params.ByName("id")

	if err := g.svc.DeleteUser(data); err != nil {
		return g.i18nMsg.DeleteError(err)
	} else {
		c.JSON(g.i18nMsg.DeleteSuccess(nil))
		return nil
	}
}

加入設定檔

在根目錄建立一個名為 config 的資料夾

api 設定檔

在底下建立 config.yaml,分別填入以下內容

程式設定檔

applicatoin 的部分是整個 api 的設定,裡面包含對外的 portgin 運行的 mode 等等

application:
  timeout:
    read: 1000
    write: 1000
  port: 8888
  uploadPath: "./upload"
  mode: "debug"
  appId: "ops"
  apiBaseRoute: "/api"

多語系設定

設定 i18n 開頭,依照需求 defaultLanguage 設定為繁體中文,設定檔案在 ./i18n 底下,翻譯檔類型為 yaml

i18n:
  defaultLanguage: "zh-Hant"
  file:
    path: "./i18n"
    type: "yaml"

log 設定

設定 log 開頭,等級設定為 info,輸出格式 formatjson

log:
  level: "info"
  format: "json"
  path: "log"
  filename: "user.log"

資料庫設定檔

config 資料夾底下建立 database.yaml,並填入以下內容

database:
  url: '127.0.0.1'
  port: 3306
  name: 'sample'
  username: 'sample'
  password: 'sample'
  type: 'mysql'
  tablePrefix: ""
  maxOpenConns: 1000
  maxIdleConns: 1000
  maxLifeTime: 5
  logMode: true
  version: "0.0.2"

加入多語系檔

多語系檔案設定可以參考 這裡

實作 CRUD 方法

Repository

/module/user 底下建立一個名為 repository 的資料夾,並在裡面建立一個名為 repository.go 的檔案,在此處我們會 implement 前面步驟宣告的 interface,內容如下

package repository

import (
	"github.com/jinzhu/gorm"
	"sample_api/model"
	"sample_api/module/user"
)

type UserRepository struct {
	orm *gorm.DB
}

func NewUserRepository(orm *gorm.DB) user.Repository {
	return &UserRepository{
		orm: orm,
	}
}

func (u *UserRepository) GetUserList(data map[string]interface{}) ([]*model.User, error) {
	var (
		err error
		in  = make([]*model.User, 0)
	)

	err = u.orm.Find(&in, data).Error
	return in, err
}

func (u *UserRepository) GetUser(in *model.User) (*model.User, error) {
	var err error
	err = u.orm.First(&in).Error
	return in, err
}

func (u *UserRepository) CreateUser(in *model.User) (*model.User, error) {
	var err error
	err = u.orm.Create(&in).Error
	return in, err
}

func (u *UserRepository) UpdateUser(in *model.User) (*model.User, error) {
	var err error
	err = u.orm.Save(&in).Error
	return in, err
}

func (u *UserRepository) ModifyUser(in *model.User, data map[string]interface{}) (*model.User, error) {
	var err error
	err = u.orm.Model(&in).Updates(data).Error
	return in, err
}

func (u *UserRepository) DeleteUser(in *model.User) error {
	return u.orm.Delete(&in).Error
}

Service

/module/user 底下建立一個名為 service 的資料夾,並在裡面建立一個名為 service.go 的檔案,在此處我們會 implement 前面步驟宣告的 interface,內容如下

package repository

import (
	"sample_api/model"
	"sample_api/module/user"
)

type UserService struct {
	repo user.Repository
}

func NewUserService(repo user.Repository) user.Service {
	return &UserService{
		repo: repo,
	}
}

func (u *UserService) GetUserList(data map[string]interface{}) ([]*model.User, error) {
	return u.repo.GetUserList(data)
}

func (u *UserService) GetUser(in *model.User) (*model.User, error) {
	return u.repo.GetUser(in)
}

func (u *UserService) CreateUser(in *model.User) (*model.User, error) {
	return u.repo.CreateUser(in)
}

func (u *UserService) UpdateUser(in *model.User) (*model.User, error) {
	return u.repo.UpdateUser(in)
}

func (u *UserService) ModifyUser(in *model.User, data map[string]interface{}) (*model.User, error) {
	return u.repo.ModifyUser(in, data)
}

func (u *UserService) DeleteUser(in *model.User) error {
	return u.repo.DeleteUser(in)
}

Http Handler

/module/user/delivery 底下建立一個名為 http 的資料夾,並在裡面建立一個名為 http.go 的檔案,在此處我們會 implement 前面步驟宣告的 interface,內容如下

package http

import (
	"github.com/astaxie/beego/validation"
	cx "github.com/codingXiang/cxgateway/delivery"
	"github.com/codingXiang/cxgateway/pkg/e"
	"github.com/codingXiang/cxgateway/pkg/i18n"
	"github.com/codingXiang/cxgateway/pkg/util"
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"sample_api/model"
	"sample_api/module/user"
	"sample_api/module/user/delivery"
)

const (
	MODULE          = "user"
)

type UserHttpHandler struct {
	i18nMsg i18n.I18nMessageHandlerInterface
	gateway cx.HttpHandler
	svc     user.Service
}

func NewUserHttpHandler(gateway cx.HttpHandler, svc user.Service) delivery.HttpHandler {
	var handler = &UserHttpHandler{
		i18nMsg: i18n.NewI18nMessageHandler(MODULE),
		gateway: gateway,
		svc:     svc,
	}
	/*
		v1 版本的 User API
	*/
	v1 := gateway.GetApiRoute().Group("/v1/user")
	v1.GET("", e.Wrapper(handler.GetUserList))
	v1.GET("/:id", e.Wrapper(handler.GetUser))
	v1.POST("", e.Wrapper(handler.CreateUser))
	v1.PUT("/:id", e.Wrapper(handler.UpdateUser))
	v1.PATCH("/:id", e.Wrapper(handler.ModifyUser))
	v1.DELETE("/:id", e.Wrapper(handler.DeleteUser))

	return handler
}

func (g *UserHttpHandler) GetUserList(c *gin.Context) error {
	var (
		data = map[string]interface{}{}
	)
	g.i18nMsg.SetModule(MODULE)
	g.i18nMsg.SetCore(util.GetI18nData(c))

	//抓取 query string

	if in, isExist := c.GetQuery("id"); isExist {
		data["id"] = in
	}
	if in, isExist := c.GetQuery("email"); isExist {
		data["email"] = in
	}
	if in, isExist := c.GetQuery("phone"); isExist {
		data["phone"] = in
	}
	if result, err := g.svc.GetUserList(data); err != nil {
		return g.i18nMsg.GetError(err)
	} else {
		c.JSON(g.i18nMsg.GetSuccess(result))
		return nil
	}
}
func (g *UserHttpHandler) GetUser(c *gin.Context) error {
	var (
		data = new(model.User)
	)
	//將 middleware 傳入的 i18n 進行轉換
	g.i18nMsg.SetModule(MODULE)
	g.i18nMsg.SetCore(util.GetI18nData(c))
	data.ID = c.Params.ByName("id")
	if result, err := g.svc.GetUser(data); err != nil {
		return g.i18nMsg.GetError(err)
	} else {
		c.JSON(g.i18nMsg.GetSuccess(result))
	}
	return nil
}
func (g *UserHttpHandler) CreateUser(c *gin.Context) error {
	var (
		valid = new(validation.Validation)
		data  = new(model.User)
	)
	//將 middleware 傳入的 i18n 進行轉換
	g.i18nMsg.SetModule(MODULE)
	g.i18nMsg.SetCore(util.GetI18nData(c))
	//綁定參數
	var err = c.ShouldBindWith(&data, binding.JSON)
	if err != nil || data == nil {
		return g.i18nMsg.ParameterFormatError()
	}

	//驗證表單資訊是否填寫充足
	valid.Required(&data.ID, "id")
	valid.Required(&data.Email, "email")

	if err := util.NewRequestHandler().ValidValidation(valid); err != nil {
		return err
	}

	if result, err := g.svc.CreateUser(data); err != nil {
		return g.i18nMsg.CreateError(err)
	} else {
		c.JSON(g.i18nMsg.CreateSuccess(result))
		return nil
	}
}
func (g *UserHttpHandler) UpdateUser(c *gin.Context) error {
	var (
		data = new(model.User)
		err  error
	)
	//將 middleware 傳入的 i18n 進行轉換
	g.i18nMsg.SetModule(MODULE)
	g.i18nMsg.SetCore(util.GetI18nData(c))
	data.ID = c.Params.ByName("id")
	//取得 tenant
	if data, err = g.svc.GetUser(data); err != nil {
		return g.i18nMsg.GetError(err)
	}

	//綁定參數
	err = c.ShouldBindWith(data, binding.JSON)
	if err != nil || data == nil {
		return g.i18nMsg.ParameterFormatError()
	}

	//更新 tenant
	if result, err := g.svc.UpdateUser(data); err != nil {
		return g.i18nMsg.UpdateError(err)
	} else {
		c.JSON(g.i18nMsg.UpdateSuccess(result))
		return nil
	}
}
func (g *UserHttpHandler) ModifyUser(c *gin.Context) error {
	var (
		data       = new(model.User)
		updateData = new(map[string]interface{})
	)
	//將 middleware 傳入的 i18n 進行轉換
	g.i18nMsg.SetModule(MODULE)
	g.i18nMsg.SetCore(util.GetI18nData(c))
	data.ID = c.Params.ByName("id")

	//綁定參數
	err := c.ShouldBindWith(&updateData, binding.JSON)
	if err != nil || data == nil {
		return g.i18nMsg.ParameterFormatError()
	}

	if result, err := g.svc.ModifyUser(data, *updateData); err != nil {
		return g.i18nMsg.ModifyError(err)
	} else {
		c.JSON(g.i18nMsg.ModifySuccess(result))
		return nil
	}
}
func (g *UserHttpHandler) DeleteUser(c *gin.Context) error {
	var (
		data = new(model.User)
	)
	//將 middleware 傳入的 i18n 進行轉換
	g.i18nMsg.SetModule(MODULE)
	g.i18nMsg.SetCore(util.GetI18nData(c))
	data.ID = c.Params.ByName("id")

	if err := g.svc.DeleteUser(data); err != nil {
		return g.i18nMsg.DeleteError(err)
	} else {
		c.JSON(g.i18nMsg.DeleteSuccess(nil))
		return nil
	}
}

加入設定檔

在根目錄建立一個名為 config 的資料夾

api 設定檔

在底下建立 config.yaml,分別填入以下內容

程式設定檔

applicatoin 的部分是整個 api 的設定,裡面包含對外的 portgin 運行的 mode 等等

application:
  timeout:
    read: 1000
    write: 1000
  port: 8888
  uploadPath: "./upload"
  mode: "debug"
  appId: "ops"
  apiBaseRoute: "/api"

多語系設定

設定 i18n 開頭,依照需求 defaultLanguage 設定為繁體中文,設定檔案在 ./i18n 底下,翻譯檔類型為 yaml

i18n:
  defaultLanguage: "zh-Hant"
  file:
    path: "./i18n"
    type: "yaml"

log 設定

設定 log 開頭,等級設定為 info,輸出格式 formatjson

log:
  level: "info"
  format: "json"
  path: "log"
  filename: "user.log"

資料庫設定檔

config 資料夾底下建立 database.yaml,並填入以下內容

database:
  url: '127.0.0.1'
  port: 3306
  name: 'sample'
  username: 'sample'
  password: 'sample'
  type: 'mysql'
  tablePrefix: ""
  maxOpenConns: 1000
  maxIdleConns: 1000
  maxLifeTime: 5
  logMode: true
  version: "0.0.2"

加入多語系檔

多語系檔案設定可以參考 這裡

運行程式

最後我們要建立程式的入口,在根目錄建立一個 main.go,首先先設定初始化方法 init,在初始化方法讀取參數檔

func init () {
	var err error
	//初始化 configer,設定預設讀取環境變數
	config := configer.NewConfigerCore("yaml", "config", "./config", ".")
	config.SetAutomaticEnv("")
	//初始化 Gateway
	cx.Gateway = cx.NewApiGateway("config", config)

	//初始化 db 參數
	db := configer.NewConfigerCore("yaml", "database", "./config", ".")
	db.SetAutomaticEnv("")
	configer.Config.AddCore("db", db)
	//設定資料庫
	if orm.DatabaseORM, err = orm.NewOrm("database", configer.Config.GetCore("db")); err == nil {
		// 建立 Table Schema (Module)
		logger.Log.Debug("setup table schema")
		{
			//設定 使用者資料
			orm.DatabaseORM.CheckTable(true, &model.User{})
		}
	} else {
		logger.Log.Error(err.Error())
		panic(err.Error())
	}
}

有了初始化後,接著我們實作 main 方法,在方法內我們分別實作了 repositoryservicehttp handler,最後透過封裝好的方法 Run 啟動 Server

func main() {
	// 建立 Repository
	logger.Log.Debug("Create Repository Instance")
	var (
		db = orm.DatabaseORM.GetInstance()
		userRepo = repository.NewUserRepository(db)
	)
	// 建立 Service
	logger.Log.Debug("Create Service Instance")
	var (
		userSvc = repository2.NewUserService(userRepo)
	)
	// 建立 Handler (Module)
	logger.Log.Debug("Create Http Handler")
	{
		http.NewUserHttpHandler(cx.Gateway, userSvc)
	}
	cx.Gateway.Run()
}

運行程式

程式運行後看到以下結果代表成功拉

[GIN-debug] GET    /api/v1/user              --> github.com/codingXiang/cxgateway/pkg/e.Wrapper.func1 (8 handlers)
[GIN-debug] GET    /api/v1/user/:id          --> github.com/codingXiang/cxgateway/pkg/e.Wrapper.func1 (8 handlers)
[GIN-debug] POST   /api/v1/user              --> github.com/codingXiang/cxgateway/pkg/e.Wrapper.func1 (8 handlers)
[GIN-debug] PUT    /api/v1/user/:id          --> github.com/codingXiang/cxgateway/pkg/e.Wrapper.func1 (8 handlers)
[GIN-debug] PATCH  /api/v1/user/:id          --> github.com/codingXiang/cxgateway/pkg/e.Wrapper.func1 (8 handlers)
[GIN-debug] DELETE /api/v1/user/:id          --> github.com/codingXiang/cxgateway/pkg/e.Wrapper.func1 (8 handlers)
{"level":"info","msg":"[API Gateway Start Running]","time":"2020-09-14T23:39:04+08:00"}

小結

從今天的範例中可以知道其實實作 RESTful API 很簡單,明天讓我們來詳細的解讀整體的程式碼吧!

程式範例檔

程式範例我放在個人的 github 中,有興趣的朋友可以到 https://github.com/codingXiang/sample_api


上一篇
Day14 | 透過 golang 實作一個簡單的使用者管理 API(一)
下一篇
Day 16 | MongoDB - 好用的 NoSQL 資料庫
系列文
Go into Web!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言