Uppy与Apollo Server集成:Node.js GraphQL服务器的上传实现
·
Uppy与Apollo Server集成:Node.js GraphQL服务器的上传实现
你还在为GraphQL文件上传功能头疼吗?用户上传文件时进度不明确?刷新页面后上传状态丢失?本文将带你通过Uppy与Apollo Server的无缝集成,解决这些问题。读完你将获得:
- 完整的GraphQL文件上传实现方案
- Uppy前端组件的最佳配置实践
- 文件上传状态持久化处理技巧
- 分块上传与断点续传的实现方法
集成准备工作
在开始集成前,请确保你的开发环境满足以下要求:
- Node.js 16.x或更高版本
- npm或yarn包管理器
- 已安装Apollo Server的Node.js项目
首先克隆项目仓库:
git clone https://github.com/transloadit/uppy.git
cd uppy
安装必要的依赖包:
npm install @uppy/core @uppy/xhr-upload apollo-server-express graphql
后端实现:Apollo Server配置
设置文件上传标量类型
创建GraphQL上传标量类型定义文件:
# src/graphql/scalars/Upload.ts
import { GraphQLScalarType } from 'graphql';
import { ReadStream } from 'fs';
export interface FileUpload {
filename: string;
mimetype: string;
encoding: string;
createReadStream: () => ReadStream;
}
export const UploadScalar = new GraphQLScalarType({
name: 'Upload',
description: '文件上传标量类型',
parseValue(value: Promise<FileUpload>): Promise<FileUpload> {
return value;
},
serialize() {
throw new Error('Upload scalar cannot be serialized');
},
parseLiteral() {
throw new Error('Upload scalar cannot be parsed from AST');
},
});
实现上传解析器
创建文件上传解析器:
// src/resolvers/fileUploadResolver.js
const { createWriteStream } = require('fs');
const { join } = require('path');
const UPLOAD_DIR = join(__dirname, '../../uploads');
// 确保上传目录存在
if (!fs.existsSync(UPLOAD_DIR)) {
fs.mkdirSync(UPLOAD_DIR, { recursive: true });
}
const fileUploadResolver = {
Mutation: {
singleUpload: async (_, { file }) => {
const { createReadStream, filename, mimetype, encoding } = await file;
const stream = createReadStream();
const path = join(UPLOAD_DIR, filename);
await new Promise((resolve, reject) => {
const writeStream = createWriteStream(path);
stream.pipe(writeStream);
writeStream.on('finish', resolve);
writeStream.on('error', reject);
});
return {
filename,
mimetype,
encoding,
url: `/uploads/${filename}`
};
},
},
};
module.exports = fileUploadResolver;
配置Apollo Server
修改Apollo Server配置以支持文件上传:
// src/server.js
const { ApolloServer } = require('apollo-server-express');
const express = require('express');
const { graphqlUploadExpress } = require('graphql-upload');
const typeDefs = require('./graphql/typeDefs');
const resolvers = require('./resolvers');
const { UploadScalar } = require('./graphql/scalars/Upload');
async function startServer() {
const app = express();
// 配置文件上传中间件
app.use(graphqlUploadExpress());
const server = new ApolloServer({
typeDefs,
resolvers: {
...resolvers,
Upload: UploadScalar
},
context: ({ req }) => ({ req }),
});
await server.start();
server.applyMiddleware({ app });
app.use('/uploads', express.static('uploads'));
const PORT = process.env.PORT || 4000;
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}${server.graphqlPath}`);
});
}
startServer();
前端实现:Uppy组件配置
基础Uppy配置
创建Uppy实例并配置XHR上传插件:
// src/components/FileUploader.js
import Uppy from '@uppy/core';
import XHRUpload from '@uppy/xhr-upload';
import Dashboard from '@uppy/dashboard';
import '@uppy/core/dist/style.css';
import '@uppy/dashboard/dist/style.css';
export function initUppy() {
const uppy = new Uppy({
debug: true,
autoProceed: false,
allowMultipleUploads: true,
// 持久化上传状态,防止页面刷新丢失
restore: {
enabled: true,
id: 'apollo-uppy-integration',
},
})
.use(Dashboard, {
inline: true,
target: '#uppy-dashboard',
replaceTargetContent: true,
showProgressDetails: true,
note: '支持拖拽文件到此处或点击选择文件',
height: 470,
metaFields: [
{ id: 'name', name: '文件名', placeholder: '输入文件名' },
{ id: 'description', name: '描述', placeholder: '输入文件描述' },
],
})
.use(XHRUpload, {
endpoint: '/graphql',
method: 'post',
headers: {
'Accept': 'application/json',
},
// 自定义上传请求格式以适应GraphQL
prepareUpload: (file) => {
const formData = new FormData();
const operations = JSON.stringify({
query: `
mutation SingleUpload($file: Upload!) {
singleUpload(file: $file) {
filename
mimetype
encoding
url
}
}
`,
variables: { file: null }
});
const map = JSON.stringify({
'0': ['variables.file']
});
formData.append('operations', operations);
formData.append('map', map);
formData.append('0', file.data, file.name);
return { formData };
},
});
uppy.on('complete', (result) => {
console.log('Upload complete! We’ve uploaded these files:', result.successful);
// 处理上传完成后的逻辑
});
return uppy;
}
在React组件中使用Uppy
// src/components/UploadComponent.jsx
import React, { useEffect, useRef } from 'react';
import { initUppy } from './FileUploader';
const UploadComponent = () => {
const uppyRef = useRef(null);
useEffect(() => {
uppyRef.current = initUppy();
return () => {
uppyRef.current.close();
};
}, []);
return (
<div className="upload-container">
<h2>文件上传</h2>
<div id="uppy-dashboard" />
</div>
);
};
export default UploadComponent;
高级功能实现
文件上传进度显示
Uppy提供了丰富的事件系统,可以轻松实现上传进度显示:
uppy.on('upload-progress', (file, progress) => {
console.log(`文件 ${file.name} 上传进度: ${Math.round(progress.percentage)}%`);
// 更新UI显示进度
});
uppy.on('file-added', (file) => {
console.log('新增文件:', file);
});
uppy.on('file-removed', (file) => {
console.log('移除文件:', file);
});
uppy.on('error', (error) => {
console.error('上传错误:', error);
});
分块上传大文件
对于大文件上传,启用Uppy的分块上传功能:
import TUS from '@uppy/tus';
import '@uppy/tus/dist/style.css';
uppy.use(TUS, {
endpoint: 'https://your-tus-server.com/files/', // 可以使用单独的TUS服务器
chunkSize: 5 * 1024 * 1024, // 5MB分块大小
retryDelays: [0, 1000, 3000, 5000],
metadata: {
filename: (file) => file.name,
filetype: (file) => file.type,
},
onShouldRetry: (err, retryCount) => {
if (err.status >= 500 || err.status === 429) {
// 服务器错误或请求过于频繁时重试
return retryCount < 3;
}
return false;
},
});
完整集成示例
以下是Uppy与Apollo Server集成的完整工作流程:
- 用户选择文件并添加元数据
- Uppy前端组件验证文件类型和大小
- 文件通过GraphQL API上传到Apollo Server
- 服务器处理文件并存储
- 返回上传结果给前端
- Uppy显示上传完成状态
常见问题解决方案
跨域问题处理
如果你的前端和后端不在同一域名下,需要在Apollo Server中配置CORS:
// 在ApolloServer配置中添加
cors: {
origin: 'http://localhost:3000', // 前端应用地址
credentials: true,
},
上传状态持久化
Uppy的Golden Retriever插件可以帮助你在页面刷新后恢复上传状态:
import GoldenRetriever from '@uppy/golden-retriever';
uppy.use(GoldenRetriever, {
serviceWorker: true,
indexedDB: true,
});
文件类型和大小限制
在Uppy初始化时配置文件验证规则:
const uppy = new Uppy({
restrictions: {
maxFileSize: 100 * 1024 * 1024, // 最大100MB
maxNumberOfFiles: 10,
allowedFileTypes: ['image/*', 'application/pdf', 'video/*'],
},
})
总结与展望
通过本文的步骤,你已经成功实现了Uppy与Apollo Server的集成,为你的GraphQL应用添加了强大的文件上传功能。Uppy提供的丰富插件生态系统可以进一步扩展上传功能,如:
- @uppy/image-editor:图片编辑功能
- @uppy/compressor:图片压缩
- @uppy/webcam:摄像头拍摄上传
未来可以考虑添加的功能:
- 文件上传权限控制
- 上传文件的病毒扫描
- 上传完成后的自动处理流程(如图片裁剪、格式转换)
希望本文对你的项目有所帮助!如果你有任何问题或建议,欢迎在项目的issues中提出。
更多推荐

所有评论(0)