运之盟开放平台


请求示例代码-JAVA版

<pre><code>package com.yzm.center.open.gateway; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ByteUtil; import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.bouncycastle.jce.provider.BouncyCastleProvider; import javax.crypto.Cipher; import java.io.Serializable; import java.security.*; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.*; import java.util.function.BiFunction; @Slf4j public class SignTest { private static String url = &amp;quot;https://open-beta.56yzm.com:8443&amp;quot;; private static String clientId = &amp;quot;分配的clientId&amp;quot;; private static String clientSecret = &amp;quot;管理员分配的clientSecret&amp;quot;; private static String sign_secret_key = &amp;quot;管理员分配的sign_secret_key&amp;quot;; private static String sign_public_key = &amp;quot;管理员分配的sign_public_key&amp;quot;; private static String ivs_public_key = &amp;quot;管理员分配的ivs_public_key&amp;quot;; private static String userCode = &amp;quot;管理员分配的user-code&amp;quot;; private static List&amp;lt;String&amp;gt; fields = Arrays.asList(&amp;quot;idCardNum&amp;quot;, &amp;quot;phone&amp;quot;, &amp;quot;password&amp;quot;, &amp;quot;idCardNo&amp;quot;, &amp;quot;idCard&amp;quot;, &amp;quot;qualificationCardNo&amp;quot;, &amp;quot;bankIdCardNo&amp;quot;, &amp;quot;legalPersonIdCard&amp;quot;, &amp;quot;bankPhone&amp;quot;, &amp;quot;adminPhone&amp;quot;, &amp;quot;financeContactTel&amp;quot;, &amp;quot;businessContactTel&amp;quot;, &amp;quot;userPhone&amp;quot;, &amp;quot;payeePhone&amp;quot;, &amp;quot;driverPhone&amp;quot;, &amp;quot;oldPayPwd&amp;quot;, &amp;quot;newPayPwd&amp;quot;, &amp;quot;confirmPayPwd&amp;quot;); public static void main(String[] args) { String apiUrl = String.format(&amp;quot;%s%s&amp;quot;, url, &amp;quot;/api-server/openapi/vehicle/v2/createVehicle&amp;quot;); String reqJsonStr = &amp;quot;{\&amp;quot;carInfoDto\&amp;quot;:{\&amp;quot;carDrivingCopyAddImg\&amp;quot;:\&amp;quot;https://file.rlsk.link/12961571/fb14799fda39444d978972d9b85204f3.jpeg\&amp;quot;,\&amp;quot;carDrivingCopyImg\&amp;quot;:\&amp;quot;https://file.rlsk.link/12961571/fb14799fda39444d978972d9b85204f3.jpeg\&amp;quot;,\&amp;quot;carDrivingFrontImg\&amp;quot;:\&amp;quot;https://file.rlsk.link/12961571/fb14799fda39444d978972d9b85204f3.jpeg\&amp;quot;,\&amp;quot;carLength\&amp;quot;:11,\&amp;quot;carTypesOf\&amp;quot;:17,\&amp;quot;plateColor\&amp;quot;:\&amp;quot;YELLOW\&amp;quot;,\&amp;quot;plateNo\&amp;quot;:\&amp;quot;晋K62255\&amp;quot;,\&amp;quot;roadTransportPermImg\&amp;quot;:\&amp;quot;https://file.rlsk.link/12961571/fb14799fda39444d978972d9b85204f3.jpeg\&amp;quot;,\&amp;quot;trailerCarDrivingCopyImg\&amp;quot;:\&amp;quot;https://file.rlsk.link/12961571/fb14799fda39444d978972d9b85204f3.jpeg\&amp;quot;,\&amp;quot;trailerCarDrivingFrontImg\&amp;quot;:\&amp;quot;https://file.rlsk.link/12961571/fb14799fda39444d978972d9b85204f3.jpeg\&amp;quot;,\&amp;quot;trailerNo\&amp;quot;:\&amp;quot;晋K6U92挂\&amp;quot;,\&amp;quot;useCharacter\&amp;quot;:\&amp;quot;货运\&amp;quot;},\&amp;quot;driverInfoDto\&amp;quot;:{\&amp;quot;driverName\&amp;quot;:\&amp;quot;孟庆祯\&amp;quot;,\&amp;quot;drivingCopyImg\&amp;quot;:\&amp;quot;https://file.rlsk.link/12961571/fb14799fda39444d978972d9b85204f3.jpeg\&amp;quot;,\&amp;quot;drivingImg\&amp;quot;:\&amp;quot;https://file.rlsk.link/12961571/fb14799fda39444d978972d9b85204f3.jpeg\&amp;quot;,\&amp;quot;idCardBackImg\&amp;quot;:\&amp;quot;https://file.rlsk.link/12961571/fb14799fda39444d978972d9b85204f3.jpeg\&amp;quot;,\&amp;quot;idCardFrontImg\&amp;quot;:\&amp;quot;https://file.rlsk.link/12961571/fb14799fda39444d978972d9b85204f3.jpeg\&amp;quot;,\&amp;quot;idCardNo\&amp;quot;:\&amp;quot;429006199005131414\&amp;quot;,\&amp;quot;phone\&amp;quot;:\&amp;quot;15113151415\&amp;quot;,\&amp;quot;qualificationCardImg\&amp;quot;:\&amp;quot;https://file.rlsk.link/12961571/fb14799fda39444d978972d9b85204f3.jpeg\&amp;quot;},\&amp;quot;yzmUserCode\&amp;quot;:\&amp;quot;YZM_9dO0fhTtOt\&amp;quot;}&amp;quot;; Security.addProvider(new BouncyCastleProvider()); //把请求的原始参数,进行加密(有需要加密的字段,才进行加密) String reqJson = startHandleJsonFields(reqJsonStr, ivs_public_key, fields, 1); String token = getYzmToken(clientId, clientSecret); YzmSignDto yzmSign = getSign(sign_secret_key, reqJson); YzmResultBasicDto yzmResultBasicDto = postToYzm(YzmResultBasicDto.class, apiUrl, token, yzmSign, reqJson, userCode); System.out.println(JSONObject.toJSONString(yzmResultBasicDto)); } /** * 获取 token * * @param clientId * @param clientSecret * @return */ public static String getYzmToken(String clientId, String clientSecret) { HttpResponse httpResponse = HttpUtil .createPost(String.format(&amp;quot;%s%s&amp;quot;, url, &amp;quot;/open-oauth/oauth/token&amp;quot;)) .contentType(&amp;quot;application/x-www-form-urlencoded&amp;quot;) .form(&amp;quot;grant_type&amp;quot;, &amp;quot;client_credentials&amp;quot;) .form(&amp;quot;client_id&amp;quot;, clientId) .form(&amp;quot;client_secret&amp;quot;, clientSecret) .form(&amp;quot;scope&amp;quot;, &amp;quot;read&amp;quot;) .execute(); if (httpResponse.getStatus() != 200) { log.error(&amp;quot;运之盟-开放平台登录异常:{}&amp;quot;, httpResponse.getStatus()); } String body = httpResponse.body(); YzmTokenDto yzmTokenDto = JSONObject.parseObject(body, YzmTokenDto.class); return yzmTokenDto.getAccessToken(); } /** * 生成签名 * * @param signSecretKey 加密SecretKey * @param jsonStr 待签名参数 json * @return */ private static YzmSignDto getSign(String signSecretKey, String jsonStr) { try { long timeMillis = System.currentTimeMillis(); long nonce = generateNonce(); String sortJsonByKey = sortJsonByKey(jsonStr); String signString = sign(signSecretKey, sortJsonByKey.getBytes(), nonce, timeMillis); YzmSignDto yzmSignDto = new YzmSignDto(); yzmSignDto.setSign(signString); yzmSignDto.setTimeMillis(timeMillis); yzmSignDto.setNonce(nonce); return yzmSignDto; } catch (Exception e) { log.error(&amp;quot;获取签名异常:&amp;quot; + e); return null; } } /** * 验证签名 * * @param pkStr 公钥字符串 * @param data 数据(json数据通过 sortJsonByKey 方法排序的 json 数据) * @param sign sign 签名 * @param nonce 现时标志 * @param timestamp 时间戳 * @return boolean * @throws NoSuchAlgorithmException * @throws InvalidKeyException * @throws SignatureException */ public boolean verifySignature(String pkStr, byte[] data, String sign, long nonce, long timestamp) { PublicKey publicKey = publicKeyFromString(pkStr); Signature signature = null; try { signature = Signature.getInstance(&amp;quot;SHA256withECDSA&amp;quot;); signature.initVerify(publicKey); if (!isTimestampValid(timestamp)) { log.error(&amp;quot;无效签名 , timestamp&amp;quot;); return false; } // 将原始数据、时间戳和nonce结合在一起进行验签 signature.update(data); signature.update(ByteUtil.longToBytes(timestamp)); signature.update(ByteUtil.longToBytes(nonce)); return signature.verify(Base64.getDecoder().decode(sign)); } catch (NoSuchAlgorithmException e) { log.error(&amp;quot;请确保所请求的签名算法为SHA256withECDSA算法,并且必要的加密提供者可用&amp;quot;); return false; } catch (InvalidKeyException e) { log.error(&amp;quot;提供的密钥无效。请使用正确的EC密钥&amp;quot;); return false; } catch (SignatureException e) { log.error(&amp;quot;签名验证异常,请检查密钥是否正确, 请求体是否完整, 签名算法是否一致&amp;quot;); return false; } } /** * 时间戳是否有效 * 和服务器时间作对比, 10分钟以内则有效 * * @param timestamp 时间戳 * @return boolean */ private boolean isTimestampValid(long timestamp) { long currentTime = System.currentTimeMillis(); long difference = Math.abs(currentTime - timestamp); return difference &amp;lt;= 10 * 60 * 1000; // 10分钟 } /** * 递归 对 json 字符串 进行 加解密 * * @param jsonStr 待加解密 字符串 * @param key 公钥 * @param fields 需要加密的字段名称 * @param type 加解密类型 1:加密 2:解密 * @return */ public static String startHandleJsonFields(String jsonStr, String key, List&amp;lt;String&amp;gt; fields, int type) { if (CollUtil.isEmpty(fields)) { return jsonStr; } Object json = JSON.parse(jsonStr); BiFunction function = null; switch (type) { case 1: function = (key1, json1) -&amp;gt; encrypt((String) key1, (String) json1); break; case 2: function = (key1, json1) -&amp;gt; decrypt((String) key1, (String) json1); break; default: break; } Object processedJson = handleJsonFieldsDeep(json, fields, key, function); return JSON.toJSONString(processedJson); } /** * 递归深度解密json字段 * * @param json json * @param fields 字段 * @param key 他私钥 * @return {@link Object} */ private static Object handleJsonFieldsDeep(Object json, List&amp;lt;String&amp;gt; fields, String key, BiFunction&amp;lt;String, String, String&amp;gt; function) { if (json instanceof JSONObject) { JSONObject jsonObject = (JSONObject) json; Map&amp;lt;String, Object&amp;gt; processedMap = new LinkedHashMap&amp;lt;&amp;gt;(); for (String jsonKey : jsonObject.keySet()) { Object value = jsonObject.get(jsonKey); if (fields.contains(jsonKey) &amp;amp;&amp;amp; value instanceof String) { // 加/解密 value = new String(function.apply(key, (String) value)); } else if (value instanceof JSONObject || value instanceof JSONArray) { value = handleJsonFieldsDeep(value, fields, key, function); } processedMap.put(jsonKey, value); } return processedMap; } else if (json instanceof JSONArray) { JSONArray jsonArray = (JSONArray) json; for (int i = 0; i &amp;lt; jsonArray.size(); i++) { jsonArray.set(i, handleJsonFieldsDeep(jsonArray.get(i), fields, key, function)); } return jsonArray; } return json; } /** * 使用私钥解密数据。 */ public static String decrypt(String skStr, String encryptedData) { try { PrivateKey privateKey = privateKeyFromString(skStr); Cipher cipher = Cipher.getInstance(&amp;quot;ECIES&amp;quot;); cipher.init(Cipher.DECRYPT_MODE, privateKey); return new String(cipher.doFinal(Base64.getDecoder().decode(encryptedData)), &amp;quot;utf-8&amp;quot;); } catch (Exception e) { log.error(&amp;quot;解密错误&amp;quot;); return null; } } /** * 敏感字段-请求字符串加密 * * @param pkStr 公钥 * @param str 待加密字符串 * @return */ public static String encrypt(String pkStr, String str) { try { byte[] plaintext = str.getBytes(); PublicKey publicKey = publicKeyFromString(pkStr); Cipher cipher = Cipher.getInstance(&amp;quot;ECIES&amp;quot;); cipher.init(Cipher.ENCRYPT_MODE, publicKey); return Base64.getEncoder().encodeToString(cipher.doFinal(plaintext)); } catch (Exception e) { log.error(&amp;quot;字段加密报错&amp;quot;); return null; } } /** * 来自字符串公钥 * * @param keyStr 关键str * @return {@link PublicKey} */ public static PublicKey publicKeyFromString(String keyStr) { try { byte[] decodedKey = Base64.getDecoder().decode(keyStr); X509EncodedKeySpec spec = new X509EncodedKeySpec(decodedKey); KeyFactory keyFactory = KeyFactory.getInstance(&amp;quot;EC&amp;quot;); return keyFactory.generatePublic(spec); } catch (Exception e) { log.error(&amp;quot;字段报错&amp;quot;); return null; } } /** * @param clazz 待转换类型 * @param apiUrl 请求地址 * @param token token * @param yzmSign 当前毫秒时间戳 * @param json body * @param &amp;lt;T&amp;gt; 返回类型 * @return */ private static &amp;lt;T&amp;gt; T postToYzm(Class&amp;lt;T&amp;gt; clazz, String apiUrl, String token, YzmSignDto yzmSign, String json, String userCode) { long startTime = System.currentTimeMillis(); log.info(&amp;quot;运之盟开放平台-请求参数:{}&amp;quot;, json); HttpResponse authorization = HttpUtil.createPost(apiUrl) .contentType(&amp;quot;application/json&amp;quot;) .header(&amp;quot;Authorization&amp;quot;, &amp;quot;Bearer &amp;quot; + token) .header(&amp;quot;X-Timestamp&amp;quot;, String.valueOf(yzmSign.getTimeMillis())) .header(&amp;quot;X-Nonce&amp;quot;, String.valueOf(yzmSign.getNonce())) .header(&amp;quot;X-Signature&amp;quot;, yzmSign.getSign()) .header(&amp;quot;user-code&amp;quot;, userCode) .body(json) .execute(); //验证结果 if (authorization.getStatus() != 200) { log.error(&amp;quot;请求异常&amp;quot;); } long endTime = System.currentTimeMillis(); log.info(&amp;quot;运之盟开放平台-耗时:{},返回参数:{}&amp;quot;, endTime - startTime, authorization.body()); return JSONObject.parseObject(authorization.body(), clazz); } private static long generateNonce() { long minimum = 100000000000000000L; // 10^17 long maximum = 999999999999999999L; // Just below 10^18 return minimum + ((long) (new SecureRandom().nextDouble() * (maximum - minimum))); } /** * 签名 * * @param skStr 私钥字符串 * @param data 数据byte数组 * @param nonce 现时标志 * @param timestamp 时间戳 * @return {@link String} base64 的签名 */ public static String sign(String skStr, byte[] data, long nonce, long timestamp) throws Exception { try { // 私钥 base64 String 转真实 私钥 PrivateKey privateKey = privateKeyFromString(skStr); Signature signature = Signature.getInstance(&amp;quot;SHA256withECDSA&amp;quot;); signature.initSign(privateKey); // 将原始数据、时间戳和nonce结合在一起进行签名 signature.update(data); signature.update(ByteUtil.longToBytes(timestamp)); signature.update(ByteUtil.longToBytes(nonce)); // 使用Base64 将真实签名转换位字符串 return Base64.getEncoder().encodeToString(signature.sign()); } catch (Exception e) { log.error(&amp;quot;签名错误&amp;quot;); return null; } } /** * 字符串转私钥 * * @param keyStr 关键str * @return {@link PrivateKey} * @throws Exception 异常 */ public static PrivateKey privateKeyFromString(String keyStr) throws Exception { byte[] decodedKey = Base64.getDecoder().decode(keyStr); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decodedKey); KeyFactory keyFactory = KeyFactory.getInstance(&amp;quot;EC&amp;quot;); return keyFactory.generatePrivate(spec); } /** * 排序json参数 * * @param jsonStr json str * @return {@link String} */ public static String sortJsonByKey(String jsonStr) { Object json = JSON.parse(jsonStr); Object sortedJson = sortObjectByKey(json); return JSON.toJSONString(sortedJson); } /** * 递归 排序json对象 参数, 私有方法, 不对外开放 * * @param json json * @return {@link Object} */ private static Object sortObjectByKey(Object json) { if (json instanceof JSONObject) { JSONObject jsonObject = (JSONObject) json; Map&amp;lt;String, Object&amp;gt; sortedMap = new LinkedHashMap&amp;lt;&amp;gt;(); jsonObject.keySet().stream() .sorted() // 默认的字符串排序,即字典序 (a-z) .forEach(key -&amp;gt; sortedMap.put(key, sortObjectByKey(jsonObject.get(key)))); return sortedMap; } else if (json instanceof JSONArray) { JSONArray jsonArray = (JSONArray) json; for (int i = 0; i &amp;lt; jsonArray.size(); i++) { jsonArray.set(i, sortObjectByKey(jsonArray.get(i))); } return jsonArray; } return json; } @Data public static class YzmSignDto { public String sign; public long timeMillis; public long nonce; } @Data public static class YzmResultBasicDto&amp;lt;T&amp;gt; implements Serializable { public Integer code; public String info; public T data; } @Data public static class YzmTokenDto { public String accessToken; public String tokenType; public Long expiresIn; public String scope; } } </code></pre>

页面列表

ITEM_HTML