EBS根据地址发送邮件,可以发送附件。
包头
CREATE OR REPLACE PACKAGE Cux_Autoemail_Pkg IS
/* ===============================================
* PROGRAM NAME:
* CUX_AUTOEMAIL_PKG
* DESCRIPTION:
* This program provide private API to perform:
* 自动发送邮件
* HISTORY:
* 1.00 2020-08-25 Link.LV Creation
* ==============================================*/
-- Public type declarations
PROCEDURE Proc_Send_Email(p_Txt VARCHAR2
,p_Sub VARCHAR2
,p_Sendor VARCHAR2
,p_Receiver VARCHAR2
,p_Server VARCHAR2
,p_Request_Id NUMBER
,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_Code_Type VARCHAR2 DEFAULT 'gb2312'
,p_Mime_Type VARCHAR2 DEFAULT 'text/plain'
,p_Encode VARCHAR2 DEFAULT 'bit 7');
/*===============================================================
Program Name: Send_Mail
Author : Brayden.liu
Created : 2012-06-18
Purpose : 发送邮件
Parameters :
In Pv_Txt 邮件正文,每行后面加 ++做结尾,如果多行以++作为分隔,否则会被截取最后两个字符
如:一行数据显示为:123456,则传递:123456++
多行数据显示为:
123
456
则传递字符串为:123++456++
In Pv_Sub 邮件标题
in Pv_Receiver 接收地址,地址之间用","或者";"隔开
In Pn_Request_Id 请求id
Out Xv_Ret_Status 返回状态 S/E
Out Xv_Ret_Message 返回信息
Return : 返回值说明
Description:
Update History
Version Date Name Description
-------- ---------- --------------- --------------------
V1.0 $DATE $AUTHER Creation
===============================================================*/
PROCEDURE Send_Mail(Pv_Txt VARCHAR2
,Pv_Sub VARCHAR2
,Pv_Receiver VARCHAR2
,Pn_Request_Id NUMBER DEFAULT 0
,Pv_Filename VARCHAR2 DEFAULT NULL
,p_Code_Type VARCHAR2 DEFAULT 'gb2312'
,Pv_Mime_Type VARCHAR2 DEFAULT 'text/plain'
,Pv_Encode VARCHAR2 DEFAULT 'bit 7');
END Cux_Autoemail_Pkg;
包体
CREATE OR REPLACE PACKAGE BODY Cux_Autoemail_Pkg IS
/* ===============================================
* PROGRAM NAME:
* CUX_AUTOEMAIL_PKG
* DESCRIPTION:
* This program provide private API to perform:
* 自动发送邮件
* HISTORY:
* 1.00 2020-08-25 Link.LV Creation
* ==============================================*/
PROCEDURE Log(p_Msg IN VARCHAR2) IS
BEGIN
Dbms_Output.Put_Line(p_Msg);
END Log;
PROCEDURE Proc_Send_Email(p_Txt VARCHAR2
,p_Sub VARCHAR2
,p_Sendor VARCHAR2
,p_Receiver VARCHAR2
,p_Server VARCHAR2
,p_Request_Id NUMBER
,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_Code_Type VARCHAR2 DEFAULT 'gb2312'
,p_Mime_Type VARCHAR2 DEFAULT 'text/plain'
,p_Encode VARCHAR2 DEFAULT 'bit 7') IS
/*
作用:用oracle发送邮件
主要功能:1、支持多收件人。
2、支持中文
3、支持抄送人
4、支持大于32K的附件
5、支持多行正文
6、支持多附件
7、支持文本附件和二进制附件
8、支持HTML格式
8、支持
作者:suk
参数说明:
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_Num NUMBER := p_Request_Id;
l_Mime_Type VARCHAR2(200) := p_Mime_Type;
l_Crlf VARCHAR2(2) := Utl_Tcp.Crlf;
l_Sendoraddress VARCHAR2(4000);
l_Splite VARCHAR2(10) := '++';
Boundary CONSTANT VARCHAR2(256) := '-----BYSUK';
First_Boundary CONSTANT VARCHAR2(256) := '--' || Boundary || l_Crlf;
Last_Boundary CONSTANT VARCHAR2(256) := '--' || Boundary || '--' || l_Crlf;
Multipart_Mime_Type CONSTANT VARCHAR2(256) := 'multipart/mixed; boundary="' || Boundary || '"';
Multipart_Mime_Type2 CONSTANT VARCHAR2(256) := p_Mime_Type || ';charset=' || p_Code_Type;
--Write_Data(l_Conn, 'Content-Type', 'text/plain;charset=gb2312');
/* 以下部分是发送大二进制附件时用到的变量 */
l_Fil BFILE;
l_File_Len NUMBER;
l_Modulo NUMBER;
l_Pieces NUMBER;
l_File_Handle Utl_File.File_Type;
l_Amt BINARY_INTEGER := 672 * 3; /* 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;
NULL;
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
--把附件分成多份,这样可以发送超过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 := 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; --定义连接
--v_CcMail Varchar2(2000):='刁云彩<diaoyuncai@kjgb.net>,占志波<zhanzhibo@kjgb.net>,陈显平<chenxianping@kjgb.net>';
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.RCPT(L_CONN, '<diaoyuncai@kjgb.net>');
UTL_SMTP.RCPT(L_CONN, '<zhanzhibo@kjgb.net>');
UTL_SMTP.RCPT(L_CONN, '<chenxianping@kjgb.net>');*/
/*设置邮件头*/
Utl_Smtp.Open_Data(l_Conn);
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, 'Cc', v_CcMail);
/*设置邮件主题*/
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');
Write_Data(l_Conn, 'Content-Type', Multipart_Mime_Type2);
--单独空一行,否则,正文内容不显示
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
--加入下面一句的目的是解决 :ORA-00955: 名称已由现有对象使用
--select CUX.PROC_SEND_EMAIL_S.NEXTVAL into L_NUM from dual;
--根据逗号或者分号拆分附件地址
p_Splite_Str(p_Filename, 2);
--循环发送附件(在同一个邮件中)
FOR k IN 1 .. My_Acct_List.Count LOOP
Attachment(Conn => l_Conn
,Mime_Type => l_Mime_Type
,Filename => My_Acct_List(k)
,Transfer_Enc => p_Encode
,Dt_Name => l_Directory_Base_Name || To_Char(k) || l_Num);
END LOOP;
END IF;
/*关闭数据写入*/
Utl_Smtp.Close_Data(l_Conn);
/*关闭连接*/
Utl_Smtp.Quit(l_Conn);
/*异常处理*/
EXCEPTION
WHEN OTHERS THEN
Dbms_Output.Put_Line(SQLERRM);
Log('***********************');
Log('sqlerrm is ' || SQLERRM);
Log('***********************');
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
Dbms_Output.Put_Line(SQLERRM);
Log('***********************');
Log('sqlerrm is ' || SQLERRM);
Log('***********************');
RAISE;
END Proc_Send_Email;
/*===============================================================
Program Name: Send_Mail
Author : Brayden.liu
Created : 2012-06-18
Purpose : 发送邮件
Parameters :
In Pv_Txt 邮件正文,每行后面加 ++做结尾,如果多行以++作为分隔,否则会被截取最后两个字符
如:一行数据显示为:123456,则传递:123456++
多行数据显示为:
123
456
则传递字符串为:123++456++
In Pv_Sub 邮件标题
in Pv_Receiver 接收地址,地址之间用","或者";"隔开
In Pn_Request_Id 请求id
Out Xv_Ret_Status 返回状态 S/E
Out Xv_Ret_Message 返回信息
Return : 返回值说明
Description:
Update History
Version Date Name Description
-------- ---------- --------------- --------------------
V1.0 $DATE $AUTHER Creation
===============================================================*/
PROCEDURE Send_Mail(Pv_Txt VARCHAR2
,Pv_Sub VARCHAR2
,Pv_Receiver VARCHAR2
,Pn_Request_Id NUMBER DEFAULT 0
,Pv_Filename VARCHAR2 DEFAULT NULL
,p_Code_Type VARCHAR2 DEFAULT 'gb2312'
,Pv_Mime_Type VARCHAR2 DEFAULT 'text/plain'
,Pv_Encode VARCHAR2 DEFAULT 'bit 7') IS
Lv_Sendoraddress VARCHAR2(1000); --: 发送人邮件地址
Lv_Emailserver VARCHAR2(1000); --: 邮件服务器地址,可以是域名或者IP
Ln_Port NUMBER DEFAULT 25; -- :邮件服务器端口
Lv_Need_Smtp INT DEFAULT 1; --:是否需要SMTP认证,0表示不需要,1表示需要
Lv_User VARCHAR2(30); --:SMTP 验证需要的用户名
Lv_Pass VARCHAR2(30); --:SMTP验证需要的密码
BEGIN
Apps.Xxfnd_Common_Utl.Get_Mail_Infomation(Pn_Sendoraddress => Lv_Sendoraddress
,Pn_Emailserver => Lv_Emailserver
,Pn_Port => Ln_Port
,Pn_User => Lv_User
,Pn_Password => Lv_Pass);
--初始化邮箱信息
Proc_Send_Email(p_Txt => Pv_Txt
,p_Sub => Pv_Sub
,p_Receiver => Pv_Receiver
,p_Request_Id => Pn_Request_Id
,p_Sendor => Lv_Sendoraddress
,p_Server => Lv_Emailserver
,p_Port => Ln_Port
,p_Need_Smtp => Lv_Need_Smtp
,p_User => Lv_User
,p_Pass => Lv_Pass
,p_Code_Type => p_Code_Type
,p_Filename => Pv_Filename
,p_Mime_Type => Pv_Mime_Type
,p_Encode => Pv_Encode);
/*
参数说明:
p_txt :邮件正文
p_sub: 邮件标题
p_Sendor : 发送人邮件地址
p_Receiver : 接收地址,可以同时发送到多个地址上,地址之间用","或者";"隔开
p_Server : 邮件服务器地址,可以是域名或者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、对于多个附件只能用同一种格式发送
*/
END Send_Mail;
END Cux_Autoemail_Pkg;