Go: 寶哥我來惹~ day 02
Tags: go
一上課就考試!
- 下列來一種 Go 語言的敘述是對的
- Go 是編譯語言(Compiled language)
- Go 常用哪一種方式處理錯誤
- Return an error
- 下列哪些宣告變數的方式是正確的(複選)
var a, b int = 1, 5
- a, b := 10, “hi”
- 下方的程式碼,請問 result 的值是多少
func main() { result := sum(1, 2) fmt.Println(result) } func sum(x, y int) (res int) { res = x + y return }
- Go 的程式碼一定要有下列哪一項目
- 含有 main 方法
- 下列總有幾個欄位(fields)是私有欄位(private fields)?
type Person struct { First string middle string Last string phone string email string }
- 3
- 觀念: 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
- 下列程式碼的列印順序是? *
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)
}
}
-
b := []byte("hello world") str := string(b)
-
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
-
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 生命週期
-
- import packages
-
- Initail variables values
-
- 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 方式
import "os"
-
import "encoding/json" import ojson "other/json"
import _ "net/http/pprof"
// Side-Effect only
-
package 命名規則建議
- package 名稱需與資料夾一致。
- 除了測試檔案與 package main 以外
- 不要用底線串接單字
- 除了測試檔案以外
- 使用名詞
- 可以縮寫
- 使用小寫
- 不要使用太過通用性的名字
- package 名稱需與資料夾一致。
- 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)
}
}
}()
}
-
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"
-
-
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 mod verify
- go proxy 用
go env
可以看- go get 的抓抓 中介 proxy
- pkg.go.dev
-
替換相依模組
- 不管 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
- 不管 require 什麼版本 都指向 v1.2.1 版本
-
go mod 指令
- go mod verify
- 驗證相依模組是否被竄改
- go mod tidy
- 移除用不到的模組
- go mod why
- 檢查套件如何被用到
- go mod vendor
- 將相依模組自動加入到工作目錄下 (目的是為了能夠加入版控)
- How do I use vendoring with modules? Is vendoring going away?
- go mod edit
- 用指令的方式調整 go.mod 檔案內容
- go mod edit -require=github.com/gorilla/mux@v1.8.0
- go mod edit -module app1
- 用指令的方式調整 go.mod 檔案內容
-
go get
- go get rec.io/quote
- go get -u rec.io/quote
-u
一起更新 rsc.io/quote 的 相依模駔
- go get -u ./…
-
其他常用指令
- 取得模組所有可用版本清單
- $ 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
- 資料暫存
- …
- net/http
-
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
- Remote Procedure Call
- c.ProtoBuf(http.StatusOK, data)
- File
- c.File(“./doc.pdf”)
- c.FileAttachment(“./doc.pdf”, “new-name.pdf”)
- XML
-
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 社群貢獻的範例程式
- auto-tls
- http2
- realtim-chat
- gRPC
- graceful-shutdown
- template
- upload-file
- …
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) }