15 minute read

Tags:

一上課就考試!

  1. 下列來一種 Go 語言的敘述是對的
    • Go 是編譯語言(Compiled language)
  2. Go 常用哪一種方式處理錯誤
    • Return an error
  3. 下列哪些宣告變數的方式是正確的(複選)
    • var a, b int = 1, 5
    • a, b := 10, “hi”
  4. 下方的程式碼,請問 result 的值是多少
    func main() {
        result := sum(1, 2)
        fmt.Println(result)
    }
    
    func sum(x, y int) (res int) {
        res = x + y
        return
    }
    
  5. Go 的程式碼一定要有下列哪一項目
    • 含有 main 方法
  6. 下列總有幾個欄位(fields)是私有欄位(private fields)?
    type Person struct {
        First string
        middle string
        Last string 
        phone string
        email string
    }
    
    • 3
  7. 觀念: array 不可變 下列程式碼會列印出什麼 *
func main() {
    arr := [3]int{1, 2, 3}
    newArr := arr
    newArr[0] = 100
    fmt.Printf("The first value: %d\n", arr[0])

    slice := arr[:2]
    slice[0] = 99
    fmt.Printf("The second value: %d", arr[0])
}
  • The first value: 1 The second value: 99
  1. 下列程式碼的列印順序是? *
func main(){
    defer fmt.Println("defer 1")
    fmt.Println("hello")
    defer fmt.Println("defer 2")
}
  • hello -> defer 2 -> defer 1


Method

func (m MyType) Do () {
    ...
}
  • method = func + receiver

Pinter Receiver vs. Value Receiver

type User struct {
ID int
Name string
}
func (u *User) Reset() {
u.Name = ""
}
func main() {
u := User{Name: "Will"}
u.Reset()
fmt.Println(u.Name) // ""
}
type User struct {
ID int
Name string
}
func (u User) Reset() {
u.Name = ""
}
func main() {
u := User{Name: "Will"}
u.Reset()
fmt.Println(u.Name) // "Will"
}
  • Receiver 類型的使用時機

    • 使用 Pointer Receiver 的判斷準則,並不是建立在效能相關的議題上, 更重要的是判斷是否需要共用狀態(Shared State)。

    • 以 Time 型別來說,並沒有共用狀態的需求,所以使用 Value Receiver 較為妥當

          func (t Time) Local() Time {
             t.setLoc(Local)
             return t
          }
      
    • • 以 File 型別來說,因為所有方法都需要讀取同一個 File 值 ,所以等同於 共用狀態,因此使用 Pointer Receiver 較為妥當,

      func (f *File) Close() error {
      if f == nil {
      return ErrInvalid
      }
      return f.file.close()
      }
    

struct 型別 Type Embedding

new(Type) == &Type{}

  • 是一個建構函式 用來建立一個型別的記憶體空間 並塞入 zero value
  • 最後 new(T) 會回傳該記憶體空間的 指標
func NewUser(id int, name string) *User{
    var user *User = new(User)
    user.ID = id
    user.Name = name
    return user
}

全等於

func NewUser(id int, name string) *User{
    return &User{
        ID:     id,
        Name: name,
    }
}
var i int 
var i2 *int = &i

===

var i2 *int = new(int) 

make

  • 是一個內建韓函式 只能用在下列三種 參考型別(Reference Type)

    • slice
    • map
    • chan(channel)

       s := make([]int, 10, 100) // slice with len(s) == 10, cap(s) == 100
       m := make(map[string]int) // initialize a map
       c := make(chan int, 10) // channel with a buffer size of 10
      

Interface

  • Go 唯一的抽象名別
    • 隱含實作
  • 介面也是 型別(type) 的一種
  • 用來定義方法集(Method Sets)
  • 採用隱含實作的方式
  • 命名規則多以 er 結尾

  • 定義一個 Notifier 介面

     type Notifier interface {
         Notify()
     }
    
  • User 型別實作 Notifier

     type User struct {
        ID int
        Name string
     }
     func (u User) Notify() {
        fmt.Printf("%s send a message", u.Name)
     }
     func main() {
        var notifier Notifier = User{Name: "Will"}
        notifier.Notify()
     }
    

