feat(doc): 添加图片篡改检测功能

- 新增 BForgeryDetectionHandle 处理类实现百度图片篡改检测API接口
- 添加 BForgeryDetectionRequest 请求参数类定义检测相关参数
- 添加 BForgeryDetectionResp 响应类处理检测结果和伪造区域信息
- 实现 ForgeryDetectionController 提供图片篡改检测接口服务
- 定义前端请求参数 ForgeryDetectionRequest 和响应数据 ForgeryDetectionResp
- 添加完整的单元测试覆盖参数校验、内容拼接和业务逻辑
- 集成测试验证与百度API的连通性和返回结构正确性
This commit is contained in:
zhengli 2026-05-25 20:13:32 +08:00
parent d3689457cc
commit 4cfa182013
9 changed files with 1062 additions and 0 deletions

View File

@ -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();
}
}

View File

@ -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.00011默认为0.9887
* 当图片篡改置信度检出阈值时篡改检测结果返回'有篡改'反之返回'无篡改'
*/
private String detectThreshold;
/**
* 是否返回伪造区域热力图
* - true返回伪造区域热力图的base64编码
* - false不返回默认
*/
private String returnHeatmap;
/**
* 返回伪造区域坐标的阈值范围0.11默认为0.8
* 当伪造区域坐标置信度分数阈值时tampered_location返回符合阈值的坐标信息
*/
private String restrictProbability;
}

View File

@ -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;
}
}

View File

@ -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="));
}
}

View File

@ -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;
}
}

View File

@ -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.00011默认为0.9887
*/
private String detectThreshold;
/**
* 是否返回伪造区域热力图
* - true返回热力图base64编码
* - false不返回默认
*/
private String returnHeatmap;
/**
* 返回伪造区域坐标的阈值范围0.11默认为0.8
*/
private String restrictProbability;
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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());
}
}