1 CREATE OR REPLACE PROCEDURE PROCSENDEMAIL (
2 P_TXT VARCHAR2,
3 P_SUB VARCHAR2,
4 P_SENDOR VARCHAR2,
5 P_RECEIVER VARCHAR2,
6 P_SERVER VARCHAR2,
7 P_PORT NUMBER DEFAULT 25 ,
8 P_NEED_SMTP INT DEFAULT 0 ,
9 P_USER VARCHAR2 DEFAULT NULL ,
10 P_PASS VARCHAR2 DEFAULT NULL ,
11 P_FILENAME VARCHAR2 DEFAULT NULL ,
12 P_ENCODE VARCHAR2 DEFAULT 'bit 7'
13 )
14 AUTHID CURRENT_USER
15 IS
16 /*
17 作用:用oracle发送邮件
18 主要功能:1、支持多收件人。
19 2、支持中文
20 3、支持抄送人
21 4、支持大于32K的附件
22 5、支持多行正文
23 6、支持多附件
24 7、支持文本附件和二进制附件
25 8、支持HTML格式
26 8、支持
27 作者:suk
28 参数说明:
29 p_txt :邮件正文
30 p_sub: 邮件标题
31 p_SendorAddress : 发送人邮件地址
32 p_ReceiverAddress : 接收地址,可以同时发送到多个地址上,地址之间用","或者";"隔开
33 p_EmailServer : 邮件服务器地址,可以是域名或者IP
34 p_Port :邮件服务器端口
35 p_need_smtp:是否需要smtp认证,0表示不需要,1表示需要
36 p_user:smtp验证需要的用户名
37 p_pass:smtp验证需要的密码
38 p_filename:附件名称,必须包含完整的路径,如"d:\temp\a.txt"。
39 可以有多个附件,附件名称只见用逗号或者分号分隔
40 p_encode:附件编码转换格式,其中 p_encode='bit 7' 表示文本类型附件
41 p_encode='base64' 表示二进制类型附件
42 注意:
43 1、对于文本类型的附件,不能用base64的方式发送,否则出错
44 2、对于多个附件只能用同一种格式发送
45 测试
46 PROCSENDEMAIL('中文测试邮件','中文主题','xiaoxiaolin_jack@163.com','258202898@qq.com,jack@adm-soft.com','smtp.163.com',25,1,'xiaoxiaolin_jack@163.com','xxl121','d:\test.txt','bit 7');
47 PROCSENDEMAIL('中文测试邮件','中文主题','xiaoxiaolin_jack@163.com','258202898@qq.com,jack@adm-soft.com','smtp.163.com',25,1,'xiaoxiaolin_jack@163.com','xxl121','d:\pic.jpg,d:\test.txt','base64');
48 PROCSENDEMAIL('中文测试邮件','中文主题','xiaoxiaolin_jack@163.com','258202898@qq.com,jack@adm-soft.com','smtp.163.com',25,1,'xiaoxiaolin_jack@163.com','xxl121','d:\unload.txt,d:\pic.jpg,d:\unload.xls','base64');
49 --使用‘bit 7’能够正常执行
50 */
51 L_CRLF VARCHAR2 (2) := UTL_TCP.CRLF;
52 L_SENDORADDRESS VARCHAR2 (4000);
53 L_SPLITE VARCHAR2 (10) := '++';
54 BOUNDARY CONSTANT VARCHAR2 (256) := '-----BYSUK';
55 FIRST_BOUNDARY CONSTANT VARCHAR2 (256) := '--' || BOUNDARY || L_CRLF;
56 LAST_BOUNDARY CONSTANT VARCHAR2 (256)
57 := '--' || BOUNDARY || '--' || L_CRLF ;
58 MULTIPART_MIME_TYPE CONSTANT VARCHAR2 (256)
59 := 'multipart/mixed; boundary="' || BOUNDARY || '"' ;
60 /* 以下部分是发送大二进制附件时用到的变量 */
61 L_FIL BFILE;
62 L_FILE_LEN NUMBER;
63 L_MODULO NUMBER;
64 L_PIECES NUMBER;
65 L_FILE_HANDLE UTL_FILE.FILE_TYPE;
66 L_AMT BINARY_INTEGER := 672 * 3; /* ensures proper format; 2016 */
67 L_FILEPOS PLS_INTEGER := 1; /* pointer for the file */
68 L_CHUNKS NUMBER;
69 L_BUF RAW (2100);
70 L_DATA RAW (2100);
71 L_MAX_LINE_WIDTH NUMBER := 54;
72 L_DIRECTORY_BASE_NAME VARCHAR2 (100) := 'DIR_FOR_SEND_MAIL';
73 L_LINE VARCHAR2 (1000);
74 L_MESG VARCHAR2 (32767);
75 /* 以上部分是发送大二进制附件时用到的变量 */
76 TYPE ADDRESS_LIST
77 IS
78 TABLE OF VARCHAR2 (100)
79 INDEX BY BINARY_INTEGER;
80 MY_ADDRESS_LIST ADDRESS_LIST;
81 TYPE ACCT_LIST
82 IS
83 TABLE OF VARCHAR2 (100)
84 INDEX BY BINARY_INTEGER;
85 MY_ACCT_LIST ACCT_LIST;
86 -------------------------------------返回附件源文件所在目录或者名称-----------------------
87 FUNCTION GET_FILE (P_FILE VARCHAR2, P_GET INT)
88 RETURN VARCHAR2
89 IS
90 --p_get=1 表示返回目录
91 --p_get=2 表示返回文件名
92 L_FILE VARCHAR2 (1000);
93 BEGIN
94 IF INSTR (P_FILE, '\') > 0
95 THEN
96 --windows
97 IF P_GET = 1
98 THEN
99 L_FILE := SUBSTR (P_FILE, 1, INSTR (P_FILE, '\', -1) - 1);
100 ELSIF P_GET = 2
101 THEN
102 L_FILE :=
103 SUBSTR (P_FILE, - (LENGTH (P_FILE) - INSTR (P_FILE, '\', -1)));
104 END IF;
105 ELSIF INSTR (P_FILE, '/') > 0
106 THEN
107 --linux/unix
108 IF P_GET = 1
109 THEN
110 L_FILE := SUBSTR (P_FILE, 1, INSTR (P_FILE, '/', -1) - 1);
111 ELSIF P_GET = 2
112 THEN
113 L_FILE :=
114 SUBSTR (P_FILE, - (LENGTH (P_FILE) - INSTR (P_FILE, '/', -1)));
115 END IF;
116 END IF;
117 RETURN L_FILE;
118 END;
119 ---------------------------------------------删除directory------------------------------------
120 PROCEDURE DROP_DIRECTORY (P_DIRECTORY_NAME VARCHAR2)
121 IS
122 BEGIN
123 EXECUTE IMMEDIATE 'drop directory ' || P_DIRECTORY_NAME;
124 EXCEPTION
125 WHEN OTHERS
126 THEN
127 NULL;
128 END;
129 --------------------------------------------------创建directory-----------------------------------------
130 PROCEDURE CREATE_DIRECTORY (P_DIRECTORY_NAME VARCHAR2, P_DIR VARCHAR2)
131 IS
132 BEGIN
133 EXECUTE IMMEDIATE 'create directory '
134 || P_DIRECTORY_NAME
135 || ' as '''
136 || P_DIR
137 || '''';
138 EXECUTE IMMEDIATE 'grant read,write on directory '
139 || P_DIRECTORY_NAME
140 || ' to public';
141 EXCEPTION
142 WHEN OTHERS
143 THEN
144 RAISE;
145 END;
146 --------------------------------------------分割邮件地址或者附件地址--------------------
147 PROCEDURE P_SPLITE_STR (P_STR VARCHAR2, P_SPLITE_FLAG INT DEFAULT 1 )
148 IS
149 L_ADDR VARCHAR2 (254) := '';
150 L_LEN INT;
151 L_STR VARCHAR2 (4000);
152 J INT := 0; --表示邮件地址或者附件的个数
153 BEGIN
154 /*处理接收邮件地址列表,包括去空格、将;转换为,等*/
155 L_STR :=
156 TRIM (RTRIM (REPLACE (REPLACE (P_STR, ';', ','), ' ', ''), ','));
157 L_LEN := LENGTH (L_STR);
158 FOR I IN 1 .. L_LEN
159 LOOP
160 IF SUBSTR (L_STR, I, 1) <> ','
161 THEN
162 L_ADDR := L_ADDR || SUBSTR (L_STR, I, 1);
163 ELSE
164 J := J + 1;
165 IF P_SPLITE_FLAG = 1
166 THEN --表示处理邮件地址
167 --前后需要加上'<>',否则很多邮箱将不能发送邮件
168 L_ADDR := '<' || L_ADDR || '>';
169 --调用邮件发送过程
170 MY_ADDRESS_LIST (J) := L_ADDR;
171 ELSIF P_SPLITE_FLAG = 2
172 THEN --表示处理附件名称
173 MY_ACCT_LIST (J) := L_ADDR;
174 END IF;
175 L_ADDR := '';
176 END IF;
177 IF I = L_LEN
178 THEN
179 J := J + 1;
180 IF P_SPLITE_FLAG = 1
181 THEN
182 --调用邮件发送过程
183 L_ADDR := '<' || L_ADDR || '>';
184 MY_ADDRESS_LIST (J) := L_ADDR;
185 ELSIF P_SPLITE_FLAG = 2
186 THEN
187 MY_ACCT_LIST (J) := L_ADDR;
188 END IF;
189 END IF;
190 END LOOP;
191 END;
192 ------------------------------------------------写邮件头和邮件内容------------------------------------------
193 PROCEDURE WRITE_DATA (P_CONN IN OUT NOCOPY UTL_SMTP.CONNECTION,
194 P_NAME IN VARCHAR2,
195 P_VALUE IN VARCHAR2,
196 P_SPLITE VARCHAR2 DEFAULT ':' ,
197 P_CRLF VARCHAR2 DEFAULT L_CRLF )
198 IS
199 BEGIN
200 /* utl_raw.cast_to_raw 对解决中文乱码问题很重要*/
201 UTL_SMTP.WRITE_RAW_DATA (
202 P_CONN,
203 UTL_RAW.CAST_TO_RAW (
204 CONVERT (P_NAME || P_SPLITE || P_VALUE || P_CRLF, 'ZHS16GBK')
205 )
206 );
207 END;
208 ----------------------------------------写MIME邮件尾部-----------------------------------------------------
209 PROCEDURE END_BOUNDARY (CONN IN OUT NOCOPY UTL_SMTP.CONNECTION,
210 LAST IN BOOLEAN DEFAULT FALSE )
211 IS
212 BEGIN
213 UTL_SMTP.WRITE_DATA (CONN, UTL_TCP.CRLF);
214 IF (LAST)
215 THEN
216 UTL_SMTP.WRITE_DATA (CONN, LAST_BOUNDARY);
217 END IF;
218 END;
219 ----------------------------------------------发送附件----------------------------------------------------
220 PROCEDURE ATTACHMENT (
221 CONN IN OUT NOCOPY UTL_SMTP.CONNECTION,
222 MIME_TYPE IN VARCHAR2 DEFAULT 'text/plain' ,
223 INLINE IN BOOLEAN DEFAULT TRUE ,
224 FILENAME IN VARCHAR2 DEFAULT 't.txt' ,
225 TRANSFER_ENC IN VARCHAR2 DEFAULT '7 bit' ,
226 DT_NAME IN VARCHAR2 DEFAULT '0'
227 )
228 IS
229 L_FILENAME VARCHAR2 (1000);
230 BEGIN
231 --写附件头
232 UTL_SMTP.WRITE_DATA (CONN, FIRST_BOUNDARY);
233 --设置附件格式
234 WRITE_DATA (CONN, 'Content-Type', MIME_TYPE);
235 --如果文件名称非空,表示有附件
236 DROP_DIRECTORY (DT_NAME);
237 --创建directory
238 CREATE_DIRECTORY (DT_NAME, GET_FILE (FILENAME, 1));
239 --得到附件文件名称
240 L_FILENAME := GET_FILE (FILENAME, 2);
241 IF (INLINE)
242 THEN
243 WRITE_DATA (CONN,
244 'Content-Disposition',
245 'inline; filename="' || L_FILENAME || '"');
246 ELSE
247 WRITE_DATA (CONN,
248 'Content-Disposition',
249 'attachment; filename="' || L_FILENAME || '"');
250 END IF;
251 --设置附件的转换格式
252 IF (TRANSFER_ENC IS NOT NULL)
253 THEN
254 WRITE_DATA (CONN, 'Content-Transfer-Encoding', TRANSFER_ENC);
255 END IF;
256 UTL_SMTP.WRITE_DATA (CONN, UTL_TCP.CRLF);
257 --begin 贴附件内容
258 IF TRANSFER_ENC = 'bit 7'
259 THEN
260 --如果是文本类型的附件
261 BEGIN
262 L_FILE_HANDLE := UTL_FILE.FOPEN (DT_NAME, L_FILENAME, 'r'); --打开文件
263 --把附件分成多份,这样可以发送超过32K的附件
264 LOOP
265 UTL_FILE.GET_LINE (L_FILE_HANDLE, L_LINE);
266 L_MESG := L_LINE || L_CRLF;
267 WRITE_DATA (CONN,
268 '',
269 L_MESG,
270 '',
271 '');
272 END LOOP;
273 UTL_FILE.FCLOSE (L_FILE_HANDLE);
274 END_BOUNDARY (CONN);
275 EXCEPTION
276 WHEN OTHERS
277 THEN
278 UTL_FILE.FCLOSE (L_FILE_HANDLE);
279 END_BOUNDARY (CONN);
280 NULL;
281 END; --结束文本类型附件的处理
282 ELSIF TRANSFER_ENC = 'base64'
283 THEN
284 --如果是二进制类型的附件
285 BEGIN
286 --把附件分成多份,这样可以发送超过32K的附件
287 L_FILEPOS := 1; --重置offset,在发送多个附件时,必须重置
288 L_FIL := BFILENAME (DT_NAME, L_FILENAME);
289 L_FILE_LEN := DBMS_LOB.GETLENGTH (L_FIL);
290 L_MODULO := MOD (L_FILE_LEN, L_AMT);
291 L_PIECES := TRUNC (L_FILE_LEN / L_AMT);
292 IF (L_MODULO <> 0)
293 THEN
294 L_PIECES := L_PIECES + 1;
295 END IF;
296 DBMS_LOB.FILEOPEN (L_FIL, DBMS_LOB.FILE_READONLY);
297 DBMS_LOB.READ (L_FIL,
298 L_AMT,
299 L_FILEPOS,
300 L_BUF);
301 L_DATA := NULL;
302 FOR I IN 1 .. L_PIECES
303 LOOP
304 L_FILEPOS := I * L_AMT + 1;
305 L_FILE_LEN := L_FILE_LEN - L_AMT;
306 L_DATA := UTL_RAW.CONCAT (L_DATA, L_BUF);
307 L_CHUNKS := TRUNC (UTL_RAW.LENGTH (L_DATA) / L_MAX_LINE_WIDTH);
308 IF (I <> L_PIECES)
309 THEN
310 L_CHUNKS := L_CHUNKS - 1;
311 END IF;
312 UTL_SMTP.WRITE_RAW_DATA (CONN,
313 UTL_ENCODE.BASE64_ENCODE (L_DATA));
314 L_DATA := NULL;
315 IF (L_FILE_LEN < L_AMT AND L_FILE_LEN > 0)
316 THEN
317 L_AMT := L_FILE_LEN;
318 END IF;
319 DBMS_LOB.READ (L_FIL,
320 L_AMT,
321 L_FILEPOS,
322 L_BUF);
323 END LOOP;
324 DBMS_LOB.FILECLOSE (L_FIL);
325 END_BOUNDARY (CONN);
326 EXCEPTION
327 WHEN OTHERS
328 THEN
329 DBMS_LOB.FILECLOSE (L_FIL);
330 END_BOUNDARY (CONN);
331 RAISE;
332 END; --结束处理二进制附件
333 END IF; --结束处理附件内容
334 DROP_DIRECTORY (DT_NAME);
335 END; --结束过程ATTACHMENT
336 ---------------------------------------------真正发送邮件的过程--------------------------------------------
337 PROCEDURE P_EMAIL (P_SENDORADDRESS2 VARCHAR2, --发送地址
338 P_RECEIVERADDRESS2 VARCHAR2) --接受地址
339 IS
340 L_CONN UTL_SMTP.CONNECTION; --定义连接
341 BEGIN
342 /*初始化邮件服务器信息,连接邮件服务器*/
343 L_CONN := UTL_SMTP.OPEN_CONNECTION (P_SERVER, P_PORT);
344 UTL_SMTP.HELO (L_CONN, P_SERVER);
345 /* smtp服务器登录校验 */
346 IF P_NEED_SMTP = 1
347 THEN
348 UTL_SMTP.COMMAND (L_CONN, 'AUTH LOGIN', '');
349 UTL_SMTP.COMMAND (
350 L_CONN,
351 UTL_RAW.CAST_TO_VARCHAR2 (
352 UTL_ENCODE.BASE64_ENCODE (UTL_RAW.CAST_TO_RAW (P_USER))
353 )
354 );
355 UTL_SMTP.COMMAND (
356 L_CONN,
357 UTL_RAW.CAST_TO_VARCHAR2 (
358 UTL_ENCODE.BASE64_ENCODE (UTL_RAW.CAST_TO_RAW (P_PASS))
359 )
360 );
361 END IF;
362 /*设置发送地址和接收地址*/
363 UTL_SMTP.MAIL (L_CONN, P_SENDORADDRESS2);
364 UTL_SMTP.RCPT (L_CONN, P_RECEIVERADDRESS2);
365 /*设置邮件头*/
366 UTL_SMTP.OPEN_DATA (L_CONN);
367 WRITE_DATA (L_CONN, 'Date', TO_CHAR (SYSDATE, 'yyyy-mm-dd hh24:mi:ss'));
368 /*设置发送人*/
369 WRITE_DATA (L_CONN, 'From', P_SENDOR);
370 /*设置接收人*/
371 WRITE_DATA (L_CONN, 'To', P_RECEIVER);
372 /*设置邮件主题*/
373 WRITE_DATA (L_CONN, 'Subject', P_SUB);
374 WRITE_DATA (L_CONN, 'Content-Type', MULTIPART_MIME_TYPE);
375 UTL_SMTP.WRITE_DATA (L_CONN, UTL_TCP.CRLF);
376 UTL_SMTP.WRITE_DATA (L_CONN, FIRST_BOUNDARY);
377 WRITE_DATA (L_CONN, 'Content-Type', 'text/plain;charset=gb2312');
378 --单独空一行,否则,正文内容不显示
379 UTL_SMTP.WRITE_DATA (L_CONN, UTL_TCP.CRLF);
380 /* 设置邮件正文
381 把分隔符还原成chr(10)。这主要是为了shell中调用该过程,如果有多行,则先把多行的内容合并成一行,并用 l_splite分隔
382 然后用 l_crlf替换chr(10)。这一步是必须的,否则将不能发送邮件正文有多行的邮件
383 */
384 WRITE_DATA (
385 L_CONN,
386 '',
387 REPLACE (REPLACE (P_TXT, L_SPLITE, CHR (10)), CHR (10), L_CRLF),
388 '',
389 ''
390 );
391 END_BOUNDARY (L_CONN);
392 --如果文件名称不为空,则发送附件
393 IF (P_FILENAME IS NOT NULL)
394 THEN
395 --根据逗号或者分号拆分附件地址
396 P_SPLITE_STR (P_FILENAME, 2);
397 --循环发送附件(在同一个邮件中)
398 FOR K IN 1 .. MY_ACCT_LIST.COUNT
399 LOOP
400 ATTACHMENT (
401 CONN => L_CONN,
402 FILENAME => MY_ACCT_LIST (K),
403 TRANSFER_ENC => P_ENCODE,
404 DT_NAME => L_DIRECTORY_BASE_NAME || TO_CHAR (K)
405 );
406 END LOOP;
407 END IF;
408 /*关闭数据写入*/
409 UTL_SMTP.CLOSE_DATA (L_CONN);
410 /*关闭连接*/
411 UTL_SMTP.QUIT (L_CONN);
412 /*异常处理*/
413 EXCEPTION
414 WHEN OTHERS
415 THEN
416 NULL;
417 RAISE;
418 END;
419 ---------------------------------------------------主过程-----------------------------------------------------
420 BEGIN
421 L_SENDORADDRESS := '<' || P_SENDOR || '>';
422 P_SPLITE_STR (P_RECEIVER); --处理邮件地址
423 FOR K IN 1 .. MY_ADDRESS_LIST.COUNT
424 LOOP
425 P_EMAIL (L_SENDORADDRESS, MY_ADDRESS_LIST (K));
426 END LOOP;
427 /*处理邮件地址,根据逗号分割邮件*/
428 EXCEPTION
429 WHEN OTHERS
430 THEN
431 RAISE;
432 --RAISE_APPLICATION_ERROR(-20000,'PROCSENDEMAIL异常中止');
433 END;