Value Receiver & Pointer Receiver

type Notifier interface {
   Notify()
}
type User struct {
   ID int
   Name string
}
func (u User) Notify() {
   fmt.Printf("%s send a message", u.Name)
}
  • value receiver

     func main() {
     var notifier Notifier = User{Name: "Doggy"}
     notifier.Notify()
     var notifier2 Notifier = &User{Name: "Doggy"}
     notifier2.Notify()
     }
    
  • pointer receiver

     func main() {
     var notifier Notifier = User{Name: "Will"} // error!!!
        notifier.Notify()
     var notifier2 Notifier = &User{Name: "Doggy"}
        notifier2.Notify()
     }
    

Empty Interface

  • 當 func 傳入型別可能不只一種的時候,就會用到空介面代表任意型別
  • 我們需要透過 Type Assertion (型別推斷) 取得實際的值

  • Type switches
func print(i interface{}){
switch v := i.(type) {
   case int:
      fmt.Printf("It's int %g\n", v)
   case User:
      fmt.Printf("It's user %v\n", v)
   default:
      fmt.Printf("Not a known type %T\n", v)
   }
}
  • Type Conversion

     b := []byte("hello world")
     str := string(b)
    
  • Type Assertion

     var i interface{} = "hello world"
     str, ok := i.(string)
    
  • interface 練習

     package main
       
     import "fmt"
       
     type CreditCard struct {
     	cardNumber string
     }
       
     type Cash struct{}
       
     type PaymentOption interface {
     	ProcessPayment(float32) error
     }
       
     func (cc CreditCard) ProcessPayment(f float32) error{
     	fmt.Println("payment: ", f)
     	return nil
     }
       
     func (c Cash) ProcessPayment(f float32) error{
     	fmt.Println("payment: ", f)
     	return nil
     }
       
     func main() {
       
     	var creditCard PaymentOption = CreditCard{cardNumber: "0000123454324321"}
     	var cash PaymentOption = Cash{}
       
     	// 請讓這兩行程式碼編譯能通過
     	Pay(creditCard, 30)
     	Pay(cash, 30)
       
     }
       
     func Pay(paymentOption PaymentOption, amount float32) error {
     	err := paymentOption.ProcessPayment(amount)
     	return err
     }
    

實作 io Reader

  • String 轉成符合 io.Reader interface 的範例

    • reader := strings.NewReader({"Id":10,"Name":"Duotify"})

    • bReader := bytes.NewBuffer([]byte({"Id": 10, "Name": "Test"}))

字串

  • string 兩種宣告

       s1 := "ab\ncd"
       s2 := `ab
       cd`
       s1 == s2 //true
    
  • string 是由 [byte]

     s := "abc123"
       
     []byte{97, 98, 99, 49, 50, 51}
       
       
     s[0] // 97
     s[1] // 98
     s[2] // 99
     s[0] == 'a' // true
    

1 byte != 1 character

  • rune

    • for range 用一個 rune 為單位迭代

    • type rune = int32
    • 使用 單引號 表示的字元是 Unicode 值,也是 rune 型別!
    • 使用 雙引號 表示多個 UTF-8 編碼[]byte

       var c1 rune = 'a' //rune: 97
       var c2 rune = ' ' //rune: 22810
       str := " "
      
    • Go 的文字處理預設採用 UTF-8 編碼的 Unicode 文字
    • 轉換 []byte 與 []rune 的方法
        var b []byte = []byte(str) // []byte{229, 164, 154, 229, 165, 135}
        var r []rune = []rune(str) // []rune{22810, 22855}
    

