海康增加人像库图片上传

This commit is contained in:
lxj
2025-08-09 20:58:21 +08:00
parent 48984a494d
commit 99758c1a60
11 changed files with 444 additions and 30 deletions

18
pom.xml
View File

@@ -91,8 +91,22 @@
<properties> <properties>
<!-- 环境标识,需要与配置文件的名称相对应 --> <!-- 环境标识,需要与配置文件的名称相对应 -->
<profiles.active>dev</profiles.active> <profiles.active>dev</profiles.active>
<nacos.server>by.missmoc.top:8848</nacos.server> <nacos.server>10.20.1.60:8848</nacos.server>
<logstash.address>by.missmoc.top:4560</logstash.address> <logstash.address>10.20.1.60:4560</logstash.address>
<nacos.discovery.group>DEFAULT_GROUP</nacos.discovery.group>
<nacos.config.group>DEFAULT_GROUP</nacos.config.group>
<nacos.username>nacos</nacos.username>
<nacos.password>nacos</nacos.password>
</properties>
</profile>
<profile>
<id>test</id>
<properties>
<!-- 环境标识,需要与配置文件的名称相对应 -->
<profiles.active>test</profiles.active>
<nacos.server>192.168.24.101:8848</nacos.server>
<logstash.address>192.168.24.101:4560</logstash.address>
<nacos.discovery.group>DEFAULT_GROUP</nacos.discovery.group> <nacos.discovery.group>DEFAULT_GROUP</nacos.discovery.group>
<nacos.config.group>DEFAULT_GROUP</nacos.config.group> <nacos.config.group>DEFAULT_GROUP</nacos.config.group>
<nacos.username>nacos</nacos.username> <nacos.username>nacos</nacos.username>

View File

@@ -3,7 +3,6 @@ package org.dromara.sis.sdk.hik;
import com.sun.jna.*; import com.sun.jna.*;
import com.sun.jna.examples.win32.W32API; import com.sun.jna.examples.win32.W32API;
import com.sun.jna.examples.win32.W32API.HWND; import com.sun.jna.examples.win32.W32API.HWND;
import com.sun.jna.ptr.ByteByReference;
import com.sun.jna.ptr.IntByReference; import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.ShortByReference; import com.sun.jna.ptr.ShortByReference;
@@ -9711,6 +9710,10 @@ DVR实现巡航数据结构
boolean NET_DVR_StopGetFile(int lFileHandle); boolean NET_DVR_StopGetFile(int lFileHandle);
int NET_DVR_StartDownload(int lUserID, int dwDownloadType, NET_DVR_DOWNLOAD_FILEVOLUME_PARAM lpInBuffer, int dwInBufferSize, String sFileName);
boolean NET_DVR_StopDownload(int lDownloadHandle);
int NET_DVR_GetDownloadPos(int lFileHandle); int NET_DVR_GetDownloadPos(int lFileHandle);
int NET_DVR_GetPlayBackPos(int lPlayHandle); int NET_DVR_GetPlayBackPos(int lPlayHandle);
@@ -10682,6 +10685,46 @@ DVR实现巡航数据结构
public byte byWorkMode ;//云台锁定控制0- 解锁1- 锁定 public byte byWorkMode ;//云台锁定控制0- 解锁1- 锁定
public byte[] byRes = new byte[123]; public byte[] byRes = new byte[123];
} }
/**
* sdk 下载图片回调
*/
interface DownloadCallBack extends Callback {
public void invoke(int lHandle, NET_DOWNLOAD_CB_INFO pCBInfo, Pointer pUserData);
}
class NET_DOWNLOAD_CB_INFO extends SDK_Structure {
public int dwType;
public Pointer pData;
public int dwDataLen;
public Pointer pFileInfo;
public int dwFileInfoLen;
public byte[] byRes = new byte[120];
}
class NET_DVR_DOWNLOAD_FILEVOLUME_PARAM extends SDK_Structure {
private static final int MAX_URL_LEN = 240;
// 结构体大小
public int dwSize;
// 文件上传路径
public byte[] sUrl = new byte[MAX_URL_LEN];
// 保留置为0
public byte[] byRes = new byte[248];
public DownloadCallBack fnDownloadDataCB;
public Pointer pUserData;
}
class NET_DVR_UPLOAD_FILEVOLUME_PRARAM extends SDK_Structure {
private static final int NET_SDK_MAX_FILE_NAME = 100;
public int dwSize;
public byte byFileType;
public byte byFileVolnumeID;
public byte[] byRes1 = new byte[2];
public int dwFileSize;
public byte[] szFileName = new byte[NET_SDK_MAX_FILE_NAME];
public byte[] byRes = new byte[300];
}
} }

