问题
Think i tried everything, but nothing seems to work. I have a React app, and i want to upload pictures to a folder. I am using Apollo server and Graphql.
The weird thing is that if the file is less than 17 kb, it works!. But anything bigger than that, the file gets corrupted. I can see the image file in the folder but it is 0kb, and when i try to open it it says that the image contains errors". I don't have a limit to size of items.
The resolver: I tried using a different method of doing this but i got the same issue, so i don't believe this is the issue.
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const { UserInputError } = require('apollo-server');
const path = require('path');
const fs = require('fs');
async uploadFile(_, { file }) {
const { createReadStream, filename, mimetype, encoding } = await file;
const stream = createReadStream();
const pathName = path.join(__dirname, "../../public/images/", filename);
await stream.pipe(fs.createWriteStream(pathName));
return {
url: `http://localhost:5000/images/${filename}`,
}
}
Type definitions:
type File {
url: String!
filename: String!
mimetype: String!
encoding: String!
}
type Mutation {
uploadFile(file: Upload!): File!
}
The Apollo provider:
import React from 'react';
import App from './App';
import ApolloClient from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
/* import { createHttpLink } from 'apollo-link-http'; */
import { ApolloProvider } from '@apollo/react-hooks';
import { setContext } from 'apollo-link-context';
import { createUploadLink } from 'apollo-upload-client';
const uploadLink = createUploadLink({
uri: 'http://localhost:5000/graphql'
});
const authLink = setContext(() => {
const token = localStorage.getItem('jwtToken');
return {
headers: {
Authorization: token ? `Bearer ${token}` : ''
}
};
});
const client = new ApolloClient({
link: authLink.concat(uploadLink),
cache: new InMemoryCache(),
onError: ({ networkError, graphQLErrors }) => {
console.log( 'graphQLErrors', graphQLErrors)
console.log( 'networkError', networkError)
}
});
export default (
<ApolloProvider client={client}>
<App />
</ApolloProvider>
);
The Index.js on the server:
const { ApolloServer, PubSub } = require('apollo-server-express');
const express = require('express');
const cors = require('cors');
const mongoose = require('mongoose');
const typeDefs = require('./graphql/typeDefs');
const resolvers = require('./graphql/resolvers');
const { MONGODB } = require('./config.js');
const pubsub = new PubSub();
const PORT = process.env.PORT || 5000;
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => ({ req, pubsub })
});
const app = express();
server.applyMiddleware({ app });
app.use(express.static('public'));
app.use(cors());
mongoose
.connect(MONGODB, { useNewUrlParser: true })
.then(() => {
console.log('MongoDB Connected');
return app.listen({ port: PORT }, () => {
console.log(`Server running at http://localhost:5000/ Graphql Playground at http://localhost:5000/graphql `);
});
})
.catch(err => {
console.error(err)
})
I tried doing the input form differently but i got the same issue so i don't think the issue is here, but i don't know.
import React, { useCallback } from 'react';
import { useDropzone } from 'react-dropzone';
import { useMutation, gql } from '@apollo/client';
function UploadForm() {
const [uploadFile] = useMutation(UPLOAD_FILE_MUTATION, {
onCompleted: data => console.log(data),
});
const onDrop = useCallback(
([file]) => {
uploadFile({ variables: { file } });
},
[uploadFile]
);
const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop });
return (
<div {...getRootProps()}>
<input {...getInputProps()}/>
{isDragActive ? (
<p>Drop the files here!</p>
) : (
<p>Drag and drop files here, or click to select files</p>
)}
</div>
)
}
const UPLOAD_FILE_MUTATION = gql`
mutation uploadFile($file: Upload!){
uploadFile(file: $file){
url
}
}
`
export default UploadForm;
I suspect the issue is in the index.js or the Apollo provider, but i really don't know. Thanks.
回答1:
"async/await only works with promises, not with streams" - xadm
Solution:
Changed the resolver to this:
const path = require('path');
const fs = require('fs');
const Image = require('../../models/Image');
const files = [];
module.exports = {
Mutation: {
async uploadProfilePic(_, { file }, context) {
const { createReadStream, filename, mimetype, encoding } = await file;
const { ext } = path.parse(filename);
const randomName = generateRandomString(12) + ext;
files.push(randomName);
console.log(file);
const storeUpload = async ({ stream, filename, mimetype, encoding }) => {
const path = `public/images/${randomName}`
return new Promise((resolve, reject) =>
stream
.pipe(fs.createWriteStream(path))
.on("finish", () => resolve({
category: "profilePic",
url: `http://localhost:5000/images/${randomName}`,
path,
filename: randomName,
mimetype,
encoding,
createdAt: new Date().toISOString(),
commentsCount: 0,
likesCount: 0,
sharesCount: 0
}))
.on("error", reject)
);
};
const processUpload = async (upload) => {
const { createReadStream, filename, mimetype, encoding } = await upload;
const stream = createReadStream();
const file = await storeUpload({ stream, filename, mimetype, encoding });
return file;
};
const upload = await processUpload(file);
return {
url: `http://localhost:5000/images/${randomName}`,
};
}
}
};
来源:https://stackoverflow.com/questions/65222358/files-bigger-than-16-kb-gets-corrupted-when-uploaded-using-apollo-and-graphql