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: empa.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: est.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: est.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;