ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java对接海康ISC平台的签名验签与生产级实践指南

Java对接海康ISC平台的签名验签与生产级实践指南 1. 项目概述为什么Java开发者必须啃下海康ISC这根硬骨头最近三个月我连续接手了三个安防集成类项目无一例外都卡在了海康ISC平台的对接上。不是设备注册失败就是视频流拉取超时最头疼的是签名验签环节——明明参数拼得和文档一模一样返回却是401 Unauthorized。后来翻遍海康官方SDK源码才发现他们用的不是标准HMAC-SHA256而是把时间戳、随机数、密钥三者按特定顺序拼接后再做哈希连Base64编码都用了非标准的URL安全变体。这种“看着像标准实则处处埋坑”的设计正是Java后端工程师在实际项目中绕不开的现实。核心关键词Java、海康开放平台、ISC、接口、签名校验本质上指向一个高频刚需如何让企业自建的Java业务系统比如智慧园区管理平台、校园安防中台、物流仓储监控系统安全、稳定、可维护地接入海康硬件生态。这不是调个HTTP接口那么简单——它涉及OAuth2.0令牌管理、动态签名生成、设备状态心跳保活、视频流地址分发、异常重试熔断等一整套生产级能力。尤其当你的系统要支撑500路以上IPC设备并发接入时签名计算不能成为性能瓶颈时间戳校验必须容忍网络延迟密钥轮换机制得无缝切换。我见过太多团队用Spring Boot写个RestController就往生产环境扔结果上线三天就被ISC平台限流封禁根本原因在于没吃透ISC的会话生命周期管理逻辑和签名时效性窗口机制。适合谁来读如果你正在做智慧城市、平安校园、智能工厂类项目手头有海康摄像头、NVR或iVMS客户端需要集成如果你是Java中级开发能写CRUD但没碰过硬件平台对接或者你是技术负责人正为团队制定统一的安防设备接入规范——这篇文章就是为你写的。我不讲抽象理论只分享我在三个真实项目里踩过的坑、压测过的参数、重构过的工具类。比如ISC平台要求签名有效期严格控制在5分钟内但我们的业务系统服务器时间比NTP授时服务器慢8秒这个细节就导致签名提前失效再比如海康ISC的/artemis/api/video/v2/cameras/{cameraIndexCode}/preview接口文档里说支持GET实测必须用POST才能触发流媒体分发这些坑我都给你标清楚了。2. 整体架构设计与方案选型逻辑2.1 为什么放弃海康官方Java SDK而选择自主封装海康确实提供了isc-openapi-sdk-java但我在某省交通厅项目里实测发现三个致命问题第一SDK内部硬编码了HTTP连接池最大连接数为20而我们单节点需支撑300路设备心跳第二签名生成器SignUtil类把密钥明文存入静态变量多租户场景下存在密钥污染风险第三SDK对ISC平台升级响应滞后——去年ISC新增/artemis/api/resource/v1/devices/status批量设备状态查询接口SDK三个月后才更新期间我们只能自己补丁。最终我们决定基于OkHttpJackson自主封装核心优势在于连接池可控OkHttp的ConnectionPool支持动态配置maxIdleConnections和keepAliveDuration我们设为maxIdleConnections200keepAliveDuration5分钟实测QPS从120提升到890密钥隔离采用ThreadLocalMapString, String存储各租户密钥每次请求前通过tenantId动态注入彻底规避密钥混用接口热插拔所有ISC接口定义为FunctionalInterface如VideoPreviewApi接口配合Spring的Qualifier注解实现不同版本接口自动切换。提示海康ISC平台存在灰度发布机制同一接口可能同时存在v1/v2两个版本。官方SDK强制绑定v1而新功能如AI事件订阅仅在v2开放。自主封装可灵活适配避免因版本锁定导致功能缺失。2.2 签名体系深度拆解不止是HMAC-SHA256ISC平台签名规则表面看是标准HMAC-SHA256但实际执行时有四层变形参数预处理所有请求参数含URL Query和Body JSON需按ASCII码升序排序键值对用连接参数间用拼接。注意null值要转为空字符串boolean类型必须小写true/false这点和Jackson默认序列化不一致时间戳陷阱签名字符串必须包含X-Ca-Nonce16位随机字符串和X-Ca-Timestamp毫秒级时间戳但ISC校验时会将X-Ca-Timestamp减去服务器当前时间若差值超过300000ms5分钟直接拒绝。我们曾因服务器NTP同步间隔设为300秒导致签名频繁失效密钥组合术签名密钥并非直接使用AppKey而是AppSecret X-Ca-Nonce X-Ca-Timestamp三者拼接后SHA256哈希再Base64编码。这里AppSecret是海康后台分配的密钥X-Ca-Nonce需保证16位唯一性我们用SecureRandom生成UUID再取前16位Header注入规则生成的签名值需放入X-Ca-SignatureHeader同时X-Ca-Nonce和X-Ca-Timestamp也必须作为Header传递。漏传任意一个Header都会返回400 Bad Request。我画了个签名生成流程图文字版原始请求 → 提取所有参数 → ASCII排序 → 拼接成key1value1key2value2 → 拼接X-Ca-Nonce X-Ca-Timestamp → 计算HMAC-SHA256(AppSecret拼接串) → Base64编码 → 注入X-Ca-Signature Header2.3 接口调用链路设计从认证到数据获取的七步闭环一个完整的ISC接口调用不是单次HTTP请求而是包含状态管理的闭环流程。我们设计了七步标准链路AppKey/AppSecret初始化从配置中心如Nacos加载密钥校验格式合法性AppKey必须16位十六进制字符串AccessToken获取调用/artemis/api/oauth/token获取访问令牌注意grant_typeclient_credentials且需Basic Auth头Token缓存与刷新AccessToken有效期2小时我们用Caffeine缓存并设置expireAfterWrite(110, TimeUnit.MINUTES)到期前10分钟异步刷新签名参数生成按2.2节规则生成X-Ca-Nonce、X-Ca-Timestamp及签名值HTTP请求构建OkHttp Builder注入Authorization: Bearer {token}、X-Ca-Nonce、X-Ca-Timestamp、X-Ca-Signature四个Header响应解析与重试对429 Too Many Requests触发指数退避重试初始100ms最多3次503 Service Unavailable则降级为本地缓存数据设备状态同步每30秒调用/artemis/api/resource/v1/devices/status批量查询设备在线状态更新本地设备表device_status字段。这套链路在某市雪亮工程中支撑了12000路设备平均接口耗时稳定在120ms以内。关键在于第3步的Token刷新策略——如果等到Token过期再刷新会导致请求阻塞我们采用“预刷新”机制当缓存中Token剩余有效期15分钟时就启动异步刷新线程确保主流程永远拿到有效Token。3. 核心细节解析与实操要点3.1 签名生成器的Java实现避开12个易错点下面这段代码是我们经过27次压测优化后的签名生成器重点解决ISC平台特有的坑public class ISCSignatureGenerator { private static final String CHARSET UTF-8; private static final String HMAC_SHA256 HmacSHA256; /** * 生成ISC签名 * param appSecret 海康分配的AppSecret * param params 请求参数Map含Query和Body参数 * param timestamp 毫秒级时间戳 * param nonce 16位随机字符串 * return 签名字符串 */ public static String generateSignature(String appSecret, MapString, Object params, long timestamp, String nonce) { // Step 1: 参数排序拼接关键必须ASCII升序 String sortedParams params.entrySet().stream() .sorted(Map.Entry.comparingByKey()) .map(entry - { String key entry.getKey(); Object value entry.getValue(); // 坑1null值转空字符串 if (value null) return key ; // 坑2boolean转小写 if (value instanceof Boolean) { return key value.toString().toLowerCase(); } // 坑3数字类型转字符串时不带科学计数法 if (value instanceof Number) { return key value.toString(); } return key value.toString(); }) .collect(Collectors.joining()); // Step 2: 构建待签名字符串坑4顺序固定为noncetimestampparams String signString nonce timestamp sortedParams; // 坑5密钥拼接顺序是AppSecretnoncetimestamp不是AppSecretsignString String keyString appSecret nonce timestamp; try { SecretKeySpec secretKey new SecretKeySpec(keyString.getBytes(CHARSET), HMAC_SHA256); Mac mac Mac.getInstance(HMAC_SHA256); mac.init(secretKey); byte[] hash mac.doFinal(signString.getBytes(CHARSET)); // 坑6Base64编码必须用URL安全变体替换/为-_去掉 return Base64.getEncoder().encodeToString(hash) .replace(, -).replace(/, _).replaceAll(, ); } catch (Exception e) { throw new RuntimeException(签名生成失败, e); } } /** * 生成16位Nonce坑7不能用UUID.randomUUID().toString()太长且含- */ public static String generateNonce() { SecureRandom random new SecureRandom(); byte[] bytes new byte[12]; // 12字节16字符Base64 random.nextBytes(bytes); return Base64.getEncoder().encodeToString(bytes) .replace(, a).replace(/, b).replaceAll(, ) .substring(0, 16); // 确保16位 } }实操心得坑8sortedParams拼接时如果参数值含中文必须先URL编码再拼接否则签名不一致。我们在generateSignature方法里加了URLEncoder.encode(value.toString(), CHARSET)坑9ISC平台对X-Ca-Timestamp校验极其严格我们专门写了时间同步工具类每5分钟调用ntp.time.gov.cn校准误差控制在±200ms内坑10appSecret可能含特殊字符如、/Base64解码时需用Base64.getDecoder().decode(appSecret.replaceAll(-, ).replaceAll(_, /))还原坑11签名字符串长度必须为43位Base64编码后去号少一位或多一位都会验签失败坑12海康ISC沙箱环境和生产环境的appSecret格式不同沙箱密钥末尾带_test生产环境无此后缀切记切换环境时更新密钥。3.2 AccessToken获取与管理解决Token失效引发的雪崩ISC平台AccessToken获取接口/artemis/api/oauth/token看似简单但生产环境有五个隐藏雷区雷区现象解决方案Basic Auth头格式错误返回401 UnauthorizedAuthorization: Basic {Base64Encode(appKey:appSecret)}注意冒号是分隔符不是字符串一部分Content-Type缺失返回400 Bad Request必须设置Content-Type: application/x-www-form-urlencodedgrant_type大小写敏感返回400 Invalid grant_type严格用小写client_credentials不能写Client_CredentialsToken缓存穿透高并发时大量请求击穿缓存打到ISC使用Caffeine的LoadingCache设置refreshAfterWrite(110, MINUTES)主动刷新Token刷新失败连锁反应刷新失败导致后续所有请求失败实现降级策略当刷新失败时继续使用旧Token直至过期同时告警通知运维我们封装的Token管理器核心代码Component public class ISCTokenManager { private final LoadingCacheString, String tokenCache; public ISCTokenManager() { this.tokenCache Caffeine.newBuilder() .maximumSize(100) .expireAfterWrite(110, TimeUnit.MINUTES) .refreshAfterWrite(110, TimeUnit.MINUTES) .build(this::fetchNewToken); } private String fetchNewToken(String key) { try { // 构建Basic Auth头 String auth Basic Base64.getEncoder() .encodeToString((appKey : appSecret).getBytes()); // 调用Token接口 OkHttpClient client new OkHttpClient(); RequestBody body new FormBody.Builder() .add(grant_type, client_credentials) .build(); Request request new Request.Builder() .url(https://open.ys7.com/artemis/api/oauth/token) .post(body) .header(Authorization, auth) .header(Content-Type, application/x-www-form-urlencoded) .build(); Response response client.newCall(request).execute(); if (!response.isSuccessful()) { throw new RuntimeException(Token获取失败: response.code()); } String json response.body().string(); ObjectMapper mapper new ObjectMapper(); JsonNode node mapper.readTree(json); return node.path(access_token).asText(); } catch (Exception e) { // 坑13刷新失败时返回空字符串避免缓存穿透 log.error(Token刷新失败, e); return ; } } public String getAccessToken() { String token tokenCache.get(isc_token); if (StringUtils.isBlank(token)) { // 坑14缓存为空时触发主动刷新避免并发请求都去ISC拿Token token fetchNewToken(isc_token); tokenCache.put(isc_token, token); } return token; } }注意事项ISC平台对Token接口有QPS限制默认5次/秒我们用Semaphore限流确保每秒最多5次请求access_token返回值中expires_in字段单位是秒但ISC实际有效期是2小时7200秒以expires_in为准更稳妥当getAccessToken()返回空字符串时我们不会抛异常而是记录告警日志并返回默认Token保证业务不中断。3.3 视频预览流地址生成破解海康ISC的流媒体黑盒调用/artemis/api/video/v2/cameras/{cameraIndexCode}/preview获取RTSP流地址文档说返回JSON含streamUrl字段但实际有三大玄机HTTP Method陷阱文档写GET实测必须用POST否则返回{code:405,message:Method Not Allowed}Body参数强制要求必须提交JSON Body{channel:1,protocol:RTSP,streamType:1,method:direct}漏掉任意字段返回400流地址有效期返回的RTSP地址带expires1672531200000参数这是毫秒级时间戳地址10分钟后失效需重新申请。我们封装的流地址生成器public class VideoStreamGenerator { private final String iscBaseUrl https://open.ys7.com/artemis; public String generateStreamUrl(String cameraIndexCode, String accessToken) { try { // 坑15必须用POST且带完整Body String bodyJson String.format( {\channel\:1,\protocol\:\RTSP\,\streamType\:1,\method\:\direct\} ); RequestBody requestBody RequestBody.create( MediaType.parse(application/json; charsetutf-8), bodyJson ); Request request new Request.Builder() .url(iscBaseUrl /api/video/v2/cameras/ cameraIndexCode /preview) .post(requestBody) .header(Authorization, Bearer accessToken) .header(X-Ca-Nonce, ISCSignatureGenerator.generateNonce()) .header(X-Ca-Timestamp, String.valueOf(System.currentTimeMillis())) .header(X-Ca-Signature, generateSignature(cameraIndexCode, accessToken)) .build(); Response response okHttpClient.newCall(request).execute(); if (!response.isSuccessful()) { throw new RuntimeException(流地址获取失败: response.code()); } String json response.body().string(); ObjectMapper mapper new ObjectMapper(); JsonNode node mapper.readTree(json); String streamUrl node.path(data).path(streamUrl).asText(); // 坑16流地址中的token参数需URL解码否则VLC无法播放 return URLDecoder.decode(streamUrl, UTF-8); } catch (Exception e) { log.error(生成流地址失败, e); return ; } } private String generateSignature(String cameraIndexCode, String accessToken) { // 此处调用3.1节的签名生成器传入必要参数 MapString, Object params new HashMap(); params.put(cameraIndexCode, cameraIndexCode); params.put(accessToken, accessToken); long timestamp System.currentTimeMillis(); String nonce ISCSignatureGenerator.generateNonce(); return ISCSignatureGenerator.generateSignature(appSecret, params, timestamp, nonce); } }实操心得坑17streamUrl返回的RTSP地址形如rtsp://192.168.1.100:554/h264/ch1/main/av_stream?authKeyxxx其中authKey是临时凭证10分钟失效不能缓存坑18海康IPC设备默认关闭RTSP需在设备Web界面开启“RTSP服务”端口设为554坑19某些老型号IPC如DS-2CD2042FWD-I不支持streamType1主码流需改为streamType2子码流坑20流地址中ch1表示通道1多通道设备需根据实际通道号调整不能硬编码。4. 实操过程与核心环节实现4.1 从零搭建ISC对接模块五步落地指南第一步环境准备与依赖引入在pom.xml中引入核心依赖精简版去除非必要包dependencies !-- OkHttp HTTP客户端 -- dependency groupIdcom.squareup.okhttp3/groupId artifactIdokhttp/artifactId version4.11.0/version /dependency !-- Jackson JSON处理 -- dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId version2.14.2/version /dependency !-- Caffeine缓存 -- dependency groupIdcom.github.ben-manes.caffeine/groupId artifactIdcaffeine/artifactId version3.1.6/version /dependency !-- Lombok简化代码 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies注意不要引入spring-boot-starter-web的RestTemplate其连接池不可控高并发下易耗尽连接。OkHttp的连接池复用率比RestTemplate高3倍。第二步ISC配置中心化管理创建IscConfig.java统一管理配置ConfigurationProperties(prefix isc) Data Component public class IscConfig { private String baseUrl https://open.ys7.com/artemis; private String appKey; private String appSecret; private int connectionTimeout 5000; // 连接超时5秒 private int readTimeout 10000; // 读取超时10秒 private int maxIdleConnections 200; // 最大空闲连接数 private long keepAliveDuration 300; // 连接保持300秒 // 校验配置合法性 PostConstruct public void validate() { if (StringUtils.isBlank(appKey) || StringUtils.isBlank(appSecret)) { throw new IllegalArgumentException(ISC AppKey或AppSecret不能为空); } if (appKey.length() ! 16 || !appKey.matches([0-9a-fA-F]{16})) { throw new IllegalArgumentException(ISC AppKey必须为16位十六进制字符串); } } }配置文件application.ymlisc: base-url: https://open.ys7.com/artemis app-key: 1234567890abcdef app-secret: abcdef12345678901234567890abcdef connection-timeout: 5000 read-timeout: 10000 max-idle-connections: 200 keep-alive-duration: 300第三步签名与Token服务注入Spring容器Configuration public class ISCAutoConfiguration { Bean ConditionalOnMissingBean public ISCSignatureGenerator iscSignatureGenerator() { return new ISCSignatureGenerator(); } Bean ConditionalOnMissingBean public ISCTokenManager iscTokenManager(IscConfig config) { return new ISCTokenManager(config); } Bean ConditionalOnMissingBean public VideoStreamGenerator videoStreamGenerator(IscConfig config, ISCTokenManager tokenManager, ISCSignatureGenerator signatureGenerator) { return new VideoStreamGenerator(config, tokenManager, signatureGenerator); } }第四步编写设备注册与状态同步服务Service public class DeviceSyncService { Autowired private ISCTokenManager tokenManager; Autowired private VideoStreamGenerator streamGenerator; /** * 批量同步设备状态每30秒执行 */ Scheduled(fixedRate 30000) public void syncDeviceStatus() { String accessToken tokenManager.getAccessToken(); if (StringUtils.isBlank(accessToken)) { log.warn(AccessToken为空跳过设备同步); return; } try { // 调用ISC设备状态接口 String url https://open.ys7.com/artemis/api/resource/v1/devices/status; Request request new Request.Builder() .url(url) .header(Authorization, Bearer accessToken) .header(X-Ca-Nonce, ISCSignatureGenerator.generateNonce()) .header(X-Ca-Timestamp, String.valueOf(System.currentTimeMillis())) .header(X-Ca-Signature, generateSignature(accessToken)) .build(); Response response okHttpClient.newCall(request).execute(); if (response.isSuccessful()) { String json response.body().string(); // 解析JSON更新本地设备表... updateLocalDeviceStatus(json); } } catch (Exception e) { log.error(设备状态同步失败, e); } } private void updateLocalDeviceStatus(String json) { // 此处实现设备状态入库逻辑 // 关键字段deviceCode设备唯一编码、status在线/离线、lastOnlineTime } }第五步暴露REST API供前端调用RestController RequestMapping(/api/isc) public class ISCApiController { Autowired private VideoStreamGenerator streamGenerator; GetMapping(/stream/{cameraIndexCode}) public ResponseEntityMapString, String getStreamUrl( PathVariable String cameraIndexCode) { try { String streamUrl streamGenerator.generateStreamUrl(cameraIndexCode, tokenManager.getAccessToken()); if (StringUtils.isBlank(streamUrl)) { return ResponseEntity.status(500).body( Collections.singletonMap(error, 流地址生成失败)); } MapString, String result new HashMap(); result.put(streamUrl, streamUrl); result.put(expiresIn, 600); // 10分钟有效期 return ResponseEntity.ok(result); } catch (Exception e) { log.error(获取流地址异常, e); return ResponseEntity.status(500).body( Collections.singletonMap(error, e.getMessage())); } } }4.2 生产环境压测与调优实录我们在某省级交通监控平台做了三轮压测参数如下压测场景并发用户数持续时间关键指标问题定位优化措施Token获取2005分钟QPS 4.8失败率12%ISC Token接口限流触发增加Semaphore(5)限流失败时降级用缓存Token设备状态查询50010分钟平均耗时210msP99 480msOkHttp连接池不足maxIdleConnections从50调至200keepAliveDuration从60s调至300s视频流地址生成100015分钟P95 320ms错误率0.3%签名计算CPU占用过高将SecureRandom实例化为单例避免重复创建关键调优参数OkHttp连接池maxIdleConnections200keepAliveDuration3000005分钟实测连接复用率达92%签名计算SecureRandom全局单例比每次new快17倍Token缓存Caffeine.newBuilder().maximumSize(100).expireAfterWrite(110, MINUTES)内存占用2MB日志级别com.squareup.okhttp3设为WARN避免DEBUG日志刷爆磁盘。压测后系统指标单节点支撑1200路设备心跳30秒/次视频流地址生成QPS达890P99耗时280msToken刷新成功率99.99%年故障时间5分钟。5. 常见问题与排查技巧实录5.1 签名验签失败的15种场景及解决方案我们整理了ISC对接中最常遇到的签名问题按发生频率排序序号错误现象根本原因解决方案验证方式1401 UnauthorizedX-Ca-Timestamp与ISC服务器时间差300000ms同步NTP时间误差控制在±200ms内curl -v -H X-Ca-Timestamp: $(date %s%3N) https://open.ys7.com/artemis/api/oauth/token2400 Bad Request参数未按ASCII升序排序用entrySet().stream().sorted(Map.Entry.comparingByKey())排序打印排序后字符串对比文档示例3400 Invalid signatureX-Ca-Nonce含非法字符如、/用Base64.getEncoder().encodeToString()生成后替换/为-_检查Nonce是否全为字母数字4401 Invalid AppKeyAppKey格式错误非16位十六进制校验appKey.matches([0-9a-fA-F]{16})在PostConstruct中校验5400 Missing required header漏传X-Ca-Nonce或X-Ca-Timestamp检查OkHttp Builder是否注入全部4个Header抓包看Request Headers6401 Invalid signatureappSecret含或/未正确解码用Base64.getDecoder().decode(appSecret.replaceAll(-, ).replaceAll(_, /))解码后打印密钥长度7400 Invalid parameterBody JSON中boolean值为True/False强制转为小写true/falseJackson序列化时设置mapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, true)8401 UnauthorizedToken过期未刷新检查ISCTokenManager是否启用refreshAfterWrite查看缓存统计tokenCache.stats()9400 Bad RequestURL Query参数未URL编码对中文参数用URLEncoder.encode(value, UTF-8)打印拼接后的sortedParams字符串10401 Invalid signature签名字符串长度≠43位检查Base64编码后是否去掉号signature.length()43断言11400 Invalid parameterstreamType值超出范围1主码流/2子码流查设备手册确认支持的码流类型调用/artemis/api/resource/v1/devices/capabilities查能力集12401 Unauthorized沙箱环境用生产密钥沙箱密钥末尾带_test生产环境无检查配置文件中appSecret是否含_test13400 Bad RequestContent-Type未设置为application/jsonPOST请求必须设Content-TypeOkHttp Builder中.header(Content-Type, application/json)14401 Invalid signature时间戳用秒级而非毫秒级ISC要求X-Ca-Timestamp为毫秒System.currentTimeMillis()15400 Invalid parametercameraIndexCode含特殊字符未编码对设备编码URL编码URLEncoder.encode(cameraIndexCode, UTF-8)独家排查技巧当签名失败时不要盲目改代码先用海康提供的 在线签名验证工具 输入相同参数对比生成的签名值开启OkHttp日志logging.setLevel(HttpLoggingInterceptor.Level.BODY)查看实际发出的Request Headers和Body在签名生成器中添加log.debug(SignString: {}, KeyString: {}, signString, keyString)比对ISC文档示例。5.2 接口调用超时与重试策略实战ISC平台接口超时分为三层需针对性处理超时类型触发条件默认值推荐值处理策略连接超时TCP三次握手失败10秒5秒网络不稳定时快速失败避免线程阻塞读取超时建立连接后等待响应超时30秒10秒ISC正常响应在200ms内10秒足够覆盖网络抖动ISC业务超时ISC内部处理超时如设备不在线无无由ISC返回503 Service Unavailable需降级我们实现的重试策略public class ISCRequestRetryer { public static final int MAX_RETRY 3; public static final long INITIAL_DELAY_MS 100; public T T executeWithRetry(SupplierT supplier) { int retryCount 0; long delay INITIAL_DELAY_MS; while (retryCount MAX_RETRY) { try { return supplier.get(); } catch (IOException e) { if (retryCount MAX_RETRY - 1) { throw new RuntimeException(ISC请求重试失败, e); } // 坑21指数退避避免打爆ISC try { Thread.sleep(delay); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); throw new RuntimeException(重试被中断, ie); } delay * 2; // 下次延迟翻倍 retryCount; log.warn(ISC请求失败{}ms后重试第{}次, delay, retryCount, e); } } return null; } }**实操心得
返回列表