当前位置:网站首页>Generate XML based on annotations and reflection

Generate XML based on annotations and reflection

2022-06-10 18:08:00 llp1110

Generate based on annotations and reflection xml

​ Hello everyone , I am a llp. Recently, when interfacing with third-party interfaces , Found that they are all xml A message of form , And there are differences. Some collections contain parent tags while others do not , So I want to do a generation based on annotation reflection xml The plan . I just finished today , Make a record and share , There must be many deficiencies in the program , Your suggestions are very welcome .

1.XMl grammar

Before that, we need to know XML The document is divided into the following parts

1. The document statement

2. Elements

3. attribute

4. notes

5.CDATA District 、 Special characters

Specific details , You can refer to the official website documentation

2. Define your own set of annotations

Here I divide annotations into the following categories :

1.@XmlBeanElement   Used to decorate a class 
2.@XmlPropertyELement  Used to decorate fields of common types 
3.@XmlBeanElement  Used to modify bean Type field 
4.@XmlCDATA  Used to decorate and generate CDATA Field of zone 
5.@XmlGatherElement  Fields used to decorate collection types  List<String>List<Person>List<Map>
6.@XmlMapElement  Used to modify Map Type field  Map<String,String>

here Map Only ordinary types of key,val The way

[email protected]

/** * xml Annotate with the label (xml Elements ) * ElementType.TYPE  decorator  * @Retention(RetentionPolicy.RUNTIME)  Run time takes effect  */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XmlRootElement {
    

    /** *  Alias  */
    String name() default "";

    /** *  Namespace  */
    String[] namespace() default "";

    /** *  Version number   For document declaration  */
    String version() default "v1.0";

    /** *  code   For document declaration  */
    String encoding() default "utf-8";

}

[email protected]

/** * xml Attribute annotation  * @Target(ElementType.FIELD)  Decorate fields  */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XmlPropertyELement {
    

    /** *  Alias  */
    String name() default "";

    /** *  Namespace  */
    String namespace() default "";

    /** *  Whether to resolve null value  */
    boolean analysisEmpty() default false;

}

[email protected]

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XmlBeanElement {
    

    /** *  Alias  */
    String name() default "";

    /** *  Namespace  */
    String namespace() default "";

    /** *  Whether to resolve null value  * @return */
    boolean analysisEmpty() default false;

}

[email protected]

/** * CDATA District  * <![CDATA[ *  Here you can display the characters you entered as they are , No resolution  xml * ]]> * 1. Because the design here @XmlCDATA It's cooperation @XmlPropertyELement Use annotations  *  Whether to resolve null value 、 Namespace 、 Aliases, etc. are handed over to @XmlPropertyELement control  * [email protected] Annotations focus only on xml in CDATA Treatment of the area  */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XmlCDATA {
    
    /** * <code> * <!-- If you want to put some strings , As plain text , Use CDATA Include  --> * <![CDATA[ * <script data-compress=strip> * function h(obj){ * obj.style.behavior='url(#default#homepage)'; * var a = obj.setHomePage('//www.baidu.com/'); * } * </script> * ]]> * </code> */

}

[email protected]

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XmlGatherElement {
    

    /** *  Alias  */
    String name() default "";

    /** *  Namespace  */
    String namespace() default "";

    /** *  Child tags  */
    String childName() default "";

    /** *  Whether to resolve null value  */
    boolean analysisEmpty() default false;

    /** *  Whether to display parent signature  */
    boolean showName() default true;

    /** *  Whether it is bean */
    boolean isBean() default false;
}

[email protected]

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XmlMapElement {
    

    /** *  Tag alias  */
    String name() default "";

    /** *  Namespace  */
    String namespace() default "";

    /** *  Whether to resolve null value  */
    boolean analysisEmpty() default false;
}

3. Generate XML Tool class of

public class XmlUtil {
    

