本文目录导读:

- 第一步:数据库设计
- 第二步:目录结构
- 第三步:数据库连接 (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 语句 + 清理关联资源 |
按照这个结构,你可以轻松扩展到学生选课、课程章节管理、视频上传等更复杂的功能,如果还有具体的细节问题(比如多表关联、权限控制),欢迎继续追问。
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播