Golang使用Gin+Gorm+Boostrap从零开始写一个博客

Gin博客项目-项目架构 规范包及目录 |-controller |-dao |-router |-model |-assets |-templates

Gin博客项目-项目架构

规范包及目录

|-controller
|-dao
|-router
|-model
|-assets
|-templates

初始化项目

go mod init blog

Gin博客项目-集成gorm

下载包

go get -u gorm.io/gormgo get -u gorm.io/driver/mysql

创建模型

package models
import "github.com/jinzhu/gorm"
type User struct {
    gorm.Model
    Username string `json:"username"`
    Password string `json:"passowrd"`
}

创建Dao

package dao
import (
    "log"
    "pro04/models"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)
type Manager interface {
    AddUser(user *models.User)
}
type manager struct {
    db *gorm.DB
}
var Mgr Manager
func init() {
    dsn := "root:123456@tcp(127.0.0.1:3306)/golang_db?charset=utf8mb4&parseTime=True&loc=Local"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        log.Fatal("Failed to init db:", err)
    }
    Mgr = &manager{db: db}
    db.AutoMigrate(&models.User{})
}
func (mgr *manager) AddUser(user *models.User) {
    mgr.db.Create(user)
}

测试

user := models.User{
Username: username,
Password: password,
}
dao.Mgr.AddUser(&user)

Gin博客项目-集成Bootstrap创建用户表单

下载bootstrap

https://getbootstrap.com/

添加js和css到assets目录下面

创建添加用户html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="stylesheet" href="/assets/css/bootstrap.min.css">
        
        <title>用户管理</title>
    </head>
    <body>
        <div class="container">
            <div class="row mt-3 justify-content-center">
                <div class="col-md-4">
                    <form method="post" action="/users">
                        <div class="mb-3">
                            <label for="exampleInputEmail1" class="form-label">用户名称</label>
                            <input type="username" class="form-control"
                                id="exampleInputEmail1"
                                aria-describedby="emailHelp">
                            <div id="emailHelp" class="form-text">We'll never
                                share your
                                email with anyone else.</div>
                        </div>
                        <div class="mb-3">
                            <label for="exampleInputPassword1"
                                class="form-label">密码</label>
                            <input type="password" class="form-control"
                                name="password"
                                id="exampleInputPassword1">
                        </div>
                        <div class="mb-3 form-check">
                            <input type="checkbox" class="form-check-input"
                                id="exampleCheck1">
                            <label class="form-check-label" for="exampleCheck1">Check
                                me
                                out</label>
                        </div>
                        <textarea name="test" id="test" cols="30" rows="10"></textarea>
                        <button type="submit" class="btn btn-primary">添加</button>
                    </form>
                    <hr>
                    <ul class="list-group list-group-flush">
                        <li class="list-group-item">tom 123</li>
                        <li class="list-group-item">kite 456</li>
                    </ul>
                </div>
            </div>
        </div>
    </body>
</html>

Gin 博客项目-实现控制器和路由

控制器controller


package controller
import (
    "pro04/dao"
    "pro04/models"
    "github.com/gin-gonic/gin"
)
func AddUser(c *gin.Context) {
    username := c.PostForm("username")
    password := c.PostForm("password")
    user := models.User{
        Username: username,
        Password: password,
    }
    dao.Mgr.AddUser(&user)
}
func ListUser(c *gin.Context) {
    c.HTML(200, "user.html", nil)
}

路由router

package routers
import (
    "pro04/controller"
    "github.com/gin-gonic/gin"
)
func Start() {
    e := gin.Default()
    e.LoadHTMLGlob("templates/*")
    e.Static("/assets", "./assets")
    e.GET("/users", controller.ListUser)
    e.POST("/users", controller.AddUser)
    e.Run()
}

Gin 博客项目-设计静态页面

{{define "header"}}
<header class="p-3 bg-dark text-white">
    <div class="container">
      <div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
        <a class="navbar-brand" href="#">多课网</a>
        <ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0">
          <li><a href="#" class="nav-link px-2 text-secondary">首页</a></li>
          <li><a href="#" class="nav-link px-2 text-white">博客</a></li>
        </ul>
        <form class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3">
          <input type="search" class="form-control form-control-dark" placeholder="Search..." aria-label="Search">
        </form>
        <div class="text-end">
          <button type="button" class="btn btn-outline-light me-2">登录</button>
          <a type="button" class="btn btn-warning" href="/register">注册</a>
        </div>
      </div>
    </div>
  </header>
{{end}}