    /** *  Generate xml character string  * * @param obj xml The object decorated by the element annotation  * @return */
    public static String generateXml(Object obj) {
    
        //1. Get class Object's 
        Class<?> clazz = obj.getClass();
        //2. obtain xml Element annotation 
        XmlRootElement xmlRootElement = clazz.getAnnotation(XmlRootElement.class);
        //3. Define a StringBuffer Object is used to splice xml character string 
        StringBuffer xml = new StringBuffer();
        //4. The document statement   assemble  <?xml version="1.0" encoding="UTF-8"?>
        xml.append("<?xml ")
                .append("version=" + xmlRootElement.version())
                .append(" ")
                .append("encoding=" + xmlRootElement.encoding())
                .append("?>");

        //5.xml Elements ( Follow the label ) Splicing 
        String name = xmlRootElement.name();
        // If the alias is empty , Take the class name as the label 
        if ("".equals(name) || name == null) {
    
            name = clazz.getSimpleName();
        }
        xml.append("<")
                .append(name)
                .append(" ");

        String[] namespace = xmlRootElement.namespace();
        //6. Traversing namespaces , Splicing xml Element namespace 
        for (int i = 0; i < namespace.length; i++) {
    
            if (StringUtils.isNotBlank(namespace[i])) {
    
                xml.append(namespace[i])
                        .append(" ");
            }

        }
        // Remove extra space at the end 
        xml.deleteCharAt(xml.lastIndexOf(" "));
        xml.append(">");

        //7. Splicing xml attribute 
        element(xml, clazz, obj);

        //8.xml End of element 
        xml.append("</")
                .append(xmlRootElement.name())
                .append(">");
        return xml.toString();
    }

    /** *  assemble xml Attribute tag content  * * @param xml * @param clazz * @param obj */
    private static void element(StringBuffer xml, Class<?> clazz, Object obj) {
    
        //1. Get all the fields of the class 
        Field[] fields = clazz.getDeclaredFields();
        //2. Traverse all fields 
        for (Field field : fields) {
    
            Object val = getFieldVal(field, obj);
            //3. Determine what type of annotation each field is decorated with 
            if (field.isAnnotationPresent(XmlPropertyELement.class)) {
    
                // Basic type field content processing 
                propertyElement(field, xml, val);
            } else if (field.isAnnotationPresent(XmlBeanElement.class)) {
    
                // Object field content processing 
                beanELement(field, xml, val);
            } else if (field.isAnnotationPresent(XmlGatherElement.class)) {
    
                // Set field content processing 
                gatherELement(field, xml, val);
            } else if (field.isAnnotationPresent(XmlMapElement.class)) {
    
                //map Field content processing 
                mapELement(field, xml, val);
            }
        }
    }

    /** * map Field content processing  * * @param field * @param xml * @param val */
    private static void mapELement(Field field, StringBuffer xml, Object val) {
    
        //1. obtain @XmlMapElement annotation 
        XmlMapElement xmlMapElement = field.getAnnotation(XmlMapElement.class);
        //2. Determine whether to resolve null values 
        if (!xmlMapElement.analysisEmpty() && isEmpty(val)) {
    
            return;
        }
        //3. Get alias , If the alias is empty, the field name is used as the label 
        String name = xmlMapElement.name();
        if ("".equals(name) || name == null) {
    
            name = field.getName();
        }
        xml.append("<").append(name);
        //4. Get namespace , Splice if present 
        if (!"".equals(xmlMapElement.namespace()) && xmlMapElement.namespace() != null) {
    
            xml.append(" ").append(xmlMapElement.namespace());
        }
        xml.append(">");
        // Traverse map Splicing 
        if (val instanceof Map) {
    
            mapToXmlString(xml, (Map) val);
        } else {
    
            throw new RuntimeException("@XmlMapElement The identity field must be Map type ");
        }
        // End label splicing 
        xml.append("</").append(name).append(">");
    }

