去提问»
提问背景 Question:
以下程序是我们取回密码的go程序,你它为例子,你要帮我写一个 baike.go程序,程序包是ai目录包。另外你之前写的模板我也发给你,你要修改成go标签可用。 ``` package page import ( "encoding/json" "fmt" "net/http" "path/filepath" "strings" "stv/fun" "time" "github.com/julienschmidt/httprouter" "golang.org/x/crypto/bcrypt" ) // 1. 读取并渲染 getpwd.html 页面模板 func Getpwd_page(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { path := filepath.Join(fun.AppPath, "/static/index.html") mb, err := fun.TManager.Get(path) if err != nil { http.Error(w, "getpwd.go > "+err.Error(), http.StatusInternalServerError) fun.WriteLog("getpwd.go > %s", err) return } err = mb.Execute(w, nil) if err != nil { http.Error(w, "getpwd.go > "+err.Error(), http.StatusInternalServerError) fun.WriteLog("getpwd.go > %s", err) } } ``` 上面是渲染页面的go程序,数据库结构和三张表你还记得吧。 ``` CREATE TABLE cms_article (id INTEGER PRIMARY KEY, question TEXT NOT NULL, title TEXT NOT NULL, description TEXT, content TEXT NOT NULL, source TEXT NOT NULL DEFAULT 'manual', ip TEXT DEFAULT NULL, score INTEGER DEFAULT (0), click_count INTEGER DEFAULT 0, like_count INTEGER DEFAULT 0, status INTEGER DEFAULT 0, created_at INTEGER NOT NULL DEFAULT (0), updated_at INTEGER NOT NULL DEFAULT (0), user_id INTEGER DEFAULT (0), msg_type INTEGER DEFAULT (0)) ``` 这是内容表。 数据库连接用的是类似err := fun.DB.QueryRow 另外二张表是 ``` CREATE TABLE cms_article_tag ( article_id INTEGER, tag_id INTEGER, PRIMARY KEY (article_id, tag_id), FOREIGN KEY (article_id) REFERENCES cms_article(id) ON DELETE CASCADE, FOREIGN KEY (tag_id) REFERENCES cms_tag(id) ON DELETE CASCADE ) CREATE TABLE cms_tag ( id INTEGER PRIMARY KEY, name TEXT NOT NULL UNIQUE, count INTEGER DEFAULT 0, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL ) ``` 模板就是你刚才设计的哪个模板,回头我添加标签就可以了,关键是如何写读取数据库,并利用标签写进模板。没问题的话,可以写程序了。

Go与SQLite百科后端实现

发布时间:2026-08-17 21:53 👁️ 阅读:2

为了完美契合你现有的 Go 项目架构(使用 httprouterstv/fun 封装库、SQLite 数据库),我为你编写了完整的 baike.go 后端业务代码。程序包归属于 package ai

同时,我也将之前设计的 HTML 页面改写为了兼容 Go 原生 html/template 的模板。由于文章详情页包含 AI 生成的格式化内容(通常是 Markdown 转换后的 HTML 或者是带分段的文本),我们在模板中需要将内容作为非转义 HTML 渲染。


一、 后端 Go 代码:ai/baike.go

请在你的项目中创建 ai/baike.go,代码如下:

package ai

import (
	"database/sql"
	"html/template"
	"net/http"
	"path/filepath"
	"strconv"
	"stv/fun"
	"time"

	"github.com/julienschmidt/httprouter"
)

// Article 对应 cms_article 表及关联标签
type Article struct {
	ID          int
	Question    string
	Title       string
	Description string
	Content     template.HTML // 使用 template.HTML 避免 HTML 标签被转义
	ClickCount  int
	LikeCount   int
	CreatedAt   string
	Tags        []Tag
}

// Tag 对应 cms_tag 标签表
type Tag struct {
	ID   int
	Name string
	Count int
}

// BaikeIndexData 首页模板渲染数据
type BaikeIndexData struct {
	Articles []Article
	HotTags  []Tag
	Keyword  string
}

// BaikeDetailData 详情页模板渲染数据
type BaikeDetailData struct {
	Article Article
	Tags    []Tag
}

