SAP与OBS通讯接口架构及技术说明
目录
文章目录
- SAP与OBS通讯接口架构及技术说明
- 目录
- 1. 业务背景
- 2. 接口架构
- 3. 接口实现
- 3.1 计算签名
- 3.1.1 签名算法的实现
- 3.1.1.1 OBS 授权方式
- 3.1.1.2 Header和URL中携带签名的区别
- 3.1.1.3 对象
- 3.1.1.4 签名算法实现
- 3.1.2 PI接口
- 3.1.2.1 创建PI接口和User-Defined Function
- 3.1.2.2 PI代码实现
- 3.2 文件操作
- 3.2.1 附件上载 UPLOAD_ATTACHMENT
- 3.2.2 附件下载 DOWNLOAD_ATTACHMENT
- 3.2.3 附件删除 DELETE_ATTACHMENT
- 3.2.4 文件内容下载 DOWNLOAD_FILE
- 3.2.5 显示附件 DISPLAY_ATTACHMENT
- 3.2.6 复制附件
- 3.2.6.1 两步法:先下载再上载 COPY_ATTACHMENT_NEW
- 3.2.6.2 一步法:直接调用OBS复制功能COPY_ATTACHMENT
- 3.2.6.3 测试程序
- 3.3 桶的基本操作
- 3.3.1 列举桶内对象
- 3.3.1.1 列举桶内对象采用GET方法
- 3.3.1.2 计算签名
- 3.3.1.3 设置HTTP头域
- 3.3.1.4 设置url
- 3.3.1.5 调用HTTP返回对象清单
- 3.3.1.6 解析返回的XML文件
- 3.3.1.7 源码实现
- 3.3.1.8 测试程序
1. 业务背景
业务需求用文件传输来实现接口,或者需要将附件保存到服务器(照片、合同附件等等)。
常规的做法有两种:
一种是搭建FTP/SFTP服务器。
SAP封装了FTP函数来操作文件。 常用的FTP函数如下,FTP_CONNECT 连接FTP
FTP_DISCONNECT 断开连接
FTP_COMMAND 发送FTP命令
FTP_COMMAND_LIST 发送多个FTP命令
FTP_R3_TO_SERVER 将文件放到FTP
FTP_SERVER_TO_R3 从FTP读取文件
但对于SFTP,需要安全认证,目前ABAP不能直接访问SFTP,需借用PI或JAVA来实现。
第二种是搭建文档服务器或存储服务器。使用HTTP RESTful访问(PUT、GET、DELETE等等)。 一般在访问前可以通过获取Token或签名的方式,来保证文件的安全访问。
本文介绍的华为云OBS(以下简称OBS),是华为云的对象存储服务。
2. 接口架构
对开发者来说,OBS提供了API和SDK的方式访问。由于ABAP代码不能引入JAR包,所以只能用API的方式。
OBS的访问需要先根据AK(访问密钥)和SK(私有访问密钥)计算签名,然后将签名放到HTTP请求中。
由于ABAP不能实现这个签名算法,所以我们需要一个能执行这段JAVA代码的环境来计算签名(当然,如果后续SAP支持这段算法计算的,也可以直接在SAP计算签名)。
假设有一个外围的JAVA系统能开启一个服务计算签名,这个问题就解决了。但目前没有这样的外围系统,所以我们借用PI的User-Defined Function(UDF)来说实现这段签名算法。
借用PI的UDF需要配置PI接口,但我们不需要跟外围系统进行交互, 所以PI的发送方和接受方都是SAP系统。
3. 接口实现
3.1 计算签名
3.1.1 签名算法的实现
3.1.1.1 OBS 授权方式
OBS授权方式主要有4种,详细可参考华为云官方文档。https://support.huaweicloud.com/api-obs/obs_04_0008.html
- 1. 用户签名验证
- 2. Header中携带签名
- 3. URL中携带签名
- 4. 基于浏览器上传的表单中携带签名
本次接口使用第2种和第3种。
3.1.1.2 Header和URL中携带签名的区别
签名算法不一样
URL中携带签名
需要传入有效期(单位秒),当前系统时间的秒数+传入的有效期作为计算签名的参数
StringToSign = HTTP-Verb + "\n" + Content-MD5 + "\n" + Content-Type + "\n" + Expires + "\n" + CanonicalizedHeaders + CanonicalizedResource;
Header中携带签名
取当前系统时间转换成GMT格式作为计算签名的参数
StringToSign = HTTP-Verb + "\n" + Content-MD5 + "\n" + Content-Type + "\n" + Date + "\n" + CanonicalizedHeaders + CanonicalizedResource
创建HTTP请求时,授权信息放的位置不一样
URL中携带签名,授权信息作为URL的参数:
HOST/ObjectKey” />
在消息类型映射Message Mapping中引用自定义的User-Defined Function
3.1.2.2 PI代码实现
- 创建一个公共类ZCL_OBS,包含SAP与OBS接口通讯的所有操作
- 在S4创建自定义签名信息表ZOBST_SIGNTRUE,存储PI返回的签名信息
- 实现PI接口代理类:
在类ZCL_OBS中创建获取签名的方法GET_SIGNTRUE,- 先调用PI Outbound的接口,将计算签名所需的信息发送到PI。
- 在PI Inbound接口中,将返回的签名信息存到表ZOBS_SIGNTRUE;
- 在方法GET_SIGNTRUE中取出返回的签名信息。
METHOD get_signtrue. DATA: lo_proxy_out TYPE REF TO zpico_si_bc000501_erp2erp_syn, ls_output TYPE zpimt_bc000501_req_out, ls_input TYPE zpimt_bc000501_res, ls_bc000501_req TYPE zpidt_bc000501_req, lv_message TYPE bapi_msg, lv_uuid TYPE sysuuid_c32, lv_seconds TYPE p LENGTH 3 DECIMALS 1 VALUE '0.1'. CLEAR: ls_output, ls_input, es_return, rs_signtrue, lv_uuid. " 1. 根据桶的配置获取AK/SK CLEAR: ms_obs_config. CALL METHOD get_obs_config EXPORTING iv_bucketname = is_signtrue_req-bucketname* iv_httpmethod = is_signtrue_req-httpmethod IMPORTING es_obs_config = ms_obs_config. IF ms_obs_config IS INITIAL. "桶&1方法&2的签名配置丢失或未激活,请检查表ZOBS_CONFIG MESSAGE e003 WITH is_signtrue_req-bucketname is_signtrue_req-httpmethod INTO lv_message. es_return = zcl_message=>convert_syst_to_bapiret2( ). RETURN. ELSE. "insert begin by kevin.liang on 20210401 "AK/SK包含空格会导致计算签名出错 CONDENSE: ms_obs_config-obsak , ms_obs_config-obssk . "insert end by kevin.liang on 20210401 ENDIF. "2. 调用PI计算签名 CLEAR: ls_bc000501_req. "获取UUID TRY . CALL METHOD cl_system_uuid=>if_system_uuid_static~create_uuid_c32 RECEIVING uuid = lv_uuid. CATCH cx_uuid_error. ENDTRY. ls_bc000501_req-uuid = lv_uuid ."请求PI的唯一码 ls_bc000501_req-http_method = mv_http_method ."http请求操作方法,PUT/GET/DELETE ls_bc000501_req-access_key = ms_obs_config-obsak ."访问密钥AK ls_bc000501_req-security_key = ms_obs_config-obssk ."私有访问密钥SK ls_bc000501_req-author_type = is_signtrue_req-authortype ."签名授权方式(Header中携带签名、URL中携带签名) ls_bc000501_req-expires = is_signtrue_req-expires ."有效期 ls_bc000501_req-content_type = is_signtrue_req-contenttype ."Content-Type内容类型 ls_bc000501_req-content_md5 = is_signtrue_req-contentmd5 ."Content-MD5 ls_bc000501_req-canonicalize_headers = is_signtrue_req-canonicalizeheaders ."HTTP请求头域中的OBS请求头字段 ls_bc000501_req-canonicalize_resource = is_signtrue_req-canonicalizeresource."HTTP请求所指定的OBS资源 "调用PI计算签名 CALL METHOD calc_signtrue EXPORTING is_bc000501_req = ls_bc000501_req IMPORTING es_return = es_return RECEIVING rs_signtrue = rs_signtrue. ms_signtrue = rs_signtrue. ENDMETHOD.
3.2 文件操作
创建配置表ZOBS_CONFIG, 用来配置每个桶不同请求方法的AK、SK以及日志保留天数等等。
在类ZCL_OBS中创建6基本操作方式,授权方式默认使用header中携带签名;
[附件上载UPLOAD_ATTACHMENT](#3.2.1 附件上载 UPLOAD_ATTACHMENT)>;
[附件下载DOWNLOAD_ATTACHMENT](#3.2.2 附件下载 DOWNLOAD_ATTACHMENT) ;
[附件删除DELETE_ATTACHMENT](#3.2.3 附件删除 DELETE_ATTACHMENT);
另外创建直接[下载文件内容的方法DOWNLOAD_FILE](#3.2.4 文件内容下载 DOWNLOAD_FILE),采用**[URL携带签名的方式授权](#3.1.1.1 OBS 授权方式)**;
[显示附件 DISPLAY_ATTACHMENT](#3.2.5 显示附件 DISPLAY_ATTACHMENT)
[复制附件](#3.2.6 复制附件)
3.2.1 附件上载 UPLOAD_ATTACHMENT
附件上载使用PUT方法;
调用计算签名方法;
创建HTTP客户端,HOST需要包含Object Key;
设置HTTP头域(Header);
将本地文件转换成XSTRING,并填充到HTTP Client;
发送请求,并提取返回的结果。
METHOD upload_attachment. CALL METHOD upload_object EXPORTING iv_bucketname = iv_bucketname iv_filename = iv_filename iv_data = iv_data iv_objectkey = iv_objectkey IMPORTING es_return = es_return es_result = es_result. "记录日志 logging_obslog( is_return = es_return is_result = es_result ). "日志自动清理 clear_obslog( ). ENDMETHOD.
METHOD upload_object. DATA: lv_objectkey TYPE string. DATA: lv_data TYPE xstring, lv_filelength TYPE i, lv_message TYPE bapi_msg. "设置桶信息 set_bucket( iv_bucketname = iv_bucketname iv_objectkey = iv_objectkey ). "设置OBS操作 set_action( c_action_upload ). CLEAR: lv_objectkey. lv_objectkey = iv_objectkey. CALL METHOD adjust_objectkey IMPORTING es_return = es_return CHANGING cv_objectkey = lv_objectkey. IF es_return-type NA 'EAX'. "获取OBS的签名 CLEAR:ms_signtrue_req , es_return , es_result . ms_signtrue_req-bucketname = iv_bucketname. ms_signtrue_req-httpmethod = mv_http_method. ms_signtrue_req-authortype = c_authortype_header. ms_signtrue_req-contenttype = c_contenttype_bin . ms_signtrue_req-contentmd5 = ''. ms_signtrue_req-canonicalizeheaders = ''. ms_signtrue_req-canonicalizeresource = '/' && iv_bucketname && lv_objectkey. CALL METHOD get_signtrue EXPORTING is_signtrue_req = ms_signtrue_req IMPORTING es_return = es_return. ENDIF. IF es_return-type NA 'EAX'. "上载文件 IF iv_filename IS NOT INITIAL. CALL METHOD gui_upload EXPORTING iv_filename = iv_filename IMPORTING ev_filelength = lv_filelength RECEIVING rv_data = lv_data EXCEPTIONS error = 1 OTHERS = 2. IF sy-subrc 0. es_return = zcl_message=>convert_syst_to_bapiret2( ). ENDIF. ELSEIF iv_data IS NOT INITIAL. lv_data = iv_data. lv_filelength = xstrlen( lv_data ). ELSE. "上载内容不能为空 MESSAGE e015 INTO lv_message. es_return = zcl_message=>convert_syst_to_bapiret2( ). ENDIF. IF es_return-type NA 'EAX'. "采用HTTP Client访问OBS call_obs_httpclient( EXPORTING iv_objectkey = lv_objectkey iv_data = lv_data "二进制数据 iv_length = lv_filelength IMPORTING es_return = es_return es_result = es_result ). IF es_result-code = 200. "附件上载成功,ObjectKey为&1. MESSAGE s005 WITH iv_objectkey INTO lv_message. es_return = zcl_message=>convert_syst_to_bapiret2( ). ELSE. "附件上载失败,错误原因:&1&2 MESSAGE e006 WITH es_result-code es_result-reason INTO lv_message. es_return = zcl_message=>convert_syst_to_bapiret2( ). ENDIF. ENDIF. ENDIF. ENDMETHOD.
测试程序ZOBS_DEMO_ATTCH_UPLOAD
*&---------------------------------------------------------------------**& Report ZOBS_ATTCH_UPLOAD_TEST*&---------------------------------------------------------------------**&*&---------------------------------------------------------------------*REPORT zobs_demo_attch_upload.SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME TITLE TEXT-s01. PARAMETERS: p_bucket TYPE zzbucket_obs. PARAMETERS: p_file TYPE rlgrap-filename MEMORY ID zfilename.SELECTION-SCREEN END OF BLOCK b1.AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_file. PERFORM frm_select_file CHANGING p_file.START-OF-SELECTION. "上载文件 PERFORM frm_upload_file.*&---------------------------------------------------------------------**& Form frm_upload_file*&---------------------------------------------------------------------**& text*&---------------------------------------------------------------------**& --> p1 text*& <-- p2 text*&---------------------------------------------------------------------*FORM frm_upload_file . DATA: lo_obs TYPE REF TO zcl_obs, lv_filename TYPE string, lv_objectkey TYPE string. lv_filename = p_file. CALL FUNCTION 'SO_SPLIT_FILE_AND_PATH' EXPORTING full_name = lv_filename IMPORTING stripped_name = lv_objectkey* FILE_PATH = EXCEPTIONS x_error = 1 OTHERS = 2. IF sy-subrc 0.* Implement suitable error handling here ENDIF. lv_objectkey = '/sapdemo/' && sy-datum && '/' && lv_objectkey. CREATE OBJECT lo_obs. CALL METHOD lo_obs->upload_attachment EXPORTING iv_bucketname = p_bucket iv_filename = lv_filename iv_objectkey = lv_objectkey* iv_expires = 3600 IMPORTING es_return = DATA(ls_return). IF ls_return IS NOT INITIAL.. MESSAGE ID ls_return-id TYPE ls_return-type NUMBER ls_return-number WITH ls_return-message_v1 ls_return-message_v2 ls_return-message_v3 ls_return-message_v4. ENDIF.ENDFORM.*&---------------------------------------------------------------------**& Form frm_select_file*&---------------------------------------------------------------------**& text*&---------------------------------------------------------------------**& --> p1 text*& file_open_dialog EXPORTING window_title = lv_title* file_filter = 'Excel(*.xlsx)|*.xlsx|Excel(*.xls)|*.xls' CHANGING file_table = lt_filetable rc = lv_subrc EXCEPTIONS file_open_dialog_failed = 1 cntl_error = 2 error_no_gui = 3 not_supported_by_gui = 4 OTHERS = 5. IF sy-subrc EQ 0. LOOP AT lt_filetable INTO ls_filetable. cv_file = ls_filetable-filename. ENDLOOP. ENDIF.ENDFORM.
3.2.2 附件下载 DOWNLOAD_ATTACHMENT
附件下载,FTYPE参数默认是BIN,返回二进制格式;如果需要[直接返回文本内容](#3.2.4 文件内容下载 DOWNLOAD_FILE),则赋值为TEXT
附件上载使用GET方法;
调用计算签名方法;
创建HTTP客户端,HOST需要包含Object Key;
设置HTTP头域(Header);
发送请求;
获取返回的结果,并将返回的数据XSTRING下载到本地。
METHOD download_attachment. CALL METHOD download_object EXPORTING iv_bucketname = iv_bucketname iv_objectkey = iv_objectkey iv_filetype = iv_filetype iv_direct_down = iv_direct_down iv_newline = iv_newline iv_default_filename = iv_default_filename IMPORTING es_return = es_return es_result = es_result. "记录日志 logging_obslog( is_return = es_return is_result = es_result ). "日志自动清理 clear_obslog( ). ENDMETHOD.
METHOD download_object. DATA: lv_message TYPE bapi_msg. DATA: lv_filename TYPE string, lv_extension TYPE string, lv_objectkey TYPE string, lv_user_action TYPE i, lv_action TYPE zzaction_obs. DATA:lt_extension TYPE TABLE OF string, lv_lines TYPE i. "设置桶信息 set_bucket( iv_bucketname = iv_bucketname iv_objectkey = iv_objectkey ). "设置OBS操作 set_action( c_action_download ). CLEAR: lv_objectkey. lv_objectkey = iv_objectkey. CALL METHOD adjust_objectkey IMPORTING es_return = es_return CHANGING cv_objectkey = lv_objectkey. IF es_return-type CA 'EAX'. RETURN. ENDIF. "获取OBS的签名 CLEAR:ms_signtrue_req , es_return , es_result . ms_signtrue_req-bucketname = iv_bucketname. ms_signtrue_req-httpmethod = mv_http_method. ms_signtrue_req-authortype = c_authortype_header. ms_signtrue_req-contenttype = c_contenttype_bin . ms_signtrue_req-contentmd5 = ''. ms_signtrue_req-canonicalizeheaders = ''. ms_signtrue_req-canonicalizeresource = '/' && iv_bucketname && lv_objectkey. CALL METHOD get_signtrue EXPORTING is_signtrue_req = ms_signtrue_req IMPORTING es_return = es_return. IF es_return-type CA 'EAX'. RETURN. ENDIF. "采用HTTP Client访问OBS call_obs_httpclient( EXPORTING iv_objectkey = lv_objectkey IMPORTING es_return = es_return es_result = es_result ). IF es_result-code = 200. "附件二进制下载 IF iv_direct_down IS NOT INITIAL AND iv_filetype = 'BIN'.* 取得OBS key的文件名 "根据Object key设置默认文件名 CALL METHOD get_default_filename EXPORTING iv_objectkey = iv_objectkey IMPORTING ev_filename = lv_filename ev_extension = lv_extension. IF iv_default_filename IS NOT INITIAL. "使用OBS Key的后半部分作为文件名 lv_filename = iv_default_filename. "使用外部传入的文件名 ENDIF. CALL METHOD file_save_dialog EXPORTING default_extension = lv_extension default_file_name = lv_filename CHANGING fullpath = lv_filename user_action = lv_user_action EXCEPTIONS error = 1 OTHERS = 2. IF sy-subrc 0 OR lv_user_action = 9. es_return = zcl_message=>convert_syst_to_bapiret2( ). RETURN. ENDIF.* 判断后缀名是否与服务器文件名一致 REFRESH: lt_extension . SPLIT lv_filename AT '.' INTO TABLE lt_extension . CLEAR: lv_lines. DESCRIBE TABLE lt_extension LINES lv_lines. IF lv_lines > 0. READ TABLE lt_extension INTO DATA(lv_extension_t) INDEX lv_lines. IF sy-subrc EQ 0. IF lv_extension_t lv_extension. CONCATENATE lv_filename '.' lv_extension INTO lv_filename. ENDIF. ELSE. CONCATENATE lv_filename '.' lv_extension INTO lv_filename. ENDIF. ENDIF. CALL METHOD gui_download EXPORTING iv_filename = lv_filename iv_data = es_result-rawbizdata EXCEPTIONS error = 1 OTHERS = 2. ELSEIF iv_filetype = 'TEXT'."直接提取文本内容 IF iv_newline IS NOT INITIAL. "LF换行 SPLIT es_result-bizdata AT cl_abap_char_utilities=>newline INTO TABLE es_result-data[]. ELSE. "CR_LF换行 SPLIT es_result-bizdata AT cl_abap_char_utilities=>cr_lf INTO TABLE es_result-data[]. ENDIF. ENDIF. "附件下载成功,ObjectKey为&1. MESSAGE s009 WITH iv_objectkey INTO lv_message. es_return = zcl_message=>convert_syst_to_bapiret2( ). ELSE. "附件下载失败,错误原因:&1&2 MESSAGE e010 WITH es_result-code es_result-reason INTO lv_message. es_return = zcl_message=>convert_syst_to_bapiret2( ). ENDIF. ENDMETHOD.
测试程序ZOBS_DEMO_ATTCH_DOWNLOAD
*&---------------------------------------------------------------------**& Report ZOBS_ATTCH_DOWNLOAD_TEST*&---------------------------------------------------------------------**&*&---------------------------------------------------------------------*REPORT zobs_demo_attch_download.SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME TITLE TEXT-s01. PARAMETERS: p_bucket TYPE zzbucket_obs DEFAULT 'mrm-attachment'. PARAMETERS: p_objkey TYPE zzobjectkey_obs . PARAMETERS: p_ftype TYPE char10 DEFAULT 'BIN'. SELECTION-SCREEN COMMENT 50(50) TEXT-001 FOR FIELD p_ftype ."默认BIN,直接下载文件内容TEXTSELECTION-SCREEN END OF BLOCK b1.START-OF-SELECTION. "下载文件 PERFORM frm_download_attachment.*&---------------------------------------------------------------------**& Form frm_download_attachment*&---------------------------------------------------------------------**& text*&---------------------------------------------------------------------**& --> p1 text*& download_attachment EXPORTING iv_bucketname = p_bucket iv_objectkey = lv_objectkey IMPORTING es_return = ls_return. ELSE. CALL METHOD lo_obs->download_attachment EXPORTING iv_bucketname = p_bucket iv_objectkey = lv_objectkey iv_filetype = 'TEXT' iv_newline = 'X' IMPORTING es_return = ls_return es_result = DATA(ls_result). cl_demo_output=>display( data = ls_result-data ). ENDIF. IF ls_return IS NOT INITIAL.. MESSAGE ID ls_return-id TYPE 'S' NUMBER ls_return-number WITH ls_return-message_v1 ls_return-message_v2 ls_return-message_v3 ls_return-message_v4 DISPLAY LIKE ls_return-type. ENDIF.ENDFORM.
3.2.3 附件删除 DELETE_ATTACHMENT
附件上载使用DELETE方法;
调用计算签名方法GET_SIGNTRUE;
创建HTTP客户端,HOST需要包含Object Key;
设置HTTP头域(Header);
发送请求,根据状态码=204判断文件是否删除成功(备注:如果删除的对象不存在,删除也是返回204)。
METHOD delete_attachment. CALL METHOD delete_object EXPORTING iv_bucketname = iv_bucketname iv_objectkey = iv_objectkey IMPORTING es_return = es_return es_result = es_result. "记录日志 logging_obslog( is_return = es_return is_result = es_result ). "日志自动清理 clear_obslog( ). ENDMETHOD.
METHOD DELETE_OBJECT. DATA: lv_objectkey TYPE string, lv_message TYPE bapi_msg. "设置桶信息 set_bucket( iv_bucketname = iv_bucketname iv_objectkey = iv_objectkey ). "设置OBS操作 set_action( c_action_delete ). CLEAR: lv_objectkey. lv_objectkey = iv_objectkey. CALL METHOD adjust_objectkey IMPORTING es_return = es_return CHANGING cv_objectkey = lv_objectkey. IF es_return-type NA 'EAX'. "获取OBS的签名 CLEAR:ms_signtrue_req , es_return , es_result . ms_signtrue_req-bucketname = iv_bucketname. ms_signtrue_req-httpmethod = mv_http_method. ms_signtrue_req-authortype = c_authortype_header. ms_signtrue_req-contenttype = c_contenttype_bin . ms_signtrue_req-contentmd5 = ''. ms_signtrue_req-canonicalizeheaders = ''. ms_signtrue_req-canonicalizeresource = '/' && iv_bucketname && lv_objectkey. CALL METHOD get_signtrue EXPORTING is_signtrue_req = ms_signtrue_req IMPORTING es_return = es_return. ENDIF. IF es_return-type NA 'EAX'. "采用HTTP Client访问OBS call_obs_httpclient( EXPORTING iv_objectkey = lv_objectkey IMPORTING es_return = es_return es_result = es_result ). IF es_result-code = 204. " "附件删除成功,ObjectKey为&1. MESSAGE s007 WITH iv_objectkey INTO lv_message. es_return = zcl_message=>convert_syst_to_bapiret2( ). ELSE. "附件删除失败,错误原因:&1&2 MESSAGE e008 WITH es_result-code es_result-reason INTO lv_message. es_return = zcl_message=>convert_syst_to_bapiret2( ). ENDIF. ENDIF. ENDMETHOD.
测试程序ZOBS_DEMO_ATTCH_DELETE
*&---------------------------------------------------------------------**& Report ZOBS_DEMO_ATTCH_DELETE*&---------------------------------------------------------------------**&*&---------------------------------------------------------------------*REPORT zobs_demo_attch_delete.SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME TITLE TEXT-s01. PARAMETERS: p_bucket TYPE zzbucket_obs . PARAMETERS: p_objkey TYPE zzobjectkey_obs .SELECTION-SCREEN END OF BLOCK b1.START-OF-SELECTION. "上载文件 PERFORM frm_delete_attachment .*&---------------------------------------------------------------------**& Form frm_delete_attachment*&---------------------------------------------------------------------**& text*&---------------------------------------------------------------------**& --> p1 text*& delete_attachment EXPORTING iv_bucketname = p_bucket iv_objectkey = lv_objectkey IMPORTING es_return = ls_return es_result = DATA(ls_result). IF ls_return IS NOT INITIAL.. MESSAGE ID ls_return-id TYPE ls_return-type NUMBER ls_return-number WITH ls_return-message_v1 ls_return-message_v2 ls_return-message_v3 ls_return-message_v4. ENDIF.ENDFORM.
3.2.4 文件内容下载 DOWNLOAD_FILE
此功能可以使用[附件下载](#3.2.2 附件下载 DOWNLOAD_ATTACHMENT )功能实现,只需要将FTYPE参数赋值为TEXT即可
- 直接下载Text文件内容,请求方法使用GET;
- 调用计算签名方法GET_SIGNTRUE*(采用URL中携带签名的方式计算签名)*;
- 创建HTTP客户端,HOST需要包含Object Key和授权信息;
- 设置HTTP头域(Header),不包含授权信息;
- 发送请求,根据状态码=200判断文件是否下载成功。
- 将文件内容转换成String的内表*(根据传入的参数,决定Windows/linux的换行符)*
- 源码实现
METHOD download_file. DATA: lv_objectkey TYPE string. "设置桶信息 set_bucket( iv_bucketname = iv_bucketname iv_objectkey = iv_objectkey ). "设置OBS操作 set_action( c_action_download ). "格式化对象Key CLEAR: lv_objectkey. lv_objectkey = iv_objectkey. CALL METHOD adjust_objectkey IMPORTING es_return = es_return CHANGING cv_objectkey = lv_objectkey. IF es_return-type CA 'EAX'. "记录日志 logging_obslog( is_return = es_return is_result = es_result ). RETURN. ENDIF. DO 50 TIMES. "获取OBS的签名 CLEAR:ms_signtrue_req , es_return , es_result . ms_signtrue_req-bucketname = iv_bucketname. ms_signtrue_req-httpmethod = mv_http_method. ms_signtrue_req-authortype = c_authortype_url. ms_signtrue_req-contenttype = c_contenttype_bin . ms_signtrue_req-contentmd5 = ''. ms_signtrue_req-canonicalizeheaders = ''. ms_signtrue_req-canonicalizeresource = '/' && iv_bucketname && lv_objectkey. CALL METHOD get_signtrue EXPORTING is_signtrue_req = ms_signtrue_req IMPORTING es_return = es_return. IF es_return-type CA 'EAX'. "记录日志 logging_obslog( is_return = es_return is_result = es_result ). RETURN. ENDIF. "采用HTTP Client访问OBS call_obs_httpclient( EXPORTING iv_objectkey = lv_objectkey IMPORTING es_return = es_return es_result = es_result ). IF es_result-code = 200. es_return-type = 'S'. IF iv_newline IS NOT INITIAL. "LF换行 SPLIT es_result-bizdata AT cl_abap_char_utilities=>newline INTO TABLE es_result-data[]. ELSE. "CR_LF换行 SPLIT es_result-bizdata AT cl_abap_char_utilities=>cr_lf INTO TABLE es_result-data[]. ENDIF. EXIT. ELSEIF es_result-code = 404." Connection Refused 网络不通 es_return-type = 'E'. EXIT. ELSE. CLEAR: es_return. es_return-type = 'E'. WAIT UP TO '0.1' SECONDS. ENDIF. ENDDO. "记录日志 logging_obslog( is_return = es_return is_result = es_result ). "日志自动清理 clear_obslog( ). ENDMETHOD.
测试程序 ZOBS_DEMO_TEXTFILE_DOWNLOAD
*&---------------------------------------------------------------------**& Report ZOBS_ATTCH_DOWNLOAD_TEST*&---------------------------------------------------------------------**&*&---------------------------------------------------------------------*REPORT zobs_demo_textfile_download.SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME TITLE TEXT-s01. PARAMETERS: p_bucket TYPE zzbucket_obs. PARAMETERS: p_objkey TYPE zzobjectkey_obs .SELECTION-SCREEN END OF BLOCK b1.START-OF-SELECTION. "下载文件 PERFORM frm_download_file.*&---------------------------------------------------------------------**& Form frm_download_file*&---------------------------------------------------------------------**& text*&---------------------------------------------------------------------**& --> p1 text*& download_file EXPORTING iv_bucketname = p_bucket* iv_httpmethod = 'GET' iv_objectkey = lv_objectkey* iv_expires = 3600* iv_newline = 'X' IMPORTING es_return = ls_return es_result = DATA(ls_result). IF ls_return IS NOT INITIAL.. MESSAGE ID ls_return-id TYPE ls_return-type NUMBER ls_return-number WITH ls_return-message_v1 ls_return-message_v2 ls_return-message_v3 ls_return-message_v4. ENDIF. IF ls_return-type = 'S'. cl_demo_output=>display( data = ls_result-data ). ENDIF.ENDFORM.
3.2.5 显示附件 DISPLAY_ATTACHMENT
*此功能可以使用[附件下载](#3.2.2 附件下载 DOWNLOAD_ATTACHMENT )功能实现,将文件下载的二进制用HTML、OLE控件显示出来
调用计算签名方法GET_SIGNTRUE*(采用Header中携带签名的方式计算签名)*;
创建HTTP客户端,HOST需要包含Object Key和授权信息;
设置HTTP头域(Header),包含授权信息;
发送请求,根据状态码=200判断文件是否下载成功。
将文件内容转换成String的内表 (根据传入的参数,决定Windows/linux的换行符)
源码实现
METHOD display_attachment. DATA: lv_type TYPE text20, lv_subtype TYPE text20, lv_filename TYPE string. "下载附件 CALL METHOD download_object EXPORTING iv_bucketname = iv_bucketname iv_objectkey = iv_objectkey iv_filetype = 'BIN' iv_direct_down = '' IMPORTING es_return = es_return es_result = DATA(ls_result). IF es_return-type CA 'EAX'. RETURN. ENDIF. "显示附件 DATA(lv_apptype) = get_application_type( EXPORTING iv_dockey = iv_objectkey IMPORTING ex_type = lv_type ex_subtype = lv_subtype ). IF lv_type = 'text'. CALL METHOD zcl_obs=>text_convert CHANGING cv_data = ls_result-rawbizdata. ENDIF. CASE lv_apptype. WHEN app_type_html. IF iv_dialog IS INITIAL. CALL FUNCTION 'ZFM_OBS_DISPLAY_HTML' EXPORTING iv_data = ls_result-rawbizdata iv_type = lv_type iv_subtype = lv_subtype iv_filename = iv_objectkey. ELSE. CALL METHOD html_show EXPORTING iv_data = ls_result-rawbizdata iv_type = lv_type iv_subtype = lv_subtype iv_filename = iv_objectkey. ENDIF. WHEN app_type_ole. IF iv_dialog IS INITIAL. CALL FUNCTION 'ZFM_OBS_DISPLAY_OLE' EXPORTING iv_data = ls_result-rawbizdata iv_type = lv_type iv_subtype = lv_subtype iv_filename = iv_objectkey. ELSE. CALL METHOD ole_show EXPORTING iv_data = ls_result-rawbizdata iv_type = lv_type iv_subtype = lv_subtype iv_filename = iv_objectkey. ENDIF. WHEN OTHERS. MESSAGE i000 WITH '不支持打开此类型的文件。'(001). RETURN. ENDCASE. ENDMETHOD.
测试程序
*&---------------------------------------------------------------------**& Report ZOBS_ATTCH_DOWNLOAD_TEST*&---------------------------------------------------------------------**&*&---------------------------------------------------------------------*REPORT zobs_demo_attch_display.SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME TITLE TEXT-s01. PARAMETERS: p_bucket TYPE zzbucket_obs DEFAULT 'mrm-attachment'. PARAMETERS: p_objkey TYPE zzobjectkey_obs .SELECTION-SCREEN END OF BLOCK b1.START-OF-SELECTION. "显示对象 PERFORM frm_display_attachment.*&---------------------------------------------------------------------**& Form frm_display_attachment*&---------------------------------------------------------------------**& text*&---------------------------------------------------------------------**& --> p1 text*& display_attachment EXPORTING iv_bucketname = p_bucket iv_objectkey = lv_objectkey iv_dialog = abap_false "选择屏幕调用,参数设为空 IMPORTING es_return = ls_return. IF ls_return IS NOT INITIAL.. MESSAGE ID ls_return-id TYPE ls_return-type NUMBER ls_return-number WITH ls_return-message_v1 ls_return-message_v2 ls_return-message_v3 ls_return-message_v4. ENDIF.ENDFORM.
3.2.6 复制附件
3.2.6.1 两步法:先下载再上载 COPY_ATTACHMENT_NEW
由于华为云OBS API调用有问题,采用临时方案,先将文件下载,再将文件上载成另外一个对象**
先调用下载方法,获取文件的二进制;
将获取的二进制上载到新的对象Key;
源码实现
**/*----------------------------------------------------------------------** 复制对象的结果不能仅根据HTTP返回头域中的status_code来判断请求是否成功,* 头域中status_code返回200时表示服务端已经收到请求,且开始处理复制对象请求。* 复制是否成功会在响应消息的body中,只有body体中有ETag标签才表示成功,* 否则表示复制失败。*----------------------------------------------------------------------**/METHOD copy_attachment_new. DATA: lv_data TYPE xstring, lv_filelength TYPE i, lv_message TYPE bapi_msg. "下载源文件 "下载附件 CALL METHOD download_object EXPORTING iv_bucketname = iv_sourcebucket iv_objectkey = iv_sourceobject iv_filetype = 'BIN' iv_direct_down = '' IMPORTING es_return = es_return es_result = es_result. IF es_return-type CA 'EAX'. RETURN. ENDIF. lv_data = es_result-rawbizdata. "上载目标文件 CALL METHOD upload_object EXPORTING iv_bucketname = iv_destbucket iv_objectkey = iv_destobject iv_data = lv_data IMPORTING es_return = es_return es_result = es_result. IF es_result-code = 200. "附件上载成功,ObjectKey为&1. MESSAGE s013 WITH iv_destobject INTO lv_message. es_return = zcl_message=>convert_syst_to_bapiret2( ). ELSE. "附件上载失败,错误原因:&1&2 MESSAGE e014 WITH es_result-code es_result-reason INTO lv_message. es_return = zcl_message=>convert_syst_to_bapiret2( ). ENDIF. "设置OBS操作 set_action( c_action_copy ). "记录日志 logging_obslog( is_return = es_return is_result = es_result ). "日志自动清理 clear_obslog( ).ENDMETHOD.
3.2.6.2 一步法:直接调用OBS复制功能COPY_ATTACHMENT
复制对象使用PUT方法;
调用计算签名方法;
计算签名的参数CanonicalizedHeaders中需要包含参数x-obs-copy-source。
参数x-obs-copy-source
用来指定复制对象操作的源桶名以及源对象名。当源对象存在多个版本时,通过versionId参数指定版本源对象。
类型:String
约束:中文字符,需要进行URLEncode
示例:x-obs-copy-source: /source_bucket/sourceObject
创建HTTP客户端,HOST需要包含Object Key;
设置HTTP头域(Header);
发送请求,并提取返回的结果。
3.2.6.3 测试程序
*&---------------------------------------------------------------------**& Report ZOBS_ATTCH_DOWNLOAD_TEST*&---------------------------------------------------------------------**&*&---------------------------------------------------------------------*REPORT zobs_demo_attch_copy.SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME TITLE TEXT-s01. PARAMETERS: p_srcbkt TYPE zzbucket_obs DEFAULT 'sap-mm-attachment '. PARAMETERS: p_srcobj TYPE zzobjectkey_obs. PARAMETERS: p_dstbkt TYPE zzbucket_obs DEFAULT 'sap-mm-attachment '. PARAMETERS: p_dstobj TYPE zzobjectkey_obs. SELECTION-SCREEN BEGIN OF BLOCK b2 WITH FRAME TITLE TEXT-s02. PARAMETERS: p_copy1 RADIOBUTTON GROUP gp1. PARAMETERS: p_copy2 RADIOBUTTON GROUP gp1 DEFAULT 'X'. SELECTION-SCREEN END OF BLOCK b2.SELECTION-SCREEN END OF BLOCK b1.START-OF-SELECTION. "复制对象 PERFORM frm_copy_attachment.*&---------------------------------------------------------------------**& Form frm_copy_attachment*&---------------------------------------------------------------------**& text*&---------------------------------------------------------------------**& --> p1 text*& copy_attachment EXPORTING iv_sourcebucket = p_srcbkt iv_sourceobject = lv_sourceobject iv_destbucket = p_dstbkt iv_destobject = lv_destobject IMPORTING es_return = ls_return es_result = ls_result. ELSE. "先下载再上载 CALL METHOD lo_obs->copy_attachment_new EXPORTING iv_sourcebucket = p_srcbkt iv_sourceobject = lv_sourceobject iv_destbucket = p_dstbkt iv_destobject = lv_destobject IMPORTING es_return = ls_return es_result = ls_result. ENDIF. IF ls_return IS NOT INITIAL.. MESSAGE ID ls_return-id TYPE 'S' NUMBER ls_return-number WITH ls_return-message_v1 ls_return-message_v2 ls_return-message_v3 ls_return-message_v4 DISPLAY LIKE ls_return-type. ENDIF.ENDFORM.
3.3 桶的基本操作
3.3.1 列举桶内对象
对桶拥有读权限的用户可以执行获取桶内对象列表的操作。
如果用户在请求的URI里只指定了桶名,即GET /BucketName,则返回信息中会包含桶内部分或所有对象的描述信息(一次最多返回1000个对象信息);如果用户还指定了prefix、marker、max-keys、delimiter参数中的一个或多个,则返回的对象列表将按照如表1所示规定的语义返回指定的对象。
由于每次最多返回1000个文件,需要根据上次返回的NextMarker参数循环调用才能获取完整的对象列表。
列举桶内对象的方法LIST_BUCKET_OBJECTS
3.3.1.1 列举桶内对象采用GET方法
3.3.1.2 计算签名
访问桶内的对象清单是对桶的操作,所以请求访问的资源参数CanonicalizedResource指定为/BucketName/
"获取OBS的签名CLEAR:ms_signtrue_req , es_return .ms_signtrue_req-bucketname = iv_bucketname.ms_signtrue_req-httpmethod = mv_http_method.ms_signtrue_req-authortype = c_authortype_header.ms_signtrue_req-contenttype = '' .ms_signtrue_req-contentmd5 = ''.ms_signtrue_req-canonicalizeheaders = ''.ms_signtrue_req-canonicalizeresource = '/' && iv_bucketname && '/' .
3.3.1.3 设置HTTP头域
Date: dateAuthorization: authorization
经过测试发现,Date一定要在Authorization,否则会报错,应该是OBS后端的设置导致的。
3.3.1.4 设置url
经测试发现,前缀prefix和起始标识marker只有拼接在URL中才生效。
prefix:列举以指定的字符串prefix开头的对象
marker:列举桶内对象列表时,指定一个标识符,从该标识符以后按字典顺序返回对象列表。等于上一次返回的NextMarker。
NextMarker:如果本次没有返回全部结果,响应请求中将包含此字段,用于标明本次请求列举到的最后一个对象。后续请求可以指定Marker等于该值来列举剩余的对象。
CLEAR: me->mt_parameters. me->mt_parameters = VALUE #( ( name = 'prefix' value = iv_prefix ) ( name = '&marker' value = lv_nextmarker ) ).
3.3.1.5 调用HTTP返回对象清单
3.3.1.6 解析返回的XML文件
METHOD parse_list_bucket_result. DATA: lr_ixml TYPE REF TO if_ixml, lr_streamfactory TYPE REF TO if_ixml_stream_factory, lr_parser TYPE REF TO if_ixml_parser, lr_istram TYPE REF TO if_ixml_istream, lr_document TYPE REF TO if_ixml_document. "create main ixml factory lr_ixml = cl_ixml=>create( ). "create a stream factory lr_streamfactory = lr_ixml->create_stream_factory( ). lr_istram = lr_streamfactory->create_istream_string( xmldata ). "create a document lr_document = lr_ixml->create_document( ). "Creates a parser lr_parser = lr_ixml->create_parser( document = lr_document istream = lr_istram stream_factory = lr_streamfactory ). IF lr_parser->parse( ) NE 0."Full parsing of input stream into a DOM. IF lr_parser->num_errors( ) 0." RETURN. ENDIF. ENDIF. IF lr_parser->is_dom_generating( ) = abap_true. "DOM是否生成 "解析DOM is_truncated = parse_dom_document( EXPORTING document = lr_document IMPORTING nextmarker = nextmarker ). ENDIF. ENDMETHOD.
METHOD parse_dom_document. DATA: lr_node TYPE REF TO if_ixml_node, lr_iterator TYPE REF TO if_ixml_node_iterator, lr_node_map TYPE REF TO if_ixml_named_node_map, lv_name TYPE string, lv_value TYPE string. lr_node " />mt_http_header = VALUE #( BASE me->mt_http_header ( name = 'Date' vaule = ms_signtrue-date ) ). " 日期 me->mt_http_header = VALUE #( BASE me->mt_http_header ( name = 'Authorization' vaule = ms_signtrue-authorization ) ). "CLEAR: me->mt_parameters. me->mt_parameters = VALUE #( ( name = 'prefix' value = iv_prefix ) ( name = '&marker' value = lv_nextmarker ) ). "采用HTTP Client访问OBS me->get_obs_object_list( EXPORTING iv_bucketname = iv_bucketname it_parameters = me->mt_parameters IMPORTING es_return = ls_return es_result = ls_result ). IF ls_result-code = 200. "获取桶&1对象列表成功. MESSAGE s016 WITH iv_bucketname INTO lv_message. es_return = zcl_message=>convert_syst_to_bapiret2( ). "解析XML,获取文件列表 IF me->parse_list_bucket_result( EXPORTING xmldata = ls_result-bizdata IMPORTING nextmarker = lv_nextmarker ) me->c_true. "如果解析出的XML文件中的IsTruncated=false时,标识已经返回全部结果,则终止循环 EXIT. ENDIF. ELSE. "获取桶&1对象列表失败,错误原因:&1&2 MESSAGE e017 WITH ls_result-code ls_result-reason INTO lv_message. es_return = zcl_message=>convert_syst_to_bapiret2( ). EXIT. ENDIF. ENDDO. rt_object_list = me->mt_object_list. ENDMETHOD.
3.3.1.8 测试程序
*&---------------------------------------------------------------------**& Report ZOBS_DEMO_LIST_BUCKET_OBJECTS*&---------------------------------------------------------------------**&*&---------------------------------------------------------------------*REPORT zobs_demo_list_bucket_objects NO STANDARD PAGE HEADING MESSAGE-ID zobs.SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME TITLE TEXT-s01. PARAMETERS: p_bucket TYPE zzbucket_obs DEFAULT 'sap-mm-attachment'. PARAMETERS: p_prefix TYPE zzprefix_obs DEFAULT ''.SELECTION-SCREEN END OF BLOCK b1.START-OF-SELECTION. PERFORM frm_get_object_list.*&---------------------------------------------------------------------**& Form frm_GET_OBJECT_LIST*&---------------------------------------------------------------------**& text*&---------------------------------------------------------------------**& --> p1 text*& factory( ). DATA(lt_object_list) = lr_obs->list_bucket_objects( EXPORTING iv_bucketname = p_bucket " OBS桶 iv_prefix = p_prefix IMPORTING es_return = ls_return " 返回参数 ). IF lt_object_list IS NOT INITIAL. TRY. cl_salv_table=>factory( IMPORTING r_salv_table = gr_table CHANGING t_table = lt_object_list ). gr_salv = zcl_salv=>factory( ). gr_salv->display( EXPORTING ir_table = gr_table CHANGING ct_data = lt_object_list ). CATCH cx_salv_msg. " ALV: General Error Class with Message ENDTRY. ENDIF.ENDFORM.