package
0.0.0-20220301134809-f78f1b81283a
Repository: https://github.com/oneleo/golang-cht.git
Documentation: pkg.go.dev

# README

參考

安裝

  • git for windows
  • golang
  • go get -u github.com/gin-gonic/gin

Step 1:建置論壇骨架

新建「功能表」模板:./templates/menu.html

<!--menu.html-->

<nav class="navbar navbar-default">
  <div class="container">
    <div class="navbar-header">
      <a class="navbar-brand" href="/">
        Home
      </a>
    </div>
  </div>
</nav>

新建「標頭」模板:./templates/header.html

<!--header.html-->

<!doctype html>
<html>

<head>
    <!--Use the title variable to set the title of the page-->
    <title>{{ .title }}</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta charset="UTF-8">

    <!--Use bootstrap to make the application look nice-->
    <!--An overview of Bootstrap: https://bootstrapdocs.com/v3.3.6/docs/getting-started/-->
    <!-- Latest compiled and minified CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"
        integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">

    <!-- Optional theme -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css"
        integrity="sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r" crossorigin="anonymous">

    <!-- Latest compiled and minified JavaScript -->
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"
        integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS"
        crossorigin="anonymous"></script>
</head>

<body class="container">
    <!--Embed the menu.html template at this location-->
    {{ template "menu.html" . }}

新建「頁尾」模板:./templates/footer.html

<!--footer.html-->

  </body>

</html>

新建「首頁」模板:./templates/index.html

<!--index.html-->

<!--Embed the header.html template at this location-->
{{ template "header.html" .}}

  <h1>Hello Gin!</h1>

<!--Embed the footer.html template at this location-->
{{ template "footer.html" .}}

新建 Go 程式碼:./main.go

package main