View File

@@ -1,20 +0,0 @@
package org.dromara.sis.sdk.hik;
public interface HikApi {
Boolean login(String ip, short port, String user, String psw);
void getAcsStatus(String deviceIp);
Boolean controlGateway(String deviceIp, Integer floorNum, Integer cmd);
/**
* 设备登出
*
* @param deviceIp 设备ip
*/
Boolean loginOut(String deviceIp);
}

View File

@@ -1,12 +1,16 @@
package org.dromara.sis.sdk.hik; package org.dromara.sis.sdk.hik;
import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.Base64Utils;
import org.dromara.sis.sdk.hik.model.DeviceInfo; import org.dromara.sis.sdk.hik.model.DeviceInfo;
import org.dromara.sis.sdk.hik.service.CamearService; import org.dromara.sis.sdk.hik.model.upload.FaceAppendData;
import org.dromara.sis.sdk.hik.service.DoorService; import org.dromara.sis.sdk.hik.model.upload.UploadStatus;
import org.dromara.sis.sdk.hik.service.LoginService; import org.dromara.sis.sdk.hik.service.*;
import org.dromara.sis.sdk.hik.service.SdkBaseServer;
import java.io.FileInputStream;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
/** /**
@@ -151,4 +155,116 @@ public class HikApiService {
Integer lHandel = CACHE.get(deviceIp); Integer lHandel = CACHE.get(deviceIp);
return CamearService.getDeviceChannelInfo(lHandel); return CamearService.getDeviceChannelInfo(lHandel);
} }
// 允许上传的文件类型
private static final List<String> ALLOW_PIC_TYPES = Arrays.asList("jpg");
/**
* 人像比对文件上传
*
* @param deviceIp 设备ip
* @param file 上传文件
* @return 返回上传文件名称
*/
public static String fdLibUpload(String deviceIp, byte[] file, int libId, FaceAppendData data) {
long startTime = System.currentTimeMillis();
// 文件名称
String fileNamePrefix = IdUtil.fastSimpleUUID();
int luserId = CACHE.get(deviceIp);
if (luserId == -1) {
log.error("海康设备[{}]未登录,上传文件失败.", deviceIp);
return null;
}
int uploadId = -1;
// 获取文件类型
String type = Base64Utils.getType(file);
log.info("获取上传的文件类型,type: {}", type);
if (type == null) {
log.error("上传文件获取类型失败.");
return null;
}
if (!ALLOW_PIC_TYPES.contains(type)) {
log.error("不允许的上传文件类型, type: {}", type);
return null;
}
// 生成文件名称
String fileName = fileNamePrefix + "." + type;
// 上传状态
int statusCode = -1;
// 已发送的数据
int fileLen = file.length;
log.info("开始文件上传fileName={} len={}", fileName, fileLen);
try {
// 1 发起上传
uploadId = FdLibUploadService.uploadFile(luserId, libId);
log.info("开始发起hik上传操作uploadId={}", uploadId);
if (uploadId == -1) {
log.info("发起上传失败,errCode: {}", FdLibUploadService.getErrorCode());
return null;
}
// 2 发送图片的二进制数据
byte fileType = getFileType(type);
FdLibUploadService.uploadSend(uploadId, file, fileType, data);
// 3 循环查询, 上传进度 总共查询300次 每次等待20ms
int num = 200; // 循环次数
int sleepTime = 40; // 每次循环休眠时间
int allowQueryErrorNum = 5; // 最多允许接口调用异常
int errorNum = 0; // 错误次数
for (int i = 0; i < num; i++) {
UploadStatus uploadState = FdLibUploadService.getUploadState(uploadId);
log.info("循环获取文件上传进度, uploadState= {}", uploadState);
if (uploadState.getStatusCode() == -1) {
int errorCode = FdLibUploadService.getErrorCode();
log.info("文件进度查询失败, errorCode:{}", errorCode);
errorNum += 1;
if (errorNum == allowQueryErrorNum) {
log.info("sdk 接口错误次数达到设置上限,退出文件上传");
throw new RuntimeException("sdk接口错误次数达到设置上限退出文件上传 maxErrorNum=" + allowQueryErrorNum);
}
} else {
// 上传成功
if (uploadState.getStatusCode() == 1) {
statusCode = 1;
break;
}
// 上传中
else if (uploadState.getStatusCode() == 2) {
Thread.sleep(sleepTime);
}
// 上传失败
else if (uploadState.getStatusCode() == 3) {
throw new RuntimeException("文件上传失败, 返回码3");
}
// 其他异常
else {
throw new RuntimeException("文件上传失败, 返回码:" + uploadState);
}
}
}
if (statusCode == -1) {
log.info("图片上传失败,取消上传.");
return null;
}
// 4 获取上传图片的路径
String uploadResult = FdLibUploadService.getUploadResult(uploadId);
log.info("文件上传完成, result= {}, 耗时={}ms", uploadResult, System.currentTimeMillis() - startTime);
return uploadResult;
} catch (Exception e) {
if (uploadId != -1) {
FdLibUploadService.uploadClose(uploadId);
}
e.printStackTrace();
}
return null;
}
private static byte getFileType(String type) {
return switch (type) {
case "jpg" -> 1;
case "png" -> 3;
case "bmp" -> 2;
case "gif" -> 5;
default -> throw new RuntimeException("不允许上传的图片类型:" + type);
};
}
} }

