G01學習筆記-8

zcold發表於2021-05-27

閱讀位置10.8

知識點

1、 gorm 遷移

1.1 gorm.DB.AutoMigrate ()

// 自動遷移
db.AutoMigrate(
    &user.User{},
    &article.Article{},
)

1.2 欄位標籤

 //User 使用者模型 
 type User struct { 
     models.BaseModel 
     Name string `gorm:"column:name;type:varchar(255);not null;unique"` 
     Email string `gorm:"column:email;type:varchar(255);default:NULL;unique;"`
     Password string `gorm:"column:password;type:varchar(255)"` 
 }

上面使用 Struct 元素型別後面跟著 gorm: 開頭的就是 GORM 提供的欄位標籤。告知 GORM 在遷移時,設定資料庫欄位。
宣告 GORM 資料模型時,欄位標籤是可選的,GORM 支援以下:(注:名大小寫不敏感,但建議使用 camelCase 風格)

標籤名 說明
column 指定 db 列名
type 列資料型別,推薦使用相容性好的通用型別,例如:所有資料庫都支援 bool、int、uint、float、string、time、bytes 並且可以和其他標籤一起使用,例如:not nullsize, autoIncrement… 像 varbinary(8) 這樣指定資料庫資料型別也是支援的。在使用指定資料庫資料型別時,它需要是完整的資料庫資料型別,如:MEDIUMINT UNSINED not NULL AUTO_INSTREMENT
size 指定列大小,例如:size:256
primaryKey 指定列為主鍵
unique 指定列為唯一
default 指定列的預設值
precision 指定列的精度
scale 指定列大小
not null 指定列為 NOT NULL
autoIncrement 指定列為自動增長
embedded 巢狀欄位
embeddedPrefix 嵌入欄位的列名字首
autoCreateTime 建立時追蹤當前時間,對於 int 欄位,它會追蹤時間戳秒數,您可以使用 nano/milli 來追蹤納秒、毫秒時間戳,例如:autoCreateTime:nano
autoUpdateTime 建立 / 更新時追蹤當前時間,對於 int 欄位,它會追蹤時間戳秒數,您可以使用 nano/milli 來追蹤納秒、毫秒時間戳,例如:autoUpdateTime:milli
index 根據引數建立索引,多個欄位使用相同的名稱則建立複合索引,檢視 索引 獲取詳情
uniqueIndex index 相同,但建立的是唯一索引
check 建立檢查約束,例如 check:age > 13,檢視 約束 獲取詳情
<- 設定欄位寫入的許可權, <-:create 只建立、<-:update 只更新、<-:false 無寫入許可權、<- 建立和更新許可權
-> 設定欄位讀的許可權,->:false 無讀許可權
- 忽略該欄位,- 無讀寫許可權

gorm遷移文件: gorm.io/docs/migration.html#conten...

2、 表單驗證

比較知名的有 asaskevich/govalidatorthedevsaddam/govalidator

後面基於筆記thedevsaddam/govalidator

2.1 定義表單規則

rules := govalidator.MapData{
        "name":             []string{"required", "alpha_num", "between:3,20"},
        "email":            []string{"required", "min:4", "max:30", "email"},
        "password":         []string{"required", "min:6"},
        "password_confirm": []string{"required"},
}

常用的規則

規則名稱 作用
required 欄位必須有值
alpha_num 只允許英文字母和數字混合
between:3,20 欄位長度介於 3 ~ 20 之間
min:4 最少四個字元
max:30 最大 30 個字元
email 必須為 Email

全部規則:github.com/thedevsaddam/govalidato...

2.2 定義錯誤資訊

// 定製錯誤訊息
    messages := govalidator.MapData{
        "name": []string{
            "required:使用者名稱為必填項",
            "alpha_num:格式錯誤,只允許數字和英文",
            "between:使用者名稱長度需在 3~20 之間",
        },
        "email": []string{
            "required:Email 為必填項",
            "min:Email 長度需大於 4",
            "max:Email 長度需小於 30",
            "email:Email 格式不正確,請提供有效的郵箱地址",
        },
        "password": []string{
            "required:密碼為必填項",
            "min:長度需大於 6",
        },
        "password_confirm": []string{
            "required:確認密碼框為必填項",
        },
    }

2.3 配置

type User struct { 
    models.BaseModel 
    Name string `gorm:"type:varchar(255);not null;unique" valid:"name"` 
    Email string `gorm:"type:varchar(255);unique;" valid:"email"` 
    Password string `gorm:"type:varchar(255)" valid:"password"` 
    // gorm:"-" —— 設定 GORM 在讀寫時略過此欄位,僅用於表單驗證
    PasswordConfirm string `gorm:"-" valid:"password_confirm"` 
}
// 根據 valid 標識的進行對應規則的校驗
opts := govalidator.Options{
        Data:          &_user,
        Rules:         rules,
        TagIdentifier: "valid", // Struct 標籤識別符號 
        Messages:      messages,
    }

