feat(doc): 调整文档分类接口错误码返回格式
- 新增 BaiduOcrErrorCode 枚举类,包含百度 OCR 全部错误码定义 - 修改 DocClassifyController 参数校验逻辑,本地校验失败时按百度错误码返回 - 调用百度平台返回 error_code 时,按原始百度错误码和错误信息返回 - 重构错误响应构造逻辑,新增 baiduErrorR 方法统一处理百度风格错误响应 - 更新单元测试验证百度错误码格式返回 - 完善错误诊断信息的日志记录格式
This commit is contained in:
parent
39738eead9
commit
ab6e5c8524
@ -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 平台全部错误码枚举。
|
||||
* <p>
|
||||
* 数据来源:<a href="https://cloud.baidu.com/doc/OCR/s/dk3h7y5vr">百度 OCR 错误码文档</a>
|
||||
* </p>
|
||||
* <p>
|
||||
* 每个枚举项包含百度官方的 {@link #code}(错误码)、{@link #errorMsg}(英文错误信息)
|
||||
* 和 {@link #description}(中文描述),方便本地校验场景仿照百度格式统一返回。
|
||||
* </p>
|
||||
*
|
||||
* <h3>使用示例</h3>
|
||||
* <pre>
|
||||
* BaiduOcrErrorCode errorCode = BaiduOcrErrorCode.EMPTY_IMAGE;
|
||||
* result.setCode(errorCode.getCode());
|
||||
* result.setMsg(errorCode.getErrorMsg());
|
||||
*
|
||||
* // 根据百度返回的错误码查找
|
||||
* BaiduOcrErrorCode fromPlatform = BaiduOcrErrorCode.fromCode("216200");
|
||||
* </pre>
|
||||
*
|
||||
* @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<String, BaiduOcrErrorCode> BY_CODE;
|
||||
|
||||
static {
|
||||
Map<String, BaiduOcrErrorCode> 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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
@ -48,8 +49,10 @@ import java.util.Map;
|
||||
*
|
||||
* <h3>返回约定</h3>
|
||||
* <ul>
|
||||
* <li>一律 {@code R.ok()};入参校验失败、平台异常、识别结果为空等情况,将说明写入 {@code msg},
|
||||
* {@code data} 可能为空或字段不全(对外不暴露上游平台字样,日志内可排查)</li>
|
||||
* <li>百度平台返回 {@code error_code} 时,{@code R.code} 按百度原始错误码返回,
|
||||
* {@code R.msg} 按百度原始 {@code error_msg} 返回</li>
|
||||
* <li>本地校验不通过时,仿照百度错误码返回对应的 {@code code} 和 {@code msg};详细诊断信息仅写日志</li>
|
||||
* <li>识别成功但结果为空等软失败场景,一律 {@code R.ok()},说明写入 {@code msg}</li>
|
||||
* <li>入参校验失败时<strong>未调用平台、不计费</strong></li>
|
||||
* </ul>
|
||||
*
|
||||
@ -81,13 +84,9 @@ public class DocClassifyController extends AbstractRecognizeController {
|
||||
RecognizeContext ctx = null;
|
||||
try {
|
||||
// ---------- 步骤①:参数校验(不调平台、不扣费) ----------
|
||||
String validateError = validateRequest(request);
|
||||
R<DocClassifyResp> 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 +96,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<String, Object> 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<DocClassifyResp> 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 +157,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 +268,32 @@ public class DocClassifyController extends AbstractRecognizeController {
|
||||
|
||||
// ===================== 校验与上下文 =====================
|
||||
|
||||
private String validateRequest(DocClassifyRequest request) {
|
||||
private R<DocClassifyResp> 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 +372,52 @@ public class DocClassifyController extends AbstractRecognizeController {
|
||||
|
||||
// ===================== 私有工具 =====================
|
||||
|
||||
/**
|
||||
* 构造百度风格错误响应 R。
|
||||
*
|
||||
* @param errorCode 百度错误码枚举
|
||||
* @return 带百度错误码/信息的 R 对象,data 为空 DocClassifyResp
|
||||
*/
|
||||
private R<DocClassifyResp> baiduErrorR(BaiduOcrErrorCode errorCode) {
|
||||
R<DocClassifyResp> 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断分类结果是否为空。
|
||||
* <p>doc_classify 的 words_result 是 List,与 OCR 识别的 Map 结构不同,
|
||||
|
||||
@ -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<DocClassifyResp> 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<DocClassifyResp> 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<DocClassifyResp> 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<DocClassifyResp> 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<DocClassifyResp> 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<DocClassifyResp> 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<DocClassifyResp> 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<DocClassifyResp> 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);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user