最近公司与一些大公司对接服务,使用的是老掉牙的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大法文章,所以决定将自己所学所用所整理的知识分享给大家,主要还是想为了后浪们少走些弯路,多些正能量的博客,如有错漏,欢迎指正,仅希望大家能在我的博客中学到知识,解决到问题,那么就足够了。谢谢大家!(转载请注明原文出处)