diff --git a/api-third/src/main/java/com/heyu/api/baidu/handle/doc/BForgeryDetectionHandle.java b/api-third/src/main/java/com/heyu/api/baidu/handle/doc/BForgeryDetectionHandle.java new file mode 100644 index 0000000..974d6b9 --- /dev/null +++ b/api-third/src/main/java/com/heyu/api/baidu/handle/doc/BForgeryDetectionHandle.java @@ -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 { + + 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(); + } +} diff --git a/api-third/src/main/java/com/heyu/api/baidu/request/doc/BForgeryDetectionRequest.java b/api-third/src/main/java/com/heyu/api/baidu/request/doc/BForgeryDetectionRequest.java new file mode 100644 index 0000000..01de38f --- /dev/null +++ b/api-third/src/main/java/com/heyu/api/baidu/request/doc/BForgeryDetectionRequest.java @@ -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; +} diff --git a/api-third/src/main/java/com/heyu/api/baidu/response/doc/BForgeryDetectionResp.java b/api-third/src/main/java/com/heyu/api/baidu/response/doc/BForgeryDetectionResp.java new file mode 100644 index 0000000..7ff23f3 --- /dev/null +++ b/api-third/src/main/java/com/heyu/api/baidu/response/doc/BForgeryDetectionResp.java @@ -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 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; + } +} diff --git a/api-third/src/test/java/com/heyu/api/baidu/handle/doc/BForgeryDetectionHandleTest.java b/api-third/src/test/java/com/heyu/api/baidu/handle/doc/BForgeryDetectionHandleTest.java new file mode 100644 index 0000000..f596887 --- /dev/null +++ b/api-third/src/test/java/com/heyu/api/baidu/handle/doc/BForgeryDetectionHandleTest.java @@ -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=")); + } +} diff --git a/api-web/api-interface/src/main/java/com/heyu/api/controller/doc/ForgeryDetectionController.java b/api-web/api-interface/src/main/java/com/heyu/api/controller/doc/ForgeryDetectionController.java new file mode 100644 index 0000000..762d669 --- /dev/null +++ b/api-web/api-interface/src/main/java/com/heyu/api/controller/doc/ForgeryDetectionController.java @@ -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 forgeryDetection(@RequestBody ForgeryDetectionRequest request) { + long start = System.currentTimeMillis(); + + BForgeryDetectionRequest bRequest = toBaiduRequest(request); + + ApiR 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 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; + } +} diff --git a/api-web/api-interface/src/main/java/com/heyu/api/request/doc/ForgeryDetectionRequest.java b/api-web/api-interface/src/main/java/com/heyu/api/request/doc/ForgeryDetectionRequest.java new file mode 100644 index 0000000..6c49dc5 --- /dev/null +++ b/api-web/api-interface/src/main/java/com/heyu/api/request/doc/ForgeryDetectionRequest.java @@ -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; +} diff --git a/api-web/api-interface/src/main/java/com/heyu/api/resp/doc/ForgeryDetectionResp.java b/api-web/api-interface/src/main/java/com/heyu/api/resp/doc/ForgeryDetectionResp.java new file mode 100644 index 0000000..8b6a835 --- /dev/null +++ b/api-web/api-interface/src/main/java/com/heyu/api/resp/doc/ForgeryDetectionResp.java @@ -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; + + @Data + public static class TamperedLocation { + + /** + * 伪造区域的左上顶点的水平坐标 + */ + private Integer left; + + /** + * 伪造区域的左上顶点的垂直坐标 + */ + private Integer top; + + /** + * 伪造区域的宽度 + */ + private Integer width; + + /** + * 伪造区域的高度 + */ + private Integer height; + + /** + * 该区域伪造置信度分数 + */ + private Double probability; + } +} diff --git a/api-web/api-interface/src/test/java/com/api/test/TestForgeryDetection.java b/api-web/api-interface/src/test/java/com/api/test/TestForgeryDetection.java new file mode 100644 index 0000000..055545c --- /dev/null +++ b/api-web/api-interface/src/test/java/com/api/test/TestForgeryDetection.java @@ -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; + } + } +} diff --git a/api-web/api-interface/src/test/java/com/heyu/api/controller/doc/ForgeryDetectionControllerTest.java b/api-web/api-interface/src/test/java/com/heyu/api/controller/doc/ForgeryDetectionControllerTest.java new file mode 100644 index 0000000..dc2a379 --- /dev/null +++ b/api-web/api-interface/src/test/java/com/heyu/api/controller/doc/ForgeryDetectionControllerTest.java @@ -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 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 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 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 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 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 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 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 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 r = controller.forgeryDetection(request); + assertEquals("400", r.getCode()); + } +}