创建基于 Web 的 BBS,第 2 部分

作者:Reuven M. Lerner

上个月,我演示了如何使用 Perl 和关系数据库在 Web 上构建一个小型公告板系统 (BBS)。 这样的公告板是让人们在网站上聚集在一起的另一个有用工具。 本月,我将向您展示如何编写几个不同的程序,包括创建和列出现有消息的程序。

在继续之前,让我们回顾一下包含我们 BBS 中信息的两个表。 我使用 SQL 定义了这些表,这意味着虽然我使用 MySQL 编写了数据库,但大多数定义也应该适用于其他关系数据库。 创建这两个表的代码如清单 1 所示。

清单 1. 表创建代码

这两个表将支持对主题(即消息组)和消息执行许多技巧。 每条消息都属于一个主题。 每条消息(和主题)都包含在单个数据库行中,包括作者姓名、电子邮件地址、主题标题和消息文本。

有关关系数据库或 MySQL 的更多信息的指针可以在“资源”中找到,其中还包括有关 Perl 供应商无关数据库接口 (DBI) 的信息。

使用 Cookie 创建主题

上个月,我编写了一个程序 list-threads.pl 来列出当前定义的主题(讨论主题),以及一个程序 add-thread.pl 来创建一个新主题。 但是,我没有为 add-thread.pl 提供 HTML 表单,因为该表单必须在 CGI 程序中生成。 执行此功能的程序是归档文件清单 2 中的 add-thread-form.pl。 由于篇幅限制,清单 2 到 5 未在此处打印,但可以通过匿名下载文件 ftp.linuxjournal.com/pub/lj/listings/issue58/3252.tgz 获取。

为什么要使用 CGI 程序而不是静态表单? 老实说,唯一的原因是为了提供我特别喜欢的一点功能。 我在网上订购了很多商品,经常多次返回同一家供应商。 我不喜欢每次在 Web 上填写 HTML 表单时都必须输入我的姓名和电子邮件地址。 我决定让使用我们公告板系统的人们的生活更轻松一些,方法是自动填写“姓名”和“电子邮件”字段,其中包含用户在上一次交易中发布的信息。

如果我使用像 Embperl 或 ePerl 这样的模板系统,我可以使用更接近标准 HTML 的文件,而无需将 HTML 埋藏在 CGI 程序中。 由于多种原因,包括我的网络空间提供商在撰写本文时尚未提供 mod_perl,我决定使用 CGI 程序而不是模板。

无论使用 CGI 程序还是模板,从之前的表单提交中插入值都需要跨 HTTP 事务跟踪状态。 HTTP 是一种无状态协议; 也就是说,每次连接的发生都没有来自之前的任何记忆。 那么,如何从之前的表单提交中检索数据呢?

答案是 HTTP Cookie,这是一种巧妙的技巧,已成为 Web 上商业和交易的基石。 Cookie 是一个名称-值对,有点像变量或哈希表中的条目。 但是,Cookie 由用户的浏览器存储,这意味着它可以在多个事务中使用。

站点可以将 Cookie 设置为 HTTP 响应的一部分,并带有“Set-cookie”标头。 每当用户访问之前设置过 Cookie 的站点时,它都会在其 HTTP 请求中包含“Cookie”标头。 因此,Cookie 的值可用于自动填写 HTML 表单中“name”和“email”字段的“value”属性。 当用户提交表单以创建新主题时,程序会发送标头,在用户的浏览器上设置“name”和“email”Cookie。 下次用户访问该站点时,这些值将作为 HTTP 标头的一部分发送,并且可以在我们的程序中检索和使用。

Perl 的 CGI.pm 模块允许我们使用“cookie”方法轻松地处理 Cookie。 以下代码放在表单创建程序中

my $email = $query->cookie(-name => "email") ||
     "";
print "<TR>\n";
print "<TD>Your e-mail address</TD>\n";
print "<TD><input type=\"text\"
size=\"50\" ";
print "value=\"$email\"
name=\"email\"></TD>\n";
print "</TR>\n\n";

它将 $email 分配为“email”Cookie 的值或空字符串。 空字符串可以忽略,因为 Perl 会在第一次检索变量值时自动将空字符串分配给变量。 但是,这会产生警告消息,因为程序将使用未定义变量的值。 尽可能分配空字符串是最安全的。