注册register.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="/assets/css/bootstrap.min.css">
    
    <title>用户注册</title>
  </head>
  <body>
    <div class="container">
      {{template "header"}}
      <div class="row  justify-content-center mt-3">
        <div class="col-md-4">
          <form>
            <div class="mb-3">
              <label for="exampleInputEmail1" class="form-label">用户名称</label>
              <input type="email" class="form-control" id="exampleInputEmail1"
                aria-describedby="emailHelp">
            </div>
            <div class="mb-3">
              <label for="exampleInputPassword1" class="form-label">用户密码</label>
              <input type="password" class="form-control"
                id="exampleInputPassword1">
            </div>
            <div class="mb-3 form-check">
              <input type="checkbox" class="form-check-input"
                id="exampleCheck1">
              <label class="form-check-label" for="exampleCheck1">记住我</label>
            </div>
            <button type="submit" class="btn btn-primary">添加</button>
          </form>
        </div>
      </div>
    </div>
  </body>
</html>

登录login.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="/assets/css/bootstrap.min.css">
    
    <title>用户注册</title>
  </head>
  <body>
    <div class="container">
      {{template "header"}}
      
      <div class="row  justify-content-center mt-3">
        <div class="col-md-4">
          <form>
            <div class="mb-3">
              <label for="exampleInputEmail1" class="form-label">用户名称</label>
              <input type="email" class="form-control" id="exampleInputEmail1"
                aria-describedby="emailHelp">
            </div>
            <div class="mb-3">
              <label for="exampleInputPassword1" class="form-label">用户密码</label>
              <input type="password" class="form-control"
                id="exampleInputPassword1">
            </div>
            <div class="mb-3 form-check">
              <input type="checkbox" class="form-check-input"
                id="exampleCheck1">
              <label class="form-check-label" for="exampleCheck1">记住我</label>
            </div>
            <button type="submit" class="btn btn-primary">添加</button>
          </form>
        </div>
      </div>
    </div>
  </body>
</html>

首页index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="/assets/css/bootstrap.min.css">
    
    <title>Document</title>
  </head>
  <body>
    <div class="container">
      {{template "header"}}
      <div class="row justify-content-center mt-3">
        <div class="col-md-4">
          <p>首页...</p>
        </div>
      </div>
    </div>
  </body>
</html>

Gin 博客项目-用户注册

controller

func RegisterUser(c *gin.Context) {
    username := c.PostForm("username")
    password := c.PostForm("password")
    user := model.User{
        Username: username,
        Password: password,
    }
    dao.Mgr.AddUser(&user)
    c.Redirect(200, "/")
}
func GoRegister(c *gin.Context) {
    c.HTML(200, "register.html", nil)
}

router


package router
import (
    "blog/controller"
    "github.com/gin-gonic/gin"
)
func Start() {
    e := gin.Default()
    e.LoadHTMLGlob("templates/*")
    e.Static("/assets", "./assets")
    e.GET("/", controller.Index)
    e.POST("/register", controller.RegisterUser)
    e.GET("/register", controller.GoRegister)
    e.Run()
}

Gin 博客项目-用户登录

dao

package dao
import (
    "blog/model"
    "log"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)
type Manager interface {
    AddUser(user *model.User)
    Login(username string) model.User
}
type manager struct {
    db *gorm.DB
}
var Mgr Manager
func init() {
    dsn := "root:123456@tcp(127.0.0.1:3306)/golang_db?charset=utf8mb4&parseTime=True&loc=Local"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        log.Fatal("Failed to init db:", err)
    }
    Mgr = &manager{db: db}
    db.AutoMigrate(&model.User{})
}
func (mgr *manager) AddUser(user *model.User) {
    mgr.db.Create(user)
}
func (mgr *manager) Login(username string) model.User {
    var user model.User
    mgr.db.Where("username=?", username).First(&user)
    return user
}

controller

package controller
import (
    "blog/dao"
    "blog/model"
    "fmt"
    "github.com/gin-gonic/gin"
)
func RegisterUser(c *gin.Context) {
    username := c.PostForm("username")
    password := c.PostForm("password")
    user := model.User{
        Username: username,
        Password: password,
    }
    dao.Mgr.AddUser(&user)
    c.Redirect(200, "/")
}
func GoRegister(c *gin.Context) {
    c.HTML(200, "register.html", nil)
}
func GoLogin(c *gin.Context) {
    c.HTML(200, "login.html", nil)
}
func Login(c *gin.Context) {
    username := c.PostForm("username")
    password := c.PostForm("password")
    fmt.Println(username)
    u := dao.Mgr.Login(username)
    if u.Username == "" {
        c.HTML(200, "login.html", "用户名不存在!")
        fmt.Println("用户名不存在!")
    } else {
        if u.Password != password {
            fmt.Println("密码错误")
            c.HTML(200, "login.html", "密码错误")
        } else {
            fmt.Println("登录成功")
            c.Redirect(301, "/")
        }
    }
}
func Index(c *gin.Context) {
    c.HTML(200, "index.html", nil)
}
func ListUser(c *gin.Context) {
    c.HTML(200, "index.html", nil)
}