標籤 Struct

  • Struct Tag

    • 只能用在 struct 型別的欄位上
    • 可替欄位加上額外的 metadata
    • 多個 tag 之間可以用空白間隔
    • Tag 格式 key:”value”
      • “value” 一定是一個 string
  • encode/json

  • ex

     package main
       
     import (
     	"encoding/json"
     	"os"
     )
       
     type User struct {
     	Name    string `json:"firstName"`
     	Address string `json:"address,omitempty"`
     	Phone   string `json:"-"`
     	UserId  int    `json:"-,"`
     	Score   int    `json:"score,string"`
     }
       
     func main() {
     	u := &User{
     		Name:    "Will",
     		Address: "",
     		Phone:   "0912332123",
     		UserId:  3,
     		Score:   82,
     	}
     	b, _ := json.Marshal(&u)
     	os.Stdout.Write(b)
     }
    

json package []byte

  • Marshal

     var u User = User{
        Name: "Will",
        Phone: "0912345678",
     }
     marshal, _ := json.Marshal(u)
     os.Stdout.Write(marshal)
    
  • Unmarshal

     jsonStr := `{"firstName":"aaaa","address":"eee","-":83,"score":"11"}`
     var u User
     json.Unmarshal([]byte(jsonStr), &u)
    

json package Reader/Writer

  • NewEncoder

     u := User{
        Name: "Will",
        Phone: "0912345678",
     }
     file, _ := os.OpenFile("temp.json", os.O_CREATE|os.O_APPEND, 0660)
       
     encoder := json.NewEncoder(file)
     encoder.Encode(u)
    
  • NewDecoder

     var u User
     file, _ := os.Open("temp.json")
     decoder := json.NewDecoder(file)
     decoder.Decode(&u)
     fmt.Println(u)
    

Package

  • package 生命週期

      1. import packages
      1. Initail variables values
      1. Call init()
  • 無論 package 被 import 幾次 所有的宣告語句都只會 被執行一遍 只有 init() 函式/func 會被執行
  • 一個 go file 可以同時有多個 init() go 會自行決定執行順序
  • 無法在其他函式呼叫 init() 函式
package reg

// 1
import "os"

// 2
var env = os.Getenv("env") // singleton

// 3
func init() {
   if env == "prod" {
   ...
   }
}
  • 三種 import 方式

    1. import "os"
    2.    import "encoding/json"
         import ojson "other/json"
      
    3. import _ "net/http/pprof" // Side-Effect only
  • package 命名規則建議

    • package 名稱需與資料夾一致。
      • 除了測試檔案與 package main 以外
    • 不要用底線串接單字
      • 除了測試檔案以外
    • 使用名詞
    • 可以縮寫
    • 使用小寫
    • 不要使用太過通用性的名字
  • internal package
    • Go 邊義氣會自動判斷 internal 資料夾 並決定 Go package 的可視範圍
    • 公開範圍從 internal 的一層富自料夾與一層副資料夾到底下所有資料夾
  • 一整個專案就是一個 module!~

pprof

  • Package pprof serves via its HTTP server runtime profiling data in the format expected by the pprof visualization tool.



  • 放飯 好玩時間 快樂轉圈圈 XDD
package main

import (
	"fmt"
	"time"
)

func main() {

	func() {
		for {
			for _, v := range `-\|/` {
				fmt.Printf("\r%c", v)
				time.Sleep(100 * time.Millisecond)
			}
		}
	}()
}
  • fmt

  • general

     %v	the value in a default format 
          when printing structs, the plus flag (%+v) adds field names
     %#v	a Go-syntax representation of the value
     %T	a Go-syntax representation of the type of the value
     %%	a literal percent sign; consumes no value
    
  • boolean

     %t	the word true or false
    
  • integer

     %b	base 2
     %c	the character represented by the corresponding Unicode code point
     %d	base 10
     %o	base 8
     %O	base 8 with 0o prefix
     %q	a single-quoted character literal safely escaped with Go syntax.
     %x	base 16, with lower-case letters for a-f
     %X	base 16, with upper-case letters for A-F
     %U	Unicode format: U+1234; same as "U+%04X"
    
  • \r

    • is a carriage return character; it tells your terminal emulator to move the cursor at the start of the line

    • The cursor is the position where the next characters will be rendered.

    • So, printing a \r allows to override the current line of the terminal emulator.



