如果你有一个静态 HTML 网站,但你想包含评论,这里有一个有趣的方法来使用 PostgreSQL 的 NOTIFY 和 LISTEN。
最大的想法是将评论写为静态 HTML,仅当评论发生变化时,而不是每次都进行数据库查询来显示它们。如果您遇到流量激增,这可以防止“死亡拥抱”。
我已经这样做了六年多了,效果很好。这是使用 Ruby 作为粘合剂的秘诀,尽管您可以使用任何脚本语言。
- 用于注释的 PostgreSQL 数据库表
- Ruby 接收表单帖子,插入数据库
- 当评论改变时,PostgreSQL 触发器发送 NOTIFY
- Ruby 运行 PostgreSQL LISTEN,将更新的评论导出到 HTML
- 静态页面上的 JavaScript 包括 HTML
用于注释的 PostgreSQL 数据库表
create table comments ( id integer primary key generated by default as identity, uri text, created_at date default current_date, name text, email text, comment text ); create index on comments(uri);
Ruby 接收表单帖子,插入数据库
将其放在您想要评论的任何 HTML 页面上:
<section id="comments"></section> <script src="/comments.js"></script>
将下一个代码放入您的 Nginx 配置中,以将 /comments 发送到 localhost。
location = /comments { proxy_pass http://127.0.0.1:4567; }
Ruby Sinatra 接收表单帖子。
require 'pg' require 'sinatra' DB = PG::Connection.new(dbname: 'test', user: 'tester') post '/comments' do DB.exec_params("insert into comments (uri, name, email, comment) values ($1, $2, $3, $4)", [params[:uri], params[:name], params[:email], params[:comment]]) redirect to(request.env['HTTP_REFERER']) end
在服务器上的终端中运行它,它应该默认侦听端口 4567。
当评论改变时,PostgreSQL 触发器发送 NOTIFY
create function comments_changed() returns trigger as $$ begin perform pg_notify('comments_changed', new.uri); return new; end; $$ language plpgsql; create trigger comments_changed after insert or update on comments for each row execute procedure comments_changed();
将该函数加载到具有您的评论表的 PostgreSQL 数据库中。
它向侦听器(如下)发送此 URI的注释已更改的通知。然后侦听器将重新输出仅针对此 URI 的注释,而不是全部。
Ruby 运行 PostgreSQL LISTEN,将更新的评论导出到 HTML
在您的 Web 根目录中创建一个名为 /commentcache/ 的目录,以保存静态评论。
然后让这个 Ruby 脚本在终端中运行以监听数据库更改,并将更新后的注释以 HTML 格式写入磁盘。
require 'pg' DB = PG::Connection.new(dbname: 'test', user: 'tester') BASEDIR = '/var/www/htdocs/commentcache/' # directory in your web root # a single comment list entry, used in ol map, below def li(row) '<li><cite>%s (%s)</cite><p>%s</p></li>' % [row['name'], row['created_at'], row['comment']] end # top-level map of database rows into HTML list def ol(rows) rows.inject('') {|html, row| html += li(row) ; html} end # write comments to disk for this URI def save_comments(uri) rows = DB.exec_params("select name, created_at, comment from comments where uri = $1 order by id", [uri]).to_a File.open(BASEDIR + uri, 'w') do |f| f.puts ol(rows) end end # first write them all DB.exec("select distinct(uri) from comments").each do |r| save_comments(r['uri']) end # listen for changes. re-write when changed DB.exec('listen comments_changed') while true do DB.wait_for_notify do |event, pid, uri| save_comments(uri) end end
静态页面上的 JavaScript 在查看时包含当前 HTML
使用 JavaScript 显示表单以发表评论,并从 /commentcache/ 路径加载评论列表。
function showForm(uri) { document.getElementById('comments').innerHTML = ` <header><h1>Comments:</h1></header> <form method="post" action="/comments"> <input type="hidden" name="uri" value="${uri}"> <label for="name">Your Name</label> <input type="text" name="name" id="name" required> <label for="email">Your Email</label> <input type="email" name="email" id="email" required> <label for="comment">Comment</label> <textarea name="comment" id="comment" cols="80" rows="10" required></textarea> <input type="submit" value="post comment"> </form> <ol id="commentlist"></ol>`; } function getComments(uri) { try { const xhr = new XMLHttpRequest(); xhr.open('get', '/commentcache/' + uri); xhr.send(null); xhr.onload = function() { if (xhr.status === 200) { document.getElementById('commentlist').innerHTML = xhr.responseText; } }; } catch(e) { } } // /blog/topic/page.html uri = 'blog_topic_page.html' for filesystem const uri = location.pathname.substring(1).replace(/\//g, '_'); showForm(uri); getComments(uri);
就这样。我已经从我的实际使用中稍微简化了它,我有一些约束和检查会分散本示例的核心点。
还有其他方法可以做到这一点。 NOTIFY 和 LISTEN 不是必需的。接收发布评论的 Ruby Sinatra 路由可以立即将 HTML 写入磁盘。但我还有其他删除和更新评论的脚本,我喜欢NOTIFY 触发器和 LISTEN 脚本的组合如何始终让它们在磁盘上保持更新。
另一种有趣的方法是将注释直接写入每个 HTML 文件,而不是单独的文件,因此您根本不需要 JavaScript。
可选升级:删除时通知
我为示例简化了 PostgreSQL 触发器,但是通过多几行代码,您也可以使用相同的触发器来通知已删除的评论。删除行的值在“旧”中,而插入和更新的值在“新”中,因此我们必须创建一个 uri 变量和一个 if/then/else 来知道使用哪个。
create or replace function comments_changed() returns trigger as $$ declare uri text; begin if tg_op = 'DELETE' then uri = old.uri; else uri = new.uri; end if; perform pg_notify('comments_changed', uri); return old; end; $$ language plpgsql; create trigger comments_changed after insert or update or delete on comments for each row execute procedure comments_changed();
你可以在我博客中的任何页面看到这段代码,除了这个,因为我怀疑这会充满烦人的测试评论,我删除了。如果您确实在其中一个页面上发表评论,请保持友善,不要让我后悔花了四个小时写这篇文章。