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

PHP 導入 CSV 文件亂碼終極解決方案:編碼檢測、Locale 設置與跨平台兼容

     編輯:PHP 綜合知識

亂碼根因分析

場景典型現象核心原因
Windows 開發環境中文顯示為 或 縮CSV 為 GB2312/GBK,PHP 腳本為 UTF-8,未轉碼
Linux 生產環境同樣代碼讀取失敗或亂碼系統 Locale 為 en_US.UTF-8,fgetcsv() 按單字節解析多字節字符
跨平台提交Windows 編寫 → Linux 運行失效文件編碼 + 系統 Locale 雙重不匹配

核心解決三步走

步驟 1:設置正確 Locale(Linux 關鍵)

<?php
// 必須在任何 fgetcsv() 呼叫之前
setlocale(LC_ALL, 'zh_CN.GBK', 'zh_CN', 'Chinese_China.936');
// 返回值檢查
if (setlocale(LC_ALL, 'zh_CN.GBK') === false) {
    error_log("Locale zh_CN.GBK 不可用,請執行:locale-gen zh_CN.GBK");
}
?>

原理:fgetcsv() 依賴 C 库 setlocale 判斷多字節字符邊界。Linux 默認 en_US.UTF-8 會將 GBK 雙字節誤判為兩個單字節,導致欄位分割錯亂。

步驟 2:編碼檢測與統一轉碼

<?php
function readCsvSafe($filePath) {
    $handle = fopen($filePath, 'r');
    if (!$handle) throw new Exception("無法打開文件:$filePath");

    // 讀取首行檢測編碼
    $firstLine = fgets($handle, 4096);
    rewind($handle); // 重置指針

    $encoding = mb_detect_encoding($firstLine, ['GB2312', 'GBK', 'UTF-8', 'ASCII'], true);
    if (!$encoding) throw new Exception("無法識別文件編碼");

    $data = [];
    while (($row = fgetcsv($handle, 0, ',')) !== false) {
        // 統一轉為 UTF-8 供後續處理
        $row = array_map(function($cell) use ($encoding) {
            return $encoding === 'UTF-8' ? $cell : iconv($encoding, 'UTF-8//IGNORE', $cell);
        }, $row);
        $data[] = $row;
    }
    fclose($handle);
    return $data;
}
?>

步驟 3:完整導入腳本(生產就緒版)

<?php
// import_csv.php
require_once 'vendor/autoload.php'; // 若使用 Composer

// 1. 設置 Locale(Linux 必須)
setlocale(LC_ALL, 'zh_CN.GBK', 'zh_CN', 'Chinese_China.936');

// 2. 文件上傳處理
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['csv_file'])) {
    $file = $_FILES['csv_file'];
    if ($file['error'] !== UPLOAD_ERR_OK) die("上傳失敗:錯誤碼 {$file['error']}");

    // 副檔名校驗
    if (pathinfo($file['name'], PATHINFO_EXTENSION) !== 'csv') die("僅支援 .csv 文件");

    try {
        $data = readCsvSafe($file['tmp_name']);
        if (empty($data)) die("CSV 內容為空");

        // 假設第一行為標題
        $header = array_shift($data);
        $header = array_map('trim', $header);

        // 3. 業務邏輯:批量入庫示例(PDO 事務)
        $pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4', 'user', 'pass');
        $pdo->beginTransaction();

        $placeholders = implode(',', array_fill(0, count($header), '?'));
        $sql = "INSERT INTO products (" . implode(',', $header) . ") VALUES ($placeholders)";
        $stmt = $pdo->prepare($sql);

        $success = 0;
        foreach ($data as $rowNum => $row) {
            if (count($row) !== count($header)) {
                error_log("第 {$rowNum} 行欄位數不匹配,跳過");
                continue;
            }
            $stmt->execute($row);
            $success++;
        }

        $pdo->commit();
        echo "✅ 成功導入 $success 條記錄";

    } catch (Exception $e) {
        if (isset($pdo)) $pdo->rollBack();
        echo "❌ 導入失敗:" . $e->getMessage();
    }
    exit;
}
?>
<!DOCTYPE html>
<html><body>
<form method="post" enctype="multipart/form-data">
    <input type="file" name="csv_file" accept=".csv" required>
    <button type="submit">導入 CSV</button>
</form>
</body></html>

常用 Locale 標識對照表

標識編碼適用系統
zh_CN.GBK / zh_CNGBK/GB2312Linux (需 locale-gen zh_CN.GBK)
Chinese_China.936GBKWindows
en_US.UTF-8UTF-8通用(不適合 GBK CSV)
zh_TW.BIG5BIG5繁體中文環境

查看系統支援:locale -a (Linux) / 控制面板→地區 (Windows)。

避坑指南

  • ❌ 依賴 HTTP Header 設置編碼:header("Content-Type: ... charset=GB2312") 僅影響瀏覽器顯示,不改變 fgetcsv 解析行為。
  • ❌ 使用 str_getcsv 替代:同樣受 Locale 影響,且需自行讀取文件內容。
  • ✅ 統一 UTF-8 工作流:要求用戶上傳 UTF-8 CSV(Excel 另存為「CSV UTF-8」),服務端 setlocale(LC_ALL, 'en_US.UTF-8'),徹底消除編碼痛苦。
  • ✅ 大文件流式處理:逐行 fgetcsv + 生成器 yield,避免一次性載入記憶體。

擴展:導出 CSV(反向操作)

<?php
function exportCsv(array $data, string $filename = 'export.csv', string $encoding = 'GBK') {
    header("Content-Type: text/csv; charset=$encoding");
    header("Content-Disposition: attachment; filename=\"$filename\"");
    $out = fopen('php://output', 'w');
    // 寫入 BOM(Excel 識別 UTF-8 必需)
    if ($encoding === 'UTF-8') fwrite($out, "\xEF\xBB\xBF");
    foreach ($data as $row) {
        $row = array_map(fn($v) => $encoding === 'UTF-8' ? $v : iconv('UTF-8', "$encoding//IGNORE", $v), $row);
        fputcsv($out, $row);
    }
    fclose($out);
}
?>
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved