TinyBoard, 一个500行的PHP留言板
项目共有7个文件:install.php, index.php, util.php, cfg.php, login.php, detail.php, manage.php.
install.php实现数据库内容安装,创建配置文件(即cfg.php)。install.php内容如下:
<?php
// TinyBoard 安装程序
?>
<!DOCTYPE html>
<html>
<head> <meta charset="UTF-8"><title>TinyBoard | Install </title></head>
<body>
<?php
if(version_compare('7.0.0', PHP_VERSION, '>='))
die('Current PHP version:' . PHP_VERSION . '. too low, requiring version higher than 7.0.' . "\n");
if(empty($_POST) || !isset($_POST['host']))
{
?>
<form method = "post">
<h3>database</h3>
host <input type="text" name="host" value="localhost"><br />
user <input type="text" name="user" value="root"><br />
password <input type="text" name="password" value="123"><br />
dbname <input type="text" name="dbname" value="board"><br />
<input type="submit" value="install">
</form>
<?php
goto End;
}
// step 1 显示输入内容
echo '<h3>Input</h3><br />';
echo 'dbhost ', $_POST['host'], '<br />';
echo 'dbname ', $_POST['dbname'], '<br />';
echo 'user ', $_POST['user'], '<br />';
echo 'password ', $_POST['password'], '<br />';
echo 'installing ... ', '<br />';
// step 2 安装数据库
$addUser = <<<QUERY
drop table if exists user;
create table user
(
id int not null auto_increment,
name varchar(30) not null,
passwd varchar(30) not null,
phone varchar(30) not null,
primary key(id)
);
insert into user values(1, 'Huyelei', '123', '18262288888');
insert into user values(2, 'root', '123', '133');
insert into user values(3, 'LiLei', '789', '139');
insert into user values(4, 'HanMeimei', 'ABC', '188');
QUERY;
$addMessage = <<<QUERY
drop table if exists message;
create table message
(
id int not null auto_increment,
content varchar(255) not null,
author int not null,
authorName varchar(30) not null,
date DATETIME not null,
primary key(id)
);
create index index_msg_author on message(author);
QUERY;
$host = $_POST['host'] ?? '';
$dbname = $_POST['dbname'] ?? '';
$dbusr = $_POST['user'] ?? '';
$passwd = $_POST['password'] ?? '';
try {
$dsn = sprintf('mysql:host=%s;dbname=%s', $host, $dbname);
$option = [PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC];
$pdo = new PDO($dsn, $dbusr, $passwd, $option);
} catch (PDOException $e) { exit($e->getMessage()); }
$pdo->query($addUser);
$pdo->query($addMessage);
$tables = $pdo->query('show tables')->fetchAll(PDO::FETCH_GROUP);
if(!in_array('user', array_keys($tables)) || !in_array('message', array_keys($tables)))
die('add table fail');
// 循环输入message table内容
$stm = $pdo->prepare('insert into message values(?, ?, ?, ?, ?)');
$stm->bindParam(1, $id);
$stm->bindParam(2, $msg);
$stm->bindParam(3, $author);
$stm->bindParam(4, $authorName);
$stm->bindParam(5, $time);
$names = [1=> 'Huyelei', 2=>'root', 3=>'LiLei', 4=>'HanMeimei'];
for($i=1; $i<=300; $i++) // 300
{
$id = $i;
$msg = sprintf('this is number %s message', $i);
$author = ($i-1)%3 + 1;
$authorName = $names[$author];
$time = sprintf('2018-01-03 15:00::%02d', $i%60);
$stm->execute();
}
$stm->rowCount() > 0 or die('set msg content fail');
// step 3 生成配置文件
$f = fopen('cfg.php', 'w') or die(__FILE__ . ' line ' . __LINE__);
fwrite($f, "<?php \n");
fwrite($f, '// create time: '. date('Y-m-d H:i:s') . "\n\n");
function writeConst($f, $dst, $src)
{
$str = sprintf("define('%s', '%s'); \n", $dst, $src);
fwrite($f, $str);
}
writeConst($f, 'DB_HOST', $host);
writeConst($f, 'DB_USR', $dbusr);
writeConst($f, 'DB_PASSWD', $passwd);
writeConst($f, 'DB_NAME', $dbname);
function writeConst_($f, $dst, $src)
{
$str = sprintf("define('%s', %s); \n", $dst, $src);
fwrite($f, $str);
}
writeConst_($f, 'LINE_PER_PAGE', 10);
writeConst_($f, 'PAGE_PER_CHAPTER', 5);
writeConst_($f, 'TINY_DEBUG', true);
fwrite($f, "\n");
$str = <<<CODE
if(TINY_DEBUG == true)
{
error_reporting(E_ALL);
ini_set('display_errors','On');
}
else
{
error_reporting(E_USER_NOTICE);
ini_set('display_errors','Off');
ini_set('log_errors', 'On');
}
CODE;
fwrite($f, $str);
fwrite($f, "\n\n");
fclose($f);
echo 'installation successfully finished. <br />';
End:
?>
</body></html>
index.php 是入口文件,列表显示留言内容,index.php如下:
<?php
/* TinyBoard, 一个500行的留言板程序
author huyelei@yeah.net 2018.01.05 QQ群619348997
使用方法:
1 所有文件拷贝到Web根目录
2 执行install.php, 实现数据库导入和配置文件cfg.php的创建
3 手工删除install.php, 访问index.php。
Enjoy.
*/
?>
<!DOCTYPE html>
<html><head>
<meta charset="UTF-8">
<title>TinyBoard</title>
<style type="text/css">
.header {
width:820px;
margin: auto;
}
.sheet {
display:table;
border-collapse:separate;
margin:auto;
}
.row { display:table-row; }
.row div { display:table-cell; } /* row类中的 div标签 */
.row .one { width:80px; } /* row类中的第一列 */
.row .two {
width:320px;
max-width:320px;
white-space:nowrap;
overflow:hidden;
text-overflow: ellipsis;
}
.row .three { width:96px; }
.row .four {
width: 200px;
max-width:200px;
white-space:nowrap;
overflow:hidden;
text-overflow: ellipsis;
}
.row .five { width: 120px; }
.leg { /* 页码 */
width:800px;
text-align:right;
margin: auto;
}
</style>
</head><body>
<?php
session_start();
echo '<div class="header">';
if(!isset($_SESSION['username']))
echo '<a href="login.php">登录</a>';
else
echo $_SESSION['username'], ' <a href="login.php?logout=1">退出登录</a> ', ' <a href="manage.php?type=1">发帖</a> ';
echo '</div><br />';
require 'cfg.php';
require 'util.php';
$pdo = getPdo();
$stat = $pdo->query('select count(1) from message');
$r = $stat->fetch();
$r['count(1)'] > 0 or die('die at line:' . __LINE__);
$lineSum = $r['count(1)'];
$pageSum = 1 + (int)($lineSum - 1)/LINE_PER_PAGE;
$page = truncate($_GET['page'], $pageSum - 1); // 当前页, 从0开始
$lineStart = (int) $page * LINE_PER_PAGE; // 开始行,从0开始
$lineEnd = min($lineSum - 1, $lineStart + LINE_PER_PAGE - 1);
$lineNum = $lineEnd - $lineStart + 1;
$str = sprintf('select * from message order by id desc limit %s, %s;', $lineStart, $lineNum);
$stat = $pdo->query($str);
$items = $stat->fetchAll();
$horizonLine =<<<HTML
<div class="row">
<div class="one">----------</div>
<div class="two">----------------------------------------</div>
<div class="three">------------</div>
<div class="four">-------------------------</div>
<div class="five">---------------</div>
</div>
HTML;
?>
<div class="sheet">
<?php echo $horizonLine; ?>
<div class="row">
<div class="one"> ID</div>
<div class="two">内容</div>
<div class="three"> 用户</div>
<div class="four">时间</div>
<div class="five">操作</div>
</div>
<?php echo $horizonLine; ?>
<?php foreach ($items as $item): ?>
<div class="row">
<div class="one"> <?php echo $item['id'] ?></div>
<div class="two"><?php echo $item['content'] ?></div>
<div class="three"> <?php echo $item['authorName'] ?></div>
<div class="four"><?php echo $item['date'] ?></div>
<div class="five"> <?php
$url = 'detail.php?id=' . $item['id'];
echo ' <a href="' . $url . '">详情</a> ';
if(isset($_SESSION['userId']) && $_SESSION['userId'] == $item['author'])
{
$delete = 'manage.php?type=2&id=' . $item['id'];
$update = 'manage.php?type=3&id=' . $item['id'];
echo ' <a href="' . $delete . '">删除</a> ';
echo ' <a href="' . $update . '">编辑</a> ';
}
?></div>
</div>
<?php endforeach; ?>
<?php echo $horizonLine; ?>
</div>
<div class="leg">
<?php
$chapt = (int)($page/PAGE_PER_CHAPTER); // 当前chapter, 从0开始
$chaptSum = 1 + (int)(($pageSum - 1)/PAGE_PER_CHAPTER);
$pageStart = (int)($chapt * PAGE_PER_CHAPTER);
$pageEnd = min($pageSum - 1, $pageStart + PAGE_PER_CHAPTER - 1);
$pageNum = $pageEnd - $pageStart + 1;
// echo '$page:', $page, ' $chap:', $chapt, '<br />';
if($chapt > 0)
{
$pagePriv = (int)(($chapt - 1)*PAGE_PER_CHAPTER);
$url = 'index.php?page=' . $pagePriv;
echo ' <a href="' . $url . '">' . '<</a> ';
}
for($i=0; $i<$pageNum; $i++)
{
$id = $pageStart + $i;
$url = 'index.php?page=' . $id;
echo ' <a href="' . $url . '">' . ($id+1). '</a> ';
}
if($chapt < ($chaptSum - 1))
{
$pageNext = (int)(($chapt + 1)*PAGE_PER_CHAPTER);
$url = 'index.php?page=' . $pageNext;
echo ' <a href="' . $url . '">' . '></a> ';
}
?>
</div>
</body></html>
util.php较为简短,提供工具函数包括数据库连接等,如下:
<?php
function getPdo()
{
static $pdo = null;
if($pdo !== null)
return $pdo;
try
{
// echo 'pdo connecting <br>';
$dsn = sprintf('mysql:host=%s;dbname=%s;charset=utf8', DB_HOST, DB_NAME);
$option = [PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC];
return $pdo = new PDO($dsn, DB_USR, DB_PASSWD, $option);
}
catch (PDOException $e) { exit($e->getMessage()); }
}
function truncate(&$src, $max)
{
$dst = 0;
if(isset($src))
{
if($src >= $max)
$dst = $max;
else if($src < 0)
$dst = 0;
else
$dst = $src;
}
return $dst;
}
cfg.php记录项目配置信息,例如数据库用户名等,cfg.php是install.php生成,笔者的cfg.php如下:
<?php
// create time: 2018-01-05 02:24:13
define('DB_HOST', 'localhost');
define('DB_USR', 'root');
define('DB_PASSWD', '123');
define('DB_NAME', 'board');
define('LINE_PER_PAGE', 10);
define('PAGE_PER_CHAPTER', 5);
define('TINY_DEBUG', 1);
if(TINY_DEBUG == true)
{
error_reporting(E_ALL);
ini_set('display_errors','On');
}
else
{
error_reporting(E_USER_NOTICE);
ini_set('display_errors','Off');
ini_set('log_errors', 'On');
}
login.php实现网站用户的登录和登出功能,login.php如下:
<!DOCTYPE html>
<html>
<head> <title>TinyBoard | login</title> </head><body>
<a href="index.php">Home</a> <br />
<?php
// 用户登录和登出
session_start();
if(isset($_SESSION['username']))
{
isset($_GET['logout']) or die('die at line:' . __LINE__);
// 退出登录
$_SESSION = [];
if(isset($_COOKIE[session_name()]))
setcookie(session_name(), '', time()-1, '/');
session_destroy();
echo 'logout successfully <br />';
goto End;
}
if(!isset($_POST['username']) || !isset($_POST['passwd']))
{
?>
<form method="post">
user name: <input type="text" name="username" value="Huyelei"> <br />
password : <input type="password" name="passwd" value="123"> <br />
<input type="submit" value="Login">
</form>
<?php
goto End;
}
$username = $_POST['username'];
$password = $_POST['passwd'];
$query = "select * from user where name='$username' and passwd='$password'";
require 'cfg.php';
require 'util.php';
$stat = getPdo()->prepare($query);
$stat->execute();
$r = $stat->fetch(PDO::FETCH_ASSOC);
!empty($r) && isset($r['name']) or die('die at line:' . __LINE__);
// 登录成功
$_SESSION['username'] = $r['name'];
$_SESSION['userId'] = $r['id'];
echo 'user:', $_SESSION['username'],' login successfully.', '<br />';
End:
?>
</body></html>
detail.php描述某条留言(message)的详细信息,留言的ID通过$_GET['id']获得。detail.php如下:
<?php
// 消息细节页面
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>TinyBoard | detail</title>
</head>
<body>
<a href="index.php">首页</a><br /><br />
<?php
session_start();
require 'cfg.php';
require 'util.php';
isset($_GET['id']) or die('die at line:' . __LINE__);
$stat = getPdo()->prepare('select * from message where id = ?');
$stat->execute([$_GET['id']]);
$item = $stat->fetch();
extract($item);
echo 'id: ', $id, '<br />';
echo 'content: ', $content, '<br />';
echo 'authorId: ', $author, '<br />';
echo 'authorName: ', $authorName, '<br />';
echo 'date: ', $date, '<br />';
if(isset($_SESSION['userId']) && $_SESSION['userId'] == $item['author'])
{
echo '<br />';
$delete = 'manage.php?type=2&id=' . $item['id'];
$update = 'manage.php?type=3&id=' . $item['id'];
echo ' <a href="' . $delete . '">删除</a> ';
echo ' <a href="' . $update . '">编辑</a> ';
}
?>
</body>
</html>
manage.php实现对message列表的增删改操作,访问manage.php采用GET方式,带有参数type和id,type表明操作类型(增删改),id表明消息id(仅删除和更改操作有意义)。manage.php如下:
<?php
// 对某个消息的增删改页面, 增删改类型依次为 1 2 3
?>
<!DOCTYPE html>
<html><head>
<meta charset="UTF-8">
<title>TinyBoard | manage </title>
</head> <body>
<a href="index.php">首页</a><br /><br />
<?php
session_start();
require 'cfg.php';
require 'util.php';
$type = truncate($_GET['type'], 4);
$type > 0 && $type < 4 or die('die at line:' . __LINE__);
if($type == 1) // 增
{
if(!isset($_GET['ready'])) // 未上传参数
{
?>
<form>
content <input type="text" name="content"><br />
<input type="hidden" name="type" value="1">
<input type="hidden" name="ready" value="1">
<input type="submit" value="增加">
</form>
<?php
}
else // 上传
{
$content = $_GET['content'] ?? '';
!empty($content) or die('die at line:' . __LINE__);
$str = sprintf("insert into message values(0, ?, %d, '%s', '%s')",
$_SESSION['userId'], $_SESSION['username'], date('Y-m-d H:i:s'));
$stat = getPdo()->prepare($str);
$stat->execute([$content]);
$stat->rowCount() > 0 or die('die at line:' . __LINE__);
echo 'content input:', $content, '<br />';
echo 'message successfully created. <br />';
}
}
else if($type == 2) // 删
{
isset($_GET['id']) or die('die at line:' . __LINE__);
$stat = getPdo()->prepare('delete from message where id = ?');
$stat->execute([$_GET['id']]);
$stat->rowCount() > 0 or die('line:' . __LINE__);
echo 'message successfully deleted. <br />';
}
else if($type == 3) // 改
{
isset($_GET['id']) or die('die at line:' . __LINE__);
if(!isset($_GET['ready'])) // 未上传参数
{
$stat = getPdo()->prepare('select * from message where id=?');
$stat->execute([$_GET['id']]);
$r = $stat->fetch();
$content = $r['content'] ?? '';
?>
<form>
content <input type="text" name="content" value="<?php echo $content; ?>"><br />
<input type="hidden" name="id" value="<?php echo $_GET['id']; ?>">
<input type="hidden" name="type" value="3">
<input type="hidden" name="ready" value="1">
<input type="submit" value="编辑">
</form>
<?php
}
else
{
$content = $_GET['content'] ?? '';
!empty($content) or die('die at line:' . __LINE__);
$stat = getPdo()->prepare('update message set content=?,date=? where id=?;');
$stat->execute([$content, date('Y-m-d H:i:s'), $_GET['id']]);
$stat->rowCount() > 0 or die('die at line:' . __LINE__);
echo 'content input:', $content, '<br />';
echo 'message successfully updated. <br />';
}
}
?>
</body></html>
以上是所有代码。index.php执行效果如下图所示:

代码使用方法:将7个文件拷贝到web根目录,例如F:\wamp64\www,然后浏览器访问install.php,然后访问index.php。
项目代码github地址为:https://github.com/huyl2002/TinyBoard。欢迎加入QQ群技术交流:619348997。
浙公网安备 33010602011771号