我的Vue之旅 07 Axios + Golang + Sqlite3 實現簡單評論機制

2022-10-19 12:00:49

第三期 · 使用 Vue 3.1 + TailWind.CSS + Axios + Golang + Sqlite3 實現簡單評論機制

效果圖


CommentArea.vue


Golang 伺服器端

C:.
│   comment.json
│   go.mod
│   go.sum
│   main.go
│   
├───data
│       data.db
│       
└───lib
    ├───http
    │       server.go
    │       
    ├───mysql
    └───sqlite
            sq3_comment.go
            sq3_init.go
            sq3_users.go

JSON2GO

我們把訊息JSON格式擬定出來

[
  {
    "id": 1,
    "uid": 1001,
    "name": "小王",
    "text": "看起來很好玩的樣子。",
    "pid": 100,
    "date": 1665908807784
  }
]

JSON 轉GO,JSON轉GO程式碼, go json解析 (sojson.com)

type AutoGenerated []struct {
	ID int `json:"id"`
	UID int `json:"uid"`
	Name string `json:"name"`
	Text string `json:"text"`
	Pid int `json:"pid"`
	Date int64 `json:"date"`
}

解決sqlite3 gcc:exec: "gcc": executable file not found in %PATH%

Windows 如果使用 Go 語言使用 sqlite3 時,需要gcd來編譯sqlite3模組相關c程式碼。

解決方法:安裝tdm64-gcc-9.2.0.exe, https://jmeubank.github.io/tdm-gcc/download/


資料庫處理邏輯 sq3_vue包

sq3_init.go

init() 初始化函數獲取main執行目錄,並按作業系統連線檔案位置,讀取檔案。

package sq3_vue

import (
	"database/sql"
	"os"
	"path"

	_ "github.com/mattn/go-sqlite3"
)

var db *sql.DB

func init() {
	p, err := os.Getwd()
	checkError(err)
	db, err = sql.Open("sqlite3", path.Join(p, "data/data.db"))
	checkError(err)
}

func checkError(err error) {
	if err != nil {
		panic(err)
	}
}

sq3_comment.go

為具體的資料庫處理邏輯,插入返回comment的json位元組切片 {},查詢返回comment陣列的json位元組切片 [{},{},{}]

*sql.DB 是Go標準庫規定的介面,方便操作。

stmt、rows 需要 defer close()

package sq3_vue

import (
	"encoding/json"
	"fmt"
	"time"
)

type Comment struct {
	ID   int    `json:"id"`
	UID  int    `json:"uid"`
	Name string `json:"name"`
	Text string `json:"text"`
	Pid  int    `json:"pid"`
	Date int64  `json:"date"`
}

const insertStmt = `
INSERT INTO comments(uid,text,pid,date) values(?,?,?,?)
`
const lastedStmt = `
select id,uid,text,pid,date,name from comments join users using(uid) where id = ?
`

func (Comment) InsertComment(uid, pid int64, text string) (json_ []byte, err error) {
	stmt, err := db.Prepare(insertStmt)
	checkError(err)
	defer stmt.Close()
	res, err := stmt.Exec(uid, text, pid, time.Now().UnixMilli())
	checkError(err)
	n, err := res.RowsAffected()
	checkError(err)
	if n == 0 {
		return nil, fmt.Errorf("插入失敗")
	}
	n, err = res.LastInsertId()
	checkError(err)
	stmt, err = db.Prepare(lastedStmt)
	checkError(err)
	defer stmt.Close()
	rows, err := stmt.Query(n)
	checkError(err)
	defer rows.Close()
	rows.Next()
	var c Comment
	rows.Scan(&c.ID, &c.UID, &c.Text, &c.Pid, &c.Date, &c.Name)
	checkError(err)
	json_, err = json.Marshal(c)
	checkError(err)
	return json_, nil
}

const deleteStmt = `
delete from comments where id = ?
`

func (Comment) DeleteComment(id int64) error {
	stmt, err := db.Prepare(deleteStmt)
	checkError(err)
	defer stmt.Close()
	res, err := stmt.Exec(id)
	checkError(err)
	n, err := res.RowsAffected()
	checkError(err)
	if n == 0 {
		return fmt.Errorf("刪除失敗")
	}
	return nil
}

const queryStmt = `
select id,uid,text,pid,date,name from comments join users using(uid) where pid = ?
`