import (
  "net/http"

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

var router *gin.Engine

func main() {

  // Set the router as the default one provided by Gin
  router = gin.Default()

  // Process the templates at the start so that they don't have to be loaded
  // from the disk again. This makes serving HTML pages very fast.
  router.LoadHTMLGlob("templates/*")

  // Define the route for the index page and display the index.html template
  // To start with, we'll use an inline route handler. Later on, we'll create
  // standalone functions that will be used as route handlers.
  router.GET("/", func(c *gin.Context) {

    // Call the HTML method of the Context to render a template
    c.HTML(
      // Set the HTTP status to 200 (OK)
      http.StatusOK,
      // Use the index.html template
      "index.html",
      // Pass the data that the page uses (in this case, 'title')
      gin.H{
        "title": "Home Page",
      },
    )
  })

  // Start serving the application
  router.Run()
}

測試

> go build -o app.exe
> ./app.exe

Step 2:建置可瀏覽預設文章的論壇

新建 Go 程式碼:./routes.go

// routes.go

package main

func initializeRoutes() {

  // Handle the index route
  router.GET("/", showIndexPage)
}

【修改】 Go 程式碼:./main.go

package main

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

var router *gin.Engine

func main() {

	// Set the router as the default one provided by Gin
	router = gin.Default()

	// Process the templates at the start so that they don't have to be loaded
	// from the disk again. This makes serving HTML pages very fast.
	router.LoadHTMLGlob("templates/*")

	// Initialize the routes
	initializeRoutes()

	// Start serving the application
	router.Run()
}

新建 Go 程式碼:./models.article.go

// models.article.go

package main

type article struct {
  ID      int    `json:"id"`
  Title   string `json:"title"`
  Content string `json:"content"`
}

// For this demo, we're storing the article list in memory
// In a real application, this list will most likely be fetched
// from a database or from static files
var articleList = []article{
  article{ID: 1, Title: "Article 1", Content: "Article 1 body"},
  article{ID: 2, Title: "Article 2", Content: "Article 2 body"},
}

// Return a list of all the articles
func getAllArticles() []article {
  return articleList
}

【修改】「首頁」模板:./templates/index.html

<!--index.html-->

<!--Embed the header.html template at this location-->
{{ template "header.html" .}}

  <!--Loop over the `payload` variable, which is the list of articles-->
  {{range .payload }}
    <!--Create the link for the article based on its ID-->
    <a href="/article/view/{{.ID}}">
      <!--Display the title of the article -->
      <h2>{{.Title}}</h2>
    </a>
    <!--Display the content of the article-->
    <p>{{.Content}}</p>
  {{end}}

<!--Embed the footer.html template at this location-->
{{ template "footer.html" .}}

新建 Go 程式碼:./handlers.article.go

// handlers.article.go

package main

import (
  "net/http"

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

func showIndexPage(c *gin.Context) {
  articles := getAllArticles()

  // Call the HTML method of the Context to render a template
  c.HTML(
    // Set the HTTP status to 200 (OK)
    http.StatusOK,
    // Use the index.html template
    "index.html",
    // Pass the data that the page uses
    gin.H{
      "title":   "Home Page",
      "payload": articles,
    },
  )
}

測試

> go build -o app.exe
> ./app.exe

Step 3:建置可查看文章的論壇

【修改】 Go 程式碼:./routes.go

// routes.go

package main

func initializeRoutes() {

  // Handle the index route
  router.GET("/", showIndexPage)

  // Handle GET requests at /article/view/some_article_id
  router.GET("/article/view/:article_id", getArticle)
}

新建「文章」模板:./templates/article.html

<!--article.html-->

<!--Embed the header.html template at this location-->
{{ template "header.html" .}}

<!--Display the title of the article-->
<h1>{{.payload.Title}}</h1>

<!--Display the content of the article-->
<p>{{.payload.Content}}</p>

<!--Embed the footer.html template at this location-->
{{ template "footer.html" .}}

【修改】 Go 程式碼:./models.article.go

// models.article.go

package main

import "errors"

type article struct {
	ID      int    `json:"id"`
	Title   string `json:"title"`
	Content string `json:"content"`
}

// For this demo, we're storing the article list in memory
// In a real application, this list will most likely be fetched
// from a database or from static files
var articleList = []article{
	article{ID: 1, Title: "Article 1", Content: "Article 1 body"},
	article{ID: 2, Title: "Article 2", Content: "Article 2 body"},
}

// Return a list of all the articles
func getAllArticles() []article {
	return articleList
}

func getArticleByID(id int) (*article, error) {
	for _, a := range articleList {
		if a.ID == id {
			return &a, nil
		}
	}
	return nil, errors.New("Article not found")
}

【修改】 Go 程式碼:./handlers.article.go

// handlers.article.go

package main

import (
	"net/http"
	"strconv"

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

func showIndexPage(c *gin.Context) {
	articles := getAllArticles()

	// Call the HTML method of the Context to render a template
	c.HTML(
		// Set the HTTP status to 200 (OK)
		http.StatusOK,
		// Use the index.html template
		"index.html",
		// Pass the data that the page uses
		gin.H{
			"title":   "Home Page",
			"payload": articles,
		},
	)
}

func getArticle(c *gin.Context) {
	// Check if the article ID is valid
	if articleID, err := strconv.Atoi(c.Param("article_id")); err == nil {
		// Check if the article exists
		if article, err := getArticleByID(articleID); err == nil {
			// Call the HTML method of the Context to render a template
			c.HTML(
				// Set the HTTP status to 200 (OK)
				http.StatusOK,
				// Use the index.html template
				"article.html",
				// Pass the data that the page uses
				gin.H{
					"title":   article.Title,
					"payload": article,
				},
			)

		} else {
			// If the article is not found, abort with an error
			c.AbortWithError(http.StatusNotFound, err)
		}

	} else {
		// If an invalid article ID is specified in the URL, abort with an error
		c.AbortWithStatus(http.StatusNotFound)
	}
}

測試

> go build -o app.exe
> ./app.exe

Step 4:建置可回傳 XML、JSON 格式的論壇

【修改】 Go 程式碼:./main.go

package main

import (
	"net/http"

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

var router *gin.Engine

func main() {

	// Set the router as the default one provided by Gin
	router = gin.Default()

	// Process the templates at the start so that they don't have to be loaded
	// from the disk again. This makes serving HTML pages very fast.
	router.LoadHTMLGlob("templates/*")

	// Initialize the routes
	initializeRoutes()

	// Start serving the application
	router.Run()
}

// Render one of HTML, JSON or CSV based on the 'Accept' header of the request
// If the header doesn't specify this, HTML is rendered, provided that
// the template name is present
func render(c *gin.Context, data gin.H, templateName string) {

	switch c.Request.Header.Get("Accept") {
	case "application/json":
		// Respond with JSON
		c.JSON(http.StatusOK, data["payload"])
	case "application/xml":
		// Respond with XML
		c.XML(http.StatusOK, data["payload"])
	default:
		// Respond with HTML
		c.HTML(http.StatusOK, templateName, data)
	}
}

【修改】 Go 程式碼:./handlers.article.go

// handlers.article.go

package main

import (
	"net/http"
	"strconv"

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

func showIndexPage(c *gin.Context) {
	articles := getAllArticles()

	// Call the render function with the name of the template to render
	render(c, gin.H{
		"title":   "Home Page",
		"payload": articles}, "index.html")
}

func getArticle(c *gin.Context) {
	// Check if the article ID is valid
	if articleID, err := strconv.Atoi(c.Param("article_id")); err == nil {
		// Check if the article exists
		if article, err := getArticleByID(articleID); err == nil {
			// Call the render function with the title, article and the name of the
			// template
			render(c, gin.H{
				"title":   article.Title,
				"payload": article}, "article.html")

		} else {
			// If the article is not found, abort with an error
			c.AbortWithError(http.StatusNotFound, err)
		}

	} else {
		// If an invalid article ID is specified in the URL, abort with an error
		c.AbortWithStatus(http.StatusNotFound)
	}
}

測試

> go build -o app.exe
> ./app.exe

使用 CMD 測試

> curl -X GET -H "Accept: application/json" http://localhost:8080/
> curl -X GET -H "Accept: application/xml" http://localhost:8080/article/view/1

Step 5:建置的論壇

新建 Go 程式碼:./models.user.go

// models.user.go

package main

import "errors"

type user struct {
    Username string `json:"username"`
    Password string `json:"-"`
}

var userList = []user{
    user{Username: "user1", Password: "pass1"},
    user{Username: "user2", Password: "pass2"},
    user{Username: "user3", Password: "pass3"},
}

func registerNewUser(username, password string) (*user, error) {
    return nil, errors.New("placeholder error")
}

func isUsernameAvailable(username string) bool {
    return false
}

新建 Go 程式碼:./handlers.user.go

// handlers.user.go

package main

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

func showRegistrationPage(c *gin.Context) {
}

func register(c *gin.Context) {
}

【修改】 Go 程式碼:./routes.go

// routes.go

package main

func initializeRoutes() {

    router.GET("/", showIndexPage)

    userRoutes := router.Group("/u")
    {
        userRoutes.GET("/register", showRegistrationPage)

        userRoutes.POST("/register", register)
    }

    router.GET("/article/view/:article_id", getArticle)
}

新建「註冊使用者」模板:./templates/register.html

<!--register.html-->

<!--Embed the header.html template at this location-->
{{ template "header.html" .}}

<h1>Register</h1>

<div class="panel panel-default col-sm-6">
  <div class="panel-body">
    <!--If there's an error, display the error-->
    {{ if .ErrorTitle}}
    <p class="bg-danger">
      {{.ErrorTitle}}: {{.ErrorMessage}}
    </p>
    {{end}}
    <!--Create a form that POSTs to the `/u/register` route-->
    <form class="form" action="/u/register" method="POST">
      <div class="form-group">
        <label for="username">Username</label>
        <input type="text" class="form-control" id="username" name="username" placeholder="Username">
      </div>
      <div class="form-group">
        <label for="password">Password</label>
        <input type="password" name="password" class="form-control" id="password" placeholder="Password">
      </div>
      <button type="submit" class="btn btn-primary">Register</button>
    </form>
  </div>
</div>  


<!--Embed the footer.html template at this location-->
{{ template "footer.html" .}}

新建「登入成功」模板:./templates/login-successful.html

<!--login-successful.html-->

<!--Embed the header.html template at this location-->
{{ template "header.html" .}}

<div>
  You have successfully logged in.
</div>

<!--Embed the footer.html template at this location-->
{{ template "footer.html" .}}

【修改】「功能表」模板:./templates/menu.html

<!--menu.html-->

<nav class="navbar navbar-default">
  <div class="container">
    <div class="navbar-header">
      <a class="navbar-brand" href="/">
        Home
      </a>
    </div>
    <ul class="nav navbar-nav">
        <li><a href="/u/register">Register</a></li>
    </ul>
  </div>
</nav>

【修改】Go 程式碼:./models.user.go

// models.user.go

package main

import (
    "errors"
    "strings"
)

type user struct {
    Username string `json:"username"`
    Password string `json:"-"`
}

// For this demo, we're storing the user list in memory
// We also have some users predefined.
// In a real application, this list will most likely be fetched
// from a database. Moreover, in production settings, you should
// store passwords securely by salting and hashing them instead
// of using them as we're doing in this demo
var userList = []user{
    user{Username: "user1", Password: "pass1"},
    user{Username: "user2", Password: "pass2"},
    user{Username: "user3", Password: "pass3"},
}

// Register a new user with the given username and password
func registerNewUser(username, password string) (*user, error) {
    if strings.TrimSpace(password) == "" {
        return nil, errors.New("The password can't be empty")
    } else if !isUsernameAvailable(username) {
        return nil, errors.New("The username isn't available")
    }

    u := user{Username: username, Password: password}

    userList = append(userList, u)

    return &u, nil
}

// Check if the supplied username is available
func isUsernameAvailable(username string) bool {
    for _, u := range userList {
        if u.Username == username {
            return false
        }
    }
    return true
}

【修改】Go 程式碼:./handlers.user.go

// handlers.user.go

package main

import (
    "math/rand"
    "net/http"
    "strconv"

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

func generateSessionToken() string {
    // We're using a random 16 character string as the session token
    // This is NOT a secure way of generating session tokens
    // DO NOT USE THIS IN PRODUCTION
    return strconv.FormatInt(rand.Int63(), 16)
}

func showRegistrationPage(c *gin.Context) {
    // Call the render function with the name of the template to render
    render(c, gin.H{
        "title": "Register"}, "register.html")
}

func register(c *gin.Context) {
    // Obtain the POSTed username and password values
    username := c.PostForm("username")
    password := c.PostForm("password")

    if _, err := registerNewUser(username, password); err == nil {
        // If the user is created, set the token in a cookie and log the user in
        token := generateSessionToken()
        c.SetCookie("token", token, 3600, "", "", false, true)
        c.Set("is_logged_in", true)

        render(c, gin.H{
            "title": "Successful registration & Login"}, "login-successful.html")

    } else {
        // If the username/password combination is invalid,
        // show the error message on the login page
        c.HTML(http.StatusBadRequest, "register.html", gin.H{
            "ErrorTitle":   "Registration Failed",
            "ErrorMessage": err.Error()})

    }
}

【修改】Go 程式碼:./models.user.go

// models.user.go

package main

import (
    "errors"
    "strings"
)

type user struct {
    Username string `json:"username"`
    Password string `json:"-"`
}

// For this demo, we're storing the user list in memory
// We also have some users predefined.
// In a real application, this list will most likely be fetched
// from a database. Moreover, in production settings, you should
// store passwords securely by salting and hashing them instead
// of using them as we're doing in this demo
var userList = []user{
    user{Username: "user1", Password: "pass1"},
    user{Username: "user2", Password: "pass2"},
    user{Username: "user3", Password: "pass3"},
}

func isUserValid(username, password string) bool {
    return false
}

// Register a new user with the given username and password
func registerNewUser(username, password string) (*user, error) {
    if strings.TrimSpace(password) == "" {
        return nil, errors.New("The password can't be empty")
    } else if !isUsernameAvailable(username) {
        return nil, errors.New("The username isn't available")
    }

    u := user{Username: username, Password: password}

    userList = append(userList, u)

    return &u, nil
}

// Check if the supplied username is available
func isUsernameAvailable(username string) bool {
    for _, u := range userList {
        if u.Username == username {
            return false
        }
    }
    return true
}

【修改】Go 程式碼:./handlers.user.go

// handlers.user.go

package main

import (
    "math/rand"
    "net/http"
    "strconv"

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

func showLoginPage(c *gin.Context) {}

func performLogin(c *gin.Context) {}

func logout(c *gin.Context) {}

func generateSessionToken() string {
    // We're using a random 16 character string as the session token
    // This is NOT a secure way of generating session tokens
    // DO NOT USE THIS IN PRODUCTION
    return strconv.FormatInt(rand.Int63(), 16)
}

func showRegistrationPage(c *gin.Context) {
    // Call the render function with the name of the template to render
    render(c, gin.H{
        "title": "Register"}, "register.html")
}

func register(c *gin.Context) {
    // Obtain the POSTed username and password values
    username := c.PostForm("username")
    password := c.PostForm("password")

    if _, err := registerNewUser(username, password); err == nil {
        // If the user is created, set the token in a cookie and log the user in
        token := generateSessionToken()
        c.SetCookie("token", token, 3600, "", "", false, true)
        c.Set("is_logged_in", true)

        render(c, gin.H{
            "title": "Successful registration & Login"}, "login-successful.html")

    } else {
        // If the username/password combination is invalid,
        // show the error message on the login page
        c.HTML(http.StatusBadRequest, "register.html", gin.H{
            "ErrorTitle":   "Registration Failed",
            "ErrorMessage": err.Error()})

    }
}

【修改】 Go 程式碼:./routes.go

// routes.go

package main

func initializeRoutes() {

    router.GET("/", showIndexPage)

    userRoutes := router.Group("/u")
    {
        userRoutes.GET("/register", showRegistrationPage)

		userRoutes.POST("/register", register)
		
		userRoutes.GET("/login", showLoginPage)
        userRoutes.POST("/login", performLogin)
        userRoutes.GET("/logout", logout)
    }

    router.GET("/article/view/:article_id", getArticle)
}

新建「登入」模板:./templates/login.html

<!--login.html-->

{{ template "header.html" .}}

<h1>Login</h1>


<div class="panel panel-default col-sm-6">
  <div class="panel-body">

    {{ if .ErrorTitle}}
    <p class="bg-danger">
      {{.ErrorTitle}}: {{.ErrorMessage}}
    </p>
    {{end}}

    <form class="form" action="/u/login" method="POST">
      <div class="form-group">
        <label for="username">Username</label>
        <input type="text" class="form-control" id="username" name="username" placeholder="Username">
      </div>
      <div class="form-group">
        <label for="password">Password</label>
        <input type="password" class="form-control" id="password" name="password" placeholder="Password">
      </div>
      <button type="submit" class="btn btn-primary">Login</button>
    </form>
  </div>
</div>

{{ template "footer.html" .}}

【修改】「功能表」模板:./templates/menu.html

<!--menu.html-->

<nav class="navbar navbar-default">
  <div class="container">
    <div class="navbar-header">
      <a class="navbar-brand" href="/">
        Home
      </a>
    </div>
    <ul class="nav navbar-nav">
		<li><a href="/u/register">Register</a></li>
        <li><a href="/u/login">Login</a></li>
        <li><a href="/u/logout">Logout</a></li>
    </ul>
  </div>
</nav>

【修改】Go 程式碼:./models.user.go

// models.user.go

package main

import (
    "errors"
    "strings"
)

type user struct {
    Username string `json:"username"`
    Password string `json:"-"`
}

// For this demo, we're storing the user list in memory
// We also have some users predefined.
// In a real application, this list will most likely be fetched
// from a database. Moreover, in production settings, you should
// store passwords securely by salting and hashing them instead
// of using them as we're doing in this demo
var userList = []user{
    user{Username: "user1", Password: "pass1"},
    user{Username: "user2", Password: "pass2"},
    user{Username: "user3", Password: "pass3"},
}

func isUserValid(username, password string) bool {
    for _, u := range userList {
        if u.Username == username && u.Password == password {
            return true
        }
    }
    return false
}

// Register a new user with the given username and password
func registerNewUser(username, password string) (*user, error) {
    if strings.TrimSpace(password) == "" {
        return nil, errors.New("The password can't be empty")
    } else if !isUsernameAvailable(username) {
        return nil, errors.New("The username isn't available")
    }

    u := user{Username: username, Password: password}

    userList = append(userList, u)

    return &u, nil
}

// Check if the supplied username is available
func isUsernameAvailable(username string) bool {
    for _, u := range userList {
        if u.Username == username {
            return false
        }
    }
    return true
}

【修改】Go 程式碼:./handlers.user.go

// handlers.user.go

package main

import (
    "math/rand"
    "net/http"
    "strconv"

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

func showLoginPage(c *gin.Context) {
    render(c, gin.H{
        "title": "Login",
    }, "login.html")
}

func performLogin(c *gin.Context) {
    username := c.PostForm("username")
    password := c.PostForm("password")

    if isUserValid(username, password) {
        token := generateSessionToken()
        c.SetCookie("token", token, 3600, "", "", false, true)

        render(c, gin.H{
            "title": "Successful Login"}, "login-successful.html")

    } else {
        c.HTML(http.StatusBadRequest, "login.html", gin.H{
            "ErrorTitle":   "Login Failed",
            "ErrorMessage": "Invalid credentials provided"})
    }
}

func logout(c *gin.Context) {
    c.SetCookie("token", "", -1, "", "", false, true)

    c.Redirect(http.StatusTemporaryRedirect, "/")
}

func generateSessionToken() string {
    // We're using a random 16 character string as the session token
    // This is NOT a secure way of generating session tokens
    // DO NOT USE THIS IN PRODUCTION
    return strconv.FormatInt(rand.Int63(), 16)
}

func showRegistrationPage(c *gin.Context) {
    // Call the render function with the name of the template to render
    render(c, gin.H{
        "title": "Register"}, "register.html")
}

func register(c *gin.Context) {
    // Obtain the POSTed username and password values
    username := c.PostForm("username")
    password := c.PostForm("password")

    if _, err := registerNewUser(username, password); err == nil {
        // If the user is created, set the token in a cookie and log the user in
        token := generateSessionToken()
        c.SetCookie("token", token, 3600, "", "", false, true)
        c.Set("is_logged_in", true)

        render(c, gin.H{
            "title": "Successful registration & Login"}, "login-successful.html")

    } else {
        // If the username/password combination is invalid,
        // show the error message on the login page
        c.HTML(http.StatusBadRequest, "register.html", gin.H{
            "ErrorTitle":   "Registration Failed",
            "ErrorMessage": err.Error()})

    }
}

【修改】 Go 程式碼:./models.article.go

// models.article.go

package main

import "errors"

type article struct {
	ID      int    `json:"id"`
	Title   string `json:"title"`
	Content string `json:"content"`
}

// For this demo, we're storing the article list in memory
// In a real application, this list will most likely be fetched
// from a database or from static files
var articleList = []article{
	article{ID: 1, Title: "Article 1", Content: "Article 1 body"},
	article{ID: 2, Title: "Article 2", Content: "Article 2 body"},
}

// Return a list of all the articles
func getAllArticles() []article {
	return articleList
}

func getArticleByID(id int) (*article, error) {
	for _, a := range articleList {
		if a.ID == id {
			return &a, nil
		}
	}
	return nil, errors.New("Article not found")
}

func createNewArticle(title, content string) (*article, error) {
    return nil, nil
}

【修改】 Go 程式碼:./handlers.article.go

// handlers.article.go

package main

import (
	"net/http"
	"strconv"

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

func showIndexPage(c *gin.Context) {
	articles := getAllArticles()

	// Call the render function with the name of the template to render
	render(c, gin.H{
		"title":   "Home Page",
		"payload": articles}, "index.html")
}

func showArticleCreationPage(c *gin.Context) {}

func getArticle(c *gin.Context) {
	// Check if the article ID is valid
	if articleID, err := strconv.Atoi(c.Param("article_id")); err == nil {
		// Check if the article exists
		if article, err := getArticleByID(articleID); err == nil {
			// Call the render function with the title, article and the name of the
			// template
			render(c, gin.H{
				"title":   article.Title,
				"payload": article}, "article.html")

		} else {
			// If the article is not found, abort with an error
			c.AbortWithError(http.StatusNotFound, err)
		}

	} else {
		// If an invalid article ID is specified in the URL, abort with an error
		c.AbortWithStatus(http.StatusNotFound)
	}
}

func createArticle(c *gin.Context) {}

【修改】 Go 程式碼:./routes.go

// routes.go

package main

func initializeRoutes() {

    router.GET("/", showIndexPage)

    userRoutes := router.Group("/u")
    {
        userRoutes.GET("/register", showRegistrationPage)

		userRoutes.POST("/register", register)
		
		userRoutes.GET("/login", showLoginPage)
        userRoutes.POST("/login", performLogin)
        userRoutes.GET("/logout", logout)
    }

    articleRoutes := router.Group("/article")
    {
        // route from Part 1 of the tutorial
        articleRoutes.GET("/view/:article_id", getArticle)

        articleRoutes.GET("/create", showArticleCreationPage)

        articleRoutes.POST("/create", createArticle)
    }
}

新建「新增文章」模板:./templates/create-article.html

<!--create-article.html-->

{{ template "header.html" .}}

<h1>Create Article</h1>

<div class="panel panel-default col-sm-12">
  <div class="panel-body">
    {{ if .ErrorTitle}}
    <p class="bg-danger">
      {{.ErrorTitle}}: {{.ErrorMessage}}
    </p>
    {{end}}

    <form class="form" action="/article/create" method="POST">
      <div class="form-group">
        <label for="title">Username</label>
        <input type="text" class="form-control" id="title" name="title" placeholder="Title">
      </div>
      <div class="form-group">
        <label for="content">Password</label>
        <textarea name="content" class="form-control" rows="10" id="content" laceholder="Article Content"></textarea>
      </div>
      <button type="submit" class="btn btn-primary">Submit</button>
    </form>
  </div>
</div>

{{ template "footer.html" .}}

新建「送出文章」模板:./templates/submission-successful.html

<!--submission-successful.html-->

{{ template "header.html" .}}

<div>
  <strong>The article was successfully submitted.</strong>

  <a href="/article/view/{{.payload.ID}}">{{.payload.Title}}</a>
</div>

{{ template "footer.html" .}}

【修改】「功能表」模板:./templates/menu.html

<!--menu.html-->

<nav class="navbar navbar-default">
  <div class="container">
    <div class="navbar-header">
      <a class="navbar-brand" href="/">
        Home
      </a>
    </div>
    <ul class="nav navbar-nav">
		<li><a href="/article/create">Create Article</a></li>
		<li><a href="/u/register">Register</a></li>
        <li><a href="/u/login">Login</a></li>
        <li><a href="/u/logout">Logout</a></li>
    </ul>
  </div>
</nav>

【修改】 Go 程式碼:./models.article.go

// models.article.go

package main

import "errors"

type article struct {
	ID      int    `json:"id"`
	Title   string `json:"title"`
	Content string `json:"content"`
}

// For this demo, we're storing the article list in memory
// In a real application, this list will most likely be fetched
// from a database or from static files
var articleList = []article{
	article{ID: 1, Title: "Article 1", Content: "Article 1 body"},
	article{ID: 2, Title: "Article 2", Content: "Article 2 body"},
}

// Return a list of all the articles
func getAllArticles() []article {
	return articleList
}

func getArticleByID(id int) (*article, error) {
	for _, a := range articleList {
		if a.ID == id {
			return &a, nil
		}
	}
	return nil, errors.New("Article not found")
}

func createNewArticle(title, content string) (*article, error) {
    a := article{ID: len(articleList) + 1, Title: title, Content: content}

    articleList = append(articleList, a)

    return &a, nil
}

【修改】 Go 程式碼:./handlers.article.go

// handlers.article.go

package main

import (
	"net/http"
	"strconv"

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

func showIndexPage(c *gin.Context) {
	articles := getAllArticles()

	// Call the render function with the name of the template to render
	render(c, gin.H{
		"title":   "Home Page",
		"payload": articles}, "index.html")
}

func showArticleCreationPage(c *gin.Context) {
    render(c, gin.H{
        "title": "Create New Article"}, "create-article.html")
}

func getArticle(c *gin.Context) {
	// Check if the article ID is valid
	if articleID, err := strconv.Atoi(c.Param("article_id")); err == nil {
		// Check if the article exists
		if article, err := getArticleByID(articleID); err == nil {
			// Call the render function with the title, article and the name of the
			// template
			render(c, gin.H{
				"title":   article.Title,
				"payload": article}, "article.html")

		} else {
			// If the article is not found, abort with an error
			c.AbortWithError(http.StatusNotFound, err)
		}

	} else {
		// If an invalid article ID is specified in the URL, abort with an error
		c.AbortWithStatus(http.StatusNotFound)
	}
}

func createArticle(c *gin.Context) {
    title := c.PostForm("title")
    content := c.PostForm("content")

    if a, err := createNewArticle(title, content); err == nil {
        render(c, gin.H{
            "title":   "Submission Successful",
            "payload": a}, "submission-successful.html")
    } else {
        c.AbortWithStatus(http.StatusBadRequest)
    }
}

Step 5:建置的論壇

新建 Go 程式碼:./middleware.auth.go

// middleware.auth.go

package main

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

func ensureLoggedIn() gin.HandlerFunc {
    return func(c *gin.Context) {

    }
}

func ensureNotLoggedIn() gin.HandlerFunc {
    return func(c *gin.Context) {

    }
}

func setUserStatus() gin.HandlerFunc {
    return func(c *gin.Context) {

    }
}

【修改】Go 程式碼:./middleware.auth.go

// middleware.auth.go

package main

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

func ensureLoggedIn() gin.HandlerFunc {
    return func(c *gin.Context) {
        loggedInInterface, _ := c.Get("is_logged_in")
        loggedIn := loggedInInterface.(bool)
        if !loggedIn {
            c.AbortWithStatus(http.StatusUnauthorized)
        }
    }
}

func ensureNotLoggedIn() gin.HandlerFunc {
    return func(c *gin.Context) {
        loggedInInterface, _ := c.Get("is_logged_in")
        loggedIn := loggedInInterface.(bool)
        if loggedIn {
            c.AbortWithStatus(http.StatusUnauthorized)
        }
    }
}

func setUserStatus() gin.HandlerFunc {
    return func(c *gin.Context) {
        if token, err := c.Cookie("token"); err == nil || token != "" {
            c.Set("is_logged_in", true)
        } else {
            c.Set("is_logged_in", false)
        }
    }
}

【修改】 Go 程式碼:./routes.go

// routes.go

package main

func initializeRoutes() {

    router.Use(setUserStatus())

    router.GET("/", showIndexPage)

    userRoutes := router.Group("/u")
    {
        userRoutes.GET("/login", ensureNotLoggedIn(), showLoginPage)

        userRoutes.POST("/login", ensureNotLoggedIn(), performLogin)

        userRoutes.GET("/logout", ensureLoggedIn(), logout)

        userRoutes.GET("/register", ensureNotLoggedIn(), showRegistrationPage)

        userRoutes.POST("/register", ensureNotLoggedIn(), register)
    }

    articleRoutes := router.Group("/article")
    {
        articleRoutes.GET("/view/:article_id", getArticle)

        articleRoutes.GET("/create", ensureLoggedIn(), showArticleCreationPage)

        articleRoutes.POST("/create", ensureLoggedIn(), createArticle)
    }
}

【修改】「功能表」模板:./templates/menu.html

<!--menu.html-->

<nav class="navbar navbar-default">
  <div class="container">
    <div class="navbar-header">
      <a class="navbar-brand" href="/">
        Home
      </a>
    </div>
    <ul class="nav navbar-nav">
      {{ if .is_logged_in }}
        <!--Display this link only when the user is logged in-->
        <li><a href="/article/create">Create Article</a></li>
      {{end}}
      {{ if not .is_logged_in }}
        <!--Display this link only when the user is not logged in-->
        <li><a href="/u/register">Register</a></li>
      {{end}}
      {{ if not .is_logged_in }}
        <!--Display this link only when the user is not logged in-->
        <li><a href="/u/login">Login</a></li>
      {{end}}
      {{ if .is_logged_in }}
        <!--Display this link only when the user is logged in-->
        <li><a href="/u/logout">Logout</a></li>
      {{end}}
    </ul>
  </div>
</nav>