微策云对接流程
微策云血糖管理系统父模块为vivachek,其中api为web与app(9801)后台系统,即院内实际部署的子模块,它依赖多个子模块,his模块就为其中之一; his模块为院内数据对接模块,api模块默认不依赖该模块,部署前需手动依赖his模块。
his模块默认实现视图对接,这边暂不概述视图对接,只对webservice对接方式进行介绍,由于ws对接实现方式取决于his提供的接口,所以his模块中只能抽象的提供通用方法,具体实现需要根据现场而定,需要修改的配置以todo标出。
-
修改默认同步逻辑实现类
his模块同步逻辑服务接口为HisSynService;现有三个实现类,分别为:HisSynServiceImpl(视图对接);WSSynServiceImpl(webservice对接);HttpSynServiceImpl(http对接)。默认为视图对接,需手动改换为需要的实现类。
-
配置参数
在进行接口调用之前,需要配置接口地址、命名空间、方法名、服务编码、回参标签参数:
- 接口地址:该地址为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。
-
入参数据编辑
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 { JAXBContext.newInstance(object.getClass()); context = Marshaller marshaller = context.createMarshaller(); setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.setProperty(Marshaller.JAXB_ENCODING, "utf-8"); marshaller.marshal(object, writer); marshaller.catch (JAXBException e) { } error("对象转XML错误,obj:{},err:{}", object, e); log. }return writer.getBuffer().toString(); }
-
接口请求和参数解析
请求方法如下:
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) { }error("ws参数转化错误----xml:{},err:{}", xml, e); log.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(); replace("<?xml version=\"1.0\" encoding=\"UTF-8\"?>",""); key = key. replaceAll("^[^>]*\\>", "<MESSAGE xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">") key = key.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) { }error("ws参数转化错误----xml:{},err:{}", xml, e); log.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){ put(e.getName(), e.getTextTrim()); map.//如果为叶子节点,那么直接把名字和值放入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(); unmarshal(new File("Person.xml")); Person person = (Person)unmarshaller.System.out.println("Perosn is=" +person.toString());
这样便完成了用XPath进行解析的步骤。
- tips:可使用soapUI工具对ws进行解析调用,然后查询工具中的请求参数。
发表回复