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

  • 我的Vue之旅 07 Axios + Golang + Sqlite3 实现简单评论机制已关闭评论
  • 186 次浏览
  • A+
所属分类:Web前端
摘要

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

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

效果图

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

CommentArea.vue

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

我们需要借助js的Data对象把毫秒时间戳转化成 UTCString() 。并在模板表达式中使用 {{ dateConvert(value.date) }}

src="@/assets/avater/hamster.jpg" alt="我的Vue之旅 07 Axios + Golang + Sqlite3 实现简单评论机制"头像目前目前是固定的,也可以将头像资源地址存入数据库中。

获取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" alt="我的Vue之旅 07 Axios + Golang + Sqlite3 实现简单评论机制"             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 创建数据库跟表

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

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

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

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)