// 1. 百科首页 Handler(带搜索支持)
// 路由配置:r.GET("/baike", ai.Baike_index)
func Baike_index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	query := r.URL.Query()
	keyword := query.Get("q")
	tagName := query.Get("tag")

	// 获取热门标签 (前20个)
	var hotTags []Tag
	rows, err := fun.DB.Query("SELECT id, name, count FROM cms_tag WHERE count > 0 ORDER BY count DESC LIMIT 20")
	if err == nil {
		for rows.Next() {
			var t Tag
			if err := rows.Scan(&t.ID, &t.Name, &t.Count); err == nil {
				hotTags = append(hotTags, t)
			}
		}
		rows.Close()
	}

	// 查询文章列表
	var articles []Article
	var sqlStr string
	var args []interface{}

	if keyword != "" {
		// 关键词搜索模式
		sqlStr = `SELECT id, title, description, click_count, created_at 
		          FROM cms_article 
		          WHERE status = 1 AND (title LIKE ? OR content LIKE ? OR question LIKE ?) 
		          ORDER BY id DESC LIMIT 50`
		likeKey := "%" + keyword + "%"
		args = append(args, likeKey, likeKey, likeKey)
	} else if tagName != "" {
		// 标签过滤模式
		sqlStr = `SELECT a.id, a.title, a.description, a.click_count, a.created_at 
		          FROM cms_article a
		          INNER JOIN cms_article_tag at ON a.id = at.article_id
		          INNER JOIN cms_tag t ON t.id = at.tag_id
		          WHERE a.status = 1 AND t.name = ? 
		          ORDER BY a.id DESC LIMIT 50`
		args = append(args, tagName)
	} else {
		// 默认最新列表
		sqlStr = `SELECT id, title, description, click_count, created_at 
		          FROM cms_article 
		          WHERE status = 1 
		          ORDER BY id DESC LIMIT 15`
	}

	rows, err = fun.DB.Query(sqlStr, args...)
	if err != nil {
		http.Error(w, "读取数据库失败: "+err.Error(), http.StatusInternalServerError)
		return
	}
	defer rows.Close()

	for rows.Next() {
		var a Article
		var createdAtInt int64
		err := rows.Scan(&a.ID, &a.Title, &a.Description, &a.ClickCount, &createdAtInt)
		if err == nil {
			// 格式化时间戳
			a.CreatedAt = time.Unix(createdAtInt, 0).Format("2006-01-02 15:04")
			articles = append(articles, a)
		}
	}

	// 拼装首页数据
	data := BaikeIndexData{
		Articles: articles,
		HotTags:  hotTags,
		Keyword:  keyword,
	}

	// 渲染模板
	path := filepath.Join(fun.AppPath, "/static/baike_index.html")
	mb, err := fun.TManager.Get(path)
	if err != nil {
		http.Error(w, "模板加载失败: "+err.Error(), http.StatusInternalServerError)
		return
	}

	err = mb.Execute(w, data)
	if err != nil {
		fun.WriteLog("渲染百科首页模板报错: %s", err)
	}
}

// 2. 百科详情页 Handler
// 路由配置:r.GET("/baike/article/:id", ai.Baike_detail)
func Baike_detail(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	articleIDStr := ps.ByName("id")
	articleID, err := strconv.Atoi(articleIDStr)
	if err != nil {
		http.NotFound(w, r)
		return
	}

	// 1. 查询文章主体
	var a Article
	var contentRaw string
	var createdAtInt int64
	row := fun.DB.QueryRow(
		"SELECT id, question, title, description, content, click_count, like_count, created_at FROM cms_article WHERE id = ? AND status = 1",
		articleID,
	)
	err = row.Scan(&a.ID, &a.Question, &a.Title, &a.Description, &contentRaw, &a.ClickCount, &a.LikeCount, &createdAtInt)
	if err != nil {
		if err == sql.ErrNoRows {
			http.NotFound(w, r)
		} else {
			http.Error(w, "数据库错误: "+err.Error(), http.StatusInternalServerError)
		}
		return
	}

	a.Content = template.HTML(contentRaw) // 转换为非转义 HTML,直接渲染
	a.CreatedAt = time.Unix(createdAtInt, 0).Format("2006-01-02 15:04")

	// 2. 异步增加浏览量(避免阻塞主渲染)
	go func(id int) {
		_, _ = fun.DB.Exec("UPDATE cms_article SET click_count = click_count + 1 WHERE id = ?", id)
	}(articleID)

	// 3. 查询当前文章的 Tags
	var tags []Tag
	tagRows, err := fun.DB.Query(`
		SELECT t.id, t.name 
		FROM cms_tag t 
		INNER JOIN cms_article_tag at ON t.id = at.tag_id 
		WHERE at.article_id = ?`,
		articleID,
	)
	if err == nil {
		for tagRows.Next() {
			var t Tag
			if err := tagRows.Scan(&t.ID, &t.Name); err == nil {
				tags = append(tags, t)
			}
		}
		tagRows.Close()
	}

	data := BaikeDetailData{
		Article: a,
		Tags:    tags,
	}

	// 4. 渲染模板
	path := filepath.Join(fun.AppPath, "/static/baike_detail.html")
	mb, err := fun.TManager.Get(path)
	if err != nil {
		http.Error(w, "详情模板渲染失败: "+err.Error(), http.StatusInternalServerError)
		return
	}

	err = mb.Execute(w, data)
	if err != nil {
		fun.WriteLog("渲染百科详情页模板报错: %s", err)
	}
}

