在生产版本中无法加载图像 next.js 14

问题描述 投票:0回答:1

我有一个小型、简单的应用程序,我通过管理门户网站添加 PDF,并将它们分配给数据库中的供应商。但是,在生产模式下,当我运行此生产版本时,图片会正常加载。但是,在上传具有新名称的新图片并尝试显示它后,即使从数据库加载链接后链接正确并且位于公共文件夹中,它也不会加载。然后我需要重新启动服务器,PDF 再次正确显示。为什么会发生这种情况?为什么我需要重新启动服务器才能使一切正常工作?我需要它是动态的。

这是我从数据库加载信息的代码:

/app/api/suppliers/route.ts

import { NextRequest, NextResponse } from 'next/server';
import { getSuppliers, addSupplier, updateSupplier, deleteSupplier } from '@/lib/suppliers';

export async function GET() {
  try {
    const suppliers = await getSuppliers();
    const response = NextResponse.json(suppliers, { status: 200 });
  
    return response;
  } catch  {
    return NextResponse.json({ message: 'Error fetching suppliers' }, { status: 500 });
  }
}

这是来自 /app/lib/suppliers.ts 的函数

import sql from "mssql";
import { connectToDb } from "./db";

export async function getSuppliers() {
  const pool = await connectToDb();
  try {
    const result = await pool.request()
      .query(`SELECT s.id, s.Name as [name], s.tel, s.price, si.imagePath 
FROM suppliers s
left join supplier_images si on si.id = s.image_id`);
    return result.recordset;
  } catch (error) {
    console.error("Error fetching suppliers:", error);
    throw error;
  }
}

这是我上传文件的代码: /app/admin/upload-menu/page.tsx

'use client';
export const dynamic = 'force-dynamic'; // Zabezpečenie dynamického spracovania

import React from 'react';
import UploadMenuImages from '@/components/Admin/UploadMenuImages';

const UploadMenu: React.FC = () => {
  return (
    <div className="p-2">
      <h1 className="text-2xl font-bold mb-4 text-yellow-600">Správa menu</h1>
      <UploadMenuImages />
    </div>
  );
};

export default UploadMenu;


这里是代码中自己使用的组件功能: /components/Admin/UploadMenuImages.tsx

export const dynamic = "force-dynamic"; // Zabezpečenie dynamického spracovania
import axios from "axios";
import React, { useState, useEffect } from "react";

interface Supplier {
  id: number;
  name: string;
  attachment: string | null; // Názov aktuálneho súboru/prílohy
  fileExists: boolean; // Stav na označenie existencie súboru
}

const UploadMenuImages: React.FC = () => {
  const [selectedSupplier, setSelectedSupplier] = useState<string | null>(null);
  const [selectedAttachment, setSelectedAttachment] = useState<string | null>(
    null
  ); // Na uchovanie názvu prílohy pre náhľad
  const [image, setImage] = useState<File | null>(null);
  const [suppliers, setSuppliers] = useState<Supplier[]>([]);

  useEffect(() => {
    fetchSuppliers();
  }, []);

  // Upravená funkcia na načítanie dodávateľov a prispôsobenie dát štruktúre `Supplier`
  const fetchSuppliers = async () => {
    try {
      const response = await axios.get(`/api/suppliers`);
      const data = response.data as {
        id: number;
        name: string;
        imagePath: string | null;
      }[];

      // Mapovanie API dát na štruktúru `Supplier` a kontrola existencie súborov
      const formattedSuppliers: Supplier[] = await Promise.all(
        data.map(async (supplier) => {
          const fileExists = supplier.imagePath
            ? await checkFileExists(supplier.imagePath)
            : false;
          return {
            id: supplier.id,
            name: supplier.name,
            attachment: supplier.imagePath || null, // Priradenie attachmentu, ak existuje
            fileExists, // Kontrola, či súbor existuje
          };
        })
      );

      setSuppliers(formattedSuppliers);
    } catch (error) {
      console.error("Error fetching suppliers:", error);
    }
  };

  // Funkcia na overenie existencie súboru
  const checkFileExists = async (filePath: string) => {
    try {
      const response = await axios.get(`/api/file-exists?filePath=${filePath}`);
      return response.data.exists;
    } catch (error) {
      console.error("Error checking file existence:", error);
      return false;
    }
  };

  const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    if (event.target.files) {
      setImage(event.target.files[0]);
    }
  };

  const handleSubmit = async () => {
    if (!selectedSupplier || !image) {
      alert("Vyberte dodávateľa a súbor");
      return;
    }

    const formData = new FormData();
    formData.append("supplierId", selectedSupplier);
    formData.append("file", image);

    try {
      const response = await axios.post(`/api/menu/upload`, formData);
      if (response.status === 200) {
        alert(`Súbor bol úspešne nahraný: ${response.data.filePath}`);
        fetchSuppliers(); // Obnovíme zoznam dodávateľov
      } else {
        alert("Chyba pri nahrávaní súboru");
      }
    } catch (error) {
      console.error("Chyba pri odosielaní súboru:", error);
    }
  };

  const handleRowClick = (supplier: Supplier) => {
    //console.log("Selected supplier:", supplier);
    if (supplier.attachment && supplier.fileExists) {
      setSelectedAttachment(supplier.attachment); // Nastavíme náhľad
    } else {
      setSelectedAttachment(null); // Ak príloha neexistuje, nastavíme na null
    }
  };

  return (
    <div>
      <select
        onChange={(e) => setSelectedSupplier(e.target.value)}
        className="mb-4"
      >
        <option value="">Vyber dodávateľa</option>
        {suppliers.map((supplier) => (
          <option key={supplier.id} value={supplier.id.toString()}>
            {supplier.name}
          </option>
        ))}
      </select>

      <input type="file" onChange={handleFileChange} className="mb-4" />
      <button
        onClick={handleSubmit}
        className="bg-blue-500 text-white px-4 py-2"
      >
        Nahrať
      </button>

      {/* Tabuľka dodávateľov s označením existencie príloh */}
      <table className="min-w-full bg-white mt-4">
        <thead>
          <tr>
            <th className="border px-4 py-2">Dodávateľ</th>
            <th className="border px-4 py-2">Príloha</th>
          </tr>
        </thead>
        <tbody>
          {suppliers.map((supplier) => (
            <tr
              key={supplier.id}
              className={`cursor-pointer ${
                supplier.fileExists ? "bg-green-100" : "bg-red-100"
              }`}
              onClick={() => handleRowClick(supplier)} // Kliknutie na riadok
            >
              <td className="border px-4 py-2">{supplier.name}</td>
              <td className="border px-4 py-2">
                {supplier.attachment ? supplier.attachment : "Žiadna príloha"}
              </td>
            </tr>
          ))}
        </tbody>
      </table>

      {/* Zobrazenie náhľadu prílohy, ak bola vybratá */}
      {selectedAttachment && (
        <div className="mt-4">
          <h3>Náhľad prílohy:</h3>
          {/* Ak je príloha obrázok, zobrazíme ju */}
          {selectedAttachment.match(/\.(jpg|jpeg|png|gif)$/i) ? (
            <img
              src={`${selectedAttachment}`}
              alt="Náhľad obrázka"
              className="w-full h-auto"
            />
          ) : selectedAttachment.endsWith(".pdf") ? (
            // Ak je príloha PDF, zobrazíme PDF viewer
            <object
              data={`${selectedAttachment}`}
              type="application/pdf"
              width="100%"
              height="600px"
            >
              <p>PDF nie je možné zobraziť.</p>
            </object>
          ) : (
            <p>Formát súboru nie je podporovaný pre náhľad.</p>
          )}
        </div>
      )}
    </div>
  );
};

export default UploadMenuImages;

还有上传的 api: /app/api/menu/upload/route.ts

import { NextResponse } from "next/server";
import multer from "multer";
import path from "path";
import { saveSupplierImage } from "@/lib/suppliers";
import fs from "fs";


// Nastavenie `multer` na ukladanie súborov do priečinka public/uploads
multer({
  storage: multer.diskStorage({
    destination: "./public/uploads", // Umiestnenie nahraných súborov
    filename: (req, file, cb) => {
      const uniqueSuffix = Date.now() + "-" + Math.round(Math.random() * 1e9);
      cb(
        null,
        file.fieldname + "-" + uniqueSuffix + path.extname(file.originalname)
      ); // Generovanie názvu súboru s príponou
    },
  }),
  limits: { fileSize: 1024 * 1024 * 10 }, // Obmedzenie veľkosti súboru na 10 MB
});

// API route na spracovanie nahrania súboru spolu s údajmi o dodávateľovi
export const POST = async (req: Request) => {
  const formData = await req.formData();

  const file = formData.get("file") as File;
  const supplierId = formData.get("supplierId");

  if (!file || !supplierId) {
    console.error("Súbor alebo ID dodávateľa nebolo poskytnuté.");
    return NextResponse.json({ message: "Chýba súbor alebo dodávateľ" }, { status: 400 });
  }

  const filePath = `/uploads/${file.name}`;
  
  // Uložme súbor na disk
  const buffer = await file.arrayBuffer();
 
  fs.writeFileSync(`./public${filePath}`, Buffer.from(buffer));

  // Uloženie cesty k súboru do databázy
  await saveSupplierImage(supplierId.toString(), filePath);

  return NextResponse.json({
    message: "Súbor bol úspešne nahratý",
    filePath,
  });
};


// Zakázanie natívneho spracovania tela v Next.js 14
export const runtime = 'nodejs';
export const preferredRegion = 'auto';
//export const disableBodyParser = true;

谢谢你们的帮助。

我的代码使用 Axios 而不是 Fetch。我首先尝试使用 Fetch 并使用了我找到的所有缓存控件,但它们都没有帮助。然后我改用axios,结果是一样的。

我需要在不重新启动服务器的情况下动态更新它。

请帮忙。

file production next.js14 uploading
1个回答
0
投票

/public 文件夹用于存储静态文件(JavaScript、CSS、图像等),因此使用它来提供动态上传的文件并不是一个很好的解决方案。

您需要将文件存储在外部服务上,例如AWS S3。然后,您可以将用于访问它的 URL 存储在数据库中,并在用户请求时使用该 URL 从 S3 获取文件。

© www.soinside.com 2019 - 2024. All rights reserved.