想要給網站加個評論功能,接入第三方服務怕數據不安全,引入龐大的前端框架又覺得“殺雞焉用牛刀”?
其實,利用瀏覽器現代標準 Web Components 和編譯極其方便的 Go 語言,我們可以快速構建一個無依賴、高性能的全棧評論系統。它不需要複雜的構建流程,部署時只需一個二進制文件,非常適合個人開發者快速上手。
技術棧一覽
- Backend (後端): Go (Gin 框架) + Gorm + SQLite
- 理由:部署極其簡單,編譯後只是一個二進制文件,自帶數據庫,無需額外安裝 MySQL。
- Frontend (前端): 原生 JavaScript + Web Components
- 理由:無依賴,無構建流程,標準 HTML 標籤,真正的組件化。
第一部分:後端 —— 極速搭建 API
我們將使用 Go 語言最流行的 Gin 框架,配合 Gorm 進行數據庫操作。整個後端代碼不到 60 行。
1. 初始化項目
首先,在終端中創建一個文件夾並初始化 Go 模塊:
mkdir go-comments
cd go-comments
go mod init go-comments
安裝必要的依賴包:
# Web 框架
go get -u github.com/gin-gonic/gin
# ORM 和 數據庫驅動
go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite
# 跨域處理中間件
go get -u github.com/gin-contrib/cors
2. 編寫 main.go
新建 main.go,這是我們要實現的全部邏輯:
package main
import (
"time"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
// 定義評論模型,Gorm 會自動把它轉換成數據庫表
type Comment struct {
ID uint `gorm:"primaryKey" json:"id"`
Content string `json:"content"`
CreatedAt time.Time `json:"created_at"`
}
func main() {
// 1. 連接 SQLite 數據庫 (自動創建 comments.db)
db, err := gorm.Open(sqlite.Open("comments.db"), &gorm.Config{})
if err != nil {
panic("數據庫連接失敗")
}
// 2. 自動遷移:保證數據庫表結構與 Struct 同步
db.AutoMigrate(&Comment{})
// 3. 初始化 Gin 引擎
r := gin.Default()
// 4. 配置 CORS (允許跨域)
// 因為前端可能跑在不同端口,必須允許跨域請求
r.Use(cors.Default())
// 5. 路由定義
// 獲取評論列表
r.GET("/comments", func(c *gin.Context) {
var comments []Comment
// 按時間倒序查詢
db.Order("created_at desc").Find(&comments)
c.JSON(200, comments)
})
// 提交新評論
r.POST("/comments", func(c *gin.Context) {
var jsonInput Comment
// 綁定 JSON 數據
if err := c.ShouldBindJSON(&jsonInput); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// 寫入數據庫
db.Create(&jsonInput)
c.JSON(200, jsonInput)
})
// 6. 在 8080 端口啓動服務
r.Run(":8080")
}
運行後端非常簡單:
go run main.go
看到 Listening and serving HTTP on :8080 即表示成功。
第二部分:前端 —— 封裝 Web Components
我們要創建一個自定義標籤 <my-comments>,無論把它放在哪個 HTML 頁面裏,它都能自動渲染出評論區並擁有交互功能。
編寫 index.html
為了演示方便,我們將 JS 直接寫在 HTML 裏(實際開發中建議分離為 .js 文件)。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Go + Web Components Demo</title>
</head>
<body>
<my-comments></my-comments>
<script>
// 自定義組件類
class MyComments extends HTMLElement {
constructor() {
super();
// 開啓 Shadow DOM,讓樣式與外部隔離,互不影響
this.attachShadow({ mode: 'open' });
this.apiUrl = 'http://localhost:8080/comments';
}
connectedCallback() {
this.render();
this.fetchComments(); // 組件加載時拉取數據
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host { display: block; font-family: sans-serif; }
.container {
max-width: 500px; margin: 20px auto;
border: 1px solid #e0e0e0; border-radius: 8px; padding: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
}
h3 { margin-top: 0; color: #333; }
ul { list-style: none; padding: 0; max-height: 300px; overflow-y: auto; }
li { border-bottom: 1px solid #f0f0f0; padding: 10px 0; }
.content { font-size: 16px; color: #444; }
.time { font-size: 12px; color: #999; margin-top: 4px; }
.input-group { display: flex; gap: 10px; margin-top: 20px; }
input { flex: 1; padding: 8px; border: 1px solid #ccc; border-radius: 4px; }
button {
padding: 8px 16px; background: #007bff; color: white;
border: none; border-radius: 4px; cursor: pointer;
}
button:hover { background: #0056b3; }
</style>
<div class="container">
<h3>💬 評論區</h3>
<ul id="list">加載中...</ul>
<div class="input-group">
<input type="text" id="input" placeholder="寫下你的想法..." />
<button id="btn">發送</button>
</div>
</div>
`;
// 綁定點擊事件
this.shadowRoot.getElementById('btn').onclick = () => this.postComment();
}
// 獲取評論 (GET)
async fetchComments() {
try {
const res = await fetch(this.apiUrl);
const data = await res.json();
const list = this.shadowRoot.getElementById('list');
if (data.length === 0) {
list.innerHTML = '<li style="text-align:center;color:#999">暫無評論,快來搶沙發!</li>';
return;
}
list.innerHTML = data.map(c => `
<li>
<div class="content">${this.escapeHtml(c.content)}</div>
<div class="time">${new Date(c.created_at).toLocaleString()}</div>
</li>
`).join('');
} catch (err) {
console.error("API Error:", err);
this.shadowRoot.getElementById('list').innerHTML = '加載失敗,請檢查後端服務。';
}
}
// 提交評論 (POST)
async postComment() {
const input = this.shadowRoot.getElementById('input');
const content = input.value.trim();
if (!content) return;
try {
await fetch(this.apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content: content })
});
input.value = ''; // 清空輸入框
this.fetchComments(); // 刷新列表
} catch (err) {
alert("發送失敗!");
}
}
// 簡單的 XSS 防禦
escapeHtml(text) {
return text
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
}
// 註冊組件
customElements.define('my-comments', MyComments);
</script>
</body>
</html>
運行效果
- 保持
go run main.go在終端運行。 - 雙擊打開
index.html。 - 輸入評論併發送,你會發現評論立刻保存到了 SQLite 數據庫中,刷新頁面數據依然存在。
總結
通過這個小 Demo,我們發現:
- Go 做後端真的很爽:不用配置複雜的 Webpack/Vite,不用擔心數據庫安裝,一個二進制文件走天下。
- Web Components 很強大:它讓我們迴歸 HTML 的本質。想象一下,你可以在你的個人博客、公司的老舊項目,甚至是別人的網站(通過油猴腳本)裏,只用一行
<my-comments></my-comments>就插入這個功能,是不是很酷?