1.楼主所在公司有个业务需求是需要访问地方医保局发布的一个DRGs查询SOAP接口,POST入参,同时获取接口返回数据。
2.楼主搜了一些资料之后,自己研究用oracle里的UTL_HTTP包成功的访问到了接口,并获取了接口返回。
3.教程都整理成word,放在附件了。下面简单说下步骤。
4.使用utl_http包之前需要做以下数据库配置配置脚本如下
1. 创建ACL,如果原来数据库就已经创建了ACL文件,可以直接略过这步,如果没有则脚本如下。
begin
dbms_network_acl_admin.create_acl(
acl => 'utl_http.xml', -- 自定义文件名称
description => 'HTTP Access', -- 自定义描述
principal => 'xx', -- 要配置的数据库账号,大小写敏感
is_grant => TRUE, -- 授权还是取消授权
privilege => 'connect', -- 连接权限
start_date => null, -- 起始日期,不维护则为不限制,下同
end_date => null -- 结束日期
);
end;
2. 然后再在这个acl中给这个用户增加一个resolve的权限。
begin
dbms_network_acl_admin.add_privilege( acl => 'utl_http.xml', -- 刚才创建的acl名称
principal => 'xx', -- 与上同
is_grant => TRUE, -- 与上同
privilege => 'resolve', -- resolve权限
start_date => null,
end_date => null);
end;
3. 最后在acl中添加要访问的目标网址(ip和域名均可),可以指定端口范围。
begin
dbms_network_acl_admin.assign_acl( -- 该段命令意思是允许访问acl名为utl_http.xml下授权的用户,使用oracle网络访问包,所允许访问的目的主机,及其端口范围。
acl => 'utl_http.xml',
host => '192.168.xxx.xxx', -- ip地址或者域名,填写https://localhost:9000/hello与https://localhost:9000/是会报host无效的,且建议使用ip地址或者使用域名,若用localhost,当oracle不是安装在本机上的情况下,会出现问题
lower_port => '10344', -- 允许访问的起始端口号
upper_port => '10344' -- 允许访问的截止端口号
);
commit;
end;
5.创建解析接口响应xml格式文件的管道函数,附件有响应报文用例。
1. 自定义表类型
因为需要以表的方式来输出解析xml响应文件,所以函数需要使用管道函数,不清楚什么是管道函数的可以百度下,所以创建管道函数之前需要创建表类型drg_table作为管道函数的返回值类型。
创建函数脚本如下
create or replace type drg_table is table of drg_data_type
2. 自定义表类型字段
定义drg_table表类型中所含的字段,这里根据要解析的xml响应文件,也就是接口里的返回报文里面各个xml节点值对应得定义成表类型的字段。
创建drg_table表类型里所包含的字段脚本如下
create or replace type drg_data_type as object
(grp_time date,--分组时间
grp_ver varchar2(100),--分组版本
adrg_name varchar2(400),--ADRG 名称
grp_name varchar2(400),--分组名称
mdc_codg varchar2(100),--MDC 编码
grp_spec_ver varchar2(100),--分组规范版本号
ccmcc varchar2(400),--合并症并发症
grp_rslt varchar2(12),--分组成功标志
fixmedins_code varchar2(100),--定点医药机构编号
mdc_name varchar2(500),--MDC 名称
psn_no varchar2(50),--人员编号
place_grp_pay_std number(16,2),--地方分组支付标准
err_info varchar2(1000),--错误信息
place_drg_codg varchar2(100),--地方 DRG 编码
abbr varchar2(500),--简称
drg_name varchar2(500),--DRG 名称
place_grp_spec_ver varchar2(12),--地方分组规范版本号
adrg_codg varchar2(100),--ADRG 编码
setl_id varchar2(100),--结算 ID
drg_admdvs varchar2(12),--DRG 医保区划
setl_list_sn varchar2(200),--结算清单流水号
place_drg_name varchar2(400),--地方 DRG 名称
setl_ym varchar2(12),--结算年月
place_grp_ver varchar2(100),--地方分组版本
vali_flag varchar2(40),--有效标志
drg_code varchar2(100),--DRG 编码
grp_type varchar2(12)--分组类别
)
3. 创建管道函数
创建函数,解析xml响应文件脚本如下
/****************创建函数代码****************/
function of_get_drgval (as_clob_string in clob,as_clob_mark in varchar2) return drg_table pipelined—利用管道函数来存储xml解析出来的节点值
as
vs_drg_table drg_data_type;
vs_bmark VARCHAR2(1000);
vs_emark VARCHAR2(1000);
vi_bpos integer;
vi_epos integer;
vs_clob_subr clob;--响应文件入参,clob类型支持超过32767长度的大文本
vs_grp_time_str varchar2(50);
vs_grp_time date;--分组时间
vs_grp_ver varchar2(50);--分组版本
vs_adrg_name varchar2(100);--ADRG 名称
vs_grp_name varchar2(200);--分组名称
vs_mdc_codg varchar2(40);--MDC 编码
vs_grp_spec_ver varchar2(50);--分组规范版本号
vs_ccmcc varchar2(200);--合并症并发症
vs_grp_rslt varchar2(6);--分组成功标志
vs_fixmedins_code varchar2(50);--定点医药机构编号
vs_mdc_name varchar2(100);--MDC 名称
vs_psn_no varchar2(30);--人员编号
vs_place_grp_pay_std number(16,2);--地方分组支付标准
vs_err_info varchar2(500);--错误信息
vs_place_drg_codg varchar2(50);--地方 DRG 编码
vs_abbr varchar2(50);--简称
vs_drg_name varchar2(100);--DRG 名称
vs_place_grp_spec_ver varchar2(6);--地方分组规范版本号
vs_adrg_codg varchar2(50);--ADRG 编码
vs_setl_id varchar2(50);--结算 ID
vs_drg_admdvs varchar2(6);--DRG 医保区划
vs_setl_list_sn varchar2(50);--结算清单流水号
vs_place_drg_name varchar2(100);--地方 DRG 名称
vs_setl_ym varchar2(6);--结算年月
vs_place_grp_ver varchar2(50);--地方分组版本
vs_vali_flag varchar2(20);--有效标志
vs_drg_code varchar2(50);--DRG 编码
vs_grp_type varchar2(6);--分组类别
begin
vs_bmark := '<' || lower(as_clob_mark) || '>';--截取段开始标记前缀,xml
格式文本一般每个节点的标记是以< 和/>括起来的内容。
vs_emark := '</' || lower(as_clob_mark) || '>'; --截取段结束标记前缀
vi_bpos:=dbms_lob.instr(dbms_lob.substr(as_clob_string,5000),vs_bmark);-- 截取开始节点标记位置
vi_epos:=dbms_lob.instr(dbms_lob.substr(as_clob_string,5000),vs_emark);--截取结束节点标记位置
/**因为xml文件长度超过32767,所以需要一段一段截取出来,这里以5000长度为一次截取长度循环截取出来,然后循环提取节点值**/
while dbms_lob.instr(dbms_lob.substr(as_clob_string,5000,vi_bpos),vs_emark)>0 loop
vi_epos:=dbms_lob.instr(dbms_lob.substr(as_clob_string,5000,vi_bpos),vs_emark);
vs_clob_subr:=dbms_lob.substr(dbms_lob.substr(as_clob_string,5000,vi_bpos),vi_epos+LENGTH(vs_bmark));
vi_bpos:=vi_bpos+DBMS_LOB.GETLENGTH(vs_clob_subr);
/** xmltable 函数逐个解析xml里的节点出来,注意路径,这里举例的路径是//data**/
select x.* into vs_grp_time_str, vs_grp_ver, vs_adrg_name, vs_grp_name, vs_mdc_codg, vs_grp_spec_ver, vs_ccmcc, vs_grp_rslt, vs_fixmedins_code, vs_mdc_name, vs_psn_no, vs_place_grp_pay_std, vs_err_info, vs_place_drg_codg, vs_abbr, vs_drg_name, vs_place_grp_spec_ver, vs_adrg_codg, vs_setl_id, vs_drg_admdvs, vs_setl_list_sn, vs_place_drg_name, vs_setl_ym, vs_place_grp_ver, vs_vali_flag, vs_drg_code, vs_grp_type
from xmltable('//data' passing xmltype(vs_clob_subr)
columns
grp_time varchar2(50) path '//grp_time',
grp_ver varchar2(50) path '//grp_ver',
adrg_name varchar2(100) path '//adrg_name',
grp_name varchar2(200) path '//grp_name',
mdc_codg varchar2(40) path '//mdc_codg',
grp_spec_ver varchar2(50) path '//grp_spec_ver',
ccmcc varchar2(200) path '//ccmcc',
grp_rslt varchar2(6) path '//grp_rslt',
fixmedins_code varchar2(50) path '//fixmedins_code',
mdc_name varchar2(100) path '//mdc_name',
psn_no varchar2(30) path '//psn_no',
place_grp_pay_std number(16,2) path '//place_grp_pay_std',
err_info varchar2(500) path '//err_info',
place_drg_codg varchar2(50) path '//place_drg_codg',
abbr varchar2(50) path '//abbr',
drg_name varchar2(100) path '//drg_name',
place_grp_spec_ver varchar2(6) path '//place_grp_spec_ver',
adrg_codg varchar2(50) path '//adrg_codg',
setl_id varchar2(50) path '//setl_id',
drg_admdvs varchar2(6) path '//drg_admdvs',
setl_list_sn varchar2(50) path '//setl_list_sn',
place_drg_name varchar2(100) path '//place_drg_name',
setl_ym varchar2(6) path '//setl_ym',
place_grp_ver varchar2(50) path '//place_grp_ver',
vali_flag varchar2(20) path '//vali_flag',
drg_code varchar2(50) path '//drg_code',
grp_type varchar2(6) path '//grp_type') x;
vs_grp_time:=vs_grp_time_str/(1000*60*60*24)+TO_DATE('1970-01-01 08:00:00','yyyy-MM-dd hh24:mi:ss');
vs_drg_table :=drg_data_type(vs_grp_time, vs_grp_ver, vs_adrg_name, vs_grp_name, vs_mdc_codg, vs_grp_spec_ver, vs_ccmcc, vs_grp_rslt, vs_fixmedins_code, vs_mdc_name, vs_psn_no, vs_place_grp_pay_std, vs_err_info, vs_place_drg_codg, vs_abbr, vs_drg_name, vs_place_grp_spec_ver, vs_adrg_codg, vs_setl_id, vs_drg_admdvs, vs_setl_list_sn, vs_place_drg_name, vs_setl_ym, vs_place_grp_ver, vs_vali_flag, vs_drg_code, vs_grp_type);
pipe row(vs_drg_table);--pipe row将提取出来的节点值全部填充到表类型vs_drg_table里的字段中,然后在存储过程中就可以用select a.* from table(of_get_drgval(vs_clob, 'data')) a语法像访问一般的数据库表一样访问到函数返回值。
end loop;
return;
end of_get_drgval;
6.创建访问soap/http接口,并解析xml的存储过程
/****************创建存储过程代码****************/
/*存储过程入参什么的可以自己定义,我这里是有4个入参,一个出参作为返回用*/
create or replace procedure sp_yourname(as_setl_ym in VARCHAR2,
as_page_num in number,
as_page_size in number,
as_setl_id in VARCHAR2,
aCur_temp out sys_refcursor) is
HTTP_REQ UTL_HTTP.REQ;-- UTL_HTTP包请求类型参数
HTTP_RESP UTL_HTTP.RESP; -- UTL_HTTP包响应类型参数
REQUEST_ENV CLOB;--通过存储过程的入参拼接成,请求入参
VS_ERROR_ERRM VARCHAR2(200);--自定义异常参数
VS_ERROR_CODE VARCHAR2(50);--自定义异常代码
VS_TEXT VARCHAR2(32767);--接收响应文本
VS_CLOB CLOB;
begin
/***这段三个if条件是楼主自己根据对方接口文档加的一个逻辑前置判断,可以无理会****/
if as_setl_ym is null then RAISE_APPLICATION_ERROR(-20001,'请输入结算年月'); end if;
if as_page_num is null or as_page_num=0 then RAISE_APPLICATION_ERROR(-20002,'请输入分页页码'); end if;
if as_page_size is null or as_page_size=0 then RAISE_APPLICATION_ERROR(-20003,'请输入分页数量'); end if;
/***这段初始化,是因为使用了CLOB 字段作为入参,CLOB 类型参数需要初始化****/
request_env := empty_clob();--初始化请求入参
vs_clob := empty_clob();--初始化请求入参
DBMS_LOB.CREATETEMPORARY(request_env, TRUE);--初始化请求入参
dbms_lob.createtemporary(vs_clob, TRUE);--初始化请求入参
/***这段根据存储过程4个入参拼接成访问接口的入参,用DBMS_LOB.APPEND ()函数添加内容到CLOB 类型参数request_env 中,作为访问接口的入参,CLOB 类型参数支持超过32767长度的数据,而varchar2不支持。****/
DBMS_LOB.APPEND(request_env,'<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/">
<soapenv:Header/>
<soapenv:Body>
<tem:ClientRequest>
<!--Optional:-->
<tem:xmlParam><![CDATA[<paramMethod>3xx5</paramMethod> <paramInputBody>{"infno":"3xx5","msgid": 3xxxxxx2131'||to_char(sysdate,'yyyymmddhh24miss')||'","mdtrtarea_admvs": "6xxx99", "insuplc_admdvs": "", "recer_admdvs": "6xxx02", "dev_no": "", "dev_safe_info": "", "signtype": "SM3", "cainfo": "", "infver": "V1.0", "opter_type": "1", "opter": "999", "opter_name": "xx员", "inf_time": "'||sysdate||'", "fixmedins_code": "Hxxx502xxxxx", "fixmedins_name": "xxxxxxx", "sign_no": "3453422", "recer_sys_code": "1", "input": { "data": { "setl_ym": "'||as_setl_ym||'", "page_num": "'||as_page_num||'", "page_size": "'||as_page_size||'", "setl_id": "'||as_setl_id||'" } } }</paramInputBody>]]></tem:xmlParam>
</tem:ClientRequest>
</soapenv:Body>
</soapenv:Envelope>');
begin
http_req := UTL_HTTP.begin_request('http://IP:端口/jiekouhouzui?wsdl','POST','HTTP/1.1');--本文是post接口,这段需要配置所访问接口的IP/端口/地址,发送请求
Utl_Http.Set_Header(http_req, 'Content-Type', 'text/xml;charset=utf-8');--设置请求头,字符集,请求格式,也可以支持json,text/xml改成application/json即可
Utl_Http.Set_Body_Charset(http_req, 'utf-8');--设置请求体字符集
Utl_Http.Set_Persistent_Conn_Support(http_req, TRUE);--这段是保持接口连接活动状态
Utl_Http.Set_Header(http_req,'Content-Length',DBMS_LOB.GETLENGTH(request_env));--设置请求报文长度,不能少
UTL_HTTP.SET_HEADER(http_req,'Transfer-Encoding', 'chunked');--设置请求头模式
Utl_Http.Write_raw(http_req, UTL_RAW.cast_to_raw(request_env));--将请求参数重写成raw,防止请求参数里一些中文乱码或者特殊符号失效问题
http_resp := utl_http.get_response(http_req);--获取接口响应报文
if http_resp.status_code = 200 then--如果响应代码是200,表示接口正常
begin
loop
utl_http.read_line(http_resp,vs_text);--读取响应报文至vs_text
dbms_lob.writeappend(vs_clob,DBMS_LOB.GETLENGTH(vs_text),vs_text);--将vs_text逐行添加到大字段vs_clob中
end loop;
exception
when utl_http.end_of_body then
utl_http.end_response(http_resp);--结束请求
end;
else
RAISE_APPLICATION_ERROR(-20005,'请求错误'); --异常处理
/**也可以用下面一段将异常信息插到数据库表中,方便查询和追踪历史异常信息记录**/
/**vs_ERROR_ERRM:=SQLERRM||dbms_utility.format_error_backtrace;
vs_ERROR_codE:=SQLCODE||'--'||http_resp.status_code;
INSERT INTO POST_DATE_ERROR (ERROR_TYPE,ERROR_SOURCE, ERROR_CODE, ERROR_ERRM, ERROR_PK_SOURCE, FLOGCBY, FLOGCDATE)
VALUES ('PG','sp_yourname', VS_ERROR_CODE,VS_ERROR_ERRM,'', '', SYSDATE); **/
utl_http.end_response(http_Resp);
end if;
/** 调用of_get_drgval函数,解析xml响应文件,打开aCur_temp游标以表的方式将xml节点输出。**/
begin
open aCur_temp for
select a.* from table(of_get_drgval(vs_clob, 'data')) a;
EXCEPTION
when others then
RAISE_APPLICATION_ERROR(-20005,'函数 of_get_drgval 错误');
end;
--dbms_output.put_line(dbms_lob.substr(vs_clob,32767));--打印响应报文,文本方式查看响应报文
--dbms_output.put_line(dbms_lob.substr(request_env,32767));--打印请求报文,查看请求报文
dbms_lob.freetemporary(request_env);--释放大字段
dbms_lob.freetemporary(vs_clob);--释放大字段
EXCEPTION
WHEN utl_http.end_of_body THEN
utl_http.end_response(http_Resp);
END;
end sp_yourname;