問題起源
自自動化測試用例接入雲測平臺後,出現一兩條用例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>
裏面主要有兩個字段加密了,分別爲originalData
和signedData
,而這兩個字段均爲new String(Base64.encodeBase64(bytes), 'utf-8')
加密的,那我就先用工具對originalData
base64解密一下,出現爲:
<?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對於字符處理更高級?