目标:构建一个简单的博客系统
我们将创建一个包含以下功能的博客网站:

- 首页:展示所有博客文章列表。
- 文章详情页:点击文章标题,查看文章的完整内容。
- 后台管理:
- 登录功能。
- 发布新文章。
- 编辑现有文章。
- 删除文章。
第一步:环境准备
在开始之前,你需要一个本地的 PHP 开发环境,最简单的方式是使用集成环境包。
推荐工具:XAMPP
XAMPP 是一个集成了 Apache (Web服务器)、MySQL (数据库) 和 PHP 的免费软件包。
- 下载:访问 XAMPP 官网,下载适合你操作系统的版本。
- 安装:按照安装向导完成安装,记住安装路径,通常是
C:\xampp(Windows) 或/Applications/XAMPP(macOS)。 - 启动服务:
- 打开 XAMPP Control Panel。
- 点击 Apache 和 MySQL 模块的 Start 按钮。
- 验证:在浏览器中访问
http://localhost或http://127.0.0.1,如果你能看到 XAMPP 的欢迎页面,说明环境已成功搭建。
创建项目目录
- 在 XAMPP 的安装目录下,找到
htdocs文件夹。 - 在
htdocs文件夹中创建一个新文件夹,命名为my_blog。 - 这个
my_blog文件夹就是我们的网站根目录,之后所有代码都放在这里。
第二步:数据库设计与创建
我们的博客需要一个数据库来存储文章和用户信息。
- 访问 phpMyAdmin:在浏览器中访问
http://localhost/phpmyadmin。 - 创建数据库:
- 在主页面的 "创建数据库" 输入框中,输入
my_blog_db。 - 选择排序规则(通常使用
utf8mb4_general_ci以支持中文和 Emoji)。 - 点击 "创建"。
- 在主页面的 "创建数据库" 输入框中,输入
- 创建数据表:
- 在左侧选择刚创建的
my_blog_db。 - 在 "在数据库 my_blog_db 中创建表" 输入框中,输入
posts。 - 设置字段数为 4,然后点击 "执行"。
- 填写
posts表结构:id: 类型选INT,勾选A_I(Auto Increment,自增) 作为主键。title: 类型VARCHAR(255),长度 255。content: 类型TEXT,用于存储长文本。created_at: 类型DATETIME,用于记录创建时间。
- 点击 "保存"。
- 在左侧选择刚创建的
- 创建用户表:
- 再次在 "在数据库 my_blog_db 中创建表" 输入框中,输入
users。 - 设置字段数为 3,然后点击 "执行"。
- 填写
users表结构:id: 类型INT,勾选A_I作为主键。username: 类型VARCHAR(50),长度 50。password: 类型VARCHAR(255),长度一定要足够长,因为我们会存储哈希后的密码。
- 点击 "保存"。
- 再次在 "在数据库 my_blog_db 中创建表" 输入框中,输入
- 插入一个管理员用户:
- 在
users表页,点击 "插入" 标签页。 username填入admin。password填入123456(这只是明文,我们稍后会用 PHP 代码来正确地创建一个哈希密码)。- 点击 "执行"。
- 在
第三步:项目结构与基础文件
在 my_blog 文件夹中,我们创建以下结构来组织代码:

my_blog/
├── assets/
│ ├── css/
│ │ └── style.css # 存放样式文件
│ └── images/
├── config/
│ └── database.php # 数据库连接配置
├── admin/
│ ├── login.php # 管理员登录页
│ ├── dashboard.php # 管理后台首页
│ ├── create_post.php # 创建文章页面
│ └── edit_post.php # 编辑文章页面
├── includes/
│ ├── header.php # 公共头部
│ └── footer.php # 公共底部
├── index.php # 博客首页
├── post.php # 文章详情页
└── .htaccess # URL 重写规则 (可选,但推荐)
第四步:编写核心代码
数据库连接 (config/database.php)
这是一个非常重要的文件,我们把它单独拿出来,方便以后修改数据库信息而不用改动其他文件。
<?php
// config/database.php
$host = 'localhost';
$db_name = 'my_blog_db';
$username = 'root'; // XAMPP 默认用户名
$password = ''; // XAMPP 默认密码为空
try {
$pdo = new PDO("mysql:host={$host};dbname={$db_name}", $username, $password);
// 设置 PDO 错误模式为异常
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e) {
die("连接失败: " . $e->getMessage());
}
?>
公共头部 (includes/header.php)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">我的博客</title>
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
<header>
<h1><a href="index.php">我的博客</a></h1>
<nav>
<a href="index.php">首页</a>
<a href="admin/login.php">管理后台</a>
</nav>
</header>
<div class="container">
公共底部 (includes/footer.php)
</div> <!-- .container -->
<footer>
<p>© <?php echo date('Y'); ?> 我的博客. All rights reserved.</p>
</footer>
</body>
</html>
博客首页 (index.php)
展示文章列表。
<?php
require_once 'config/database.php';
require_once 'includes/header.php';
// 从数据库获取文章
try {
$stmt = $pdo->query("SELECT id, title, created_at FROM posts ORDER BY created_at DESC");
$posts = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
die("获取文章失败: " . $e->getMessage());
}
?>
<h2>最新文章</h2>
<?php if (empty($posts)): ?>
<p>暂无文章。</p>
<?php else: ?>
<ul class="post-list">
<?php foreach ($posts as $post): ?>
<li>
<a href="post.php?id=<?php echo htmlspecialchars($post['id']); ?>">
<?php echo htmlspecialchars($post['title']); ?>
</a>
<span class="post-date"><?php echo date('Y-m-d', strtotime($post['created_at'])); ?></span>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<?php require_once 'includes/footer.php'; ?>
文章详情页 (post.php)
显示单篇文章的完整内容。
<?php
require_once 'config/database.php';
require_once 'includes/header.php';
$post_id = $_GET['id'] ?? null;
if (!$post_id) {
die('文章 ID 未指定。');
}
try {
$stmt = $pdo->prepare("SELECT * FROM posts WHERE id = ?");
$stmt->execute([$post_id]);
$post = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$post) {
die('文章不存在。');
}
} catch (PDOException $e) {
die("获取文章失败: " . $e->getMessage());
}
?>
<h1><?php echo htmlspecialchars($post['title']); ?></h1>
<p class="post-meta">发布于: <?php echo date('Y-m-d H:i:s', strtotime($post['created_at'])); ?></p>
<hr>
<div class="post-content">
<?php
// nl2br 函数将换行符转换为 <br> 标签,使文本在 HTML 中正确换行
echo nl2br(htmlspecialchars($post['content']));
?>
</div>
<?php require_once 'includes/footer.php'; ?>
第五步:实现后台管理功能
管理员登录 (admin/login.php)
<?php
session_start(); // 必须在所有输出之前调用
// 如果已经登录,直接跳转到后台
if (isset($_SESSION['admin_logged_in']) && $_SESSION['admin_logged_in'] === true) {
header('Location: dashboard.php');
exit;
}
require_once '../config/database.php';
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
// 注意:在实际应用中,密码应该是哈希后存储和比较的
// 这里为了简化,直接比较明文。**不推荐在生产环境中使用!**
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->execute([$username, $password]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user) {
// 登录成功,设置 session
$_SESSION['admin_logged_in'] = true;
$_SESSION['admin_username'] = $user['username'];
header('Location: dashboard.php');
exit;
} else {
$error = '用户名或密码错误。';
}
}
?>
<?php require_once '../includes/header.php'; ?>
<h2>管理员登录</h2>
<?php if ($error): ?>
<p class="error"><?php echo $error; ?></p>
<?php endif; ?>
<form action="login.php" method="POST" class="login-form">
<div>
<label for="username">用户名:</label>
<input type="text" id="username" name="username" required>
</div>
<div>
<label for="password">密码:</label>
<input type="password" id="password" name="password" required>
</div>
<button type="submit">登录</button>
</form>
<?php require_once '../includes/footer.php'; ?>
后台首页 (admin/dashboard.php)
<?php
session_start();
// 检查是否已登录,如果未登录则跳转到登录页
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
header('Location: login.php');
exit;
}
require_once '../config/database.php';
require_once '../includes/header.php';
?>
<h2>欢迎, <?php echo htmlspecialchars($_SESSION['admin_username']); ?>!</h2>
<p>这是管理后台。</p>
<ul>
<li><a href="create_post.php">发布新文章</a></li>
<li><a href="edit_post.php">编辑文章</a></li>
<li><a href="../index.php">返回博客首页</a></li>
<li><a href="logout.php">退出登录</a></li>
</ul>
<?php require_once '../includes/footer.php'; ?>
退出登录 (admin/logout.php)
<?php
session_start();
// 销毁所有 session 变量
session_unset();
// 销毁 session
session_destroy();
// 重定向到登录页
header('Location: login.php');
exit;
?>
创建文章 (admin/create_post.php)
<?php
session_start();
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
header('Location: login.php');
exit;
}
require_once '../config/database.php';
require_once '../includes/header.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$title = $_POST['title'] ?? '';
$content = $_POST['content'] ?? '';
if (!empty($title) && !empty($content)) {
try {
$stmt = $pdo->prepare("INSERT INTO posts (title, content, created_at) VALUES (?, ?, NOW())");
$stmt->execute([$title, $content]);
echo "<p class='success'>文章发布成功!</p>";
// 清空表单
$title = '';
$content = '';
} catch (PDOException $e) {
echo "<p class='error'>发布失败: " . $e->getMessage() . "</p>";
}
} else {
echo "<p class='error'>标题和内容都不能为空!</p>";
}
}
?>
<h2>发布新文章</h2>
<form action="create_post.php" method="POST" class="post-form">
<div>
<label for="title">标题:</label>
<input type="text" id="title" name="title" value="<?php echo htmlspecialchars($title ?? ''); ?>" required>
</div>
<div>
<label for="content">内容:</label>
<textarea id="content" name="content" rows="10" required><?php echo htmlspecialchars($content ?? ''); ?></textarea>
</div>
<button type="submit">发布</button>
</form>
<?php require_once '../includes/footer.php'; ?>
编辑文章 (admin/edit_post.php)
这个页面需要先获取现有文章内容,然后允许修改。

