RagApp_API
  1. 文档管理 /documents
RagApp_API
  • 概述
  • 息壤大模型名称列表
  • 数据库连接信息
  • MinerU的APIToken仅14天
  • 用户(弃用)
    • 注册
      POST
    • 登录
      POST
    • 验证邮箱
      GET
    • 重发验证邮件
      POST
    • 获取当前用户信息
      GET
    • 测试用户登录状态
      GET
  • 开发测试接口
    • 简单对话
      POST
    • 简单对话+上下文
      POST
    • 网络延迟测试
      POST
  • RAG 聊天
    • [说明] 关于User-ID
    • 文档管理 /documents
      • [说明] 文件上传指南
      • 获取预签名上传 URL
        POST
      • 确认文件上传
        POST
      • (传统)上传文档
        POST
      • 查询文档处理状态
        GET
      • 重新触发索引
        POST
      • 获取文档列表
        GET
      • 获取文档详情
        GET
      • 删除文档
        DELETE
    • 对话 /rag
      • [说明] 完整使用流程示例
      • 单轮 RAG 查询
      • 多轮对话 RAG
      • (dev) 对话
  1. 文档管理 /documents

[说明] 文件上传指南

Generated by 克劳德 Opus 4.5😂
本文档说明了前端如何通过直接上传到 OSS 的方法,绕过 Vercel 限制,实现 >4.5MB 的文档导入到我们后端的RAG知识库里面。
参考链接
阿里云OSS用户指南:使用预签名URL上传文件
https://help.aliyun.com/zh/oss/user-guide/upload-files-using-presigned-urls#392d9bf073h38
image.png

上传流程概述#

由于后端部署在 Vercel Functions,存在 4.5MB 请求体限制,我们采用 前端直传 OSS 方案。
┌─────────────┐      ┌─────────────┐      ┌─────────────┐
│     前端    │      │   后端 API   │      │  阿里云 OSS  │
└──────┬──────┘      └──────┬──────┘      └──────┬──────┘
       │                    │                    │
       │ 1. POST /presign   │                    │
       │ ──────────────────>│                    │
       │                    │                    │
       │ 2. 返回签名URL      │                    │
       │ <──────────────────│                    │
       │                    │                    │
       │ 3. PUT 直传文件     │                    │
       │ ───────────────────────────────────────>│
       │                    │                    │
       │ 4. 上传成功         │                    │
       │ <───────────────────────────────────────│
       │                    │                    │
       │ 5. POST /confirm   │                    │
       │ ──────────────────>│                    │
       │                    │                    │
       │ 6. 开始索引         │                    │
       │ <──────────────────│                    │
       │                    │                    │
       │ 7. 轮询状态         │                    │
       │ ──────────────────>│                    │
       │                    │                    │

为什么需要这个方案?#

传统上传流程是:前端 → 后端 → OSS。但 Vercel Functions 限制请求体最大 4.5MB,超过会返回 413 FUNCTION_PAYLOAD_TOO_LARGE 错误。
新方案让前端直接上传到 OSS,绕过 Vercel 限制,支持最大 200MB 文件。

流程总结#

步骤方向接口说明
1前端 → 后端POST /documents/presign获取签名上传 URL
2前端 → OSSPUT {upload_url}直传文件(绕过 Vercel)
3前端 → 后端POST /documents/{id}/confirm确认上传,触发索引
4前端 → 后端GET /documents/{id}/status轮询索引状态

依赖安装#

完整代码实现#

1. 上传服务封装#

2. React 组件示例#

// components/DocumentUploader.tsx

import React, { useState } from 'react';
import {
  View,
  Text,
  TouchableOpacity,
  StyleSheet,
  ActivityIndicator,
  Alert,
} from 'react-native';
import { pickAndUploadDocument } from '../services/uploadService';

type UploadState = 'idle' | 'selecting' | 'uploading' | 'processing' | 'completed' | 'failed';

