首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> Oracle 資料庫 >> Oracle 資料庫基礎

Oracle UTL_SMTP 發送郵件完整實戰代碼 (第四部分:附件總控與文字附件處理)

     編輯:Oracle 資料庫基礎

代碼定位

本文為完整郵件發送套件的**第四部分**,涵蓋:**字串分割處理 (收件人/附件路徑)**、**郵件頭與正文寫入**、 **MIME 邊界結尾**、 **附件總控程序 ATTACHMENT**、**文字附件逐行讀取傳輸**、 **二進制附件處理入口**。

1. 字串分割核心:P_SPLITE_STR


PROCEDURE P_SPLITE_STR (
  P_STR VARCHAR2, 
  P_SPLITE_FLAG INT DEFAULT 1  -- 1: 收件人地址, 2: 附件檔名
) 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 P_SPLITE_STR;

關鍵點

  • 支援 `;` 和 `,` 混用分隔
  • 自動去除空格
  • 收件人自動包裝 `< >` (SMTP 標準格式,否則多郵箱拒收)
  • 結果存入索引表 `MY_ADDRESS_LIST` / `MY_ACCT_LIST`

2. 郵件頭與正文寫入:WRITE_DATA


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 + CONVERT 解決中文亂碼
  UTL_SMTP.WRITE_RAW_DATA(
    P_CONN,
    UTL_RAW.CAST_TO_RAW(
      CONVERT(P_NAME || P_SPLITE || P_VALUE || P_CRLF, 'ZHS16GBK')
    )
  );
END WRITE_DATA;

編碼處理精華

  • `CONVERT(..., 'ZHS16GBK')`:將資料庫字符集 (如 AL32UTF8) 轉為郵件指定編碼 (GB2312/GBK)
  • `UTL_RAW.CAST_TO_RAW`:轉為 RAW 型別,`WRITE_RAW_DATA` 直接傳輸位元組流,避免隱性字符集轉換
  • 郵件頭 `Content-Type: text/plain;charset=gb2312` 必須與實際編碼一致

3. MIME 邊界結尾:END_BOUNDARY


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);  -- --BOUNDARY--
  END IF;
END END_BOUNDARY;

4. 附件總控:ATTACHMENT 程序


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 'bit 7',  -- 'bit 7'=文字, 'base64'=二進制
  DT_NAME IN VARCHAR2 DEFAULT '0'            -- 動態目錄物件名稱前綴
) IS
  L_FILENAME VARCHAR2(1000);
BEGIN
  -- 寫入 MIME 邊界開頭
  UTL_SMTP.WRITE_DATA(CONN, FIRST_BOUNDARY);

  -- 附件 Content-Type
  WRITE_DATA(CONN, 'Content-Type', MIME_TYPE);

  -- 刪除舊同名目錄物件 (避免衝突)
  DROP_DIRECTORY(DT_NAME);

  -- 建立動態目錄物件指向附件所在目錄
  CREATE_DIRECTORY(DT_NAME, GET_FILE(FILENAME, 1));

  -- 解析純檔名
  L_FILENAME := GET_FILE(FILENAME, 2);

  -- Content-Disposition: inline (內嵌顯示) 或 attachment (下載)
  IF INLINE THEN
    WRITE_DATA(CONN, 'Content-Disposition', 'inline; filename="' || L_FILENAME || '"');
  ELSE
    WRITE_DATA(CONN, 'Content-Disposition', 'attachment; filename="' || L_FILENAME || '"');
  END IF;

  -- Transfer-Encoding
  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);  -- 空行分隔頭與體

  -- === 附件內容傳輸分支 ===
  IF TRANSFER_ENC = 'bit 7' THEN
    -- 文字附件:UTL_FILE 逐行讀取
    BEGIN
      L_FILE_HANDLE := UTL_FILE.FOPEN(DT_NAME, L_FILENAME, 'r');
      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;  -- 吞沒例外 (建議改為 RAISE)
    END;

  ELSIF TRANSFER_ENC = 'base64' THEN
    -- 二進制附件:轉入 DBMS_LOB 分片處理 (見第五部分)
    BEGIN
      -- 此處呼叫第五部分的分片邏輯
      NULL;  -- 佔位
    END;
  END IF;
END ATTACHMENT;

5. 輔助函數:路徑解析與目錄物件管理

GET_FILE — 解析目錄與檔名


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 GET_FILE;

DROP_DIRECTORY — 安全刪除目錄物件


PROCEDURE DROP_DIRECTORY (P_DIRECTORY_NAME VARCHAR2) IS
BEGIN
  EXECUTE IMMEDIATE 'DROP DIRECTORY ' || P_DIRECTORY_NAME;
EXCEPTION
  WHEN OTHERS THEN NULL;  -- 不存在時忽略
END DROP_DIRECTORY;

CREATE_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 CREATE_DIRECTORY;

完整套件架構 (四、五部分合併視圖)


PROCSENDEMAIL (主入口)
  ├─ P_SPLITE_STR (字串分割)
  ├─ GET_FILE (路徑解析)
  ├─ CREATE_DIRECTORY / DROP_DIRECTORY (動態目錄)
  ├─ WRITE_DATA (頭/體寫入 + 編碼轉換)
  ├─ END_BOUNDARY (MIME 邊界)
  ├─ ATTACHMENT (附件總控)
  │    ├─ 文字分支: UTL_FILE.GET_LINE 逐行
  │    └─ 二進制分支: DBMS_LOB 分片 Base64 (第五部分)
  └─ P_EMAIL (SMTP 會話總控)
       ├─ OPEN_CONNECTION / HELO / AUTH
       ├─ MAIL / RCPT / OPEN_DATA
       ├─ 寫入頭、正文、附件
       └─ CLOSE_DATA / QUIT

關鍵設計決策總結

決策點選擇理由
動態目錄物件每個附件建立獨立目錄物件 (DIR_FOR_SEND_MAIL1, 2...)避免並發衝突、權限隔離、用完即刪
編碼轉換CONVERT + UTL_RAW.CAST_TO_RAW + WRITE_RAW_DATA徹底解決中文亂碼,支援任意資料庫字符集
文字附件傳輸UTL_FILE.GET_LINE 逐行記憶體佔用極低,支援超大文字檔
二進制附件傳輸DBMS_LOB 分片 + Base64 (2016 位元組/片)記憶體恆定、支援 GB 級附件、符合 SMTP 行長度
收件人格式自動包裝 <email@domain>符合 RFC 5321,避免郵箱拒收
多行正文傳遞Shell 以 ++ 分隔,PL/SQL 還原 CHR(10)繞過命令列換行限制

此為生產級郵件發送套件核心邏輯 (第 4/5 部分),配合第一~三部分 (參數宣告、主程序入口、Shell 調用) 可構建完整可用系統。部署時必須配置 ACL 授權 SMTP 主機埠。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved