惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
WordPress大学
WordPress大学
云风的 BLOG
云风的 BLOG
Stack Overflow Blog
Stack Overflow Blog
MongoDB | Blog
MongoDB | Blog
腾讯CDC
V
V2EX
Martin Fowler
Martin Fowler
A
About on SuperTechFans
大猫的无限游戏
大猫的无限游戏
Blog — PlanetScale
Blog — PlanetScale
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
酷 壳 – CoolShell
酷 壳 – CoolShell
C
Check Point Blog
博客园 - 【当耐特】
Cisco Talos Blog
Cisco Talos Blog
The Hacker News
The Hacker News
K
Kaspersky official blog
Security Latest
Security Latest
H
Help Net Security
博客园_首页
美团技术团队
Spread Privacy
Spread Privacy
博客园 - 司徒正美
Hugging Face - Blog
Hugging Face - Blog
S
SegmentFault 最新的问题
G
Google Developers Blog
NISL@THU
NISL@THU
爱范儿
爱范儿
I
Intezer
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
阮一峰的网络日志
阮一峰的网络日志
N
News and Events Feed by Topic
P
Privacy International News Feed
Application and Cybersecurity Blog
Application and Cybersecurity Blog
S
Security @ Cisco Blogs
Schneier on Security
Schneier on Security
雷峰网
雷峰网
人人都是产品经理
人人都是产品经理
V
Vulnerabilities – Threatpost
W
WeLiveSecurity
P
Palo Alto Networks Blog
G
GRAHAM CLULEY
Hacker News: Ask HN
Hacker News: Ask HN
I
InfoQ
The Cloudflare Blog
F
Full Disclosure
SecWiki News
SecWiki News
宝玉的分享
宝玉的分享
N
Netflix TechBlog - Medium

晚花行乐