文本字段的值设置为 Cookie 的当前值或空字符串; 也就是说,用户的电子邮件地址或什么都没有。 用户名也使用了类似的方法,这样她就不必多次输入她的名字。

表单提交到我们上个月检查过的 add-thread.pl。 该程序使用提交的 HTML 表单的元素来创建 SQL 查询,该查询将相应的行插入到 ATFThreads 表中。 由于我们已使用 AUTO_INCREMENT 属性定义了 ATFThreads 的 ID 列,因此我们可以确保每个主题都将具有其自己的自动生成的 ID 号,我们可以在程序中引用该 ID 号。

当表单提交时,我们的 CGI 程序会创建两个新的 Cookie,一个名为“email”,另一个名为“name”。 然后,我们可以使用 CGI.pm 的“cookie”方法检索这些值,如上所示。 创建 Cookie 几乎与检索 Cookie 一样容易

my $namecookie = $query->cookie(-name => "name",
  -value => $query->param("name"),
  -expires => "+1y");
my $emailcookie = $query->cookie(-name => "email",
  -value => $query->param("email"),
  -expires => "+1y");

一旦我们创建了 $namecookie$emailcookie,我们就可以将它们发送到用户的浏览器,从而通过将它们合并到 HTTP 标头中来设置 Cookie 值

print $query->header(-type => "text/html",
 -cookie => [$namecookie, $emailcookie]);

由于两个 Cookie 都设置为在创建后一年 (+1y) 过期,因此用户的浏览器应在将来访问该站点时继续发送姓名和电子邮件地址。

处理消息

现在我们已经了解了底层数据库系统如何处理主题,我们需要开始处理实际消息。 因为这是一个简单的系统,我们将仅查看向主题发布新消息以及查看主题的内容。

在许多方面,向主题发布新消息与创建新主题类似。 在这两种情况下,都需要用户的姓名和电子邮件地址。 在这两种情况下,都会记录主题的创建日期和时间,并且用户可以输入标题和消息正文。

消息和主题之间的主要区别在于,每条消息都必须与一个主题关联。 此关联用于创建消息单独存储的错觉,而实际上它们都存储在同一个表 (ATFMessages) 中。 但是,用户一次只能看到一个“按主题划分的切片”。

正如我使用程序来创建主题添加表单一样,我将使用 CGI 程序来创建消息发布表单,称为 post-comment-form.pl(归档文件中的清单 3)。 此表单将将其内容提交给 post-comment.pl(归档文件中的清单 4)。

我将通过在表单中放置一个选择列表来确保每条消息都与一个主题关联。 选择列表中的每个选项都将在内部使用相关主题的 ID 代码进行标识,并将显示主题行。

为了使此列表反映数据库的当前状态,将执行数据库查询,并将结果显示在表单中。 查询由以下代码设置

my $sql =
"SELECT id,subject FROM ATFThreads ORDER BY subject";

然后执行,遍历每个 id,subject 对。 每个对都插入到 <option> 标签中,我们可以看到

while (my @row = $sth->fetchrow)
{
print "<option value=\"$row[0]\" ";
print " selected " if ($thread_id == $row[0]);
print ">$row[1]\n";
}

标准的 DBI $sth->fetchrow 方法用于从 SELECT 查询返回下一行。 当没有更多行要检索时,$sth->fetchrow 返回 false,这结束了 while 循环。

另请注意如何通过将其 $thread_id$row[0] 进行比较来选择特定主题的主题。 $thread_id 设置为查询字符串的值,查询字符串可以粗略地定义为“URL 中问号后面的任何内容”。 行

my $thread_id = $query->param("keywords") || 0;

使 CGI.pm 自动将参数 keywords 分配给查询字符串的值。 如果用户使用 http://www.lerner.co.il/cgi-bin/post-comment-form.pl?5 调用程序,则 $thread_id 将被赋值为 5。 如果未分配查询字符串,则该值保留为 0,在这种情况下,不选择默认主题。

发布消息

当 HTML 表单提交到 post-message.pl(归档文件中的清单 4)时,表单元素用于将新行插入到 ATFMessages 中。 正如我在上面指出的,post-message.pl 与 add-thread.pl 没有太大区别,除了它存储了主题 ID 号以及所有其他信息

my $sql = "INSERT INTO ATFMessages ";
$sql .= "(thread,date,author,email,subject,text)";
$sql .= "VALUES ";
$sql .= "($thread_id,NOW(),$name,$email,$subject,$text)";

变量值可以在不加引号的情况下插入,因为使用了标准的 $dbh->quote 方法。 我最近才发现这种方法,并且仍然对没有它我曾经是如何生存下来的感到惊讶。 同时,表单元素被检索并在以下代码行中适当地引用

my $name = $dbh->quote($query->param("name"));
my $email = $dbh->quote($query->param("email"));
my $thread_id = $dbh->quote($query->param
("thread"));
my $subject = $dbh->quote($query->param
("subject"));
my $text = $dbh->quote($query->param("text"));

完成此操作后,上面的 SQL 查询将 INSERT 一行新行。 我们告诉用户已添加新消息,并生成一个带有多个选项的菜单栏。

信不信由你,这两个简短的程序就是将消息插入数据库,从而插入到我们的 BBS 中所需的全部内容。

查看主题

至此,功能已接近完成。 剩下的唯一要做的就是创建 view-thread.pl(归档文件中的清单 5),它允许我们查看主题的当前内容。

为了使此程序正常工作,必须在查询字符串中传递单个参数以标识主题。 要检索此值,请使用 CGI.pm 创建的 keywords HTML 表单元素

my $thread_id = $query->param("keywords");

一旦分配了 $thread_id,我就可以从表中检索有关该主题的相应信息。 实际上,执行了两个单独的查询:一个来自 ATFThreads,第二个来自 ATFMessages。 (我可以将查询合并为一个大的 SELECT 语句,但我选择将它们分开。)

早期,我决定将用户的发布日期和时间以及发布的文本一起打印出来。 鉴于 DATETIME 数据类型,我们如何以智能方式检索日期和时间? MySQL 提供了一个 DATE_FORMAT 函数,该函数从列中获取值并使用指定的格式写入内容。

为了让生活更轻松,我实际上检索了相同的“date”列两次,一次用于日期,另一次用于时间。 这允许在日期和时间之间插入文字字符,而无需担心可能的误解

$sql = "SELECT id, DATE_FORMAT(date,
   \"%W, %d %b %Y\"), ";
$sql .= "DATE_FORMAT(date, \"%h:%i %p\"), ";
$sql .= "author, email, subject, text FROM ATFMessages ";
$sql .= "WHERE thread = $thread_id ORDER BY date desc";

DATE_FORMAT 接受两个参数:要检索的列的名称和一组代码(类似于 C 的 printf 语句),指示要使用的值。

一旦执行此查询,代码将遍历结果,并按消息的到来顺序(从最新到最旧)打印消息。 它们将按该顺序到达,因为 SELECT 语句中有 ORDER BY 子句。 让数据库为我们完成脏活意味着我们只需使用以下简短循环即可打印主题中的所有消息

while (my @row = $sth->fetchrow)
{
my ($id, $date, $time, $author, $email, $subject,
   $text) = @row;
print "<a name=\"$id\"><B>$subject</B>, ";
print "by <a href=\"mailto:$email\">$author</a> ";
print "on $date at $time</P*gt;\n";
print "<blockquote>$text</blockquote>\n\n";
}
总结

基本的 BBS 软件现已完成。 它可以创建主题、向主题添加消息以及查看主题内的消息。 如果没有别的,这个项目展示了一组数据库表在与一些 CGI 程序配对时可以多么强大。

也许这个系统太基本了; 它缺少任何好的公告板(或任何好的 Web 应用程序)都应处理的一些功能。 例如,如果能够搜索已发布消息中的文本字符串或正则表达式,以查找与特定主题相关的消息,那将是很不错的。

提供一些公众无法访问的管理功能来处理相对高级的事情也很有用。 例如,我们可能希望保留删除冒犯性或与主题无关的消息的能力。 我们甚至可能希望将此能力授予某些其他用户,赋予他们“副版主”身份。

下个月,我们将看到如何通过仅添加一两个新的 CGI 程序来提供这些功能。 同时,您可以在 http://www.lerner.co.il/atf/ 上查看这些程序的实际运行情况,那里的“At the Forge”BBS 已经在使用中。

资源

Creating a Web-based BBS, Part 2
Reuven M. Lerner 是一位居住在以色列海法的互联网和 Web 顾问,自 1993 年初以来一直使用 Web。 在业余时间,他做饭、阅读并为社区的教育项目做志愿者。 您可以通过 reuven@netvision.net.il 与他联系。
加载 Disqus 评论