golang 參考 layout

  • Standard Go Project Layout
    • 由社群整理的 Go 專案架構
    • 選擇需要的留下,不用的刪除
    • 簡單的小專案,如 PoC,套用 Project Layout 可能會複雜化 Go 專案
  • 正體中文翻譯

  • main 是進入點

bbgo kkk

型別別名(Alias Declaration)

  • Alias Declaration
type AliasTime = time.Time
// 1. AliasTime 等於 time.Time

// 2. AliasTime 會有 time.Time 的方法

// 3. AliasTime 不可以定義自己的方法 因為 AliasTime 是 time.Time
func(a NewTime) ToString() string{
    ...  // ===> GG!
}
  • Type Definition
type NewTime time.Time
// 1. NewTime 是一個全新的型別

// 2. NewTime 不會有 time.Time 的方法

// 3. NewTime 可以定義方法
func(a NewTime) ToString() string{
    ...
}

認識 Logrus 套件

  • Log to console
package main
import(
    // 先執行 go get 安裝的好處是: 可以享受 intelliSense 提示!
    log "github.com/sirupsen/logrus"
    "os"
)
func main() {
    log.SetOutput(os.Stdout)
    log.Print("Log something")
}

看 go path

  • 大力強推 阿黃姐的好文章~~~

  • go env

    PS C:\Users\tim23> go env
    set GO111MODULE=
    set GOARCH=amd64
    set GOBIN=
    set GOCACHE=C:\Users\tim23\AppData\Local\go-build
    set GOENV=C:\Users\tim23\AppData\Roaming\go\env
    set GOEXE=.exe
    set GOFLAGS=
    set GOHOSTARCH=amd64
    set GOHOSTOS=windows
    set GOINSECURE=
    set GONOPROXY=
    set GONOSUMDB=
    set GOOS=windows
    set GOPATH=C:\Users\tim23\go
    set GOPRIVATE=
    set GOPROXY=https://proxy.golang.org,direct
    set GOROOT=E:\_APP\Go
    set GOSUMDB=sum.golang.org
    set GOTMPDIR=
    set GOTOOLDIR=E:\_APP\Go\pkg\tool\windows_amd64
    set GCCGO=gccgo
    set AR=ar
    set CC=gcc
    set CXX=g++
    set CGO_ENABLED=1
    set GOMOD=
    set CGO_CFLAGS=-g -O2
    set CGO_CPPFLAGS=
    set CGO_CXXFLAGS=-g -O2
    set CGO_FFLAGS=-g -O2
    set CGO_LDFLAGS=-g -O2
    set PKG_CONFIG=pkg-config
    set GOGCCFLAGS=-m64 -mthreads -fmessage-length=0 -fdebug-prefix-map=C:\Users\tim23\AppData\Local\Temp\go-build743311086=/tmp/go-build -gno-record-gcc-switches
    

Go Module

Sematic Versioning

  • v1.5.3-pre1

    • v 版本前綴自
    • 1 主板號: 做了不相容的 API 修改
    • 5 次版號: 當你做向下相容的功能性新增
    • 3 修訂號: 當你做了向下相容的問題修正
    • pre1 先行版號及版本編譯資訊(不穩定版號標示)
  • Go Module

    • package 是多個 go files 的集合
    • Module 是多個 package 的集合
    • Module 可以不必再 GOPATH 底下運作
  • 初始化

        go mod init <name>
        go mod init github.com/doggy8088/projectname
    
  • 認識 go.mod

module github.com/doggy8088/helloworld

go 1.15

