我的Vue之旅 07 Axios + Golang + Sqlite3 实现简单评论机制

第三期 · 使用 Vue 3.1 + TailWind.CSS + Axios + Golang + Sqlite3 实现简单评论机制

效果图

我的Vue之旅 07 Axios + Golang + Sqlite3 实现简单评论机制插图1

CommentArea.vue

我的Vue之旅 07 Axios + Golang + Sqlite3 实现简单评论机制插图2
我们需要借助js的Data对象把毫秒时间戳转化成 UTCString() 。并在模板表达式中使用 {{ dateConvert(value.date) }}

src="@/assets/avater/hamster.jpg"头像目前目前是固定的,也可以将头像资源地址存入数据库中。

获取JavaScript时间戳函数的方法和js时间戳转时间方法_半生过往的博客-CSDN博客_js时间戳转时间

    dateConvert(date: number): string {
      return new Date(date).toUTCString();
    },



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


将 deleteComment 绑定到commentArea的delete-comment事件上,将 insertComment 、 queryComment 分别绑定到两个按钮的click事件上。

insertComment 成功执行将拿到插入的评论json对象并放入当前数组中。

deleteComment 成功执行将通过数组的filter函数删除当前评论json对象。

下方代码相比前几期多了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 创建数据库跟表

我的Vue之旅 07 Axios + Golang + Sqlite3 实现简单评论机制插图4

我的Vue之旅 07 Axios + Golang + Sqlite3 实现简单评论机制插图5

我的Vue之旅 07 Axios + Golang + Sqlite3 实现简单评论机制插图6

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)

文章来源于互联网:我的Vue之旅 07 Axios + Golang + Sqlite3 实现简单评论机制

THE END
分享
二维码