【Oracle中创建自定义工具包FTP,实现从远程服务器目录下载文件到本地目录】

发布于:2022-12-31 ⋅ 阅读:(261) ⋅ 点赞:(0)

前言:FTP包是国外Oracle ACE大神Tim Hall创建的工具包,本文用于解析FTP.GET方法的实现原理。该方法可以很便捷的实现从远程目录通过TCP协议下载文件到本地目录。该工具包基于Oracle内置UTL_TCP,DBMS_LOB,UTL_FILE包实现。本文不对内置包进行使用说明,可自行查阅文档。

相关链接:

创建语句-FTP包说明
创建语句-FTP包主体
UTL_TCP包Oracle 11g R2官方文档
UTL_FILE包Oracle 11g R2官方文档
DBMS_LOB包Oracle 11g R2官方文档

FTP.GET语法

ftp.get(
	p_conn      in out nocopy utl_tcp.connection,   --传入一个FTP的登录连接
	p_from_file in varchar2,						--需要下载的文件路径
	p_to_dir    in varchar2,						--服务器的目录
	p_to_file   in varchar2) ;						--生成的文件名

实例

--通过ftp下载远程服务器文件下载到本地
declare
  l_conn      UTL_TCP.connection; --定义一个连接类型
  r_from_file varchar2(255);
  l_blob_len  integer;
  p_reason    varchar2(2555) := null; --定义报错信息,默认为null
  v_result    varchar2(255) := 0; --定义结果信息
  v_sign      number(4) := 0; --定义结果状态,默认为0
begin
  --使用ftp.login方法创建连接,参数依次为IP/端口/用户名/密码
  l_conn := ftp.login('127.0.0.1', '21', 'Oracle', 'test');
  begin
    --获取远程文件绝对路径
    r_from_file := '/data/total/'||to_char(add_months(sysdate,-1),'YYYYMM')||'.AVL';
    --获取指定文件字节大小
    l_blob_len := dbms_lob.getlength(ftp.get_remote_binary_data(l_conn,r_from_file));
    --使用ftp.get下载文件
    ftp.get(p_conn      => l_conn, --传入连接
            p_from_file => r_from_file, --件绝对路径
            p_to_dir    => 'JX_AUDIT_LINE', ---传入oracle服务器的目录,需确保dba_directories表下存在
            p_to_file   => 'EPBB_DATA.CSV'); --生成的文件名
    --判断文件是否为空,如果为空返回失败
    if l_blob_len = 0 then
      v_sign   := 1;
      v_result := '失败';
    else
      v_result := '成功';
    end if;
    --获取异常
  exception
    when others then
      --dbms_utility.format_error_backtrace方法用于获取报错的行数,sqlerrm用于获取报错信息
      p_reason := dbms_utility.format_error_backtrace || sqlerrm;
      v_sign   := 1;
      v_result := '失败';
  end;
  ftp.logout(l_conn); --关闭fpt连接
  utl_tcp.close_all_connections; --关闭tcp连接
  insert into gsc_test values(v_sign, v_result || ',数据大小为:' ||l_blob_len, p_reason);
  commit;
end;
--执行日志如下
select * from gsc_test;
sign	result						reason
1		失败,数据大小为:0			"ORA-06512: at ""IRMJX.FTP"", line 299 ORA-06512:at line 16 ORA-20000: 550 Failed to open file."
0		成功,数据大小为:80071357	

方法解析-FTP.LOGIN

该方法用于返回一个TCP的连接

  function login(p_host    in varchar2, --主机IP
                 p_port    in varchar2,	--端口
                 p_user    in varchar2,--用户名
                 p_pass    in varchar2,--密码
                 p_timeout in number := null --超时时间) return utl_tcp.connection is
    l_conn utl_tcp.connection;
  begin
 
  --删除集合中所有的内容,之前定义了g_reply为is table of varchar2(32767)的嵌套表集合
    g_reply.delete;
	--utl_tcp.open_connection用法具体参见UTL_TCP包Oracle 11g R2官方文档
    l_conn := utl_tcp.open_connection(p_host,
                                      p_port,
                                      tx_timeout => p_timeout);
    --调用get_reply方法判断连接信息是否正常
    get_reply(l_conn);
    --连接TCP成功发送用户名和密码指令进行登录
    send_command(l_conn, 'USER ' || p_user);
    send_command(l_conn, 'PASS ' || p_pass);
    return l_conn;
  end;

方法解析-FTP.GET_REPLY

该方法用于判断TPC连接是否正确,并获取主机回复信息

procedure get_reply(p_conn in out nocopy utl_tcp.connection) is
    -- --------------------------------------------------------------------------
    l_reply_code varchar2(3) := null;
  begin
    loop
      --给集合g_reply添加一个空元素
      g_reply.extend;
      --获取连接结果写入到g_reply集合最后一个索引中
      g_reply(g_reply.last) := utl_tcp.get_line(p_conn, true);
      --不知道干什么用的
      debug(g_reply(g_reply.last));
      if l_reply_code is null then
      --截取TPC连接的前三位状态码例如,220,404写入到l_reply_code 中
        l_reply_code := substr(g_reply(g_reply.last), 1, 3);
      end if;
      --如果状态码的首位为4或者5,代表访问失败
      if substr(l_reply_code, 1, 1) in ('4', '5') then
      --RAISE_APPLICATION_ERROR函数用于自定义错误类型,并返回给控制台
        raise_application_error(-20000, g_reply(g_reply.last));
        --如果集合中的最后一个索引等于状态码和g_reply的第四个位置为空格便退出
      elsif (substr(g_reply(g_reply.last), 1, 3) = l_reply_code and
            substr(g_reply(g_reply.last), 4, 1) = ' ') then
        exit;
      end if;
    end loop;
  exception
    when utl_tcp.end_of_input then
      null;
  end;

方法解析- FTP.SEND_COMMAND

该方法用于发送TCP写入指令

procedure send_command(p_conn    in out nocopy utl_tcp.connection,
                         p_command in varchar2,
                         p_reply   in boolean := true) is
    l_result pls_integer;
  begin
  --发送信息
    l_result := utl_tcp.write_line(p_conn, p_command);
    if p_reply then
    --调用get_reply获取主机回复信息
      get_reply(p_conn);
    end if;
  end;

方法解析-GET_REMOTE_BINARY_DATA

该方法用于将文件数据写入到一个临时的blob中

function get_remote_binary_data(p_conn in out nocopy utl_tcp.connection,
                                  p_file in varchar2) return blob is
    -- --------------------------------------------------------------------------
    l_conn   utl_tcp.connection;
    l_amount pls_integer;
    l_buffer raw(32767);
    l_data   blob;
  begin
  --dbms_lob.createtemporary过程用于在用户的默认临时表空间中创建一个临时的BLOB或者CLOB及其对应的索引
    dbms_lob.createtemporary(lob_loc => l_data,
                             cache   => true,
                             dur     => dbms_lob.call);
    --获取下载文件的端口连接
    l_conn := get_passive(p_conn);
    --发送RETR 命令下载文件
    send_command(p_conn, 'RETR ' || p_file, true);
	--
    begin
      loop
       --在打开的连接上从服务接收二进制数据
       --utl_tcp.read_raw会返回接受数据的长度,l_buffer是接收到的数据,具体使用方法不做具体描述,请查阅文档
        l_amount := utl_tcp.read_raw(l_conn, l_buffer, 32767);
        --dbms_lob.writeappend会将读取到的数据写入到blob中
        dbms_lob.writeappend(l_data, l_amount, l_buffer);
      end loop;
    exception
      when utl_tcp.end_of_input then
        null;
      when others then
        null;
    end;
    utl_tcp.close_connection(l_conn);
    get_reply(p_conn);

    return l_data;

  exception
    when others then
      utl_tcp.close_connection(l_conn);
      raise;
  end;

方法解析-GET_PASSIVE

该方法用于获取PASV模式返回的端口,打开新的传输连接

function get_passive(p_conn in out nocopy utl_tcp.connection)
    return utl_tcp.connection is
    -- --------------------------------------------------------------------------
    l_conn  utl_tcp.connection;
    l_reply varchar2(32767);
    --l_host    VARCHAR(100);
    l_port1 number(10);
    l_port2 number(10);
  begin
  --发送指令打开PASV模式传输数据
    send_command(p_conn, 'PASV');
    --获取远程主机返回的端口列表
    --227 Entering Passive Mode (10,239,53,98,180,202).
    l_reply := g_reply(g_reply.last);
   --替换端口信息的,替换成. 10.239.53.98.180.202
    l_reply := replace(substr(l_reply,
                              instr(l_reply, '(') + 1,
                              (instr(l_reply, ')')) - (instr(l_reply, '(')) - 1),
                       ',',
                       '.');
    --l_host  := SUBSTR(l_reply, 1, INSTR(l_reply, '.', 1, 4)-1);
    --截取端口:180
    l_port1 := to_number(substr(l_reply,
                                instr(l_reply, '.', 1, 4) + 1,
                                (instr(l_reply, '.', 1, 5) - 1) -
                                (instr(l_reply, '.', 1, 4))));
     --截取端口202
    l_port2 := to_number(substr(l_reply, instr(l_reply, '.', 1, 5) + 1));
	
    --l_conn := utl_tcp.open_connection(l_host, 256 * l_port1 + l_port2);
    --打开传输端口连接46282
    l_conn := utl_tcp.open_connection(p_conn.remote_host,
                                      256 * l_port1 + l_port2);
    return l_conn;
  end;

方法解析-PUT_LOCAL_BINARY_DATA

该方法用于将默认临时表空间中临时的BLOB写入到指定文件中

procedure put_local_binary_data(p_data in blob,
                                  p_dir  in varchar2,
                                  p_file in varchar2) is
    -- --------------------------------------------------------------------------
    l_out_file utl_file.file_type;
    l_buffer   raw(32767);
    l_amount   binary_integer := 32767;
    l_pos      integer := 1;
    l_blob_len integer;
  begin
  --获取临时文件的长度
    l_blob_len := dbms_lob.getlength(p_data);
 --打开要下载到的文件,'wb'代表写入字节模式
    l_out_file := utl_file.fopen(p_dir, p_file, 'wb', 32767);
--循环写入数据
    while l_pos <= l_blob_len loop
    --读取p_data的数据
      dbms_lob.read(p_data, l_amount, l_pos, l_buffer);
      --写入数据到文件
      utl_file.put_raw(l_out_file, l_buffer, true);
      --刷新文件信息
      utl_file.fflush(l_out_file);
      --计算写入数据值
      l_pos := l_pos + l_amount;
    end loop;
--关闭文件
    utl_file.fclose(l_out_file);
  exception
    when others then
      if utl_file.is_open(l_out_file) then
        utl_file.fclose(l_out_file);
      end if;
      raise;
  end;

最终控制台输出如下

220 "Authorized users only. All activity may be monitored and reported"
331 Please specify the password.
230 Login successful.
227 Entering Passive Mode (10,239,53,98,180,202).
150 Opening BINARY mode data connection for /irms/data/.AVL (80071357 bytes).
226 Transfer complete.
227 Entering Passive Mode (10,239,53,98,171,105).
150 Opening BINARY mode data connection for /irms/data/.AVL (80071357 bytes).
226 Transfer complete.
221 Goodbye.

网站公告

今日签到

点亮在社区的每一天
去签到