Appearance
实战 5:简单后台管理系统(CMS)
项目简介
本实战项目将开发一个简单的后台管理系统(CMS - Content Management System),用于管理网站内容。通过这个项目,你将学习如何构建一个完整的后台管理系统,包括用户认证、内容管理、权限控制等核心功能。
技术栈
- PHP 7.4+
- MySQL 5.7+
- HTML5 + CSS3 + JavaScript
- Bootstrap 5(前端框架)
项目结构
cms/
├── admin/
│ ├── dashboard.php # 后台首页
│ ├── users.php # 用户管理
│ ├── articles.php # 文章管理
│ ├── categories.php # 分类管理
│ ├── settings.php # 系统设置
│ └── profile.php # 个人资料
├── includes/
│ ├── config.php # 配置文件
│ ├── db.php # 数据库连接
│ ├── functions.php # 函数库
│ └── header.php # 头部模板
├── public/
│ ├── css/ # 样式文件
│ ├── js/ # JavaScript 文件
│ └── uploads/ # 上传文件目录
├── index.php # 前台首页
├── login.php # 登录页面
└── logout.php # 退出页面数据库设计
users 表
| 字段名 | 数据类型 | 描述 |
|---|---|---|
| id | INT(11) | 用户 ID |
| username | VARCHAR(50) | 用户名 |
| password | VARCHAR(255) | 密码(哈希加密) |
| VARCHAR(100) | 邮箱 | |
| role | ENUM('admin', 'editor') | 用户角色 |
| created_at | TIMESTAMP | 创建时间 |
| updated_at | TIMESTAMP | 更新时间 |
categories 表
| 字段名 | 数据类型 | 描述 |
|---|---|---|
| id | INT(11) | 分类 ID |
| name | VARCHAR(50) | 分类名称 |
| slug | VARCHAR(50) | 分类别名 |
| created_at | TIMESTAMP | 创建时间 |
| updated_at | TIMESTAMP | 更新时间 |
articles 表
| 字段名 | 数据类型 | 描述 |
|---|---|---|
| id | INT(11) | 文章 ID |
| title | VARCHAR(255) | 文章标题 |
| slug | VARCHAR(255) | 文章别名 |
| content | TEXT | 文章内容 |
| category_id | INT(11) | 分类 ID |
| user_id | INT(11) | 作者 ID |
| status | ENUM('published', 'draft') | 文章状态 |
| created_at | TIMESTAMP | 创建时间 |
| updated_at | TIMESTAMP | 更新时间 |
核心功能实现
1. 用户登录与认证
php
// login.php
<?php
session_start();
include 'includes/config.php';
include 'includes/db.php';
if (isset($_POST['login'])) {
$username = $_POST['username'];
$password = $_POST['password'];
$sql = "SELECT * FROM users WHERE username = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param('s', $username);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows > 0) {
$user = $result->fetch_assoc();
if (password_verify($password, $user['password'])) {
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['role'] = $user['role'];
header('Location: admin/dashboard.php');
exit;
} else {
$error = "密码错误";
}
} else {
$error = "用户名不存在";
}
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录 - CMS 管理系统</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h3 class="text-center">CMS 管理系统登录</h3>
</div>
<div class="card-body">
<?php if (isset($error)): ?>
<div class="alert alert-danger" role="alert">
<?php echo $error; ?>
</div>
<?php endif; ?>
<form method="POST">
<div class="mb-3">
<label for="username" class="form-label">用户名</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">密码</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" name="login" class="btn btn-primary w-100">登录</button>
</form>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>2. 后台首页(仪表盘)
php
// admin/dashboard.php
<?php
session_start();
include '../includes/config.php';
include '../includes/db.php';
// 检查登录状态
if (!isset($_SESSION['user_id'])) {
header('Location: ../login.php');
exit;
}
// 获取统计数据
$user_count = $conn->query("SELECT COUNT(*) as count FROM users")->fetch_assoc()['count'];
$article_count = $conn->query("SELECT COUNT(*) as count FROM articles")->fetch_assoc()['count'];
$category_count = $conn->query("SELECT COUNT(*) as count FROM categories")->fetch_assoc()['count'];
$published_count = $conn->query("SELECT COUNT(*) as count FROM articles WHERE status = 'published'")->fetch_assoc()['count'];
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>仪表盘 - CMS 管理系统</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css">
</head>
<body>
<?php include '../includes/header.php'; ?>
<div class="container-fluid">
<div class="row">
<!-- 侧边栏 -->
<nav class="col-md-3 col-lg-2 d-md-block bg-light sidebar">
<div class="position-sticky pt-3">
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link active" href="dashboard.php">
<i class="fa fa-dashboard"></i> 仪表盘
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="articles.php">
<i class="fa fa-file-text"></i> 文章管理
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="categories.php">
<i class="fa fa-list"></i> 分类管理
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="users.php">
<i class="fa fa-users"></i> 用户管理
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="settings.php">
<i class="fa fa-cog"></i> 系统设置
</a>
</li>
</ul>
</div>
</nav>
<!-- 主内容 -->
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">仪表盘</h1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group me-2">
<a href="articles.php?action=add" class="btn btn-sm btn-outline-secondary">
<i class="fa fa-plus"></i> 新建文章
</a>
</div>
</div>
</div>
<!-- 统计卡片 -->
<div class="row">
<div class="col-md-3">
<div class="card text-white bg-primary mb-3">
<div class="card-body">
<h5 class="card-title">用户总数</h5>
<p class="card-text display-4"><?php echo $user_count; ?></p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-white bg-success mb-3">
<div class="card-body">
<h5 class="card-title">文章总数</h5>
<p class="card-text display-4"><?php echo $article_count; ?></p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-white bg-warning mb-3">
<div class="card-body">
<h5 class="card-title">分类总数</h5>
<p class="card-text display-4"><?php echo $category_count; ?></p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-white bg-info mb-3">
<div class="card-body">
<h5 class="card-title">已发布文章</h5>
<p class="card-text display-4"><?php echo $published_count; ?></p>
</div>
</div>
</div>
</div>
<!-- 最近文章 -->
<h2 class="h4 mt-4">最近文章</h2>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th>标题</th>
<th>分类</th>
<th>作者</th>
<th>状态</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php
$sql = "SELECT a.id, a.title, c.name as category, u.username as author, a.status, a.created_at
FROM articles a
LEFT JOIN categories c ON a.category_id = c.id
LEFT JOIN users u ON a.user_id = u.id
ORDER BY a.created_at DESC
LIMIT 5";
$result = $conn->query($sql);
while ($row = $result->fetch_assoc()):
?>
<tr>
<td><?php echo $row['title']; ?></td>
<td><?php echo $row['category']; ?></td>
<td><?php echo $row['author']; ?></td>
<td>
<span class="badge <?php echo $row['status'] == 'published' ? 'bg-success' : 'bg-warning'; ?>">
<?php echo $row['status'] == 'published' ? '已发布' : '草稿'; ?>
</span>
</td>
<td><?php echo $row['created_at']; ?></td>
<td>
<a href="articles.php?action=edit&id=<?php echo $row['id']; ?>" class="btn btn-sm btn-primary">编辑</a>
<a href="articles.php?action=delete&id=<?php echo $row['id']; ?>" class="btn btn-sm btn-danger" onclick="return confirm('确定要删除吗?');">删除</a>
</td>
</tr>
<?php endwhile; ?>
</tbody>
</table>
</div>
</main>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>3. 文章管理
php
// admin/articles.php
<?php
session_start();
include '../includes/config.php';
include '../includes/db.php';
// 检查登录状态
if (!isset($_SESSION['user_id'])) {
header('Location: ../login.php');
exit;
}
// 处理操作
if (isset($_GET['action'])) {
$action = $_GET['action'];
$id = isset($_GET['id']) ? $_GET['id'] : '';
switch ($action) {
case 'add':
// 添加文章
if (isset($_POST['submit'])) {
$title = $_POST['title'];
$slug = $_POST['slug'];
$content = $_POST['content'];
$category_id = $_POST['category_id'];
$status = $_POST['status'];
$user_id = $_SESSION['user_id'];
$sql = "INSERT INTO articles (title, slug, content, category_id, user_id, status, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, NOW(), NOW())";
$stmt = $conn->prepare($sql);
$stmt->bind_param('sssiii', $title, $slug, $content, $category_id, $user_id, $status);
if ($stmt->execute()) {
$_SESSION['message'] = "文章添加成功";
header('Location: articles.php');
exit;
} else {
$error = "文章添加失败";
}
}
break;
case 'edit':
// 编辑文章
if (isset($_POST['submit'])) {
$title = $_POST['title'];
$slug = $_POST['slug'];
$content = $_POST['content'];
$category_id = $_POST['category_id'];
$status = $_POST['status'];
$sql = "UPDATE articles SET title = ?, slug = ?, content = ?, category_id = ?, status = ?, updated_at = NOW() WHERE id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param('sssiii', $title, $slug, $content, $category_id, $status, $id);
if ($stmt->execute()) {
$_SESSION['message'] = "文章更新成功";
header('Location: articles.php');
exit;
} else {
$error = "文章更新失败";
}
}
// 获取文章信息
$sql = "SELECT * FROM articles WHERE id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param('i', $id);
$stmt->execute();
$article = $stmt->get_result()->fetch_assoc();
break;
case 'delete':
// 删除文章
$sql = "DELETE FROM articles WHERE id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param('i', $id);
if ($stmt->execute()) {
$_SESSION['message'] = "文章删除成功";
} else {
$_SESSION['message'] = "文章删除失败";
}
header('Location: articles.php');
exit;
break;
}
}
// 获取所有文章
$sql = "SELECT a.id, a.title, c.name as category, u.username as author, a.status, a.created_at
FROM articles a
LEFT JOIN categories c ON a.category_id = c.id
LEFT JOIN users u ON a.user_id = u.id
ORDER BY a.created_at DESC";
$result = $conn->query($sql);
// 获取所有分类
$categories = $conn->query("SELECT * FROM categories");
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文章管理 - CMS 管理系统</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css">
</head>
<body>
<?php include '../includes/header.php'; ?>
<div class="container-fluid">
<div class="row">
<!-- 侧边栏 -->
<nav class="col-md-3 col-lg-2 d-md-block bg-light sidebar">
<div class="position-sticky pt-3">
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link" href="dashboard.php">
<i class="fa fa-dashboard"></i> 仪表盘
</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="articles.php">
<i class="fa fa-file-text"></i> 文章管理
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="categories.php">
<i class="fa fa-list"></i> 分类管理
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="users.php">
<i class="fa fa-users"></i> 用户管理
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="settings.php">
<i class="fa fa-cog"></i> 系统设置
</a>
</li>
</ul>
</div>
</nav>
<!-- 主内容 -->
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">文章管理</h1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group me-2">
<a href="articles.php?action=add" class="btn btn-sm btn-outline-secondary">
<i class="fa fa-plus"></i> 新建文章
</a>
</div>
</div>
</div>
<!-- 消息提示 -->
<?php if (isset($_SESSION['message'])): ?>
<div class="alert alert-success" role="alert">
<?php echo $_SESSION['message']; unset($_SESSION['message']); ?>
</div>
<?php endif; ?>
<!-- 错误提示 -->
<?php if (isset($error)): ?>
<div class="alert alert-danger" role="alert">
<?php echo $error; ?>
</div>
<?php endif; ?>
<!-- 表单 -->
<?php if (isset($action) && ($action == 'add' || $action == 'edit')): ?>
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title"><?php echo $action == 'add' ? '新建文章' : '编辑文章'; ?></h5>
</div>
<div class="card-body">
<form method="POST">
<div class="mb-3">
<label for="title" class="form-label">标题</label>
<input type="text" class="form-control" id="title" name="title" value="<?php echo isset($article) ? $article['title'] : ''; ?>" required>
</div>
<div class="mb-3">
<label for="slug" class="form-label">别名</label>
<input type="text" class="form-control" id="slug" name="slug" value="<?php echo isset($article) ? $article['slug'] : ''; ?>" required>
</div>
<div class="mb-3">
<label for="category_id" class="form-label">分类</label>
<select class="form-select" id="category_id" name="category_id" required>
<?php while ($category = $categories->fetch_assoc()): ?>
<option value="<?php echo $category['id']; ?>" <?php echo isset($article) && $article['category_id'] == $category['id'] ? 'selected' : ''; ?>>
<?php echo $category['name']; ?>
</option>
<?php endwhile; ?>
</select>
</div>
<div class="mb-3">
<label for="content" class="form-label">内容</label>
<textarea class="form-control" id="content" name="content" rows="10" required><?php echo isset($article) ? $article['content'] : ''; ?></textarea>
</div>
<div class="mb-3">
<label for="status" class="form-label">状态</label>
<select class="form-select" id="status" name="status" required>
<option value="published" <?php echo isset($article) && $article['status'] == 'published' ? 'selected' : ''; ?>>已发布</option>
<option value="draft" <?php echo isset($article) && $article['status'] == 'draft' ? 'selected' : ''; ?>>草稿</option>
</select>
</div>
<button type="submit" name="submit" class="btn btn-primary">保存</button>
<a href="articles.php" class="btn btn-secondary">取消</a>
</form>
</div>
</div>
<?php endif; ?>
<!-- 文章列表 -->
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th>ID</th>
<th>标题</th>
<th>分类</th>
<th>作者</th>
<th>状态</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php while ($row = $result->fetch_assoc()): ?>
<tr>
<td><?php echo $row['id']; ?></td>
<td><?php echo $row['title']; ?></td>
<td><?php echo $row['category']; ?></td>
<td><?php echo $row['author']; ?></td>
<td>
<span class="badge <?php echo $row['status'] == 'published' ? 'bg-success' : 'bg-warning'; ?>">
<?php echo $row['status'] == 'published' ? '已发布' : '草稿'; ?>
</span>
</td>
<td><?php echo $row['created_at']; ?></td>
<td>
<a href="articles.php?action=edit&id=<?php echo $row['id']; ?>" class="btn btn-sm btn-primary">编辑</a>
<a href="articles.php?action=delete&id=<?php echo $row['id']; ?>" class="btn btn-sm btn-danger" onclick="return confirm('确定要删除吗?');">删除</a>
</td>
</tr>
<?php endwhile; ?>
</tbody>
</table>
</div>
</main>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>4. 分类管理
php
// admin/categories.php
<?php
session_start();
include '../includes/config.php';
include '../includes/db.php';
// 检查登录状态
if (!isset($_SESSION['user_id'])) {
header('Location: ../login.php');
exit;
}
// 处理操作
if (isset($_GET['action'])) {
$action = $_GET['action'];
$id = isset($_GET['id']) ? $_GET['id'] : '';
switch ($action) {
case 'add':
// 添加分类
if (isset($_POST['submit'])) {
$name = $_POST['name'];
$slug = $_POST['slug'];
$sql = "INSERT INTO categories (name, slug, created_at, updated_at) VALUES (?, ?, NOW(), NOW())";
$stmt = $conn->prepare($sql);
$stmt->bind_param('ss', $name, $slug);
if ($stmt->execute()) {
$_SESSION['message'] = "分类添加成功";
header('Location: categories.php');
exit;
} else {
$error = "分类添加失败";
}
}
break;
case 'edit':
// 编辑分类
if (isset($_POST['submit'])) {
$name = $_POST['name'];
$slug = $_POST['slug'];
$sql = "UPDATE categories SET name = ?, slug = ?, updated_at = NOW() WHERE id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param('ssi', $name, $slug, $id);
if ($stmt->execute()) {
$_SESSION['message'] = "分类更新成功";
header('Location: categories.php');
exit;
} else {
$error = "分类更新失败";
}
}
// 获取分类信息
$sql = "SELECT * FROM categories WHERE id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param('i', $id);
$stmt->execute();
$category = $stmt->get_result()->fetch_assoc();
break;
case 'delete':
// 删除分类
$sql = "DELETE FROM categories WHERE id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param('i', $id);
if ($stmt->execute()) {
$_SESSION['message'] = "分类删除成功";
} else {
$_SESSION['message'] = "分类删除失败";
}
header('Location: categories.php');
exit;
break;
}
}
// 获取所有分类
$sql = "SELECT * FROM categories ORDER BY created_at DESC";
$result = $conn->query($sql);
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>分类管理 - CMS 管理系统</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css">
</head>
<body>
<?php include '../includes/header.php'; ?>
<div class="container-fluid">
<div class="row">
<!-- 侧边栏 -->
<nav class="col-md-3 col-lg-2 d-md-block bg-light sidebar">
<div class="position-sticky pt-3">
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link" href="dashboard.php">
<i class="fa fa-dashboard"></i> 仪表盘
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="articles.php">
<i class="fa fa-file-text"></i> 文章管理
</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="categories.php">
<i class="fa fa-list"></i> 分类管理
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="users.php">
<i class="fa fa-users"></i> 用户管理
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="settings.php">
<i class="fa fa-cog"></i> 系统设置
</a>
</li>
</ul>
</div>
</nav>
<!-- 主内容 -->
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">分类管理</h1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group me-2">
<a href="categories.php?action=add" class="btn btn-sm btn-outline-secondary">
<i class="fa fa-plus"></i> 新建分类
</a>
</div>
</div>
</div>
<!-- 消息提示 -->
<?php if (isset($_SESSION['message'])): ?>
<div class="alert alert-success" role="alert">
<?php echo $_SESSION['message']; unset($_SESSION['message']); ?>
</div>
<?php endif; ?>
<!-- 错误提示 -->
<?php if (isset($error)): ?>
<div class="alert alert-danger" role="alert">
<?php echo $error; ?>
</div>
<?php endif; ?>
<!-- 表单 -->
<?php if (isset($action) && ($action == 'add' || $action == 'edit')): ?>
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title"><?php echo $action == 'add' ? '新建分类' : '编辑分类'; ?></h5>
</div>
<div class="card-body">
<form method="POST">
<div class="mb-3">
<label for="name" class="form-label">分类名称</label>
<input type="text" class="form-control" id="name" name="name" value="<?php echo isset($category) ? $category['name'] : ''; ?>" required>
</div>
<div class="mb-3">
<label for="slug" class="form-label">别名</label>
<input type="text" class="form-control" id="slug" name="slug" value="<?php echo isset($category) ? $category['slug'] : ''; ?>" required>
</div>
<button type="submit" name="submit" class="btn btn-primary">保存</button>
<a href="categories.php" class="btn btn-secondary">取消</a>
</form>
</div>
</div>
<?php endif; ?>
<!-- 分类列表 -->
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th>ID</th>
<th>分类名称</th>
<th>别名</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php while ($row = $result->fetch_assoc()): ?>
<tr>
<td><?php echo $row['id']; ?></td>
<td><?php echo $row['name']; ?></td>
<td><?php echo $row['slug']; ?></td>
<td><?php echo $row['created_at']; ?></td>
<td>
<a href="categories.php?action=edit&id=<?php echo $row['id']; ?>" class="btn btn-sm btn-primary">编辑</a>
<a href="categories.php?action=delete&id=<?php echo $row['id']; ?>" class="btn btn-sm btn-danger" onclick="return confirm('确定要删除吗?');">删除</a>
</td>
</tr>
<?php endwhile; ?>
</tbody>
</table>
</div>
</main>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>5. 用户管理
php
// admin/users.php
<?php
session_start();
include '../includes/config.php';
include '../includes/db.php';
// 检查登录状态
if (!isset($_SESSION['user_id'])) {
header('Location: ../login.php');
exit;
}
// 处理操作
if (isset($_GET['action'])) {
$action = $_GET['action'];
$id = isset($_GET['id']) ? $_GET['id'] : '';
switch ($action) {
case 'add':
// 添加用户
if (isset($_POST['submit'])) {
$username = $_POST['username'];
$email = $_POST['email'];
$password = password_hash($_POST['password'], PASSWORD_DEFAULT);
$role = $_POST['role'];
$sql = "INSERT INTO users (username, email, password, role, created_at, updated_at) VALUES (?, ?, ?, ?, NOW(), NOW())";
$stmt = $conn->prepare($sql);
$stmt->bind_param('ssss', $username, $email, $password, $role);
if ($stmt->execute()) {
$_SESSION['message'] = "用户添加成功";
header('Location: users.php');
exit;
} else {
$error = "用户添加失败";
}
}
break;
case 'edit':
// 编辑用户
if (isset($_POST['submit'])) {
$username = $_POST['username'];
$email = $_POST['email'];
$role = $_POST['role'];
$password = $_POST['password'] ? password_hash($_POST['password'], PASSWORD_DEFAULT) : '';
if ($password) {
$sql = "UPDATE users SET username = ?, email = ?, password = ?, role = ?, updated_at = NOW() WHERE id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param('ssssi', $username, $email, $password, $role, $id);
} else {
$sql = "UPDATE users SET username = ?, email = ?, role = ?, updated_at = NOW() WHERE id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param('sssi', $username, $email, $role, $id);
}
if ($stmt->execute()) {
$_SESSION['message'] = "用户更新成功";
header('Location: users.php');
exit;
} else {
$error = "用户更新失败";
}
}
// 获取用户信息
$sql = "SELECT * FROM users WHERE id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param('i', $id);
$stmt->execute();
$user = $stmt->get_result()->fetch_assoc();
break;
case 'delete':
// 删除用户
$sql = "DELETE FROM users WHERE id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param('i', $id);
if ($stmt->execute()) {
$_SESSION['message'] = "用户删除成功";
} else {
$_SESSION['message'] = "用户删除失败";
}
header('Location: users.php');
exit;
break;
}
}
// 获取所有用户
$sql = "SELECT * FROM users ORDER BY created_at DESC";
$result = $conn->query($sql);
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>用户管理 - CMS 管理系统</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css">
</head>
<body>
<?php include '../includes/header.php'; ?>
<div class="container-fluid">
<div class="row">
<!-- 侧边栏 -->
<nav class="col-md-3 col-lg-2 d-md-block bg-light sidebar">
<div class="position-sticky pt-3">
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link" href="dashboard.php">
<i class="fa fa-dashboard"></i> 仪表盘
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="articles.php">
<i class="fa fa-file-text"></i> 文章管理
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="categories.php">
<i class="fa fa-list"></i> 分类管理
</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="users.php">
<i class="fa fa-users"></i> 用户管理
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="settings.php">
<i class="fa fa-cog"></i> 系统设置
</a>
</li>
</ul>
</div>
</nav>
<!-- 主内容 -->
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">用户管理</h1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group me-2">
<a href="users.php?action=add" class="btn btn-sm btn-outline-secondary">
<i class="fa fa-plus"></i> 新建用户
</a>
</div>
</div>
</div>
<!-- 消息提示 -->
<?php if (isset($_SESSION['message'])): ?>
<div class="alert alert-success" role="alert">
<?php echo $_SESSION['message']; unset($_SESSION['message']); ?>
</div>
<?php endif; ?>
<!-- 错误提示 -->
<?php if (isset($error)): ?>
<div class="alert alert-danger" role="alert">
<?php echo $error; ?>
</div>
<?php endif; ?>
<!-- 表单 -->
<?php if (isset($action) && ($action == 'add' || $action == 'edit')): ?>
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title"><?php echo $action == 'add' ? '新建用户' : '编辑用户'; ?></h5>
</div>
<div class="card-body">
<form method="POST">
<div class="mb-3">
<label for="username" class="form-label">用户名</label>
<input type="text" class="form-control" id="username" name="username" value="<?php echo isset($user) ? $user['username'] : ''; ?>" required>
</div>
<div class="mb-3">
<label for="email" class="form-label">邮箱</label>
<input type="email" class="form-control" id="email" name="email" value="<?php echo isset($user) ? $user['email'] : ''; ?>" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">密码 <?php echo $action == 'edit' ? '(留空表示不修改)' : ''; ?></label>
<input type="password" class="form-control" id="password" name="password" <?php echo $action == 'add' ? 'required' : ''; ?>>
</div>
<div class="mb-3">
<label for="role" class="form-label">角色</label>
<select class="form-select" id="role" name="role" required>
<option value="admin" <?php echo isset($user) && $user['role'] == 'admin' ? 'selected' : ''; ?>>管理员</option>
<option value="editor" <?php echo isset($user) && $user['role'] == 'editor' ? 'selected' : ''; ?>>编辑</option>
</select>
</div>
<button type="submit" name="submit" class="btn btn-primary">保存</button>
<a href="users.php" class="btn btn-secondary">取消</a>
</form>
</div>
</div>
<?php endif; ?>
<!-- 用户列表 -->
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th>ID</th>
<th>用户名</th>
<th>邮箱</th>
<th>角色</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php while ($row = $result->fetch_assoc()): ?>
<tr>
<td><?php echo $row['id']; ?></td>
<td><?php echo $row['username']; ?></td>
<td><?php echo $row['email']; ?></td>
<td>
<span class="badge <?php echo $row['role'] == 'admin' ? 'bg-danger' : 'bg-info'; ?>">
<?php echo $row['role'] == 'admin' ? '管理员' : '编辑'; ?>
</span>
</td>
<td><?php echo $row['created_at']; ?></td>
<td>
<a href="users.php?action=edit&id=<?php echo $row['id']; ?>" class="btn btn-sm btn-primary">编辑</a>
<a href="users.php?action=delete&id=<?php echo $row['id']; ?>" class="btn btn-sm btn-danger" onclick="return confirm('确定要删除吗?');">删除</a>
</td>
</tr>
<?php endwhile; ?>
</tbody>
</table>
</div>
</main>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>6. 系统设置
php
// admin/settings.php
<?php
session_start();
include '../includes/config.php';
include '../includes/db.php';
// 检查登录状态
if (!isset($_SESSION['user_id'])) {
header('Location: ../login.php');
exit;
}
// 处理设置保存
if (isset($_POST['save'])) {
$site_name = $_POST['site_name'];
$site_description = $_POST['site_description'];
$admin_email = $_POST['admin_email'];
// 这里可以将设置保存到数据库或配置文件
// 为了简化,我们直接显示保存成功的消息
$_SESSION['message'] = "设置保存成功";
header('Location: settings.php');
exit;
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>系统设置 - CMS 管理系统</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css">
</head>
<body>
<?php include '../includes/header.php'; ?>
<div class="container-fluid">
<div class="row">
<!-- 侧边栏 -->
<nav class="col-md-3 col-lg-2 d-md-block bg-light sidebar">
<div class="position-sticky pt-3">
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link" href="dashboard.php">
<i class="fa fa-dashboard"></i> 仪表盘
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="articles.php">
<i class="fa fa-file-text"></i> 文章管理
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="categories.php">
<i class="fa fa-list"></i> 分类管理
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="users.php">
<i class="fa fa-users"></i> 用户管理
</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="settings.php">
<i class="fa fa-cog"></i> 系统设置
</a>
</li>
</ul>
</div>
</nav>
<!-- 主内容 -->
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">系统设置</h1>
</div>
<!-- 消息提示 -->
<?php if (isset($_SESSION['message'])): ?>
<div class="alert alert-success" role="alert">
<?php echo $_SESSION['message']; unset($_SESSION['message']); ?>
</div>
<?php endif; ?>
<!-- 设置表单 -->
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title">基本设置</h5>
</div>
<div class="card-body">
<form method="POST">
<div class="mb-3">
<label for="site_name" class="form-label">网站名称</label>
<input type="text" class="form-control" id="site_name" name="site_name" value="CMS 管理系统" required>
</div>
<div class="mb-3">
<label for="site_description" class="form-label">网站描述</label>
<textarea class="form-control" id="site_description" name="site_description" rows="3">一个简单的内容管理系统</textarea>
</div>
<div class="mb-3">
<label for="admin_email" class="form-label">管理员邮箱</label>
<input type="email" class="form-control" id="admin_email" name="admin_email" value="admin@example.com" required>
</div>
<button type="submit" name="save" class="btn btn-primary">保存设置</button>
</form>
</div>
</div>
<!-- 系统信息 -->
<div class="card">
<div class="card-header">
<h5 class="card-title">系统信息</h5>
</div>
<div class="card-body">
<table class="table table-striped">
<tbody>
<tr>
<td>PHP 版本</td>
<td><?php echo phpversion(); ?></td>
</tr>
<tr>
<td>MySQL 版本</td>
<td><?php echo $conn->server_info; ?></td>
</tr>
<tr>
<td>服务器操作系统</td>
<td><?php echo PHP_OS; ?></td>
</tr>
<tr>
<td>服务器软件</td>
<td><?php echo $_SERVER['SERVER_SOFTWARE']; ?></td>
</tr>
<tr>
<td>系统时间</td>
<td><?php echo date('Y-m-d H:i:s'); ?></td>
</tr>
</tbody>
</table>
</div>
</div>
</main>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>7. 退出登录
php
// logout.php
<?php
session_start();
session_destroy();
header('Location: login.php');
exit;
?>8. 头部模板
php
// includes/header.php
<nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4">
<div class="container-fluid">
<a class="navbar-brand" href="dashboard.php">CMS 管理系统</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav me-auto mb-2 mb-md-0">
<li class="nav-item">
<a class="nav-link" href="../index.php" target="_blank">前台</a>
</li>
</ul>
<div class="d-flex">
<a class="nav-link text-white me-3" href="profile.php">
<?php echo $_SESSION['username']; ?>
</a>
<a class="btn btn-outline-light" href="../logout.php">退出</a>
</div>
</div>
</div>
</nav>项目部署
- 创建数据库:使用 phpMyAdmin 或 MySQL 命令行创建数据库,并导入以下 SQL 语句:
sql
-- 创建数据库
CREATE DATABASE IF NOT EXISTS cms;
USE cms;
-- 创建用户表
CREATE TABLE IF NOT EXISTS users (
id INT(11) AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
role ENUM('admin', 'editor') DEFAULT 'editor',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 创建分类表
CREATE TABLE IF NOT EXISTS categories (
id INT(11) AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE,
slug VARCHAR(50) NOT NULL UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 创建文章表
CREATE TABLE IF NOT EXISTS articles (
id INT(11) AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
slug VARCHAR(255) NOT NULL UNIQUE,
content TEXT NOT NULL,
category_id INT(11) NOT NULL,
user_id INT(11) NOT NULL,
status ENUM('published', 'draft') DEFAULT 'draft',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
-- 插入默认管理员用户
INSERT INTO users (username, password, email, role) VALUES
('admin', '$2y$10$e0pXbN1z9z9z9z9z9z9z9e9z9z9z9z9z9z9z9z9z9z9z9z9z9z', 'admin@example.com', 'admin');
-- 插入默认分类
INSERT INTO categories (name, slug) VALUES
('技术', 'tech'),
('生活', 'life'),
('工作', 'work');- 配置数据库连接:修改
includes/config.php文件,设置数据库连接信息:
php
// includes/config.php
<?php
// 数据库配置
define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASS', '');
define('DB_NAME', 'cms');
// 网站配置
define('SITE_URL', 'http://localhost/cms');
?>- 配置数据库连接文件:创建
includes/db.php文件:
php
// includes/db.php
<?php
include 'config.php';
// 连接数据库
$conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
// 检查连接
if ($conn->connect_error) {
die("连接失败: " . $conn->connect_error);
}
// 设置字符集
$conn->set_charset('utf8mb4');
?>创建上传目录:在
public目录下创建uploads目录,并设置可写权限。访问系统:在浏览器中访问
http://localhost/cms/login.php,使用默认账号登录:- 用户名:admin
- 密码:admin123
项目扩展
添加图片上传功能:在文章编辑页面添加图片上传功能,支持图片插入到文章内容中。
添加评论系统:为文章添加评论功能,支持用户发表评论。
添加标签功能:为文章添加标签,方便分类和搜索。
添加 SEO 优化:为文章添加 meta 标签、关键词等 SEO 相关功能。
添加主题切换:支持后台主题切换,提高用户体验。
添加权限管理:细化用户权限,实现更精细的权限控制。
总结
通过本实战项目,你已经学习了如何构建一个完整的后台管理系统(CMS),包括:
- 用户认证与权限管理
- 文章管理(增删改查)
- 分类管理
- 系统设置
- 数据库设计与操作
- 前端界面设计
这个项目涵盖了 CMS 系统的核心功能,是一个非常好的 PHP 实战练习。你可以根据自己的需求对项目进行扩展和优化,进一步提升你的 PHP 开发技能。
