feat(doc): 添加图片篡改检测功能
- 新增 BForgeryDetectionHandle 处理类实现百度图片篡改检测API接口 - 添加 BForgeryDetectionRequest 请求参数类定义检测相关参数 - 添加 BForgeryDetectionResp 响应类处理检测结果和伪造区域信息 - 实现 ForgeryDetectionController 提供图片篡改检测接口服务 - 定义前端请求参数 ForgeryDetectionRequest 和响应数据 ForgeryDetectionResp - 添加完整的单元测试覆盖参数校验、内容拼接和业务逻辑 - 集成测试验证与百度API的连通性和返回结构正确性
This commit is contained in:
parent
d3689457cc
commit
4cfa182013
@ -0,0 +1,91 @@
|
||||
package com.heyu.api.baidu.handle.doc;
|
||||
|
||||
import com.heyu.api.baidu.BaiduBaseHandle;
|
||||
import com.heyu.api.baidu.request.doc.BForgeryDetectionRequest;
|
||||
import com.heyu.api.baidu.response.doc.BForgeryDetectionResp;
|
||||
import com.heyu.api.data.utils.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 图片篡改检测 - Handle
|
||||
*
|
||||
* 百度文档:https://cloud.baidu.com/doc/OCR/s/kmfdnccok
|
||||
* URI: /rest/2.0/ocr/v1/forgery_detection
|
||||
*/
|
||||
@Component
|
||||
public class BForgeryDetectionHandle extends BaiduBaseHandle<BForgeryDetectionRequest, BForgeryDetectionResp> {
|
||||
|
||||
private static final double THRESHOLD_MIN = 0.0001;
|
||||
private static final double THRESHOLD_MAX = 1.0;
|
||||
private static final double PROBABILITY_MIN = 0.1;
|
||||
private static final double PROBABILITY_MAX = 1.0;
|
||||
|
||||
@Override
|
||||
public String getUri() {
|
||||
return "/rest/2.0/ocr/v1/forgery_detection";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String check(BForgeryDetectionRequest request) {
|
||||
String imageError = checkImageUri(request);
|
||||
if (imageError != null) {
|
||||
return imageError;
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(request.getDetectProportion())) {
|
||||
if (checkNotTrueFalse(request.getDetectProportion())) {
|
||||
return "detectProportion 必须传 true 或 false";
|
||||
}
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(request.getDetectThreshold())) {
|
||||
try {
|
||||
double threshold = Double.parseDouble(request.getDetectThreshold());
|
||||
if (threshold < THRESHOLD_MIN || threshold > THRESHOLD_MAX) {
|
||||
return "detectThreshold 范围必须在 " + THRESHOLD_MIN + "~" + THRESHOLD_MAX + " 之间";
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
return "detectThreshold 必须为数字";
|
||||
}
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(request.getReturnHeatmap())) {
|
||||
if (checkNotTrueFalse(request.getReturnHeatmap())) {
|
||||
return "returnHeatmap 必须传 true 或 false";
|
||||
}
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(request.getRestrictProbability())) {
|
||||
try {
|
||||
double probability = Double.parseDouble(request.getRestrictProbability());
|
||||
if (probability < PROBABILITY_MIN || probability > PROBABILITY_MAX) {
|
||||
return "restrictProbability 范围必须在 " + PROBABILITY_MIN + "~" + PROBABILITY_MAX + " 之间";
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
return "restrictProbability 必须为数字";
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContent(BForgeryDetectionRequest request) {
|
||||
StringBuffer sb = getImageContent(request);
|
||||
|
||||
if (StringUtils.isNotBlank(request.getDetectProportion())) {
|
||||
sb.append("&detect_proportion=").append(request.getDetectProportion());
|
||||
}
|
||||
if (StringUtils.isNotBlank(request.getDetectThreshold())) {
|
||||
sb.append("&detect_threshold=").append(request.getDetectThreshold());
|
||||
}
|
||||
if (StringUtils.isNotBlank(request.getReturnHeatmap())) {
|
||||
sb.append("&return_heatmap=").append(request.getReturnHeatmap());
|
||||
}
|
||||
if (StringUtils.isNotBlank(request.getRestrictProbability())) {
|
||||
sb.append("&restrict_probability=").append(request.getRestrictProbability());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
package com.heyu.api.baidu.request.doc;
|
||||
|
||||
import com.heyu.api.baidu.request.BaiduImageUrlRequest;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 图片篡改检测 - 百度API请求参数
|
||||
*
|
||||
* 百度文档:https://cloud.baidu.com/doc/OCR/s/kmfdnccok
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class BForgeryDetectionRequest extends BaiduImageUrlRequest {
|
||||
|
||||
/**
|
||||
* 是否返回图片篡改置信度
|
||||
* - true:返回图片篡改置信度
|
||||
* - false:不返回(默认)
|
||||
*/
|
||||
private String detectProportion;
|
||||
|
||||
/**
|
||||
* 图片篡改检出阈值,范围0.0001~1,默认为0.9887
|
||||
* 当图片篡改置信度≥检出阈值时,篡改检测结果返回'有篡改',反之返回'无篡改'
|
||||
*/
|
||||
private String detectThreshold;
|
||||
|
||||
/**
|
||||
* 是否返回伪造区域热力图
|
||||
* - true:返回伪造区域热力图的base64编码
|
||||
* - false:不返回(默认)
|
||||
*/
|
||||
private String returnHeatmap;
|
||||
|
||||
/**
|
||||
* 返回伪造区域坐标的阈值,范围0.1~1,默认为0.8
|
||||
* 当伪造区域坐标置信度分数≥阈值时,tampered_location返回符合阈值的坐标信息
|
||||
*/
|
||||
private String restrictProbability;
|
||||
}
|
||||
@ -0,0 +1,96 @@
|
||||
package com.heyu.api.baidu.response.doc;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.heyu.api.baidu.response.BBaseResp;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 图片篡改检测 - 百度API响应
|
||||
*
|
||||
* 百度文档:https://cloud.baidu.com/doc/OCR/s/kmfdnccok
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
public class BForgeryDetectionResp extends BBaseResp {
|
||||
|
||||
@JsonProperty("log_id")
|
||||
private Long logId;
|
||||
|
||||
@JsonProperty("result")
|
||||
private ResultDTO result;
|
||||
|
||||
@JsonProperty("error_code")
|
||||
private Integer errorCode;
|
||||
|
||||
@JsonProperty("error_msg")
|
||||
private String errorMsg;
|
||||
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
public static class ResultDTO {
|
||||
|
||||
/**
|
||||
* 篡改检测结果
|
||||
* - fake:有篡改
|
||||
* - real:无篡改
|
||||
*/
|
||||
@JsonProperty("detection_result")
|
||||
private String detectionResult;
|
||||
|
||||
/**
|
||||
* 图片篡改置信度(当请求参数 detect_proportion = true 时返回)
|
||||
*/
|
||||
@JsonProperty("tampered_proportion")
|
||||
private Double tamperedProportion;
|
||||
|
||||
/**
|
||||
* 篡改区域热力图base64编码(当请求参数 return_heatmap = true 时返回)
|
||||
*/
|
||||
@JsonProperty("heatmap")
|
||||
private String heatmap;
|
||||
|
||||
/**
|
||||
* 伪造区域的坐标信息
|
||||
*/
|
||||
@JsonProperty("tampered_location")
|
||||
private List<TamperedLocationDTO> tamperedLocation;
|
||||
}
|
||||
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
public static class TamperedLocationDTO {
|
||||
|
||||
/**
|
||||
* 伪造区域的左上顶点的水平坐标
|
||||
*/
|
||||
@JsonProperty("left")
|
||||
private Integer left;
|
||||
|
||||
/**
|
||||
* 伪造区域的左上顶点的垂直坐标
|
||||
*/
|
||||
@JsonProperty("top")
|
||||
private Integer top;
|
||||
|
||||
/**
|
||||
* 伪造区域的宽度
|
||||
*/
|
||||
@JsonProperty("width")
|
||||
private Integer width;
|
||||
|
||||
/**
|
||||
* 伪造区域的高度
|
||||
*/
|
||||
@JsonProperty("height")
|
||||
private Integer height;
|
||||
|
||||
/**
|
||||
* 该区域伪造置信度分数
|
||||
*/
|
||||
@JsonProperty("probability")
|
||||
private Double probability;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,264 @@
|
||||
package com.heyu.api.baidu.handle.doc;
|
||||
|
||||
import com.heyu.api.baidu.request.doc.BForgeryDetectionRequest;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* BForgeryDetectionHandle 单元测试
|
||||
* 测试参数校验和内容拼接逻辑
|
||||
*/
|
||||
public class BForgeryDetectionHandleTest {
|
||||
|
||||
private BForgeryDetectionHandle handle;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
handle = new BForgeryDetectionHandle();
|
||||
}
|
||||
|
||||
// ========== URI 测试 ==========
|
||||
|
||||
@Test
|
||||
public void getUriTest() {
|
||||
assertEquals("/rest/2.0/ocr/v1/forgery_detection", handle.getUri());
|
||||
}
|
||||
|
||||
// ========== 图片校验测试 ==========
|
||||
|
||||
@Test
|
||||
public void checkImageAndUrlBothEmptyTest() {
|
||||
BForgeryDetectionRequest request = new BForgeryDetectionRequest();
|
||||
String error = handle.check(request);
|
||||
assertNotNull(error);
|
||||
assertTrue(error.contains("不能都为空"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkWithImageBase64Test() {
|
||||
BForgeryDetectionRequest request = new BForgeryDetectionRequest();
|
||||
request.setImageBase64("base64Data");
|
||||
String error = handle.check(request);
|
||||
assertNull(error);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkWithImageUrlTest() {
|
||||
BForgeryDetectionRequest request = new BForgeryDetectionRequest();
|
||||
request.setImageUrl("https://example.com/image.jpg");
|
||||
String error = handle.check(request);
|
||||
assertNull(error);
|
||||
}
|
||||
|
||||
// ========== detectProportion 校验测试 ==========
|
||||
|
||||
@Test
|
||||
public void checkDetectProportionValidTrueTest() {
|
||||
BForgeryDetectionRequest request = new BForgeryDetectionRequest();
|
||||
request.setImageBase64("data");
|
||||
request.setDetectProportion("true");
|
||||
assertNull(handle.check(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkDetectProportionValidFalseTest() {
|
||||
BForgeryDetectionRequest request = new BForgeryDetectionRequest();
|
||||
request.setImageBase64("data");
|
||||
request.setDetectProportion("false");
|
||||
assertNull(handle.check(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkDetectProportionInvalidTest() {
|
||||
BForgeryDetectionRequest request = new BForgeryDetectionRequest();
|
||||
request.setImageBase64("data");
|
||||
request.setDetectProportion("yes");
|
||||
String error = handle.check(request);
|
||||
assertNotNull(error);
|
||||
assertTrue(error.contains("detectProportion"));
|
||||
}
|
||||
|
||||
// ========== detectThreshold 校验测试 ==========
|
||||
|
||||
@Test
|
||||
public void checkDetectThresholdMinValidTest() {
|
||||
BForgeryDetectionRequest request = new BForgeryDetectionRequest();
|
||||
request.setImageBase64("data");
|
||||
request.setDetectThreshold("0.0001");
|
||||
assertNull(handle.check(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkDetectThresholdMaxValidTest() {
|
||||
BForgeryDetectionRequest request = new BForgeryDetectionRequest();
|
||||
request.setImageBase64("data");
|
||||
request.setDetectThreshold("1");
|
||||
assertNull(handle.check(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkDetectThresholdTooSmallTest() {
|
||||
BForgeryDetectionRequest request = new BForgeryDetectionRequest();
|
||||
request.setImageBase64("data");
|
||||
request.setDetectThreshold("0.00001");
|
||||
String error = handle.check(request);
|
||||
assertNotNull(error);
|
||||
assertTrue(error.contains("detectThreshold"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkDetectThresholdTooLargeTest() {
|
||||
BForgeryDetectionRequest request = new BForgeryDetectionRequest();
|
||||
request.setImageBase64("data");
|
||||
request.setDetectThreshold("1.1");
|
||||
String error = handle.check(request);
|
||||
assertNotNull(error);
|
||||
assertTrue(error.contains("detectThreshold"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkDetectThresholdNotNumberTest() {
|
||||
BForgeryDetectionRequest request = new BForgeryDetectionRequest();
|
||||
request.setImageBase64("data");
|
||||
request.setDetectThreshold("abc");
|
||||
String error = handle.check(request);
|
||||
assertNotNull(error);
|
||||
assertTrue(error.contains("detectThreshold"));
|
||||
}
|
||||
|
||||
// ========== returnHeatmap 校验测试 ==========
|
||||
|
||||
@Test
|
||||
public void checkReturnHeatmapValidTest() {
|
||||
BForgeryDetectionRequest request = new BForgeryDetectionRequest();
|
||||
request.setImageBase64("data");
|
||||
request.setReturnHeatmap("true");
|
||||
assertNull(handle.check(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkReturnHeatmapInvalidTest() {
|
||||
BForgeryDetectionRequest request = new BForgeryDetectionRequest();
|
||||
request.setImageBase64("data");
|
||||
request.setReturnHeatmap("1");
|
||||
String error = handle.check(request);
|
||||
assertNotNull(error);
|
||||
assertTrue(error.contains("returnHeatmap"));
|
||||
}
|
||||
|
||||
// ========== restrictProbability 校验测试 ==========
|
||||
|
||||
@Test
|
||||
public void checkRestrictProbabilityMinValidTest() {
|
||||
BForgeryDetectionRequest request = new BForgeryDetectionRequest();
|
||||
request.setImageBase64("data");
|
||||
request.setRestrictProbability("0.1");
|
||||
assertNull(handle.check(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkRestrictProbabilityMaxValidTest() {
|
||||
BForgeryDetectionRequest request = new BForgeryDetectionRequest();
|
||||
request.setImageBase64("data");
|
||||
request.setRestrictProbability("1");
|
||||
assertNull(handle.check(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkRestrictProbabilityTooSmallTest() {
|
||||
BForgeryDetectionRequest request = new BForgeryDetectionRequest();
|
||||
request.setImageBase64("data");
|
||||
request.setRestrictProbability("0.05");
|
||||
String error = handle.check(request);
|
||||
assertNotNull(error);
|
||||
assertTrue(error.contains("restrictProbability"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkRestrictProbabilityTooLargeTest() {
|
||||
BForgeryDetectionRequest request = new BForgeryDetectionRequest();
|
||||
request.setImageBase64("data");
|
||||
request.setRestrictProbability("1.5");
|
||||
String error = handle.check(request);
|
||||
assertNotNull(error);
|
||||
assertTrue(error.contains("restrictProbability"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkRestrictProbabilityNotNumberTest() {
|
||||
BForgeryDetectionRequest request = new BForgeryDetectionRequest();
|
||||
request.setImageBase64("data");
|
||||
request.setRestrictProbability("xyz");
|
||||
String error = handle.check(request);
|
||||
assertNotNull(error);
|
||||
assertTrue(error.contains("restrictProbability"));
|
||||
}
|
||||
|
||||
// ========== 可选参数为空时不校验 ==========
|
||||
|
||||
@Test
|
||||
public void checkOptionalParamsNullTest() {
|
||||
BForgeryDetectionRequest request = new BForgeryDetectionRequest();
|
||||
request.setImageBase64("data");
|
||||
// 所有可选参数都不设置
|
||||
assertNull(handle.check(request));
|
||||
}
|
||||
|
||||
// ========== getContent 内容拼接测试 ==========
|
||||
|
||||
@Test
|
||||
public void getContentImageOnlyTest() {
|
||||
BForgeryDetectionRequest request = new BForgeryDetectionRequest();
|
||||
request.setImageBase64("base64Img");
|
||||
String content = handle.getContent(request);
|
||||
assertTrue(content.contains("&image=base64Img"));
|
||||
assertFalse(content.contains("&url="));
|
||||
assertFalse(content.contains("&detect_proportion="));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getContentUrlOnlyTest() {
|
||||
BForgeryDetectionRequest request = new BForgeryDetectionRequest();
|
||||
request.setImageUrl("https://example.com/img.jpg");
|
||||
String content = handle.getContent(request);
|
||||
assertTrue(content.contains("&url=https://example.com/img.jpg"));
|
||||
assertFalse(content.contains("&image="));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getContentAllParamsTest() {
|
||||
BForgeryDetectionRequest request = new BForgeryDetectionRequest();
|
||||
request.setImageBase64("base64Img");
|
||||
request.setDetectProportion("true");
|
||||
request.setDetectThreshold("0.9");
|
||||
request.setReturnHeatmap("true");
|
||||
request.setRestrictProbability("0.8");
|
||||
|
||||
String content = handle.getContent(request);
|
||||
|
||||
assertTrue(content.contains("&image=base64Img"));
|
||||
assertTrue(content.contains("&detect_proportion=true"));
|
||||
assertTrue(content.contains("&detect_threshold=0.9"));
|
||||
assertTrue(content.contains("&return_heatmap=true"));
|
||||
assertTrue(content.contains("&restrict_probability=0.8"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getContentOptionalParamsNullTest() {
|
||||
BForgeryDetectionRequest request = new BForgeryDetectionRequest();
|
||||
request.setImageBase64("base64Img");
|
||||
// 不设置任何可选参数
|
||||
String content = handle.getContent(request);
|
||||
assertTrue(content.contains("&image=base64Img"));
|
||||
assertFalse(content.contains("&detect_proportion="));
|
||||
assertFalse(content.contains("&detect_threshold="));
|
||||
assertFalse(content.contains("&return_heatmap="));
|
||||
assertFalse(content.contains("&restrict_probability="));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,101 @@
|
||||
package com.heyu.api.controller.doc;
|
||||
|
||||
import com.heyu.api.baidu.handle.doc.BForgeryDetectionHandle;
|
||||
import com.heyu.api.baidu.request.doc.BForgeryDetectionRequest;
|
||||
import com.heyu.api.baidu.response.doc.BForgeryDetectionResp;
|
||||
import com.heyu.api.data.annotation.NotIntercept;
|
||||
import com.heyu.api.data.utils.ApiR;
|
||||
import com.heyu.api.data.utils.R;
|
||||
import com.heyu.api.request.doc.ForgeryDetectionRequest;
|
||||
import com.heyu.api.resp.doc.ForgeryDetectionResp;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 图片篡改检测
|
||||
*
|
||||
* 基于深度神经网络与跨模态分析技术,精准检测伪造图像,
|
||||
* 支持返回图像篡改检测结果及伪造区域坐标;
|
||||
* 支持对图像中的伪造区域以热力图形式进行可视化返回。
|
||||
*
|
||||
* 百度文档:https://cloud.baidu.com/doc/OCR/s/kmfdnccok
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/doc")
|
||||
@NotIntercept
|
||||
public class ForgeryDetectionController {
|
||||
|
||||
@Autowired
|
||||
private BForgeryDetectionHandle bForgeryDetectionHandle;
|
||||
|
||||
@PostMapping("/forgeryDetection")
|
||||
public R<ForgeryDetectionResp> forgeryDetection(@RequestBody ForgeryDetectionRequest request) {
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
BForgeryDetectionRequest bRequest = toBaiduRequest(request);
|
||||
|
||||
ApiR<BForgeryDetectionResp> bR = bForgeryDetectionHandle.handle(bRequest);
|
||||
if (!bR.isSuccess()) {
|
||||
log.info("图片篡改检测-失败, error:{}, 耗时:{}ms", bR.getErrorMsg(), System.currentTimeMillis() - start);
|
||||
return R.error(bR.getErrorMsg());
|
||||
}
|
||||
|
||||
BForgeryDetectionResp bResp = bR.getData();
|
||||
if (bResp.getResult() == null) {
|
||||
log.info("图片篡改检测-结果为空, 耗时:{}ms", System.currentTimeMillis() - start);
|
||||
return R.error("图片篡改检测结果为空");
|
||||
}
|
||||
|
||||
ForgeryDetectionResp resp = toResp(bResp);
|
||||
|
||||
log.info("图片篡改检测-完成, 耗时:{}ms", System.currentTimeMillis() - start);
|
||||
return R.ok().setData(resp);
|
||||
}
|
||||
|
||||
private BForgeryDetectionRequest toBaiduRequest(ForgeryDetectionRequest request) {
|
||||
BForgeryDetectionRequest bRequest = new BForgeryDetectionRequest();
|
||||
if (request == null) {
|
||||
return bRequest;
|
||||
}
|
||||
bRequest.setImageBase64(request.getImageBase64());
|
||||
bRequest.setImageUrl(request.getImageUrl());
|
||||
bRequest.setDetectProportion(request.getDetectProportion());
|
||||
bRequest.setDetectThreshold(request.getDetectThreshold());
|
||||
bRequest.setReturnHeatmap(request.getReturnHeatmap());
|
||||
bRequest.setRestrictProbability(request.getRestrictProbability());
|
||||
return bRequest;
|
||||
}
|
||||
|
||||
private ForgeryDetectionResp toResp(BForgeryDetectionResp bResp) {
|
||||
ForgeryDetectionResp resp = new ForgeryDetectionResp();
|
||||
BForgeryDetectionResp.ResultDTO result = bResp.getResult();
|
||||
|
||||
resp.setDetectionResult(result.getDetectionResult());
|
||||
resp.setTamperedProportion(result.getTamperedProportion());
|
||||
resp.setHeatmap(result.getHeatmap());
|
||||
|
||||
if (result.getTamperedLocation() != null) {
|
||||
List<ForgeryDetectionResp.TamperedLocation> locations = new ArrayList<>();
|
||||
for (BForgeryDetectionResp.TamperedLocationDTO dto : result.getTamperedLocation()) {
|
||||
ForgeryDetectionResp.TamperedLocation location = new ForgeryDetectionResp.TamperedLocation();
|
||||
location.setLeft(dto.getLeft());
|
||||
location.setTop(dto.getTop());
|
||||
location.setWidth(dto.getWidth());
|
||||
location.setHeight(dto.getHeight());
|
||||
location.setProbability(dto.getProbability());
|
||||
locations.add(location);
|
||||
}
|
||||
resp.setTamperedLocation(locations);
|
||||
}
|
||||
|
||||
return resp;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
package com.heyu.api.request.doc;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 图片篡改检测 - 前端请求参数
|
||||
*/
|
||||
@Data
|
||||
public class ForgeryDetectionRequest {
|
||||
|
||||
/**
|
||||
* 图像数据,base64编码后进行urlencode
|
||||
* 和 imageUrl 二选一
|
||||
*/
|
||||
private String imageBase64;
|
||||
|
||||
/**
|
||||
* 图片完整URL
|
||||
* 和 imageBase64 二选一
|
||||
*/
|
||||
private String imageUrl;
|
||||
|
||||
/**
|
||||
* 是否返回图片篡改置信度
|
||||
* - true:返回
|
||||
* - false:不返回(默认)
|
||||
*/
|
||||
private String detectProportion;
|
||||
|
||||
/**
|
||||
* 图片篡改检出阈值,范围0.0001~1,默认为0.9887
|
||||
*/
|
||||
private String detectThreshold;
|
||||
|
||||
/**
|
||||
* 是否返回伪造区域热力图
|
||||
* - true:返回热力图base64编码
|
||||
* - false:不返回(默认)
|
||||
*/
|
||||
private String returnHeatmap;
|
||||
|
||||
/**
|
||||
* 返回伪造区域坐标的阈值,范围0.1~1,默认为0.8
|
||||
*/
|
||||
private String restrictProbability;
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
package com.heyu.api.resp.doc;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 图片篡改检测 - 前端响应
|
||||
*/
|
||||
@Data
|
||||
public class ForgeryDetectionResp {
|
||||
|
||||
/**
|
||||
* 篡改检测结果
|
||||
* - fake:有篡改
|
||||
* - real:无篡改
|
||||
*/
|
||||
private String detectionResult;
|
||||
|
||||
/**
|
||||
* 图片篡改置信度
|
||||
*/
|
||||
private Double tamperedProportion;
|
||||
|
||||
/**
|
||||
* 篡改区域热力图base64编码
|
||||
*/
|
||||
private String heatmap;
|
||||
|
||||
/**
|
||||
* 伪造区域的坐标信息
|
||||
*/
|
||||
private List<TamperedLocation> tamperedLocation;
|
||||
|
||||
@Data
|
||||
public static class TamperedLocation {
|
||||
|
||||
/**
|
||||
* 伪造区域的左上顶点的水平坐标
|
||||
*/
|
||||
private Integer left;
|
||||
|
||||
/**
|
||||
* 伪造区域的左上顶点的垂直坐标
|
||||
*/
|
||||
private Integer top;
|
||||
|
||||
/**
|
||||
* 伪造区域的宽度
|
||||
*/
|
||||
private Integer width;
|
||||
|
||||
/**
|
||||
* 伪造区域的高度
|
||||
*/
|
||||
private Integer height;
|
||||
|
||||
/**
|
||||
* 该区域伪造置信度分数
|
||||
*/
|
||||
private Double probability;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,138 @@
|
||||
package com.api.test;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
|
||||
/**
|
||||
* 图片篡改检测 - 集成测试
|
||||
* 直接调用百度 API,验证接口连通性和返回结构
|
||||
*/
|
||||
public class TestForgeryDetection {
|
||||
|
||||
private static final String API_KEY = "zs9oN4gSuoS3eK8dVJg6jyKh";
|
||||
private static final String SECRET_KEY = "uHIRXkj6rbW1eXy8eRVCeP1e3cRQKXay";
|
||||
private static final OkHttpClient HTTP_CLIENT = new OkHttpClient().newBuilder().build();
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
// 1. 获取 access_token
|
||||
String accessToken = getAccessToken();
|
||||
System.out.println("access_token: " + accessToken);
|
||||
|
||||
// 2. 构造请求参数
|
||||
String imageUrl = "https://pis.baiwang.com/smkp-vue/previewInvoiceAllEle?param=994386497A3EC72F12692CBDDCC42D051AEA0207F96FABE1B78125CB13A374336BFB46C25F0AC688628DA556C733B4E3F4883E6A0CBA0768E22723D80F925AB9";
|
||||
|
||||
// 测试1: 基础检测(只传图片URL)
|
||||
System.out.println("\n========== 测试1: 基础检测 ==========");
|
||||
testBasic(accessToken, imageUrl);
|
||||
|
||||
// 测试2: 带置信度检测
|
||||
System.out.println("\n========== 测试2: 带置信度检测 ==========");
|
||||
testWithProportion(accessToken, imageUrl);
|
||||
|
||||
// 测试3: 带热力图检测
|
||||
System.out.println("\n========== 测试3: 带热力图检测 ==========");
|
||||
testWithHeatmap(accessToken, imageUrl);
|
||||
}
|
||||
|
||||
private static void testBasic(String accessToken, String imageUrl) throws Exception {
|
||||
String param = "&url=" + URLEncoder.encode(imageUrl, "UTF-8");
|
||||
String result = callBaiduApi(accessToken, param);
|
||||
System.out.println("返回结果:\n" + formatJson(result));
|
||||
parseResult(result);
|
||||
}
|
||||
|
||||
private static void testWithProportion(String accessToken, String imageUrl) throws Exception {
|
||||
String param = "&url=" + URLEncoder.encode(imageUrl, "UTF-8")
|
||||
+ "&detect_proportion=true";
|
||||
String result = callBaiduApi(accessToken, param);
|
||||
System.out.println("返回结果:\n" + formatJson(result));
|
||||
parseResult(result);
|
||||
}
|
||||
|
||||
private static void testWithHeatmap(String accessToken, String imageUrl) throws Exception {
|
||||
String param = "&url=" + URLEncoder.encode(imageUrl, "UTF-8")
|
||||
+ "&detect_proportion=true"
|
||||
+ "&return_heatmap=true";
|
||||
String result = callBaiduApi(accessToken, param);
|
||||
System.out.println("返回结果:\n" + formatJson(result));
|
||||
parseResult(result);
|
||||
}
|
||||
|
||||
private static String callBaiduApi(String accessToken, String param) throws Exception {
|
||||
if (param.startsWith("&")) {
|
||||
param = param.substring(1);
|
||||
}
|
||||
MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
|
||||
RequestBody body = RequestBody.create(mediaType, param);
|
||||
Request request = new Request.Builder()
|
||||
.url("https://aip.baidubce.com/rest/2.0/ocr/v1/forgery_detection?access_token=" + accessToken)
|
||||
.method("POST", body)
|
||||
.addHeader("Content-Type", "application/x-www-form-urlencoded")
|
||||
.addHeader("Accept", "application/json")
|
||||
.build();
|
||||
|
||||
try (Response response = HTTP_CLIENT.newCall(request).execute()) {
|
||||
return response.body().string();
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseResult(String result) {
|
||||
JSONObject json = JSONObject.parseObject(result);
|
||||
|
||||
if (json.containsKey("error_code")) {
|
||||
System.out.println("API错误: error_code=" + json.getString("error_code")
|
||||
+ ", error_msg=" + json.getString("error_msg"));
|
||||
return;
|
||||
}
|
||||
|
||||
JSONObject resultObj = json.getJSONObject("result");
|
||||
if (resultObj == null) {
|
||||
System.out.println("result 为空");
|
||||
return;
|
||||
}
|
||||
|
||||
String detectionResult = resultObj.getString("detection_result");
|
||||
System.out.println("检测结果: " + ("fake".equals(detectionResult) ? "有篡改" : "无篡改"));
|
||||
|
||||
Double tamperedProportion = resultObj.getDouble("tampered_proportion");
|
||||
if (tamperedProportion != null) {
|
||||
System.out.println("篡改置信度: " + tamperedProportion);
|
||||
}
|
||||
|
||||
String heatmap = resultObj.getString("heatmap");
|
||||
if (heatmap != null) {
|
||||
System.out.println("热力图长度: " + heatmap.length() + " 字符");
|
||||
}
|
||||
}
|
||||
|
||||
private static String getAccessToken() throws Exception {
|
||||
MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
|
||||
RequestBody body = RequestBody.create(mediaType,
|
||||
"grant_type=client_credentials&client_id=" + API_KEY + "&client_secret=" + SECRET_KEY);
|
||||
Request request = new Request.Builder()
|
||||
.url("https://aip.baidubce.com/oauth/2.0/token")
|
||||
.method("POST", body)
|
||||
.addHeader("Content-Type", "application/x-www-form-urlencoded")
|
||||
.build();
|
||||
|
||||
try (Response response = HTTP_CLIENT.newCall(request).execute()) {
|
||||
String result = response.body().string();
|
||||
JSONObject json = JSONObject.parseObject(result);
|
||||
return json.getString("access_token");
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatJson(String json) {
|
||||
try {
|
||||
return JSONObject.parseObject(json).toString();
|
||||
} catch (Exception e) {
|
||||
return json;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,222 @@
|
||||
package com.heyu.api.controller.doc;
|
||||
|
||||
import com.heyu.api.baidu.handle.doc.BForgeryDetectionHandle;
|
||||
import com.heyu.api.baidu.request.doc.BForgeryDetectionRequest;
|
||||
import com.heyu.api.baidu.response.doc.BForgeryDetectionResp;
|
||||
import com.heyu.api.data.utils.ApiR;
|
||||
import com.heyu.api.data.utils.R;
|
||||
import com.heyu.api.request.doc.ForgeryDetectionRequest;
|
||||
import com.heyu.api.resp.doc.ForgeryDetectionResp;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* ForgeryDetectionController 单元测试
|
||||
*/
|
||||
public class ForgeryDetectionControllerTest {
|
||||
|
||||
@InjectMocks
|
||||
private ForgeryDetectionController controller;
|
||||
|
||||
@Mock
|
||||
private BForgeryDetectionHandle bForgeryDetectionHandle;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
// ========== 正常流程测试 ==========
|
||||
|
||||
@Test
|
||||
public void forgeryDetectionSuccessTest() {
|
||||
ForgeryDetectionRequest request = new ForgeryDetectionRequest();
|
||||
request.setImageBase64("base64ImageData");
|
||||
|
||||
BForgeryDetectionResp bResp = new BForgeryDetectionResp();
|
||||
BForgeryDetectionResp.ResultDTO result = new BForgeryDetectionResp.ResultDTO();
|
||||
result.setDetectionResult("fake");
|
||||
result.setTamperedProportion(0.85);
|
||||
bResp.setResult(result);
|
||||
|
||||
when(bForgeryDetectionHandle.handle(any(BForgeryDetectionRequest.class)))
|
||||
.thenReturn(ApiR.setData(bResp));
|
||||
|
||||
R<ForgeryDetectionResp> r = controller.forgeryDetection(request);
|
||||
|
||||
assertNotNull(r);
|
||||
assertEquals("200", r.getCode());
|
||||
ForgeryDetectionResp resp = r.getData();
|
||||
assertNotNull(resp);
|
||||
assertEquals("fake", resp.getDetectionResult());
|
||||
assertEquals(Double.valueOf(0.85), resp.getTamperedProportion());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forgeryDetectionRealImageTest() {
|
||||
ForgeryDetectionRequest request = new ForgeryDetectionRequest();
|
||||
request.setImageUrl("https://example.com/image.jpg");
|
||||
|
||||
BForgeryDetectionResp bResp = new BForgeryDetectionResp();
|
||||
BForgeryDetectionResp.ResultDTO result = new BForgeryDetectionResp.ResultDTO();
|
||||
result.setDetectionResult("real");
|
||||
bResp.setResult(result);
|
||||
|
||||
when(bForgeryDetectionHandle.handle(any(BForgeryDetectionRequest.class)))
|
||||
.thenReturn(ApiR.setData(bResp));
|
||||
|
||||
R<ForgeryDetectionResp> r = controller.forgeryDetection(request);
|
||||
|
||||
assertNotNull(r);
|
||||
assertEquals("200", r.getCode());
|
||||
assertEquals("real", r.getData().getDetectionResult());
|
||||
assertNull(r.getData().getTamperedProportion());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forgeryDetectionWithHeatmapTest() {
|
||||
ForgeryDetectionRequest request = new ForgeryDetectionRequest();
|
||||
request.setImageBase64("base64ImageData");
|
||||
request.setReturnHeatmap("true");
|
||||
|
||||
BForgeryDetectionResp bResp = new BForgeryDetectionResp();
|
||||
BForgeryDetectionResp.ResultDTO result = new BForgeryDetectionResp.ResultDTO();
|
||||
result.setDetectionResult("fake");
|
||||
result.setHeatmap("heatmapBase64Data");
|
||||
bResp.setResult(result);
|
||||
|
||||
when(bForgeryDetectionHandle.handle(any(BForgeryDetectionRequest.class)))
|
||||
.thenReturn(ApiR.setData(bResp));
|
||||
|
||||
R<ForgeryDetectionResp> r = controller.forgeryDetection(request);
|
||||
|
||||
assertEquals("fake", r.getData().getDetectionResult());
|
||||
assertEquals("heatmapBase64Data", r.getData().getHeatmap());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forgeryDetectionWithTamperedLocationTest() {
|
||||
ForgeryDetectionRequest request = new ForgeryDetectionRequest();
|
||||
request.setImageBase64("base64ImageData");
|
||||
request.setRestrictProbability("0.5");
|
||||
|
||||
BForgeryDetectionResp bResp = new BForgeryDetectionResp();
|
||||
BForgeryDetectionResp.ResultDTO result = new BForgeryDetectionResp.ResultDTO();
|
||||
result.setDetectionResult("fake");
|
||||
|
||||
List<BForgeryDetectionResp.TamperedLocationDTO> locations = new ArrayList<>();
|
||||
BForgeryDetectionResp.TamperedLocationDTO loc = new BForgeryDetectionResp.TamperedLocationDTO();
|
||||
loc.setLeft(100);
|
||||
loc.setTop(200);
|
||||
loc.setWidth(300);
|
||||
loc.setHeight(150);
|
||||
loc.setProbability(0.95);
|
||||
locations.add(loc);
|
||||
result.setTamperedLocation(locations);
|
||||
bResp.setResult(result);
|
||||
|
||||
when(bForgeryDetectionHandle.handle(any(BForgeryDetectionRequest.class)))
|
||||
.thenReturn(ApiR.setData(bResp));
|
||||
|
||||
R<ForgeryDetectionResp> r = controller.forgeryDetection(request);
|
||||
|
||||
ForgeryDetectionResp resp = r.getData();
|
||||
assertNotNull(resp.getTamperedLocation());
|
||||
assertEquals(1, resp.getTamperedLocation().size());
|
||||
ForgeryDetectionResp.TamperedLocation respLoc = resp.getTamperedLocation().get(0);
|
||||
assertEquals(Integer.valueOf(100), respLoc.getLeft());
|
||||
assertEquals(Integer.valueOf(200), respLoc.getTop());
|
||||
assertEquals(Integer.valueOf(300), respLoc.getWidth());
|
||||
assertEquals(Integer.valueOf(150), respLoc.getHeight());
|
||||
assertEquals(Double.valueOf(0.95), respLoc.getProbability());
|
||||
}
|
||||
|
||||
// ========== 异常流程测试 ==========
|
||||
|
||||
@Test
|
||||
public void forgeryDetectionHandleErrorTest() {
|
||||
ForgeryDetectionRequest request = new ForgeryDetectionRequest();
|
||||
|
||||
when(bForgeryDetectionHandle.handle(any(BForgeryDetectionRequest.class)))
|
||||
.thenReturn(ApiR.error("imageUrl和imageBase64不能同时为空"));
|
||||
|
||||
R<ForgeryDetectionResp> r = controller.forgeryDetection(request);
|
||||
|
||||
assertNotNull(r);
|
||||
assertEquals("400", r.getCode());
|
||||
assertEquals("imageUrl和imageBase64不能同时为空", r.getMsg());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forgeryDetectionResultNullTest() {
|
||||
ForgeryDetectionRequest request = new ForgeryDetectionRequest();
|
||||
request.setImageBase64("data");
|
||||
|
||||
BForgeryDetectionResp bResp = new BForgeryDetectionResp();
|
||||
bResp.setResult(null);
|
||||
|
||||
when(bForgeryDetectionHandle.handle(any(BForgeryDetectionRequest.class)))
|
||||
.thenReturn(ApiR.setData(bResp));
|
||||
|
||||
R<ForgeryDetectionResp> r = controller.forgeryDetection(request);
|
||||
|
||||
assertNotNull(r);
|
||||
assertEquals("400", r.getCode());
|
||||
}
|
||||
|
||||
// ========== 请求映射测试 ==========
|
||||
|
||||
@Test
|
||||
public void toBaiduRequestMappingTest() {
|
||||
ForgeryDetectionRequest request = new ForgeryDetectionRequest();
|
||||
request.setImageBase64("base64");
|
||||
request.setImageUrl("https://example.com/img.jpg");
|
||||
request.setDetectProportion("true");
|
||||
request.setDetectThreshold("0.9");
|
||||
request.setReturnHeatmap("true");
|
||||
request.setRestrictProbability("0.8");
|
||||
|
||||
BForgeryDetectionResp bResp = new BForgeryDetectionResp();
|
||||
BForgeryDetectionResp.ResultDTO result = new BForgeryDetectionResp.ResultDTO();
|
||||
result.setDetectionResult("real");
|
||||
bResp.setResult(result);
|
||||
|
||||
when(bForgeryDetectionHandle.handle(any(BForgeryDetectionRequest.class)))
|
||||
.thenAnswer(invocation -> {
|
||||
BForgeryDetectionRequest bReq = invocation.getArgument(0);
|
||||
assertEquals("base64", bReq.getImageBase64());
|
||||
assertEquals("https://example.com/img.jpg", bReq.getImageUrl());
|
||||
assertEquals("true", bReq.getDetectProportion());
|
||||
assertEquals("0.9", bReq.getDetectThreshold());
|
||||
assertEquals("true", bReq.getReturnHeatmap());
|
||||
assertEquals("0.8", bReq.getRestrictProbability());
|
||||
return ApiR.setData(bResp);
|
||||
});
|
||||
|
||||
R<ForgeryDetectionResp> r = controller.forgeryDetection(request);
|
||||
assertEquals("200", r.getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toBaiduRequestNullInputTest() {
|
||||
ForgeryDetectionRequest request = null;
|
||||
|
||||
when(bForgeryDetectionHandle.handle(any(BForgeryDetectionRequest.class)))
|
||||
.thenReturn(ApiR.error("你的image , url不能都为空"));
|
||||
|
||||
R<ForgeryDetectionResp> r = controller.forgeryDetection(request);
|
||||
assertEquals("400", r.getCode());
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user