马克卡尼在2026年达沃斯论坛上的讲话(阅读材料) | 晚花行乐 鸡娃如何用力才是恰到好处 | 晚花行乐 读万卷书,行万里路的辩证关系 | 晚花行乐 反对培训机构掐尖招生 | 晚花行乐 小泽和建国会谈最后10分钟全文(阅读材料) | 晚花行乐 来看看 DeepSeek 怎么鸡娃 | 晚花行乐 谈谈基本功 | 晚花行乐 惠普 ProDesk SFF PC 各系列参数对比 | 晚花行乐 解决瘦客户机上安装 Debian 12 启动失败问题 | 晚花行乐 意拾喻言:老外写的文言文 | 晚花行乐 笠翁对韵中的典故(十三元) | 晚花行乐 谈谈中考取消四小门 | 晚花行乐 亚马逊云科技产品免费试用攻略(3) - 对象存储服务 | 晚花行乐 在 Windows 10 LTSC 版本上安装 WSL2 | 晚花行乐 Debian 12 的常用配置项 | 晚花行乐 在 Debian 12 上安装 Nvidia 显卡驱动程序 | 晚花行乐 解决 Debian 12 关机失败问题 | 晚花行乐 解决 VS Code 自动更新版本后卡在连接界面 | 晚花行乐 观看巴黎奥运会有感 | 晚花行乐 在 Windows10 上安装惠普旧打印机驱动程序 | 晚花行乐 欢迎关注公众号:晚花行乐 | 晚花行乐 如何编写拼写检查器 | 晚花行乐 亚马逊云科技产品免费试用攻略(2) - 云服务器 | 晚花行乐 Pandas 中 axis 参数的理解(附实例) | 晚花行乐 我打算命个名,叫什么什么 Manager | 晚花行乐 上海武康路历史建筑一览 | 晚花行乐 Python 实现简单的数学表达式解析并处理 | 晚花行乐 观看马拉松的感悟 | 晚花行乐 Python 保存 Cookies 到文件并再次读取 | 晚花行乐 Linux 共享打印服务 CUPS | 晚花行乐 如何为 Hugo 静态网站添加搜索功能 | 晚花行乐 解决 CSV 文件的第一列不能解析 | 晚花行乐 亚马逊云科技产品免费试用攻略(1) - 注册账户 | 晚花行乐 第二幕 Atma 的闲聊 | 晚花行乐 第二幕野蛮人的语音 | 晚花行乐 古入声和普通话平声对照 | 晚花行乐 第一幕的背景音乐 | 晚花行乐 第二幕亚马逊的语音 | 晚花行乐 第一幕的亚马逊的语音 | 晚花行乐 第一幕的野蛮人的语音 | 晚花行乐 MacOS 的彩蛋:Here's to the crazy ones | 晚花行乐 笠翁对韵的基本知识 | 晚花行乐 笠翁对韵中的典故(十二文) | 晚花行乐 笠翁对韵中的典故(十一真) | 晚花行乐 笠翁对韵中的典故(十灰) | 晚花行乐 杭州景点的楹联 | 晚花行乐 adb keycode 大全 | 晚花行乐 Scikit-learn 学习笔记(0)名词术语 | 晚花行乐 Scikit-learn 学习笔记(3)监督学习的例子 | 晚花行乐 SQLite 文档的学习笔记(1)长期支持计划 | 晚花行乐 SQLite 文档的学习笔记(2)测试方法 | 晚花行乐 笠翁对韵中的典故(九佳) | 晚花行乐 Ansible 如何检查一个程序的版本 | 晚花行乐 Ansible 如何检查一个文件夹是否存在 | 晚花行乐 pip 配置文件详解 | 晚花行乐 Ansible 如何检查一个URL是否正常 | 晚花行乐 Ansible 如何修改 iptables 规则 | 晚花行乐 Ansible 指定 playbook 运行的主机 | 晚花行乐 Ansible 如何清空文件夹 | 晚花行乐 Ansible 如何在本机执行命令 | 晚花行乐 笠翁对韵中的典故(八齐) | 晚花行乐 Python 中 Defaultdict 的理解 | 晚花行乐 《伊索寓言》电子书 | 晚花行乐 菲伯尔钢琴伴奏:第二册 | 晚花行乐 Python 的 Keyword-Only Arguments 理解 | 晚花行乐 Python 的 函数参数处理机制 | 晚花行乐 瓦瑞夫在第一幕的闲聊 | 晚花行乐 瓦瑞夫在第一幕的任务提示 | 晚花行乐 第一幕的女巫语音 | 晚花行乐 《Fluent Python》 读书笔记:文本和字节序列 | 晚花行乐 第一幕的罗格语音 | 晚花行乐 第一幕的圣骑士语音 | 晚花行乐 第一幕的男巫语音 | 晚花行乐 第一幕的旁白 | 晚花行乐 第一幕的恶魔 | 晚花行乐 Python 中 Element Tree 的理解 | 晚花行乐 《Fluent Python》 读书笔记:字典和集合 | 晚花行乐 笠翁对韵中的典故(七虞) | 晚花行乐 笠翁对韵中的典故(五微) | 晚花行乐 笠翁对韵中的典故(六鱼) | 晚花行乐 笠翁对韵中的典故(一东) | 晚花行乐 笠翁对韵中的典故(二冬) | 晚花行乐 笠翁对韵中的典故(三江) | 晚花行乐 笠翁对韵中的典故(四支) | 晚花行乐 姜太公钓鱼 | 晚花行乐 武王建立周朝 | 晚花行乐 大禹治水 | 晚花行乐 尧舜让位 | 晚花行乐 黄帝战蚩尤 | 晚花行乐 上下五千年-精简版 | 晚花行乐 成功修复鼠标按键 | 晚花行乐 横向的Word文档怎么加页眉页脚 | 晚花行乐 商标通用化的故事:商标代替商品名 | 晚花行乐 搜索空文件夹的批处理程序 | 晚花行乐 Sn0wbreeze不能运行? | 晚花行乐 天线的驻波比 | 晚花行乐 天线参数:增益Gain | 晚花行乐 天线参数:方向图Radiation pattern | 晚花行乐 本拉登别墅的Google Earth坐标 | 晚花行乐 宜家帕克斯(PAX)衣柜的拼装过程 | 晚花行乐
如何为 Hugo 静态网站添加评论功能 | 晚花行乐
2023-12-13 · via 晚花行乐

我的网站使用 Hugo 编译静态页面。在此基础上,支持了搜索和评论等动态内容。我打算分两篇文章介绍具体的实现方法,分享给读者。这是第二篇。

