原文地址: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;