如何用PHP项目实现频道管理?

wen java案例 2

本文目录导读:

如何用PHP项目实现频道管理?

  1. 数据库设计
  2. 频道模型类 (Channel.php)
  3. 控制器 (ChannelController.php)
  4. 视图文件
  5. 路由配置 (routes.php)
  6. 使用示例 - API接口 (api/channels.php)
  7. 扩展功能 - 缓存优化

我将为您提供一个完整的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 = [];
    }
}

这个完整的频道管理系统提供了:

  1. 完善的数据结构:支持多级频道、排序、状态管理
  2. 完整的CRUD操作:创建、读取、更新、删除
  3. 用户友好的界面:列表展示、表单验证、错误提示
  4. 可扩展性:支持API接口、缓存优化
  5. 安全特性:CSRF防护、输入验证、SQL注入防护

您可以根据实际需求调整数据库字段、界面样式和功能逻辑。

抱歉,评论功能暂时关闭!