export function DocumentUploader() {
  const [state, setState] = useState<UploadState>('idle');
  const [progress, setProgress] = useState(0);
  const [statusText, setStatusText] = useState('');
  const [documentId, setDocumentId] = useState<number | null>(null);

  const handleUpload = async () => {
    try {
      setState('selecting');
      setProgress(0);
      setStatusText('选择文件...');

      const result = await pickAndUploadDocument({
        onProgress: (p) => {
          setState('uploading');
          setProgress(p);
          setStatusText(`上传中 ${Math.round(p * 100)}%`);
        },
        onStatusChange: (status) => {
          setDocumentId(status.document_id);

          switch (status.status) {
            case 'pending':
              setState('processing');
              setStatusText('等待处理...');
              break;
            case 'processing':
              setState('processing');
              setStatusText('正在索引文档...');
              break;
            case 'completed':
              setState('completed');
              setStatusText(`索引完成!共 ${status.chunk_count} 个分块`);
              break;
            case 'failed':
              setState('failed');
              setStatusText(`索引失败: ${status.error_message}`);
              break;
          }
        },
      });

      if (result.status === 'completed') {
        Alert.alert('成功', '文档上传并索引完成,现在可以进行 RAG 查询了');
      } else {
        Alert.alert('失败', result.error_message || '索引失败');
      }
    } catch (error: any) {
      setState('failed');
      setStatusText(error.message);
      Alert.alert('错误', error.message);
    }
  };

  const renderContent = () => {
    switch (state) {
      case 'idle':
        return (
          <TouchableOpacity style={styles.button} onPress={handleUpload}>
            <Text style={styles.buttonText}>选择文件上传</Text>
          </TouchableOpacity>
        );

      case 'selecting':
        return (
          <View style={styles.statusContainer}>
            <ActivityIndicator size="large" color="#007AFF" />
            <Text style={styles.statusText}>选择文件...</Text>
          </View>
        );

      case 'uploading':
        return (
          <View style={styles.statusContainer}>
            <View style={styles.progressBar}>
              <View style={[styles.progressFill, { width: `${progress * 100}%` }]} />
            </View>
            <Text style={styles.statusText}>{statusText}</Text>
          </View>
        );

      case 'processing':
        return (
          <View style={styles.statusContainer}>
            <ActivityIndicator size="large" color="#007AFF" />
            <Text style={styles.statusText}>{statusText}</Text>
          </View>
        );

      case 'completed':
        return (
          <View style={styles.statusContainer}>
            <Text style={styles.successText}>✓ {statusText}</Text>
            <TouchableOpacity style={styles.button} onPress={handleUpload}>
              <Text style={styles.buttonText}>上传另一个文件</Text>
            </TouchableOpacity>
          </View>
        );

      case 'failed':
        return (
          <View style={styles.statusContainer}>
            <Text style={styles.errorText}>✗ {statusText}</Text>
            <TouchableOpacity style={styles.button} onPress={handleUpload}>
              <Text style={styles.buttonText}>重试</Text>
            </TouchableOpacity>
          </View>
        );
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>文档上传</Text>
      <Text style={styles.subtitle}>
        支持格式: PDF, Word, PPT, 图片, TXT, Markdown
      </Text>
      <Text style={styles.subtitle}>最大文件大小: 200MB</Text>
      {renderContent()}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    padding: 20,
    alignItems: 'center',
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    marginBottom: 8,
  },
  subtitle: {
    fontSize: 14,
    color: '#666',
    marginBottom: 4,
  },
  button: {
    backgroundColor: '#007AFF',
    paddingHorizontal: 24,
    paddingVertical: 12,
    borderRadius: 8,
    marginTop: 20,
  },
  buttonText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '600',
  },
  statusContainer: {
    alignItems: 'center',
    marginTop: 20,
    width: '100%',
  },
  statusText: {
    marginTop: 12,
    fontSize: 14,
    color: '#333',
  },
  successText: {
    fontSize: 16,
    color: '#34C759',
    fontWeight: '600',
    marginBottom: 12,
  },
  errorText: {
    fontSize: 16,
    color: '#FF3B30',
    fontWeight: '600',
    marginBottom: 12,
  },
  progressBar: {
    width: '100%',
    height: 8,
    backgroundColor: '#E5E5EA',
    borderRadius: 4,
    overflow: 'hidden',
  },
  progressFill: {
    height: '100%',
    backgroundColor: '#007AFF',
  },
});

3. 使用 Hook 封装(可选)#

API 接口说明#

POST /documents/presign#

获取预签名上传 URL。
请求体:
{
  "filename": "document.pdf",
  "file_size": 10485760,
  "content_type": "application/pdf"
}
其中这个 content_type 怎么填,参见 #支持的文件类型
响应:
{
  "document_id": 1,
  "upload_url": "https://bucket.oss-cn-xxx.aliyuncs.com/...",
  "oss_path": "users/123/documents/1/document.pdf",
  "expires_in": 600
}

PUT {upload_url}#

直接上传文件到 OSS(使用上一步返回的 upload_url)。
请求头:
Content-Type: application/pdf  // 与 presign 请求中的 content_type 一致
请求体: 文件二进制内容

POST /documents/{document_id}/confirm#

确认文件已上传,触发后台索引。
响应:
{
  "document_id": 1,
  "filename": "document.pdf",
  "file_size": 10485760,
  "status": "pending",
  "message": "上传确认成功,正在后台处理索引"
}

GET /documents/{document_id}/status#

查询文档处理状态。
响应:
{
  "document_id": 1,
  "status": "completed",  // pending | processing | completed | failed
  "chunk_count": 45,
  "error_message": null
}

支持的文件类型#

扩展名MIME 类型说明
.pdfapplication/pdfPDF 文档
.docapplication/mswordWord 97-2003
.docxapplication/vnd.openxmlformats-officedocument.wordprocessingml.documentWord 2007+
.pptapplication/vnd.ms-powerpointPPT 97-2003
.pptxapplication/vnd.openxmlformats-officedocument.presentationml.presentationPPT 2007+
.pngimage/pngPNG 图片
.jpg/.jpegimage/jpegJPEG 图片
.txttext/plain纯文本
.mdtext/markdownMarkdown

常见问题#

1. 上传失败:签名 URL 过期#

签名 URL 有效期为 10 分钟。如果用户选择文件后长时间未上传,需要重新调用 /presign 获取新的 URL。

2. OSS 上传报 CORS 错误(我的 OSS 没限制跨域,放心)#

OSS 控制台配置 CORS 规则:
来源:* 或你的 App 域名
允许方法:PUT
允许头:Content-Type
暴露头:ETag

3. 大文件上传超时#

对于大文件,建议:
增加 fetch/RNFS 的超时时间
显示上传进度,让用户知道上传正在进行

4. 索引时间过长#

文档索引时间取决于文件大小和内容复杂度。建议:
轮询间隔设为 2-3 秒
设置合理的超时时间(如 2 分钟)
提供"后台处理"选项,让用户可以离开页面

错误处理建议#

修改于 2025-12-03 14:27:46
上一页
[说明] 关于User-ID
下一页
获取预签名上传 URL
Built with