我有一个小型、简单的应用程序,我通过管理门户网站添加 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,结果是一样的。
我需要在不重新启动服务器的情况下动态更新它。
请帮忙。
/public 文件夹用于存储静态文件(JavaScript、CSS、图像等),因此使用它来提供动态上传的文件并不是一个很好的解决方案。
您需要将文件存储在外部服务上,例如AWS S3。然后,您可以将用于访问它的 URL 存储在数据库中,并在用户请求时使用该 URL 从 S3 获取文件。