GraphQL 文件上传必须用 multipart 请求,因原生不支持二进制数据;需用 Upload! 类型、服务端异步解析XML,禁用XXE,限制文件大小并校验编码与命名空间。
GraphQL 原生不支持文件上传,query 和 mutation 的 JSON body 无法携带二进制数据。必须改用 multipart/form-data 编码,把操作定义(operations)和文件(map)分块发送。

常见错误是直接在 mutation 中传 Base64 字符串——这会显著放大传输体积、增加内存压力、且服务端解析成本高,不推荐用于 >1MB 的文件。
graphql-request 配合 FormData,或 apollo-upload-client(已适配 Apollo Client v3+)graphql-upload 并调用 processRequest;Nexus/Envelop 等框架也有对应插件upload 类型必须显式声明为 Upload!(注意感叹号),不能用 String 或自定义 scalar 模拟把 XML 当作字符串字段(xmlContent: String!)提交,看似简单,实则埋下隐患:服务端无法校验结构、无法复用 XML Schema、无法流式解析大文件、也无法与现有 XML 工具链(如 XSLT、XPath)自然衔接。
真正合理的做法是「分离关注点」:GraphQL 只负责调度和元数据管理,XML 处理交给专用模块。
Upload! 接收文件,保存为临时路径或对象存储 URLlibxml2、lxml 或 xmldom 解析该 XML,提取关键字段入库或触发业务逻辑xmlPreview),且限制长度、做字符转义,避免注入风险不要试图让一个 mutation 同时完成上传 + 解析 + 存储 + 返回完整 XML 结构。拆成两步更可控:
# 步骤 1:上传
mutation UploadXml($file: Upload!) {
uploadXml(file: $file) {
id
filename
status # "uploaded" | "processing"
}
}
步骤 2:轮询或订阅结果(可选)
subscription XmlProcessingStatus($id: ID!) {
xmlProcessingStatus(id: $id) {
status # "success" | "failed"
errors
}
}
关键点:
uploadXml resolver 返回后立即响应,不阻塞;XML 解析必须异步fs.readFileSync 或 DOMParser.parseFromString —— Node.js 单线程会被卡住timeoutMs: Int = 5000 参数,内部用 Promise.race 控制最长等待,超时则返回 status: "queued"
文件上传 + XML 解析组合是典型的攻击面叠加区。以下三点常被跳过但至关重要:
maxFileSize(如 10MB),并在 graphql-upload 初始化时设置,而非仅靠 GraphQL schema 的 String @length(max: 10000000)
xxe),例如 lxml 要设 resolve_entities=False,libxmljs 要关 optionParseExternalEntities
utf8mb4)且长度足够XML 的命名空间、编码声明、DOCTYPE 声明在上传过程中可能被篡改或丢失,解析前务必检查 是否存在且一致。