问题起源

自自动化测试用例接入云测平台后,出现一两条用例100%失败,出现以下现象(同一套测试代码):
1.本机eclipse、IntelliJ IDEA (windows 7)都可以跑通
2.执行自动化的虚拟机eclipse(windows)跑不通
3.执行自动化的虚拟机IntelliJ IDEA(windows server 2008 R2)可以跑通
4.通过jenkins集成跑不通


问题重现

  • 使用自动化的虚拟机eclipse发起1笔交易
  • 观察被测系统服务器的日志(公司通过splunk可以快捷查询)
2018-09-04 09:55:43,557 INFO  [tomcat-threads--33] (com.xx.xx.xx.api.impl.xx.pay(xx.java:110))- pki unseal cost120
2018-09-04 09:55:43,557 INFO  [tomcat-threads--33] (com.xx.xx.xx.api.impl.xx.pay(xx.java:116))- requestXml is <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<pay2BankOrder>
    <orderId>TA20180904095502884</orderId>
    <bankName>����</bankName>
    <branchName>�Ƕ�֧��</branchName>
    <creditName>�����</creditName>
    <mobile>13400053487</mobile>
    <bankAcctId>6222090253475890</bankAcctId>
    <amount>2000</amount>
    <province>����</province>
    <city>�Ͼ�</city>
    <remark>5�¹���</remark>
    <feeAction>1</feeAction>
</pay2BankOrder>

问题分析

由此可以断定为字符编码问题,于是回到自动化测试脚本,发现脚本中有多处出现字符编码,以下为测试代码片段:

private static String genPKIMsg(Map<String, String> data) {
        String currentTime = DateUtil.getCurrentDate(DateUtil.YYYYMMDDHHMMSS);
        String orderId = "TA" + currentTime + String.valueOf(Math.abs(new Random().nextInt()) % 1000);
        data.put("orderId", orderId);
        Pay2bankOrder pay2bankOrder = new Pay2bankOrder();
        CommonUtil.copyProperties(pay2bankOrder, data);
        String reqOriginalXml = XmlUtil.convertToXml(pay2bankOrder, "UTF-8");  // 第1次编码
        Reporter.log("请求明文报文: " + reqOriginalXml);
        // 加签、加密
        Mpf mpf = new Mpf();
        mpf.setFeatureCode(CommonUtil.getDataDrivenValue(data, "featureCode"));
        mpf.setMemberCode(CommonUtil.getDataDrivenValue(data, "memberCode"));
        SealedData sealedData = null;
        try {
            ICryptoService service = CryptoServiceFactory.createCryptoService();
            sealedData = service.seal(mpf, reqOriginalXml.getBytes());    // 第2次编码
        } catch (Exception e) {
            Reporter.FALSE(e.getMessage());
        }
        Pay2bankRequest request = genRequest(data.get("memberCode"));
        byte[] nullByte = {};
        byte[] byteOri = sealedData.getOriginalData() == null ? nullByte : sealedData.getOriginalData();
        byte[] byteEnc = sealedData.getEncryptedData() == null ? nullByte : sealedData.getEncryptedData();
        byte[] byteEnv = sealedData.getDigitalEnvelope() == null ? nullByte : sealedData.getDigitalEnvelope();
        byte[] byteSig = sealedData.getSignedData() == null ? nullByte : sealedData.getSignedData();
        request.getRequestBody().getSealDataType().setOriginalData(PKIUtil.byte2UTF8StringWithBase64(byteOri));   // 第3次编码
request.getRequestBody().getSealDataType().setSignedData(PKIUtil.byte2UTF8StringWithBase64(byteSig));
request.getRequestBody().getSealDataType().setEncryptedData(PKIUtil.byte2UTF8StringWithBase64(byteEnc));
request.getRequestBody().getSealDataType().setDigitalEnvelope(PKIUtil.byte2UTF8StringWithBase64(byteEnv));

        String requestXml = XmlUtil.convertToXml(request, "UTF-8");   // 第4次编码
        Reporter.log("请求加密报文: " + requestXml);
        return requestXml;
    }

我是从下往上分析,既然服务器接收的是乱码的请求,那我就先把加密报文拿出来分析一下,以下为加密报文:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<pay2BankRequest>
    <pay2bankHead>
        <version>1.0</version>
        <memberCode>10013538466</memberCode>
    </pay2bankHead>
    <requestBody>
        <sealDataType>
            <originalData>PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9InllcyI/Pgo8cGF5MkJhbmtPcmRlcj4KICAgIDxvcmRlcklkPlRBMjAxODA5MDQwOTE0MjQzNzI8L29yZGVySWQ+CiAgICA8YmFua05hbWU+1dDQ0DwvYmFua05hbWU+CiAgICA8YnJhbmNoTmFtZT6zx7ar1qfQ0DwvYnJhbmNoTmFtZT4KICAgIDxjcmVkaXROYW1lPrqryum+6jwvY3JlZGl0TmFtZT4KICAgIDxtb2JpbGU+MTM0MDAwNTM0ODc8L21vYmlsZT4KICAgIDxiYW5rQWNjdElkPjYyMjIwOTAyNTM0NzU4OTA8L2JhbmtBY2N0SWQ+CiAgICA8YW1vdW50PjIwMDA8L2Ftb3VudD4KICAgIDxwcm92aW5jZT69rcvVPC9wcm92aW5jZT4KICAgIDxjaXR5PsTPvqk8L2NpdHk+CiAgICA8cmVtYXJrPjXUwrmk18o8L3JlbWFyaz4KICAgIDxmZWVBY3Rpb24+MTwvZmVlQWN0aW9uPgo8L3BheTJCYW5rT3JkZXI+Cg==</originalData>
            <signedData>KE+NCx1uXlvgKE5hpXd50A==</signedData>
            <encryptedData></encryptedData>
            <digitalEnvelope></digitalEnvelope>
        </sealDataType>
    </requestBody>
</pay2BankRequest>

里面主要有两个字段加密了,分别为originalDatasignedData,而这两个字段均为new String(Base64.encodeBase64(bytes), 'utf-8')加密的,那我就先用工具对originalDatabase64解密一下,出现为:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<pay2BankOrder>
    <orderId>TA20180904091424372</orderId>
    <bankName>ÕÐÐÐ</bankName>
    <branchName>³Ç¶«Ö§ÐÐ</branchName>
    <creditName>º«Êé¾ê</creditName>
    <mobile>13400053487</mobile>
    <bankAcctId>6222090253475890</bankAcctId>
    <amount>2000</amount>
    <province>½­ËÕ</province>
    <city>ÄϾ©</city>
    <remark>5Ô¹¤×Ê</remark>
    <feeAction>1</feeAction>
</pay2BankOrder>

乱码了,而byteOri的数据从sealedData = service.seal(mpf, reqOriginalXml.getBytes());来的,于是我就去研究了下getBytes()方法,以下为官方javadoc

/**
     * Encodes this {@code String} into a sequence of bytes using the
     * platform's default charset, storing the result into a new byte array.
     *
     * <p> The behavior of this method when this string cannot be encoded in
     * the default charset is unspecified.  The {@link
     * java.nio.charset.CharsetEncoder} class should be used when more control
     * over the encoding process is required.
     *
     * @return  The resultant byte array
     *
     * @since      JDK1.1
     */
    public byte[] getBytes() {
	return StringCoding.encode(value, offset, count);
    }

大致意思是String的getBytes()方法是得到一个操作系统默认的编码格式的字节数组,也就是说在不同的操作系统返回的不一样,参考链接:https://www.cnblogs.com/jiayouxiage/p/6120604.html

问题修正

sealedData = service.seal(mpf, reqOriginalXml.getBytes());

改为

sealedData = service.seal(mpf, reqOriginalXml.getBytes("UTF-8"));

原因:既然后面使用new String(Base64.encodeBase64(bytes), 'utf-8')utf-8还原数据,那前面也要使用utf-8处理数据,因为受操作系统影响,那我们就强制加上utf-8,加上重新跑一下,用例正常

遗留一个疑问:

如果说操作系统影响了编码,我本地的eclipse能跑通(windows7),那为何虚拟机的idea也能跑通,难道idea对于字符处理更高级?

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