iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 6
1
Modern Web

Go into Web!系列 第 6

Day6 | 透過 golang 實作一個簡單的登入功能

昨天我們利用 gin 做了一個簡單的網頁,但網站可不會只有看的功能,有時候使用者也需要輸入資料與網站進行互動,因此,今天我們會說明如何利用 gin 實作一個 登入 的功能。

目標

首先先來訂定要完成的目標,今天預期要完成幾件事情

  1. 寫出登入的核心程式
  2. 實作登入畫面,並且與 /login 路徑進行綁定
  3. 登入成功會顯示 登入成功 的訊息,登入失敗會顯示 帳號或密碼錯誤 訊息

訂定好目標後,就讓我們開始吧!

構思程式架構

在開始寫程式之前,我們先思考一下要實作登入功能的流程,以下為一個簡單的登入流程

  1. 判斷是否有輸入使用者帳號密碼
  2. 判斷使用者名稱是否存在
  3. 判斷使用者密碼是否與名稱相匹配
  4. 顯示登入訊息

有了以上架構後就可以開始進行程式開發拉

核心程式

首先,我們先將使用者的帳號密碼定義為 map,並且在 init 的方法中進行初始化

如果想要加入更多測試的帳號密碼,可以在 map 中依序加入

var UserData map[string]string

func init() {
	UserData = map[string]string{
		"test": "test",
	}
}

接著我們試著寫出判斷使用者是否存在的程式,邏輯就是為判斷 map 中是否擁有匹配的 key

[備註]本範例因為沒有串連資料庫,因此就是單純使用變數來做判斷

func CheckUserIsExist(username string) bool {
	_, isExist := UserData[username]
	return isExist
}

再來寫出密碼比對

func CheckPassword(p1 string, p2 string) error {
	if p1 == p2 {
		return nil
	} else {
		return errors.New("password is not correct")
	}
}

然後再將上面的程式拼湊成驗證身份的程式

func Auth(username string, password string) error {
	if isExist := CheckUserIsExist(username); isExist {
		return CheckPassword(UserData[username], password)
	} else {
		return errors.New("user is not exist")
	}
}

最後將所有的程式放入名為 auth.go 的檔案中並放置於根目錄,內容如下

package main

import "errors"

var UserData map[string]string

func init() {
	UserData = map[string]string{
		"test": "test",
	}
}

func CheckUserIsExist(username string) bool {
	_, isExist := UserData[username]
	return isExist
}

func CheckPassword(p1 string, p2 string) error {
	if p1 == p2 {
		return nil
	} else {
		return errors.New("password is not correct")
	}
}

func Auth(username string, password string) error {
	if isExist := CheckUserIsExist(username); isExist {
		return CheckPassword(UserData[username], password)
	} else {
		return errors.New("user is not exist")
	}
}

以上就完成了核心程式了

登入畫面

有了核心邏輯的程式後,我們就來製作畫面吧

首先,先建立好登入的 request handler,這邊我們建立兩個,一個為接收 GET 一個接收 POST

func LoginPage(c *gin.Context) {

}

func LoginAuth(c *gin.Context) {
	
}

LoginPage

畫面

LoginPage 基本上只要顯示出登入的畫面即可,因此我們先建立好的登入的 html,登入的 html 如下

<link href="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
<link href="/assets/css/custom.css" rel="stylesheet">

<script src="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
{{ if .success }}
    <div class="alert alert-success" role="alert">
        {{ .success }}
    </div>
{{ end }}
{{if .error}}
    <div class="alert alert-danger" role="alert">
        {{ .error }}
    </div>
{{end}}
<div class="container register-form">
    <form METHOD="POST" ACTION="/login">
        <div class="form">
            <div class="note">
                <p>登入範例</p>
            </div>

            <div class="form-content">
                <div class="row">
                    <div class="col-md-12">
                        <div class="form-group">
                            <input type="text" class="form-control" placeholder="使用者名稱" name="username" value=""/>
                        </div>
                        <div class="form-group">
                            <input type="password" class="form-control" placeholder="密碼" name="password" value=""/>
                        </div>
                    </div>
                </div>
                <button type="submit" class="btnSubmit">登入</button>
            </div>
        </div>
    </form>
</div>

CSS 如下

.note
{
    text-align: center;
    height: 80px;
    background: -webkit-linear-gradient(left, #0072ff, #8811c5);
    color: #fff;
    font-weight: bold;
    line-height: 80px;
}
.form-content
{
    padding: 5%;
    border: 1px solid #ced4da;
    margin-bottom: 2%;
}
.form-control{
    border-radius:1.5rem;
}
.btnSubmit
{
    border:none;
    border-radius:1.5rem;
    padding: 1%;
    width: 20%;
    cursor: pointer;
    background: #0062cc;
    color: #fff;
}

我們將檔案分別處理

  1. html 儲存成 login.html 後,放置到 ./template/html
  2. css 儲存成 custom.css 後,放置到 ./template/assets/css

[備註] 在 html 中的 if 是用於如果我們有這個變數存在了才為顯示裡面所放的元素

handler

接著我們修改 LoginPage,將其改成下面這樣

func LoginPage(c *gin.Context) {
	c.HTML(http.StatusOK, "login.html", nil)
}

將剛才寫好的 login.html 透過 LoginPage 進行渲染

routing

最後,我們在 main.go 裡面將 GET /loginLoginPage 進行綁定

server.GET("/login", LoginPage)

完成後的 main.go 會像這樣

package main

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

func main() {
	server := gin.Default()
	server.LoadHTMLGlob("template/html/*")
	//設定靜態資源的讀取
	server.Static("/assets", "./template/assets")
	server.GET("/login", LoginPage)
	server.Run(":8888")
}

將程式運行起來後,輸入 http://127.0.0.1:8888/login 看到以下畫面就代表成功了

LoginAuth

設定完登入畫面後,我們開始寫登入功能

handler

首先我們先設定好會用到的變數,因為這次主要會用到的就是 帳號密碼,因此我們設定如下

var (
    username string
    password string
)

再來我們設定 POST FORM 的接收,gin.Context 提供了 GetPostForm 方法可以使用,因此我們判斷是否有輸入帳號密碼的程式如下

if in, isExist := c.GetPostForm("username"); isExist && in != "" {
		username = in
	} else {
		c.HTML(http.StatusBadRequest, "login.html", gin.H{
			"error": errors.New("必須輸入使用者名稱"),
		})
		return
	}
	if in, isExist := c.GetPostForm("password"); isExist && in != "" {
		password = in
	} else {
		c.HTML(http.StatusBadRequest, "login.html", gin.H{
			"error": errors.New("必須輸入密碼名稱"),
		})
		return
	}

如果沒有輸入帳號或是密碼,就會回傳 error,在畫面上就會顯示出錯誤了

接著判斷輸入的帳號密碼是否有誤,可以很簡單的呼叫最上面我們封裝的 Auth 方法即可,程式如下

if err := Auth(username, password); err == nil {
    c.HTML(http.StatusOK, "login.html", gin.H{
        "success": "登入成功",
    })
    return
} else {
    c.HTML(http.StatusUnauthorized, "login.html", gin.H{
        "error": err,
    })
    return
}

最後拼湊出來的程式如下


func LoginAuth(c *gin.Context) {
	var (
		username string
		password string
	)
	if in, isExist := c.GetPostForm("username"); isExist && in != "" {
		username = in
	} else {
		c.HTML(http.StatusBadRequest, "login.html", gin.H{
			"error": errors.New("必須輸入使用者名稱"),
		})
		return
	}
	if in, isExist := c.GetPostForm("password"); isExist && in != "" {
		password = in
	} else {
		c.HTML(http.StatusBadRequest, "login.html", gin.H{
			"error": errors.New("必須輸入密碼名稱"),
		})
		return
	}
	if err := Auth(username, password); err == nil {
		c.HTML(http.StatusOK, "login.html", gin.H{
			"success": "登入成功",
		})
		return
	} else {
		c.HTML(http.StatusUnauthorized, "login.html", gin.H{
			"error": err,
		})
		return
	}
}

routing

最後,我們在 main.go 裡面將 GET /loginLoginAuth 進行綁定

server.POST("/login", LoginPage)

完成後的 main.go 會像這樣

package main

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

func main() {
	server := gin.Default()
	server.LoadHTMLGlob("template/html/*")
	//設定靜態資源的讀取
	server.Static("/assets", "./template/assets")
	server.GET("/login", LoginPage)
	server.POST("/login", LoginAuth)
	server.Run(":8888")
}

測試功能

最後,我們可以來測試功能拉,設定好幾個情境進行測試,如果看到以下畫面代表正確

沒有輸入帳號

沒有輸入密碼

帳號密碼錯誤

登入成功

小結

今天的範例很簡單的實作了最常見的使用者登入的功能,但登入的資訊如果要增加每一次都要修改程式好麻煩的,通常這個時候我們就會利用 資料庫 來解決這個問題!

因此明天就來學習怎麼透過 orm 來存取資料庫吧!

由於週末兩天都特別忙碌,今天這篇算是趕著出來的,有任何的錯誤再麻煩大家告知了~

參考資料

這次沒有任何的參考資料~


上一篇
Day5 | Gin - 好用的 web framework
下一篇
Day7 | 使用 GoLang 與資料庫進行互動
系列文
Go into Web!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
1
jo890117
iT邦新手 5 級 ‧ 2022-03-24 14:36:12

我應該把LoginAuth放在哪個程式,
我目前放在main.go
但執行他讀不到auth

看更多先前的回應...收起先前的回應...

comannd line: go run .

執行全部試試看

Stance iT邦新手 5 級 ‧ 2022-08-11 18:19:45 檢舉

還是一樣耶⋯⋯

阿翔 iT邦新手 4 級 ‧ 2022-09-02 16:30:22 檢舉

LoginAuth 是放在 main.go 裡面哦,基本上不管切成幾個檔案,在同一個目錄底下 package 名稱相同就不需要額外的做 import

zhijiun iT邦新手 4 級 ‧ 2022-11-15 12:12:02 檢舉

我執行的時候也讀不到Auth

0
scott_chu
iT邦新手 5 級 ‧ 2023-01-10 17:17:01

將我的問題與解決方式留下
問題:panic: html/template: pattern matches no files:
解決:如果建立的檔案,不是在GO的安裝目錄底下
將func main裡面的
server.LoadHTMLGlob("template/html/")
改成server.LoadHTMLGlob("./template/html/")
原因:go的環境變數預設為安裝目錄,直接使用相對路徑會找不到,要加上"./"
參考:來源

我要留言

立即登入留言