Uppy与GraphQL.js集成:Node.js中的原生GraphQL上传

Uppy与GraphQL.js集成:Node.js中的原生GraphQL上传

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

在现代Web应用开发中,文件上传功能是用户体验的关键组成部分。传统的REST API上传方案往往需要额外的HTTP端点和复杂的状态管理,而GraphQL作为一种声明式查询语言,为解决这一痛点提供了全新的思路。本文将详细介绍如何在Node.js环境中实现Uppy与GraphQL.js的无缝集成,构建高效、灵活的文件上传系统。

技术架构概览

Uppy作为开源的现代文件上传组件,提供了直观的用户界面和强大的上传功能。通过与GraphQL.js的结合,我们可以实现:

  • 统一的API端点管理
  • 类型安全的文件上传接口
  • 与现有GraphQL查询/变更系统的无缝集成
  • 实时上传进度反馈

Uppy上传演示

核心技术栈包括:

准备工作

环境搭建

首先,确保您的项目中已安装必要的依赖:

npm install @uppy/core @uppy/xhr-upload graphql express-graphql formidable

项目结构

我们将基于Uppy的Node.js示例进行扩展,典型的项目结构如下:

examples/
└── graphql-upload/
    ├── client/           # Uppy前端实现
    │   ├── index.html    # 页面入口
    │   └── main.js       # Uppy配置
    └── server/
        ├── schema.js     # GraphQL模式定义
        └── server.js     # 服务器入口

后端实现:GraphQL文件上传

1. 配置GraphQL上传标量类型

创建GraphQL自定义标量类型来处理文件上传:

// server/schema.js
const { GraphQLScalarType, Kind } = require('graphql');

const GraphQLUpload = new GraphQLScalarType({
  name: 'Upload',
  description: '文件上传标量类型',
  parseValue(value) {
    return value; // 接收到的File对象
  },
  serialize(value) {
    return value;
  },
  parseLiteral(ast) {
    throw new Error('文件上传必须通过变量提供');
  },
});

2. 实现文件上传解析器

使用Formidable处理文件上传,并集成到GraphQL解析器中:

// server/schema.js
const { makeExecutableSchema } = require('@graphql-tools/schema');
const formidable = require('formidable');
const { createWriteStream } = require('fs');
const { mkdir } = require('fs/promises');
const { fileURLToPath } = require('url');
const path = require('path');

// 创建上传目录
const UPLOAD_DIR = path.join(__dirname, '../uploads/');
mkdir(UPLOAD_DIR, { recursive: true });

const typeDefs = `
  scalar Upload
  
  type File {
    id: ID!
    filename: String!
    mimetype: String!
    path: String!
    size: Int!
  }
  
  type Mutation {
    uploadFile(file: Upload!): File!
  }
`;

const resolvers = {
  Upload: GraphQLUpload,
  Mutation: {
    uploadFile: async (_, { file }) => {
      const { createReadStream, filename, mimetype, encoding } = await file;
      
      // 生成唯一文件名
      const uniqueFilename = `${Date.now()}-${filename}`;
      const filePath = path.join(UPLOAD_DIR, uniqueFilename);
      
      // 保存文件
      await new Promise((resolve, reject) => {
        const stream = createReadStream();
        const writeStream = createWriteStream(filePath);
        
        stream.pipe(writeStream);
        
        writeStream.on('finish', resolve);
        writeStream.on('error', reject);
      });
      
      // 获取文件大小
      const stats = await fs.promises.stat(filePath);
      
      return {
        id: Date.now().toString(),
        filename,
        mimetype,
        path: filePath,
        size: stats.size
      };
    }
  }
};

module.exports = makeExecutableSchema({ typeDefs, resolvers });

3. 配置GraphQL服务器

基于Express构建GraphQL服务器,配置文件上传处理中间件:

// server/server.js
const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const schema = require('./schema');
const formidable = require('formidable');

const app = express();

// 自定义文件上传中间件
app.use('/graphql', (req, res, next) => {
  if (req.method === 'POST' && req.headers['content-type'].startsWith('multipart/form-data')) {
    const form = formidable({ multiples: false });
    
    form.parse(req, (err, fields, files) => {
      if (err) {
        next(err);
        return;
      }
      
      // 将文件数据附加到请求对象
      req.body = {
        query: fields.query,
        variables: JSON.parse(fields.variables || '{}'),
        file: files.file
      };
      
      next();
    });
  } else {
    express.json()(req, res, next);
  }
});

// 配置GraphQL端点
app.use('/graphql', graphqlHTTP({
  schema,
  graphiql: true, // 启用GraphQL Playground
}));

const PORT = process.env.PORT || 4000;
app.listen(PORT, () => {
  console.log(`GraphQL服务器运行在 http://localhost:${PORT}/graphql`);
});

前端实现:Uppy配置

1. 自定义GraphQL上传插件

创建Uppy插件以支持GraphQL上传:

// client/GraphQLUpload.js
import { Plugin } from '@uppy/core';

export default class GraphQLUpload extends Plugin {
  constructor(uppy, opts) {
    super(uppy, opts);
    this.id = 'GraphQLUpload';
    this.opts = {
      endpoint: '/graphql',
      ...opts,
    };
  }

  uploadFile(file) {
    const { endpoint } = this.opts;
    const formData = new FormData();
    
    // 构建GraphQL查询
    const query = `
      mutation UploadFile($file: Upload!) {
        uploadFile(file: $file) {
          id
          filename
          mimetype
          path
          size
        }
      }
    `;
    
    // 准备FormData
    formData.append('query', query);
    formData.append('variables', JSON.stringify({
      file: null // 将在请求中被实际文件替换
    }));
    formData.append('file', file.data, file.name);
    
    // 创建上传请求
    const xhr = new XMLHttpRequest();
    xhr.open('POST', endpoint);
    
    // 处理上传进度
    xhr.upload.addEventListener('progress', (e) => {
      if (e.lengthComputable) {
        const progress = e.loaded / e.total;
        this.uppy.emit('upload-progress', file.id, progress);
      }
    });
    
    // 处理完成
    return new Promise((resolve, reject) => {
      xhr.addEventListener('load', () => {
        if (xhr.status >= 200 && xhr.status < 300) {
          const response = JSON.parse(xhr.responseText);
          if (response.errors) {
            reject(new Error(response.errors[0].message));
          } else {
            resolve(response.data.uploadFile);
          }
        } else {
          reject(new Error(`上传失败: ${xhr.statusText}`));
        }
      });
      
      xhr.addEventListener('error', () => {
        reject(new Error('网络错误'));
      });
      
      xhr.send(formData);
    });
  }
}

2. 初始化Uppy实例

集成自定义GraphQL上传插件到Uppy:

// client/main.js
import Uppy from '@uppy/core';
import Dashboard from '@uppy/dashboard';
import Webcam from '@uppy/webcam';
import GraphQLUpload from './GraphQLUpload';
import '@uppy/core/dist/style.css';
import '@uppy/dashboard/dist/style.css';
import '@uppy/webcam/dist/style.css';

const uppy = new Uppy({
  debug: true,
  autoProceed: false,
  restrictions: {
    maxFileSize: 10000000, // 10MB
    maxNumberOfFiles: 5,
    allowedFileTypes: ['image/*', 'application/pdf']
  }
});

// 使用Uppy插件
uppy.use(Dashboard, {
  inline: true,
  target: '#uppy-container',
  height: 470,
  metaFields: [
    { id: 'name', name: '文件名', placeholder: '输入文件名' }
  ]
});

uppy.use(Webcam, { target: Dashboard });
uppy.use(GraphQLUpload, {
  endpoint: 'http://localhost:4000/graphql'
});

// 处理上传完成事件
uppy.on('complete', (result) => {
  console.log('上传完成:', result.successful);
});

3. 创建前端页面

<!-- client/index.html -->
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Uppy GraphQL上传示例</title>
</head>
<body>
  <h1>文件上传系统</h1>
  <div id="uppy-container"></div>
  <script type="module" src="./main.js"></script>
</body>
</html>

完整集成示例

项目结构

完整的项目结构参考Uppy官方示例架构:

examples/graphql-upload/
├── client/
│   ├── index.html
│   ├── main.js
│   └── GraphQLUpload.js
├── server/
│   ├── schema.js
│   └── server.js
├── uploads/
└── package.json

运行与测试

  1. 启动服务器:
node server/server.js
  1. 启动前端开发服务器:
npx serve client/
  1. 访问GraphQL Playground进行测试:
http://localhost:4000/graphql
  1. 使用以下mutation测试文件上传:
mutation UploadFile($file: Upload!) {
  uploadFile(file: $file) {
    id
    filename
    mimetype
    size
  }
}

高级特性与最佳实践

多文件上传

扩展GraphQL模式以支持多文件上传:

type Mutation {
  uploadFiles(files: [Upload!]!): [File!]!
}

相应的解析器实现:

uploadFiles: async (_, { files }) => {
  // 并行处理多个文件
  return Promise.all(files.map(handleSingleFileUpload));
}

文件验证

在上传前添加文件验证逻辑:

// 在resolvers中
uploadFile: async (_, { file }) => {
  const { filename, mimetype } = await file;
  
  // 验证文件类型
  const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];
  if (!allowedTypes.includes(mimetype)) {
    throw new Error(`不支持的文件类型: ${mimetype}`);
  }
  
  // 验证文件大小
  // ...
  
  // 继续上传流程
  // ...
}

安全考虑

  1. 文件存储安全

    • 始终验证文件类型(不仅通过扩展名)
    • 使用安全的文件存储位置,避免直接暴露在Web根目录
    • 考虑使用云存储服务如AWS S3
  2. 访问控制

    • 在GraphQL解析器中集成身份验证检查
    • 实现细粒度的文件访问权限控制
  3. 输入验证

    • 使用GraphQL输入类型验证文件元数据
    • 实现文件内容扫描以防止恶意文件

总结与扩展

通过本文介绍的方法,我们成功实现了Uppy与GraphQL.js的原生集成,构建了一个类型安全、接口统一的文件上传系统。这一方案相比传统的REST上传方式具有以下优势:

  • 减少API端点数量,简化前后端交互
  • 强类型定义带来更好的开发体验和错误检查
  • 与现有GraphQL生态系统无缝集成
  • 灵活的文件处理流程,支持复杂业务需求

后续扩展方向

  1. 断点续传:结合Uppy的断点续传功能和GraphQL的订阅功能,实现大文件断点续传
  2. 实时通知:使用GraphQL订阅推送文件处理进度和结果
  3. 文件转换:集成文件处理服务,在上传后自动进行格式转换或压缩

Uppy作为功能丰富的上传组件,其生态系统还提供了许多高级功能,如:

希望本文能为您的项目提供有价值的参考,实现高效、可靠的文件上传体验。

参考资源

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

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值