diff --git a/api-web/api-interface/src/main/java/com/heyu/api/controller/BaiduOcrErrorCode.java b/api-web/api-interface/src/main/java/com/heyu/api/controller/BaiduOcrErrorCode.java new file mode 100644 index 0000000..ebab88e --- /dev/null +++ b/api-web/api-interface/src/main/java/com/heyu/api/controller/BaiduOcrErrorCode.java @@ -0,0 +1,168 @@ +package com.heyu.api.controller; + +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; + +/** + * 百度 OCR 平台全部错误码枚举。 + *

+ * 数据来源:百度 OCR 错误码文档 + *

+ *

+ * 每个枚举项包含百度官方的 {@link #code}(错误码)、{@link #errorMsg}(英文错误信息) + * 和 {@link #description}(中文描述),方便本地校验场景仿照百度格式统一返回。 + *

+ * + *

使用示例

+ *
+ *   BaiduOcrErrorCode errorCode = BaiduOcrErrorCode.EMPTY_IMAGE;
+ *   result.setCode(errorCode.getCode());
+ *   result.setMsg(errorCode.getErrorMsg());
+ *
+ *   // 根据百度返回的错误码查找
+ *   BaiduOcrErrorCode fromPlatform = BaiduOcrErrorCode.fromCode("216200");
+ * 
+ * + * @author zhengli + * @since 20260610_zl + */ +public enum BaiduOcrErrorCode { + + // ===================== 系统级错误(1~19) ===================== + + UNKNOWN("1", "Unknown error", "未知错误,请再次请求,如果持续出现此类错误,请在控制台提交工单联系技术支持团队"), + SERVICE_UNAVAILABLE("2", "Service temporarily unavailable", "服务暂不可用,请再次请求,如果持续出现此类错误,请在控制台提交工单联系技术支持团队"), + UNSUPPORTED_METHOD("3", "Unsupported openapi method", "调用的API不存在,请检查请求URL后重新尝试"), + CLUSTER_LIMIT("4", "Open api request limit reached", "集群超限额,请再次请求,如果持续出现此类错误,请在控制台提交工单联系技术支持团队"), + NO_PERMISSION("6", "No permission to access data", "无接口调用权限,创建应用时未勾选相关文字识别接口"), + IAM_CERT_FAILED("14", "IAM Certification failed", "IAM鉴权失败,建议用户参照文档自查生成sign的方式是否正确"), + DAILY_LIMIT("17", "Open api daily request limit reached", "免费测试资源使用完毕,每天请求量超限额"), + QPS_LIMIT("18", "Open api qps request limit reached", "QPS超限额"), + TOTAL_LIMIT("19", "Open api total request limit reached", "请求总量超限额"), + + // ===================== 鉴权错误(100~111) ===================== + + INVALID_PARAMETER("100", "Invalid parameter", "无效的access_token参数,token拉取失败"), + ACCESS_TOKEN_INVALID("110", "Access token invalid or no longer valid", "access_token无效"), + ACCESS_TOKEN_EXPIRED("111", "Access token expired", "access token过期"), + + // ===================== 业务参数错误(216xxx) ===================== + + INVALID_PARAM("216100", "invalid param", "请求中包含非法参数,请检查后重新尝试"), + NOT_ENOUGH_PARAM("216101", "not enough param", "缺少必须的参数,请检查参数是否有遗漏"), + SERVICE_NOT_SUPPORT("216102", "service not support", "请求了不支持的服务,请检查调用的url"), + PARAM_TOO_LONG("216103", "param too long", "请求中某些参数过长,请检查后重新尝试"), + APPID_NOT_EXIST("216110", "appid not exist", "appid不存在,请重新核对信息"), + + // ===================== 影像相关错误(2162xx) ===================== + + EMPTY_IMAGE("216200", "empty image", "图片为空,请检查后重新尝试"), + IMAGE_FORMAT_ERROR("216201", "image format error", "上传的图片格式错误,现阶段支持 PNG、JPG、JPEG、BMP"), + IMAGE_SIZE_ERROR("216202", "image size error", "上传的图片大小错误"), + INPUT_OVERSIZE("216205", "input oversize", "传入的请求体大小错误"), + + // ===================== 文件/上传错误(2163xx) ===================== + + UPLOAD_FILE_ERROR("216306", "Upload file error", "上传文件失败,请检查提交请求接口的请求参数"), + IMAGE_ANALYSIS_ERROR("216307", "image analysis error", "请求数据解析异常,图片数据解析加载失败"), + PDF_FILE_NUM_EXCEED("216308", "Pdf_file_num exceeds the number of pdf pages", "参数pdf_file_num大于PDF文件实际页数"), + + // ===================== 任务错误(2164xx) ===================== + + CREATE_TASK_FAILED("216401", "Create task failed", "提交请求失败"), + QUERY_TASK_FAILED("216402", "Query task failed", "获取结果失败"), + + // ===================== PDF/配额错误(2166xx) ===================== + + CHECK_PDF_FAILED("216603", "Check pdf page num failed", "获取PDF文件页数失败"), + INSUFFICIENT_QUOTA("216604", "Insufficient available quota", "请求总量超限额"), + RECOGNIZE_ERROR("216630", "recognize error", "识别错误,请再次请求"), + RECOGNIZE_BANK_CARD_ERROR("216631", "recognize bank card error", "识别银行卡错误"), + RECOGNIZE_IDCARD_ERROR("216633", "recognize idcard error", "识别身份证错误"), + DETECT_ERROR("216634", "detect error", "检测错误,请再次请求"), + + // ===================== 企业核验错误(216600~216602) ===================== + + BUSINESS_VERIFY_FAILED("216600", "business verify failed", "企业核验相关服务请求失败"), + BUSINESS_VERIFY_EMPTY("216601", "business verify result empty", "企业核验相关服务查询成功,但无查询结果"), + BUSINESS_VERIFY_TIMEOUT("216602", "business verify timeout", "企业核验相关服务接口超时"), + + // ===================== 引擎内部错误(282xxx) ===================== + + INTERNAL_ERROR("282000", "internal error", "服务器内部错误"), + MISSING_PARAMETERS("282003", "missing parameters", "请求参数缺失"), + BATCH_PROCESSING_ERROR("282005", "batch processing error", "处理批量任务时发生部分或全部错误"), + BATCH_TASK_LIMIT("282006", "batch task limit reached", "批量任务处理数量超出限制"), + IMAGE_TRANSCODE_ERROR("282100", "image transcode error", "图片压缩转码错误"), + TARGET_DETECT_ERROR("282102", "target detect error", "未检测到图片中识别目标"), + TEMPLATE_MATCH_ERROR("282103", "recognize error, failed to match the template", "图片目标识别错误"), + + // ===================== URL 相关错误(2821xx) ===================== + + URLS_NOT_EXIT("282110", "urls not exit", "URL参数不存在,请核对URL后再次提交"), + URL_FORMAT_ILLEGAL("282111", "url format illegal", "URL格式非法"), + URL_DOWNLOAD_TIMEOUT("282112", "url download timeout", "url下载超时"), + URL_RESPONSE_INVALID("282113", "url response invalid", "URL返回无效参数"), + URL_SIZE_ERROR("282114", "url size error", "URL长度超过1024字节或为0"), + IMAGE_FETCH_FAILED("282115", "image fetch failed", "通过URL获取图片失败"), + + // ===================== 增值税发票验真错误(282134) ===================== + + OFFICIAL_WEB_EXCEPTION("282134", "officialWeb service exception", "国税局端网络超时"), + + // ===================== 异步任务错误(2828xx) ===================== + + REQUEST_ID_NOT_EXIST("282808", "request id not exist", "request id 不存在"), + RESULT_TYPE_ERROR("282809", "result type error", "返回结果请求错误"), + IMAGE_RECOGNIZE_ERROR("282810", "image recognize error", "图像识别错误"), + + // ===================== 行驶证核验错误(2821xx) ===================== + + DRIVING_LICENSE_RESOURCE_OVERRUN("282160", "driving license backend resource overrun", "后端资源超限"), + DRIVING_LICENSE_TOO_FREQUENT("282161", "driving license requests too frequently", "请求过于频繁"), + ; + + private final String code; + private final String errorMsg; + private final String description; + + private static final Map BY_CODE; + + static { + Map m = new HashMap<>(); + for (BaiduOcrErrorCode e : EnumSet.allOf(BaiduOcrErrorCode.class)) { + m.put(e.code, e); + } + BY_CODE = Collections.unmodifiableMap(m); + } + + BaiduOcrErrorCode(String code, String errorMsg, String description) { + this.code = code; + this.errorMsg = errorMsg; + this.description = description; + } + + public String getCode() { + return code; + } + + public String getErrorMsg() { + return errorMsg; + } + + public String getDescription() { + return description; + } + + /** + * 根据百度错误码查找枚举值。 + * + * @param code 百度错误码 + * @return 对应的枚举值,未匹配时返回 null + */ + public static BaiduOcrErrorCode fromCode(String code) { + return BY_CODE.get(code); + } +} diff --git a/api-web/api-interface/src/main/java/com/heyu/api/controller/doc/DocClassifyController.java b/api-web/api-interface/src/main/java/com/heyu/api/controller/doc/DocClassifyController.java index c75ff0c..a0e8e4e 100644 --- a/api-web/api-interface/src/main/java/com/heyu/api/controller/doc/DocClassifyController.java +++ b/api-web/api-interface/src/main/java/com/heyu/api/controller/doc/DocClassifyController.java @@ -3,6 +3,7 @@ package com.heyu.api.controller.doc; import com.heyu.api.controller.AbstractRecognizeController; import com.heyu.api.controller.BaiduOcrError; +import com.heyu.api.controller.BaiduOcrErrorCode; import com.heyu.api.data.annotation.CacheResult; import com.heyu.api.data.annotation.EbAuthentication; import com.heyu.api.data.annotation.NotIntercept; @@ -17,7 +18,6 @@ import com.heyu.api.resp.doc.DocClassifyLocationResp; import com.heyu.api.resp.doc.DocClassifyResp; import lombok.extern.slf4j.Slf4j; 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; @@ -48,8 +48,10 @@ import java.util.Map; * *

返回约定

* * @@ -76,18 +78,14 @@ public class DocClassifyController extends AbstractRecognizeController { @EbAuthentication(tencent = ApiConstants.TENCENT_AUTH) @PostMapping("/classify") @CacheResult - public R recognize(@RequestBody DocClassifyRequest request) { + public R recognize(DocClassifyRequest request) { long start = System.currentTimeMillis(); RecognizeContext ctx = null; try { // ---------- 步骤①:参数校验(不调平台、不扣费) ---------- - String validateError = validateRequest(request); + R validateError = validateRequest(request); if (validateError != null) { - ResolvedImageInput validateImage = request != null - ? ImageInputUtils.resolve(request.getImageUrlOrBase64()) : null; - log.info("文档分类:参数检查没通过,接口仍返回成功并附带提示(还没调识别、不扣费)。{} 返回给客户:{}", - buildInputLogContext(request, validateImage), abbreviate(validateError, 120)); - return okResult(null, validateError, null); + return validateError; } // ---------- 步骤②:解析影像入参 & 构建上下文 ---------- @@ -97,32 +95,58 @@ public class DocClassifyController extends AbstractRecognizeController { // ---------- 步骤③:组装请求体 ---------- String content = buildRequestContent(imageInput); if (isBlank(content)) { - log.error("文档分类:组装请求失败,请求里没带有效图片。{}。{}", - ctx.imageInput.getType().getDesc(), ctx.inputLog); - return okResult(null, formatHint( + String hint = formatHint( "报文组装异常", "识别请求体在序列化后未包含任何影像载荷,平台侧无法受理本次识别", "请核对 imageUrlOrBase64 是否非空且为有效 Base64 或 HTTP(S) 链接", "影像模式=" + ctx.imageInput.getType().name() - ), null); + ); + log.error("文档分类:组装请求失败,请求里没带有效图片。{}。{}。详细诊断:{}", + ctx.imageInput.getType().getDesc(), ctx.inputLog, abbreviate(hint, 200)); + return baiduErrorR(BaiduOcrErrorCode.EMPTY_IMAGE); } // ---------- 步骤④:调用平台识别 ---------- Map platformResult = callPlatform(content, ctx); if (platformResult == null) { - return okResult(null, formatHint( + String hint = formatHint( "服务无回执", "识别指令已下发,但在约定时间内未收到平台可解析的 JSON 回执", "建议间隔 3~5 秒重试;若连续失败,请记录 traceId、调用时刻并联系技术支持", "影像模式=" + ctx.imageInput.getType().name() - ), null); + ); + log.error("文档分类:平台无回执。{}。详细诊断:{}", ctx.inputLog, abbreviate(hint, 200)); + return baiduErrorR(BaiduOcrErrorCode.INTERNAL_ERROR); } // ---------- 步骤⑤:解析平台结果 → 构造响应对象 ---------- DocClassifyResp data = buildResp(platformResult); + + // ---------- 步骤⑥:百度返回错误码时,按百度原始 code/msg 返回 ---------- + Object errorCodeObj = platformResult.get("error_code"); + if (errorCodeObj != null) { + String errorCode = String.valueOf(errorCodeObj); + Object errorMsgObj = platformResult.get("error_msg"); + String errorMsg; + if (errorMsgObj != null) { + errorMsg = errorMsgObj.toString(); + } else { + BaiduOcrErrorCode baiduCode = BaiduOcrErrorCode.fromCode(errorCode); + errorMsg = baiduCode != null ? baiduCode.getErrorMsg() : "未知错误"; + } + // 详细诊断信息写入日志(不返回给客户端) + String hint = resolvePlatformHint(platformResult, ctx, data); + logRecognizeResult(ctx, platformResult, data, hint, start); + R result = new R<>(); + result.setCode(errorCode); + result.setMsg(errorMsg); + result.setData(data != null ? data : new DocClassifyResp()); + return result; + } + String hint = resolvePlatformHint(platformResult, ctx, data); - // ---------- 步骤⑥:日志记录 & 返回 ---------- + // ---------- 步骤⑦:日志记录 & 返回 ---------- logRecognizeResult(ctx, platformResult, data, hint, start); return okResult(null, hint, data); @@ -132,17 +156,19 @@ public class DocClassifyController extends AbstractRecognizeController { ? ImageInputUtils.resolve(request.getImageUrlOrBase64()) : null; String mode = ctx != null ? ctx.imageInput.getType().getDesc() : (fallbackImage != null ? fallbackImage.getType().getDesc() : "未知"); - log.error("文档分类:程序运行出错,耗时 {} ms。{}。客户传的:{}。异常:{} - {}", - cost, mode, buildInputLogContext(request, fallbackImage), - e.getClass().getSimpleName(), - e.getMessage() != null ? e.getMessage() : "无具体说明", e); - return okResult(null, formatHint( + String hint = formatHint( "运行时故障", "服务端在处理分类流程时抛出未预期异常,识别结果不可用", "请勿重复高频重试;请保存 traceId、异常发生时间,由技术支持结合堆栈进一步定位", "异常类型=" + e.getClass().getSimpleName() + (e.getMessage() != null ? ",摘要=" + e.getMessage() : "") - ), null); + ); + log.error("文档分类:程序运行出错,耗时 {} ms。{}。客户传的:{}。异常:{} - {}。详细诊断:{}", + cost, mode, buildInputLogContext(request, fallbackImage), + e.getClass().getSimpleName(), + e.getMessage() != null ? e.getMessage() : "无具体说明", + abbreviate(hint, 200), e); + return baiduErrorR(BaiduOcrErrorCode.INTERNAL_ERROR); } } @@ -241,30 +267,32 @@ public class DocClassifyController extends AbstractRecognizeController { // ===================== 校验与上下文 ===================== - private String validateRequest(DocClassifyRequest request) { + private R validateRequest(DocClassifyRequest request) { if (request == null) { - log.info("文档分类:没收到任何请求参数,直接拒绝,未调用识别、不扣费"); - return formatHint( + String hint = formatHint( "入参绑定失败", "控制器未接收到可绑定的请求对象,所有业务字段均为空", "请确认使用 POST 提交;Content-Type 为 application/json;" + "字段名与接口文档一致(imageUrlOrBase64)", "绑定结果=DocClassifyRequest 为 null" ); + log.info("文档分类:没收到任何请求参数,直接拒绝,未调用识别、不扣费。详细诊断:{}", + abbreviate(hint, 200)); + return baiduErrorR(BaiduOcrErrorCode.NOT_ENOUGH_PARAM); } ImageInputUtils.ValidationResult imageValidation = ImageInputUtils.validate(request.getImageUrlOrBase64()); if (!imageValidation.isValid()) { - log.info("文档分类:imageUrlOrBase64 校验未通过({}),未调用识别、不扣费。原因:{}。{}", - imageValidation.getCategory(), - imageValidation.getReason(), - buildInputLogContext(request, null)); - return formatHint( + String hint = formatHint( imageValidation.getCategory(), imageValidation.getReason(), imageValidation.getSuggestion(), imageValidation.getDetail() ); + log.info("文档分类:imageUrlOrBase64 校验未通过({}),未调用识别、不扣费。详细诊断:{}", + imageValidation.getCategory(), abbreviate(hint, 200)); + BaiduOcrErrorCode baiduError = mapValidationToBaiduError(imageValidation.getCategory()); + return baiduErrorR(baiduError); } return null; } @@ -343,6 +371,52 @@ public class DocClassifyController extends AbstractRecognizeController { // ===================== 私有工具 ===================== + /** + * 构造百度风格错误响应 R。 + * + * @param errorCode 百度错误码枚举 + * @return 带百度错误码/信息的 R 对象,data 为空 DocClassifyResp + */ + private R baiduErrorR(BaiduOcrErrorCode errorCode) { + R result = new R<>(); + result.setCode(errorCode.getCode()); + result.setMsg(errorCode.getErrorMsg()); + result.setData(new DocClassifyResp()); + return result; + } + + /** + * 将本地影像校验类别映射为百度 OCR 错误码枚举。 + * + * @param category ImageInputUtils.ValidationResult 中的 category + * @return 对应的百度错误码枚举 + */ + private static BaiduOcrErrorCode mapValidationToBaiduError(String category) { + if (category == null) { + return BaiduOcrErrorCode.INVALID_PARAM; + } + switch (category) { + case "影像源缺失": + case "Base64 内容过短": + return BaiduOcrErrorCode.EMPTY_IMAGE; + case "链接协议无效": + case "链接解析失败": + case "链接缺少协议头": + return BaiduOcrErrorCode.URL_FORMAT_ILLEGAL; + case "链接过长": + return BaiduOcrErrorCode.URL_SIZE_ERROR; + case "影像格式无效": + case "Base64 前缀无效": + case "Base64 字符非法": + case "Base64 解码失败": + return BaiduOcrErrorCode.IMAGE_FORMAT_ERROR; + case "Base64 体积过大": + return BaiduOcrErrorCode.INPUT_OVERSIZE; + default: + return BaiduOcrErrorCode.INVALID_PARAM; + } + } + /** * 判断分类结果是否为空。 *

doc_classify 的 words_result 是 List,与 OCR 识别的 Map 结构不同, diff --git a/api-web/api-interface/src/test/java/com/heyu/api/controller/doc/DocClassifyControllerTest.java b/api-web/api-interface/src/test/java/com/heyu/api/controller/doc/DocClassifyControllerTest.java index cb26fb0..1bfa788 100644 --- a/api-web/api-interface/src/test/java/com/heyu/api/controller/doc/DocClassifyControllerTest.java +++ b/api-web/api-interface/src/test/java/com/heyu/api/controller/doc/DocClassifyControllerTest.java @@ -2,6 +2,7 @@ package com.heyu.api.controller.doc; import com.ApiInterfaceApplicationTests; import com.TestConstant; +import com.heyu.api.controller.BaiduOcrErrorCode; import com.heyu.api.data.utils.R; import com.heyu.api.request.doc.DocClassifyRequest; import com.heyu.api.resp.doc.DocClassifyResp; @@ -26,33 +27,32 @@ public class DocClassifyControllerTest extends ApiInterfaceApplicationTests { @Autowired private DocClassifyController docClassifyController; - // ===================== 参数校验场景 ===================== + // ===================== 参数校验场景(仿照百度错误码返回) ===================== /** * 假设(Given): 不传任何请求参数 * 当(When): 调用 classify 接口 - * 则(Then): 返回 code=200,data 不为 null,msg 含【文档分类·入参绑定失败】 + * 则(Then): 返回 code=216101,msg=not enough param,data 为空 DocClassifyResp */ @Test void classifyRequestNullTest() { // Act - R result = docClassifyController.recognize(null); + R result = docClassifyController.recognize(null); // Assert assertNotNull(result, "返回结果不能为空"); - assertEquals("200", result.getCode(), "接口应返回成功"); - assertNotNull(result.getData(), "data 不能为 null(defaultEmptyResp 生效)"); + assertEquals(BaiduOcrErrorCode.NOT_ENOUGH_PARAM.getCode(), result.getCode(), + "缺少必须参数时返回百度错误码 216101"); + assertEquals(BaiduOcrErrorCode.NOT_ENOUGH_PARAM.getErrorMsg(), result.getMsg(), + "msg 应为百度官方 error_msg"); + assertNotNull(result.getData(), "data 不能为 null"); assertTrue(result.getData() instanceof DocClassifyResp, "data 类型应为 DocClassifyResp"); - String msg = result.getMsg(); - assertNotNull(msg, "msg 不应为 null"); - assertTrue(msg.contains("【文档分类·入参绑定失败】"), "msg 应含入参绑定失败前缀"); - assertNoUpstreamPlatformName(msg, "msg"); } /** * 假设(Given): imageUrlOrBase64 为空字符串 * 当(When): 调用 classify 接口 - * 则(Then): 返回 code=200,msg 含【文档分类·影像源缺失】 + * 则(Then): 返回 code=216200,msg=empty image */ @Test void classifyImageEmptyTest() { @@ -61,22 +61,21 @@ public class DocClassifyControllerTest extends ApiInterfaceApplicationTests { request.setImageUrlOrBase64(""); // Act - R result = docClassifyController.recognize(request); + R result = docClassifyController.recognize(request); // Assert assertNotNull(result, "返回结果不能为空"); - assertEquals("200", result.getCode(), "接口应返回成功"); + assertEquals(BaiduOcrErrorCode.EMPTY_IMAGE.getCode(), result.getCode(), + "影像源缺失时返回百度错误码 216200"); + assertEquals(BaiduOcrErrorCode.EMPTY_IMAGE.getErrorMsg(), result.getMsg(), + "msg 应为 empty image"); assertNotNull(result.getData(), "data 不能为 null"); - String msg = result.getMsg(); - assertNotNull(msg, "msg 不应为 null"); - assertTrue(msg.contains("【文档分类·影像源缺失】"), "msg 应含影像源缺失前缀"); - assertNoUpstreamPlatformName(msg, "msg"); } /** - * 假设(Given): imageUrlOrBase64 为非法 Base64 + * 假设(Given): imageUrlOrBase64 为非法 Base64(含非法字符) * 当(When): 调用 classify 接口 - * 则(Then): 返回 code=200,msg 含分类错误信息 + * 则(Then): 返回 code=216201,msg=image format error */ @Test void classifyInvalidBase64Test() { @@ -85,22 +84,21 @@ public class DocClassifyControllerTest extends ApiInterfaceApplicationTests { request.setImageUrlOrBase64("not_a_valid_base64!!!"); // Act - R result = docClassifyController.recognize(request); + R result = docClassifyController.recognize(request); // Assert assertNotNull(result, "返回结果不能为空"); - assertEquals("200", result.getCode(), "接口应返回成功"); + assertEquals(BaiduOcrErrorCode.IMAGE_FORMAT_ERROR.getCode(), result.getCode(), + "Base64 字符非法时返回百度错误码 216201"); + assertEquals(BaiduOcrErrorCode.IMAGE_FORMAT_ERROR.getErrorMsg(), result.getMsg(), + "msg 应为 image format error"); assertNotNull(result.getData(), "data 不能为 null"); - String msg = result.getMsg(); - assertNotNull(msg, "msg 不应为 null"); - assertTrue(msg.startsWith("【文档分类·"), "msg 应含文档分类前缀"); - assertNoUpstreamPlatformName(msg, "msg"); } /** * 假设(Given): imageUrlOrBase64 为非法 URL(ftp 协议) * 当(When): 调用 classify 接口 - * 则(Then): 返回 code=200,msg 含链接协议无效 + * 则(Then): 返回 code=282111,msg=url format illegal */ @Test void classifyInvalidUrlProtocolTest() { @@ -109,15 +107,66 @@ public class DocClassifyControllerTest extends ApiInterfaceApplicationTests { request.setImageUrlOrBase64("ftp://example.com/image.png"); // Act - R result = docClassifyController.recognize(request); + R result = docClassifyController.recognize(request); // Assert assertNotNull(result, "返回结果不能为空"); - assertEquals("200", result.getCode(), "接口应返回成功"); + assertEquals(BaiduOcrErrorCode.URL_FORMAT_ILLEGAL.getCode(), result.getCode(), + "链接协议无效时返回百度错误码 282111"); + assertEquals(BaiduOcrErrorCode.URL_FORMAT_ILLEGAL.getErrorMsg(), result.getMsg(), + "msg 应为 url format illegal"); + assertNotNull(result.getData(), "data 不能为 null"); + } + + /** + * 假设(Given): 通过URL获取图片失败 + * 当(When): 调用 classify 接口 + * 则(Then): 返回 code=282115,msg=image fetch failed + */ + @Test + void classifyUrlTooLongTest() { + // Arrange — 构造超过 1024 字符的 URL + StringBuilder longUrl = new StringBuilder("https://example.com/"); + for (int i = 0; i < 60; i++) { + longUrl.append("path_segment_").append(i).append("/"); + } + longUrl.append("image.png"); + DocClassifyRequest request = new DocClassifyRequest(); + request.setImageUrlOrBase64(longUrl.toString()); + + // Act + R result = docClassifyController.recognize(request); + + // Assert + assertNotNull(result, "返回结果不能为空"); + assertEquals(BaiduOcrErrorCode.IMAGE_FETCH_FAILED.getCode(), result.getCode(), + "URL 过长时返回百度错误码 282115"); + assertEquals(BaiduOcrErrorCode.IMAGE_FETCH_FAILED.getErrorMsg(), result.getMsg(), + "msg 应为 image fetch failed"); + assertNotNull(result.getData(), "data 不能为 null"); + } + + /** + * 假设(Given): imageUrlOrBase64 为 Base64 内容过短 + * 当(When): 调用 classify 接口 + * 则(Then): 返回 code=216200,msg=empty image + */ + @Test + void classifyBase64TooShortTest() { + // Arrange + DocClassifyRequest request = new DocClassifyRequest(); + request.setImageUrlOrBase64("abc"); + + // Act + R result = docClassifyController.recognize(request); + + // Assert + assertNotNull(result, "返回结果不能为空"); + assertEquals(BaiduOcrErrorCode.EMPTY_IMAGE.getCode(), result.getCode(), + "Base64 内容过短时返回百度错误码 216200"); + assertEquals(BaiduOcrErrorCode.EMPTY_IMAGE.getErrorMsg(), result.getMsg(), + "msg 应为 empty image"); assertNotNull(result.getData(), "data 不能为 null"); - String msg = result.getMsg(); - assertTrue(msg.contains("【文档分类·"), "msg 应含文档分类前缀"); - assertNoUpstreamPlatformName(msg, "msg"); } // ===================== 识别成功场景 ===================== @@ -134,14 +183,13 @@ public class DocClassifyControllerTest extends ApiInterfaceApplicationTests { request.setImageUrlOrBase64(TestConstant.TARGET_IMAGE_URL); // Act - R result = docClassifyController.recognize(request); + R result = docClassifyController.recognize(request); // Assert assertNotNull(result, "返回结果不能为空"); assertEquals("200", result.getCode(), "接口应返回成功"); assertNotNull(result.getData(), "返回数据不能为空"); assertTrue(result.getData() instanceof DocClassifyResp, "data 类型应为 DocClassifyResp"); - assertNoUpstreamPlatformName(result.getMsg(), "msg"); } /** @@ -156,26 +204,11 @@ public class DocClassifyControllerTest extends ApiInterfaceApplicationTests { request.setImageUrlOrBase64(TestConstant.TARGET_IMAGE_BASE64); // Act - R result = docClassifyController.recognize(request); + R result = docClassifyController.recognize(request); // Assert assertNotNull(result, "返回结果不能为空"); assertEquals("200", result.getCode(), "base64 传图应返回成功"); assertNotNull(result.getData(), "返回数据不能为空"); - assertNoUpstreamPlatformName(result.getMsg(), "msg"); - } - - // ===================== 断言工具方法 ===================== - - /** - * 断言文本中不包含上游平台字样(百度、baidu 等)。 - */ - private void assertNoUpstreamPlatformName(String text, String fieldName) { - if (text == null) { - return; - } - String lower = text.toLowerCase(); - assertTrue(!lower.contains("百度") && !lower.contains("baidu") && !lower.contains("aliyun") && !lower.contains("tencent"), - fieldName + " 不应包含上游平台字样,实际值:" + text); } }