require (
   // 使用的相依模組 
   github.com/bob/mymod v1.2.1 // indirect
   github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
   github.com/gin-gonic/gin v1.6.3
   github.com/go-playground/validator v9.31.0+incompatible // indirect
)
exclude (
   // 排除模組的某個版本
   gorm.io/gorm v1.2.4
)
replace (
    // 替換相依模組的程式碼
   github.com/bob/mymod v1.2.1 => ../ret/mymod 
)
  • 認識 go.sum

    • go mod verify
      • 比對 版本
  • go proxy 用 go env 可以看
  • 替換相依模組

    • 不管 require 什麼版本 都指向 v1.2.1 版本
      • require github.com/bob/mymod v1.8.2
      • replace github.com/bob/mymod => github.com/bob/mymod v1.2.1
    • 指向自己 fork 的版本
      • require github.com/bob/mymod v1.8.2
      • replace github.com/bob/mymod => github.com/doggy8088/mymod v1.2.1
    • 指向特定分支的版本
      • require github.com/bob/mymod v1.8.2
      • replace github.com/bob/mymod => github.com/bob/mymod-fork develop
    • 指向木製到本地端的本版
      • require github.com/bob/mymod v1.8.2
      • replace github.com/bob/mymod => /your/forked/path
  • go mod 指令

  • go mod verify
    • 驗證相依模組是否被竄改
  • go mod tidy
    • 移除用不到的模組
  • go mod why
    • 檢查套件如何被用到
  • go mod vendor
  • go mod edit
    • 用指令的方式調整 go.mod 檔案內容
      • go mod edit -require=github.com/gorilla/mux@v1.8.0
      • go mod edit -module app1
  • go get

  • 其他常用指令

  • 取得模組所有可用版本清單
    • $ go list -m -versions github.com/mactsouk/myModule
  • 取得特定版本
    • $ go get github.com/mactsouk/myModule@v1.2.0 // 版本號
    • $ go get github.com/mactsouk/myModule@master // 分支
    • $ go get github.com/mactsouk/myModule@5e40c1d4 // commit hash

寫 Web API

  • HTTP Request/Response
Client                              Server 
------------------------------------------
           |        Handler       |
           |  ===  Request   ===> |
           | <===  Response  ===  |
           |                      |
------------------------------------------
  • 寫法比較 所有的 Web FrameWork 一定是 吃 原生 net/http

     package main
       
     import (
     	"github.com/gin-gonic/gin"
     	"net/http"
     )
       
     func main() {
     	//mux := http.NewServeMux()
      //mux.HandleFunc("/test", func( w http.ResponseWriter, r *http.Request){
      //	w.Write([]byte("Hello Mux"))
     	//})
     	//http.ListenAndServe(":8080", mux)
       
     	r := gin.Default()
     	r.GET("/test2", func(c *gin.Context){
     		c.JSON(http.StatusOK, gin.H{"message":"hello from gin"})
     	})
     	r.Run(":8888")
     }
    
  • Route Prefix 群組路由

func main() {
router := gin.Default()

   v1 := router.Group("/admin")
   {
      v1.POST("/login", func(c *gin.Context) { ... })
      v1.GET("/user", func(c *gin.Context) { ... })
   }

router.Run()
}
  • Query string
func main() {
router := gin.Default()
router.GET("/user", func(c *gin.Context) {
defaultValue := "guest"
firstname := c.DefaultQuery("firstname", defaultValue)
// c.Request.URL.Query().Get("lastname")
lastname := c.Query("lastname")
...
})
router.Run()
}
  • gin.Context

    • net/http
      • http.ResponseWriter *http.Request
      • 資料連結
      • 資料驗證
      • 流程控管
      • Render
      • 資料暫存
  • Data Binding

type Account struct {
   User string `json:"user" binding:"required"`
   Password string `json:"password" binding:"required"`
   ConfirmPassword string `json:"confirm_password" binding:"required,eqfield=Password"`
}

router.POST("/create", func(c *gin.Context) {
   var acct Account
   if err := c.ShouldBindJSON(&acct); err != nil {
      c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
      return
   }
    c.JSON(http.StatusOK, "success")
})
  • 自定義驗證規則
func main() {
	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
		v.RegisterValidation("bookabledate", func(fl validator.FieldLevel) bool {
			date, ok := fl.Field().Interface().(time.Time)
			if ok {
				today := time.Now()
				if today.After(date) {
					return false
				}
			}
			return true
		})
	}
	
}
  • 匿名 struct 回應任意格式的 JSON
router.GET("/json1", func(c *gin.Context) {
	var msg = struct {
		Name string `json:"name"`
		Message string
		Number int
	}{
		Name: "Doggy",
		Message: "hello",
		Number: 123,
	}
	c.JSON(http.StatusOK, msg)
})
  • gin.H
    • type H map[string]interface{}
  • 其他 Response

    • XML
      • c.XML(http.StatusOK, data)
    • YAML
      • c.YAML(http.StateusOK, data)
    • ProtoBuf
    • File
      • c.File(“./doc.pdf”)
      • c.FileAttachment(“./doc.pdf”, “new-name.pdf”)
  • Middleware 資料傳遞

    • map[string]interface{}

    • func (c *Context) Set(key string, value interface{})
    • func (c *Context) Get(key string) (value interface{}, exists bool)
    • func (c *Context) MustGet(key string) interface{} // 找不到 key 就 panic
  • Middleware 中斷執行

    • abort() 只會停止執行 接下來的 handler
    • 已經執行的 handler 還是會執行完
        func (c *Context) Abort() // 這個 Abort()方法會讓 gin 回傳 HTTP 200 狀態 
        func (c *Context) AbortWithStatus(code int)
        func (c *Context) AbortWithStatusJSON(code int, jsonObj interface{})
        func (c *Context) AbortWithError(code int, err error) *Error
    

middleware 集合 gin-gonic/contrib

  • cors
  • jwt
  • health-check
  • sessions
  • csrf
  • oauth2

Gin 社群貢獻的範例程式

SPA


package main
import (
"github.com/gin-contrib/static"
"github.com/gin-gonic/gin"
)
func main() {
    r := gin.Default()
    // 預設SPA 首頁
    router.NoRoute(func(c *gin.Context) { c.File("./dist/index.html") })
    // Allow Directiondex
	r.Use(static.Serve("/", static.LocalFile("./dist", false)))
	r.GET("/ping", func(c *gin.Context) {
		c.String(200, "test")
	})
	r.Run(":8080")
}

操作資料庫 database/sql

  • Connectional Pool
package main
import (
"database/sql"
"github.com/lib/pq"
)
var db *sql.DB
func main() {
    // 註冊c driver
	sql.Register("postgres", &pq.Driver{}) 
    var err error
    // 注意! 此步驟還未連線
	db, err = sql.Open("postgres", "host=myhost port=myport ...")
    if err != nil { panic(err) }
    // 嘗試連線資料庫
	err = db.Ping()
	if err != nil { panic(err) }
}
  • 簡化寫法
package main
import (
   "database/sql"
   // 註冊 driver
   _ "github.com/lib/pq"
)
var db *sql.DB
func main() {
   var err error
   db, err = sql.Open("postgres", "host=myhost port=myport ...")
   if err != nil { panic(err) }
   err = db.Ping()
   if err != nil { panic(err) }
}
func main() {
	dsn := "user=postgres password=123456 dbname=postgres port=5432 ..."
	db, err := sql.Open("postgres", dsn)
	if err != nil {log.Fatal(err)}
	rows, err := db.Query(`SELECT id, user_name FROM users WHERE id = $1`, 1)
	if err != nil {log.Fatal(err)}
	for rows.Next() {
		var u User
		err = rows.Scan(&u.ID, &u.UserName)
		if err != nil {
			log.Print(err)
			continue
		}
		fmt.Println(u)
	}
}
  • Exec
func main() {
	dsn := "user=postgres password=123456 dbname=postgres port=5432 ..."
	db, err := sql.Open("postgres", dsn)
	if err != nil {
		log.Fatal(err)
	}
	exec, err := db.Exec(`INSERT INTO users(user_name) VALUES($1)`, "Doggy")
	if err != nil {
		log.Fatal(err)
	}
	affectdCount, err := exec.RowsAffected()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Print(affectdCount)
}
  • 支援的 SQLDrivers

    • Oracle
    • MySQL
    • SQL Server
    • PostgreSQL
    • SQLite

GROM

  • CURD
  • Migrations
  • Chainable API
  • Event Hooks
  • Eager Loading
  • 支援的資料庫

    • MySQL
    • PostgresSQL
    • SQlite3
    • SQL Server
  • 建立方式
package main
import (
   "gorm.io/driver/postgres"
   "gorm.io/gorm"
)
var db *gorm.DB
func main() {
   dsn := "user=postgres password=123456 dbname=postg..."
   db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
   if err != nil { panic(err) }
   ...
}
  • Auto Migration
type User struct {
   Id uint
   UserName string
}
func main() {
   db, _ := gorm.Open("postgres", "host=localhost ...")
   db.AutoMigrate(&User{})
}
  • 只會建立資料表新增資料表欄位建立索引
    • 不會編輯、不會刪除資料表(欄位)
  • 欄位名稱 ID 會預設為主索引鍵(P.K.)
    • 同時也是自動遞增(autoIncrement)
  • 資料表名稱會自動複數化
  • 命名轉換規則採用小寫+底線
    • 資料表資料表欄位套用同樣規則
  • 除了主索引鍵,欄位預設是 Nullable
type User struct {
    Id uint
    UserName string
}
// db:  
// table: 
//   id        integer
//   user_name text
  • 自訂主索引鍵

    • 設定主索引鍵欄位

       type User struct {
          UserId uint `gorm:"primaryKey"`
          UserName string
       }
      
    • 取消自動遞增(autoIncrement)

       type User struct {
          UserId uint `gorm:"primaryKey;autoIncrement:false"`
          UserName string
       }
      
  • 常用 tags

  • 新稱資料

type User struct {
gorm.Model
PositionName string
}
user := User {
PositionName: "Manager",
}
db.Create(&user)
fmt.Println(user.ID)
INSERT INTO "users" ("created_at","updated_at","deleted_at","position_name")
VALUES ('2020-08-12 11:42:54','2020-08-12 11:42:54', NULL, 'Manager') RETURNING "users"."id"
  • 更新全部欄位(Save)
u := User{Model:gorm.Model{ID: 4}}
db.First(&u)
SELECT * FROM "users"
WHERE "users"."deleted_at" IS NULL AND "users"."id" = 4
ORDER BY "users"."id" ASC LIMIT 1
u.PositionName = "Clerk"
db.Save(&u)
UPDATE "users"
SET "created_at" = '2020-08-12 05:47:53', "updated_at" = '2020-08-13 12:29:40',
"deleted_at" = NULL, "position_name" = 'Clerk'
WHERE "users"."deleted_at" IS NULL AND "users"."id" = 4
  • 更新單一欄位(Update)
u := User{Model:gorm.Model{ID: 3}}
db.Model(&u).Update("position_name", "Clerk")
UPDATE "users"
SET "position_name" = 'Clerk', "updated_at" = '2020-08-13 12:55:59'
WHERE "users"."deleted_at" IS NULL AND "users"."id" = 3
  • 更新多個欄位 (Updates)
u := User{Model:gorm.Model{ID: 3}}
m:= make(map[string]interface{})
m["position_name"] = "Clerk"
m["other_fields"] = "something"
db.Model(&u).Updates(m)
UPDATE "users"
SET "position_name" = 'Clerk', "other_fields" = something',
"updated_at" = '2020-08-13 12:55:59'
WHERE "users"."deleted_at" IS NULL AND "users"."id" = 3
  • 軟刪除
u := User{Model:gorm.Model{ID: 3}}
db.Delete(&u)
UPDATE "users"
SET "deleted_at"='2020-08-13 15:00:22'
WHERE "users"."deleted_at" IS NULL AND "users"."id" = 3
  • 永久刪除
