- 后台管理:登录、发布新闻、编辑新闻、删除新闻。
- 前台展示:列出所有新闻、查看单条新闻详情。
- 数据持久化:使用 MySQL 数据库存储数据。
第零步:环境准备
在开始之前,请确保你的电脑上已经安装了以下软件:

(图片来源网络,侵删)
- Web 服务器:如 XAMPP、WAMP 或 MAMP,这些软件包通常集成了 Apache (Web服务器) 和 MySQL (数据库)。
- PHP:Web 服务器包通常会包含 PHP。
- MySQL:同样,Web 服务器包会包含,你也可以单独安装。
安装好 XAMPP 后,启动 Apache 和 MySQL 服务。
第一步:数据库设计
我们需要一个数据库来存储新闻数据,我们将创建一个名为 news_db 的数据库,并在其中创建一个 articles 表。
- 打开你的浏览器,访问
http://localhost/phpmyadmin。 - 点击“新建”,创建一个数据库,命名为
news_db。 - 在
news_db中,点击“新建表”,创建一个名为articles的表,包含以下字段:
| 字段名 | 类型 | 长度/值 | 索引 | 注释 |
|---|---|---|---|---|
id |
INT | 11 | PRIMARY, AUTO_INCREMENT | 新闻的唯一ID |
content |
TEXT | |||
created_at |
DATETIME | 发布时间 | ||
updated_at |
DATETIME | 更新时间 |
SQL 语句(你也可以直接在 phpMyAdmin 中执行):
CREATE DATABASE `news_db`; USE `news_db`; CREATE TABLE `articles` ( `id` int(11) NOT NULL AUTO_INCREMENT, varchar(255) NOT NULL, `content` text NOT NULL, `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
第二步:项目文件结构
在 Web 服务器的根目录(对于 XAMPP 是 htdocs)下,创建一个新的文件夹,simple-cms,我们的所有文件都将放在这个文件夹里。

(图片来源网络,侵删)
最终的文件结构如下:
/simple-cms/
├── admin/ # 后台管理目录
│ ├── login.php # 登录页面
│ ├── index.php # 后台首页(新闻列表)
│ ├── create.php # 创建新闻页面
│ ├── edit.php # 编辑新闻页面
│ └── process.php # 处理增删改操作的逻辑
├── assets/ # 静态资源(CSS, JS, 图片)
│ └── css/
│ └── style.css # 样式文件
├── config/ # 配置文件
│ └── db.php # 数据库连接配置
├── index.php # 前台首页(新闻列表)
├── article.php # 前台查看单条新闻
└── logout.php # 退出登录
第三步:数据库连接 (config/db.php)
创建一个文件来管理数据库连接,这样我们可以在所有需要的地方复用它,并且方便修改。
config/db.php
<?php
// 数据库连接配置
$host = 'localhost';
$dbname = 'news_db';
$username = 'root'; // 默认用户名
$password = ''; // 默认密码为空
try {
// 创建 PDO 实例
$pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $username, $password);
// 设置 PDO 错误模式为异常
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
// 如果连接失败,显示错误信息并停止脚本
die("数据库连接失败: " . $e->getMessage());
}
?>
第四步:后台管理系统
登录功能 (admin/login.php)
后台需要登录才能访问,我们用一个简单的会话来管理用户状态。

(图片来源网络,侵删)
admin/login.php
<?php
session_start();
if (isset($_SESSION['admin_logged_in']) && $_SESSION['admin_logged_in'] === true) {
header('Location: index.php');
exit;
}
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// 这里是硬编码的用户名和密码,实际项目中应该从数据库验证
$admin_username = 'admin';
$admin_password = 'password123';
if ($_POST['username'] === $admin_username && $_POST['password'] === $admin_password) {
$_SESSION['admin_logged_in'] = true;
header('Location: index.php');
exit;
} else {
$error = '用户名或密码错误!';
}
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">后台登录</title>
<link rel="stylesheet" href="../assets/css/style.css">
</head>
<body>
<div class="container">
<h1>后台管理系统</h1>
<?php if ($error): ?>
<p class="error"><?php echo $error; ?></p>
<?php endif; ?>
<form action="login.php" method="POST">
<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>
</div>
</body>
</html>
后台首页 (admin/index.php)
这是登录后看到的新闻管理列表。
admin/index.php
<?php
session_start();
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
header('Location: login.php');
exit;
}
require_once '../config/db.php';
// 获取所有新闻
$stmt = $pdo->query("SELECT id, title, created_at FROM articles ORDER BY created_at DESC");
$articles = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">新闻管理</title>
<link rel="stylesheet" href="../assets/css/style.css">
</head>
<body>
<div class="container">
<h1>新闻管理</h1>
<a href="create.php" class="btn">发布新新闻</a>
<a href="../index.php" class="btn">返回首页</a>
<a href="logout.php" class="btn btn-danger">退出登录</a>
<table>
<thead>
<tr>
<th>ID</th>
<th>标题</th>
<th>发布时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php foreach ($articles as $article): ?>
<tr>
<td><?php echo $article['id']; ?></td>
<td><?php echo htmlspecialchars($article['title']); ?></td>
<td><?php echo $article['created_at']; ?></td>
<td>
<a href="edit.php?id=<?php echo $article['id']; ?>" class="btn">编辑</a>
<a href="process.php?action=delete&id=<?php echo $article['id']; ?>" class="btn btn-danger" onclick="return confirm('确定要删除吗?');">删除</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</body>
</html>
创建新闻 (admin/create.php)
一个简单的表单,用于输入新闻标题和内容。
admin/create.php
<?php
session_start();
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
header('Location: login.php');
exit;
}
// 处理表单提交
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$title = trim($_POST['title']);
$content = trim($_POST['content']);
if (empty($title) || empty($content)) {
$error = '标题和内容都不能为空!';
} else {
require_once '../config/db.php';
$stmt = $pdo->prepare("INSERT INTO articles (title, content) VALUES (:title, :content)");
$stmt->execute(['title' => $title, 'content' => $content]);
header('Location: index.php');
exit;
}
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">发布新闻</title>
<link rel="stylesheet" href="../assets/css/style.css">
</head>
<body>
<div class="container">
<h1>发布新新闻</h1>
<a href="index.php" class="btn">返回列表</a>
<?php if (isset($error)): ?>
<p class="error"><?php echo $error; ?></p>
<?php endif; ?>
<form action="create.php" method="POST">
<div>
<label for="title">标题:</label>
<input type="text" id="title" name="title" required>
</div>
<div>
<label for="content">内容:</label>
<textarea id="content" name="content" rows="10" required></textarea>
</div>
<button type="submit">发布</button>
</form>
</div>
</body>
</html>
编辑新闻 (admin/edit.php)
从数据库中取出已有新闻的数据并填充到表单中。
admin/edit.php
<?php
session_start();
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
header('Location: login.php');
exit;
}
require_once '../config/db.php';
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
die('无效的新闻ID');
}
$id = $_GET['id'];
$stmt = $pdo->prepare("SELECT * FROM articles WHERE id = :id");
$stmt->execute(['id' => $id]);
$article = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$article) {
die('新闻不存在');
}
// 处理表单提交
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$title = trim($_POST['title']);
$content = trim($_POST['content']);
if (empty($title) || empty($content)) {
$error = '标题和内容都不能为空!';
} else {
$stmt = $pdo->prepare("UPDATE articles SET title = :title, content = :content WHERE id = :id");
$stmt->execute(['title' => $title, 'content' => $content, 'id' => $id]);
header('Location: index.php');
exit;
}
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">编辑新闻</title>
<link rel="stylesheet" href="../assets/css/style.css">
</head>
<body>
<div class="container">
<h1>编辑新闻</h1>
<a href="index.php" class="btn">返回列表</a>
<?php if (isset($error)): ?>
<p class="error"><?php echo $error; ?></p>
<?php endif; ?>
<form action="edit.php?id=<?php echo $id; ?>" method="POST">
<div>
<label for="title">标题:</label>
<input type="text" id="title" name="title" value="<?php echo htmlspecialchars($article['title']); ?>" required>
</div>
<div>
<label for="content">内容:</label>
<textarea id="content" name="content" rows="10" required><?php echo htmlspecialchars($article['content']); ?></textarea>
</div>
<button type="submit">更新</button>
</form>
</div>
</body>
</html>
处理逻辑 (admin/process.php)
这个文件用于处理删除操作。
admin/process.php
<?php
session_start();
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
header('Location: login.php');
exit;
}
require_once '../config/db.php';
if (isset($_GET['action']) && isset($_GET['id'])) {
$action = $_GET['action'];
$id = $_GET['id'];
if ($action === 'delete' && is_numeric($id)) {
$stmt = $pdo->prepare("DELETE FROM articles WHERE id = :id");
$stmt->execute(['id' => $id]);
}
}
header('Location: index.php');
exit;
退出登录 (admin/logout.php)
销毁会话,让用户退出。
admin/logout.php
<?php
session_start();
session_destroy();
header('Location: login.php');
exit;
?>
第五步:前台展示
前台首页 (index.php)
展示所有新闻的列表。
index.php
<?php
require_once 'config/db.php';
// 获取所有新闻,限制数量以便分页(这里简单处理)
$stmt = $pdo->query("SELECT id, title, created_at, LEFT(content, 150) AS short_content FROM articles ORDER BY created_at DESC LIMIT 10");
$articles = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">我的新闻网站</title>
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
<div class="container">
<h1>我的新闻网站</h1>
<a href="admin/login.php" class="btn">进入后台</a>
<div class="news-list">
<?php foreach ($articles as $article): ?>
<div class="news-item">
<h2><a href="article.php?id=<?php echo $article['id']; ?>"><?php echo htmlspecialchars($article['title']); ?></a></h2>
<p class="short-content"><?php echo htmlspecialchars($article['short_content']) . '...'; ?></p>
<p class="date">发布于: <?php echo $article['created_at']; ?></p>
</div>
<?php endforeach; ?>
</div>
</div>
</body>
</html>
新闻详情页 (article.php)
展示单条新闻的完整内容。
article.php
<?php
require_once 'config/db.php';
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
die('无效的新闻ID');
}
$id = $_GET['id'];
$stmt = $pdo->prepare("SELECT * FROM articles WHERE id = :id");
$stmt->execute(['id' => $id]);
$article = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$article) {
die('新闻不存在');
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8"><?php echo htmlspecialchars($article['title']); ?></title>
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
<div class="container">
<a href="index.php" class="btn">返回首页</a>
<a href="admin/login.php" class="btn">进入后台</a>
<article class="news-detail">
<h1><?php echo htmlspecialchars($article['title']); ?></h1>
<p class="date">发布于: <?php echo $article['created_at']; ?></p>
<div class="content">
<?php echo nl2br(htmlspecialchars($article['content'])); ?>
<!-- nl2br 将换行符转换为 <br> 标签 -->
</div>
</article>
</div>
</body>
</html>
第六步:添加基本样式 (assets/css/style.css)
为了让我们的网站看起来不那么简陋,添加一些 CSS 样式。
assets/css/style.css
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
line-height: 1.6;
margin: 0;
padding: 20px;
background-color: #f4f4f4;
color: #333;
}
.container {
max-width: 800px;
margin: 0 auto;
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
h1, h2 {
color: #0056b3;
}
a {
color: #007bff;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.btn {
display: inline-block;
background: #007bff;
color: #fff;
padding: 8px 15px;
border-radius: 4px;
margin-right: 10px;
margin-bottom: 10px;
}
.btn-danger {
background: #dc3545;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
th, td {
border: 1px solid #ddd;
padding: 12px;
text-align: left;
}
th {
background-color: #f2f2f2;
}
tr:nth-child(even) {
background-color: #f9f9f9;
}
.news-list .news-item {
border-bottom: 1px solid #eee;
padding-bottom: 15px;
margin-bottom: 15px;
}
.news-list .news-item h2 a {
color: #0056b3;
}
.news-list .short-content {
color: #666;
margin: 10px 0;
}
.news-list .date {
font-size: 0.9em;
color: #999;
}
.news-detail h1 {
border-bottom: 2px solid #eee;
padding-bottom: 10px;
margin-bottom: 20px;
}
.news-detail .content {
white-space: pre-wrap; /* 保留换行和空格 */
}
.error {
color: #dc3545;
background: #f8d7da;
border: 1px solid #f5c6cb;
padding: 10px;
border-radius: 4px;
margin-bottom: 15px;
}
textarea {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box; /* 确保padding不会影响宽度 */
}
总结与后续改进
恭喜!你已经成功搭建了一个功能简单的 PHP CMS 新闻系统。
回顾一下我们做了什么:
- 设计了数据库:用
articles表存储新闻。 - 创建了核心文件:包括数据库连接、后台管理(增删改查)和前台展示。
- 使用了会话:保护后台管理页面,只有登录后才能访问。
- 加入了基本样式:让界面更美观。
可以进一步改进的方向:
- 用户认证:将硬编码的用户名密码改为从数据库验证。
- 富文本编辑器:集成如 TinyMCE 或 CKEditor 编辑器,让新闻发布更方便。
- 分页:当新闻很多时,前台和后台列表都需要分页显示。
- 图片上传:允许上传图片并插入到新闻内容中。
- SEO优化:为每个页面设置
<title>和<meta description>。 - 更安全的代码:防止 SQL 注入(我们已用 PDO 预处理语句防止)、XSS 攻击(我们已用
htmlspecialchars防止)、CSRF 攻击(可以添加 Token 验证)。 - URL 重写:使用
.htaccess将article.php?id=1这样的 URL 变成更友好的/article/1。
这个项目是学习 PHP Web 开发的一个绝佳起点,你可以基于它进行扩展和深化。
