Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 840|回复: 0

终于解决了PHP调用SOAP过程中的种种问题。(转)

[复制链接]
  • TA的每日心情
    奋斗
    2024-4-6 11:05
  • 签到天数: 748 天

    [LV.9]以坛为家II

    2034

    主题

    2092

    帖子

    70万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    705612
    发表于 2021-7-1 06:56:24 | 显示全部楼层 |阅读模式

    最近在做公司和第三方的一个合作项目,需要调用统一验证接口和统一支付接口。由于牵涉公司机密,所以我要单独写一层PHP的接口给第三方用。前面那个验证接口主要卡在了des加密的方式上,这个有时间再说。这篇主要说说在实现统一支付接口上的问题。
        统一支付顾名思义,是公司的扣费系统,其中提供许多支付方式和支付种类(比如支付宝之类的),然后还会让你选择提交的银行方式。这里的逻辑业务我以后再说,主要说说这里涉及的一个概念。由于在网上银行交费页面提交完数据后,页面不会自动进行跳转,所以底层的接口(我们这里是JAVA实现的)要自动监听返回状态。但是返回的必然是一个加密的串,称为TokenString。这个TokenString的解密算法显然只有公司内部的人才能够知道,所以对于给合作方提供的时候,我必然要写一个WEBSERVICE来提供JAVA程序异步调用来解密,然后前台程序也来通过这个程序获取返回状态,比如交费是否成功之类的。好了,前面是我要做这东西的起因。下面具体说我在开发过程中遇到的问题吧。相信如果你用php要开发这个业务的话,都会遇到这个问题的。

    -----------------------------------分割线-----------------------------------------------

        webservice的一种常用实现方式就是soap了。我们后端的JAVA也是用soap的原理实现的。那么我显然首先要上网上搜搜关于soap的文章。最早进入实现的是PHP写的nusoap类。这个nusoap.php文件是完全用PHP写法来实现的soap方法。优点是不用给php装动态模块,也不用重新编译PHP。我当时像找到了新大陆一样,一头就载进去了。
    先构建一个soap_server.php的文件,主要就是负责把给TokenString解密的getMsgSoap函数。具体解密算法我肯定是封装在自己写的类里了。
    <?php

    define('IFENG_LOG_LEVEL_DEBUG', 8);
    require('./include/nusoap.php');
    include "./include/class.IFengSystem.php";
    include "./include/class.IFengHttp.php";
    include "./include/class.IFengPay.php";


    //创建soap服务类
    $server = new soap_server;
    $server->register('getMsgSoap');

    //$pay = new IFengPay();    //创建支付类
    $pay = IFengPay::getInstance();    //单态创建一个类
    //$token = $pay->des->decrypt('C445FFDB6DE7093E58E0D60F9249B54DBEE2719EAD036262BCAABD5C234155B98229C4F8283B643FF2A454B22CED20F1C53C75F6C578EC30F597F656B125997CF0121F184989D32CFA1D40E74ECA4FBE93212FF5FC839625EE459294FF052A9FBC1F1961DBA8FAB6DBFB6E3C1A53FFBEA91B0C95E370588C44C9B1A5786A49D6BC67D79894A65664');    //调用des解密算法


    function getMsgSoap($token)
    {
        global $pay;
        $token = $pay->des->decrypt($token);    //调用des解密算法
        return $token;
    }
    $HTTP_RAW_POST_DATA = isset($HTTP_RAW_POST_DATA) ? $HTTP_RAW_POST_DATA : '';
    $server->service($HTTP_RAW_POST_DATA);
    ?>

    然后再写一个soap_client.php,实际上就是调用soap_server.php上封装好的方法。
    代码如下:
    <?php
    //引用numsoap包。
    require('./include/nusoap.php');

    //soap的服务端
    $server_url = 'http://app.finance.ifeng.com/Sso/soap_server.php';

    //创建一个实例
    $client = new soapclient($server_url);
    //调用的soap端的函数协议
    $soap_method_name = 'getMsgSoap';
    //TokenString是返回的加密串的参数名
    $data = array('token' => $_GET['TokenString']);
    $result = $client->call($soap_method_name, $data);
    // Display the result
    print_r($result);
    ?>

        测试完了,我一测试,OK了。高兴,兴奋,原来这么简单。结果没过多久,我们技术部的同事yetao就找我来了,你这个WEBSERVICE我那边调用不了。
        其实我当时还没有害怕,看看怎么回事儿呗。结果yetao说我这个nusoap自动生成的wsdl和他JAVA,还有C#生成的wsdl不统一。也就是说我这个不是标准的。晕,不是吧。心里凉了半截。
        这里需要给大家说一下什么是WSDL,基本上这就是XML的一种标准变形,类似于SVG和XSL那种,又自己的命名规则。剩下的知识大家搜索一下吧。

        没办法了。换别的方法吧。于是继续在网上寻觅着。突然有篇帖子说nusoap已经过时了,而且通用性不好,最好还是用PHP5中自带的soap函数。切,最后还是躲不过要安装动态模块的老路。结果就看看手册上的安装帮助呗。手册上的安装需求部分会提示This extension makes use of the » GNOME xml library. Download and install this library. You will need at least libxml-2.5.4. 于是我开始在网上找libxml的package,这个还挺好找了,用了和上次mcrypt一样的安装方法,详见http://blog.sina.com.cn/s/blog_582246d20100dej9.html

        装完了libxml我说继续找soap的安装包吧,结果满互联网找竟然都没找到一个安装的文件,无论是php.net,还是pecl网站都没找到,也可能是我笨吧。问问合作方用的PHP是什么版本,发现和我的一样,我想同样的版本说不定直接把soap.so文件要过来就成了(这绝对是用多了windows系统人的弊病,别以为这是php_soap.dll那样的,因为只要是在windows环境下,dll文件就通用)但是在linux下可就不同了,果不其然,对方的系统是centos,和我这边的不同。这条路也断了。

        问了问技术部同事sunli,他建议我只能重新编译php了,然后在编译是加上--enable-soap。于是我写个phpinfo()把Configure Command 对应的代码都拷贝下来,然后再加上我新要加上的这个模块。
    代码如下:
    './configure' '--prefix=/usr/local/php5' '--with-libxml-dir=/usr/lib' '--with-zlib' '--with-gd=/usr/local/gdlibforphp/gd' '--with-zlib-dir=/usr' '--with-mysql=/data/mysql' '--enable-sockets' '--enable-mbstring' '--enable-soap' '--with-apxs2=/data/apache/bin/apxs' '--enable-safe-mode' '--enable-ftp' '--with-png-dir=/usr/local' '--with-freetype-dir=/usr' '--with-jpeg-dir=/usr/local/gdlibforphp/jpeg' '--with-sqlite=shared'

    记住,编译之前要把那个生成的文件夹删除,从新用tar命令解压,然后执行这编译命令,再make && make install来安装。完毕后重启apache,再看phpinfo。发现soap包终于有了。看来有的时候想躲一些事情是躲不掉的。但是这样有一个问题,就是现在这个只是在测试机上编译安装。如果在产品机上安装,那肯定得找个浏览人数少的时候,所以未来这一两天,我看那天晚上方便,给产品机重新编译一下。

    然后重新构造这两个soap文件,准确的说是三个,因为还要自己构造一个wsdl文件。
    soap_server.php程序代码如下:
    <?php

    define('IFENG_LOG_LEVEL_DEBUG', 8);
    include "./include/class.IFengSystem.php";
    include "./include/class.IFengHttp.php";
    include "./include/class.IFengPay.php";

    class msg
    {
        public function getMsgSoap($token = '')
        {
            $pay = IFengPay::getInstance();    //单态创建一个类
            $token = $pay->des->decrypt($token);    //调用des解密算法
            return $token;
        }
    }
    $server = new SoapServer('msg.wsdl', array('soap_version' => SOAP_1_2, 'encoding'=>'UTF-8'));  
    $server->setClass("msg");  
    $server->handle();  
    ?>

    编码强制转化为了UTF-8,然后声明一个webservice类msg,这里也可以声明function的。

    soap_client.php代码如下:
    <?php
    //$client = new SoapClient('http://app.finance.ifeng.com/Sso/msg.wsdl');   
    $client = new SoapClient("http://app.finance.ifeng.com/Sso/soap_server.php?WSDL",array('cache_wsdl' => 0));
    $TokenString = $_GET['TokenString'];
    try {
        $result = $client->getMsgSoap($TokenString);  
    } catch(SoapFault $e) {  
        print "Sorry an error was caught executing your request: {$e->getMessage()}";  
     

    print_r($result);
    ?>
        其中第一行注释的就是wsdl的位置,因为nusoap是自动生成wsdl的,所以我们之前一直没有考虑这个东西。这个wsdl是可以通过zend自动生成的。当你写好一个soap_server.php的时候,在zend中执行“工具”->“WSDL生成器”,然后选定一个wsdl文件,这个文件不同于JAVA和C#中,需要我们事先建立好这个文件,然后选中这个文件后点击下一步,选择绑定的文件(也就是我们这个soap_server.php),于是就会自动显示这个文件中的方法了。选中你想填入到wsdl中的方法,然后complete就可以了。生成的这个wsdl是标准的可以被JAVA中soap方法识别的。

        最后就当我以为已经成功的时候,程序上又输出了一行错误。“Unable to parse URL”也就是$e->getMessage()这行生效了。我在百度上搜索数条误解后,开始求助google了。于是在一篇英文的文章中找到了解决方法,不得否认,还是老外牛啊。
    该文链接:http://www.electrictoolbox.com/php-soapclient-unable-parse-url/

    根据这篇文章,我把大家需要改动的部分罗列出来吧,方便看英文不方便的同学。
    1、首先你要在生成的wsdl文件中找到<soap:address location=""/>,然后改为<soap:address location="http://app.finance.ifeng.com/Sso/soap_server.php"/>也就是换成你那个soap_server.php所在的位置。

    2、修改php.ini,加入下面这几行,是控制WSDL的缓存的。

    [soap] 
    ; Enables or disables WSDL caching feature. 
    soap.wsdl_cache_enabled=1 
    ; Sets the directory name where SOAP extension will put cache files. soap.wsdl_cache_dir="/tmp" 
    ; (time to live) Sets the number of second while cached file will be used 
    ; instead of original one. 
    soap.wsdl_cache_ttl=86400

    3、最后就是网上关于Soap类调用的例子都是直接
    $client = new SoapClient('http://host/path/file.php?wsdl');
    但是后面还要加上一个参数的,改为下面这句。
    $client = new SoapClient('http://host/path/file.php?wsdl', array('cache_wsdl' => 0));

    这三步都修改完毕后,重启apache,再看你的页面,发现终于成功了!OH,终于成功了!


    在最后的最后,yetao说他那边虽然能看见我这个方法了,但是一传参数还是会报错,由于时间紧,我们就改http的传送方式来解决这个问题了。不过这个问题的源头可能是这句话。在网上看到的。“特别注意:我发现调用php webserver的方法和调用.net web服务的方法不一样。 调用.net service方法必须传入命名参数;而调用php web服务方法,一定不能传入命名参数,只能按顺序传入,为什么?这一点尤其要注意 ”。有时间再研究吧,这个已经占用我很多时间了。

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-5-2 19:24 , Processed in 0.072337 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表