使用 gin + gorm + seesion 完成 Laravel 的 make:auth

go初学者,这篇是笔记,记录和分享go入门实践的历程。
初学一门语言实践自然从blog开始,个人打算是按照本站laravel教程入门实战来搭个sample类微博小站。现在才开头哈。
基础文档读完,差不多脸熟了一下go的语法,实践环节要选框架,我没多想就是gin+gorm,原因很简单啊,本站文档里就有iris、gin、gorm的这3个文档,iris和gin比起来我更倾向gin,因为iris看起来是个大而全的框架,laravel就是个大而全的框架了,学习go我想从小而精的gin开始。

以下是这几天学习的路程步骤^_^

  1. gin+gorm搭简单的mvc
  2. html模板
  3. session 登陆,自定义Auth中间件
  4. 密码使用bcrypt加密验证
  5. 分页列表
  6. 结构优化,api接口和web动作分组
  7. 部署到阿里云服务器

gin+gorm搭简单的mvc

当前目录结构

这个步骤下来,文件目录结构为:

go-sample/
├── apis
│   ├── user.go
│   └── welcome.go
├── models
│   ├── init.go
│   └── user.go
├── router
│   └── router.go
├── main.go

新建项目,我的项目名叫go-sample
新建main.go,打开gin的文档,复制“快速开始”的代码到这个文件

package main

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

func main() {
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })
    r.Run() // 在 0.0.0.0:8080 上监听并服务
}

这时候已经可以跑起来了

# 运行 main.go 并在浏览器上访问 0.0.0.0:8080/ping
$ go run main.go

接下来要拆分代码了,新建route目录和apis目录,这个apis目录其实就是controller层了,翻了好几个go项目,叫apis的比较多,我也叫这个吧
新建 router/router.go

package router

import (
    "github.com/gin-gonic/gin"
    "go-sample/apis"
)

// SetupRouter index
func SetupRouter() *gin.Engine {
    r := gin.Default()
    r.GET("/welcome", apis.Welcome)
    return r
}

新建 apis/welcome.go

package apis

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

func Welcome(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
        "code": 1,
        "data": "hello-world",
    })
}

main.go改成

package main

import (
    "go-sample/router"
)

func main() {
    r := router.SetupRouter()
    r.Run(":3000") // 默认在 0.0.0.0:8080 上监听并服务
}

这时候访问 127.0.0.1:3000/welcome

使用gin + gorm + seesion 完成 laravel 的 make:auth

然后是gorm,新建models目录
新建文件 models/init.go

package models

import (
    "fmt"
    _ "github.com/go-sql-driver/mysql" //加载mysql
    "github.com/jinzhu/gorm"
)

var DB *gorm.DB

func Setup() {
    var err error
    // var user userModel.User
    DB, err = gorm.Open("mysql", "root:12345678@/iu56?charset=utf8&parseTime=True&loc=Local&timeout=10ms")

    if err != nil {
        fmt.Printf("mysql connect error %v", err)
    }

    if DB.Error != nil {
        fmt.Printf("database error %v", DB.Error)
    }
    AutoMigrateAll()
}

func AutoMigrateAll() {
    DB.AutoMigrate(&User{})
}

新建文件 models/user.go

package models

import (
    "github.com/jinzhu/gorm"
)

type User struct {
    gorm.Model
    Name     string `json:"name"`
    Password string `json:"password"`
    Email    string `json:"email"`
    Gender   string `json:"gender"`
}

main.go改成

package main

import (
    "go-sample/models"
    "go-sample/router"
)

func init() {
    models.Setup()
}

func main() {
    r := router.SetupRouter()
    r.Run(":3000") // 默认在 0.0.0.0:8080 上监听并服务
}

router/router.go改成

package router

import (
    "github.com/gin-gonic/gin"
    "go-sample/apis"
)

func SetupRouter() *gin.Engine {
    r := gin.Default()
    r.GET("/welcome",apis.Welcome)

    user := r.Group("/user")
    {
        user.GET("/index", apis.UserIndex)
    }

    return r
}

新建 apis/user.go

package apis

import (
    "github.com/gin-gonic/gin"
    "go-sample/models"
    "net/http"
)

func UserIndex(c *gin.Context) {
    var user models.User
    result := models.DB.Take(&user).Value  //开头就写最简单的,获取一条记录,不指定排序

    c.JSON(http.StatusOK, gin.H{
        "code": 1,
        "data": result,
    })
}

这时候访问127.0.0.1:3000/user/index

使用gin + gorm + seesion 完成 laravel 的 make:auth

知识点

  1. 要在$GOPATH/src 下新建项目
  2. 要加深对package的理解

session 登陆,自定义Auth中间件

当前目录结构

这个步骤下来,文件目录结构如下:

go-sample/
├── apis
│   ├── auth.go
│   └── user.go
│   └── welcome.go
├── models
│   ├── init.go
│   └── user.go
├── pkg
│   ├── session.go
│   └── password.go
├── router
│   └── router.go
├── main.go