router

package router
import (
    "blog/controller"
    "github.com/gin-gonic/gin"
)
func Start() {
    e := gin.Default()
    e.LoadHTMLGlob("templates/*")
    e.Static("/assets", "./assets")
    e.GET("/login", controller.GoLogin)
    e.POST("/login", controller.Login)
    e.GET("/", controller.Index)
    e.POST("/register", controller.RegisterUser)
    e.GET("/register", controller.GoRegister)
    e.Run()
}

login.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="/assets/css/bootstrap.min.css">
    
    <title>用户注册</title>
  </head>
  <body>
    <div class="container">
      {{template "header"}}
      
      <div class="row  justify-content-center mt-3">
        <div class="col-md-4">
          <p style="background-color: red;">{{.}}</p>
          <form method="post" action="/login">
            <div class="mb-3">
              <label for="exampleInputEmail1" class="form-label">用户名称</label>
              <input type="text" name="username" class="form-control" id="exampleInputEmail1"
                aria-describedby="emailHelp">
            </div>
            <div class="mb-3">
              <label for="exampleInputPassword1" class="form-label">用户密码</label>
              <input type="password" name="password" class="form-control"
                id="exampleInputPassword1">
            </div>
            
            <button type="submit" class="btn btn-primary">登录</button>
          </form>
        </div>
      </div>
    </div>
  </body>
</html>

Gin 博客项目-集成markdown编辑器

下载mdeditor

https://pandao.github.io/editor.md/

集成

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="/assets/css/bootstrap.min.css">
    <link rel="stylesheet" href="/assets/editormd/css/editormd.css">
    
    <title>Document</title>
  </head>
  <body>
    <div class="container">
      {{template "header"}}
      <div id="test-editormd">
        <textarea style="display:none;"></textarea>
      </div>
    </div>
    
    
    
      var testEditor;
      $(function() {
          testEditor = editormd("test-editormd", {
              width   : "100%",
              height  : 640,
              syncScrolling : "single",
              path    : "assets/editormd/lib/"
          });
      });
    
  </body>
</html>

Gin 博客项目-创建博客模型和DAO

创建模型

type Post struct {
    gorm.Model
    Title   string
    Content string `gorm:"type:text"`
    Tag     string
}

创建DAO

package dao
import (
    "blog/model"
    "log"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)
type Manager interface {
    AddUser(user *model.User)
    Login(username string) model.User
    // 博客操作
    AddPost(post *model.Post)
    GetAllPost() []model.Post
    getPost(pid int) model.Post
}
type manager struct {
    db *gorm.DB
}
var Mgr Manager
func init() {
    dsn := "root:123456@tcp(127.0.0.1:3306)/golang_db?charset=utf8mb4&parseTime=True&loc=Local"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        log.Fatal("Failed to init db:", err)
    }
    Mgr = &manager{db: db}
    db.AutoMigrate(&model.User{})
    db.AutoMigrate(&model.Post{})
}
func (mgr *manager) AddUser(user *model.User) {
    mgr.db.Create(user)
}
func (mgr *manager) Login(username string) model.User {
    var user model.User
    mgr.db.Where("username=?", username).First(&user)
    return user
}
// 博客操作
func (mgr *manager) AddPost(post *model.Post) {
    mgr.db.Create(post)
}
func (mgr *manager) GetAllPost() []model.Post {
    var posts = make([]model.Post, 10)
    mgr.db.Find(&posts)
    return posts
}
func (mgr *manager) getPost(pid int) model.Post {
    var post model.Post
    mgr.db.First(&post, pid)
    return post
}

Gin 博客项目-创建博客控制器和路由

创建控制器

package controller
import (
    "blog/dao"
    "blog/model"
    "fmt"
    "github.com/gin-gonic/gin"
)
func RegisterUser(c *gin.Context) {
    username := c.PostForm("username")
    password := c.PostForm("password")
    user := model.User{
        Username: username,
        Password: password,
    }
    dao.Mgr.AddUser(&user)
    c.Redirect(200, "/")
}
func GoRegister(c *gin.Context) {
    c.HTML(200, "register.html", nil)
}
func GoLogin(c *gin.Context) {
    c.HTML(200, "login.html", nil)
}
func Login(c *gin.Context) {
    username := c.PostForm("username")
    password := c.PostForm("password")
    fmt.Println(username)
    u := dao.Mgr.Login(username)
    if u.Username == "" {
        c.HTML(200, "login.html", "用户名不存在!")
        fmt.Println("用户名不存在!")
    } else {
        if u.Password != password {
            fmt.Println("密码错误")
            c.HTML(200, "login.html", "密码错误")
        } else {
            fmt.Println("登录成功")
            c.Redirect(301, "/")
        }
    }
}
func Index(c *gin.Context) {
    c.HTML(200, "index.html", nil)
}
func ListUser(c *gin.Context) {
    c.HTML(200, "index.html", nil)
}
func GetPostIndex(c *gin.Context) {
    posts := dao.Mgr.GetAllPost()
    c.HTML(200, "postIndex.html", posts)
}
func AddPost(c *gin.Context) {
    title := c.PostForm("title")
    tag := c.PostForm("tag")
    content := c.PostForm("content")
    post := model.Post{
        Title:   title,
        Tag:     tag,
        Content: content,
    }
    dao.Mgr.AddPost(&post)
    c.Redirect(302, "/post_index")
}
func GoAddPost(c *gin.Context) {
    c.HTML(200, "post.html", nil)
}