func (Comment) QueryComment(pid int64) (json_ []byte, err error) {
	var res []Comment
	stmt, err := db.Prepare(queryStmt)
	checkError(err)
	defer stmt.Close()
	rows, err := stmt.Query(pid)
	checkError(err)
	defer rows.Close()
	for rows.Next() {
		var c Comment
		err = rows.Scan(&c.ID, &c.UID, &c.Text, &c.Pid, &c.Date, &c.Name)
		checkError(err)
		res = append(res, c)
	}
	json_, err = json.Marshal(res)
	checkError(err)
	return
}

簡單HTTP伺服器

server.go

我們分別判斷請求方法,要求刪除和插入只能用post請求,查詢只能用get請求。使用r.ParseForm() 處理表單。

r.Form["uid"] 本質上拿到的字串陣列,需要進行顯式型別轉換。

db "wolflong.com/vue_comment/lib/sqlite" 引入了前面寫的資料庫處理包。因為考慮到不一定要用 sqlite,未來可能會使用 mysql、mongoDB。目前已經強耦合了,即當前http伺服器的實現跟sq3_vue包緊密相關,考慮用介面降低耦合程度。

type comment interface {
	QueryComment(pid int64) (json_ []byte, err error)
	InsertComment(uid, pid int64, text string) (json_ []byte, err error)
	DeleteComment(id int64) error
}

var c comment = db.Comment{}

我們將資料庫行為接收者指派為Comment型別,當該型別實現了三個對應函數簽名的方法就實現了comment介面。此時我們建立一個空Comment型別賦值給comment介面變數。那麼其他資料庫邏輯處理包只要提供實現comment介面的型別物件就好了。換什麼資料庫也影響不到當前HTTP的處理邏輯。

package server

import (
	"fmt"
	"log"
	"net/http"
	"strconv"

	db "wolflong.com/vue_comment/lib/sqlite"
)

type comment interface {
	QueryComment(pid int64) (json_ []byte, err error)
	InsertComment(uid, pid int64, text string) (json_ []byte, err error)
	DeleteComment(id int64) error
}

var c comment = db.Comment{}

func checkError(err error) {
	if err != nil {
		panic(err)
	}
}

func insertComment(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		fmt.Fprintf(w, "Only POST Method")
		return
	}
	r.ParseForm()
	fmt.Println(r.Form)
	// ^ 簡單實現,有待提高健壯性
	uid, err := strconv.Atoi(r.Form["uid"][0])
	checkError(err)
	pid, err := strconv.Atoi(r.Form["pid"][0])
	checkError(err)
	text := r.Form["text"][0]
	inserted, err := c.InsertComment(int64(uid), int64(pid), text)
	if err != nil {
		fmt.Fprintf(w, "Error Insert")
		return
	}
	fmt.Fprint(w, string(inserted))
}

func deleteComment(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		fmt.Fprintf(w, "Only POST Method")
		return
	}
	r.ParseForm()
	fmt.Println(r.Form)
	id, err := strconv.Atoi(r.Form["id"][0])
	checkError(err)
	err = c.DeleteComment(int64(id))
	if err != nil {
		fmt.Fprintf(w, "Error Delete")
		return
	}
	fmt.Fprintf(w, "Success Delete")
}

func queryComment(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		fmt.Fprintf(w, "Only GET Method")
		return
	}
	r.ParseForm()
	fmt.Println(r.Form)
	pid, err := strconv.Atoi(r.Form["pid"][0])
	checkError(err)
	json, err := c.QueryComment(int64(pid))
	if err != nil {
		fmt.Fprintf(w, "Error Delete")
		return
	}
	fmt.Fprint(w, string(json))
}

func StartServer() {
	http.HandleFunc("/insertComment", insertComment)
	http.HandleFunc("/deleteComment", deleteComment)
	http.HandleFunc("/queryComment", queryComment)
	err := http.ListenAndServe(":1314", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

main.go

package main

import (
	"fmt"

	http "wolflong.com/vue_comment/lib/http"
)

func main() {
	fmt.Println("2022年10月16日 https://cnblogs.com/linxiaoxu")
	http.StartServer()
}

資料

SQLite Join | 菜鳥教學 (runoob.com)

使用 SQLite 資料庫 - 使用 Golang 打造 Web 應用程式 (gitbook.io)

mattn/go-sqlite3: sqlite3 driver for go using database/sql (github.com)

sqlite3 package - github.com/mattn/go-sqlite3 - Go Packages

go-sqlite3/simple.go at master · mattn/go-sqlite3 (github.com)

05.3. 使用 SQLite 資料庫 | 第五章. 存取資料庫 |《Go Web 程式設計》| Go 技術論壇 (learnku.com)

04.1. 處理表單的輸入 | 第四章. 表單 |《Go Web 程式設計》| Go 技術論壇 (learnku.com)