首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP + MySQL 新聞分類管理系統:從資料庫設計到完整 CRUD 實作

     編輯:PHP 綜合知識

系統需求與架構

  • 新聞分類管理(類型 1~4)
  • 支援標題、內容、圖片、附件軟體上傳
  • 首頁顯示最新文章,側邊欄列表瀏覽歷史文章
  • 條件式顯示:有圖片才顯示圖片,有附件才顯示下載連結

資料庫設計

CREATE DATABASE `news_db` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE `news_db`;

CREATE TABLE `news` (
    `id`         INT UNSIGNED NOT NULL AUTO_INCREMENT,
    `kind`       VARCHAR(32)  NOT NULL COMMENT '分類:type1/type2/type3/type4',
    `title`      VARCHAR(120) NOT NULL,
    `content`    MEDIUMTEXT   NOT NULL,
    `image`      VARCHAR(255) DEFAULT NULL COMMENT '儲存檔名',
    `soft_file`  VARCHAR(255) DEFAULT NULL COMMENT '軟體附件檔名',
    `created_at` DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `updated_at` DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    INDEX `idx_kind_created` (`kind`, `created_at` DESC)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

目錄結構與權限

project/
├── config.php          # 資料庫連線設定
├── functions.php       # 共用函式
├── insert.php          # 新增新聞 (含上傳處理)
├── list.php            # 分類列表 + 內容顯示
├── edit.php            # 編輯新聞
├── delete.php          # 刪除新聞
├── uploads/
│   ├── images/         # chmod 755, www-data 可寫
│   └── soft/           # chmod 755, www-data 可寫
└── templates/          # 視圖模板 (選用)

核心程式碼實作

1. 設定檔 config.php

<?php
// config.php
declare(strict_types=1);

return [
    'db' => [
        'host'     => '127.0.0.1',
        'port'     => 3306,
        'dbname'   => 'news_db',
        'user'     => 'news_user',
        'pass'     => 'secure_password',
        'charset'  => 'utf8mb4',
        'options'  => [
            PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES   => false,
        ],
    ],
    'upload' => [
        'image_dir' => __DIR__ . '/uploads/images',
        'soft_dir'  => __DIR__ . '/uploads/soft',
        'allowed_image_mime' => ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
        'allowed_soft_mime'  => ['application/pdf', 'application/zip', 'application/x-rar-compressed'],
        'max_size' => 10 * 1024 * 1024, // 10MB
    ],
];

2. 資料庫連線 functions.php

<?php
// functions.php
require_once 'config.php';
$config = require 'config.php';

function getPDO(): PDO {
    static $pdo = null;
    if ($pdo === null) {
        $c = $config['db'];
        $dsn = "mysql:host={$c['host']};port={$c['port']};dbname={$c['dbname']};charset={$c['charset']}";
        $pdo = new PDO($dsn, $c['user'], $c['pass'], $c['options']);
    }
    return $pdo;
}

function uploadFile(array $file, string $targetDir, array $allowedMime, int $maxSize): ?string {
    if ($file['error'] !== UPLOAD_ERR_OK) return null;
    if ($file['size'] > $maxSize) throw new Exception('檔案過大');
    
    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $mime = $finfo->file($file['tmp_name']);
    if (!in_array($mime, $allowedMime, true)) throw new Exception('檔案類型不允許');
    
    $ext = match ($mime) {
        'image/jpeg' => 'jpg', 'image/png' => 'png', 'image/gif' => 'gif', 'image/webp' => 'webp',
        'application/pdf' => 'pdf', 'application/zip' => 'zip', 'application/x-rar-compressed' => 'rar',
        default => 'bin',
    };
    
    $filename = bin2hex(random_bytes(16)) . '.' . $ext;
    $dest = $targetDir . '/' . $filename;
    
    if (!move_uploaded_file($file['tmp_name'], $dest)) {
        throw new Exception('檔案移動失敗');
    }
    return $filename;
}

function h(string $s): string {
    return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
}

3. 新增新聞 insert.php

<?php
// insert.php
require_once 'functions.php';
$config = require 'config.php';
$pdo = getPDO();

$errors = [];
$success = false;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    try {
        $title   = trim($_POST['title'] ?? '');
        $kind    = $_POST['kind'] ?? '';
        $content = $_POST['content'] ?? '';
        
        // 驗證
        if ($title === '') $errors[] = '標題不可為空';
        if (!in_array($kind, ['type1','type2','type3','type4'], true)) $errors[] = '分類錯誤';
        if ($content === '') $errors[] = '內容不可為空';
        
        // 上傳處理
        $imageName = null;
        $softName  = null;
        
        if (!empty($_FILES['image']['name'])) {
            $imageName = uploadFile($_FILES['image'], $config['upload']['image_dir'], 
                $config['upload']['allowed_image_mime'], $config['upload']['max_size']);
        }
        if (!empty($_FILES['soft']['name'])) {
            $softName = uploadFile($_FILES['soft'], $config['upload']['soft_dir'], 
                $config['upload']['allowed_soft_mime'], $config['upload']['max_size']);
        }
        
        if (!$errors) {
            $stmt = $pdo->prepare(
                'INSERT INTO news (kind, title, content, image, soft_file) VALUES (?, ?, ?, ?, ?)'
            );
            $stmt->execute([$kind, $title, $content, $imageName, $softName]);
            $success = true;
            // 重導向防止重複送出
            header('Location: list.php?kind=' . urlencode($kind) . '&msg=added');
            exit;
        }
    } catch (Exception $e) {
        $errors[] = '系統錯誤:' . $e->getMessage();
    }
}

