项目概述
这是一个基于 PHP 和 MySQL 的简单教程网站,它包含以下核心功能:

(图片来源网络,侵删)
- 后台管理:管理员可以登录后台,管理教程分类、发布/编辑/删除教程文章。
- 前台展示:访客可以浏览教程列表、查看教程详情、按分类筛选。
- 用户界面:包含一个简单的后台管理界面和响应式的前台展示界面。
技术栈
- 后端语言: PHP 7.4+
- 数据库: MySQL 5.7+
- 前端技术: HTML, CSS, Bootstrap 5 (用于快速构建美观的响应式界面)
- 服务器: Apache 或 Nginx
项目结构
tutorial-website/
├── admin/ # 后台管理目录
│ ├── index.php # 后台登录页面
│ ├── dashboard.php # 后台仪表盘
│ ├── categories/ # 分类管理
│ │ └── manage.php
│ ├── tutorials/ # 教程管理
│ │ ├── list.php # 教程列表
│ │ ├── add.php # 添加教程
│ │ └── edit.php # 编辑教程
│ └── logout.php # 退出登录
├── assets/ # 静态资源目录
│ ├── css/
│ │ └── style.css # 自定义样式
│ └── js/
│ └── main.js # 自定义脚本
├── config.php # 数据库配置文件
├── database.sql # 数据库初始化脚本
├── functions.php # 公共函数库
├── header.php # 公共头部
├── footer.php # 公共底部
├── index.php # 网站首页(展示教程列表)
├── tutorial.php # 教程详情页
└── category.php # 分类页
数据库设计 (database.sql)
您需要创建一个数据库,然后执行以下 SQL 脚本来创建必要的表。
-- 创建数据库 (如果不存在) CREATE DATABASE IF NOT EXISTS tutorial_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 使用数据库 USE tutorial_db; -- 创建分类表 CREATE TABLE `categories` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL, `slug` varchar(100) NOT NULL, `created_at` timestamp NOT NULL DEFAULT current_timestamp(), PRIMARY KEY (`id`), UNIQUE KEY `slug` (`slug`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 创建教程表 CREATE TABLE `tutorials` ( `id` int(11) NOT NULL AUTO_INCREMENT, varchar(255) NOT NULL, `slug` varchar(255) NOT NULL, `content` text NOT NULL, `category_id` int(11) NOT NULL, `author` varchar(100) NOT NULL, `created_at` timestamp NOT NULL DEFAULT current_timestamp(), `updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), PRIMARY KEY (`id`), UNIQUE KEY `slug` (`slug`), KEY `category_id` (`category_id`), CONSTRAINT `tutorials_ibfk_1` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 插入一些示例数据 INSERT INTO `categories` (`id`, `name`, `slug`) VALUES (1, 'PHP编程', 'php-programming'), (2, '前端开发', 'frontend-development'), (3, '数据库', 'database'); INSERT INTO `tutorials` (`id`, `title`, `slug`, `content`, `category_id`, `author`) VALUES (1, 'PHP入门教程:Hello World', 'php-hello-world', '<p>欢迎来到PHP的世界!</p><p>让我们从最经典的 "Hello, World!" 程序开始。</p><pre><code><?php<br>echo "Hello, World!";<br>?></code></pre>', 1, '管理员'), (2, 'HTML与CSS基础', 'html-css-basics', '<p>HTML用于构建网页的结构,CSS用于美化网页的样式。</p><p>这是一个段落。</p><p>这是另一个段落。</p>', 2, '管理员'), (3, 'MySQL基础查询', 'mysql-basic-query', '<p>MySQL是一种关系型数据库管理系统。</p><p>最常用的查询语句是 SELECT:</p><pre><code>SELECT * FROM users;</code></pre>', 3, '管理员');
核心文件源码
config.php - 数据库配置
<?php
// 数据库连接配置
define('DB_HOST', 'localhost');
define('DB_USER', 'root'); // 您的数据库用户名
define('DB_PASS', ''); // 您的数据库密码
define('DB_NAME', 'tutorial_db'); // 您的数据库名
// 连接数据库
$conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
// 检查连接
if ($conn->connect_error) {
die("数据库连接失败: " . $conn->connect_error);
}
// 设置字符集
$conn->set_charset("utf8mb4");
// 后台登录信息 (实际项目中应使用更安全的方式,如 session 和 hashed password)
define('ADMIN_USERNAME', 'admin');
define('ADMIN_PASSWORD', 'password123');
?>
functions.php - 公共函数
<?php
// 引入配置文件
require_once 'config.php';
// 获取所有分类
function getCategories() {
global $conn;
$sql = "SELECT * FROM categories ORDER BY name ASC";
$result = $conn->query($sql);
return $result->fetch_all(MYSQLI_ASSOC);
}
// 根据 ID 获取分类
function getCategoryById($id) {
global $conn;
$stmt = $conn->prepare("SELECT * FROM categories WHERE id = ?");
$stmt->bind_param("i", $id);
$stmt->execute();
$result = $stmt->get_result();
return $result->fetch_assoc();
}
// 获取所有教程
function getTutorials($category_id = null, $limit = 10, $offset = 0) {
global $conn;
$sql = "SELECT t.*, c.name as category_name
FROM tutorials t
LEFT JOIN categories c ON t.category_id = c.id";
$params = [];
$types = "";
if ($category_id) {
$sql .= " WHERE t.category_id = ?";
$params[] = $category_id;
$types .= "i";
}
$sql .= " ORDER BY t.created_at DESC LIMIT ? OFFSET ?";
$params[] = $limit;
$params[] = $offset;
$types .= "ii";
$stmt = $conn->prepare($sql);
$stmt->bind_param($types, ...$params);
$stmt->execute();
$result = $stmt->get_result();
return $result->fetch_all(MYSQLI_ASSOC);
}
// 根据 slug 获取单个教程
function getTutorialBySlug($slug) {
global $conn;
$stmt = $conn->prepare("SELECT t.*, c.name as category_name FROM tutorials t LEFT JOIN categories c ON t.category_id = c.id WHERE t.slug = ?");
$stmt->bind_param("s", $slug);
$stmt->execute();
$result = $stmt->get_result();
return $result->fetch_assoc();
}
// 生成 URL 友好的 slug
function createSlug($string) {
$string = strtolower($string);
$string = preg_replace("/[^a-z0-9\s-]/", "", $string);
$string = preg_replace("/[\s-]+/", " ", $string);
$string = trim($string);
$string = str_replace(" ", "-", $string);
return $string;
}
?>
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>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="index.php">我的教程网</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="index.php">首页</a>
</li>
<?php
$categories = getCategories();
foreach ($categories as $category) {
echo "<li class='nav-item'><a class='nav-link' href='category.php?id={$category['id']}'>{$category['name']}</a></li>";
}
?>
</ul>
</div>
</div>
</nav>
<div class="container mt-4">
footer.php - 公共底部
</div> <!-- .container -->
<!-- Bootstrap 5 JS Bundle with Popper -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js"></script>
</body>
</html>
index.php - 首页(教程列表)
<?php
require_once 'header.php';
require_once 'functions.php';
$tutorials = getTutorials();
?>
<h1 class="mb-4">最新教程</h1>
<div class="row">
<?php if (count($tutorials) > 0): ?>
<?php foreach ($tutorials as $tutorial): ?>
<div class="col-md-6 col-lg-4 mb-4">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">
<a href="tutorial.php?slug=<?php echo htmlspecialchars($tutorial['slug']); ?>" class="text-decoration-none">
<?php echo htmlspecialchars($tutorial['title']); ?>
</a>
</h5>
<p class="card-text text-muted small">
分类: <a href="category.php?id=<?php echo $tutorial['category_id']; ?>"><?php echo htmlspecialchars($tutorial['category_name']); ?></a>
</p>
<p class="card-text">
<?php echo substr(strip_tags($tutorial['content']), 0, 150) . '...'; ?>
</p>
<a href="tutorial.php?slug=<?php echo htmlspecialchars($tutorial['slug']); ?>" class="btn btn-primary btn-sm">阅读更多</a>
</div>
<div class="card-footer text-muted small">
作者: <?php echo htmlspecialchars($tutorial['author']); ?> | 发布于: <?php echo date('Y-m-d', strtotime($tutorial['created_at'])); ?>
</div>
</div>
</div>
<?php endforeach; ?>
<?php else: ?>
<div class="col-12">
<p class="text-center">暂无教程。</p>
</div>
<?php endif; ?>
</div>
<?php require_once 'footer.php'; ?>
tutorial.php - 教程详情页
<?php
require_once 'header.php';
require_once 'functions.php';
$slug = $_GET['slug'] ?? '';
if (empty($slug)) {
echo "<div class='alert alert-danger'>未指定教程。</div>";
require_once 'footer.php';
exit();
}
$tutorial = getTutorialBySlug($slug);
if (!$tutorial) {
echo "<div class='alert alert-danger'>找不到该教程。</div>";
require_once 'footer.php';
exit();
}
?>
<div class="row">
<div class="col-lg-8">
<article>
<h1><?php echo htmlspecialchars($tutorial['title']); ?></h1>
<p class="text-muted">
分类: <a href="category.php?id=<?php echo $tutorial['category_id']; ?>"><?php echo htmlspecialchars($tutorial['category_name']); ?></a> |
作者: <?php echo htmlspecialchars($tutorial['author']); ?> |
发布于: <?php echo date('Y-m-d H:i', strtotime($tutorial['created_at'])); ?>
</p>
<hr>
<div class="content">
<?php echo $tutorial['content']; ?>
</div>
</article>
</div>
<div class="col-lg-4">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">所有分类</h5>
</div>
<div class="card-body">
<ul class="list-group list-group-flush">
<?php
$categories = getCategories();
foreach ($categories as $category) {
$active = ($category['id'] == $tutorial['category_id']) ? 'active' : '';
echo "<li class='list-group-item {$active} d-flex justify-content-between align-items-center'>";
echo "<a href='category.php?id={$category['id']}'>{$category['name']}</a>";
echo "<span class='badge bg-secondary rounded-pill'>" . getTutorials($category['id'], 1, 0)[0]['total_tutorials'] ?? '0' . "</span>"; // 注意:这里需要修改函数以获取总数
echo "</li>";
}
?>
</ul>
</div>
</div>
</div>
</div>
<?php require_once 'footer.php'; ?>
admin/index.php - 后台登录页
<?php
session_start();
if (isset($_SESSION['admin_logged_in']) && $_SESSION['admin_logged_in'] === true) {
header('Location: dashboard.php');
exit;
}
$error = '';
if ($_SERVER["REQUEST_METHOD"] == "POST") {
if (isset($_POST['username']) && isset($_POST['password'])) {
if ($_POST['username'] === ADMIN_USERNAME && $_POST['password'] === ADMIN_PASSWORD) {
$_SESSION['admin_logged_in'] = true;
header('Location: dashboard.php');
exit;
} 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>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body { background-color: #f8f9fa; }
.login-form { max-width: 400px; margin: 50px auto; padding: 20px; border-radius: 10px; box-shadow: 0 0 20px rgba(0,0,0,0.1); }
</style>
</head>
<body>
<div class="container">
<div class="login-form">
<h2 class="text-center mb-4">后台管理系统</h2>
<?php if (!empty($error)): ?>
<div class="alert alert-danger"><?php echo $error; ?></div>
<?php endif; ?>
<form action="index.php" 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" class="btn btn-primary w-100">登录</button>
</form>
</div>
</div>
</body>
</html>
admin/dashboard.php - 后台仪表盘
<?php
session_start();
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
header('Location: index.php');
exit;
}
require_once '../header.php'; // 注意路径
?>
<h1>仪表盘</h1>
<p>欢迎回来, 管理员!</p>
<hr>
<div class="row">
<div class="col-md-4">
<div class="card text-white bg-primary mb-3">
<div class="card-header">教程管理</div>
<div class="card-body">
<a href="tutorials/list.php" class="btn btn-light w-100">管理教程</a>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card text-white bg-success mb-3">
<div class="card-header">分类管理</div>
<div class="card-body">
<a href="categories/manage.php" class="btn btn-light w-100">管理分类</a>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card text-white bg-danger mb-3">
<div class="card-header">退出登录</div>
<div class="card-body">
<a href="logout.php" class="btn btn-light w-100">安全退出</a>
</div>
</div>
</div>
</div>
<?php require_once '../footer.php'; ?>
如何部署和使用
- 环境准备: 确保您的电脑上已经安装了 PHP、MySQL 和 Apache/Nginx。
- 创建数据库: 使用 MySQL 管理工具(如 phpMyAdmin)创建一个数据库,并将
database.sql文件中的 SQL 语句导入。 - 修改配置: 打开
config.php文件,填入您的数据库用户名、密码和数据库名。 - 放置文件: 将整个
tutorial-website文件夹放置到您的 Web 服务器根目录(Apache 的htdocs或 Nginx 的html目录)。 - 访问网站:
- 前台: 在浏览器中访问
http://localhost/tutorial-website/。 - 后台: 在浏览器中访问
http://localhost/tutorial-website/admin/,默认用户名和密码在config.php中定义(admin/password123)。
- 前台: 在浏览器中访问
- 扩展功能: 您可以基于这个框架继续开发,
- 实现真正的用户注册和登录系统。
- 使用 PDO 代替 MySQLi 以获得更好的数据库抽象。
- 将密码哈希存储(如使用
password_hash)。 - 添加评论系统。
- 实现富文本编辑器(如 TinyMCE)来编写教程内容。
重要提示与安全改进
这份源码是一个教学示例,为了简洁,省略了许多生产环境中至关重要的安全措施,在实际项目中,请务必注意:
- SQL 注入: 虽然在
functions.php中使用了prepare和bind_param来防止,但所有接收用户输入的地方都需要进行严格的验证和过滤。 - 密码安全: 后台密码是明文存储的,绝对不能用于生产环境,请使用 PHP 内置的
password_hash()和password_verify()函数来处理密码。 - Session 管理: 后台的 session 管理很简单,可以增加超时机制和更安全的 session 配置。
- 文件上传: 如果允许上传图片,需要严格校验文件类型、大小,并存储在非 Web 可直接访问的目录。
- XSS 防护: 在
echo输出用户内容时,使用了htmlspecialchars,这是防止 XSS 攻击的基本做法。
希望这份源码能对您有所帮助!

(图片来源网络,侵删)