新建了pkg目录,封装的第三方包都在这里,session和password都在这里

使用cookie保存session,session中间件

新建文件 pkg/session.go

package pkg

import (
    "github.com/gin-contrib/sessions"
    "github.com/gin-contrib/sessions/cookie"
    "github.com/gin-gonic/gin"
    "go-sample/models"
    "net/http"
)

// gin session key
const KEY = "AEN233"

// 使用 Cookie 保存 session
func EnableCookieSession() gin.HandlerFunc {
    store := cookie.NewStore([]byte(KEY))
    return sessions.Sessions("SAMPLE", store)
}

// session中间件
func AuthSessionMiddle() gin.HandlerFunc {
    return func(c *gin.Context) {
        session := sessions.Default(c)
        sessionValue := session.Get("userId")
        if sessionValue == nil {
            c.JSON(http.StatusUnauthorized, gin.H{
                "error": "Unauthorized",
            })
            c.Abort()
            return
        }
        // 设置简单的变量
        c.Set("userId", sessionValue.(uint))

        c.Next()
        return
    }
}

// 注册和登陆时都需要保存seesion信息
func SaveAuthSession(c *gin.Context, id uint) {
    session := sessions.Default(c)
    session.Set("userId", id)
    session.Save()
}

// 退出时清除session
func ClearAuthSession(c *gin.Context) {
    session := sessions.Default(c)
    session.Clear()
    session.Save()
}

func HasSession(c *gin.Context) bool {
    session := sessions.Default(c)
    if sessionValue := session.Get("userId"); sessionValue == nil {
        return false
    }
    return true
}

func GetSessionUserId(c *gin.Context) uint {
    session := sessions.Default(c)
    sessionValue := session.Get("userId")
    if sessionValue == nil {
        return 0
    }
    return sessionValue.(uint)
}

func GetUserSession(c *gin.Context) map[string]interface{} {

    hasSession := HasSession(c)
    userName := ""
    if hasSession {
        userId := GetSessionUserId(c)
        userName = models.UserDetail(userId).Name
    }
    data := make(map[string]interface{})
    data["hasSession"] = hasSession
    data["userName"] = userName
    return data
}

密码使用x/crypto/bcrypt加密比对

新建文件 pkg/password.go

package pkg

import "golang.org/x/crypto/bcrypt"

// 密码加密
func Encrypt(source string) (string, error) {
    hashPwd, err := bcrypt.GenerateFromPassword([]byte(source), bcrypt.DefaultCost)
    return string(hashPwd), err
}

// 密码比对 (传入未加密的密码即可)
func Compare(hashedPassword, password string) error {
    return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
}

注册、登陆、退出接口

新建文件 apis.go,主要是注册、登陆、退出、获取当前用户

package apis

import (
    "github.com/gin-gonic/gin"
    "go-sample/models"
    "go-sample/pkg"
    "net/http"
)

func Login(c *gin.Context) {
    name := c.Request.FormValue("name")
    password := c.Request.FormValue("password")

    if hasSession := pkg.HasSession(c); hasSession == true {
        c.String(200, "用户已登陆")
        return
    }

    user := models.UserDetailByName(name)

    if err := pkg.Compare(user.Password, password); err != nil {
        c.String(401, "密码错误")
        return
    }

    pkg.SaveAuthSession(c, user.ID)

    c.String(200, "登录成功")
}

func Logout(c *gin.Context) {
    if hasSession := pkg.HasSession(c); hasSession == false {
        c.String(401, "用户未登陆")
        return
    }
    pkg.ClearAuthSession(c)
    c.String(200, "退出成功")
}

func Register(c *gin.Context) {
    var user models.User
    user.Name = c.Request.FormValue("name")
    user.Email = c.Request.FormValue("email")

    if hasSession := pkg.HasSession(c); hasSession == true {
        c.String(200, "用户已登陆")
        return
    }

    if existUser := models.UserDetailByName(user.Name); existUser.ID != 0 {
        c.String(200, "用户名已存在")
        return
    }

    if c.Request.FormValue("password") != c.Request.FormValue("password_confirmation") {
        c.String(200, "密码不一致")
        return
    }

    if pwd, err := pkg.Encrypt(c.Request.FormValue("password")); err == nil {
        user.Password = pwd
    }

    models.AddUser(&user)

    pkg.SaveAuthSession(c, user.ID)

    c.String(200, "注册成功")

}

func Me(c *gin.Context) {
    currentUser := c.MustGet("userId").(uint)
    c.JSON(http.StatusOK, gin.H{
        "code": 1,
        "data": currentUser,
    })
}

model中封装方法

models/user.go 新增方法

// 用户注册时新增
func AddUser(user *User) {
    DB.Create(&user)
    return
}

func UserDetailByName(name string) (user User) {
    DB.Where("name = ?", name).First(&user)
    return
}

func UserDetailByEmail(email string) (user User) {
    DB.Where("email = ?", email).First(&user)
    return
}

func UserDetail(id uint) (user User) {
    DB.Where("id = ?", id).First(&user)
    return
}

路由中加入Auth中间件

修改 router/router.go

package router

import (
    "github.com/gin-gonic/gin"
    "go-sample/apis"
    "go-sample/pkg"
)

func SetupRouter() *gin.Engine {
    r := gin.Default()

    user := r.Group("/user")
    {
        user.GET("/index", apis.UserIndex)
    }

    // use session router
    sr := r.Group("/", pkg.EnableCookieSession())
    {

        sr.GET("/welcome", apis.Welcome)
        sr.GET("/login", apis.Login)
        sr.POST("/register", apis.Register)
        sr.GET("/logout", apis.Logout)

        authorized := sr.Group("/auth", pkg.AuthSessionMiddle())
        {
            authorized.GET("/me", apis.Me)
        }
    }

    return r
}

HTML 渲染,api接口和web动作分组

当前目录结构

这个步骤下来,文件目录结构如下:

go-sample/
├── actions
│   ├── auth.go
│   └── welcome.go
├── apis
│   ├── auth.go
│   └── user.go
│   └── welcome.go
├── assets
│   ├── app.css
│   └── app.js
├── models
│   ├── init.go
│   └── user.go
├── pkg
│   ├── session.go
│   └── password.go
├── router
│   └── router.go
├── views
│   ├── layouts
│   │   ├── head.html
│   │   └── nav.html
│   └── page
│   │   ├── login.html
│   │   └── register.html
│   │   └── welcome.html
├── main.go

新加了actions目录、assets目录、views目录
actions目录和api目录逻辑处理一致,返回不同,一个是接口,一个是web动作。
aseets目录放置js和css。
views目录放置页面,和laravel类似,有一个layouts目录放置公共模板。

go-template使用

todo

路由修改

router/router.go修改为:

package router

import (
    "github.com/gin-gonic/gin"
    "go-sample/actions"
    "go-sample/apis"
    "go-sample/pkg"
    "net/http"
)

func SetupRouter() *gin.Engine {
    r := gin.Default()

    pageGroup(r)  // 将web页面路由提出来

    user := r.Group("/user")
    {
        user.GET("/index", apis.UserIndex)
    }

    // use session router
    sr := r.Group("/", pkg.EnableCookieSession())
    {

        sr.GET("/welcome", apis.Welcome)
        sr.GET("/login", apis.Login)
        sr.POST("/register", apis.Register)
        sr.GET("/logout", apis.Logout)

        authorized := sr.Group("/auth", pkg.AuthSessionMiddle())
        {
            authorized.GET("/me", apis.Me)
        }
    }

    return r
}

func pageGroup(r *gin.Engine) *gin.Engine {
    r.LoadHTMLGlob("views/**/*")
    // use session router
    sr := r.Group("/page", pkg.EnableCookieSession())
    {
        sr.StaticFS("/assets", http.Dir("assets"))
        sr.GET("/welcome", actions.Welcome)
        sr.GET("/login_page", actions.LoginPage)
        sr.GET("/register_page", actions.RegisterPage)
        sr.POST("/login", actions.Login)
        sr.POST("/register", actions.Register)
        sr.POST("/logout", actions.Logout)

        authorized := sr.Group("/auth", pkg.AuthSessionMiddle())
        {
            authorized.GET("/me", actions.Me)
        }
    }
    return r
}

分页列表

修改apis/user.go

package apis

import (
    "github.com/gin-gonic/gin"
    "go-sample/models"
    "math"
    "net/http"
    "strconv"
)
func UserIndex(c *gin.Context) {
    page, _ := strconv.Atoi(c.Query("page"))
    size, _ := strconv.Atoi(c.Query("size"))

    data := models.GetUsers(page, size)

    meta := make(map[string]interface{})
    total, _ := models.GetUserTotal()
    meta["total"] = total
    meta["current_page"] = page
    meta["per_page"] = size
    meta["last_page"] = math.Ceil(float64(total/size)) + 1

    c.JSON(http.StatusOK, gin.H{
        "code": 1,
        "data": data,
        "meta": meta,
    })
}

models/user.go 新增方法

func GetUserTotal() (int, error) {
    var count int
    if err := DB.Model(&User{}).Count(&count).Error; err != nil {
        return 0, err
    }

    return count, nil
}

func GetUsers(page int, size int) (users []User) {
    // limit定位每页大小, Offset定位偏移的查询位置.并且先进行条件筛选,最后做分页操作.
    DB.Offset((page - 1) * size).Limit(size).Find(&users)
    return
}

访问127.0.0.1:3000/user/index?page=1&size=15

使用 gin + gorm + seesion 完成 Laravel 的 make:auth

gin
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!