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">&nbsp;ID</div>
        <div class="two">内容</div>
        <div class="three">&nbsp;用户</div>
        <div class="four">时间</div>
        <div class="five">操作</div>
    </div>
    <?php echo $horizonLine; ?>

    <?php foreach ($items as $item): ?>
    <div class="row">
        <div class="one">&nbsp;<?php echo $item['id'] ?></div>
        <div class="two"><?php echo $item['content'] ?></div>
        <div class="three">&nbsp;<?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 . '">' . '&lt;</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 . '">' . '&gt;</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。

 

posted on 2018-01-06 15:13  huyelei  阅读(584)  评论(0)    收藏  举报

导航