二、前端 Go 适配模板(包含数据输出标签)

以下是修改后的适配 Go 渲染的模板,你需要在服务器上存放在 /static/baike_index.html/static/baike_detail.html

1. 首页模板:baike_index.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>AI 知识百科 - bbsgood.com</title>
    <script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-50 text-gray-800">
    <!-- 导航栏 -->
    <header class="bg-white border-b sticky top-0 z-50 shadow-sm">
        <div class="max-w-6xl mx-auto px-4 h-16 flex items-center justify-between">
            <a href="/baike" class="flex items-center space-x-2">
                <span class="text-xl font-bold bg-gradient-to-r from-blue-600 to-indigo-600 bg-clip-text text-transparent">BBSGOOD 百科</span>
            </a>
            <nav class="hidden md:flex space-x-6">
                <a href="/" class="text-gray-600 hover:text-blue-600">社区首页</a>
                <a href="/baike" class="text-blue-600 font-medium">百科主页</a>
            </nav>
        </div>
    </header>

    <!-- 主体内容 -->
    <main class="max-w-6xl mx-auto px-4 py-8">
        <!-- 搜索与标语 -->
        <div class="text-center max-w-2xl mx-auto mb-10">
            <h1 class="text-2xl md:text-3xl font-extrabold text-gray-900 mb-4">海量 AI 精选对话与知识库</h1>
            <p class="text-gray-500 mb-6">搜索你感兴趣的 AI 问答,积累数字资产</p>
            <!-- 搜索框表单 -->
            <form action="/baike" method="GET" class="relative">
                <input type="text" name="q" value="{{.Keyword}}" placeholder="搜索高价值知识、问题或标签..." class="w-full px-5 py-3 pr-12 rounded-full border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 shadow-sm transition">
                <button type="submit" class="absolute right-3 top-1/2 -translate-y-1/2 p-2 bg-blue-600 hover:bg-blue-700 text-white rounded-full transition">
                    <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></svg>
                </button>
            </form>
        </div>

        <!-- 左右分栏 -->
        <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
            <!-- 左侧核心文章列表 (占2栏) -->
            <div class="lg:col-span-2 space-y-4">
                <h2 class="text-lg font-bold text-gray-900 flex items-center space-x-2 border-b pb-2">
                    <span>{{if .Keyword}}“{{.Keyword}}”的搜索结果{{else}}最新收录百科{{end}}</span>
                </h2>
                
                {{if .Articles}}
                    {{range .Articles}}
                    <article class="bg-white p-5 rounded-xl border border-gray-100 hover:border-blue-500 shadow-sm hover:shadow-md transition duration-200">
                        <h3 class="text-lg font-bold text-gray-900 mb-2 hover:text-blue-600">
                            <a href="/baike/article/{{.ID}}">{{.Title}}</a>
                        </h3>
                        <p class="text-gray-600 text-sm mb-4 line-clamp-2">{{.Description}}</p>
                        <div class="flex items-center justify-between text-xs text-gray-400">
                            <div class="flex items-center space-x-3">
                                <span class="bg-gray-100 text-gray-600 px-2.5 py-1 rounded">浏览 {{.ClickCount}}</span>
                            </div>
                            <span>{{.CreatedAt}}</span>
                        </div>
                    </article>
                    {{end}}
                {{else}}
                    <div class="bg-white p-12 text-center rounded-xl border border-gray-100">
                        <p class="text-gray-400 text-sm">暂无匹配百科内容,换个关键词试试吧!</p>
                    </div>
                {{end}}
            </div>

            <!-- 右侧侧边栏 (占1栏) -->
            <div class="space-y-6">
                <!-- 热门标签云 -->
                <div class="bg-white p-5 rounded-xl border border-gray-100 shadow-sm">
                    <h3 class="font-bold text-gray-900 mb-4 pb-2 border-b">热门标签</h3>
                    <div class="flex flex-wrap gap-2">
                        {{range .HotTags}}
                        <a href="/baike?tag={{.Name}}" class="text-xs bg-blue-50 hover:bg-blue-100 text-blue-700 px-3 py-1.5 rounded-full transition">
                            # {{.Name}} <span class="text-blue-400 font-mono">({{.Count}})</span>
                        </a>
                        {{else}}
                        <span class="text-gray-400 text-xs">暂无标签数据</span>
                        {{end}}
                    </div>
                </div>
            </div>
        </div>
    </main>

    <footer class="bg-white border-t mt-12 py-6 text-center text-xs text-gray-400">
        <p>© 2024 bbsgood.com All Rights Reserved.</p>
    </footer>
