使用 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开始。
以下是这几天学习的路程步骤^_^
- gin+gorm搭简单的mvc
- html模板
- session 登陆,自定义Auth中间件
- 密码使用bcrypt加密验证
- 分页列表
- 结构优化,api接口和web动作分组
- 部署到阿里云服务器
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
然后是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
知识点
- 要在
$GOPATH/src
下新建项目 - 要加深对
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
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: