如何编程将外部URL的PDF文件上传至自定义内容类型文章
BODS自定义内容类型存储外部URL指向PDF的编程实现
你之前用的图片上传方案不能直接复用,核心原因是BODS的图片处理链路默认绑定了image/*类型白名单、缩略图生成、EXIF信息读取逻辑,PDF属于application/pdf类型,直接调用图片接口会触发类型校验拦截,而且文件字段如果用了图片专用存储驱动,也无法识别非图片格式的文件实体。
前置配置调整
先改自定义内容类型的文件字段配置,不然代码写了也会被拦截:
- 找到你添加的文件字段,在允许上传的MIME类型列表里新增
application/pdf - 关闭字段的「强制本地文件上传校验」开关,开启「允许外部资源引用」选项(对应版本有该配置项时操作)
- 将字段的存储驱动从「图片专用存储」切换为「通用文件存储」,跳过图片专属处理流程
具体实现逻辑
1. 拉取并校验外部PDF资源
不要直接把外部URL存进字段,先做本地拉取校验,规避死链、伪造文件问题:
$externalPdfUrl = "目标PDF的外部地址"; // 拉取资源 $ch = curl_init($externalPdfUrl); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_TIMEOUT, 10); $fileContent = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE); curl_close($ch); // 合法性校验 if ($httpCode != 200) { throw new \Exception("外部PDF拉取失败,HTTP状态码:{$httpCode}"); } if (!str_contains($contentType, 'application/pdf')) { throw new \Exception("目标资源不是PDF格式,实际类型:{$contentType}"); } // 校验文件头,防后缀伪造 if (!str_starts_with($fileContent, '%PDF')) { throw new \Exception("文件内容不符合PDF格式规范"); }
2. 写入通用文件存储
调用通用文件存储接口,不要用图片专用的存储方法:
// 生成唯一文件名,避免重名覆盖 $fileName = md5($externalPdfUrl . time()) . '.pdf'; // 写入通用文件存储 $fileEntity = \BODS\File\FileService::saveGeneralFile( content: $fileContent, fileName: $fileName, mimeType: 'application/pdf', meta: [ 'source_url' => $externalPdfUrl, 'is_external_source' => 1 ] );
3. 关联到自定义内容类型字段
拿到文件实体ID后,绑定到对应内容的文件字段上:
// 初始化自定义内容类型实体 $contentEntity = \BODS\Content\ContentService::createEntity('自定义内容类型的机器名'); // 给文件字段赋值,注意传入通用文件实体ID,不要传图片实体ID $contentEntity->setFieldValue('文件字段的机器名', [$fileEntity->id]); $contentEntity->save();
常见踩坑
- 不要直接把外部URL字符串赋值给文件字段:BODS文件字段默认关联本地文件实体表,直接存URL会导致前端渲染失败、权限校验失效
- 50M以上的大PDF不要直接拉取到内存,改用临时文件流式写入,避免内存溢出
- 文件存储完成后,要给文件实体配置和内容类型一致的访问权限,否则前台会提示无权限访问
- SaaS版无服务端代码权限的场景,可以走webhook+开放接口实现:触发流程时先调用通用文件上传接口传PDF拿到文件ID,再调用内容更新接口绑定ID即可,逻辑和上述代码一致
内容的提问来源于stack exchange,提问作者Ferland
相关产品推荐
相关产品推荐

