最近公司与一些大公司对接服务,使用的是老掉牙的web service协议的接口,其中遇到了不少的坑。在此将自己的经验描述一下,让各位后辈能学习到东西。。。

一、需求1:

甲方给了一份技术对接文档,是webservice协议的。然后给了一个wsdl文件。我这边需要用springboot写一个支付回调接口并且具体响应信息。

二、需求2:

甲方提供webservice协议的文档,需要我写一个接口去调用。

基本的需求就是上面两个。但是webservice最重要的还是xml格式的组装。每家公司都已经有了各自的组装xml参数方式,只要稍微不一致,就会报错的。

做这种接口的对接前,首先可下载一个soapui软件导入甲方给的wsdl文件,分析其请求xml数据,然后模拟调用该接口,相信大家已经会了。

这个大家肯定都是已经懂了。但是我要讲的干货肯定不是这些入门的玩意儿。 首先,当自己发布一个webservice接口,那么cxf会自己生成请求的“xml数据”,那么这个请求格式我们是不可控制的。甲方往往需要的不是自己自动生成的那种格式。所以需要有拦截器做自定义的数据组装

需要用到的maven包

    <!--  web service  -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web-services</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-frontend-jaxws</artifactId>
        <version>3.1.12</version>
    </dependency>
    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-transports-http</artifactId>
        <version>3.1.12</version>
    </dependency>
    <dependency>
        <groupId>jakarta.jws</groupId>
        <artifactId>jakarta.jws-api</artifactId>
        <version>2.1.0</version>
    </dependency>
    <dependency>
        <groupId>org.jsoup</groupId>
        <artifactId>jsoup</artifactId>
        <version>1.13.1</version>
    </dependency>
    <!--  web service  -->
CachedStream类

import org.apache.cxf.io.CachedOutputStream;
import java.io.IOException;
 * @author lihuanxin
 * @Description
 * @create 2020-11-16 19:08
