本文目录导读:

- 数据库设计
- 频道模型类 (Channel.php)
- 控制器 (ChannelController.php)
- 视图文件
- 路由配置 (routes.php)
- 使用示例 - API接口 (api/channels.php)
- 扩展功能 - 缓存优化
我将为您提供一个完整的PHP频道管理实现方案,包含数据库设计、增删改查等核心功能。
数据库设计
CREATE TABLE `channels` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL COMMENT '频道名称', `slug` varchar(100) NOT NULL COMMENT 'URL别名', `description` text COMMENT '频道描述', `icon` varchar(255) DEFAULT NULL COMMENT '图标路径', `sort_order` int(11) DEFAULT 0 COMMENT '排序', `status` tinyint(1) DEFAULT 1 COMMENT '状态:1启用 0禁用', `parent_id` int(11) DEFAULT NULL COMMENT '父频道ID', `created_at` timestamp DEFAULT CURRENT_TIMESTAMP, `updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `slug` (`slug`), KEY `parent_id` (`parent_id`), FOREIGN KEY (`parent_id`) REFERENCES `channels`(`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
频道模型类 (Channel.php)
<?php
namespace App\Models;
use PDO;
use PDOException;
class Channel {
private $db;
public function __construct(PDO $db) {
$this->db = $db;
}
// 获取所有频道
public function getAll($status = null) {
$sql = "SELECT c.*,
(SELECT COUNT(*) FROM channels WHERE parent_id = c.id) as child_count
FROM channels c";
if ($status !== null) {
$sql .= " WHERE c.status = :status";
}
$sql .= " ORDER BY c.parent_id IS NULL DESC, c.sort_order ASC";
$stmt = $this->db->prepare($sql);
if ($status !== null) {
$stmt->bindParam(':status', $status, PDO::PARAM_INT);
}
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// 获取单条频道
public function getById($id) {
$stmt = $this->db->prepare("SELECT * FROM channels WHERE id = :id");
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetch(PDO::FETCH_ASSOC);
}
// 创建频道
public function create($data) {
$sql = "INSERT INTO channels (name, slug, description, icon, sort_order, status, parent_id)
VALUES (:name, :slug, :description, :icon, :sort_order, :status, :parent_id)";
$stmt = $this->db->prepare($sql);
$stmt->bindParam(':name', $data['name'], PDO::PARAM_STR);
$stmt->bindParam(':slug', $data['slug'], PDO::PARAM_STR);
$stmt->bindParam(':description', $data['description'], PDO::PARAM_STR);
$stmt->bindParam(':icon', $data['icon'], PDO::PARAM_STR);
$stmt->bindParam(':sort_order', $data['sort_order'], PDO::PARAM_INT);
$stmt->bindParam(':status', $data['status'], PDO::PARAM_INT);
$stmt->bindParam(':parent_id', $data['parent_id'], PDO::PARAM_INT);
if ($stmt->execute()) {
return $this->db->lastInsertId();
}
return false;
}
// 更新频道
public function update($id, $data) {
$sql = "UPDATE channels SET
name = :name,
slug = :slug,
description = :description,
icon = :icon,
sort_order = :sort_order,
status = :status,
parent_id = :parent_id,
updated_at = NOW()
WHERE id = :id";
$stmt = $this->db->prepare($sql);
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->bindParam(':name', $data['name'], PDO::PARAM_STR);
$stmt->bindParam(':slug', $data['slug'], PDO::PARAM_STR);
$stmt->bindParam(':description', $data['description'], PDO::PARAM_STR);
$stmt->bindParam(':icon', $data['icon'], PDO::PARAM_STR);
$stmt->bindParam(':sort_order', $data['sort_order'], PDO::PARAM_INT);
$stmt->bindParam(':status', $data['status'], PDO::PARAM_INT);
$stmt->bindParam(':parent_id', $data['parent_id'], PDO::PARAM_INT);
return $stmt->execute();
}
// 删除频道
public function delete($id) {
$stmt = $this->db->prepare("DELETE FROM channels WHERE id = :id");
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
return $stmt->execute();
}
// 获取树形结构
public function getTree($parentId = null, $status = null) {
$channels = $this->getAll($status);
return $this->buildTree($channels, $parentId);
}
private function buildTree($channels, $parentId = null) {
$tree = [];
foreach ($channels as $channel) {
if ($channel['parent_id'] == $parentId) {
$children = $this->buildTree($channels, $channel['id']);
if ($children) {
$channel['children'] = $children;
}
$tree[] = $channel;
}
}
return $tree;
}
// 验证slug唯一性
public function slugExists($slug, $excludeId = null) {
$sql = "SELECT COUNT(*) FROM channels WHERE slug = :slug";
if ($excludeId) {
$sql .= " AND id != :exclude_id";
}
$stmt = $this->db->prepare($sql);
$stmt->bindParam(':slug', $slug, PDO::PARAM_STR);
if ($excludeId) {
$stmt->bindParam(':exclude_id', $excludeId, PDO::PARAM_INT);
}
$stmt->execute();
return $stmt->fetchColumn() > 0;
}
// 生成slug
public function generateSlug($name) {
$slug = strtolower(trim(preg_replace('/[^A-Za-z0-9-]+/', '-', $name), '-'));
$baseSlug = $slug;
$counter = 1;
while ($this->slugExists($slug)) {
$slug = $baseSlug . '-' . $counter;
$counter++;
}
return $slug;
}
}
控制器 (ChannelController.php)
<?php
namespace App\Controllers;
use App\Models\Channel;
use App\Helpers\Validation;
use App\Helpers\Response;
class ChannelController {
private $channelModel;
public function __construct(Channel $channelModel) {
$this->channelModel = $channelModel;
}
// 列表页面
public function index() {
$channels = $this->channelModel->getAll();
$tree = $this->channelModel->getTree();
require_once __DIR__ . '/../views/channels/index.php';
}
// 创建页面
public function create() {
$parentChannels = $this->channelModel->getAll(1);
require_once __DIR__ . '/../views/channels/form.php';
}
// 编辑页面
public function edit($id) {
$channel = $this->channelModel->getById($id);
if (!$channel) {
header('Location: /channels?error=not_found');
exit;
}
$parentChannels = $this->channelModel->getAll(1);
require_once __DIR__ . '/../views/channels/form.php';
}
// 保存(创建/更新)
public function save() {
$data = $_POST;
$id = $data['id'] ?? null;
// 验证
$errors = $this->validate($data, $id);
if (!empty($errors)) {
$_SESSION['errors'] = $errors;
$_SESSION['old_data'] = $data;
if ($id) {
header("Location: /channels/edit/{$id}");
} else {
header('Location: /channels/create');
}
exit;
}
// 处理slug
if (empty($data['slug'])) {
$data['slug'] = $this->channelModel->generateSlug($data['name']);
}
// 处理icon上传
if (isset($_FILES['icon']) && $_FILES['icon']['error'] === UPLOAD_ERR_OK) {
$data['icon'] = $this->handleIconUpload($_FILES['icon']);
}
// 保存
if ($id) {
$result = $this->channelModel->update($id, $data);
} else {
$result = $this->channelModel->create($data);
}
if ($result) {
header('Location: /channels?success=saved');
} else {
header('Location: /channels?error=save_failed');
}
exit;
}
// 删除
public function delete($id) {
$result = $this->channelModel->delete($id);
if ($result) {
header('Location: /channels?success=deleted');
} else {
header('Location: /channels?error=delete_failed');
}
exit;
}
// 更新状态
public function toggleStatus($id) {
$channel = $this->channelModel->getById($id);
if ($channel) {
$newStatus = $channel['status'] ? 0 : 1;
$this->channelModel->update($id, ['status' => $newStatus]);
}
header('Location: /channels');
exit;
}
private function validate($data, $id = null) {
$errors = [];
if (empty($data['name'])) {
$errors['name'] = '频道名称不能为空';
}
if (!empty($data['slug']) && $this->channelModel->slugExists($data['slug'], $id)) {
$errors['slug'] = 'URL别名已存在';
}
return $errors;
}
private function handleIconUpload($file) {
$uploadDir = __DIR__ . '/../../public/uploads/icons/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0777, true);
}
$fileName = uniqid() . '_' . basename($file['name']);
$targetPath = $uploadDir . $fileName;
if (move_uploaded_file($file['tmp_name'], $targetPath)) {
return '/uploads/icons/' . $fileName;
}
return null;
}
}
视图文件
列表页面 (views/channels/index.php)
<?php require_once __DIR__ . '/../layouts/header.php'; ?>
<div class="container mt-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1>频道管理</h1>
<a href="/channels/create" class="btn btn-primary">添加频道</a>
</div>
<?php if (isset($_GET['success'])): ?>
<div class="alert alert-success">
<?php
switch ($_GET['success']) {
case 'saved': echo '频道保存成功'; break;
case 'deleted': echo '频道删除成功'; break;
}
?>
</div>
<?php endif; ?>
<?php if (isset($_GET['error'])): ?>
<div class="alert alert-danger">
<?php
switch ($_GET['error']) {
case 'save_failed': echo '频道保存失败'; break;
case 'delete_failed': echo '频道删除失败'; break;
case 'not_found': echo '频道未找到'; break;
}
?>
</div>
<?php endif; ?>
<div class="card">
<div class="card-body">
<table class="table table-hover">
<thead>
<tr>
<th>排序</th>
<th>名称</th>
<th>URL别名</th>
<th>状态</th>
<th>子频道数</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php if (empty($channels)): ?>
<tr>
<td colspan="6" class="text-center">暂无频道数据</td>
</tr>
<?php else: ?>
<?php foreach ($channels as $channel): ?>
<tr>
<td><?php echo $channel['sort_order']; ?></td>
<td>
<?php if ($channel['icon']): ?>
<img src="<?php echo $channel['icon']; ?>" width="20" height="20" alt="icon">
<?php endif; ?>
<?php echo $channel['name']; ?>
<?php if ($channel['parent_id']): ?>
<span class="badge bg-info">子频道</span>
<?php endif; ?>
</td>
<td><code><?php echo $channel['slug']; ?></code></td>
<td>
<a href="/channels/toggle-status/<?php echo $channel['id']; ?>"
class="btn btn-sm <?php echo $channel['status'] ? 'btn-success' : 'btn-danger'; ?>">
<?php echo $channel['status'] ? '启用' : '禁用'; ?>
</a>
</td>
<td><?php echo $channel['child_count']; ?></td>
<td>
<a href="/channels/edit/<?php echo $channel['id']; ?>"
class="btn btn-sm btn-warning">编辑</a>
<a href="/channels/delete/<?php echo $channel['id']; ?>"
class="btn btn-sm btn-danger"
onclick="return confirm('确定删除此频道吗?')">删除</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<?php require_once __DIR__ . '/../layouts/footer.php'; ?>
表单页面 (views/channels/form.php)
<?php require_once __DIR__ . '/../layouts/header.php'; ?>
<div class="container mt-4">
<h1><?php echo isset($channel) ? '编辑频道' : '添加频道'; ?></h1>
<?php if (!empty($_SESSION['errors'])): ?>
<div class="alert alert-danger">
<ul>
<?php foreach ($_SESSION['errors'] as $error): ?>
<li><?php echo $error; ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php unset($_SESSION['errors']); ?>
<?php endif; ?>
<form action="/channels/save" method="POST" enctype="multipart/form-data">
<?php if (isset($channel)): ?>
<input type="hidden" name="id" value="<?php echo $channel['id']; ?>">
<?php endif; ?>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="name" class="form-label">频道名称 *</label>
<input type="text"
class="form-control"
id="name"
name="name"
value="<?php echo $_SESSION['old_data']['name'] ?? ($channel['name'] ?? ''); ?>"
required>
</div>
<div class="mb-3">
<label for="slug" class="form-label">URL别名</label>
<input type="text"
class="form-control"
id="slug"
name="slug"
value="<?php echo $_SESSION['old_data']['slug'] ?? ($channel['slug'] ?? ''); ?>"
placeholder="留空自动生成">
<small class="text-muted">用于URL的唯一标识符</small>
</div>
<div class="mb-3">
<label for="parent_id" class="form-label">父频道</label>
<select class="form-select" id="parent_id" name="parent_id">
<option value="">无(顶级频道)</option>
<?php foreach ($parentChannels as $parent): ?>
<?php if (!isset($channel) || $parent['id'] != $channel['id']): ?>
<option value="<?php echo $parent['id']; ?>"
<?php echo (($_SESSION['old_data']['parent_id'] ?? ($channel['parent_id'] ?? '')) == $parent['id']) ? 'selected' : ''; ?>>
<?php echo $parent['name']; ?>
</option>
<?php endif; ?>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="description" class="form-label">描述</label>
<textarea class="form-control"
id="description"
name="description"
rows="3"><?php echo $_SESSION['old_data']['description'] ?? ($channel['description'] ?? ''); ?></textarea>
</div>
<div class="mb-3">
<label for="icon" class="form-label">图标</label>
<input type="file" class="form-control" id="icon" name="icon">
<?php if (isset($channel) && $channel['icon']): ?>
<div class="mt-2">
<img src="<?php echo $channel['icon']; ?>" width="50" height="50" alt="当前图标">
</div>
<?php endif; ?>
</div>
<div class="mb-3">
<label for="sort_order" class="form-label">排序</label>
<input type="number"
class="form-control"
id="sort_order"
name="sort_order"
value="<?php echo $_SESSION['old_data']['sort_order'] ?? ($channel['sort_order'] ?? '0'); ?>">
</div>
<div class="mb-3">
<div class="form-check">
<input type="checkbox"
class="form-check-input"
id="status"
name="status"
value="1"
<?php echo (($_SESSION['old_data']['status'] ?? ($channel['status'] ?? '1')) == '1') ? 'checked' : ''; ?>>
<label class="form-check-label" for="status">启用</label>
</div>
</div>
</div>
</div>
<div class="d-flex justify-content-between">
<a href="/channels" class="btn btn-secondary">取消</a>
<button type="submit" class="btn btn-primary">保存</button>
</div>
</form>
</div>
<?php
unset($_SESSION['old_data']);
require_once __DIR__ . '/../layouts/footer.php';
?>
路由配置 (routes.php)
<?php
// 路由配置示例
$routes = [
'GET' => [
'/channels' => 'ChannelController@index',
'/channels/create' => 'ChannelController@create',
'/channels/edit/{id}' => 'ChannelController@edit',
'/channels/delete/{id}' => 'ChannelController@delete',
'/channels/toggle-status/{id}' => 'ChannelController@toggleStatus',
],
'POST' => [
'/channels/save' => 'ChannelController@save',
],
];
使用示例 - API接口 (api/channels.php)
<?php
header('Content-Type: application/json');
require_once __DIR__ . '/../bootstrap.php';
use App\Models\Channel;
$channelModel = new Channel($db);
$method = $_SERVER['REQUEST_METHOD'];
switch ($method) {
case 'GET':
if (isset($_GET['id'])) {
$channel = $channelModel->getById($_GET['id']);
echo json_encode($channel);
} else {
$channels = $channelModel->getAll();
echo json_encode($channels);
}
break;
case 'POST':
$data = json_decode(file_get_contents('php://input'), true);
$result = $channelModel->create($data);
echo json_encode(['success' => $result, 'id' => $result]);
break;
case 'PUT':
parse_str(file_get_contents('php://input'), $data);
$id = $_GET['id'] ?? null;
if ($id) {
$result = $channelModel->update($id, $data);
echo json_encode(['success' => $result]);
}
break;
case 'DELETE':
$id = $_GET['id'] ?? null;
if ($id) {
$result = $channelModel->delete($id);
echo json_encode(['success' => $result]);
}
break;
}
扩展功能 - 缓存优化
<?php
namespace App\Helpers;
class ChannelCache {
private static $cache = [];
private static $cacheTime = 3600; // 1小时
public static function get($key) {
if (isset(self::$cache[$key])) {
$cache = self::$cache[$key];
if (time() - $cache['time'] < self::$cacheTime) {
return $cache['data'];
}
}
return false;
}
public static function set($key, $data) {
self::$cache[$key] = [
'data' => $data,
'time' => time()
];
}
public static function clear() {
self::$cache = [];
}
}
这个完整的频道管理系统提供了:
- 完善的数据结构:支持多级频道、排序、状态管理
- 完整的CRUD操作:创建、读取、更新、删除
- 用户友好的界面:列表展示、表单验证、错误提示
- 可扩展性:支持API接口、缓存优化
- 安全特性:CSRF防护、输入验证、SQL注入防护
您可以根据实际需求调整数据库字段、界面样式和功能逻辑。