使用React Native、GraphQL和urql上传图片失败求助
React Native + GraphQL 图片上传失败问题处理记录
问题现象
使用React Native结合urql发起GraphQL mutation上传头像时,始终返回错误:[CombinedError: [Network] Network request failed]。服务器要求picture参数为MultipartFile类型,若传入非File类型数据,会收到明确报错:Variable 'picture' has an invalid value: Expected type org.springframework.web.multipart.MultipartFile but was java.util.LinkedHashMap]。
已尝试的无效方案
- 将图片转为Base64后构建Blob,再生成File对象传入mutation
- 尝试用FormData、
react-native-blob-util、rn-fetch-blob等工具处理文件上传 - 给请求添加
graphql-require-preflight请求头
相关代码片段
GraphQL Mutation定义
import { graphql } from '../../gql'; import { gql, useMutation } from 'urql'; const AddProfilePicture_Mutation = graphql(` mutation AddPicture_Mutation ($userId: ID!, $picture: Upload!) { uploadProfilePicture(input: { userId: $userId picture: $picture }) { id } }`); export const useAddProfilePicture = () => { const [{fetching, error}, execute] = useMutation(AddProfilePicture_Mutation); return { error: !!error, fetching, addProfilePicture: execute, } }
图片选择与上传逻辑
const pictureHandler = async () => { const options = { mediaType: 'photo' as MediaType, includeBase64: true, selectionLimit: 1, }; const profilePicture = await launchImageLibrary(options); if (profilePicture.assets?.[0].fileSize && profilePicture.assets?.[0].fileSize > MAXFILESIZE) { showError(t('profileScreen.PictureSize')); } if (profilePicture.assets?.[0].uri && profilePicture.assets[0].fileName && profilePicture.assets[0].type) { const blob = new Blob([profilePicture.assets[0].base64 as BlobPart], { type: profilePicture.assets[0].type, }); const file = new File([blob], profilePicture.assets[0].fileName, { type: `${profilePicture.assets[0].type}`}); const {error} = await addProfilePicture( { userId: userId!, picture: file}, { fetchOptions: { headers: { 'graphql-require-preflight': '' } } } ); if (!error) { showSuccess(t('profileScreen.PictureSuccessAdded')); navigation.navigate('UserProfile'); } else { console.log(error); showError(t('profileScreen.PictureErrorAdded')); } }; };
最终解决办法
经过多方排查仍未定位GraphQL上传失败的根源,最终通过后端新增专门的图片上传接口,使用普通fetch POST请求完成上传:
// 替代方案示例代码 const uploadViaRest = async (uri: string, fileName: string, mimeType: string, userId: string) => { const formData = new FormData(); formData.append('picture', { uri, name: fileName, type: mimeType } as any); formData.append('userId', userId); try { const response = await fetch('你的上传接口地址', { method: 'POST', body: formData, headers: { 'Content-Type': 'multipart/form-data', }, }); const result = await response.json(); if (response.ok) { showSuccess(t('profileScreen.PictureSuccessAdded')); navigation.navigate('UserProfile'); } else { throw new Error(result.message); } } catch (err) { showError(t('profileScreen.PictureErrorAdded')); console.error(err); } };
关键提示
- GraphQL文件上传依赖服务器对
multipart/form-data格式的GraphQL请求支持,比如Spring Boot后端需配置正确的文件上传解析器,同时urql客户端要启用对应文件上传插件(如urql-exchange-multipart-fetch) - React Native中的
File/Blob对象与Web环境存在差异,手动构建的File对象可能无法被正确序列化,可尝试用react-native-document-picker获取原生文件引用替代launchImageLibrary - 若GraphQL上传方案持续受阻,REST接口上传是更直接的替代方案,无需在文件上传场景下过度依赖GraphQL
内容的提问来源于stack exchange,提问作者gcollet
相关产品推荐
相关产品推荐