本文先介绍评论功能的实现方法,还有 另一篇文章 介绍如何添加搜索。

效果预览

评论效果如下:

先决条件

关于搭建静态网站的做法,我在 这篇文章 中有简单介绍。要在静态网站添加动态内容,有如下一些条件:

  • 自己掌握的云服务器
  • 反向代理(比如nginx)
  • 数据库(比如 mysql 或 redis )

具备以上的条件后,我们接下来可以了解下流程。相比搜索功能,增加评论的步骤会简单一些。

基本原理

基本的流程是:

  1. 设计一个用于搜索的路由名称,不能跟 hugo 项目已有的静态路由冲突,比如我用的 /comment/
  2. 在 Hugo 项目的每篇文章页面模板中增加评论的列表和表单
  3. 在 Hugo 项目中,在每篇文章页面中加在 JavaScript 文件,用于向后台请求评论内容。
  4. 建立后台搜索服务,从数据库中提取评论内容。
  5. 在 nginx 配置中,将来自 /comment/ 的请求,分发给后台搜索服务

简单的部署图如下:

下面分步骤介绍实现方法:

制定路由

路由的名称不能和 Hugo 项目的 content 目录下的内容重复,会被 Hugo 和 nginx 同时使用。我这里使用/comment/.

创建评论列表和表单

评论列表和表单的 HTML 代码例子如下:

<h2>精彩评论</h2>
<div class="border-0 divide-y mb-6" id="comment-container"></div>
<div class="border-0 w-full pt-4" id="comment">
    <textarea class="text-sm my-1 border-1 divide-y leading-6 border border-gray-400 w-full p-2" id="comment-content" name="comment-content" placeholder="必填" minlength="1" maxlength="1000" rows="5"></textarea>
    <div class="flex w-full flex-row flex-nowrap justify-between items-end md:justify-start md:items-center ">
        <div class="my-2 ">
            <label class="mr-1" for="comment-nick">称呼</label>
            <input class="h-5 w-40 md:w-auto border border-gray-300 px-1" id="comment-nick" placeholder="必填" name="comment-nick" autocomplete="on" maxlength="25">
        </div>
        <div class="mx-4" id="comment-prompt">
            <button id="comment-submit" style='color:rgba(200,200,200,1)' disabled class="h-5 border border-gray-400 justify-self-stretch rounded border w-24 px-2 my-2 hover:text-eureka">提交</button>
        </div>
    </div>
</div>

注意上面的代码中,id 为 comment-containerdiv 是评论列表,将来由 Javascript 请求回来并填充;id 为 commentdiv 则是提交评论的列表。

为了维护方便,上面这段代码不必直接放入文章页面的模板内,而是单独作为一个 partial 保存,例如保存在 layouts/partials/components/comment.html 文件里,再由单个文章页面的模板引用。

单个文章页面的模板文件在 Hugo 项目的 layouts/_default/single.html。编辑这个文件,在相关位置引用上面的 comment.html

{{ with .Page.Params.comment }}
  {{ partial "components/comment" . }}
{{ end }}

.Page.Params.comment 参数是在 Frontmatter 里增加的一个开关,用于控制当前这篇文章是否启用评论功能。

此处省略页面元素的样式属性,因为这取决于各个网站的视觉风格。

这里没有使用 <form> 表单语法,而使用 Vanilla Javascript 提交评论内容

页面发起请求

新建一个 JavaScript 文件,不妨命名为 comment.js ,放在 static 目录下。因为评论列表位置比较靠后,我希望在页面元素全部加载完后再执行这个 js 文件,所以放在 </body> 之前,具体位置是 layouts/_default/baseof.html 文件,增加如下语句:

{{- if .IsPage }}
    {{- $assets := .Site.Data.assets }}
    <script defer src="{{ $assets.base64.js.url }}"></script>
    <script defer src="{{ $assets.comment.js.url }}"></script>
{{- end }}

$assets 变量是定义在 data/assets.yaml 中,用于单独保存 js 文件名,效果如下:

base64:
  js: 
    url: /js/lib/base64.js

comment:
  js:
    url: /js/lib/comment.js

base64.js 文件用于压缩加密 URL 使用,让 URL 更简洁。

comment.js 文件的关键内容有:


var btn = document.getElementById('comment-submit');

// 请求评论内容
function main(){
    // 将提交按钮绑定为发送评论
    if (btn !== null){
        btn.addEventListener('click', function(){
            var data = {
                path: window.location.pathname.trim(),
                nick: nick.value.trim() || '',
                content: content_area.value.trim(),
            }
        
            var r = new XMLHttpRequest();
        
            r.addEventListener('load', function () {
                var result = JSON.parse(r.responseText)
                var prompt = document.getElementById('comment-prompt');
                if(result.status === 'ok' && prompt !== null){
                    prompt.innerHTML = '感谢您的评论,将在审核后公布'
                }
            });
            r.open("POST", "/comment/", true);
            r.setRequestHeader('content-type', 'application/json')
            r.send(JSON.stringify(data));
            btn.setAttribute('disabled' ,'')
            hide(btn);
        })
    }

    // 加载评论列表
    if(window.location.pathname.length > 0){
        var r = new XMLHttpRequest();
    
        r.addEventListener('load', function () {
            var result = JSON.parse(r.responseText)
            if(result.status === 'ok'){
                populate(result)
            }
        });
        r.open("GET", "/comment/"+BASE64.urlsafe_encode(window.location.pathname), true);
        r.send(null);
    } 
}

function populate(result){
    var comment_container = document.getElementById('comment-container');
    var divider = times('-', 100);
    if (comment_container !== null){
        // 显示评论列表
    }else{
        comment_container.innerText = '暂无评论,欢迎您在下方留言'
    }
}

window.onload = main

接下来开始处理后台数据,包括评论的保存和读写处理。

设计数据库

要实现高效的搜索,需要使用数据库。这里以 mysql 数据库为例。

安装好 mysql 后,需要首先创建库和表。为了保存搜索结果的来源信息,数据表需要存储文章的所属 section、标题 等信息。

所以表格设计如下:

+----+---------+---------------------------------+--------------------------+--------------+---------------------+--------+
| id | section | title                           | content                  | nick         | time                | state  |
+----+---------+---------------------------------+--------------------------+--------------+---------------------+--------+
|  2 | tech    | pull-docker-images-behind-proxy | 找了好久,终于找到想要的     | tony         | 2023-01-09 15:55:27 | show   |
+----+---------+---------------------------------+--------------------------+--------------+---------------------+--------+

section 和 tech 字段用于拼接文章的链接,title 字段用于显示,content 是文章的内容,用于搜索。

建立后台评论读写服务

简单的方案使用 Flask 框架建立服务,这个话题可以另开一篇,这里只列出视图函数最关键的部分:


# comment 是个蓝图,公共前缀是 /comment

# 读取评论条目
@comment.route('/<string:code>', methods=['GET'])
def index(code):

    # 省略变量准备和异常处理的部分
    c = Comment.query.filter_by(section=section, title=title).all()
    comments = [
        {
        'id':x.id, 
        'content':x.content, 
        'nick':x.nick,
        'time':datetime.datetime.strftime(x.time, '%Y-%m-%d %H:%M')
        } for x in c if x.state == 'show']
    resp = {'output': comments, 'status':'ok'}
    status_code = 200
    return jsonify(resp), status_code

# 写入评论条目

@comment.route('/', methods=['POST'])
def add_comment():
    # 省略变量准备和异常处理的部分
    c = Comment(
            section=section,
            title=title,
            nick=nick, 
            content=content, 
            time=datetime.datetime.now()
        )
    db.session.add(c)
    db.session.commit()
    resp = {'output': repr(c), 'status':'ok'}

    return jsonify(resp), status_code

另外再配合安装 gunicorn 和 supervisord 就可以开启 web 服务,这里假设 web 服务开在 8888 端口。

配置 nginx

需要配置 nginx ,将 /s/ 的请求转发到后台 web 服务上,而其余的请求仍然解释为静态文件:


location ~ /search/ {
        proxy_pass http://127.0.0.1:8888;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

这样就实现了 Hugo 静态内容配合使用的动态评论功能。

各位读后有什么想法,请在下方留言吧!如果对本文有疑问或者寻求合作,欢迎 联系邮箱邮箱已到剪贴板

精彩评论