创建路由


package router
import (
    "blog/controller"
    "github.com/gin-gonic/gin"
)
func Start() {
    e := gin.Default()
    e.LoadHTMLGlob("templates/*")
    e.Static("/assets", "./assets")
    e.GET("/login", controller.GoLogin)
    e.POST("/login", controller.Login)
    e.GET("/", controller.Index)
    e.POST("/register", controller.RegisterUser)
    e.GET("/register", controller.GoRegister)
    e.GET("/post_index", controller.GetPostIndex)
    e.POST("/post", controller.AddPost)
    e.GET("/post", controller.GoAddPost)
    e.Run()
}

Gin 博客项目-添加博客

添加博客页面

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="/assets/css/bootstrap.min.css">
    <link rel="stylesheet" href="/assets/editormd/css/editormd.css">
    
    <title>添加博客</title>
  </head>
  <body>
    <div class="container">
      {{template "header"}}
      <form action="/post" method="post">
        <div class="row">
          
            <div class="col-md-8">
              <div id="test-editormd">
                <textarea style="display:none;" name="content"></textarea>
              </div>    
            </div>
            <div class="col-md-4 mt-3">
              <label for="title" class="form-label">请输入标题</label>
              <input type="text" name="title" class="form-control" id="title"><br>
              <label for="tag" class="form-label">请输入标签</label>
              <input type="text" name="tag"  class="form-control" id="tag"><br>
              
              <button type="submit" class="btn btn-primary">添加</button>
            </div>
            
        </div>
      </form>
    </div>
    
    
    
      var testEditor;
      $(function() {
          testEditor = editormd("test-editormd", {
              width   : "100%",
              height  : 450,
              syncScrolling : "single",
              path    : "assets/editormd/lib/",
              watch : false,
          });
      });
    
  </body>
</html>

Gin 博客项目-实现博客列表

博客列表

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="/assets/css/bootstrap.min.css">
    
    <title>博客列表</title>
  </head>
  <body>
    <div class="container">
      {{template "header"}}
      <div class="row mt-3">
        {{range $post := . -}}
        <div class="col-md-6">
          <div class="row g-0 border rounded overflow-hidden flex-md-row mb-4
            shadow-sm h-md-250 position-relative">
            <div class="col p-4 d-flex flex-column position-static">
              <strong class="d-inline-block mb-2 text-primary">分类</strong>
              <h3 class="mb-0">{{$post.Title}}</h3>
              <div class="mb-1 text-muted">Nov 12</div>
              <p class="card-text mb-auto">{{$post.Content}}</p>
              <a href="#" class="stretched-link">阅读更多...</a>
            </div>
            <div class="col-auto d-none d-lg-block">
              <svg class="bd-placeholder-img" width="200" height="250"
                xmlns="http://www.w3.org/2000/svg" role="img"
                aria-label="Placeholder: Thumbnail"
                preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect
                  width="100%" height="100%" fill="#55595c"></rect><text x="50%"
                  y="50%" fill="#eceeef" dy=".3em">博客封面</text></svg>
            </div>
          </div>
        </div>
        {{- end}}
      </div>
    </div>
  </body>
</html>

Gin 博客项目-实现博客详细

博客详细页面

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="/assets/css/bootstrap.min.css">
    <link rel="stylesheet" href="/assets/editormd/css/editormd.css">
    
    <title>博客详细</title>
  </head>
  <body>
    <div class="container">
      {{template "header"}}
    
      <div class="row">
        <div class="col-md-12">
          <h1>{{.Title}}</h1>
          {{.Content}}
        </div>
      </div>
    </div>
  
  </body>
</html>

博客详细控制器

func PostDetail(c *gin.Context) {
    s := c.Query("pid")
    pid, _ := strconv.Atoi(s)
    p := dao.Mgr.GetPost(pid)
    content := blackfriday.Run([]byte(p.Content))
    c.HTML(200, "detail.html", gin.H{
        "Title":   p.Title,
        "Content": template.HTML(content),
    })
}

Comment