第三期 · 使用 Vue 3.1 + Axios + Golang + Sqlite3 實現簡單評論機制 效果圖 CommentArea.vue 我們需要藉助js的Data對象把毫秒時間戳轉化成 UTCString() 。併在模板表達式中使用 {{ dateConvert(value.date) } ...
第三期 · 使用 Vue 3.1 + Axios + Golang + Sqlite3 實現簡單評論機制
效果圖
CommentArea.vue
我們需要藉助js的Data對象把毫秒時間戳轉化成 UTCString() 。併在模板表達式中使用 {{ dateConvert(value.date) }}
src="@/assets/avater/hamster.jpg"
頭像目前目前是固定的,也可以將頭像資源地址存入資料庫中。
獲取JavaScript時間戳函數的方法和js時間戳轉時間方法_半生過往的博客-CSDN博客_js時間戳轉時間
dateConvert(date: number): string {
return new Date(date).toUTCString();
},
<template>
<div class="m-2">
<div class="text-3xl font-bold">Comments</div>
<template v-if="comments.length == 0">當前pid帖子沒有評論</template>
<template v-for="(value, index) in comments" :key="index">
<div class="border border-stone-300 p-1">
<div>
<img
src="@/assets/avater/hamster.jpg"
class="inline-block w-12 h-12 align-top"
/>
<div class="inline-block ml-2">
<div class="font-bold text-stone-700">{{ value.name }}</div>
<div class="text-stone-400 text-sm">
{{ dateConvert(value.date) }}
</div>
</div>
</div>
<div class="mt-2">{{ value.text }}</div>
<div class="float-right">
<span class="m-1 text-rose-500">回覆</span>
<span class="m-1 text-rose-500" @click="deleteComment(value.id)"
>刪除</span
>
</div>
<div class="clear-both"></div>
</div>
<div class="mt-2"></div>
</template>
</div>
</template>
<script lang="ts">
import { PropType } from "vue";
interface Comment {
date: number;
text: string;
id: number;
name: string;
}
export default {
name: "CommentArea",
props: {
comments: {
type: Array as PropType<Comment[]>,
required: true,
},
},
methods: {
dateConvert(date: number): string {
return new Date(date).toUTCString();
},
deleteComment(id: number) {
this.$emit("delete-comment", id);
},
},
};
</script>
Axios
安裝vue-axios
npm install axios vue-axios --save
導入vue-axios
修改 main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import { BootstrapIconsPlugin } from 'bootstrap-icons-vue';
import './index.css'
import axios from 'axios'
import VueAxios from 'vue-axios'
axios.defaults.baseURL = '/api'
createApp(App).use(VueAxios, axios).use(BootstrapIconsPlugin).use(store).use(router).mount('#app')
axios.defaults.baseURL = '/api'
用於解決跨域問題
解決跨域問題
修改 vue.config.js
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
devServer: {
port: 8080, //前端服務啟動的埠號
host: 'localhost', //前端服務啟動後的訪問ip,預設為localhost, host和port組成了前端服務啟動後的訪問入口。
https: false,
open: true,
//以上的ip和埠是我們本機的;下麵為需要跨域的
proxy: {//配置跨域
'/api': {
target: 'http://localhost:1314/',//這裡後臺的地址模擬的;應該填寫你們真實的後臺介面
ws: true,
changOrigin: true,//允許跨域
pathRewrite: {
'^/api': ''//請求的時候使用這個api就可以
}
}
}
}
})
CommentTestView.vue
<template>
<div class="text-center m-2">評論服務測試</div>
<div class="m-2">
<div class="text-3xl font-bold">Query Comments</div>
<input
id="pid"
class="input_text"
type="text"
placeholder="輸入帖子id查找評論"
v-model="pid"
/>
<input
type="button"
value="查詢"
class="input_button"
@click="queryComment"
/>
</div>
<div class="m-2">
<div class="text-3xl font-bold">Insert Comments</div>
<input
id="uid"
class="input_text"
type="text"
placeholder="當前用戶uid"
v-model="uid"
/>
<input
type="button"
value="添加"
class="input_button"
@click="insertComment"
/>
<div></div>
<input
id="pid"
class="input_text"
type="text"
placeholder="當前帖子pid"
v-model="pid"
/>
<div></div>
<textarea
id="text"
class="input_text w-full h-20"
rows="3"
cols="40"
placeholder="評論內容"
v-model="text"
/>
</div>
<comment-area
:comments="comments"
@delete-comment="deleteComment"
></comment-area>
</template>
將 deleteComment 綁定到commentArea的delete-comment事件上,將 insertComment 、 queryComment 分別綁定到兩個按鈕的click事件上。
insertComment 成功執行將拿到插入的評論json對象並放入當前數組中。
deleteComment 成功執行將通過數組的filter函數刪除當前評論json對象。
下方代碼相比前幾期多了style代碼塊,可以將相同標簽使用的共同功能類組合提取出來(兩個按鈕,五個輸入框),簡化代碼。
<script>
import CommentArea from '@/components/common/CommentArea.vue';
export default {
components: { CommentArea },
name: 'CommentTestView',
data: function () {
return {
pid: 100,
uid: 1003,
text: "",
comments: [
// {
// id: 1,
// uid: 1001,
// name: "西紅柿炒芹菜",
// text: "真的很不錯啊。SQLite 是一個開源的嵌入式關係資料庫,實現自包容、零配置、支持事務的 SQL 資料庫引擎。",
// date: 1665912139673,
// img: require("@/assets/avater/hamster.jpg")
// }
]
}
},
methods: {
insertComment() {
const params = new URLSearchParams();
params.append('uid', this.uid)
params.append('pid', this.pid)
params.append('text', this.text)
this.axios.post("insertComment", params
).then(response => {
console.log(response.data)
this.comments.unshift(
response.data
)
console.log(this.comments)
}).catch(err => {
console.log(err)
})
},
deleteComment(id) {
const params = new URLSearchParams();
params.append('id', id)
this.axios.post("deleteComment", params).then(response => {
console.log(response.data)
this.comments = this.comments.filter(elem => {
return elem.id != id
})
}).catch(err => {
console.log(err)
})
},
queryComment() {
this.axios.get("queryComment", {
params: {
pid: this.pid
}
}).then(response => {
if (!response.data) {
this.comments = []
return
}
this.comments = response.data
this.comments.reverse()
}).catch(err => {
console.log(err)
})
}
},
created() {
let old = localStorage.getItem(`comment_${this.pid}`)
if (old) {
this.text = old
}
},
watch: {
text() {
localStorage.setItem(`comment_${this.pid}`, this.text)
}
}
}
</script>
<style scoped>
.input_text {
@apply mt-2
inline-block
bg-white
focus:outline-none focus:ring focus:border-blue-200
py-1.5
pl-3
border border-stone-400
text-sm;
}
.input_button {
@apply border border-rose-400
text-sm
font-bold
text-rose-500
rounded-sm
px-4
py-1
mt-2
ml-4
active:bg-rose-400 active:text-white;
}
</style>
請求體編碼
axios post 請求客戶端可以直接發嗎,不能!在這裡使用了URLSearchParams
對象以application/x-www-form-urlencoded
格式發送數據。
const params = new URLSearchParams();
params.append('uid', this.uid)
params.append('pid', this.pid)
params.append('text', this.text)
其他方式可看 請求體編碼 | Axios Docs (axios-http.com)
保存沒寫完的評論
寫到一半關閉頁面後重新打開就不在了,可以用 localStorage 本地存儲臨時保存寫的內容,只能保存字元串。
created() {
let old = localStorage.getItem(`comment_${this.pid}`)
if (old) {
this.text = old
}
},
watch: {
text() {
localStorage.setItem(`comment_${this.pid}`, this.text)
}
}
創建資料庫和表
使用 Navicat Premium 創建資料庫跟表
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)