You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

186 lines
6.7 KiB

package com.inspect.nvr.service.camera;
import com.inspect.nvr.domain.Infrared.Camera;
import com.inspect.nvr.domain.Infrared.TemperatureData;
import com.inspect.nvr.enums.CameraEnum;
import lombok.extern.slf4j.Slf4j;
import java.nio.file.Files;
import java.nio.file.Path;
/**
* 品牌相机服务抽象基类。
* 统一抓图、云台预置位、测温的并发控制与重试模板,子类只实现品牌 SDK 差异钩子。
* 并发控制、统一重试等基础设施见 AbstractCameraSupport。
*/
@Slf4j
public abstract class AbstractCameraService extends AbstractCameraSupport
implements BrandCameraService {
// ------------------------------------------------------------------
// 品牌差异钩子
// ------------------------------------------------------------------
/**
* 品牌对应的摄像头枚举,brandName/fileFlag 均由它派生。
*/
@Override
public abstract CameraEnum cameraType();
/**
* 日志品牌名:取枚举 name 字段(中文名),如 大华/海康/朗驰。
*/
@Override
protected String brandName() {
return cameraType().getName();
}
/**
* 抓图文件名前缀:取枚举 value,如 dahua/hik/linc。
*/
protected String fileFlag() {
return cameraType().getValue();
}
/**
* 单次 SDK 抓图,失败返回 null 或抛异常,由模板负责重试与落盘。
*/
protected abstract byte[] doCapture(Camera camera) throws Exception;
/**
* 单次 SDK 预置位跳转,成功 true / 失败 false。
*/
protected abstract boolean doGotoPreset(Camera camera) throws Exception;
/**
* 单次 SDK 测温,失败返回 null 或抛异常,由模板负责重试。各品牌必须实现。
*/
protected abstract TemperatureData doMeasureTemperature(Camera camera) throws Exception;
/**
* Digest 认证抓图 URL 模板;不支持 Digest 兜底的品牌返回 null。
*/
protected String digestUrlTemplate() {
return null;
}
/**
* 是否支持 DLT664 抓图,默认不支持,支持的品牌重写为 true。
*/
protected boolean supportsDlt664() {
return false;
}
// ------------------------------------------------------------------
// 抓图模板
// ------------------------------------------------------------------
@Override
public byte[] capture(Camera camera) {
return withConcurrencyControl(camera, () -> captureWithRetry(camera));
}
private byte[] captureWithRetry(Camera camera) {
Path fullPath = getFullPath(fileFlag(), camera.getIp(), camera.getChannel());
// 抓图与预置位/测温共用统一重试模板:取图+落盘在一次尝试内,返回 null 触发重试
byte[] imageBytes = withRetry(camera, "抓图", () -> {
byte[] data = doCapture(camera);
if (data == null) {
return null;
}
Files.write(fullPath, data);
log.info("[{}]抓图成功:{}", brandName(), fullPath);
return data;
});
if (imageBytes != null) {
return imageBytes;
}
// SDK 抓图全部失败后,尝试 Digest 认证抓图兜底(3参静态方法,避免重复进入并发控制)
String digestUrlTemplate = digestUrlTemplate();
if (digestUrlTemplate != null) {
byte[] digestBytes = captureDigest(camera, digestUrlTemplate, fullPath);
if (digestBytes != null) {
log.info("[{}]digest抓图成功,图片地址:{}", brandName(), fullPath);
return digestBytes;
}
}
writeCaptureFailedImage(fullPath);
log.error("[{}]抓图失败,图片地址:{}", brandName(), fullPath);
return null;
}
/**
* Digest 认证抓图入口;不支持 Digest 的品牌返回 null。
*/
public byte[] captureDigest(Camera camera) {
String digestUrlTemplate = digestUrlTemplate();
if (digestUrlTemplate == null) {
log.warn("[{}]不支持Digest认证抓图", brandName());
return null;
}
Path fullPath = getFullPath(fileFlag() + "_digest", camera.getIp(), camera.getChannel());
return withConcurrencyControl(camera,
() -> captureDigest(camera, digestUrlTemplate, fullPath));
}
/**
* DLT664 抓图:不支持时返回 null;支持的品牌其 doCapture 已内嵌温度数据,直接复用抓图模板。
*/
@Override
public byte[] captureDlt664(Camera camera) {
if (!supportsDlt664()) {
log.warn("[{}]不支持DLT664抓图", brandName());
return null;
}
return capture(camera);
}
// ------------------------------------------------------------------
// 云台模板
// ------------------------------------------------------------------
@Override
public boolean gotoPreset(Camera camera) {
return withConcurrencyControl(camera, () -> {
Boolean result = withRetry(camera, "预置位跳转", () -> doGotoPreset(camera));
return Boolean.TRUE.equals(result);
});
}
/**
* 组合操作模板:一次预置位跳转 + 一次测温视为一次尝试,任一步失败则整对重试。
*/
@Override
public TemperatureData gotoPresetAndMeasure(Camera ptzCamera, Camera thermalCamera) {
Camera lockCamera = ptzCamera != null ? ptzCamera : thermalCamera;
return withConcurrencyControl(lockCamera,
() -> withRetry(lockCamera, "预置位跳转+测温",
() -> doGotoPresetAndMeasure(ptzCamera, thermalCamera)));
}
/**
* 单次“预置位跳转 + 测温”,普通品牌无需重写;双模在复合实现中分别委托两个品牌。
*/
protected TemperatureData doGotoPresetAndMeasure(Camera ptzCamera, Camera thermalCamera) throws Exception {
if (!doGotoPreset(ptzCamera)) {
throw new RuntimeException("预置位跳转失败");
}
if (presetSettleMillis > 0) {
Thread.sleep(presetSettleMillis);
}
TemperatureData data = doMeasureTemperature(thermalCamera);
if (data == null) {
throw new RuntimeException("测温返回空");
}
return data;
}
// ------------------------------------------------------------------
// 测温模板
// ------------------------------------------------------------------
@Override
public TemperatureData measureTemperature(Camera camera) {
return withConcurrencyControl(camera,
() -> withRetry(camera, "测温", () -> doMeasureTemperature(camera)));
}
}