• 用于加签验签的加解密算法


    
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONObject;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang3.StringUtils;
    
    import javax.crypto.Cipher;
    import javax.crypto.KeyGenerator;
    import javax.crypto.Mac;
    import javax.crypto.SecretKey;
    import javax.crypto.spec.SecretKeySpec;
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.InputStream;
    import java.nio.charset.StandardCharsets;
    import java.security.*;
    import java.security.spec.PKCS8EncodedKeySpec;
    import java.security.spec.X509EncodedKeySpec;
    import java.util.*;
    
    @Slf4j
    public class XYDEncryptUtils {
    
        /**
         * MAC算法可选以下多种算法
         * 
         * SHA
         * RSA
         * HmacMD5
         * HmacSHA1
         * HmacSHA256
         * HmacSHA384
         * HmacSHA512
         * MD5withRSA
         * SHA256WithRSA
         * 
    */
    public static final String KEY_SHA = "SHA"; public static final String CHAR_ENCODING = "UTF-8"; public static final String KEY_SH1 = "SHA-1"; public static final String KEY_RSA = "RSA"; public static final String KEY_MD5 = "MD5"; public static final String KEY_MAC = "HmacMD5"; public static final String KEY_SHA_RSA = "SHA1withRSA"; public static final String RSA_ALGORITHM = "RSA/ECB/PKCS1Padding"; public static final String KEY_MD5_RSA = "MD5withRSA"; public static final String KEY_SHA256_RSA = "SHA256WithRSA"; private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"}; //此处为模拟加验签流程,假设双方密钥都用这一套 public static void main(String[] args) throws Exception { final String privateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMvaEt+AGnOWX5kn80j+w06M321d2zqoS9thOZtvYPWWjSBpPhWrQjajGh1PXrksqCA3dP0axQcFa9FM1Y0EBHsnbRnFvHS+DgjvhOodqFq4dTYeqhsODWKbSjvl/TVy1k3qI6CbnexckY74oTh8CEKGVUOrFqbykpz1LzWzDhjTAgMBAAECgYBmOeDXBeW96z3bjyY4h9GndYuhy7xhqP4JcLMz2GHWJn51L3G76hsVTSuOYTwOPm+9YDIhryf5AmX0GsTIp6gauG1Hc8Mrp+MBuFfYINU4SrSUibGFFFRUQMdA2n+SWCezw5eNDMw95gZv8zwTvxNLHcUitDX3wbH8BydwkPYT+QJBAPdgVcnQg2w3yvrquMrKbQm38UGymzQClxUW8RXLYHdBJ0Urr1R6ngzP5NyYZqBCZ3SIxiAH7/gkQpsZYx7GOgcCQQDS9U//u871wrGOTJcwUWnqvg3IKSat62ojyNw0gvXRPyP8ZQ9Nr8J7MBR8E/tv9XqYx7I6cHb3nQGNGjnNQWfVAkAwJxNr7me2qAGd6BTCfLbO3nxa0n59mEd6dnXFTUpgzFDtmPxPEHM1gT5GZnALoAFtYLZYQ9NFVSGV+VIfCmz/AkB87RzU2skyrHstQ5n82RmaE7ZhpEztjY2rZ1VQG4WqxnNvEgHABadsAAsBd2vc30gclZWsGdG5zNaQUsmMDYGJAkEA7srJlIGWKxu/btMry1qKSwcp3QiIj5muU5oZ8HgtTn5yhsNQKMtSpu1kQNJMlX4sLxFZs1LSoHi4NjmFWGkr+w=="; final String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDL2hLfgBpzll+ZJ/NI/sNOjN9tXds6qEvbYTmbb2D1lo0gaT4Vq0I2oxodT165LKggN3T9GsUHBWvRTNWNBAR7J20Zxbx0vg4I74TqHahauHU2HqobDg1im0o75f01ctZN6iOgm53sXJGO+KE4fAhChlVDqxam8pKc9S81sw4Y0wIDAQAB"; String str = "{\"param\":{\"auth\":{\"businessLicense\":\"xxx\",\"mobile\":\"xxx\",\"thirdUserNo\":\"xxx\"},\"platType\":\"H5\",\"platformCode\":\"接口文档提供\",\"productNo\":\"接口文档提供\"},\"resp\":{\"code\":0,\"data\":\"https://apis.xyd.cn/titan-frontend/startPage?relatedNo=xxx&outerUserNo=xxx&orgCode=xxx\",\"message\":\"ok\"}}"; JSONObject json = JSONObject.parseObject(str); String bizParam = json.getJSONObject("param").toJSONString(); //timestamp为当前时间戳,此处为测试验证验签流程,设置为固定值 // String timestamp = String.valueOf(System.currentTimeMillis()); String timestamp = "1688710642315"; // 加签 String signData = XYDEncryptUtils.getMd5SignData(timestamp, bizParam); String sign = XYDEncryptUtils.sign(signData.getBytes(), privateKey, XYDEncryptUtils.KEY_SHA_RSA); Map<String, String> header = getHeader(sign, timestamp); //最终发送的post请求头为header,body为bizParam //对响应数据验签 String timestampResp = header.get("XS-TIMESTAMP"); String signResp = header.get("XS-SIGN"); String body = bizParam; boolean verify = XYDEncryptUtils.verify(publicKey, timestampResp, body, signResp); log.info("验签结果: {}" , verify); } private static Map<String, String> getHeader(String sign, String timestamp) { Map<String, String> header = new HashMap(); header.put("XS-ACCESSKEY", "文档提供的accessKey"); header.put("XS-SIGN", sign); header.put("XS-TIMESTAMP", timestamp); return header; } /** * BASE64解密 * * @param key * @return * @throws Exception */ public static byte[] decryptBASE64(String key) { return Base64.getDecoder().decode(key); } /** * BASE64加密 * * @param key * @return * @throws Exception */ public static String encryptBASE64(byte[] key) { return Base64.getEncoder().encodeToString(key); } /** * 初始化HMAC密钥 * * @return * @throws Exception */ public static String initMacKey() throws Exception { KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_MAC); SecretKey secretKey = keyGenerator.generateKey(); return encryptBASE64(secretKey.getEncoded()); } /** * MAC加密 * * @param data * @param key * @return * @throws Exception */ public static byte[] encryptHMAC(byte[] data, String key) throws Exception { SecretKey secretKey = new SecretKeySpec(decryptBASE64(key), KEY_MAC); Mac mac = Mac.getInstance(secretKey.getAlgorithm()); mac.init(secretKey); byte[] bytes = mac.doFinal(data); return bytes; } /** * @param origin 加密数据 * @param charset 字符编码 * @param algorithm 加密算法 * @return */ public static String encode(String origin, String charset, String algorithm) { try { byte[] digest = encode(origin.getBytes(StringUtils.isNotBlank(charset) ? charset : CHAR_ENCODING), algorithm); return byteArrayToHexString(digest); } catch (Exception exception) { } return null; } public static String byteArrayToHexString(byte b[]) { StringBuffer resultSb = new StringBuffer(); for (int i = 0; i < b.length; i++) { resultSb.append(byteToHexString(b[i])); } return resultSb.toString(); } private static String byteToHexString(byte b) { return hexDigits[(b >> 4) & 0x0f] + hexDigits[b & 0x0f]; } /** * @param data 加密数据 * @param algorithm 加密算法 * @return */ public static byte[] encode(byte[] data, String algorithm) { try { MessageDigest md = MessageDigest.getInstance(algorithm); byte[] digest = md.digest(data); return digest; } catch (Exception exception) { } return null; } /** * RSA签名 * * @param content 待签名数据 * @param privateKey 商户私钥 * @param charset 编码格式 * @return 签名值 */ public static String sign(String content, String privateKey, String signAlgorithm, String charset) throws Exception { String md5Data = encode(content, CHAR_ENCODING, KEY_SH1); return sign(md5Data.getBytes(), privateKey, signAlgorithm); } /** * 用私钥对信息生成数字签名 * * @param data 加密数据 * @param privateKey 私钥 * @return 数字签名 * @throws Exception 异常 */ public static String sign(byte[] data, String privateKey, String signAlgorithm) throws Exception { // 解密由base64编码的私钥 byte[] keyBytes = Base64.getDecoder().decode(privateKey); // 构造PKCS8EncodedKeySpec对象 PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); // KEY_ALGORITHM 指定的加密算法 KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA); // 取私钥匙对象 PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec); // 用私钥对信息生成数字签名 Signature signature = Signature.getInstance(signAlgorithm); signature.initSign(priKey); signature.update(data); return Base64.getEncoder().encodeToString(signature.sign()); } public static String encryptAES(String data, String privateKey) throws Exception { byte[] aseKey = privateKey.substring(0, 16).getBytes(CHAR_ENCODING); return encryptAES(data, aseKey); } public static String encryptAES(String data, byte[] privateKey) throws Exception { SecretKeySpec skeySpec = new SecretKeySpec(privateKey, "AES"); //"算法/模式/补码方式" Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, skeySpec); byte[] encrypted = cipher.doFinal(data.getBytes(CHAR_ENCODING)); //此处使用BASE64做转码功能,同时能起到2次加密的作用。 return encryptBASE64(encrypted); } /** * 校验数字签名 * * @param data 加密数据 * @param publicKey 公钥 * @param sign 数字签名 * @return 校验成功返回true 失败返回false * @throws Exception 异常 */ public static boolean verify(String data, String publicKey, String sign, String signAlgorithm, String charset) throws Exception { // 验证签名是否正常 return verify(data.getBytes(StringUtils.isNotBlank(charset) ? CHAR_ENCODING : charset), publicKey, sign, signAlgorithm); } /** * 校验数字签名 * * @param data 加密数据 * @param publicKey 公钥 * @param sign 数字签名 * @return 校验成功返回true 失败返回false * @throws Exception 异常 */ public static boolean verify(byte[] data, String publicKey, String sign, String signAlgorithm) throws Exception { // 解密由base64编码的公钥 byte[] keyBytes = Base64.getDecoder().decode(publicKey); // 构造X509EncodedKeySpec对象 X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); // KEY_ALGORITHM 指定的加密算法 KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA); // 取公钥匙对象 PublicKey pubKey = keyFactory.generatePublic(keySpec); Signature signature = Signature.getInstance(signAlgorithm); signature.initVerify(pubKey); signature.update(data); // 验证签名是否正常 return signature.verify(Base64.getDecoder().decode(sign)); } /** * 解密 * * @param content 密文 * @param private_key 商户私钥 * @param input_charset 编码格式 * @return 解密后的字符串 */ public static String decrypt(String content, String private_key, String input_charset) throws Exception { PrivateKey prikey = getPrivateKey(private_key); Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.DECRYPT_MODE, prikey); InputStream ins = new ByteArrayInputStream(Base64.getDecoder().decode(content)); ByteArrayOutputStream writer = new ByteArrayOutputStream(); //rsa解密的字节大小最多是128,将需要解密的内容,按128位拆开解密 byte[] buf = new byte[128]; int bufl; while ((bufl = ins.read(buf)) != -1) { byte[] block = null; if (buf.length == bufl) { block = buf; } else { block = new byte[bufl]; for (int i = 0; i < bufl; i++) { block[i] = buf[i]; } } writer.write(cipher.doFinal(block)); } return new String(writer.toByteArray(), input_charset); } /** * 得到私钥 * * @param key 密钥字符串(经过base64编码) * @throws Exception */ public static PrivateKey getPrivateKey(String key) throws Exception { byte[] keyBytes; keyBytes = Base64.getDecoder().decode(key); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PrivateKey privateKey = keyFactory.generatePrivate(keySpec); return privateKey; } /** * 验签数据 * * @return */ public static boolean verify(String publicKey, String timestamp, String body, String originSignData) throws Exception { log.info("publicKey:{},timestamp:{},body:{},originSignData:{}", publicKey, timestamp, body, originSignData); String md5Data = getMd5SignData(timestamp, body); // 验签 boolean verify = verify(md5Data.getBytes(), publicKey, originSignData, KEY_SHA_RSA); if(!verify){ // 兼容历史 转json在解密 md5Data = getMd5SignDataFormatJson(timestamp, body); verify = verify(md5Data.getBytes(), publicKey, originSignData, KEY_SHA_RSA); } return verify; } /** * 验签数据 * * @return */ public static boolean verify(String appkey, String appsecret, String publicKey, String securityTypeCode, String timestamp, String body, String originSignData) throws Exception { String md5Data = getMd5SignData(appkey, appsecret, securityTypeCode, timestamp, body); // 验签 boolean verify = verify(md5Data.getBytes(), publicKey, originSignData, KEY_SHA_RSA); return verify; } public static String getMd5SignData(String timestamp, String body) { return getMd5SignData(null, null, null, timestamp, body); } public static String getMd5SignDataFormatJson(String timestamp, String body) { return getMd5SignDataFormatJson(null, null, null, timestamp, body); } public static String getMd5SignData(String appkey, String appsecret, String securityTypeCode, String timestamp, String body) { // 1,签名固定参数:appkey、appsecret,securityTypeCode、timestamp(时间戳10位)、body(http请求体) Map<String, String> paramMap = new TreeMap<>(); if (StringUtils.isNotBlank(appkey)) { paramMap.put("appkey", appkey); } if (StringUtils.isNotBlank(appsecret)) { // 兼容3.0 paramMap.put("appsecret", appsecret); } if (StringUtils.isNotBlank(securityTypeCode)) { // 兼容3.0 paramMap.put("securityTypeCode", securityTypeCode); } paramMap.put("timestamp", timestamp); if (body != null) { paramMap.put("body", body); // paramMap.put("body", JSON.parseObject(body).toJSONString()); } // 2,针对1步要求的所有非空的参数按字段名的ASCLL码从小到大排序后,使用URL键值对的格式(如key1=value1&key2=value2)拼接成字符串 // 。字段名和字段值都采用原始值不进行URL转义 log.info("paramMap:{}", JSON.toJSONString(body)); String signData = createReqParam(paramMap); log.info("signData:{}", signData); // 3,针对第2步得到的结果进行SHA-1加密 String md5Data = encode(signData, CHAR_ENCODING, KEY_SH1); log.info("signData-md5Data:{}", md5Data); return md5Data; } public static String getMd5SignDataFormatJson(String appkey, String appsecret, String securityTypeCode, String timestamp, String body) { // 1,签名固定参数:appkey、appsecret,securityTypeCode、timestamp(时间戳10位)、body(http请求体) Map<String, String> paramMap = new TreeMap<>(); if (StringUtils.isNotBlank(appkey)) { paramMap.put("appkey", appkey); } if (StringUtils.isNotBlank(appsecret)) { // 兼容3.0 paramMap.put("appsecret", appsecret); } if (StringUtils.isNotBlank(securityTypeCode)) { // 兼容3.0 paramMap.put("securityTypeCode", securityTypeCode); } paramMap.put("timestamp", timestamp); if (body != null) { paramMap.put("body", JSON.parseObject(body).toJSONString()); } // 2,针对1步要求的所有非空的参数按字段名的ASCLL码从小到大排序后,使用URL键值对的格式(如key1=value1&key2=value2)拼接成字符串 // 。字段名和字段值都采用原始值不进行URL转义 log.info("paramMap:{}", JSON.toJSONString(body)); String signData = createReqParam(paramMap); log.info("signData:{}", signData); // 3,针对第2步得到的结果进行SHA-1加密 String md5Data = encode(signData, CHAR_ENCODING, KEY_SH1); log.info("signData-md5Data:{}", md5Data); return md5Data; } public static String createReqParam(Map params) { TreeMap treeMap = new TreeMap(); treeMap.putAll(params); StringBuffer sb = new StringBuffer(); if (treeMap != null && treeMap.size() > 0) { Iterator var2 = treeMap.entrySet().iterator(); while (var2.hasNext()) { Map.Entry<String, Object> entry = (Map.Entry) var2.next(); if (entry.getKey() != null && !(entry.getKey()).isEmpty() && entry.getValue() != null) { String k = entry.getKey(); String v = "" + entry.getValue(); sb.append(k + "=" + v + "&"); } } } sb = sb.deleteCharAt(sb.length() - 1); return sb.toString(); } public static String aesDecrypt(String encryptContent, String key) throws Exception{ Cipher cipher = initSymmetricCipher(key, Cipher.DECRYPT_MODE, "AES"); byte[] result = cipher.doFinal(org.apache.commons.codec.binary.Base64.decodeBase64(encryptContent)); return new String(result, StandardCharsets.UTF_8); } public static String aesEncrypt(String content, String key) throws Exception{ Cipher cipher = initSymmetricCipher(key, Cipher.ENCRYPT_MODE, "AES"); byte[] result = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8)); return org.apache.commons.codec.binary.Base64.encodeBase64String(result); } public static Cipher initSymmetricCipher(String key, int cipherMode, String algorithms) throws Exception{ Cipher cipher; try { SecretKeySpec secretKeySpec = new SecretKeySpec(org.apache.commons.codec.binary.Base64.decodeBase64(key), algorithms); cipher = Cipher.getInstance(StringUtils.join(algorithms, "/ECB/PKCS5Padding")); cipher.init(cipherMode, secretKeySpec); } catch (Exception e){ log.error("初始化密钥异常:", e); throw e; } return cipher; } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
    • 368
    • 369
    • 370
    • 371
    • 372
    • 373
    • 374
    • 375
    • 376
    • 377
    • 378
    • 379
    • 380
    • 381
    • 382
    • 383
    • 384
    • 385
    • 386
    • 387
    • 388
    • 389
    • 390
    • 391
    • 392
    • 393
    • 394
    • 395
    • 396
    • 397
    • 398
    • 399
    • 400
    • 401
    • 402
    • 403
    • 404
    • 405
    • 406
    • 407
    • 408
    • 409
    • 410
    • 411
    • 412
    • 413
    • 414
    • 415
    • 416
    • 417
    • 418
    • 419
    • 420
    • 421
    • 422
    • 423
    • 424
    • 425
    • 426
    • 427
    • 428
    • 429
    • 430
    • 431
    • 432
    • 433
    • 434
    • 435
    • 436
    • 437
    • 438
    • 439
    • 440
    • 441
    • 442
    • 443
    • 444
    • 445
    • 446
    • 447
    • 448
    • 449
    • 450
    • 451
    • 452
    • 453
    • 454
    • 455
    • 456
    • 457
    • 458
    • 459
    • 460
    • 461
    • 462
    • 463
    • 464
    • 465
    • 466
    • 467
    • 468
    • 469
    • 470
    • 471
    • 472
    • 473
    • 474
    • 475
    • 476
    • 477
    • 478
  • 相关阅读:
    Vue rules校验规则详解
    MySQL8.0学习记录17 -Create Table
    Kibana配置ES集群
    MySQL修改表名:重命名RENAME
    Java之多线程的生产者消费者问题的详细解析
    go语言学习-结构体
    Apache Pulsar 系列 —— 深入理解 Bookie GC 回收机制
    用 flink 插件chunjun实现全量+增量同步-达梦数据库到postgresql
    单目3D目标检测——MonoDLE 模型训练 | 模型推理
    【Kubernetes 系列】一文带你吃透 K8S 应用pod结点
  • 原文地址:https://blog.csdn.net/m0_46836425/article/details/133795033