import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.xsyt.nvr.model.CameraChannel;
import okhttp3.*;
import javax.xml.bind.DatatypeConverter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
public class HikNvrUtil {
private String ip;
private int port;
private String username;
private String password;
private OkHttpClient httpClient;
private ObjectMapper jsonMapper;
private XmlMapper xmlMapper;
public HikNvrUtil(String ip, int port, String username, String password) {
this.ip = ip;
this.port = port;
this.username = username;
this.password = password;
this.httpClient = new OkHttpClient.Builder()
.addInterceptor(new DigestAuthenticator(username, password))
.build();
this.jsonMapper = new ObjectMapper();
this.xmlMapper = new XmlMapper();
}
/**
* 获取nvr的设备ID
* @return
* @throws IOException
*/
public String getDeviceId() throws IOException {
return JSONObject.parseObject(convertXmlToJson(getDeviceInfo())).getString("deviceID").replace("-","");
}
public String getDeviceInfo() throws IOException {
String url = String.format("http://%s:%d/ISAPI/System/deviceInfo", ip, port);
return executeGetRequest(url);
}
public String getDeviceStatus() throws IOException {
String url = String.format("http://%s:%d/ISAPI/System/status", ip, port);
return executeGetRequest(url);
}
public String getChannelInfo() throws IOException {
String url = String.format("http://%s:%d/ISAPI/Streaming/channels", ip, port);
return executeGetRequest(url);
}
/**
* 获取通道ID
* @return
* @throws IOException
*/
public List<String> getChannelIds() throws IOException {
List<String> channelIds = new ArrayList<>();
JSONObject.parseObject(convertXmlToJson(getChannelInfo())).getJSONArray("StreamingChannel").forEach(channel -> {
channelIds.add(((JSONObject)channel).getString("id"));
});
return channelIds;
}
/**
* 获取通道信息
* @return
* @throws IOException
*/
public List<CameraChannel> getChannels() throws IOException {
List<String> channelIds = getChannelIds();
//通道ID改为四位数字
channelIds.forEach(channelId -> {
String channelIdStr = String.format("%04d", Integer.parseInt(channelId));
channelIds.set(channelIds.indexOf(channelId), channelIdStr);
});
List<CameraChannel> cameraChannels = new ArrayList<>();
//按照前两位数字一致的为同一个相机通道, 后两位是01的为主码通道,后两位是02的为子码通道,保存到对象CameraChannel中
channelIds.forEach(channelId -> {
if (channelId.endsWith("01")) {
String channelNo = channelId.substring(0, 2);
if (channelIds.contains(channelNo + "02")) {
cameraChannels.add(new CameraChannel(channelNo, channelId, channelNo + "02"));
} else {
cameraChannels.add(new CameraChannel(channelNo, channelId, ""));
}
} else if (channelId.endsWith("02")) {
String channelNo = channelId.substring(0, 2);
if (!channelIds.contains(channelNo + "01")) {
cameraChannels.add(new CameraChannel(channelNo, "", channelId));
}
//包含则等循环到01时候添加
} else {
String channelNo = channelId.substring(0, 2);
System.out.println("未知的通道规则:"+channelId);
cameraChannels.add(new CameraChannel(channelNo, channelId, ""));
}
});
return cameraChannels;
}
private String executeGetRequest(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.get()
.build();
try (Response response = httpClient.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
return response.body().string();
}
}
private String convertXmlToJson(String xml) throws IOException {
JsonNode node = xmlMapper.readTree(xml.getBytes(StandardCharsets.UTF_8));
return jsonMapper.writerWithDefaultPrettyPrinter().writeValueAsString(node);
}
public static void main(String[] args) {
HikNvrUtil nvrUtils = new HikNvrUtil("192.168.0.4", 80, "admin", "1qaz2wsx");
try {
System.out.println("Device ID: " + nvrUtils.getDeviceId());
// System.out.println("Device Info: " + nvrUtils.convertXmlToJson(nvrUtils.getDeviceInfo()));
// System.out.println("Device Status: " + nvrUtils.convertXmlToJson(nvrUtils.getDeviceStatus()));
// System.out.println("Channel Info: " + nvrUtils.convertXmlToJson(nvrUtils.getChannelInfo()));
// System.out.println("Channel IDs: " + nvrUtils.getChannelIds());
System.out.println("Channels: " + nvrUtils.getChannels());
} catch (IOException e) {
e.printStackTrace();
}
}
}
class DigestAuthenticator implements Interceptor {
private final String username;
private final String password;
private String realm;
private String nonce;
private String qop;
private String opaque;
private String algorithm;
public DigestAuthenticator(String username, String password) {
this.username = username;
this.password = password;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
if (response.code() == 401) {
Headers headers = response.headers();
String authHeader = headers.get("WWW-Authenticate");
if (authHeader != null && authHeader.startsWith("Digest ")) {
parseAuthHeader(authHeader);
String digestHeader = createDigestHeader(request);
Request newRequest = request.newBuilder()
.header("Authorization", digestHeader)
.build();
response.close();
return chain.proceed(newRequest);
}
}
return response;
}
private void parseAuthHeader(String authHeader) {
String[] parts = authHeader.substring(7).split(", ");
for (String part : parts) {
String[] keyValue = part.split("=", 2);
String key = keyValue[0];
String value = keyValue[1].replaceAll("\"", "");
switch (key) {
case "realm":
this.realm = value;
break;
case "nonce":
this.nonce = value;
break;
case "qop":
this.qop = value;
break;
case "opaque":
this.opaque = value;
break;
case "algorithm":
this.algorithm = value;
break;
}
}
}
private String createDigestHeader(Request request) {
String method = request.method();
String uri = request.url().encodedPath();
String ha1 = md5(username + ":" + realm + ":" + password);
String ha2 = md5(method + ":" + uri);
String nc = "00000001";
String cnonce = UUID.randomUUID().toString().replace("-", "");
String response = md5(ha1 + ":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + ha2);
return "Digest username=\"" + username + "\", realm=\"" + realm + "\", nonce=\"" + nonce + "\", uri=\"" + uri
+ "\", qop=" + qop + ", nc=" + nc + ", cnonce=\"" + cnonce + "\", response=\"" + response
+ "\", opaque=\"" + opaque + "\", algorithm=" + algorithm;
}
private String md5(String input) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] messageDigest = md.digest(input.getBytes(StandardCharsets.UTF_8));
return DatatypeConverter.printHexBinary(messageDigest).toLowerCase();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}
对应在Postman中的调用方式: