10.2 设计“网络留言板”实例
很多网站都拥有自己的留言板,用于实现访问者与管理者之间的交流和沟通。简单的留言板可以通过文件存储的方式实现,而复杂的留言板则需要有后台数据库的支持。本节介绍一个由PHP + MySQL开发的网络留言板,本实例保存在下载源代码的“10\book”目录下。
10.2.1 系统功能分析及数据库设计
网络留言板需要有一个系统管理员用户,负责维护和管理留言板的内容,回复访问者提出的问题。本实例假定系统管理员用户为Admin,默认密码为pass。
访问者无须登录就可以通过本系统留言,而且可以查看所有公开的留言内容(只给管理员查看的信息除外)。
Admin用户可以对留言信息进行管理,包括删帖、发布公告等。公告信息始终显示在留言板的最上方,即通常所说的置顶信息。
本实例使用的数据库为book。数据库中包含以下两个表。
1.表Content
表Content用来保存留言的标题和内容,表结构如表10-3所示。
表10-3 表Content的结构
本实例支持用户选择头像,所有头像图片保存在应用程序目录的 images目录下,Logo 字段中只保存文件名,不必包含路径信息。
2.表Users
表 Users 用来保存系统用户信息,本实例中只有一个用户,即系统管理员 Admin。表 Users的结构如表10-4所示。
表10-4 表Users的结构
创建数据库的脚本如下:
CREATE DATABASE IF NOT EXISTS book
COLLATE 'gb2312_chinese_ci';
USE book;
CREATE TABLE IF NOT EXISTS Users (
UserName VARCHAR(50) PRIMARY KEY,
UserPwd VARCHAR(50),
ShowName VARCHAR(50));
CREATE TABLE IF NOT EXISTS Content (
ContId INT AUTO_INCREMENT PRIMARY KEY,
Subject VARCHAR(200) NOT NULL,
Words VARCHAR(1000) NOT NULL,
UserName VARCHAR(50) NOT NULL,
Face VARCHAR(50),
Email VARCHAR(50),
Homepage VARCHAR(50),
CreateTime DATETIME,
UpperId INT
);
INSERT INTO Users VALUES('admin', 'pass', 'admin');
此段脚本保存为下载源代码的10\book\book.sql。默认的管理员用户为admin,密码为pass。
10.2.2 定义数据库访问类
为了体现出面向对象的程序设计思路,本书实例中将每个表的数据库操作都封装到类中,类与表同名。
本实例中包含两个表,即表Users和表Content。因此创建Users和Content两个类,它们保存在下载源代码的“10\book\Class”目录下。
类Users的成员函数如表10-5所示。
表10-5 类Users的成员函数
类Content的成员函数如表10-6所示。
表10-6 类Content的成员函数
本章稍后将结合具体的使用来介绍这些代码。
10.2.3 设计留言板的主页
留言板的主页为 index.PHP。普通用户和管理员的权限不同,普通用户不需要登录,即可以查看所有留言,或使用本系统留言。留言板主页的界面如图10-6所示。
图10-6 留言板首页
主页最基本的功能显示所有的留言信息,首先需要准备要显示的留言记录集,主要代码如下:
<?PHP
$UserName = $_POST['UserName'];
if($UserName != "")
include('ChkPwd.php'); //检查用户名和密码
include('Show.php'); //引用显示留言内容的函数
include('Class\Content.php'); //引用Content类的定义
$objContent = new Content(); //定义Content对象,用于访问表Content
$pageSize = 5; //设置每页显示留言记录的数量
$pageNo = (int)$_GET['Page']; //获取当前显示的页码
$recordCount = $objContent->GetRecordCount();
//处理不合法的页码
if($pageNo < 1)
$pageNo = 1;
//计算总页数
if( $recordCount ){
//如果记录总数量小于每页显示的记录数量,则只有一页
if( $recordCount < $pageSize ){
$pageCount = 1;
}
//取记录总数量不能整除每页显示记录的数量,则页数等于总记录数量除以每页显示记录数量的结果取整再加1
if( $recordCount % $pageSize ){
$pageCount = (int)($recordCount / $pageSize) + 1;
}
else { //如果没有余数,则页数等于总记录数量除以每页显示记录的数量
$pageCount = $recordCount / $pageSize;
}
}
else{ //如果结果集中没有记录,则页数为0
$pageCount = 0;
}
if($pageNo > $pageCount)
$pageNo = $pageCount;
?>
程序的主要执行过程如下。
(1)设置每页显示留言记录的数量$pageSize,默认数值为5。
(2)从参数Page中获取当前的页码,保存在变量$pageNo中。如果没有参数,则页码为1。
(3)定义Content 对象objContent,调用objContent-> GetRecordCount()获取所有留言记录的总数,保存在变量$pageCount中。
(4)计算总页数$pageCount,计算方法已经在10.1.3小节中介绍,请读者参照理解。
为了让网页中的文字格式统一,这里定义了样式main,代码如下:
<!-
.main { font-size: 10pt }
-->
样式main的字体大小为10pt。在文字上套用样式的代码如下:
class = "main"
在index.php中,显示留言内容和页码控制链接的代码如下:
<tr><td height="161" class="main"><?PHP ShowList($pageNo, $pageSize); ?></td></tr>
<tr> <td height="15"> </td></tr>
<tr>
<td height="13" class="main" <?PHPbackground="images/b3.gif">ShowPage($pageCount, $pageNo); ?></td>
</tr>
在上面的程序中,<tr>…</tr>和<td>…</td>用于构造一个表格,并在表格中调用 ShowList()函数显示留言内容,调用ShowPage()函数显示页码控制链接。关于如何显示留言内容的代码将在10.2.4小节介绍。
在主页中,管理员可以输入用户名和密码进行登录,相关表单的定义代码如下:
<?PHP
if(!$_SESSION['Passed']) {
?>
<form method="POST" action="<?PHP $_SERVER['PHP_SELF'] ?>" name="myform">
<font size="2"> 用户名 : </font><input type="text" name="UserName"size="12">
密 码: <input type="password" name="UserPwd" size="12"> <input type="submit"value="登录" name="B1">
<?PHP
}
else {
echo("<b>欢迎管理员光临!</b>");
}
?>
表单的处理脚本为$_SERVER['PHP_SELF'],即当前脚本自身index.php。当$_SESSION['Passed']等于False时才输出上面的表单,否则输出“欢迎管理员光临!”。
在index.php中,程序首先获取表单域UserName的值,如果UserName有值,则使用include()函数包含ChkPwd.php进行身份认证。ChkPwd.php的代码如下:
<?PHP
include('class\Users.php'); // 包含Users类
$user = new Users();
session_start();
//如果尚未定义Passed对象,则将其定义为False,表示没有通过身份认证
if(!isset($_SESSION['Passed'])) {
$_SESSION['Passed'] = False;
}
//如果$_SESSION['Passed']=False,则表示没有通过身份验证
if($_SESSION['Passed']==False) {
//读取从表单传递过来的身份数据
$UserName = $_POST['UserName'];
$UserPwd = $_POST['UserPwd'];
if($UserName == "")
$Errmsg = "请输入用户名和密码";
else {
//验证用户名和密码
if(!$user->verify($UserName, $UserPwd)) {
?>
<script language="javascript">
alert("用户名或密码不正确!");
</script>";
<?PHP
}
else { //登录成功 ?>
<script language="javascript">
alert("登录成功!");
</script>
<?PHP
$_SESSION['Passed'] = True;
$_SESSION['UserName'] = $UserName;
$_SESSION['ShowName'] = $user->ShowName;
//$_SESSION['ShowName'] = $row[2];
}
}
}
//经过登录不成功,则画出登录表单MyForm
if(!$_SESSION['Passed']) {
?>
<script language="javascript">
history.go(-1);
</script>
<?PHP } ?>
程序调用$user->verify()函数验证用户名和密码,如果通过验证,则将$_SESSION['Passed'] 设置为True,并将用户名和显示名保存在Session变量中。
10.2.4 显示主题留言
本留言板中的留言可以分为两种类型,一种是主题留言,另一种是回复留言。在首页按主题留言的发表时间显示,而回复留言则在主题留言的内容部分显示。
为了更方便地显示主题留言,本实例在 Show.PHP 中定义了两个函数,即 ShowPage()和ShowList()。
ShowPage()函数的功能是显示页码信息。因为论坛使用分页显示的方法显示主题留言,所以需要在留言列表的上面显示页码及翻页链接,包括下面的功能:
• 通过下拉菜单使用户可以直接跳转到指定页码的页面;
• 通过“第一页”、“上一页”、“下一页”和“最后一页”超级链接,使用户跳转到指定的页面;
• 显示论坛的当前页码和总页数。
ShowPage()的代码如下:
<?PHP
// $recordCount表示返回结果集中的总页数,$pageNo表示当前页码
function ShowPage( $pageCount, $pageNo ) {
echo("<table width=738> <tr> <td align=right class=main>");
//显示第一页,如果当前页就是第一页,则不生成链接
if($pageNo>1)
echo("<A HREF=index.php?Page=1>第一页</A> ");
else
echo("第一页 ");
//显示上一页,如果不存在上一页,则不生成链接
if($pageNo>1)
echo("<A HREF=index.php?Page=" . ($pageNo-1) . ">上一页</A> ");
else
echo("上一页 ");
//显示下一页,如果不存在下一页,则不生成链接
if($pageNo<>$pageCount)
echo("<A HREF=index.php?Page=" . ($pageNo+1) . ">下一页</A> ");
else
echo("下一页 ");
//显示最后一页,如果当前页就是最后一页,则不生成链接
if($pageNo <> $pageCount)
echo("<A HREF=index.php?Page=" . $pageCount . ">最后一页</A> ");
else
echo("最后一页 ");
//输出页码
echo($pageNo . "/" . $pageCount . "</td></tr></table>");
}
ShowPage()有两个参数,$pageCount表示总页数,$pageNo表示当前的页码。在显示“第一页”、“上一页”、“下一页”和“最后一页”超级链接时,需要根据$pageNo 的值决定是否显示超级链接。如果当前页是第一页,则“第一页”和“上一页”不显示超级链接;如果当前页是最后一页,则“下一页”和“最后一页”不显示超级链接。这些超级链接都转向到index.php,并将指定的页码作为参数传递到Page。
ShowList()函数的功能是以表格的形式主题留言,包括下面的功能:
• 显示主题、作者、创建日期和时间、最后回复日期和时间、人气等信息;
• 优先显示“置顶”的帖子;
• 留言按最后回复日期和时间降序排列,这样最后回复的帖子将出现在最上面。
ShowList()的代码如下:
<?PHP
function ShowList( $pageNo, $pageSize ) {
?>
<div align="center">
<center>
<table border="1" width="738" bordercolor="#3399FF" cellspacing="0" cellpadding="0"height="46" bordercolorlight="#FFCCFF" bordercolordark="#CCCCFF">
<?PHP
$existRecord = False;
$objContent = new Content();
$results = $objContent->load_content_byPage($pageNo, $pageSize);
//使用while语句显示所有$results中的留言数据
while($row = $results->fetch_row()) {
$existRecord = True;
?> <tr>
<td width="148" height="16" class="main" align=center> <br>
<img border="0" src="images/<?PHP echo($row[4]); /*输出 Face 字段的内容,即头像文件*/ ?>.gif" width="100" height="100"><br>
<?PHP echo($row[3]); /*输出UserName字段的值*/?><br><br>
<a href="<?PHP echo($row[6]); /*输出Homepage字段的值*/?>" target=_blank>
<img border="0" src="images/homepage.gif" width="16" height="16"></a>
<a href="mailto:<?PHP echo($row[5]); /*输出Email字段的值*/ ?>">
<img border="0" src="images/email.gif" width="16" height="16"></a><br>
<?PHP if($_SESSION["UserName"] <> "") {?>
<a href=newRec.php?UpperId=<?PHP echo($row[0]); /*ContId*/ ?>target=_blank onclick="return newwin(this.href)">回复</a>
<a href=deleteRec.php?ContId=<?PHP echo($row[0]); /*ContId*/ ?>target=_blank onclick="return newwin(this.href)">删除</a>
<?PHP } /* end of if */ ?>
</td>
<td width="584" height="16" class="main" align="left" valign="top">
<br><b>标题:<?PHP echo($row[1]);/* Subject */ ?> 时间:<?PHP echo($row[7]); /* CreateTime */ ?></b><hr><br>
<?PHP
echo($row[2]); /* Words */
//下面用于显示所有回复留言
$content = new Content(); //定义Content对象
$sub_results = $content->load_content_byUpperid($row[0]);
while($subrow = $sub_results->fetch_row()) {
echo("<BR><BR><BR>"); ?>
<img border="0" src="images/<?PHP echo($subrow[4]); /* Face */ ?>.gif"width="50" height="50">
<?PHP echo($subrow[3]); /* UserName") */ ?>
<a href="<?PHP echo($subrow[6]); /*homepage*/ ?>" target=_blank>
<img border="0" src="images/homepage.gif" width="16" height="16"></a>
<a href="mailto:<?PHP echo($subrow[5]); /* email */ ?>">
<img border="0" src="images/email.gif" width="16" height="16"></a>
<b> 标题 :<?PHP echo($subrow[1]); /*Subject */?> 时间: <?PHP echo($subrow[7]); /* CreateTime */ ?></b><hr><br>
<?PHP echo($subrow[2]); /* Words */ ?>
<?PHP
} // end of while
?>
</td>
</tr>
<?PHP
} // end of while
if(!$existRecord) {
?>
<tr>
<td width="148" height="16" align=center class="main">没有留言数据</td>
</tr>
<?PHP
} // end of if
echo("</table></center></div>");
} // end of function
?>
ShowList()函数也有两个参数,$pageSize 表示每页中允许显示的留言记录数量,$pageNo 表示当前的页码。程序定义 Content 类对象 objContent,并调用$objContent->load_content_byPage()函数获取当前页面中包含的留言记录,然后使用 while 语句依次处理并显示每条留言记录。留言记录的显示被分为两个区域,左侧单元格中显示用户头像、姓名、邮箱和主页地址,右侧单元格显示留言主题、留言时间、留言内容和回复留言信息。只有管理员才能回复留言。
objContent->load_content_byPage()函数的代码如下:
function load_content_byPage($pageNo, $pageSize)
{
$sql = "SELECT * FROM Content WHERE UpperId=0 ORDER BY CreateTime DESC LIMIT ". ($pageNo-1) * $pageSize . "," . $pageSize;
$result = $this->conn->query($sql);
Return $result;
}
程序在SELECT语句中使用LIMIT关键字指定查询的范围。关于分页显示的具体实现方法可以参照 10.1.3 小节理解。objContent->load_content_byPage()函数值加载非回复的留言(即UpperId=0),回复留言在显示留言时通过调用$content->load_content_byUpperid()函数加载后显示。
10.2.5 添加新留言
在首页中单击“我要留言”超级链接,可以打开“添加新留言”窗口,如图10-7所示。
“我要留言”超级链接的定义代码如下:
<a target="_blank" href="newRec.php" onclick="return newwin(this.href)">我要留言</a>
在index.PHP中,定义了JavaScript函数newwin(),用于定义新窗口的模式,代码如下:
<script language="JavaScript">
function newwin(url) {
var
newwin=window.open(url,"newwin","toolbar=no,location=no,directories=no,status=no,menub ar=no,scrollbars=yes,resizable=yes,width=400,height=380");
newwin.focus();
return false;
}
</script>
图10-7 “添加新留言”窗口
从“我要留言”超级链接的定义中可以看到,添加留言的脚本是newRec.php。在newRec.php中,使用表单formadd接受用户留言,定义代码如下:
<form method="POST" action="recSave.php?UpperId=<%=UpperId%>" name="formadd"onsubmit = "return ChkFields()">
当用户单击“提交”按钮时,将首先调用 ChkFields()方法进行有效性检查,然后执行recSave.php?UpperId=<%=UpperId%>存储信息。UpperId 表明当前留言是否是回复留言,如果UpperId=0,则表示当前留言为新留言,否则UpperId为回复留言的编号。
在“添加新留言”窗口中,用户可以使用下拉列表框选择自己的头像,代码如下:
<select size="1" name="logo" onChange="showlogo()">
<option selected value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
<option value="8">8</option>
<option value="9">9</option>
<option value="10">10</option>
<option value="11">11</option>
<option value="12">12</option>
<option value="13">13</option>
<option value="14">14</option>
<option value="15">15</option>
</select> <img src="images/1.gif" name="img">
下拉列表框的名称为logo,当用户选择下拉列表框的内容时,触发JavaScript函数showlogo(),改变用户头像。代码如下:
<script language="javascript">
function showlogo(){
document.images.img.src = "images/" + document.formadd.logo.options[document. formadd.logo.selectedIndex].value + ".gif";
}
</script>
document是JavaScript对象,表示当前的页面。Document.images.img表示当前页面中名为img的图片组件,src表示图片的源地址。
document.myform.logo表示当前页面中表单myform的logo组件(下拉框),options表示logo的选项,selectedIndex表示logo的当前被选索引,value表示下拉框的值。
onChange事件也可以应用于其他组件,如文本框。当用户输入数据时,可以通过程序控制执行相应的操作,如执行某种运算并显示结果,这样可以使网页的功能更强大。
recSave.php的主要代码如下:
<html>
<head>
<title>保存留言信息</title>
</head>
<body>
<?PHP
date_default_timezone_set('Asia/Chongqing'); //系统时间差8小时问题
include('Class\Content.php');
$objContent = new Content();
//从参数或表单中接收数据到变量中
$objContent->UserName = $_POST["name"];
$objContent->Subject = $_POST["Subject"];
$objContent->Words = $_POST["Words"];
$objContent->Email = $_POST["email"];
$objContent->Homepage = $_POST["homepage"];
$objContent->Face = $_POST["logo"];
$objContent->UpperId = $_POST["UpperId"];
if($objContent->UpperId == "")
$objContent->UpperId = 0;
//获取当前当前时间
$now = getdate();
$objContent->CreateTime = $now['year'] . "-" . $now['mon'] . "-" . $now['mday']
. " " . $now['hours'] . ":" . $now['minutes'] . ":" . $now['seconds'];
$objContent->insert();
echo("<h2>信息已成功保存!</h2>");
?>
</body>
<Script language="javascript">
//打开此脚本的网页将被刷新
opener.location.reload();
//停留800毫秒后关闭窗口
setTimeout("window.close()",2800);
</Script>
</html>
程序的运行步骤如下。
(1)定义Content类对象,用于操作表Content。
(2)读取表单域到Content对象对应的成员变量中。
(3)调用$objContent->insert()函数,保存留言记录。
(4)执行JavaScript脚本,刷新打开此窗口的网页(即index.php),然后关闭此窗口。具体说明如下:
• opener是JavaScript对象,表示打开当前页面的页面;location.reload()函数用于刷新页面;
• setTimeout()是JavaScript函数,语法如下:
setTimeout (表达式,延时时间)
• setTimeout()函数的功能是等候延时时间后,执行表达式;
• window.close()函数指定关闭当前窗口。
10.2.6 回复和删除留言
如果当前用户是管理员,则在主页中显示回复和删除超级链接,如图10-8所示。
回复和删除超级链接在Show.php中定义,其中“回复”超级链接的定义代码如下:
<a href=newRec.php?UpperId=<?PHP echo($row[0]); /*ContId*/ ?> target=_blank onclick="return newwin(this.href)">回复</a>
填写回复留言的脚本也是newRec.php,UpperId为当前留言的记录编号。回复留言和添加留言的方法完全相同,读者可以参照10.2.5小节理解。
“删除”超级链接的定义代码如下:
<a href=deleteRec.php?ContId=<?PHP echo($row[0]); /*ContId*/ ?> target=_blank onclick="return newwin(this.href)">删除</a>
图10-8 管理员拥有回复和删除留言的权限
删除留言的脚本为deleteRec.php,主要代码如下:
<?PHP
include('ChkPwd.php');
include('Class\Content.php');
$ContId = $_GET["ContId"]; // 获取要删除的留言记录编号
$objContent = new Content();
$objContent->delete($ContId); // 删除留言记录
echo("已成功删除留言。");
?>
<Script Language="JavaScript">
//打开此脚本的网页将被刷新
opener.location.reload();
//停留800ms后关闭窗口
setTimeout("window.close()",800);
</Script>
这段程序的功能比较简单,即使用参数ContId调用$objContent->delete()函数删除指定的留言记录和回复记录。$objContent->delete()函数的代码如下:
//删除指定的留言记录
function delete($Id)
{
$sql = "DELETE FROM Content WHERE ContId=" . $Id . " OR UpperId=" . $Id;
$this->conn->query($sql);
}