public class CachedStream extends CachedOutputStream {
    public CachedStream() {
        super();
    protected void doFlush() throws IOException {
        currentStream.flush();
    protected void doClose() throws IOException {
    protected void onWrite() throws IOException {

CxfConfig类

import com.zshine.sports.service.MyWebService;
import com.zshine.sports.service.impl.MyWebServiceImpl;
import org.apache.cxf.Bus;
import org.apache.cxf.bus.spring.SpringBus;
import org.apache.cxf.jaxws.EndpointImpl;
import org.apache.cxf.transport.servlet.CXFServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.xml.ws.Endpoint;
 * @author lihuanxin
 * @Description
 * @create 2020-11-13 17:21
@Configuration
public class CxfConfig {
    @Bean
    public ServletRegistrationBean createServletRegistrationBean() {
        return new ServletRegistrationBean(new CXFServlet(), "/suningWebService/*");
    @Bean(name = Bus.DEFAULT_BUS_ID)
    public SpringBus springBus() {
        return new SpringBus();
    @Bean
    public MyWebService myWebService() {
        return new MyWebServiceImpl();
    @Bean
    public Endpoint endpoint() {
        EndpointImpl endpoint = new EndpointImpl(springBus(), myWebService());
        endpoint.publish("/orderRelationSyncReq");
        endpoint.getInInterceptors().add(new InputInterceptor());//加入请求前拦截器
        endpoint.getOutInterceptors().add(new OutPutInterceptor());//加入响应前拦截器
        return endpoint;

InputInterceptor类

import lombok.extern.slf4j.Slf4j;
import org.apache.cxf.helpers.IOUtils;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
 * @author lihuanxin
 * @Description web-service客户端请求拦截器,自定义格式
 * @create 2020-11-16 17:27
@Slf4j
public class InputInterceptor extends AbstractPhaseInterceptor<Message> {
    public InputInterceptor() {
        super(Phase.RECEIVE);
    @Override
    public void handleMessage(Message message) throws Fault {
        try {
            InputStream is = message.getContent(InputStream.class);
            //这里可以对流做处理,从流中读取数据,然后修改为自己想要的数据
            //处理完毕,写回到message中
            //在这里需要注意一点的是,如果修改后的soap消息格式不符合webservice框架格式,比如:框架封装后的格式为
            //<soap:Envelope xmlns:soap="http://www.w3.org/2001/12/soap-envelope" <soap:Body>
            //<这里是调用服务端方法的命名空间><这是参数名称>
            //这里才是真正的消息
            //</这里是调用服务端方法的命名空间></这是参数名称>
            //</soap:Body>
            //</soap:Envelope>
            //如果是以上这种格式,在暴露的接口方法里才会真正接收到消息,而如果请求中在body里边,没有加方法命名空间和参数名称,直接就是消息体
            //那接口方法里是接收不到消息的,因为cxf是按照上面的这种格式去解析的,所以如果不符合上面的格式,就应该在这里做处理
            //……………………处理代码……………………
            String xml = IOUtils.toString(is);
            //System.out.println("营业厅接收到的xml请求数据是:"+xml);
            log.info("营业厅接收到的xml请求数据是:"+xml);
            //对标签进行去除
            xml = xml.replace("xmlns=\"http://sp.fsdp.zte.com.cn\"","");
            xml = xml.replace("xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">","xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' xmlns:sp='http://sp.fsdp.zte.com.cn'>");
            xml = xml.replace("orderRelationSyncReq","sp:orderRelationSyncReq");
            //System.out.println("营业厅接收到的xml格式化后是:"+xml);
            log.info("营业厅接收到的xml格式化后是:"+xml);
            //这里对xml做处理,处理完后同理,写回流中
            InputStream ism = new ByteArrayInputStream(xml.getBytes());
            if(is != null)
                message.setContent(InputStream.class, ism);
        } catch (Exception e) {
            log.error("Error when split original inputStream. CausedBy : "+"\n"+e);

OutPutInterceptor类

import lombok.extern.slf4j.Slf4j;
import org.apache.cxf.helpers.IOUtils;
import org.apache.cxf.io.CachedOutputStream;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.apache.cxf.message.Message;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.OutputStream;
 * @author lihuanxin
 * @Description web-service客户端响应拦截器,增加响应参数的命名空间
 * @create 2020-11-16 17:27
@Slf4j
public class OutPutInterceptor extends AbstractPhaseInterceptor<Message> {
    public OutPutInterceptor() {//这里代表流关闭之前的阶段,这很重要!可以到官网去看,拦截的阶段分为很多种
        super(Phase.PRE_STREAM);
    @Override
    public void handleMessage(Message message) {
        try {
            OutputStream os = message.getContent(OutputStream.class);
            CachedStream cs = new CachedStream();
            message.setContent(OutputStream.class, cs);
            message.getInterceptorChain().doIntercept(message);
            CachedOutputStream csnew = (CachedOutputStream) message.getContent(OutputStream.class);
            InputStream in = csnew.getInputStream();
            String xml = IOUtils.toString(in);
            log.info("未处理--响应的xml:" + xml);
            // 解析报文
            Document doc = Jsoup.parse(xml);
            String resultCode = doc.getElementsByTag("resultCode").text();
            String resultDesc = doc.getElementsByTag("resultDesc").text();
            String streamingNO = doc.getElementsByTag("streamingNO"




    
).text();
            StringBuilder sb = new StringBuilder();
            sb = sb.append("<soap:Envelope ")
                    .append("xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" ")
                    .append("xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" ")
                    .append("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">")
                    .append("<soap:Body xmlns:ns1=\"http://sp.fsdp.zte.com.cn\">")
                    .append("<ns1:orderRelationSyncResp>")
                    .append("<resultCode xmlns=\"http://sp.fsdp.zte.com.cn\">"+resultCode+"</resultCode>")
                    .append("<resultDesc xmlns=\"http://sp.fsdp.zte.com.cn\">"+resultDesc+"</resultDesc>")
                    .append("<servID xmlns=\"http://sp.fsdp.zte.com.cn\" xsi:nil=\"true\" />")
                    .append("<streamingNO xmlns=\"http://sp.fsdp.zte.com.cn\">"+streamingNO+"</streamingNO>")
                    .append("</ns1:orderRelationSyncResp>")
                    .append("</soap:Body></soap:Envelope>");
            // 这里对xml做处理,处理完后同理,写回流中
            log.info("已处理--响应的xml:" + sb.toString());
            IOUtils.copy(new ByteArrayInputStream(sb.toString().getBytes()), os);
            cs.close();
            os.flush();
            message.setContent(OutputStream.class, os);
        } catch (Exception e) {
            log.error("Error when split original inputStream. CausedBy : " + "\n" + e);

MyWebService类

import com.zshine.sports.vo.portal.WebServiceCancelVO;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
import java.io.UnsupportedEncodingException;
@WebService(targetNamespace ="http://sp.fsdp.zte.com.cn")
public interface MyWebService {
    @WebMethod
    WebServiceCancelVO orderRelationSyncReq(
            @WebParam(name="streamingNO")String streamingNO,
            @WebParam(name="opType")String opType,
            @WebParam(name="customID")String customID,
            @WebParam(name="devType")String devType,
            @WebParam(name="devNO")String devNO,
            @WebParam(name="catvID")String catvID,
            @WebParam(name="spID")String spID,
            @WebParam(name="serviceID")String serviceID,
            @WebParam(name="feeType")String feeType,
            @WebParam(name="fee")Float fee,
            @WebParam(name="originalFee")Float originalFee,
            @WebParam(name="amount")Integer amount,
            @WebParam(name="effectiveDate")String effectiveDate,
            @WebParam(name="expiryDate")String expiryDate,
            @WebParam(name="servID")String servID,
            @WebParam(name="deptid")String deptid,
            @WebParam(name="operateid")String operateid,
            @WebParam(name="citycode")String citycode
    ) throws UnsupportedEncodingException;

MyWebServiceImpl类

import com.zshine.sports.bo.WebServiceCancelBO;
import com.zshine.sports.documents.PaymentMemberRecordFSDP;
import com.zshine.sports.documents.PortalMember;
import com.zshine.sports.repository.PaymentMemberRecordFSDPRepository;
import com.zshine.sports.service.MyWebService;
import com.zshine.sports.vo.portal.WebServiceCancelVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.jni.Local;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import javax.jws.WebParam;
import javax.jws.WebService;
import java.io.UnsupportedEncodingException;
import java.time.LocalDateTime;
 * @author lihuanxin
 * @Description
 * @create 2020-11-13 17:15
@WebService(name = "suningWebService" ,targetNamespace ="http://sp.fsdp.zte.com.cn" ,endpointInterface = "com.zshine.sports.service.MyWebService")
@Slf4j
public class MyWebServiceImpl implements MyWebService {
    @Autowired
    private MongoTemplate mongoTemplate;
    @Autowired
    private PaymentMemberRecordFSDPRepository paymentMemberRecordFSDPRepository;
    @Override
    public WebServiceCancelVO orderRelationSyncReq(
            @WebParam(name="streamingNO")String streamingNO,
            @WebParam(name="opType")String opType,
            @WebParam(name="customID")String customID,
            @WebParam(name="devType")String devType,
            @WebParam(name="devNO")String devNO,
            @WebParam(name="catvID")String catvID,
            @WebParam(name="spID")String spID,
            @WebParam(name="serviceID")String serviceID,
            @WebParam(name="feeType")String feeType,
            @WebParam(name="fee")Float fee,
            @WebParam(name="originalFee")Float originalFee,
            @WebParam(name="amount")Integer amount,
            @WebParam(name="effectiveDate")String effectiveDate,
            @WebParam(name="expiryDate")String expiryDate,
            @WebParam(name="servID")String servID,
            @WebParam(name="deptid")String deptid,
            @WebParam(name="operateid")String operateid,
            @WebParam(name="citycode")String citycode
    ) throws UnsupportedEncodingException {
        //首先判断是否是重复回调
        Query queryFsdp = new Query();
        queryFsdp.addCriteria(Criteria.where("streamingNo").is(streamingNO));//流水号
        queryFsdp.addCriteria(Criteria.where("opType").is(opType));//0开通 1关闭
        queryFsdp.addCriteria(Criteria.where("devno").is(devNO));//智能卡号
        PaymentMemberRecordFSDP old = mongoTemplate.findOne(queryFsdp,PaymentMemberRecordFSDP.class);
        if (old != null){
            WebServiceCancelVO vo = new WebServiceCancelVO();
            vo.setResultCode(0);
            vo.setStreamingNO(streamingNO);
            vo.setResultDesc("成功接收异步营业厅线下订购/退订通知");
            return vo;
        //正常保存该订购记录
        PaymentMemberRecordFSDP fsdp = new PaymentMemberRecordFSDP();
        fsdp.setStreamingNo(streamingNO);
        fsdp.setOpType(opType);
        fsdp.setCustomID(customID);
        fsdp.setDevType(devType);
        fsdp.setDevno(devNO);
        fsdp.setCatvid(catvID);
        fsdp.setSpid(spID);
        fsdp.setServiceid(serviceID);
        fsdp.setFeeType(feeType);
        fsdp.setFee(fee);
        fsdp.setOriginalFee(originalFee);
        fsdp.setAmount(amount);
        fsdp.setEffectiveDate(effectiveDate);
        fsdp.setExpiryDate(expiryDate);
        fsdp.setServID(servID);
        fsdp.setDeptid(deptid);
        fsdp.setOperateid(operateid);
        fsdp.setCitycode(citycode);
        Query queryMember = new Query();
        queryMember.addCriteria(Criteria.where("deviceCode").is(devNO));//智能卡号
        PortalMember portalMember = mongoTemplate.findOne(queryMember,PortalMember.class);
        if (portalMember != null){
            fsdp.setAreaid(portalMember.getAreaid());//业务区
            fsdp.setBranchno(portalMember.getBranchno());//城市代码
        //保存订购记录
        fsdp.setCreateTime(LocalDateTime.now());
        paymentMemberRecordFSDPRepository.save(fsdp);
        //更新portalMember表中的会员状态
        Update update = new Update();
        update.set("opType",opType);//会员状态 0开通 1关闭
        update.set("updateTime", LocalDateTime.now());
        mongoTemplate.updateFirst(queryMember, update, PortalMember.class, "portalMember");
        WebServiceCancelVO vo = new WebServiceCancelVO();
        vo.setResultCode(0);
        vo.setStreamingNO(streamingNO);
        vo.setResultDesc("成功接收异步营业厅线下订购/退订通知");
        return vo;

在此我要说的是,当你的甲方请求的数据或者你返回的数据跟自己springboot生成的不一致时,一定要使用拦截器对流进行拦截。然后自定义xml的组装格式。最后接收参数或返回响应。具体的拦截器使用。主要看OutPutInterceptor、InputInterceptor、CxfConfig类的写法。

总结:只要你懂得使用拦截器拦截流,并修改内容。那么基本上不管甲方需要什么格式都能轻松应付了。

结语:以往都是看别人的博客进行学习技术,其中不乏有精华博客也有吊儿郎当的CV大法文章,所以决定将自己所学所用所整理的知识分享给大家,主要还是想为了后浪们少走些弯路,多些正能量的博客,如有错漏,欢迎指正,仅希望大家能在我的博客中学到知识,解决到问题,那么就足够了。谢谢大家!(转载请注明原文出处)

分类:
后端
标签: