如何在PL/SQL中通过apex_web_service上传Blob至Alfresco服务器?
问题:Apex PL/SQL发送BLOB至Alfresco时,multipart/form-data请求报错
场景描述
在云端Apex中执行PL/SQL脚本,需从数据库表读取BLOB对象,通过带基础认证的POST请求发送到Alfresco服务器。用Postman/Insomnia发起请求的步骤为:
- 将
Content-Type设置为multipart/form-data - 请求体添加名为
filedata的键,值选择本地文件 - 以POST方式发送并携带基础认证信息
我的代码
尝试用APEX_WEB_SERVICE模块复现操作,编写代码如下:
DECLARE f_attachment BLOB; f_response CLOB; v_login VARCHAR2(100); v_password VARCHAR2(100); l_offset INT := 1; p_clob CLOB; BEGIN v_login := '***'; v_password := '***'; SELECT PDF INTO f_attachment FROM VIPUL_TEST WHERE ID=2; apex_web_service.g_request_headers.delete; apex_web_service.g_request_headers(1).name := 'Content-Type'; apex_web_service.g_request_headers(1).value := 'multipart/form-data'; f_response := apex_web_service.make_rest_request(p_url => 'https://systest.eisenvault.net/alfresco/api/-default-/public/alfresco/versions/1/nodes/2b9d7fc9-a12e-4e0b-89b2-e556ddfd2778/children', p_http_method => 'POST', p_username => v_login, p_password => v_password, p_parm_name => apex_util.string_to_table('filedata'), p_parm_value => apex_util.string_to_table(apex_web_service.blob2clobbase64(f_attachment)) ); -- 打印数据库取出的CLOB p_clob := apex_web_service.blob2clobbase64(f_attachment); dbms_output.put_line('Print CLOB'); loop exit when l_offset > dbms_lob.getlength(p_clob); dbms_output.put_line( dbms_lob.substr( p_clob, 255, l_offset ) ); l_offset := l_offset + 255; end loop; END;
报错信息
执行时触发如下错误:
ORA-06502: PL/SQL: numeric or value error ORA-06512: at line 21 ORA-06512: at "SYS.DBMS_SQL", line 1721
问题在于p_parm_value参数接收VARCHAR2类型的表,无法直接传入CLOB;且不愿转成VARCHAR2(存在32KB上限,附件可能达数GB)。求可行解决方法。
解决方案
方法1:使用apex_web_service.make_rest_request_b直接发送BLOB
APEX_WEB_SERVICE提供了专门处理大BLOB的make_rest_request_b过程,需手动构建符合multipart/form-data格式的请求体:
DECLARE l_blob BLOB; l_boundary VARCHAR2(256) := '----WebKitFormBoundary' || DBMS_RANDOM.string('x', 16); l_clob CLOB; l_response CLOB; v_login VARCHAR2(100) := '***'; v_password VARCHAR2(100) := '***'; f_attachment BLOB; BEGIN -- 从表中读取目标BLOB SELECT PDF INTO f_attachment FROM VIPUL_TEST WHERE ID=2; -- 创建临时LOB存储请求体 DBMS_LOB.CREATETEMPORARY(l_clob, TRUE); DBMS_LOB.CREATETEMPORARY(l_blob, TRUE); -- 构建multipart分隔头信息 DBMS_LOB.WRITEAPPEND(l_clob, LENGTH('--' || l_boundary || CHR(13) || CHR(10)), '--' || l_boundary || CHR(13) || CHR(10)); DBMS_LOB.WRITEAPPEND(l_clob, LENGTH('Content-Disposition: form-data; name="filedata"; filename="document.pdf"' || CHR(13) || CHR(10)), 'Content-Disposition: form-data; name="filedata"; filename="document.pdf"' || CHR(13) || CHR(10)); DBMS_LOB.WRITEAPPEND(l_clob, LENGTH('Content-Type: application/pdf' || CHR(13) || CHR(10) || CHR(13) || CHR(10)), 'Content-Type: application/pdf' || CHR(13) || CHR(10) || CHR(13) || CHR(10)); -- 将文本头转换为BLOB,追加目标文件BLOB DBMS_LOB.CONVERTTOBLOB(l_blob, l_clob, DBMS_LOB.LOBMAXSIZE, 1, 1, DBMS_LOB.DEFAULT_CSID); DBMS_LOB.APPEND(l_blob, f_attachment); -- 追加结束分隔符 DBMS_LOB.WRITEAPPEND(l_clob, LENGTH(CHR(13) || CHR(10) || '--' || l_boundary || '--'), CHR(13) || CHR(10) || '--' || l_boundary || '--'); DBMS_LOB.CONVERTTOBLOB(l_blob, l_clob, DBMS_LOB.LOBMAXSIZE, DBMS_LOB.GETLENGTH(l_blob)+1, 1, DBMS_LOB.DEFAULT_CSID); -- 设置请求头 apex_web_service.g_request_headers.delete; apex_web_service.g_request_headers(1).name := 'Content-Type'; apex_web_service.g_request_headers(1).value := 'multipart/form-data; boundary=' || l_boundary; -- 发送POST请求 l_response := apex_web_service.make_rest_request_b( p_url => 'https://systest.eisenvault.net/alfresco/api/-default-/public/alfresco/versions/1/nodes/2b9d7fc9-a12e-4e0b-89b2-e556ddfd2778/children', p_http_method => 'POST', p_username => v_login, p_password => v_password, p_blob => l_blob ); -- 输出响应结果 DBMS_OUTPUT.PUT_LINE(l_response); -- 释放临时LOB资源 DBMS_LOB.FREETEMPORARY(l_clob); DBMS_LOB.FREETEMPORARY(l_blob); END; /
关键说明
- 原代码错误原因:
apex_util.string_to_table仅支持VARCHAR2输入,无法处理大CLOB,且make_rest_request的参数模式不适合大文件传输。 - 注意事项:
- 需根据实际文件类型调整
Content-Type和文件名 - 临时LOB使用后必须释放,避免内存泄漏
- 自动生成随机boundary,避免与文件内容冲突
- 需根据实际文件类型调整
内容的提问来源于stack exchange,提问作者Vipul Swarup
相关产品推荐
相关产品推荐

