
1. 环境准备与SDK集成搞过多年的监控项目我发现海康摄像头的SDK集成其实没那么复杂关键是要把环境配对了。先说说我踩过的坑第一次集成时因为没注意32位和64位的区别折腾了一整天。SDK获取与配置海康官方SDK需要从官网下载注意区分Windows和Linux版本。我建议直接放在项目的resources/lib目录下这样打包部署都方便。Windows平台需要这两个关键文件HCNetSDK.dllPlayCtrl.dllLinux平台则是libhcnetsdk.solibPlayCtrl.soMaven依赖配置在pom.xml中添加JNA依赖这是调用海康SDK的关键dependency groupIdnet.java.dev.jna/groupId artifactIdjna/artifactId version5.10.0/version /dependencySDK初始化代码这个初始化方法我用了不下20个项目绝对靠谱public class HikvisionSDK { private static HCNetSDK hCNetSDK HCNetSDK.INSTANCE; public static boolean init() { // 设置SDK日志路径排查问题时特别有用 hCNetSDK.NET_DVR_SetLogToFile(3, ./sdklog, true); // 设置连接超时单位毫秒 hCNetSDK.NET_DVR_SetConnectTime(2000, 1); // 初始化SDK return hCNetSDK.NET_DVR_Init(); } }2. 多摄像头设备管理管理多个摄像头时最头疼的就是设备连接状态维护。我设计了个设备管理类用Map保存设备句柄实测可以稳定管理50摄像头。设备登录实现这段登录代码我优化过三次现在的版本最稳定public class DeviceManager { private static MapString, Integer deviceMap new ConcurrentHashMap(); public static int login(String ip, short port, String username, String password) { HCNetSDK.NET_DVR_USER_LOGIN_INFO loginInfo new HCNetSDK.NET_DVR_USER_LOGIN_INFO(); HCNetSDK.NET_DVR_DEVICEINFO_V40 deviceInfo new HCNetSDK.NET_DVR_DEVICEINFO_V40(); // 设置登录参数 System.arraycopy(ip.getBytes(), 0, loginInfo.sDeviceAddress, 0, ip.length()); System.arraycopy(username.getBytes(), 0, loginInfo.sUserName, 0, username.length()); System.arraycopy(password.getBytes(), 0, loginInfo.sPassword, 0, password.length()); loginInfo.wPort port; int userId hCNetSDK.NET_DVR_Login_V40(loginInfo, deviceInfo); if(userId ! -1) { deviceMap.put(ip, userId); log.info(摄像头{}登录成功通道数{}, ip, deviceInfo.struDeviceV30.byChanNum); } return userId; } }多设备管理技巧使用连接池管理设备连接定时心跳检测设备状态异常自动重连机制合理的连接超时设置建议2-3秒3. 布防与报警回调布防是监控系统的核心功能这里有个关键点JSON数据和图片数据分离配置。这个配置能让后续数据处理简单很多。布防配置代码这段配置代码来自海康官方文档但加了异常处理public class AlarmManager { public static int setupAlarm(int userId) { HCNetSDK.NET_DVR_SETUPALARM_PARAM alarmParam new HCNetSDK.NET_DVR_SETUPALARM_PARAM(); alarmParam.dwSize alarmParam.size(); alarmParam.byLevel 1; // 布防等级 alarmParam.byAlarmInfoType 1; // 使用新报警信息 // 关键配置分离JSON和图片数据 HCNetSDK.NET_DVR_LOCAL_GENERAL_CFG generalCfg new HCNetSDK.NET_DVR_LOCAL_GENERAL_CFG(); generalCfg.byAlarmJsonPictureSeparate 1; hCNetSDK.NET_DVR_SetSDKLocalCfg(17, generalCfg.getPointer()); int alarmHandle hCNetSDK.NET_DVR_SetupAlarmChan_V41(userId, alarmParam); if(alarmHandle -1) { log.error(布防失败错误码{}, hCNetSDK.NET_DVR_GetLastError()); } return alarmHandle; } }回调函数实现这个回调类处理各种报警事件特别注意车牌识别和图片保存public class AlarmCallback implements HCNetSDK.FMSGCallBack_V31 { Override public boolean invoke(int command, HCNetSDK.NET_DVR_ALARMER alarmer, Pointer alarmInfo, int bufLen, Pointer user) { // 车牌识别处理 if(command HCNetSDK.COMM_ITS_PLATE_RESULT) { handlePlateResult(alarmInfo); } return true; } private void handlePlateResult(Pointer alarmInfo) { HCNetSDK.NET_ITS_PLATE_RESULT plateResult new HCNetSDK.NET_ITS_PLATE_RESULT(); plateResult.write(); Pointer pPlateInfo plateResult.getPointer(); pPlateInfo.write(0, alarmInfo.getByteArray(0, plateResult.size()), 0, plateResult.size()); plateResult.read(); // 保存车牌图片 savePlateImage(plateResult); } }4. 图片存储与数据管理图片存储我推荐使用本地磁盘Redis缓存MySQL持久化的三层架构。实测这种架构能承受每秒100的抓拍请求。本地存储实现这段代码处理图片保存加入了日期目录public class ImageStorage { public static String saveImage(byte[] imageData, String plateNumber) { SimpleDateFormat dateFormat new SimpleDateFormat(yyyyMMdd); String dateDir dateFormat.format(new Date()); File dir new File(./images/ dateDir); if(!dir.exists()) { dir.mkdirs(); } String filename plateNumber _ System.currentTimeMillis() .jpg; try(FileOutputStream fos new FileOutputStream( new File(dir, filename))) { fos.write(imageData); return filename; } catch(Exception e) { log.error(图片保存失败, e); return null; } } }Redis缓存设计使用Redis的Hash结构存储最新抓拍数据public class RedisCache { Autowired private RedisTemplateString, Object redisTemplate; public void cachePlateInfo(String plateNumber, PlateInfo info) { redisTemplate.opsForHash().put( plate:latest, plateNumber, info); // 设置30天过期 redisTemplate.expire(plate:latest, 30, TimeUnit.DAYS); } }MySQL表设计这是经过优化的车牌数据表结构CREATE TABLE plate_records ( id bigint NOT NULL AUTO_INCREMENT, plate_number varchar(20) NOT NULL, capture_time datetime NOT NULL, image_path varchar(255) NOT NULL, vehicle_type tinyint DEFAULT NULL, camera_id varchar(50) DEFAULT NULL, PRIMARY KEY (id), KEY idx_plate_time (plate_number,capture_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;5. 定时任务与数据同步数据同步我推荐使用Spring Batch比普通定时任务更可靠。下面是我的实战配置Spring Batch配置这个配置实现了断点续传和批量提交Configuration EnableBatchProcessing public class BatchConfig { Autowired private JobBuilderFactory jobBuilderFactory; Autowired private StepBuilderFactory stepBuilderFactory; Bean public Job syncPlateJob() { return jobBuilderFactory.get(syncPlateJob) .start(syncStep()) .build(); } Bean public Step syncStep() { return stepBuilderFactory.get(syncStep) .PlateInfo, PlateInfochunk(100) .reader(plateReader()) .processor(plateProcessor()) .writer(plateWriter()) .build(); } }数据读取器实现从Redis读取待同步数据public class PlateReader implements ItemReaderPlateInfo { Autowired private RedisTemplateString, Object redisTemplate; private IteratorObject iterator; Override public PlateInfo read() { if(iterator null || !iterator.hasNext()) { ListObject values redisTemplate.opsForHash() .values(plate:sync); iterator values.iterator(); } return iterator.hasNext() ? (PlateInfo)iterator.next() : null; } }6. 异常处理与性能优化在监控项目中稳定性比功能更重要。这是我总结的几个关键点常见错误处理SDK初始化失败检查dll/so文件路径设备登录失败检查网络和账号权限布防失败确认设备支持智能分析功能回调不触发检查JSON和图片分离配置性能优化技巧使用连接池管理设备连接图片存储使用异步线程池Redis使用Pipeline批量操作MySQL批量插入使用rewriteBatchedStatementstrue监控指标设计建议监控这些关键指标设备在线率抓拍成功率图片处理延迟存储空间使用率7. 实战经验分享最后分享几个只有踩过坑才知道的经验海康SDK的线程安全问题SDK本身不是线程安全的建议所有SDK调用都放在同一个线程处理。我专门写了个单线程的Executor来处理所有SDK操作。内存泄漏排查长期运行后如果发现内存增长重点检查回调函数中的Pointer对象是否及时释放。我遇到过因为没释放Pointer导致OOM的情况。跨平台部署Linux下需要注意so文件的权限问题建议部署时执行chmod x *.so。还有glibc版本兼容性问题最好在相同版本的系统上编译。日志管理海康SDK的日志非常详细但如果不加控制会很快占满磁盘。建议配置日志轮转只保留最近7天的日志。