求助:使用UTL_SMTP发送服务器目录中Excel附件的存储过程问题
PL/SQL 邮件存储过程修正(支持HTML正文+Excel附件)
原代码存在多处逻辑缺失与错误,导致无法正常发送带HTML正文和Excel附件的邮件,以下是修正后的完整存储过程:
CREATE OR REPLACE PROCEDURE p_send_email_with_attach AS file_handle utl_file.file_type; output VARCHAR2(32767); attachment_text CLOB; -- 改用CLOB避免VARCHAR2长度限制 v_dir VARCHAR2(300) := 'RPT_DIR'; v_recipients VARCHAR2(2000) := 'abc@gmail.com'; v_sub VARCHAR2(300) := 'Hello'; v_smtp_server VARCHAR2(255) := 'mailhost'; conn utl_smtp.connection; c_mime_boundary CONSTANT VARCHAR2(256) := '----=_Part_' || TO_CHAR(SYSTIMESTAMP, 'YYYYMMDDHH24MISSFF'); v_from_name VARCHAR2(100) := 'xyz@yahoo.com'; -- HTML正文示例(多表格结构) v_html_body VARCHAR2(32767) := '<html> <head><title>邮件正文</title></head> <body> <h3>报表数据</h3> <table border="1"> <tr><th>ID</th><th>名称</th></tr> <tr><td>1</td><td>测试数据1</td></tr> <tr><td>2</td><td>测试数据2</td></tr> </table> <br> <table border="1"> <tr><th>月份</th><th>销售额</th></tr> <tr><td>1月</td><td>10000</td></tr> <tr><td>2月</td><td>12000</td></tr> </table> </body> </html>'; BEGIN -- 1. 读取Excel/CSV文件内容到CLOB file_handle := utl_file.fopen(location => v_dir, filename => 'My_First_CSV.CSV', open_mode => 'R'); attachment_text := EMPTY_CLOB(); LOOP BEGIN utl_file.get_line(file_handle, output); -- 将读取到的行追加到CLOB,添加换行符 attachment_text := attachment_text || output || utl_tcp.crlf; EXCEPTION WHEN no_data_found THEN EXIT; END; END LOOP; utl_file.fclose(file_handle); -- 2. 建立SMTP连接 conn := utl_smtp.open_connection(v_smtp_server); utl_smtp.helo(conn, v_smtp_server); utl_smtp.mail(conn, v_from_name); -- 处理收件人(拆分分号分隔的收件人列表) FOR cur_r IN ( SELECT regexp_substr(v_recipients, '[^;]+', 1, level) AS v_recipient FROM dual CONNECT BY regexp_substr(v_recipients, '[^;]+', 1, level) IS NOT NULL ) LOOP utl_smtp.rcpt(conn, cur_r.v_recipient); END LOOP; -- 3. 开始写入邮件数据 utl_smtp.open_data(conn); -- 邮件头信息 utl_smtp.write_data(conn, 'From: ' || v_from_name || utl_tcp.crlf); utl_smtp.write_data(conn, 'To: ' || v_recipients || utl_tcp.crlf); utl_smtp.write_data(conn, 'Subject: ' || v_sub || utl_tcp.crlf); -- 声明MIME多部分邮件 utl_smtp.write_data(conn, 'MIME-Version: 1.0' || utl_tcp.crlf); utl_smtp.write_data(conn, 'Content-Type: multipart/mixed; boundary="' || c_mime_boundary || '"' || utl_tcp.crlf || utl_tcp.crlf); -- 4. 添加HTML正文部分 utl_smtp.write_data(conn, '--' || c_mime_boundary || utl_tcp.crlf); utl_smtp.write_data(conn, 'Content-Type: text/html; charset=UTF-8' || utl_tcp.crlf); utl_smtp.write_data(conn, 'Content-Disposition: inline' || utl_tcp.crlf || utl_tcp.crlf); utl_smtp.write_data(conn, v_html_body || utl_tcp.crlf || utl_tcp.crlf); -- 5. 添加Excel附件部分(Base64编码) utl_smtp.write_data(conn, '--' || c_mime_boundary || utl_tcp.crlf); utl_smtp.write_data(conn, 'Content-Type: application/vnd.ms-excel; name="My_First_CSV.CSV"' || utl_tcp.crlf); utl_smtp.write_data(conn, 'Content-Disposition: attachment; filename="My_First_CSV.CSV"' || utl_tcp.crlf); utl_smtp.write_data(conn, 'Content-Transfer-Encoding: base64' || utl_tcp.crlf || utl_tcp.crlf); -- 将CLOB内容Base64编码后分段写入(避免单行长限制) utl_smtp.write_data(conn, utl_encode.base64_encode(utl_raw.cast_to_raw(attachment_text)) || utl_tcp.crlf || utl_tcp.crlf); -- 6. 结束MIME边界 utl_smtp.write_data(conn, '--' || c_mime_boundary || '--' || utl_tcp.crlf); -- 关闭数据写入与连接 utl_smtp.close_data(conn); utl_smtp.quit(conn); EXCEPTION WHEN OTHERS THEN -- 异常时关闭未释放的资源 IF utl_file.is_open(file_handle) THEN utl_file.fclose(file_handle); END IF; IF utl_smtp.is_connected(conn) THEN utl_smtp.quit(conn); END IF; raise_application_error(-20001, '发送邮件失败: ' || SQLERRM); END p_send_email_with_attach; /
关键修正点说明:
- 文件读取逻辑:改用
CLOB存储文件内容,避免VARCHAR2的长度限制;修复行内容追加逻辑,将读取到的output实际加入到变量中。 - MIME结构完善:补充邮件头(From/To/Subject)、MIME版本声明,明确多部分邮件的边界规则。
- HTML正文支持:添加独立的HTML正文部分,设置
Content-Disposition: inline使其在邮件中直接显示,并指定UTF-8编码避免乱码。 - 附件处理:将附件类型修正为
application/vnd.ms-excel,使用base64编码处理文件内容(符合SMTP传输规范),避免原始文本传输导致格式损坏。 - 收件人处理:改为拆分
v_recipients变量中的分号分隔列表,无需依赖额外表,逻辑更通用。 - 异常资源释放:在异常块中检查并关闭未释放的文件句柄和SMTP连接,避免资源泄漏。
内容的提问来源于stack exchange,提问作者Rizwan
相关产品推荐
相关产品推荐