    /** *  Set field content processing  * * @param field * @param xml * @param val */
    private static void gatherELement(Field field, StringBuffer xml, Object val) {
    
        //1. obtain @XmlGatherElement annotation 
        XmlGatherElement gatherElement = field.getAnnotation(XmlGatherElement.class);
        // Determine whether to process null data 
        if (!gatherElement.analysisEmpty() && isEmpty(val)) {
    
            return;
        }
        String name = gatherElement.name();
        // If the alias is empty, assemble by name 
        if (gatherElement == null || name.length() == 0) {
    
            name = field.getName();
        }
        // Determine whether to assemble the parent label 
        boolean showName = gatherElement.showName();
        if (showName) {
    
            xml.append("<").append(name);
            if (gatherElement.namespace() != null && !"".equals(gatherElement.namespace()))
                xml.append(" ").append(gatherElement.namespace());
            xml.append(">");
        }
        // Get sub tag name 
        String childName = gatherElement.childName();
        // If the child tag is empty, the parent tag will be used as the child tag 
        if (isEmpty(childName))
            childName = name;
        if (val instanceof Array) {
    
            // Processing array data 
            // Determine whether the sub tag needs to be parsed 
            if (!gatherElement.isBean()) {
    
                // Not for bean Object directly traverses the array , Splice each element 
                for (Array a : (Array[]) val) {
    
                    xml.append("<").append(childName).append(">").append(a).append("</").append(childName).append(">");
                }
            } else {
    
                // Every element is bean An array of objects 
                for (Object ob : (Array[]) val) {
    
                    // Assemble sub tag data 
                    beanOrMapEment(childName, xml, ob);
                }
            }
        } else if (val instanceof Collection) {
    
            // Processing set data 
            Collection list = (Collection) val;
            // Determine whether the aggregate sub tag needs to be parsed 
            if (!gatherElement.isBean()) {
    
                for (Object ob : list) {
    
                    xml.append("<").append(childName).append(">").append(ob).append("</").append(childName).append(">");
                }
            } else {
    
                for (Object ob : list) {
    
                    // Resolve child Tags 
                    beanOrMapEment(childName, xml, ob);
                }
            }
        }
        // If the parent label is displayed , Splice parent tag end tag 
        if (showName) {
    
            xml.append("</").append(name).append(">");
        }
    }

    /** *  Each element is bean or map Data splicing  * * @param childName * @param xml * @param ob */
    private static void beanOrMapEment(String childName, StringBuffer xml, Object ob) {
    
        xml.append("<").append(childName).append(">");
        // Do not process when empty 
        if (ob != null) {
    
            // Determine whether the elements in the set are map
            if (ob instanceof Map) {
    
                // analysis map data 
                mapToXmlString(xml, (Map) ob);
            } else {
    
                // analysis bean, Fetch element class object 
                Class clazz = ob.getClass();
                // Assembly element xml message 
                element(xml, clazz, ob);
            }
        }
        xml.append("</").append(childName).append(">");
    }

    /** * map Traversal assembly  * * @param xml * @param map */
    private static void mapToXmlString(StringBuffer xml, Map map) {
    
        if (map == null || map.size() == 0) {
    
            return;
        }
        for (Object key : map.keySet()) {
    
            xml.append("<").append(key).append(">").append(map.get(key)).append("</").append(key).append(">");
        }
    }

    /** * bean Attribute field content processing  * * @param field * @param xml * @param val */
    private static void beanELement(Field field, StringBuffer xml, Object val) {
    
        //1. obtain @XmlBeanElement annotation 
        XmlBeanElement xmlBeanElement = field.getAnnotation(XmlBeanElement.class);
        //2. Determine whether to parse the null value 
        if (!xmlBeanElement.analysisEmpty() && isEmpty(val)) {
    
            // If you do not parse the null value and the attribute value is null, you can directly return
            return;
        }
        //3. Splicing bean Object start label 
        String name = xmlBeanElement.name();
        // If the alias is empty, the field name has been spliced 
        if (name == null || "".equals(name)) {
    
            name = field.getName();
        }
        xml.append("<").append(name);
        //4. Determine whether the attribute has a namespace set 
        if (!"".equals(xmlBeanElement.namespace()) && xmlBeanElement.namespace() != null) {
    
            xml.append(" ").append(xmlBeanElement.namespace());
        }
        xml.append(">");
        //5. Determine whether the field is @XmlCDATA To modify 
        if (field.isAnnotationPresent(XmlCDATA.class)) {
    
            // Wrap the content 
            xml.append("<![CDATA[").append(val).append("]]>");
        }
        //6. Get the... Of the object field class object , Yes bean Object properties , If the field also contains bean Objects are processed recursively , By analogy 
        Class<?> fieldClass = field.getType();
        element(xml, fieldClass, val);
        //7. Splicing bean Object end tag 
        xml.append("</").append(xmlBeanElement.name()).append(">");
    }

    /** *  Basic type field content processing  * * @param field * @param xml * @param val */
    private static void propertyElement(Field field, StringBuffer xml, Object val) {
    
        //1. obtain @XmlPropertyELement
        XmlPropertyELement xmlPropertyELement = field.getAnnotation(XmlPropertyELement.class);
        //2/ Determine whether to resolve null values , If it is not resolved and the value is null, it returns 
        if (!xmlPropertyELement.analysisEmpty() && isEmpty(val)) {
    
            return;
        }
        //3. Attribute tag name splicing 
        // If the alias is empty, the field name has been spliced 
        String name = xmlPropertyELement.name();
        if (name == null || "".equals(name)) {
    
            name = field.getName();
        }
        xml.append("<").append(name);
        //4. Determine whether the attribute has a namespace set , Splice if present 
        if (!"".equals(xmlPropertyELement.namespace()) && xmlPropertyELement.name() != null) {
    
            xml.append(" ").append(xmlPropertyELement.namespace());
        }
        xml.append(">");
        /** * * <![CDATA[ *  Here you can display the characters you entered as they are , No resolution  xml * ]]> */
        //5. Splice attribute tag content 
        if (field.isAnnotationPresent(XmlCDATA.class)) {
    
            // If the field is @XmlCDATA Just use... For annotation <![CDATA[ Label content ]]> Wrap the label body 
            xml.append("<![CDATA[").append(val).append("]]>");
        } else {
    
            // If the field is not @XmlCDATA Annotation decoration directly splices the label body 
            xml.append(val);
        }
        //6. Splice attribute tag end tag 
        xml.append("</").append(xmlPropertyELement.name()).append(">");
    }

    /** *  Judge whether the object value is empty  * * @param val * @return */
    private static boolean isEmpty(Object val) {
    
        return val == null || "".equals(val);
    }

    /** *  Reflection gets the field value  * * @param field * @param obj * @return */
    private static Object getFieldVal(Field field, Object obj) {
    
        try {
    
            // Allow calling private methods 
            field.setAccessible(true);
            // Reflection gets the field value 
            return field.get(obj);
        } catch (IllegalAccessException e) {
    
            e.printStackTrace();
        }
        return null;
    }

}

4. Test use

1. Use steps

1. First of all, we need to use annotations to modify the content of xml Field bean

test01—FileDto

@Data
@XmlRootElement(name = "file",
        namespace = {
    "xmlns=\"urn:gsma:params:xml:ns:rcs:rcs:fthttp\"",
                "xmlns:x=\"urn:gsma:params:xml:ns:rcs:rcs:up:fthttpext\""})
public class FileDto {
    

    @XmlBeanElement(name = "file-info" ,namespace = "type=\"thumbnail\"")
    private FileInfoDto thumbFileInfoDto;

    @XmlBeanElement(name = "file-info", namespace = "file-disposition=\"file\"")
    private FileInfoDto fileInfoDto;

}
@Data
public class FileInfoDto {
    

    @XmlPropertyELement(name = "file-size")
    private String fileSize;

    @XmlPropertyELement(name = "file-name")
    private String fileName;

    @XmlPropertyELement(name = "content-type")
    private String contentType;

    @XmlPropertyELement(name = "x:branded-url")
    private String brandedUrl;

}

test02—OutboundMessageRequestDto

/** * <?xml version="1.0" encoding="UTF-8"?> * <msg:outboundMessageRequest xmlns:msg="urn:oma:xml:rest:netapi:messaging:1"> * <messageId>5eae954c-42ca-4181-9ab4-9c0ef2e2ac66</messageId> * <clientCorrelator>567895</clientCorrelator> * </msg:outboundMessageRequest> */
@Data
@XmlRootElement(name = "msg:outboundMessageRequest",namespace = "xmlns:msg=\"urn:oma:xml:rest:netapi:messaging:1\"")
public class OutboundMessageRequestDto {
    

    @XmlPropertyELement(name = "messageId")
    private String messageId;

    @XmlPropertyELement(name = "clientCorrelator")
    private String clientCorrelator;

    @XmlCDATA
    @XmlPropertyELement(name = "bodyText",analysisEmpty = true)
    private String bodyText;
}

test03-RequestDto

@Data
@XmlRootElement(name = "Request",namespace = "xmlns:msg=\"urn:oma:xml:rest:netapi:messaging:1\"")
public class RequestDto {
    

    @XmlPropertyELement(name = "address")
    private String address;

    @XmlGatherElement(name = "destinationAddress",childName = "phone")
    private List<String> destinationAddress;

    @XmlPropertyELement(name = "senderAddress")
    private String senderAddress;

    @XmlBeanElement(name = "Message")
    private MessageDto messageDto;

    @XmlPropertyELement(name = "clientCorrelator")
    private String clientCorrelator;

}
@Data
public class MessageDto {
    

    @XmlPropertyELement(name = "contentType")
    private String contentType;

    @XmlPropertyELement(name = "conversationID")
    private String conversationID;

    @XmlPropertyELement(name = "contributionID")
    private String contributionID;

    @XmlGatherElement(name = "reportRequest",showName = true,analysisEmpty = true)
    private List<String> reportRequest;

    @XmlBeanElement(name = "serviceCapability")
    private ServiceCapabilityDto serviceCapabilityDto;

    @XmlPropertyELement(name = "messageId")
    private String messageId;

    @XmlPropertyELement(name = "bodyText")
    private String bodyText;
}
@Data
public class ServiceCapabilityDto {
    

    @XmlPropertyELement(name = "capabilityId")
    private String capabilityId;

    @XmlPropertyELement(name = "version")
    private String version;
}

2. Test cases

public class MyTest {
    

    /**  One XML The document is divided into the following parts  1. The document statement  2. Elements  3. attribute  4. notes  5.CDATA District 、 Special characters  * <?xml version="1.0" encoding="UTF-8"?> * <file * xmlns="urn:gsma:params:xml:ns:rcs:rcs:fthttp" * xmlns:x="urn:gsma:params:xml:ns:rcs:rcs:up:fthttpext"> * <file-info type="thumbnail"> * <file-size>2048</file-size> * <content-type>png</content-type> * </file-info> * <file-info type="file" file-disposition="file"> * <file-size>1024</file-size> * <file-name>1.jpg</file-name> * <content-type>jpeg</content-type> * <x:branded-url>[alternative branded HTTP URL of the file]</x:branded-url> * </file-info> * </file> */
    @Test
    public void test01(){
    
        FileDto fileDto = new FileDto();

        FileInfoDto thumbFileInfo = new FileInfoDto();
        thumbFileInfo.setFileSize("2048");
        thumbFileInfo.setContentType("png");
        FileInfoDto fileInfoDto = new FileInfoDto();
        fileInfoDto.setFileSize("1024");
        fileInfoDto.setFileName("1.jpg");
        fileInfoDto.setContentType("jpeg");

        fileDto.setThumbFileInfoDto(thumbFileInfo);
        fileDto.setFileInfoDto(fileInfoDto);

        String xml = XmlUtil.generateXml(fileDto);
        System.out.println(xml);
    }

    /** * <?xml version=v1.0 encoding=utf-8?> * <msg:outboundMessageRequest * xmlns:msg="urn:oma:xml:rest:netapi:messaging:1"> * <messageId>c8c43a68-c7dd-44c6-b047-2e7e43d5d1b9</messageId> * <clientCorrelator>123456</clientCorrelator> * <bodyText> * <![CDATA[null]]> * </bodyText> * </msg:outboundMessageRequest> */
    @Test
    public void test02(){
    
        OutboundMessageRequestDto outboundMessageRequestDto = new OutboundMessageRequestDto();
        outboundMessageRequestDto.setMessageId(UUID.randomUUID().toString());
        outboundMessageRequestDto.setClientCorrelator("123456");
        outboundMessageRequestDto.setBodyText(null);
        String xml = XmlUtil.generateXml(outboundMessageRequestDto);
        System.out.println(xml);
    }

    /** * <?xml version="1.0" encoding="UTF-8"?> * <Request * xmlns:msg="urn:oma:xml:rest:netapi:messaging:1"> * <address>tel:+8619585550103</address> * <destinationAddress> * <phone>tel:+8619585550103</phone> * <phone>tel:+8619585550104</phone> * </destinationAddress> * <senderAddress>sip:[email protected]</senderAddress> * <Message> * <contentType> text/plain * </contentType> * <conversationID>XSFDEREW#%$^$%^^&^% * </conversationID> * <contributionID>SFF$#%$%%^%&^%THT * </contributionID> * <reportRequest>Delivered</reportRequest> * <reportRequest>Failed</reportRequest> * <serviceCapability> * <capabilityId>ChatbotSA</capabilityId> * <version>+g.gsma.rcs.botversion=&quot;#=1&quot;</version> * </serviceCapability> * <messageId>5eae954c-42ca-4181-9ab4-9c0ef2e2ac66</messageId> * <bodyText>Hello * </bodyText> * </Message> * <clientCorrelator>567895</clientCorrelator> * </Request> */
    @Test
    public void test03(){
    
        RequestDto requestDto = new RequestDto();
        requestDto.setAddress("tel:+8619585550103");
        List<String> destinationAddress = new ArrayList<String>(2);
        destinationAddress.add("tel:+8619585550103");
        destinationAddress.add("tel:+8619585550104");
        requestDto.setDestinationAddress(destinationAddress);
        requestDto.setSenderAddress("sip:[email protected]");

        MessageDto messageDto = new MessageDto();
        messageDto.setContentType("text/plain");
        messageDto.setConversationID(UUID.randomUUID().toString());
        messageDto.setContributionID(UUID.randomUUID().toString());
        List<String> reportRequest = null;
// reportRequest.add("Delivered");
// reportRequest.add("Failed");
        messageDto.setReportRequest(reportRequest);

        ServiceCapabilityDto serviceCapabilityDto = new ServiceCapabilityDto();
        serviceCapabilityDto.setCapabilityId("ChatbotSA");
        serviceCapabilityDto.setVersion("+g.gsma.rcs.botversion=&quot;#=1&quot;");
        messageDto.setServiceCapabilityDto(serviceCapabilityDto);
        messageDto.setMessageId(UUID.randomUUID().toString());
        messageDto.setBodyText("Hello");

        requestDto.setMessageDto(messageDto);
        requestDto.setClientCorrelator("567895");

        String xml = XmlUtil.generateXml(requestDto);
        System.out.println(xml);
    }
}

3. test result

test01

<?xml version=v1.0 encoding=utf-8?>
<file xmlns="urn:gsma:params:xml:ns:rcs:rcs:fthttp" xmlns:x="urn:gsma:params:xml:ns:rcs:rcs:up:fthttpext">
    <file-info type="thumbnail">
        <file-size>2048</file-size>
        <content-type>png</content-type>
    </file-info>
    <file-info file-disposition="file">
        <file-size>1024</file-size>
        <file-name>1.jpg</file-name>
        <content-type>jpeg</content-type>
    </file-info>
</file>

test02

<?xml version=v1.0 encoding=utf-8?>
<msg:outboundMessageRequest xmlns:msg="urn:oma:xml:rest:netapi:messaging:1">
    <messageId>50e1ba7a-dbb5-4f2f-8811-d81859d029ff</messageId>
    <clientCorrelator>123456</clientCorrelator>
    <bodyText>
        <![CDATA[null]]>
    </bodyText>
</msg:outboundMessageRequest>

test03

<?xml version=v1.0 encoding=utf-8?>
<Request xmlns:msg="urn:oma:xml:rest:netapi:messaging:1">
    <address>tel:+8619585550103</address>
    <destinationAddress>
        <phone>tel:+8619585550103</phone>
        <phone>tel:+8619585550104</phone>
    </destinationAddress>
    <senderAddress>sip:[email protected]</senderAddress>
    <Message>
        <contentType>text/plain</contentType>
        <conversationID>3869f2e2-aedf-424f-8928-3082dc284104</conversationID>
        <contributionID>f9d6b351-a245-4e79-b850-0700114361dd</contributionID>
        <reportRequest></reportRequest>
        <serviceCapability>
            <capabilityId>ChatbotSA</capabilityId>
            <version>+g.gsma.rcs.botversion=&quot;#=1&quot;</version>
        </serviceCapability>
        <messageId>35a43af0-df1d-4a0d-94a0-60492e520bc4</messageId>
        <bodyText>Hello</bodyText>
    </Message>
    <clientCorrelator>567895</clientCorrelator>
</Request>
原网站

版权声明
本文为[llp1110]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/161/202206101723426648.html