View File

@@ -35,7 +35,7 @@ public class HikAlarmCallBack implements HCNetSDK.FMSGCallBack_V31 {
Pointer pFaceSnapInfo = strFaceSnapInfo.getPointer(); Pointer pFaceSnapInfo = strFaceSnapInfo.getPointer();
pFaceSnapInfo.write(0, pAlarmInfo.getByteArray(0, strFaceSnapInfo.size()), 0, strFaceSnapInfo.size()); pFaceSnapInfo.write(0, pAlarmInfo.getByteArray(0, strFaceSnapInfo.size()), 0, strFaceSnapInfo.size());
strFaceSnapInfo.read(); strFaceSnapInfo.read();
handleFaceSnap(strFaceSnapInfo, pAlarmer); // handleFaceSnap(strFaceSnapInfo, pAlarmer);
break; break;
} }
// 异常行为检测 // 异常行为检测

View File

@@ -0,0 +1,72 @@
package org.dromara.sis.sdk.hik.model.upload;
import jakarta.xml.bind.annotation.*;
import lombok.Data;
/**
* 上传图片的附件信息
*
* @author lxj
*/
@Data
@XmlRootElement(name = "FaceAppendData")
@XmlAccessorType(XmlAccessType.FIELD)
public class FaceAppendData {
@XmlAttribute
private String version = "2.0";
@XmlAttribute
private String xmlns = "http://www.isapi.org/ver20/XMLSchema";
/**
* 出生日期,例如:2014-12-12T00:00:00Z
*/
@XmlElement
private String bornTime;
@XmlElement
private String name;
/**
* male, female
*/
@XmlElement
private String sex;
/**
* 省份,代码索引具体请参考"全国各省份城市列表
*/
@XmlElement
private String province;
/**
* ,城市,代码索引具体请参考"全国各省份城市列表
*/
@XmlElement
private String city;
/**
* 证件类型: ID-身份证, passportID-护照, other-其他
*/
@XmlElement
private String certificateType;
/**
* 证件号
*/
@XmlElement
private String certificateNumber;
/**
* 电话号码
*/
@XmlElement
private String phoneNumber;
/**
* 自定人员id
*/
@XmlElement
private String customHumanID;
}

View File

@@ -0,0 +1,11 @@
package org.dromara.sis.sdk.hik.model.upload;
import com.sun.jna.Memory;
public class SimpleMemory extends Memory {
public SimpleMemory(long size) {
super(size);
}
}

View File

@@ -0,0 +1,18 @@
package org.dromara.sis.sdk.hik.model.upload;
import lombok.Data;
@Data
public class UploadStatus {
/**
* 上传状态码
*/
private Integer statusCode;
/**
* 上传进度
*/
private Integer process;
}

View File

@@ -0,0 +1,4 @@
package org.dromara.sis.sdk.hik.service;
public class FaceService extends SdkBaseServer{
}

View File

@@ -0,0 +1,155 @@
package org.dromara.sis.sdk.hik.service;
import cn.hutool.core.util.StrUtil;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Marshaller;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.Base64Utils;
import org.dromara.sis.sdk.hik.HCNetSDK;
import org.dromara.sis.sdk.hik.model.upload.FaceAppendData;
import org.dromara.sis.sdk.hik.model.upload.SimpleMemory;
import org.dromara.sis.sdk.hik.model.upload.UploadStatus;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import static org.dromara.sis.sdk.hik.HCNetSDK.IMPORT_DATA_TO_FACELIB;
/**
* 海康上传服务
*/
@Slf4j
public class FdLibUploadService extends SdkBaseServer {
/**
* 上传文件到人脸库
*
* @param luserId 登录句柄
* @return 返回上传句柄
*/
public static int uploadFile(int luserId, int libId) {
HCNetSDK.NET_DVR_FACELIB_COND param = new HCNetSDK.NET_DVR_FACELIB_COND();
param.dwSize = param.size();
byte[] bytes = String.valueOf(libId).getBytes();
System.arraycopy(bytes, 0, param.szFDID, 0, bytes.length);
param.byConcurrent = 0; // 设备并发处理0- 不开启(设备自动会建模)1- 开始(设备不会自动进行建模)
param.byCover = 1;
param.byCustomFaceLibID = 0;
param.byPictureSaveMode = 0;
int dwInBufferSize = param.size();
param.write();
return hcNetSDK.NET_DVR_UploadFile_V40(luserId, IMPORT_DATA_TO_FACELIB, param.getPointer(), dwInBufferSize, null, null, 0);
}
/**
* 上传数据发送
*
* @param lUploadHandle 文件上传句柄NET_DVR_UploadFile_V40的返回值
* @param data 上传文件的二进制数据
* @param fileType 文件的类型
* @return -1表示失败大于等于0表示本次发送成功的字节数未发送成功的剩余字节数可以再次调用该接口进行发送。
*/
public static int uploadSend(int lUploadHandle, byte[] data, byte fileType, FaceAppendData userInfo) throws JAXBException {
HCNetSDK.NET_DVR_SEND_PARAM_IN param = new HCNetSDK.NET_DVR_SEND_PARAM_IN();
SimpleMemory memory = new SimpleMemory(data.length);
memory.write(0, data, 0, data.length);
// 设置人像附加信息
JAXBContext context = JAXBContext.newInstance(FaceAppendData.class);
Marshaller marshaller = context.createMarshaller();
// 转换为字符串
StringWriter writer = new StringWriter();
marshaller.marshal(userInfo, writer);
String xmlString = writer.toString();
byte[] faceBytes = xmlString.getBytes();
SimpleMemory addendData = new SimpleMemory(faceBytes.length);
addendData.write(0, faceBytes, 0, faceBytes.length);
param.pSendData = memory;
param.dwSendDataLen = data.length;
param.byPicType = fileType;
param.pSendAppendData = addendData;
param.dwSendAppendDataLen = faceBytes.length;
return hcNetSDK.NET_DVR_UploadSend(lUploadHandle, param, null);
}
/**
* 查询上传数据进度
*
* @param lUploadHandle 文件上传句柄NET_DVR_UploadFile_V40的返回值
* @return -1表示函数调用失败其他为上传的状态值见下表说明。接口返回失败请调用NET_DVR_GetLastError获取错误码通过错误码判断出错原因。
* -1表示调用失败
* 1上传成功
* 2正在上传
* 3上传失败
* 其他状态码请参考sdk开发文档
*/
public static UploadStatus getUploadState(int lUploadHandle) {
IntByReference reference = new IntByReference(0);
int i = hcNetSDK.NET_DVR_GetUploadState(lUploadHandle, reference.getPointer());
UploadStatus status = new UploadStatus();
status.setStatusCode(i);
status.setProcess(reference.getValue());
return status;
}
public static String getUploadResult(int lUploadHandle) {
HCNetSDK.NET_DVR_UPLOAD_FILE_RET params = new HCNetSDK.NET_DVR_UPLOAD_FILE_RET();
params.write();
Pointer lpOutBuffer = params.getPointer();
int dwOutBufferSize = params.size();
boolean b = hcNetSDK.NET_DVR_GetUploadResult(lUploadHandle, lpOutBuffer, dwOutBufferSize);
if (b) {
params.read();
String url = new String(params.sUrl, StandardCharsets.UTF_8);
if (StrUtil.isNotEmpty(url)) {
return url.trim();
}
}
return null;
}
public static boolean uploadClose(int lUploadHandle) {
// TRUE表示成功FALSE表示失败。接口返回失败请调用NET_DVR_GetLastError获取错误码通过错误码判断出错原因。
boolean b = hcNetSDK.NET_DVR_UploadClose(lUploadHandle);
if (b) {
log.info("文件上传关闭成功, lUploadHandle:{}", lUploadHandle);
} else {
int errorCode = hcNetSDK.NET_DVR_GetLastError();
System.out.println("文件上传关闭失败, errorCode:" + errorCode);
}
return b;
}
public static int download(int luserId, String url, HCNetSDK.DownloadCallBack callback) {
HCNetSDK.NET_DVR_DOWNLOAD_FILEVOLUME_PARAM param = new HCNetSDK.NET_DVR_DOWNLOAD_FILEVOLUME_PARAM();
byte[] fns = url.getBytes();
System.arraycopy(fns, 0, param.sUrl, 0, fns.length);
param.fnDownloadDataCB = callback;
param.write();
param.dwSize = param.size();
int i = hcNetSDK.NET_DVR_StartDownload(luserId, 20, param, param.dwSize, null);
if (i == -1) {
log.info("发起图片下载失败lDownloadHandle={}, errorCode={}", i, getErrorCode());
}
return i;
}
/**
* 关闭文件下载
*
* @param lDownloadHandle 下载句柄
*/
public static void stopDownload(int lDownloadHandle) {
boolean b = hcNetSDK.NET_DVR_StopDownload(lDownloadHandle);
if (b) {
log.info("文件下载关闭成功, lDownloadHandle={}", lDownloadHandle);
} else {
System.out.println("文件下载关闭失败, errorCode:" + getErrorCode());
}
}
}

View File

@@ -1,11 +1,9 @@
package org.dromara.sis.sdk.hik.service; package org.dromara.sis.sdk.hik.service;
import com.sun.jna.Memory;
import com.sun.jna.Native; import com.sun.jna.Native;
import com.sun.jna.Pointer; import com.sun.jna.Pointer;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.sis.sdk.hik.HCNetSDK; import org.dromara.sis.sdk.hik.HCNetSDK;
import org.dromara.sis.sdk.hik.SDK_Structure;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import static org.dromara.sis.sdk.hik.common.osSelect.isWindows; import static org.dromara.sis.sdk.hik.common.osSelect.isWindows;
@@ -120,6 +118,9 @@ public class SdkBaseServer {
} }
} }
public static int getErrorCode() {
return getHcNetSDK().NET_DVR_GetLastError();
}
/** /**
* 登出操作 * 登出操作