TypeScript中req.file.key/location类型错误求助(Express+Multer-S3)
解决Express+Multer-S3上传AWS S3时的TypeScript类型错误
问题描述
使用Express框架结合Multer-S3中间件将图片上传至AWS S3时,遇到TypeScript类型错误:
Property 'key' does not exist on type 'File'Property 'location' does not exist on type 'File'
错误出现在product.ts文件中访问req.file.key和req.file.location的位置。已尝试在项目根目录创建types文件夹并编写声明文件,且将其加入tsconfig.json的include列表,但问题仍未解决。
相关代码
product.ts关键代码
/*modules*/ import express from 'express'; /*models*/ import Raffle, { IRaffle } from '../../models/Raffle'; import RafflePlayer from '../../models/RafflePlayer'; import MystBox, { IMystBox } from '../../models/MystBox'; import MystBoxPlayer from '../../models/MystBoxPlayer'; import BreakCode from '../../models/BreakCode'; import WlOrMint from '../../models/WlOrMint'; import SimpleCard from '../../models/SimpleCard'; /*middleware*/ import fetchNFTData from '../../middleware/fetchNftData'; import connectAwsS3 from '../../middleware/connect-awsS3'; /*fetch*/ import getMetadatNft from '../../services/metadataNft'; import BreakEncrypt from '../../models/BreakEncrypt'; import BreakGuess from '../../models/BreakGuess'; import BreakPlayer from '../../models/BreakPlayer'; router.post( '/addmyst', connectAwsS3.uploadCardImage.single('imagemystbox'), async (req, res) => { if (!req.file) { return res.status(400).json({ error: 'No image provided or your image has not the extenions jpeg|jpg|png. Try again!', }); } try { const { title, nbrmystbox, exp, price, posted, startdate, enddate, currentdate, } = req.body; console.log('reqbody', req.body); console.log( 'var', title, nbrmystbox, exp, price, startdate, enddate, currentdate ); let error = ''; const prizes = JSON.parse(req.body.prizes); console.log('prizes', prizes, typeof prizes); if (!title.trim()) { error = 'Title is empty.'; } else if (!nbrmystbox) { error = 'Nbr mystBox is empty.'; } else if (!price) { error = 'Price is empty.'; } else if (!exp) { error = 'Exp is empty.'; } else if (!startdate) { error = 'Start date is empty.'; } else if (!enddate) { error = 'End date is empty.'; } else if (prizes.length === 0) { error = 'No prize added.'; } if (error) { // Suppression de l'image de S3 en cas d'erreur if (req.file && req.file.key) { const cleanedKey = req.file.key.replace('MystBox/', ''); // Supprime le préfixe console.log('deletefile1', req.file, req.file.key); await connectAwsS3.deleteImageFromS3(cleanedKey, 'MystBox'); } return res.status(400).json({ error }); } const imageUrl = req.file.location; console.log('imageUrl :>> ', imageUrl); const mystBoxData: IMystBox = new MystBox({ title, nbrmystbox, prize: prizes, exp, price, posted, image: imageUrl, }); console.log('mystboxdata1', mystBoxData); if (posted === 'on') { const currentDate = new Date(currentdate); const startDate = new Date(startdate); const endDate = new Date(enddate); if (currentDate < startDate) { mystBoxData.status = 'Upcoming'; } else if (currentDate >= startDate && currentDate <= endDate) { mystBoxData.status = 'Ongoing'; } else { mystBoxData.status = 'Ended'; } mystBoxData.startdate = startDate; mystBoxData.enddate = endDate; } console.log('mystboxdata2', mystBoxData); await mystBoxData.save(); return res.status(201).json({ message: 'Mysterybox added successfully.' }); } catch (err: any) { let errorMessage = 'Impossible to create the mysterybox!'; // Gestion des erreurs if (err.code === 11000) { errorMessage = 'That title already exists!'; } // Suppression de l'image de S3 si elle a été téléchargée if (req.file && req.file.key) { const cleanedKey = req.file.key.replace('MystBox/', ''); // Supprime le préfixe console.log('deletefile2', req.file, req.file.key); await connectAwsS3.deleteImageFromS3(cleanedKey, 'MystBox'); } return res.status(400).json({ error: errorMessage }); } } );
connect-awsS3.ts中间件代码
/* eslint-disable no-console -- console*/ import path from 'node:path'; import multer from 'multer'; import multerS3 from 'multer-s3'; import { S3Client, DeleteObjectCommand } from '@aws-sdk/client-s3'; import { v4 as uuidv4 } from 'uuid'; import { Request, Response, NextFunction } from 'express'; // Ajout des types const s3Client = new S3Client({ region: process.env.AWS_REGION as string, credentials: { accessKeyId: process.env.ACCESS_KEY_S3 as string, secretAccessKey: process.env.SECRET_KEY_S3 as string, }, }); const bucketName = process.env.AWS_BUCKET_NAME!; // Middleware pour l'upload const upload = multer({ storage: multerS3({ s3: s3Client, bucket: bucketName, async key(req, file, cb) { let folder = ''; // Initialisation du dossier if (file && file.fieldname === 'imagemystbox') { folder = 'MystBox/'; } else if (file && file.fieldname === 'imagebreakcode') { folder = 'BreakCode/'; } else if (file && file.fieldname === 'questFiles') { folder = 'Quest/'; } else if (file && file.fieldname === 'imagewlormint') { folder = 'WlOrMint/'; } else if (file && file.fieldname === 'airdropFiles') { folder = 'Airdrop/'; } // Ajouter d'autres conditions pour d'autres types si nécessaire console.log('Generated folder:', folder); // Vérifie le dossier généré const fileExtension = path.extname(file.originalname).toLowerCase(); const newFilename = uuidv4() + fileExtension; // Génère un nouvel identifiant unique pour le nom du fichier cb(null, folder + newFilename); // Utilisez le nouveau nom de fichier }, acl: 'public-read', metadata(req, file, cb) { cb(null, { fieldName: file.fieldname }); }, }), limits: { fileSize: 10 * 1024 * 1024 }, // Max file size is 10MB fileFilter(req: Request, file, cb) { const fileTypes = /jpeg|jpg|png/; const extname = fileTypes.test(path.extname(file.originalname).toLowerCase()); const mimetype = fileTypes.test(file.mimetype); console.log('Nom du fichier :', file.originalname); // Ajout du console.log ici if (extname && mimetype) { return cb(null, true); } // Signaler une erreur de type de fichier à la route req.invalidFileType = true; cb(null, false); }, }); // Fonction pour supprimer l'image de S3 const deleteImageFromS3 = async (key: string, type: string) => { const folder = `${type}/`; // Dossier basé sur le type const params = { Bucket: bucketName, Key: folder + key, }; try { await s3Client.send(new DeleteObjectCommand(params)); console.log(`Successfully deleted ${key} from S3`); } catch (err) { console.error(`Error deleting ${key} from S3:`, err); } }; export default { uploadCardImage: upload, deleteImageFromS3, };
原自定义声明文件(无效)
declare module 'connect-awsS3' { import { Request, Response, NextFunction } from 'express'; interface File { key: string; location: string; } type Upload = (req: Request, res: Response, next: NextFunction) => void; type DeleteImageFromS3 = (key: string, type: string) => Promise<void>; const uploadCardImage: Upload; const deleteImageFromS3: DeleteImageFromS3; export { uploadCardImage, deleteImageFromS3 }; }
tsconfig.json配置
{ "extends": "@repo/typescript-config/base.json", "compilerOptions": { "lib": ["ES2015"], "outDir": "./dist", "rootDir": "./src", "esModuleInterop": true // Autres options de configuration spécifiques à votre projet... }, "exclude": ["node_modules"], "include": [ "src", "types" // Assurez-vous que "types" est bien inclus s'il s'agit du répertoire où se trouve connect-awsS3.d.ts ] }
解决方案
1. 修正自定义声明文件
原声明文件错误地声明了模块,正确做法是扩展Express的Request接口和Multer的File类型,添加key和location属性。在types文件夹下创建express.d.ts文件:
// types/express.d.ts declare namespace Express { interface Request { // 扩展req.file类型,添加multer-s3返回的key和location file?: import('multer').File & { key: string; location: string; }; // 保留自定义的invalidFileType属性 invalidFileType?: boolean; } } // 全局扩展multer的File类型,让所有使用multer的地方都能识别key和location declare module 'multer' { interface File { key?: string; location?: string; } }
2. 确保tsconfig.json正确包含类型文件
修改tsconfig.json的include字段,确保所有类型文件被正确识别:
"include": [ "src/**/*", "types/**/*.d.ts" ]
3. 重启TypeScript服务
如果使用VSCode,按Ctrl+Shift+P(Windows)或Cmd+Shift+P(Mac)执行TypeScript: Restart TS Server,确保新的类型声明被加载。
内容的提问来源于stack exchange,提问作者degen balls
相关产品推荐
相关产品推荐

