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 協議》,轉載必須註明作者和本文連結