草庐IT

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

小能的博客 CanAngle's Blog 2023-03-28 原文

第三期 · 使用 Vue 3.1 + TailWind.CSS + 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)

有关我的Vue之旅 07 Axios + Golang + Sqlite3 实现简单评论机制的更多相关文章

  1. ruby-on-rails - 如何在我的 Rails 应用程序 View 中打印 ruby​​ 变量的内容? - 2

    我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby​​中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R

  2. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  3. ruby - 简单获取法拉第超时 - 2

    有没有办法在这个简单的get方法中添加超时选项?我正在使用法拉第3.3。Faraday.get(url)四处寻找,我只能先发起连接后应用超时选项,然后应用超时选项。或者有什么简单的方法?这就是我现在正在做的:conn=Faraday.newresponse=conn.getdo|req|req.urlurlreq.options.timeout=2#2secondsend 最佳答案 试试这个:conn=Faraday.newdo|conn|conn.options.timeout=20endresponse=conn.get(url

  4. ruby - 我可以将我的 README.textile 以正确的格式放入我的 RDoc 中吗? - 2

    我喜欢使用Textile或Markdown为我的项目编写自述文件,但是当我生成RDoc时,自述文件被解释为RDoc并且看起来非常糟糕。有没有办法让RDoc通过RedCloth或BlueCloth而不是它自己的格式化程序运行文件?它可以配置为自动检测文件后缀的格式吗?(例如README.textile通过RedCloth运行,但README.mdown通过BlueCloth运行) 最佳答案 使用YARD直接代替RDoc将允许您包含Textile或Markdown文件,只要它们的文件后缀是合理的。我经常使用类似于以下Rake任务的东西:

  5. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  6. jquery - 我的 jquery AJAX POST 请求无需发送 Authenticity Token (Rails) - 2

    rails中是否有任何规定允许站点的所有AJAXPOST请求在没有authenticity_token的情况下通过?我有一个调用Controller方法的JqueryPOSTajax调用,但我没有在其中放置任何真实性代码,但调用成功。我的ApplicationController确实有'request_forgery_protection'并且我已经改变了config.action_controller.consider_all_requests_local在我的environments/development.rb中为false我还搜索了我的代码以确保我没有重载ajaxSend来发送

  7. ruby-on-rails - 简单的 Ruby on Rails 问题——如何将评论附加到用户和文章? - 2

    我意识到这可能是一个非常基本的问题,但我现在已经花了几天时间回过头来解决这个问题,但出于某种原因,Google就是没有帮助我。(我认为部分问题在于我是一个初学者,我不知道该问什么......)我也看过O'Reilly的RubyCookbook和RailsAPI,但我仍然停留在这个问题上.我找到了一些关于多态关系的信息,但它似乎不是我需要的(尽管如果我错了请告诉我)。我正在尝试调整MichaelHartl'stutorial创建一个包含用户、文章和评论的博客应用程序(不使用脚手架)。我希望评论既属于用户又属于文章。我的主要问题是:我不知道如何将当前文章的ID放入评论Controller。

  8. java - 我的模型类或其他类中应该有逻辑吗 - 2

    我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我

  9. ruby - 使用 Ruby 通过 Outlook 发送消息的最简单方法是什么? - 2

    我的工作要求我为某些测试自动生成电子邮件。我一直在四处寻找,但未能找到可以快速实现的合理解决方案。它需要在outlook而不是其他邮件服务器中,因为我们有一些奇怪的身份验证规则,我们需要保存草稿而不是仅仅发送邮件的选项。显然win32ole可以做到这一点,但我找不到任何相当简单的例子。 最佳答案 假设存储了Outlook凭据并且您设置为自动登录到Outlook,WIN32OLE可以很好地完成此操作:require'win32ole'outlook=WIN32OLE.new('Outlook.Application')message=

  10. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

随机推荐