</body>
</html>

2. 详情页模板:baike_detail.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{.Article.Title}} - AI 百科 - bbsgood.com</title>
    <script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-50 text-gray-800">
    <header class="bg-white border-b sticky top-0 z-50 shadow-sm">
        <div class="max-w-4xl mx-auto px-4 h-16 flex items-center justify-between">
            <a href="/baike" class="flex items-center space-x-2">
                <span class="text-xl font-bold bg-gradient-to-r from-blue-600 to-indigo-600 bg-clip-text text-transparent">BBSGOOD 百科</span>
            </a>
            <a href="/baike" class="text-sm text-blue-600 hover:underline">← 返回首页</a>
        </div>
    </header>

    <main class="max-w-4xl mx-auto px-4 py-8">
        <article class="bg-white rounded-2xl border border-gray-100 shadow-sm p-6 md:p-10">
            <!-- 面包屑/来源说明 -->
            <div class="text-xs text-gray-400 mb-4 flex items-center space-x-2">
                <a href="/baike" class="hover:underline">百科首页</a>
                <span>/</span>
                <span>文章正文</span>
            </div>

            <!-- 原始提问模块 -->
            <div class="bg-gray-50 border-l-4 border-blue-500 p-4 rounded-r-lg mb-6">
                <div class="text-xs font-bold text-blue-600 uppercase tracking-wider mb-1">提问背景 Question:</div>
                <p class="text-gray-700 font-medium text-sm md:text-base">{{.Article.Question}}</p>
            </div>

            <!-- 文章大标题 -->
            <h1 class="text-xl md:text-3xl font-extrabold text-gray-900 mb-4 leading-tight">{{.Article.Title}}</h1>

            <!-- 信息条 -->
            <div class="flex flex-wrap items-center justify-between border-b pb-4 mb-6 text-xs text-gray-400 gap-2">
                <div class="flex items-center space-x-4">
                    <span>发布时间:{{.Article.CreatedAt}}</span>
                    <span>阅读:{{.Article.ClickCount}}</span>
                    <span>有用:{{.Article.LikeCount}}</span>
                </div>
            </div>

            <!-- 核心正文 (富文本/Markdown转换后的 HTML 直接显示) -->
            <div class="prose max-w-none text-gray-700 leading-relaxed space-y-4 text-sm md:text-base">
                {{.Article.Content}}
            </div>

            <!-- 文章关联标签 -->
            {{if .Tags}}
            <div class="mt-8 pt-6 border-t">
                <div class="text-xs text-gray-400 mb-2">相关标签:</div>
                <div class="flex flex-wrap gap-2">
                    {{range .Tags}}
                    <a href="/baike?tag={{.Name}}" class="text-xs bg-gray-100 hover:bg-blue-50 hover:text-blue-600 px-3 py-1.5 rounded-full transition text-gray-600">
                        # {{.Name}}
                    </a>
                    {{end}}
                </div>
            </div>
            {{end}}
        </article>
    </main>

    <footer class="bg-white border-t mt-12 py-6 text-center text-xs text-gray-400">
        <p>© 2024 bbsgood.com All Rights Reserved.</p>
    </footer>
</body>
</html>