本文目录导读:

- 第一步:数据库设计
- 第二步:目录结构
- 第三步:数据库连接 (config/database.php)
- 第四步:课程列表页 (admin/courses/index.php)
- 第五步:添加课程 (admin/courses/create.php + store.php)
- 第六步:编辑课程 (admin/courses/edit.php + update.php)
- 第七步:删除课程 (admin/courses/delete.php)
- 第八步:前台展示(访客/学生)
- 进阶优化建议(生产环境必备)
- 总结一下核心流程
实现一个PHP课程管理系统,通常需要涵盖增、删、改、查(CRUD) 以及权限管理和关联表设计,下面给你一个从数据库设计到代码实现的完整思路和示例。
我会采用 PHP + MySQL + PDO(安全、面向对象) 的方式演示,并假设你有一个最基本的用户/管理员登录机制。
第一步:数据库设计
你需要至少两个核心表:courses(课程表)和 categories(课程分类表),如果涉及到教师和选课,还需扩展。
-- 课程分类表
CREATE TABLE `categories` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(100) NOT NULL,
`description` TEXT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 课程表
CREATE TABLE `courses` (
`id` INT AUTO_INCREMENT PRIMARY KEY, VARCHAR(200) NOT NULL COMMENT '课程标题',
`category_id` INT DEFAULT NULL COMMENT '分类ID',
`teacher_name` VARCHAR(100) NOT NULL COMMENT '授课教师',
`price` DECIMAL(10,2) DEFAULT 0.00 COMMENT '价格',
`start_date` DATE DEFAULT NULL COMMENT '开课日期',
`duration` INT DEFAULT 0 COMMENT '总课时(小时)',
`description` TEXT COMMENT '课程简介',
`cover_image` VARCHAR(255) DEFAULT NULL COMMENT '封面图',
`status` TINYINT DEFAULT 1 COMMENT '状态: 1启用 0禁用',
`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 SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
第二步:目录结构
/project
/config
database.php # 数据库配置
/includes
functions.php # 公共函数
/admin # 管理后台
courses/
create.php # 添加课程页面
store.php # 保存新增课程
edit.php # 编辑课程页面
update.php # 更新课程
delete.php # 删除课程
index.php # 课程列表
categories/ # (类似结构)
index.php # 前台展示(学生/访客)
第三步:数据库连接 (config/database.php)
<?php
$dsn = 'mysql:host=localhost;dbname=your_db;charset=utf8mb4';
$username = 'root';
$password = '';
try {
$pdo = new PDO($dsn, $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
} catch (PDOException $e) {
die('数据库连接失败: ' . $e->getMessage());
}
?>
第四步:课程列表页 (admin/courses/index.php)
<?php
require_once '../../config/database.php';
$search = $_GET['search'] ?? '';
$category_id = $_GET['category_id'] ?? '';
// 构建查询
$sql = "SELECT c.*, cat.name AS category_name
FROM courses c
LEFT JOIN categories cat ON c.category_id = cat.id
WHERE 1=1";
$params = [];
if (!empty($search)) {
$sql .= " AND c.title LIKE :search";
$params[':search'] = "%$search%";
}
if (!empty($category_id)) {
$sql .= " AND c.category_id = :category_id";
$params[':category_id'] = $category_id;
}
$sql .= " ORDER BY c.created_at DESC";
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$courses = $stmt->fetchAll();
// 获取分类列表(用于筛选)
$categories = $pdo->query("SELECT * FROM categories")->fetchAll();
?>
<!DOCTYPE html>
<html>
<head>课程管理</title>
</head>
<body>
<h1>课程列表</h1>
<form method="GET">
<input type="text" name="search" placeholder="搜索课程" value="<?= htmlspecialchars($search) ?>">
<select name="category_id">
<option value="">全部分类</option>
<?php foreach ($categories as $cat): ?>
<option value="<?= $cat['id'] ?>" <?= ($category_id == $cat['id']) ? 'selected' : '' ?>>
<?= htmlspecialchars($cat['name']) ?>
</option>
<?php endforeach; ?>
</select>
<button type="submit">搜索</button>
</form>
<a href="create.php">添加课程</a>
<table border="1" cellpadding="8">
<tr>
<th>ID</th>
<th>标题</th>
<th>分类</th>
<th>教师</th>
<th>价格</th>
<th>状态</th>
<th>操作</th>
</tr>
<?php foreach ($courses as $course): ?>
<tr>
<td><?= $course['id'] ?></td>
<td><?= htmlspecialchars($course['title']) ?></td>
<td><?= htmlspecialchars($course['category_name'] ?? '未分类') ?></td>
<td><?= htmlspecialchars($course['teacher_name']) ?></td>
<td>¥<?= number_format($course['price'], 2) ?></td>
<td><?= $course['status'] ? '启用' : '禁用' ?></td>
<td>
<a href="edit.php?id=<?= $course['id'] ?>">编辑</a>
<a href="delete.php?id=<?= $course['id'] ?>" onclick="return confirm('确定删除?')">删除</a>
</td>
</tr>
<?php endforeach; ?>
</table>
</body>
</html>
第五步:添加课程 (admin/courses/create.php + store.php)
create.php (表单页面)
<?php
require_once '../../config/database.php';
$categories = $pdo->query("SELECT * FROM categories")->fetchAll();
?>
<form action="store.php" method="POST" enctype="multipart/form-data">
<label>课程标题: <input type="text" name="title" required></label><br>
<label>分类:
<select name="category_id">
<option value="">无</option>
<?php foreach ($categories as $cat): ?>
<option value="<?= $cat['id'] ?>"><?= htmlspecialchars($cat['name']) ?></option>
<?php endforeach; ?>
</select>
</label><br>
<label>教师: <input type="text" name="teacher_name" required></label><br>
<label>价格: <input type="number" step="0.01" name="price"></label><br>
<label>开课日期: <input type="date" name="start_date"></label><br>
<label>课时(小时): <input type="number" name="duration"></label><br>
<label>简介: <textarea name="description"></textarea></label><br>
<label>封面: <input type="file" name="cover_image"></label><br>
<label>状态:
<select name="status">
<option value="1">启用</option>
<option value="0">禁用</option>
</select>
</label><br>
<button type="submit">保存</button>
</form>
store.php (处理新增)
<?php
require_once '../../config/database.php';
// 简单后端校验
if (empty($_POST['title']) || empty($_POST['teacher_name'])) {
die('标题和教师不能为空');
}
$sql = "INSERT INTO courses (title, category_id, teacher_name, price, start_date, duration, description, status, cover_image)
VALUES (:title, :category_id, :teacher_name, :price, :start_date, :duration, :description, :status, :cover_image)";
$stmt = $pdo->prepare($sql);
// 处理封面上传(示例简化,生产环境需严格验证文件类型)
$cover_image = '';
if (isset($_FILES['cover_image']) && $_FILES['cover_image']['error'] === UPLOAD_ERR_OK) {
$upload_dir = 'uploads/covers/';
if (!is_dir($upload_dir)) mkdir($upload_dir, 0777, true);
$ext = pathinfo($_FILES['cover_image']['name'], PATHINFO_EXTENSION);
$filename = uniqid() . '.' . $ext;
move_uploaded_file($_FILES['cover_image']['tmp_name'], $upload_dir . $filename);
$cover_image = $upload_dir . $filename;
}
$stmt->execute([
':title' => $_POST['title'],
':category_id' => $_POST['category_id'] ?: null,
':teacher_name' => $_POST['teacher_name'],
':price' => $_POST['price'] ?? 0,
':start_date' => $_POST['start_date'] ?: null,
':duration' => $_POST['duration'] ?? 0,
':description' => $_POST['description'] ?? '',
':status' => $_POST['status'] ?? 1,
':cover_image' => $cover_image,
]);
header('Location: index.php');
exit;
第六步:编辑课程 (admin/courses/edit.php + update.php)
edit.php 核心代码(读取现有数据,回填表单)
<?php
require_once '../../config/database.php';
$id = $_GET['id'] ?? 0;
$stmt = $pdo->prepare("SELECT * FROM courses WHERE id = :id");
$stmt->execute([':id' => $id]);
$course = $stmt->fetch();
if (!$course) {
die('课程不存在');
}
// 继续输出表单,和create.php类似,但 value 从 $course 中读取
?>
update.php 逻辑与 store.php 类似,但使用 UPDATE 语句,并且记得判断是否有新上传的封面。
第七步:删除课程 (admin/courses/delete.php)
<?php
require_once '../../config/database.php';
$id = $_GET['id'] ?? 0;
// 可选:先删除封面图片文件
$stmt = $pdo->prepare("SELECT cover_image FROM courses WHERE id = :id");
$stmt->execute([':id' => $id]);
$course = $stmt->fetch();
if ($course && $course['cover_image'] && file_exists($course['cover_image'])) {
unlink($course['cover_image']);
}
// 执行删除
$stmt = $pdo->prepare("DELETE FROM courses WHERE id = :id");
$stmt->execute([':id' => $id]);
header('Location: index.php');
exit;
第八步:前台展示(访客/学生)
通常前台只需要只读查询:
<?php
require_once 'config/database.php';
// 只显示启用状态的课程
$stmt = $pdo->query("SELECT * FROM courses WHERE status = 1 ORDER BY created_at DESC");
$courses = $stmt->fetchAll();
?>
<!-- 用卡片或列表循环显示课程 -->
进阶优化建议(生产环境必备)
-
安全性
- 使用 预处理语句 (PDO) 防止 SQL 注入。
- 上传文件需严格检查 MIME 类型、限制大小、重命名。
- 对用户输入进行
htmlspecialchars()输出转义,防止 XSS。
-
用户体验
- 分页显示:
LIMIT 10 OFFSET ?+ 获取总条数。 - 批量操作:勾选多个课程,批量删除或修改状态。
- 分页显示:
-
关联数据
- 增加
teachers表,外键关联。 - 增加
students和enrollments实现学生选课。
- 增加
-
使用框架
- 是原生 PHP 实现,实际项目中推荐使用 Laravel、ThinkPHP、Symfony 等框架,它们内置 ORM、表单验证、文件存储等,开发效率高得多。
-
RESTful API
- 如果想做前后端分离,把相同的逻辑写成 JSON API (
/api/courses),前端用 Vue/React 调用。
- 如果想做前后端分离,把相同的逻辑写成 JSON API (
总结一下核心流程
| 功能 | 关键步骤 |
|---|---|
| 查 | SELECT + 搜索/筛选 + 分页 |
| 增 | 表单 → POST → INSERT |
| 改 | 读取回填 → POST → UPDATE |
| 删 | DELETE 语句 + 清理关联资源 |
按照这个结构,你可以轻松扩展到学生选课、课程章节管理、视频上传等更复杂的功能,如果还有具体的细节问题(比如多表关联、权限控制),欢迎继续追问。