想要給網站加個評論功能,接入第三方服務怕數據不安全,引入龐大的前端框架又覺得“殺雞焉用牛刀”?

其實,利用瀏覽器現代標準 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>

運行效果

  1. 保持 go run main.go 在終端運行。
  2. 雙擊打開 index.html
  3. 輸入評論併發送,你會發現評論立刻保存到了 SQLite 數據庫中,刷新頁面數據依然存在。

go web框架_[進階]學習一下!只需3天就能用Go擼一個Web框架_#開發語言

總結

通過這個小 Demo,我們發現:

  1. Go 做後端真的很爽:不用配置複雜的 Webpack/Vite,不用擔心數據庫安裝,一個二進制文件走天下。
  2. Web Components 很強大:它讓我們迴歸 HTML 的本質。想象一下,你可以在你的個人博客、公司的老舊項目,甚至是別人的網站(通過油猴腳本)裏,只用一行 <my-comments></my-comments> 就插入這個功能,是不是很酷?