// 顯示表單 (可改用模板引擎)
?>
<!DOCTYPE html>
<html lang="zh-TW">
<head><meta charset="UTF-8"><title>新增新聞</title></head>
<body>
<h1>新增新聞</h1>
<?php if ($errors): ?><ul style="color:red"><?php foreach ($errors as $e): ?><li><?= h($e) ?></li><?php endforeach; ?></ul><?php endif; ?>
<form method="POST" enctype="multipart/form-data">
    <label>分類:
        <select name="kind" required>
            <option value="type1">類型 1</option>
            <option value="type2">類型 2</option>
            <option value="type3">類型 3</option>
            <option value="type4">類型 4</option>
        </select>
    </label><br><br>
    <label>標題:<input type="text" name="title" size="60" required></label><br><br>
    <label>內容:<br><textarea name="content" cols="60" rows="10" required></textarea></label><br><br>
    <label>圖片:<input type="file" name="image" accept="image/*"></label><br><br>
    <label>軟體附件:<input type="file" name="soft" accept=".pdf,.zip,.rar"></label><br><br>
    <button type="submit">送出</button>
</form>
</body></html>

4. 列表與顯示 list.php

<?php
// list.php
require_once 'functions.php';
$pdo = getPDO();

$kind = $_GET['kind'] ?? 'type1';
$id   = $_GET['id'] ?? null;

// 取得分類列表 (最新 10 筆)
$stmt = $pdo->prepare('SELECT id, title FROM news WHERE kind = ? ORDER BY created_at DESC LIMIT 10');
$stmt->execute([$kind]);
$list = $stmt->fetchAll();

// 決定顯示哪一篇
if ($id === null && $list) {
    $id = $list[0]['id'];
}

$article = null;
if ($id !== null) {
    $stmt = $pdo->prepare('SELECT * FROM news WHERE id = ? AND kind = ?');
    $stmt->execute([$id, $kind]);
    $article = $stmt->fetch();
}
?>
<!DOCTYPE html>
<html lang="zh-TW">
<head><meta charset="UTF-8"><title>新聞列表 - <?= h($kind) ?></title></head>
<body>
<h1><?= h($kind) ?> 最新消息</h1>

<div style="display:flex; gap:2rem;">
    <aside style="width:300px;">
        <h3>文章列表</h3>
        <ul>
            <?php foreach ($list as $item): ?>
                <li><a href="?kind=<?= h($kind) ?>&id=<?= $item['id'] ?>" <?= $item['id']==$id?'style="font-weight:bold;"':'' ?>><?= h($item['title']) ?></a></li>
            <?php endforeach; ?>
        </ul>
    </aside>

    <main style="flex:1;">
        <?php if ($article): ?>
            <h2><?= h($article['title']) ?></h2>
            <small>發布時間:<?= h($article['created_at']) ?></small><hr>
            
            <?php if ($article['image']): ?>
                <img src="/uploads/images/<?= h($article['image']) ?>" style="max-width:100%; float:right; margin-left:1rem;" alt="">
            <?php endif; ?>
            
            <div><?= nl2br(h($article['content'])) ?></div>
            
            <?php if ($article['soft_file']): ?>
                <p style="text-align:right; margin-top:2rem;">
                    <a href="/uploads/soft/<?= h($article['soft_file']) ?>" download>⬇ 下載附件:<?= h($article['soft_file']) ?></a>
                </p>
            <?php endif; ?>
        <?php else: ?>
            <p>無文章內容</p>
        <?php endif; ?>
    </main>
</div>
</body></html>

關鍵改進點對照原教學碼

原教學碼問題本文改進方案
使用 mysql_* 已移除函式使用 PDO + Prepared Statements
SQL 直接拼接變數 → SQL Injection參數綁定 execute([...])
上傳檔名直接用原檔名 → 覆蓋/注入風險隨機檔名 + MIME 驗證 + 副檔名白名單
無輸出轉義 → XSS統一 h() 輔助函式 + nl2br()
目錄權限 777755 + 擁有者 www-data,僅必要目錄可寫
硬編碼 DB 憑證設定檔分離,不納入版控
錯誤處理僅 die()try-catch + 友善錯誤訊息 + 記錄 Log

部署檢查清單

  • ☐ 建立資料庫、使用者、授權
  • ☐ mkdir -p uploads/images uploads/soft && chown www-data:www-data uploads/* && chmod 755 uploads/*
  • ☐ 設定 php.ini:upload_max_filesize, post_max_size, max_execution_time
  • ☐ 設定 Nginx/Apache 靜態檔案快取、禁止 uploads/ 執行 PHP
  • ☐ 設定定期備份資料庫與上傳檔案

總結

本文展示了從資料庫設計、檔案上傳、CRUD 操作到前端顯示的完整新聞分類系統。核心原則:**參數化查詢防注入、輸出轉義防 XSS、檔案上傳嚴格驗證、設定與程式碼分離**。此架構易於擴充:加入分頁、搜尋、標籤、權限控制、API 介面等功能。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved