在 Windows 上读取/写入 csv 文件时遇到问题

时间:2021-02-03 05:14:21

标签: symfony phpoffice-phpspreadsheet

我正在开发一项功能,允许用户上传文件(.csv、.xls 或 .xlsx)。该文件有一列包含电子邮件,然后我使用 SHA-256 将该电子邮件散列到另一列,最后导出文件。这是示例 .csv 文件:

  • 输入文件:
abc@gmail.com
xyz@gmail.com
  • 输出文件:
"abc@gmail.com","48ddb93f0b30c475423fe177832912c5bcdce3cc72872f8051627967ef278e08"
"xyz@gmail.com","32e19a491662fd86de7d3806b1199b76f0ee44e928d3475f05b0c8a59912c097"

这是我的代码,我使用 phpoffice/phpspreadsheet 来读/写:

<?php

namespace ApiBundle\Controller;

use PhpOffice\PhpSpreadsheet\Reader\Xls as ReaderXls;
use PhpOffice\PhpSpreadsheet\Writer\Xls as WriterXls;
use PhpOffice\PhpSpreadsheet\Reader\Csv as ReaderCsv;
use PhpOffice\PhpSpreadsheet\Writer\Csv as WriterCsv;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use App\Requests\HashGenerator\HashGeneratorRequest;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx as WriterXlsx;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx as ReaderXlsx;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use App\Controller\BaseController;

/**
 * Class HashGeneratorController
 * @package ApiBundle\Controller
 */
class HashGeneratorController extends BaseController
{
    public const MIME_TYPES = [
        'xls' => 'application/vnd.ms-excel',
        'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        'csv' => 'text/csv',
        'txt' => 'text/plain'
    ];

    /**
     * @Route("/hash-generator", name="hash_generator", methods={"POST"})
     * @param Request $request
     * @param HashGeneratorRequest $hashGeneratorRequest
     * @return Response
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
     * @throws \App\Exceptions\ValidationFailedException
     */
    public function processHash(Request $request, HashGeneratorRequest $hashGeneratorRequest)
    {
        $hashGeneratorRequest->setFileData($request);
        $this->validate($hashGeneratorRequest);

        /** @var UploadedFile $file */
        $file = $hashGeneratorRequest->getFile();

        $rows = $this->readFile($file);
        if ($rows === false) {
            return $this->responseErrorJson([], 'Cannot read file');
        }

        $emailHashArr = [];
        foreach ($rows as $row) {
            $emailHashArr[] = [$row[0], hash('sha256', $row[0])];
        }
        $result = $this->writeFile($emailHashArr, $file, true);
        if ($result === false) {
            return $this->responseErrorJson([], 'Cannot write file');
        }
        return $result;
    }

    public function getExportFileName($fileType, $ext = 'csv')
    {
        $fileName = uniqid() . "-$fileType";
        // get full fileName - Default format is ".xls"
        return $fileName . '.' . $ext;
    }

    /**
     * @param UploadedFile $file
     * @return array|bool
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
     */
    public function readFile($file)
    {
        switch ($file->getClientMimeType()) {
            case self::MIME_TYPES['xlsx'] :
            {
                $reader = new ReaderXlsx();
                break;
            }
            case self::MIME_TYPES['xls'] :
            {
                $reader = new ReaderXls();
                break;
            }
            case self::MIME_TYPES['csv'] :
            case self::MIME_TYPES['txt'] :
            {
                $reader = new ReaderCsv();
                break;
            }
            default: {
                $reader = null;
                break;
            }

        }
        if ($reader === null) {
            return false;
        }
        $reader->setReadDataOnly(true);
        $spreadsheet = $reader->load($file->getPathname());
        $worksheet = $spreadsheet->getActiveSheet();
        $rows = [];
        foreach ($worksheet->getRowIterator() AS $row) {
            $cellIterator = $row->getCellIterator();
            $cellIterator->setIterateOnlyExistingCells(true); // False if This loops through all cells,
            $cells = [];
            foreach ($cellIterator as $cell) {
                $cells[] = $cell->getValue();
            }
            $rows[] = $cells;
        }
        return $rows;
    }

    /**
     * @param array $emailHashArr
     * @param UploadedFile $file
     * @param string $default
     * @return bool|Response
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
     */
    public function writeFile($emailHashArr, $file, $default = false)
    {
        $spreadsheet = new Spreadsheet();
        $sheet = $spreadsheet->getActiveSheet();
        // Increase row cursor after header write
        $sheet->fromArray($emailHashArr, null, 'A1', true);

        // default export .csv file
        if ($default === true) {
            $writer = new WriterCsv($spreadsheet);
            $ext = 'csv';
        }
        else {
            switch ($file->getClientMimeType()) {
                case self::MIME_TYPES['xlsx'] :
                {
                    $writer = new WriterXlsx($spreadsheet);
                    $ext = 'xlsx';
                    break;
                }
                case self::MIME_TYPES['xls'] :
                {
                    $writer = new WriterXls($spreadsheet);
                    $ext = 'xls';
                    break;
                }
                case self::MIME_TYPES['csv'] :
                case self::MIME_TYPES['txt'] :
                {
                    $writer = new WriterCsv($spreadsheet);
                    $ext = 'csv';
                    break;
                }
                default: {
                    $writer = null;
                    break;
                }
            }
        }

        if ($writer === null) {
            return false;
        }

        $tmp = 'tmp.' . $ext;
        $writer->save($tmp);
        $response = new Response(file_get_contents($tmp));
        $fs = new Filesystem();
        $fs->remove($tmp);

        $fileName = $this->getExportFileName('email_hash', $ext);
        $response->headers->set('Content-Disposition', 'attachment; filename="' . $fileName . '"');
        if  ($default === true) {
            $response->headers->set('Content-Type', 'text/csv');
        }
        return $response;
    }
}

<?php

namespace App\Requests\HashGenerator;

use App\Requests\PostRequest;
use App\Requests\CreateRequestInterface;
use Symfony\Component\Validator\Constraints as Assert;

class HashGeneratorRequest extends PostRequest implements CreateRequestInterface
{
    /**
     * @Assert\NotBlank
     * @Assert\File(
     * maxSize = "5M",
     * mimeTypes = {
     *     "application/vnd.ms-excel",
     *     "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
     *     "text/csv",
     *     "text/plain",
     * },
     * mimeTypesMessage = "We are very sorry, but this file type is not supported. Please upload .xls, .xlsx or .csv file")
     */
    protected $file;

    /**
     * @return mixed
     */
    public function getFile()
    {
        return $this->file;
    }

    /**
     * @param mixed $file
     */
    public function setFile($file): void
    {
        $this->file = $file;
    }
}

它适用于 macos(不关心浏览器)上的所有情况,但不适用于 Windows。

  • 第一个问题,当我尝试在 Windows 上上传 .csv 文件时,错误是:
{
    "status": false,
    "message": "error",
    "data": {
        "line": 109,
        "file": "\/var\/www\/vendor\/phpoffice\/phpspreadsheet\/src\/PhpSpreadsheet\/Shared\/OLERead.php",
        "msg": "The filename \/tmp\/phpEkOjfh is not recognised as an OLE file",
        "trace": "#0 \/var\/www\/vendor\/phpoffice\/phpspreadsheet\/src\/PhpSpreadsheet\/Reader\/Xls.php(1367): PhpOffice\\PhpSpreadsheet\\Shared\\OLERead-\u003Eread(\u0027\/tmp\/phpEkOjfh\u0027)\n#1 \/var\/www\/vendor\/phpoffice\/phpspreadsheet\/src\/PhpSpreadsheet\/Reader\/Xls.php(631): PhpOffice\\PhpSpreadsheet\\Reader\\Xls-\u003EloadOLE(\u0027\/tmp\/phpEkOjfh\u0027)\n#2 \/var\/www\/src\/ApiBundle\/Controller\/HashGeneratorController.php(109): PhpOffice\\PhpSpreadsheet\\Reader\\Xls-\u003Eload(\u0027\/tmp\/phpEkOjfh\u0027)\n#3 \/var\/www\/src\/ApiBundle\/Controller\/HashGeneratorController.php(51): ApiBundle\\Controller\\HashGeneratorController-\u003EreadFile(Object(Symfony\\Component\\HttpFoundation\\File\\UploadedFile))\n#4 \/var\/www\/vendor\/symfony\/http-kernel\/HttpKernel.php(158): ApiBundle\\Controller\\HashGeneratorController-\u003EprocessHash(Object(Symfony\\Component\\HttpFoundation\\Request), Object(App\\Requests\\HashGenerator\\HashGeneratorRequest))\n#5 \/var\/www\/vendor\/symfony\/http-kernel\/HttpKernel.php(80): Symfony\\Component\\HttpKernel\\HttpKernel-\u003EhandleRaw(Object(Symfony\\Component\\HttpFoundation\\Request), 1)\n#6 \/var\/www\/vendor\/symfony\/http-kernel\/Kernel.php(201): Symfony\\Component\\HttpKernel\\HttpKernel-\u003Ehandle(Object(Symfony\\Component\\HttpFoundation\\Request), 1, true)\n#7 \/var\/www\/public\/index.php(25): Symfony\\Component\\HttpKernel\\Kernel-\u003Ehandle(Object(Symfony\\Component\\HttpFoundation\\Request))\n#8 {main}"
    }
}

我尝试打开标头请求以查看表单数据:

  • 在 MacOS 上:
------WebKitFormBoundaryFZvmvFXVTvuUNp1h
Content-Disposition: form-data; name="file"; filename="test-hash-mac.csv"
Content-Type: text/csv


------WebKitFormBoundaryFZvmvFXVTvuUNp1h--
  • 在 Windows 上:
------WebKitFormBoundaryNKfDVARlcJappYQJ
Content-Disposition: form-data; name="file"; filename="test-hash-mac.csv"
Content-Type: application/vnd.ms-excel


------WebKitFormBoundaryNKfDVARlcJappYQJ--
  • 第二个问题,当上传 .xls/.xlsx 文件然后导出 .csv 文件时,在 macos 上它运行良好(我可以打开一个 csv 文件)。但在 Windows 上它是一个未打开的文件: image

0 个答案:

没有答案