<?php
session_start();
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
header('Location: login.php');
exit;
}
require_once '../config/database.php';
require_once '../includes/header.php';
$post_id = $_GET['id'] ?? null;
$post = null;
if ($post_id) {
try {
$stmt = $pdo->prepare("SELECT * FROM posts WHERE id = ?");
$stmt->execute([$post_id]);
$post = $stmt->fetch(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
die("获取文章失败: " . $e->getMessage());
}
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $post_id) {
$title = $_POST['title'] ?? '';
$content = $_POST['content'] ?? '';
if (!empty($title) && !empty($content)) {
try {
$stmt = $pdo->prepare("UPDATE posts SET title = ?, content = ? WHERE id = ?");
$stmt->execute([$title, $content, $post_id]);
echo "<p class='success'>文章更新成功!</p>";
// 刷新页面以显示新内容
header("Refresh: 2; URL=edit_post.php?id=$post_id");
} catch (PDOException $e) {
echo "<p class='error'>更新失败: " . $e->getMessage() . "</p>";
}
} else {
echo "<p class='error'>标题和内容都不能为空!</p>";
}
}
?>
<h2>编辑文章</h2>
<?php if ($post): ?>
<form action="edit_post.php?id=<?php echo $post['id']; ?>" method="POST" class="post-form">
<div>
<label for="title">标题:</label>
<input type="text" id="title" name="title" value="<?php echo htmlspecialchars($post['title']); ?>" required>
</div>
<div>
<label for="content">内容:</label>
<textarea id="content" name="content" rows="10" required><?php echo htmlspecialchars($post['content']); ?></textarea>
</div>
<button type="submit">更新</button>
</form>
<?php else: ?>
<p>未找到要编辑的文章。</p>
<?php endif; ?>
<?php require_once '../includes/footer.php'; ?>
第六步:添加一些 CSS 样式 (assets/css/style.css)
为了让网站看起来更美观,添加一些简单的 CSS。
/* assets/css/style.css */
body {
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 0;
background-color: #f4f4f4;
color: #333;
}
.container {
max-width: 800px;
margin: 20px auto;
padding: 0 20px;
background: #fff;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
header {
background: #333;
color: #fff;
padding: 1rem 0;
text-align: center;
}
header h1 a {
color: #fff;
text-decoration: none;
}
nav a {
color: #fff;
margin: 0 15px;
text-decoration: none;
}
nav a:hover {
text-decoration: underline;
}
.post-list {
list-style: none;
padding: 0;
}
.post-list li {
background: #f9f9f9;
border: 1px solid #ddd;
padding: 15px;
margin-bottom: 10px;
border-radius: 5px;
}
.post-list a {
color: #0066cc;
text-decoration: none;
font-weight: bold;
font-size: 1.2em;
}
.post-list a:hover {
text-decoration: underline;
}
.post-date {
float: right;
color: #777;
font-size: 0.9em;
}
.post-meta {
color: #777;
font-size: 0.9em;
}
.post-content {
margin-top: 20px;
white-space: pre-wrap; /* 保留空格和换行 */
}
.login-form, .post-form {
max-width: 400px;
margin: 20px auto;
padding: 20px;
background: #f9f9f9;
border-radius: 5px;
}
.login-form div, .post-form div {
margin-bottom: 15px;
}
.login-form label, .post-form label {
display: block;
margin-bottom: 5px;
}
.login-form input, .post-form input, .post-form textarea {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box; /* 确保 padding 不会影响宽度 */
}
.login-form button, .post-form button {
background: #333;
color: #fff;
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.login-form button:hover, .post-form button:hover {
background: #555;
}
.error {
color: #D8000C;
background-color: #FFD2D2;
padding: 10px;
border-radius: 5px;
margin: 10px 0;
}
.success {
color: #4F8A10;
background-color: #DFF2BF;
padding: 10px;
border-radius: 5px;
margin: 10px 0;
}
footer {
text-align: center;
padding: 20px;
margin-top: 20px;
color: #777;
}
总结与进阶
恭喜!你已经成功创建了一个功能完整的 PHP 博客网站,这个项目涵盖了 PHP 开发中最常用的几个方面:
- MVC 思想雏形:虽然我们没有严格使用 MVC 框架,但已经将数据、视图和控制逻辑(PHP 脚本)分离开来。
- 数据库交互:使用 PDO 安全地连接和查询 MySQL 数据库。
- 会话管理:使用
$_SESSION实现用户登录状态。 - 表单处理:接收和处理用户提交的数据。
- 安全性基础:使用
htmlspecialchars()防止 XSS 攻击。
可以继续改进的方向:
- 密码哈希:在
admin/login.php中,将明文密码改为使用password_hash()和password_verify()函数。 - 文章删除功能:在
dashboard.php和edit_post.php中添加删除按钮。 - 分页:当文章很多时,首页需要分页显示。
- URL 友好化:使用
.htaccess和mod_rewrite将post.php?id=1变成post/my-first-post这样的友好 URL。 - 使用框架:学习并使用 Laravel 或 Symfony 等现代 PHP 框架,它们能让你更高效、更规范地开发大型应用。
- 增加评论功能:创建一个
comments表,并为每篇文章添加评论功能。
这个教程是一个绝佳的起点,希望它能帮助你迈出 PHP Web 开发的第一步!
