1. 前言:
artemis-http-client sdk 中提供获取门禁事件图片的方法,但实际图片访问地址为该响应的重定向地址
问题来了:虽然他提供了 sdk ,但没有办法通过 sdk 获取重定向的地址于是产生了本文,自己通过hutools的 httpUtil调用

2. 签名工具类
HikSignUtil 记得先引入一下 artemis-http-client
java
package com.lxsy.util;
import cn.hutool.core.codec.Base64;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.crypto.digest.HMac;
import cn.hutool.crypto.digest.HmacAlgorithm;
import java.nio.charset.StandardCharsets;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
/**
* 海康 OpenAPI 签名工具类
* 签名算法:HmacSHA256 + Base64
*/
public class HikSignUtil {
private static final DateTimeFormatter RFC_1123_FORMATTER =
DateTimeFormatter.RFC_1123_DATE_TIME.withZone(ZoneOffset.UTC);
/**
* 生成请求头(包含签名)
*
* @param appKey 应用Key
* @param appSecret 应用Secret
* @param url 请求的相对路径(如 /artemis/api/acs/v1/event/pictures)
* @param body 请求体(JSON字符串)
* @return 包含签名的请求头Map
*/
public static Map<String, String> buildHeaders(String appKey, String appSecret, String url, String body) {
// Content-MD5:请求体的MD5值,Base64编码
String contentMd5 = Base64.encode(DigestUtil.md5(body));
// Date 头(RFC1123)
String date = RFC_1123_FORMATTER.format(ZonedDateTime.now(ZoneOffset.UTC));
// 签名字符串拼接
// httpHeaders = HTTP METHOD + "\n" + Accept + "\n" + Content-MD5 + "\n" + Content-Type + "\n" + Date + "\n"
// customHeaders = "x-ca-key" + ":" + appKey + "\n"
// signString = httpHeaders + customHeaders + url
StringBuilder signBuilder = new StringBuilder();
signBuilder.append("POST").append("\n"); // HTTP METHOD
signBuilder.append("*/*").append("\n"); // Accept
signBuilder.append(contentMd5).append("\n"); // Content-MD5
signBuilder.append("application/json").append("\n"); // Content-Type
signBuilder.append(date).append("\n"); // Date
signBuilder.append("x-ca-key:").append(appKey).append("\n"); // customHeaders
signBuilder.append(url); // url
String signString = signBuilder.toString();
// 使用 HmacSHA256 算法计算签名
HMac hmac = new HMac(HmacAlgorithm.HmacSHA256, appSecret.getBytes(StandardCharsets.UTF_8));
String signature = Base64.encode(hmac.digest(signString));
// 构建请求头
Map<String, String> headers = new HashMap<>();
headers.put("Accept", "*/*");
headers.put("Content-Type", "application/json");
headers.put("Content-MD5", contentMd5);
headers.put("Date", date);
headers.put("x-ca-key", appKey);
headers.put("x-ca-signature", signature);
headers.put("x-ca-signature-headers", "x-ca-key");
return headers;
}
}
3. 使用
示例1
java
protected static final ArtemisConfig artemisConfig = new ArtemisConfig("192.XX.13.XX:443", "123", "123");
/**
* 使用自建 HttpClient 调用海康接口,便于排查网络/签名问题
*/
public static PageResponseResult<EventInfoDto> getEventListWithHttpClient(EventsRequest eventsRequest) throws Exception {
if (eventsRequest == null) {
eventsRequest = new EventsRequest();
}
String url = "/artemis/api/acs/v2/door/events";
String fullUrl = "https://" + artemisConfig.getHost() + url;
String body = JSON.toJSONString(eventsRequest);
Map<String, String> headers = HikSignUtil.buildHeaders(
artemisConfig.getAppKey(),
artemisConfig.getAppSecret(),
url,
body
);
try (CloseableHttpClient httpClient = createHttpClient(false)) {
HttpPost httpPost = new HttpPost(fullUrl);
headers.forEach(httpPost::setHeader);
httpPost.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON));
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
int status = response.getStatusLine().getStatusCode();
String respBody = response.getEntity() == null ? null : EntityUtils.toString(response.getEntity());
if (status == HttpStatus.SC_OK) {
return checkResp(respBody, new TypeReference<PageResponseResult<EventInfoDto>>() {
});
}
throw new Exception("获取事件列表失败,状态码: " + status + ", 响应: " + respBody);
}
}
}
示例2
java
protected static final ArtemisConfig artemisConfig = new ArtemisConfig("192.XX.13.XX:443", "123", "123");
public static String pictures(String svrIndexCode, String picUri) throws Exception {
String url = "/artemis/api/acs/v1/event/pictures";
String fullUrl = "https://" + artemisConfig.getHost() + url;
// 构建请求体
PicturesRequest request = new PicturesRequest(svrIndexCode, picUri);
String body = JSON.toJSONString(request);
// 使用签名工具生成请求头
Map<String, String> headers = HikSignUtil.buildHeaders(
artemisConfig.getAppKey(),
artemisConfig.getAppSecret(),
url,
body
);
try (CloseableHttpClient httpClient = createHttpClient(false)) {
HttpPost httpPost = new HttpPost(fullUrl);
headers.forEach(httpPost::setHeader);
httpPost.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON));
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
int status = response.getStatusLine().getStatusCode();
if (status == HttpStatus.SC_MOVED_TEMPORARILY
|| status == HttpStatus.SC_MOVED_PERMANENTLY
|| status == HttpStatus.SC_SEE_OTHER
|| status == HttpStatus.SC_TEMPORARY_REDIRECT
|| status == HTTP_STATUS_PERMANENT_REDIRECT) {
Header locationHeader = response.getFirstHeader("Location");
if (locationHeader != null && StrUtil.isNotBlank(locationHeader.getValue())) {
return locationHeader.getValue();
}
throw new Exception("302重定向但未获取到Location头");
}
String respBody = response.getEntity() == null ? null : EntityUtils.toString(response.getEntity());
throw new Exception("获取图片失败,状态码: " + status + ", 响应: " + respBody);
}
}
}