2.4 校驗

 // 開始驗證
 errs := govalidator.New(opts).ValidateStruct()

2.5 自定義校驗規則

govalidator.AddCustomRule()

// 此方法會在初始化時執行
func init() {
    // not_exists:users,email
    govalidator.AddCustomRule("not_exists", func(field string, rule string, message string, value interface{}) error {
        rng := strings.Split(strings.TrimPrefix(rule, "not_exists:"), ",")

        tableName := rng[0]
        dbFiled := rng[1]
        val := value.(string)

        var count int64
        model.DB.Table(tableName).Where(dbFiled+" = ?", val).Count(&count)

        if count != 0 {

            if message != "" {
                return errors.New(message)
            }

            return fmt.Errorf("%v 已被佔用", val)
        }
        return nil
    })
}

fmt.Errorf 函式。先呼叫 fmt.Sprintf 函式,得到確切的錯誤資訊;再呼叫 errors.New 函式,得到包含該錯誤資訊的 error 型別值,最後返回該值。

3、登入和會話控制

使用 gorilla/sessions 來做會話管理

gorilla/sessions provides cookie and filesystem sessions and infrastructure for custom session backends.

    import (
        "net/http"
        "github.com/gorilla/sessions"
    )
    // Note: Don't store your key in your source code. Pass it via an
    // environmental variable, or flag (or both), and don't accidentally commit it
    // alongside your code. Ensure your key is sufficiently random - i.e. use Go's
    // crypto/rand or securecookie.GenerateRandomKey(32) and persist the result.
    var store = sessions.NewCookieStore([]byte(os.Getenv("SESSION_KEY")))

    func MyHandler(w http.ResponseWriter, r *http.Request) {
        // Get a session. We're ignoring the error resulted from decoding an
        // existing session: Get() always returns a session, even if empty.
        session, _ := store.Get(r, "session-name")
        // Set some session values.
        session.Values["foo"] = "bar"
        session.Values[42] = 43
        // Save it before we write to the response/return from the handler.
        err := session.Save(r, w)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
    }

封裝

package session

import (
    "goblog/pkg/logger"
    "net/http"

    "github.com/gorilla/sessions"
)

// Store gorilla sessions 的儲存庫
var Store = sessions.NewCookieStore([]byte("33446a9dcf9ea060a0a6532b166da32f304af0de"))

// Session 當前會話
var Session *sessions.Session

// Request 用以獲取會話
var Request *http.Request

// Response 用以寫入會話
var Response http.ResponseWriter

// StartSession 初始化會話,在中介軟體中呼叫
func StartSession(w http.ResponseWriter, r *http.Request) {
    var err error

    // Store.Get() 的第二個引數是 Cookie 的名稱
    // gorilla/sessions 支援多會話,本專案我們只使用單一會話即可
    Session, err = Store.Get(r, "goblog-session")
    logger.LogError(err)

    Request = r
    Response = w
}

// Put 寫入鍵值對應的會話資料
func Put(key string, value interface{}) {
    Session.Values[key] = value
    Save()
}

// Get 獲取會話資料,獲取資料時請做型別檢測
func Get(key string) interface{} {
    return Session.Values[key]
}

// Forget 刪除某個會話項
func Forget(key string) {
    delete(Session.Values, key)
    Save()
}

// Flush 刪除當前會話
func Flush() {
    Session.Options.MaxAge = -1
    Save()
}

// Save 保持會話
func Save() {
    // 非 HTTPS 的連結無法使用 Secure 和 HttpOnly,瀏覽器會報錯
    // Session.Options.Secure = true
    // Session.Options.HttpOnly = true
    err := Session.Save(Request, Response)
    logger.LogError(err)
}

第三方的儲存器

例:redis
go get gopkg.in/boj/redistore.v1

Example

// Fetch new store.
store, err := NewRediStore(10, "tcp", ":6379", "", []byte("secret-key"))
if err != nil {
    panic(err)
}
defer store.Close()

// Get a session.
session, err = store.Get(req, "session-key")
if err != nil {
    log.Error(err.Error())
}

// Add a value.
session.Values["foo"] = "bar"

// Save.
if err = sessions.Save(req, rsp); err != nil {
    t.Fatalf("Error saving session: %v", err)
}

// Delete session.
session.Options.MaxAge = -1
if err = sessions.Save(req, rsp); err != nil {
    t.Fatalf("Error saving session: %v", err)
}

// Change session storage configuration for MaxAge = 10 days.
store.SetMaxAge(10 * 24 * 3600)
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章