原文地址:https://blog.csdn.net/zhanzhib/article/details/42292455
经测试,好像单用户发送没问题附件没问题,多用户发送只有第一个用户能正常打开附件。(PS格式,不完美)
2018/07/27 最终解决了多用户发问题,原程序有几个小问题,更新!
发邮件进程:
CREATE OR REPLACE Procedure NI_TEST_EMAIL(P_TXT Varchar2,
P_SUB Varchar2,
P_SENDOR Varchar2,
P_RECEIVER Varchar2,
P_SERVER Varchar2,
P_PORT Number Default 25,
P_NEED_SMTP Int Default 0,
P_USER Varchar2 Default Null,
P_PASS Varchar2 Default Null,
P_FILENAME Varchar2 Default Null,
P_ENCODE Varchar2 Default 'bit 7')
Authid Current_User Is
/*
作用:用oracle发送邮件
主要功能:1、支持多收件人。
2、支持中文
3、支持抄送人
4、支持大于32K的附件
5、支持多行正文
6、支持多附件
7、支持文本附件和二进制附件
8、支持HTML格式
参数说明:
p_txt :邮件正文
p_sub: 邮件标题
p_SendorAddress : 发送人邮件地址
p_ReceiverAddress : 接收地址,可以同时发送到多个地址上,地址之间用","或者";"隔开
p_EmailServer : 邮件服务器地址,可以是域名或者IP
p_Port :邮件服务器端口
p_need_smtp:是否需要smtp认证,0表示不需要,1表示需要
p_user:smtp验证需要的用户名
p_pass:smtp验证需要的密码
p_filename:附件名称,必须包含完整的路径,如"d:\temp\a.txt"。
可以有多个附件,附件名称只见用逗号或者分号分隔
p_encode:附件编码转换格式,其中 p_encode='bit 7' 表示文本类型附件
p_encode='base64' 表示二进制类型附件
注意:
1、对于文本类型的附件,不能用base64的方式发送,否则出错
2、对于多个附件只能用同一种格式发送
*/
L_CRLF Varchar2(2) := UTL_TCP.CRLF;
L_SENDORADDRESS Varchar2(4000);
L_SPLITE Varchar2(10) := '++';
BOUNDARY Constant Varchar2(256) := '-----By ERP';
FIRST_BOUNDARY Constant Varchar2(256) := '--' || BOUNDARY || L_CRLF;
LAST_BOUNDARY Constant Varchar2(256) := '--' || BOUNDARY || '--' || L_CRLF;
--2018/07/27 增加charset=gb2312; 解决部分系统标题乱码
MULTIPART_MIME_TYPE Constant Varchar2(256) := 'multipart/mixed; charset=gb2312;boundary="' ||BOUNDARY || '"';
/* 以下部分是发送大二进制附件时用到的变量 */
L_FIL Bfile;
L_FILE_LEN Number;
--L_MODULO Number;
L_PIECES Number;
L_FILE_HANDLE UTL_FILE.FILE_TYPE;
--2018/07/27 屏蔽员 L_AMT:=672 * 3的赋值,是不能多用户发送的元凶,再写附件时初始化
L_AMT Binary_Integer; /* ensures proper format; 2016 */
L_FILEPOS Pls_Integer := 1; /* pointer for the file */
--L_CHUNKS Number;
L_BUF Raw(2100);
L_DATA Raw(2100);
--L_MAX_LINE_WIDTH Number := 54;
L_DIRECTORY_BASE_NAME Varchar2(100) := 'DIR_FOR_SEND_MAIL';--此环境变量不存在的
L_LINE Varchar2(1000);
L_MESG Varchar2(32767);
/* 以上部分是发送大二进制附件时用到的变量 */
Type ADDRESS_LIST Is Table Of Varchar2(100) Index By Binary_Integer;
MY_ADDRESS_LIST ADDRESS_LIST;
Type ACCT_LIST Is Table Of Varchar2(100) Index By Binary_Integer;
MY_ACCT_LIST ACCT_LIST;
-------------------------------------返回附件源文件所在目录或者名称--------------------------------------
Function GET_FILE(P_FILE Varchar2, P_GET Int) Return Varchar2 Is
--p_get=1 表示返回目录
--p_get=2 表示返回文件名
L_FILE Varchar2(1000);
Begin
If INSTR(P_FILE, '\') > 0 Then
--windows
If P_GET = 1 Then
L_FILE := SUBSTR(P_FILE, 1, INSTR(P_FILE, '\', -1) - 1);
Elsif P_GET = 2 Then
L_FILE := SUBSTR(P_FILE,
- (LENGTH(P_FILE) -
INSTR(P_FILE, '\', -1)));
End If;
Elsif INSTR(P_FILE, '/') > 0 Then
--linux/unix
If P_GET = 1 Then
L_FILE := SUBSTR(P_FILE, 1, INSTR(P_FILE, '/', -1) - 1);
Elsif P_GET = 2 Then
L_FILE := SUBSTR(P_FILE,
- (LENGTH(P_FILE) -
INSTR(P_FILE, '/', -1)));
End If;
End If;
Return L_FILE;
End;
---------------------------------------------删除directory------------------------------------
Procedure DROP_DIRECTORY(P_DIRECTORY_NAME Varchar2) Is
Begin
Execute Immediate 'drop directory ' || P_DIRECTORY_NAME;
Exception
When Others Then
Null;
End;
--------------------------------------------------创建directory-----------------------------------------
Procedure CREATE_DIRECTORY(P_DIRECTORY_NAME Varchar2, P_DIR Varchar2) Is
Begin
Execute Immediate 'create directory ' || P_DIRECTORY_NAME ||
' as ''' || P_DIR || '''';
Execute Immediate 'grant read,write on directory ' ||
P_DIRECTORY_NAME || ' to public';
Exception
When Others Then
Raise;
End;
--------------------------------------------分割邮件地址或者附件地址-----------------------------------
Procedure P_SPLITE_STR(P_STR Varchar2, P_SPLITE_FLAG Int Default 1) Is
L_ADDR Varchar2(254) := '';
L_LEN Int;
L_STR Varchar2(4000);
J Int := 0; --表示邮件地址或者附件的个数
Begin
/*处理接收邮件地址列表,包括去空格、将;转换为,等*/
L_STR := Trim(RTRIM(Replace(Replace(P_STR, ';', ','), ' ', ''), ','));
L_LEN := LENGTH(L_STR);
For I In 1 .. L_LEN Loop
If SUBSTR(L_STR, I, 1) <> ',' Then
L_ADDR := L_ADDR || SUBSTR(L_STR, I, 1);
Else
J := J + 1;
If P_SPLITE_FLAG = 1 Then
--表示处理邮件地址
--前后需要加上'<>',否则很多邮箱将不能发送邮件
L_ADDR := '<' || L_ADDR || '>';
--调用邮件发送过程
MY_ADDRESS_LIST(J) := L_ADDR;
Elsif P_SPLITE_FLAG = 2 Then
--表示处理附件名称
MY_ACCT_LIST(J) := L_ADDR;
End If;
L_ADDR := '';
End If;
If I = L_LEN Then
J := J + 1;
If P_SPLITE_FLAG = 1 Then
--调用邮件发送过程
L_ADDR := '<' || L_ADDR || '>';
MY_ADDRESS_LIST(J) := L_ADDR;
Elsif P_SPLITE_FLAG = 2 Then
MY_ACCT_LIST(J) := L_ADDR;
End If;
End If;
End Loop;
End;
------------------------------------------------写邮件头和邮件内容------------------------------------------
Procedure WRITE_DATA(P_CONN In Out Nocopy UTL_SMTP.CONNECTION,
P_NAME In Varchar2,
P_VALUE In Varchar2,
P_SPLITE Varchar2 Default ':',
P_CRLF Varchar2 Default L_CRLF) Is
Begin
/* utl_raw.cast_to_raw 对解决中文乱码问题很重要*/
UTL_SMTP.WRITE_RAW_DATA(P_CONN,
UTL_RAW.CAST_TO_RAW(CONVERT(P_NAME ||
P_SPLITE ||
P_VALUE ||
P_CRLF,
'ZHS16GBK')));
End;
----------------------------------------写MIME邮件尾部-----------------------------------------------------
Procedure END_BOUNDARY(CONN In Out Nocopy UTL_SMTP.CONNECTION,
Last In Boolean Default False) Is
Begin
UTL_SMTP.WRITE_DATA(CONN, UTL_TCP.CRLF);
If (Last) Then
UTL_SMTP.WRITE_DATA(CONN, LAST_BOUNDARY);
End If;
End;
----------------------------------------------发送附件----------------------------------------------------
Procedure ATTACHMENT(CONN In Out Nocopy UTL_SMTP.CONNECTION,
MIME_TYPE In Varchar2 Default 'text/plain',
INLINE In Boolean Default True,
FILENAME In Varchar2 Default 't.txt',
TRANSFER_ENC In Varchar2 Default '7 bit',
DT_NAME In Varchar2 Default '0') Is
L_FILENAME Varchar2(1000);
Begin
--写附件头
UTL_SMTP.WRITE_DATA(CONN, FIRST_BOUNDARY);
--设置附件格式
WRITE_DATA(CONN, 'Content-Type', MIME_TYPE);
--如果文件名称非空,表示有附件
DROP_DIRECTORY(DT_NAME);
--创建directory
CREATE_DIRECTORY(DT_NAME, GET_FILE(FILENAME, 1));
--得到附件文件名称
L_FILENAME := GET_FILE(FILENAME, 2);
If (INLINE) Then
WRITE_DATA(CONN,
'Content-Disposition',
'inline; filename="' || L_FILENAME || '"');
Else
WRITE_DATA(CONN,
'Content-Disposition',
'attachment; filename="' || L_FILENAME || '"');
End If;
--设置附件的转换格式
If (TRANSFER_ENC Is Not Null) Then
WRITE_DATA(CONN, 'Content-Transfer-Encoding', TRANSFER_ENC);
End If;
UTL_SMTP.WRITE_DATA(CONN, UTL_TCP.CRLF);
--begin 贴附件内容
If TRANSFER_ENC = 'bit 7' Then
--如果是文本类型的附件
Begin
L_FILE_HANDLE := UTL_FILE.FOPEN(DT_NAME,
L_FILENAME,
'r'); --打开文件
--把附件分成多份,这样可以发送超过32K的附件
Loop
UTL_FILE.GET_LINE(L_FILE_HANDLE, L_LINE);
L_MESG := L_LINE || L_CRLF;
WRITE_DATA(CONN, '', L_MESG, '', '');
End Loop;
UTL_FILE.FCLOSE(L_FILE_HANDLE);
END_BOUNDARY(CONN);
Exception
When Others Then
UTL_FILE.FCLOSE(L_FILE_HANDLE);
END_BOUNDARY(CONN);
Null;
End; --结束文本类型附件的处理
Elsif TRANSFER_ENC = 'base64' Then
--如果是二进制类型的附件
Begin
L_AMT := 672 * 3;----2018/07/27 增加 初始化
--把附件分成多份,这样可以发送超过32K的附件
L_FILEPOS := 1; --重置offset,在发送多个附件时,必须重置
L_FIL := BFILENAME(DT_NAME, L_FILENAME);
L_FILE_LEN := DBMS_LOB.GETLENGTH(L_FIL);
--L_MODULO := Mod(L_FILE_LEN, L_AMT);
L_PIECES := Ceil(L_FILE_LEN / L_AMT);--2018/07/26 附件打不开修改 TRUNC(L_FILE_LEN / L_AMT);
/* If (L_MODULO <> 0) Then
L_PIECES := L_PIECES + 1;
End If;*/
DBMS_LOB.FILEOPEN(L_FIL, DBMS_LOB.FILE_READONLY);
DBMS_LOB.READ(L_FIL, L_AMT, L_FILEPOS, L_BUF);
L_DATA := Null;
For I In 1 .. L_PIECES Loop
L_FILEPOS := I * L_AMT + 1;
L_FILE_LEN := L_FILE_LEN - L_AMT;
L_DATA := UTL_RAW.CONCAT(L_DATA, L_BUF);
/*L_CHUNKS := TRUNC(UTL_RAW.LENGTH(L_DATA) /L_MAX_LINE_WIDTH);
If (I <> L_PIECES) Then
L_CHUNKS := L_CHUNKS - 1;
End If;*/
UTL_SMTP.WRITE_RAW_DATA(CONN,UTL_ENCODE.BASE64_ENCODE(L_DATA));
L_DATA := Null;
If (L_FILE_LEN < L_AMT And L_FILE_LEN > 0) Then
L_AMT := L_FILE_LEN;
End If;
DBMS_LOB.READ(L_FIL, L_AMT, L_FILEPOS, L_BUF);
End Loop;
DBMS_LOB.FILECLOSE(L_FIL);
END_BOUNDARY(CONN);
Exception
When Others Then
DBMS_LOB.FILECLOSE(L_FIL);
END_BOUNDARY(CONN);
Raise;
End; --结束处理二进制附件
End If; --结束处理附件内容
DROP_DIRECTORY(DT_NAME);
End; --结束过程ATTACHMENT
---------------------------------------------真正发送邮件的过程--------------------------------------------
Procedure P_EMAIL(P_SENDORADDRESS2 Varchar2, --发送地址
P_RECEIVERADDRESS2 Varchar2) --接受地址
Is
L_CONN UTL_SMTP.CONNECTION; --定义连接
Begin
/*初始化邮件服务器信息,连接邮件服务器*/
L_CONN := UTL_SMTP.OPEN_CONNECTION(P_SERVER, P_PORT);
--使用UTL_SMTP.HELO有可能会提示“ORA-29279: SMTP 永久性错误: 503 5.5.2 Send hello first.”
--改成使用UTL_SMTP.EHLO就好了
--UTL_SMTP.HELO(L_CONN, P_SERVER);
UTL_SMTP.EHLO(L_CONN, P_SERVER);
/* smtp服务器登录校验 */
If P_NEED_SMTP = 1 Then
UTL_SMTP.COMMAND(L_CONN, 'AUTH LOGIN', '');
UTL_SMTP.COMMAND(L_CONN,
UTL_RAW.CAST_TO_VARCHAR2(UTL_ENCODE.BASE64_ENCODE(UTL_RAW.CAST_TO_RAW(P_USER))));
UTL_SMTP.COMMAND(L_CONN,
UTL_RAW.CAST_TO_VARCHAR2(UTL_ENCODE.BASE64_ENCODE(UTL_RAW.CAST_TO_RAW(P_PASS))));
End If;
/*设置发送地址和接收地址*/
UTL_SMTP.MAIL(L_CONN, P_SENDORADDRESS2);
UTL_SMTP.RCPT(L_CONN, P_RECEIVERADDRESS2);
/*设置邮件头*/
UTL_SMTP.OPEN_DATA(L_CONN);
--2018/07/26 WRITE_DATA(L_CONN,'Date',TO_CHAR(Sysdate, 'yyyy-mm-dd hh24:mi:ss'));
/*设置发送人*/
WRITE_DATA(L_CONN, 'From', P_SENDOR);
/*设置接收人*/
WRITE_DATA(L_CONN, 'To', P_RECEIVER);
/*设置邮件主题*/
WRITE_DATA(L_CONN, 'Subject', P_SUB);
WRITE_DATA(L_CONN, 'Content-Type', MULTIPART_MIME_TYPE);
UTL_SMTP.WRITE_DATA(L_CONN, UTL_TCP.CRLF);
UTL_SMTP.WRITE_DATA(L_CONN, FIRST_BOUNDARY);
WRITE_DATA(L_CONN, 'Content-Type', 'text/plain;charset=gb2312');
--单独空一行,否则,正文内容不显示
UTL_SMTP.WRITE_DATA(L_CONN, UTL_TCP.CRLF);
/* 设置邮件正文
把分隔符还原成chr(10)。这主要是为了shell中调用该过程,如果有多行,则先把多行的内容合并成一行,并用 l_splite分隔
然后用 l_crlf替换chr(10)。这一步是必须的,否则将不能发送邮件正文有多行的邮件
*/
WRITE_DATA(L_CONN, '',Replace(Replace(P_TXT, L_SPLITE, CHR(10)),CHR(10),L_CRLF),'','');
END_BOUNDARY(L_CONN);
--如果文件名称不为空,则发送附件
If (P_FILENAME Is Not Null) Then
--根据逗号或者分号拆分附件地址
P_SPLITE_STR(P_FILENAME, 2);
--循环发送附件(在同一个邮件中)
For K In 1 .. MY_ACCT_LIST.COUNT Loop
ATTACHMENT(CONN => L_CONN,
INLINE => false,
FILENAME => MY_ACCT_LIST(K),
TRANSFER_ENC => P_ENCODE,
DT_NAME => L_DIRECTORY_BASE_NAME ||TO_CHAR(K));
End Loop;
End If;
/*关闭数据写入*/
UTL_SMTP.CLOSE_DATA(L_CONN);
/*关闭连接*/
UTL_SMTP.QUIT(L_CONN);
/*异常处理*/
Exception
When Others Then
Null;
Raise;
End;
---------------------------------------------------主过程-----------------------------------------------------
Begin
L_SENDORADDRESS := '<' || P_SENDOR || '>';
P_SPLITE_STR(P_RECEIVER); --处理邮件地址
For K In 1 .. MY_ADDRESS_LIST.COUNT Loop
P_EMAIL(L_SENDORADDRESS, MY_ADDRESS_LIST(K));
End Loop;
/*处理邮件地址,根据逗号分割邮件*/
Exception
When Others Then
Raise;
End;
报表附件发送前修改后缀,原报表后缀是.out,修改为.ps(.xls也可以)
Declare
p_Request_Id Number := 19779753;--报表ID
v_email_address Varchar2(200) := '[email protected]';
Cursor c Is
Select a.Request_Id Request_Id,
b.Outfile_Name Outfile_Name,
a.Status_Code Status_Code,
a.Phase_Code Phase_Code,
b.Output_File_Type,
a.Program
From Fnd_Conc_Req_Summary_v a, Fnd_Concurrent_Requests b
Where a.Request_Id = b.Request_Id
And a.Request_Id = p_Request_Id
And a.Phase_Code = 'C'
And a.Status_Code = 'C'
And b.Outfile_Name Is Not Null;
ln_last_split Number;
v_src_location Varchar2(300);
v_file_name Varchar2(100);
v_dest_filename Varchar2(100);
Begin
For C1 In c Loop
ln_last_split := instr(C1.Outfile_Name, '/', -1, 1);
--原名
v_file_name := substr(C1.Outfile_Name, ln_last_split + 1);
--转名后(改ps后缀)
v_dest_filename := substr(v_file_name,1,instr(v_file_name, '.', -1, 1) ) ||'ps';
--最终文件路径
v_src_location := substr(C1.Outfile_Name, 1, ln_last_split) || v_dest_filename;
utl_file.fcopy('REPORT_DIR', --(需手动创建)环境变量路径 create directory REPORT_DIR as 路径地址;
v_file_name, --原文件名
'REPORT_DIR', --(需手动创建)环境变量路径 create directory REPORT_DIR as 路径地址;
v_dest_filename --拷贝后文件名
);
NI_TEST_EMAIL(C1.program || 'output file',
C1.program,
'[email protected]',
v_email_address,
'smtp1.email.com',
25,
0,
Null,
Null,
v_src_location,
'base64');
End Loop;
End;
(此问题已解决,保留当其他方式)同时发给多个EMAIL用户,附件有打不开的情况,分拆成单个发送又没问题。
分拆方法,下文中出现ni_app_ext.split()代码:
--先创建数组
CREATE OR REPLACE TYPE NI_STR_ARRAY is VARRAY(10000) OF VARCHAR2(500)
--分拆函数
Function Split(v_source Varchar2, v_delimiter Varchar2 Default ',')
Return NI_STR_ARRAY Is
l_DelLen Number;
l_Pos Number;
l_Start Number;
l_Length Number;
l_holder Varchar2(200);
l_Array NI_STR_ARRAY := ni_str_array();
Begin
--Check for NULL
If v_source Is Null
Or v_delimiter Is Null Then
l_Array.Extend;
l_Array(l_Array.Count) := '';
Return l_Array;
End If;
--Get the length of the delimeter
l_DelLen := Length(v_Delimiter);
l_Pos := instr(Upper(v_source), Upper(v_Delimiter));
--Only one entry was found
If l_Pos = 0 Then
l_Array.extend;
l_Array(l_Array.Count) := v_source;
Return l_Array;
End If;
--More than one entry was found - loop to get all of them
l_Start := 1;
While l_Pos > 0 Loop
--Set current entry
l_Length := l_Pos - l_Start;
l_holder := substr(v_source, l_start, l_length);
-- Update array and counter
l_Array.extend;
l_Array(l_Array.Count) := l_holder;
--Set the new starting position
l_Start := l_Pos + l_DelLen;
l_Pos := instr(Upper(v_source), Upper(v_Delimiter), l_Start);
End Loop;
--Set last entry
l_holder := substr(v_source, l_start, Length(v_source));
-- Update array and counter if necessary
If Length(l_holder) > 0 Then
l_Array.extend;
l_Array(l_Array.Count) := l_holder;
End If;
--Return the number of entries found
Return l_Array;
End;
循环分拆发送:
Declare
p_Request_Id Number := 19781729;
v_email_address Varchar2(1000) := '[email protected];[email protected];[email protected]';
Cursor c Is
Select a.Request_Id Request_Id,
b.Outfile_Name Outfile_Name,
a.Status_Code Status_Code,
a.Phase_Code Phase_Code,
b.Output_File_Type,
a.Program
From Fnd_Conc_Req_Summary_v a, Fnd_Concurrent_Requests b
Where a.Request_Id = b.Request_Id
And a.Request_Id = p_Request_Id
And a.Phase_Code = 'C'
And a.Status_Code = 'C'
And b.Outfile_Name Is Not Null;
ln_last_split Number;
v_src_location Varchar2(300);
v_file_name Varchar2(100);
v_dest_filename Varchar2(100);
v_one_email Varchar2(100);
Begin
For C1 In c Loop
ln_last_split := instr(C1.Outfile_Name, '/', -1, 1);
--原名
v_file_name := substr(C1.Outfile_Name, ln_last_split + 1);
--转名后(改ps后缀)
v_dest_filename := substr(v_file_name,1,instr(v_file_name, '.', -1, 1) ) ||'xls';
--最终文件路径
v_src_location := substr(C1.Outfile_Name, 1, ln_last_split) || v_dest_filename;
utl_file.fcopy('REPORT_DIR', --环境变量路径 DIRECTORY_NAME
v_file_name, --原文件名
'REPORT_DIR', --环境变量路径 DIRECTORY_NAME
v_dest_filename --拷贝后文件名
);
--循环发邮件开始
For i In 1 .. ni_app_ext.split(v_email_address,';').count Loop
v_one_email:=null;
v_one_email:=ni_app_ext.split(v_email_address, ';') (i);
If v_one_email Is Not Null Then
NI_TEST_EMAIL(C1.program || '附件!'||chr(10)||'如出现无法打开现象,请自行到ERP中获取,报表ID:'||p_Request_Id,
C1.program,
'[email protected]',
v_one_email,
'smtp1.email.com',
25,
0,
Null,
Null,
v_src_location,
'base64');
End If;
End Loop;
--循环发邮件结束
End Loop;
End;