u := User{Model:gorm.Model{ID: 3}}
db.Unscoped().Delete(&u)
DELETE FROM "users" WHERE "users"."id" = 3
type XUser struct {
ID uint
PositionName string
}
u := XUser{ID: 3 }
db.Delete(&u)
DELETE FROM "x_users" WHERE "x_users"."id" = 3
  • 查詢 First (單筆)

    • Where

       var user User
       db.Where(map[string]interface{}{"position_name": "Manager"}).First(&user)
       db.Where("position_name = ?", "Manager").First(&user)
      
    • Inline

       var user User
       db.First(&user, map[string]interface{}{"position_name": "Manager"})
       db.First(&user, "position_name = ?", "Manager")
      
          SELECT * FROM "users"
          WHERE "deleted_at" IS NULL AND position_name = 'Manager'
          ORDER BY "users"."id" ASC LIMIT 1
      
  • 查詢 Findt (多筆)

    • Where
        var users []User
        db.Where(map[string]interface{}{"position_name": "Manager"}).Find(&users)
        db.Where("position_name = ?", "Manager").Find(&users)
    
    • Inline
        var users []User
        db.Find(&users, map[string]interface{}{"position_name": "Manager"})
        db.Find(&users, "position_name = ?", "Manager")
    
     SELECT * FROM "users"
     WHERE "deleted_at" IS NULL AND position_name = 'Manager'
    
  • struct 查詢

var users []User
db.Where(User{ID: 0, Age: 20, PositionName: ""}).Find(&users)
SELECT * FROM "users" WHERE ("users"."age" = 20)
  • Raw SQL

    • 不須取得回傳數
       db.Exec("INSERT INTO users (position_name) VALUES (?);", "others")
      
    • 取得回傳數值

       type User struct {
          ID uint
          PositionName string
       }
       var users []User
       db.Raw("select id,position_name from query_user()").Scan(&users)
      
  • 還有很多多~~

    • 看老師講義 到時候補上 XDD
  • 發佈到 容器

FROM golang:1.15 AS builder
WORKDIR /go/src/app
COPY go.mod .
COPY go.sum .
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -a -o app .

FROM alpine:latest
LABEL Name=gostart Version=0.0.1
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /go/src/app/app .
ENTRYPOINT ./app
# EXPOSE 80

Goroutines

  • buffer channel
package main

import "fmt"

func main() {
	//                   buffer: 0 hen住
	var c = make(chan int, 0)
	go func(){
		fmt.Println("h1")
		// ...
		// ...
		c <- 10

		fmt.Print("Done")
	}()

	go func(){
		fmt.Println("add")
		// ....
	var result =  <- c

	fmt.Print(result)
	}()

	fmt.Println("main start")
}
  • unbuffer channel

Go by Example

  • Goroutines

     package main
     import (
         "fmt"
         "time"
     )
     func f(from string) {
         for i := 0; i < 3; i++ {
             fmt.Println(from, ":", i)
         }
     }
     func main() {
       
         f("direct")
       
         go f("goroutine")
         go func(msg string) {
             fmt.Println(msg)
         }("going")
       
         time.Sleep(time.Second)
         fmt.Println("done")
     }
    
  • Channels

     package main
     import "fmt"
     func main() {
         messages := make(chan string)
       
         go func() { messages <- "ping" }()
       
         msg := <-messages
         fmt.Println(msg)
     }
    
  • Channel Buffering

     package main
     import "fmt"
     func main() {
       
         messages := make(chan string, 2)
       
         messages <- "buffered"
         messages <- "channel"
       
         fmt.Println(<-messages)
         fmt.Println(<-messages)
     }
    
  • Channel Synchronization

     package main
     import (
         "fmt"
         "time"
     )
       
     func worker(done chan bool) {
         fmt.Print("working...")
         time.Sleep(time.Second)
         fmt.Println("done")
       
         done <- true
     }
     func main() {
       
         done := make(chan bool, 1)
         go worker(done)
       
         <-done
     }
    
  • Channel Directions

     package main
     import "fmt"
       
       
     func ping(pings chan<- string, msg string) {
         pings <- msg
     }
       
     func pong(pings <-chan string, pongs chan<- string) {
         msg := <-pings
         pongs <- msg
     }
     func main() {
         pings := make(chan string, 1)
         pongs := make(chan string, 1)
         ping(pings, "passed message")
         pong(pings, pongs)
         fmt.Println(<-pongs)
     }
    

Tags:

Updated: