文章目錄
- 1 問題背景
- 2 思路
- 3 代碼實現
1 問題背景
在發起支付的時候,一般都需要對發送的請求參數進行加密或者簽名,下文簡稱這個過程為“簽名”。行業內比較普遍的簽發算法有:
(1)按支付渠道給定的字段排序進行拼接,最后再拼一個密鑰,形成一個待簽名的字符串tobeSign
,然后對這個tobeSign
進行MD5編碼。比如MD5(商戶號+子應用ID+商戶訂單號+流水號+金額+幣種+密鑰)
;
(2)針對請求參數中的字段(僅針對第一層的字段,不需要針對字段里面的字段,即不需要遞歸),進行字典升序排序,用格式key=value
和&
連接符拼接,最后再拼一個密鑰,再用MD5編碼比如MD5(a=value1&b=value2&key=密鑰)
;
這次遇到一種比較有趣的簽名算法,筆者認為是基于第(2)的變種,渠道方要求針對請求參數中的字段,如果該字段是對象類型,那么該字段里面的字段也要按字典升序排序進行拼接,相當于是遞歸字典升序,困難度有一點提升
2 思路
文字描述得有點抽象,可以結合第3小節的代碼實現來看
遍歷每一層字段,都用一個容器存起來,要按字典升序存
。維護一個層序遍歷的容器——雙向隊列
。將前面升序的數據入隊。遍歷隊列的每一元素,元素從隊頭出隊,再遍歷元素中的字段是否是對象類型或者數組類型,使用一個容器存起來,要按字典降序存
,存完后使用頭插法入隊。使用頭插法倒敘
入隊,每一次從隊頭遍歷,那么每一次遍歷都是升序遍歷。
3 代碼實現
解釋:代碼中的
BeansUtil.bean2MapIgnoreEmptyStr()
是將對象轉成一個Map。SymbolConstant.EQUAL
的值是一個=
,SymbolConstant.AND
的值是一個&
public static String buildToBeSignStr(Object payReq) {// 將對象轉成一個MapMap<String, String> map = BeansUtil.bean2MapIgnoreEmptyStr(payReq);TreeMap<String, String> treeMap = new TreeMap<>(map);List<String> result = new LinkedList<>();for (Map.Entry<String, String> entry : treeMap.entrySet()) {// 層序遍歷容器Deque<Map.Entry<String, String>> bfsHolder = new LinkedList<>();// 結果暫存容器List<String> tmpResult = new LinkedList<>();// 入隊bfsHolder.offer(entry);while (CollectionUtils.isNotEmpty(bfsHolder)) {Map.Entry<String, String> pollEntry = bfsHolder.poll();String pKey = pollEntry.getKey();String pVal = pollEntry.getValue();if (StringUtils.isNotBlank(pVal) && JSONValidator.from(pVal).validate()) {// 是json串,仍需要繼續解析log.info("value of key:{} is json str.", pKey);// 解析JSON字符串Object parsedObject = JSON.parse(pVal);boolean isJSONObject = parsedObject instanceof JSONObject;boolean isJSONArray = parsedObject instanceof JSONArray;if (isJSONObject || isJSONArray) {Map<String, String> map1 = null;if (isJSONObject) {log.info("JSON字符串是一個對象");JSONObject jsonObject = (JSONObject) parsedObject;// 處理對象map1 = BeansUtil.buildMapFromJsonStr(pVal);} else if (isJSONArray) {System.out.println("JSON字符串是一個數組");JSONArray jsonArray = (JSONArray) parsedObject;// 處理數組for (Object o : jsonArray) {map1 = BeansUtil.bean2MapIgnoreEmptyStr(o);}}if (MapUtils.isNotEmpty(map1)) {// 倒敘排序Map<String, String> treeMap1 = new TreeMap<>(Comparator.reverseOrder());treeMap1.putAll(map1);// 插入到隊頭Streams.of(treeMap1.entrySet()).forEach(bfsHolder::offerFirst);}} else {tmpResult.add(pKey + SymbolConstant.EQUAL + pVal);}} else {tmpResult.add(pKey + SymbolConstant.EQUAL + pVal);}}if (CollectionUtils.isNotEmpty(tmpResult)) {String tmpResultStr = String.join(SymbolConstant.AND, tmpResult);result.add(tmpResultStr);}}if (CollectionUtils.isNotEmpty(result)) {return String.join(SymbolConstant.AND, result);}return "";}