1. 项目背景
以往我们在开发基于webservice的项目中,我们总习惯于直接使用webservice的一些框架,如Axis,axis2和Xfire等。框架的好处是将webservice所涉及到的soap协议、wsdl以及uddi都封装起来,我们只需要直接调用方法传值并执行请求就可以。但框架也有缺陷,比如axis2要基于java工程,哪怕只是一个简单的功能我们都要搭建一个java web项目去实现它。这样做的后果就是:
- 1. 占用服务器磁盘资源(一个java web项目至少也要几M);
- 2. 占用内存资源。特别是对那些不需要页面的项目来说,java web实在是大材小用了。
就拿我最近开发的短信定时发送项目为例。在这个项目里,我们只需要每天从数据库取数据然后拼成短信,定时调用总公司的短信接口发送短信就可以了。不需要任何图形界面,功能也很单一,如果用java项目去做就太小题大做了。所以我们想到一个巧妙的办法,直接利用webservice的底层知识去实现这个功能。即直接用Linux的shell脚本生成soap消息,然后用soap请求webservice。
2. 实施过程
2.1项目分析
上文提到的短信项目是一个为了实现我们公司办公信息化的小项目。其主要功能就是每天从数据库读取分公司即各支公司保费收入及业务达成情况,然后组合成短信发给各个领导以便于其合理调整公司发展策略。
这个项目有两个特点:
- 1. 功能比较单一;
- 2. 不需要图形界面操作。因此我们完全可以抛开java项目,直接用shell脚本去实现。
2.2实现流程
2.2.1 技术介绍
在介绍实现过程之前我们先介绍下soap协议。Soap协议全称为simple Object Access Protocol,是一个基于http的访问协议,这里的simple Object指代就是soap消息,它本质上是一个xml格式的文件,必须包含以下元素:
- * 必需的 Envelope 元素,可把此 XML 文档标识为一条 SOAP 消息
- * 可选的 Header 元素,包含头部信息
- * 必需的 Body 元素,包含所有的调用和响应信息
- * 可选的 Fault 元素,提供有关在处理此消息所发生错误的信息
同时soap协议还要基于POST请求,因为post请求有body域,可以用来封装soap消息。
Soap请求的方式有很多种,可以在框架中请求(后文中有介绍,直接封装在框架中,用户只要传值就可以)。也可以自己基于格式编写soap消息,直接用http命令请求,比如Linux的curl命令。相对来说后者的效率会更高些。
2.3.2 具体实现
2.3.2.1 soap消息生成工具脚本
由于soap消息是一个有固定格式的xml文件,因此我们可以用工具脚本去生成。其代码如下所示:
- #!/bin/bash
- # filename: create_xml.sh
- # create_MarsCheng_20160922
- # 从外部传入的第一个参数作为xml的文件名
- outfile=$1
- # xml中的缩进位
- tabs=0
- # ++++++++++++++++++++++++++++
- # 组装一个节点,输出到文件
- # 说一说传参数时的这几个区别:假如有下面这个脚本执行的命令
- # /path/to/scriptname opt1 opt2 opt3 opt4
- # $0: 的值是默认是脚本的名字,从$1-$4 开始就是参数的值
- # $# :代表后接的参数『个数』
- # $@ :代表『 "$1" "$2" "$3" "$4" 』之意,每个变量是独立的(用双引号括起来);
- # $* :代表『 "$1c$2c$3c$4" 』,其中 c 为分隔字节,默认为空白键, 所以本例中代表『 "$1 $2 $3 $4" 』之意。
- # 在shell中我们可以也可以使用${}包含变量名,来调用变量
- # ++++++++++++++++++++++++++++
- put(){
- echo '<'${*}'>' >> /tmp/sms/request/$outfile
- }
-
- # 这里也是输出一个xml的节点,只是比上面的节点有更多的设置
- # ${@:3} 的意思:它的值就是由第二个参数开始到最后一个参数,为什么要这样?有时可能你的第二个参数中有空格,shell接受参数是以空格计算的
- put_tag() {
- echo '<'${1}' xsi:type="'${2}'">'${@:3}'</'${1}'>' >> /tmp/sms/request/$outfile
- }
- # 同样是一个输出节点函数,但是添加了CDATA,防止特殊字符造成xml解析失败
- put_tag_cdata() {
- echo '<'${1}' xsi:type="'${2}'"><![CDATA['${@:3}']]></'${1}'>' >> /tmp/sms/request/$outfile
- }
- #由于soap消息的头部一部分一般固定,因此可以直接写一个固定的方法输出
- put_head(){
- echo –e “…”>> /tmp/sms/request/$outfile
- }
- #由于soap消息的尾部一部分一般固定,因此可以直接写一个固定的方法输出
- put_end(){
- echo –e “…”>> /tmp/sms/request/$outfile
- }
- tag_start(){
- str=""
- str=${1}' xsi:type="'${2}'"'
- put $str
- }
-
- tag_start_array(){
- str=""
- str=${1}' q1:arrayType="'${2}'" xsi:type="q1:Array"'
- put $str
- }
-
- tag() {
- str=""
- str=${1}' xsi:type="'${2}'" /'
- put $str
- }
-
- tag_value(){
- if [ "$1" == 0 ]
- then
- put_tag ${2} ${3} $(echo ${@:4})
- elif [ "$1" == 1 ]
- then
- put_tag_cdata ${2} ${3} $(echo ${@:4})
- fi
- }
-
- tag_end(){
- put '/'${1}
- }
2.3.2.2具体执行脚本
具体执行请求的脚本,在这个脚本里会调用soap消息生成工具脚本,会连接数据库读取短信内容,最后用curl实现soap请求。
- #统计日期,即当前日期前一天
- cal_date=$(date -d -1day +%Y-%m-%d)
- #短信是否发送标记,若为1则表示短信已发送,否则为0
- flag=$(sqlplus -S statistic/123@tjfxdb << EOF
- set heading off feedback off pagesize 0 verify off echo off
- select flag from statistic.smsflg where calDate ='$cal_date';
- commit;
- quit;
- EOF)
-
- if [ ! -n "$flag" ];
- then
- #从数据库取出短信内容,存入smsinfo
- smsinfo=$(sqlplus -S statistic/123@tjfxdb << EOF
- set heading off feedback off pagesize 0 verify off echo off
- select smsinfo from statistic.smsInfo where calDate =date'$cal_date' and smscode ='AHYZ' and bvalid = 1 ;
- commit;
- quit;
- EOF)
-
- if [ ! -n "$smsinfo" ]; #若短信内容为空,不发送
- then
- echo $(date "+%Y-%m-%d %H:%M:%S")" The content is not ready.">> /tmp/sms/log/$cal_date'.log'
- else
- #生成soap xml文件名
- file_name='request'$(date "+%Y%m%d%H%M%S")'.xml'
- #调用xml的生成脚本
- source '/tmp/sms/create_xml.sh' $file_name
- put_head
-
- taskID=$(date "+%s")
- declare -i message_size=1
- declare -i labels_size=4
- name=('DEPTCODE' 'CUR_DATE' 'FUN_DESC' 'CONTENT')
- value[0]='123456'
- value[1]=$cal_date'日'
- value[2]='保费达成情况'
- value[3]=$smsinfo
- #短信发送对象
- telephone=(‘12345678')
- message_str2='q2:SmsMessage['$message_size']'
- label_str2='q2:Label['$labels_size']'
- tag_start_array 'messages' $message_str2
- declare -i j=0
- #短信核心主体
- while ((j<message_size))
- do
- tag_start 'SmsMessage' 'q2:SmsMessage'
- tag_value 0 'packetLength' 'xsd:int' '0'
- tag_value 0 'contents' 'xsd:string' '中国人寿'
- tag_start_array 'labels' $label_str2
- declare -i i=0
- while ((i<labels_size))
- do
- tag_start 'Label' 'q2:Label'
- tag_value 0 'packetLength' 'xsd:int' '0'
- tag_value 0 'labelName' 'xsd:string' ${name[i]}
- if [ $i == 3 ]
- then
- tag_value 1 'labelValue' 'xsd:string' ${value[i]}
- else
- tag_value 0 'labelValue' 'xsd:string' ${value[i]}
- fi
- tag_end 'Label'
- let i++
- done
- tag_end 'labels'
- tag_value 0 'orgCode' 'xsd:string' '123456'
- tag_value 0 'receiver' 'xsd:string' ${telephone[j]}
- tag_end 'SmsMessage'
- let j++
- done
- tag_end 'messages'
- tag_value 0 'taskId' 'xsd:string' $taskID
- put_end
- #请求webservice服务发送短信
- curl -H "Content-Type: text/xml;charset=UTF-8" -H "SOAPAction: "http://WebXml.com.cn/getStockInfoByCode"" -d @/tmp/sms/request/$file_name http://Xxxxxxxxxx >> /tmp/sms/log/$cal_date'.log'
- #写短信发送标记位
- sqlplus -S statistic/123@tjfxdb << EOF
- set heading off feedback off pagesize 0 verify off echo off
- insert into smsflg(caldate,flag) values('$cal_date',1);
- commit;
- quit;
- EOF
分析:
curl是一个利用URL规则在命令行下工作的文件传输工具,它支持文件的上传和下载,因此在脚本中我们用它来进行soap请求。其命令格式如下。
-H后面跟的是http请求头,’SOAPAction’键通常用来表示这个HTTP请求是一个web服务请求,同时取值可以为空字符串,但是也可以是请求的web服务操作的名称。
2.3.2.3定时执行
为了让系统更加智能化,执行脚本可以加入Linux的crontab定时任务当中。
40 10-18 * * * root sh /tmp/sms/sms_send.sh
上面表示每天10点到18点的40分,脚本都会启动一次,因为在执行脚本中我们加了短信发送标志位,因此不用担心短信回重复发送。
3. 经验总结
Webservice接口的请求方式有很多种,可以通过框架请求,也可以在java项目中直接基于soap请求,还可以像我们这样直接用shell脚本来完成请求。不同的方式有不同的优缺点,我们在实际项目中要学会灵活选择,适合的才是最好的。