Uppy与Apollo Server集成:Node.js GraphQL服务器的上传实现

【免费下载链接】uppy The next open source file uploader for web browsers :dog: 【免费下载链接】uppy 项目地址: https://gitcode.com/gh_mirrors/up/uppy

你还在为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集成的完整工作流程:

  1. 用户选择文件并添加元数据
  2. Uppy前端组件验证文件类型和大小
  3. 文件通过GraphQL API上传到Apollo Server
  4. 服务器处理文件并存储
  5. 返回上传结果给前端
  6. 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提供的丰富插件生态系统可以进一步扩展上传功能,如:

未来可以考虑添加的功能:

  • 文件上传权限控制
  • 上传文件的病毒扫描
  • 上传完成后的自动处理流程(如图片裁剪、格式转换)

希望本文对你的项目有所帮助!如果你有任何问题或建议,欢迎在项目的issues中提出。

【免费下载链接】uppy The next open source file uploader for web browsers :dog: 【免费下载链接】uppy 项目地址: https://gitcode.com/gh_mirrors/up/uppy

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