微策云ws对接流程

微策云对接流程

微策云血糖管理系统父模块为vivachek,其中api为web与app(9801)后台系统,即院内实际部署的子模块,它依赖多个子模块,his模块就为其中之一; his模块为院内数据对接模块,api模块默认不依赖该模块,部署前需手动依赖his模块。

his模块默认实现视图对接,这边暂不概述视图对接,只对webservice对接方式进行介绍,由于ws对接实现方式取决于his提供的接口,所以his模块中只能抽象的提供通用方法,具体实现需要根据现场而定,需要修改的配置以todo标出。

  1. 修改默认同步逻辑实现类

    his模块同步逻辑服务接口为HisSynService;现有三个实现类,分别为:HisSynServiceImpl(视图对接);WSSynServiceImpl(webservice对接);HttpSynServiceImpl(http对接)。默认为视图对接,需手动改换为需要的实现类。

  2. 配置参数

    在进行接口调用之前,需要配置接口地址、命名空间、方法名、服务编码、回参标签参数:

    • 接口地址:该地址为WSDL服务发布地址,位于service标签的soap:address中,示例如下:
    <service name="MyServiceImplService">
         <port name="MyServiceImplPort" binding="tns:MyServiceImplPortBinding">
             <soap:address location="http://localhost:8888/ws"/>
         </port>
     </service>

    ​ 若没有额外配置,发布地址则与WSDL地址相同;

    ​ 所属文件:WSSynServiceImpl。

    • 命名空间:

      所属文件:WSSynServiceImpl。

    • 方法名:

      所属文件:WSTradingCode。

      实际请求的时候,命名空间与方法名是组合的,请求参数为soapAction,命名空间可以理解为JAVA中的包名和路径,soapAction就是定位包和方法用的。

    • 服务编码:

      若his不通过方法来区分请求,而是通过传递的参数值来区分,则需要设置该值

      所属文件:WSTradingCode。

    • :回参标签

      若回参为soap格式,需要设置回参的标签值,来定位回参所属节点。

      所属文件:WSTradingCode。

  3. 入参数据编辑

    ws传输的数据是基于soap协议,所以在进行请求之前需要将参数用soap信封进行封装,信封格式则根据服务端实际场景配置,参数可以用<[CDATA[ ]]>转义为纯文体,防止特殊字符被解析。

    需要配置的方法如下:

    package com.vivachek.cloud.his.Utils;
    
    @Slf4j
    public class WSUtil {
    
         ...
    
        /**
         * todo WS配置:接口入参在此封装,可以通过bean2xml生成,也可以自己拼接字符串
         * @param tradingCode 服务编码,若his所有接口都用同一个方法,通过服务编码来区分,则传入该值
         * @param date 查询时间,查询该时间点之后新增的值
         * @return
         */
        public static String packInXml(String tradingCode, Date date) {
    
            return null;
        }
    
    
        //将实体类转化为xml格式的字符串
        public static String bean2xml(Object object) {
            StringWriter writer = new StringWriter();
            JAXBContext context;
            try {
                context = JAXBContext.newInstance(object.getClass());
                Marshaller marshaller = context.createMarshaller();
                marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
                marshaller.setProperty(Marshaller.JAXB_ENCODING, "utf-8");
                marshaller.marshal(object, writer);
            } catch (JAXBException e) {
                log.error("对象转XML错误,obj:{},err:{}", object, e);
            }
            return writer.getBuffer().toString();
        }
  4. 接口请求和参数解析

    请求方法如下:

    package com.vivachek.cloud.his.Utils;
    
    @Slf4j
    public class WSUtil {
    
        ···
    
        //todo WS配置:访问ws接口获取参数
        public static <T>T excuteWS(Class<T> tClass, String url, String namespace, String method, String reqXml) {
            //默认soap1.1方式,若his为1.2可自行更改
            String resXml = doPostSoap1_1(url, reqXml, namespace + "/" + method);
            return soap2bean(tClass, resXml, WSTradingCode.RETURN_PATH_NAME);
            //return xml2bean(tClass, resXml);
        }
    
         //将xml格式字符串转为实体类
        public static <T>T xml2bean(Class<T> tClass, String xml) {
            try {
                JAXBContext jc = JAXBContext.newInstance(tClass);
                Unmarshaller unmarshaller = jc.createUnmarshaller();
                StringReader stringReader = new StringReader(xml);
                return (T)unmarshaller.unmarshal(stringReader);
            }catch (JAXBException e) {
                log.error("ws参数转化错误----xml:{},err:{}", xml, e);
                return null;
            }
        }
    
        //将soap+xml格式字符串转为实体类
        public static <T>T soap2bean(Class<T> tClass, String xml, String returnName) {
    
            Map<String, Object> map = new HashMap<>();
            try {
                Map map2 = parse(xml, map);
                String key = map2.get(returnName).toString();
    
                key = key.replace("<?xml version=\"1.0\" encoding=\"UTF-8\"?>","");
    
                key = key.replaceAll("^[^>]*\\>", "<MESSAGE xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">")
                        .replaceAll("</\\w+>$", "</MESSAGE>");
    
                JAXBContext jc = JAXBContext.newInstance(tClass);
                Unmarshaller unmarshaller = jc.createUnmarshaller();
                StringReader stringReader = new StringReader(key);
                return (T)unmarshaller.unmarshal(stringReader);
            }catch (JAXBException | DocumentException e) {
                log.error("ws参数转化错误----xml:{},err:{}", xml, e);
                return null;
            }
        }
    
            private static Map parse(String soap, Map<String,Object> map) throws DocumentException{
            Document doc = DocumentHelper.parseText(soap);//报文转成doc对象
            Element root = doc.getRootElement();//获取根元素,准备递归解析这个XML树
            getCode(root, map);
            return map;
        }
    
    
        private static void getCode(Element root, Map<String,Object> map){
            if(root.elements()!=null){
                List<Element> list = root.elements();//如果当前跟节点有子节点,找到子节点
                for(Element e:list){//遍历每个节点
                    if(e.elements().size()>0){
                        getCode(e, map);//当前节点不为空的话,递归遍历子节点;
                    }
                    if(e.elements().size()==0){
                        map.put(e.getName(), e.getTextTrim());
                    }//如果为叶子节点,那么直接把名字和值放入map
                }
            }
        }

    返回参数默认为soap+xml样式,并进行相应解析,若需要其他解析方式需自己调整,需要注意,主干中的xml解析只能解析传统xml方式,如要解析xpath节点,需要自行配置。

    下面通过JAXB解析常规xml,his返回的数据范例:

    <soap:Envelope
            xmlns:soap="http://www.w3.org/2001/12/soap-envelope"
            soap:encodingStyle="http://www.w3.org/2001/12/soap-encoding">   
        <soap:Body>
            <MESSAGE>  
                <![CDATA[
                   <ROWS>
                        <ROW>
                            <id>1</id>
                            <dept_name>内分泌科</dept_name>
                            <status>有效</status>
                        </ROW>
                        <ROW>
                            <id>2</id>
                            <dept_name>消化科</dept_name>
                            <status>失效</status>
                        </ROW>
                    </ROWS>
             ]]>
            </MESSAGE>
        </soap:Body>
    </soap:Envelope>

    如图所示,回参的数据标签为,配置好回参标签后,可以通过WSUtil中的soap2bean方法获取并解析其中的数据,但是由于回参中的数据标签与我们的字段名称不符,所以需要进行字段解析的配置,配置实例如下:

    package com.vivachek.cloud.core.model.his;
    
    //必须要标明数据的根元素
    @XmlRootElement(name = "ROWS")
    public class HisDeptInfoWs {
    
       private List<DeptInfo> row;
    
        public List<DeptInfo> getRow() {
            return row;
        }
    
        //注意@XmlElement在本处使用时不能直接注解于字段上
        @XmlElement(name = "ROW")
        public void setRow(List<DeptInfo> row) {
            this.row = row;
        }
    }
    
    
    package com.vivachek.cloud.core.model.po;
    
    public class DeptInfo {
    
        private String id;
    
        private String name;
    
        //通过该注解可在数据注入前对数据进行预处理
        @XmlJavaTypeAdapter(XmlStatusAdapter.class)
        private Integer status;
    
        @XmlElement(name = "dept_name")
        public void setName(String name) {
            this.name = name;
        }
    
        public void setId(String Id) {
            this.name = name;
        }
    
        public void setStatus(String status) {
            this.status = status;
        }
        //@getters
    
    }
    
    
    package com.vivachek.cloud.core.Adapter;
    
    public class XmlStatusAdapter extends XmlAdapter<String, Integer> {
    
        @Override
        public Integer unmarshal(String v){
           if ("有效".equals(v)) {
               return 1;
           }else if ("无效".equals(v)) {
               return 0;
           }
           return null;
        }
    
        @Override
        public String marshal(Integer v) {
            return null;
        }
    }
    

    这边只举例了几个常用注解,更多注解可以在此查询https://howtodoinjava.com/jaxb/jaxb-annotations/。

    如果是对方采用XPath节点来封装数据,则需要进一步处理,这边先大致讲解下XPath概念:

    XPath 是一门在 XML 文档中查找信息的语言,它 用于在 XML 文档中通过元素和属性进行导航。

    在Xpath中有多种类型的节点,我们暂时不需要了解,只要知道他的格式,并能去解析它便可以。

    下面是一个多级标签的XML示例

    
    <?xml version="1.0" encoding="UTF-8"?>
    <person>
        <firstname>Barok</firstname>
        <lastname>Obama</lastname>
        <age>52</age>
        <car>
            <model>Green Ford Focus 1.4L</model>
            <data root="20210225"></data> 
        </car>
    </person>

    若使用JAXB去解析该XML时,我们需要创建一个Person类和Car类,而通过基于JAXB实现的Moxy就可以完成对多级节点的数据解析。

    首先导入Moxy的jar包:

    <dependency>
        <groupId>org.eclipse.persistence</groupId>
        <artifactId>org.eclipse.persistence.moxy</artifactId>
        <version>2.7.7</version>
    </dependency>

    为了告诉JDK在运行时将MOXy用于JAXB实现,需要放置了一个名为jaxb.properties与 POJO位于同一目录中。 它包含一行:

    javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory

    在springboot中还需要在pom中标明添加扫描该文件,不然不会生效。

    完成后可以通过以下代码检查是否生效:

    JAXBContext jaxbContext = JAXBContext.newInstance(Person.class);
    System.out.println("jaxbContext is=" +jaxbContext.toString());

    如果显示以下内容表示配置成功:

    jaxbContext is=org.eclipse.persistence.jaxb.JAXBContext@5e3974

    接下来对实体类进行配置:

    
    @XmlRootElement
    public class Person {
        private String firstname;
        private String lastname;
        private int age;
    
        @XmlPath("car/model/text()")
        private String model;
    
        @XmlPath("car/date/@root")
        private Integer date;
        }
    
    public static void unmarshall() throws Exception {
        JAXBContext jaxbContext = JAXBContext.newInstance(Person.class);
        Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
        Person person = (Person)unmarshaller.unmarshal(new File("Person.xml"));
        System.out.println("Perosn is=" +person.toString());

    这样便完成了用XPath进行解析的步骤。

  • tips:可使用soapUI工具对ws进行解析调用,然后查询工具中的请求参数。

评论

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注