提交修改
This commit is contained in:
parent
206f836bc3
commit
88bfc16289
@ -1,42 +1,530 @@
|
|||||||
package com.heyu.api.controller.car;
|
package com.heyu.api.controller.car;
|
||||||
|
|
||||||
import com.heyu.api.baidu.handle.traffic.BDriverLicenseHandle;
|
|
||||||
import com.heyu.api.baidu.request.traffic.BDriverLicenseRequest;
|
|
||||||
import com.heyu.api.controller.BaseController;
|
import com.heyu.api.controller.BaseController;
|
||||||
import com.heyu.api.controller.ocr.BaiduOcrResult;
|
|
||||||
import com.heyu.api.data.annotation.EbAuthentication;
|
import com.heyu.api.data.annotation.EbAuthentication;
|
||||||
import com.heyu.api.data.constants.ApiConstants;
|
import com.heyu.api.data.constants.ApiConstants;
|
||||||
|
import com.heyu.api.data.utils.MapUtils;
|
||||||
import com.heyu.api.data.utils.R;
|
import com.heyu.api.data.utils.R;
|
||||||
import com.heyu.api.data.utils.StringUtils;
|
import com.heyu.api.data.utils.StringUtils;
|
||||||
import com.heyu.api.request.car.DriverLicenseRecognizeRequest;
|
import com.heyu.api.request.car.DriverLicenseRecognizeRequest;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import com.heyu.api.resp.car.RecognizeDriverLicenseBackResp;
|
||||||
|
import com.heyu.api.resp.car.RecognizeDriverLicenseFaceResp;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 驾驶证识别控制器
|
||||||
|
* <p>
|
||||||
|
* 对机动车驾驶证正页、副页及电子驾驶证正页进行结构化识别。
|
||||||
|
* 校验、请求组装、结果封装及异常处理均在 Controller 层完成。
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 接口路径:POST /driver/license/recognize
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 返回约定:进入平台识别链路(组装请求体之后)一律 {@code R.ok()};若有异常则将详细提示写入 {@code msg}。
|
||||||
|
* 仅入参校验失败时返回 {@code R.error()}(未调用平台识别、不计费)。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author heyu
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/driver/license")
|
@RequestMapping("/driver/license")
|
||||||
public class RecognizeDriverLicenseController extends BaseController {
|
public class RecognizeDriverLicenseController extends BaseController {
|
||||||
|
|
||||||
@Autowired
|
private static final String DRIVING_LICENSE_URI = "/rest/2.0/ocr/v1/driving_license";
|
||||||
private BDriverLicenseHandle bDriverLicenseHandle;
|
|
||||||
|
/** 平台错误码 → 对外原因说明(每条语义独立,便于客服判读) */
|
||||||
|
private static final Map<String, String> PLATFORM_ERROR_HINTS = new HashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
PLATFORM_ERROR_HINTS.put("17", "本接口当日累计调用次数已触达平台日配额上限,本次识别请求被拒绝");
|
||||||
|
PLATFORM_ERROR_HINTS.put("18", "单位时间请求过于密集,已触发平台 QPS 限流,请拉长调用间隔后重试");
|
||||||
|
PLATFORM_ERROR_HINTS.put("19", "平台侧总调用配额已耗尽,需运营侧扩容或次日再试");
|
||||||
|
PLATFORM_ERROR_HINTS.put("100", "提交的表单字段组合不符合驾驶证识别接口契约,请对照文档核对字段名与取值");
|
||||||
|
PLATFORM_ERROR_HINTS.put("110", "平台鉴权凭证校验未通过,属服务端配置异常,需运维刷新令牌");
|
||||||
|
PLATFORM_ERROR_HINTS.put("111", "平台鉴权凭证已超过有效期,属服务端配置异常,需运维重新签发");
|
||||||
|
PLATFORM_ERROR_HINTS.put("216100", "存在非法或与接口不匹配的参数项");
|
||||||
|
PLATFORM_ERROR_HINTS.put("216101", "影像入参缺失:未提供可用的 image 或 url 字段");
|
||||||
|
PLATFORM_ERROR_HINTS.put("216103", "单个参数字段长度超限(常见于 url 过长或 base64 体积过大)");
|
||||||
|
PLATFORM_ERROR_HINTS.put("216110", "当前应用未开通驾驶证识别能力或授权范围不包含本接口");
|
||||||
|
PLATFORM_ERROR_HINTS.put("216200", "解码后的图片数据为空,上传环节可能未完成或内容损坏");
|
||||||
|
PLATFORM_ERROR_HINTS.put("216201", "图片编码格式不在允许列表,仅支持 jpg、jpeg、png、bmp");
|
||||||
|
PLATFORM_ERROR_HINTS.put("216202", "像素尺寸或编码后体积不满足规范(边长 15~4096px,编码后≤4M)");
|
||||||
|
PLATFORM_ERROR_HINTS.put("216203", "引擎无法从当前画面中稳定提取驾驶证文本,多见于模糊、过曝或非证件照");
|
||||||
|
PLATFORM_ERROR_HINTS.put("216630", "证件版面识别失败,建议重拍并保证四角完整、无遮挡");
|
||||||
|
PLATFORM_ERROR_HINTS.put("282000", "平台识别引擎内部异常,属短暂性故障,可间隔数秒后重试");
|
||||||
|
}
|
||||||
|
|
||||||
@EbAuthentication(tencent = ApiConstants.TENCENT_AUTH)
|
@EbAuthentication(tencent = ApiConstants.TENCENT_AUTH)
|
||||||
@PostMapping("/recognize")
|
@PostMapping("/recognize")
|
||||||
public R recognize(DriverLicenseRecognizeRequest request) {
|
public R recognize(DriverLicenseRecognizeRequest request) {
|
||||||
return BaiduOcrResult.raw(bDriverLicenseHandle.handle(toBaiduRequest(request)));
|
long start = System.currentTimeMillis();
|
||||||
|
try {
|
||||||
|
String validateError = validateRequest(request);
|
||||||
|
if (validateError != null) {
|
||||||
|
log.info("[驾驶证识别] 阶段=入参校验 结论=拦截 说明=请求未进入识别链路(不计费) "
|
||||||
|
+ "入参摘要={} 对外提示摘要={}",
|
||||||
|
buildInputLogContext(request), abbreviate(validateError, 120));
|
||||||
|
return R.error(validateError);
|
||||||
|
}
|
||||||
|
|
||||||
|
String drivingLicenseSide = resolveDrivingLicenseSide(request);
|
||||||
|
String imageMode = resolveImageMode(request);
|
||||||
|
String inputContext = buildInputLogContext(request);
|
||||||
|
|
||||||
|
String content = buildRequestContent(request, drivingLicenseSide);
|
||||||
|
if (isBlank(content)) {
|
||||||
|
log.error("[驾驶证识别] 阶段=报文组装 结论=失败 目标页面={} 页面标签={} 影像模式={} "
|
||||||
|
+ "请求体字节长度=0 入参摘要={} 原因=未写入有效image或url节点",
|
||||||
|
drivingLicenseSide, sideLabel(drivingLicenseSide), imageMode, inputContext);
|
||||||
|
return okResult(drivingLicenseSide, formatHint(
|
||||||
|
"报文组装异常",
|
||||||
|
"识别请求体在序列化后未包含任何影像载荷,平台侧无法受理本次识别",
|
||||||
|
"请核对 imageBase64 是否已 urlencode、imageUrl 是否非空字符串;"
|
||||||
|
+ "请求头须为 application/x-www-form-urlencoded",
|
||||||
|
"目标页面=" + sideLabel(drivingLicenseSide) + ",影像模式=" + imageMode
|
||||||
|
), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
int requestBodyLength = content.length();
|
||||||
|
log.info("[驾驶证识别] 阶段=平台调用 开始 目标页面={} 页面标签={} 影像模式={} "
|
||||||
|
+ "请求体字节长度={} 入参摘要={}",
|
||||||
|
drivingLicenseSide, sideLabel(drivingLicenseSide), imageMode, requestBodyLength, inputContext);
|
||||||
|
Map<String, Object> platformResult = requestBaidu(DRIVING_LICENSE_URI, content);
|
||||||
|
if (platformResult == null) {
|
||||||
|
log.error("[驾驶证识别] 阶段=平台调用 结论=无回执 目标页面={} 页面标签={} 影像模式={} "
|
||||||
|
+ "请求体字节长度={} 入参摘要={} 可能原因=链路超时/鉴权失败/响应不可解析",
|
||||||
|
drivingLicenseSide, sideLabel(drivingLicenseSide), imageMode, requestBodyLength, inputContext);
|
||||||
|
return okResult(drivingLicenseSide, formatHint(
|
||||||
|
"服务无回执",
|
||||||
|
"识别指令已下发,但在约定时间内未收到平台可解析的 JSON 回执",
|
||||||
|
"建议间隔 3~5 秒重试;若连续失败,请记录 traceId、调用时刻与 side 并联系技术支持排查链路",
|
||||||
|
"目标页面=" + sideLabel(drivingLicenseSide) + ",影像模式=" + imageMode
|
||||||
|
), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
String hint = resolvePlatformHint(platformResult, drivingLicenseSide, inputContext, imageMode);
|
||||||
|
Object data = ApiConstants.back.equals(drivingLicenseSide)
|
||||||
|
? buildBackResp(platformResult)
|
||||||
|
: buildFaceResp(platformResult);
|
||||||
|
|
||||||
|
if (hint == null && isRecognizeDataEmpty(data)) {
|
||||||
|
log.info("[驾驶证识别] 阶段=字段映射 结论=全空 目标页面={} 页面标签={} 影像模式={} "
|
||||||
|
+ "入参摘要={} 平台回执摘要={} 已映射字段数=0 说明=回执结构正常但未抽取到任何业务字段",
|
||||||
|
drivingLicenseSide, sideLabel(drivingLicenseSide), imageMode,
|
||||||
|
inputContext, buildPlatformReceiptSummary(platformResult));
|
||||||
|
hint = formatHint(
|
||||||
|
"字段映射为空",
|
||||||
|
"平台回执已通过结构校验,但证号、姓名、准驾车型等结构化字段均未命中",
|
||||||
|
ApiConstants.back.equals(drivingLicenseSide)
|
||||||
|
? "副页识别请确认 side=back,且画面包含「记录」「档案编号」等区域;避免裁剪或反光"
|
||||||
|
: "正页识别请确认 side=face/front,画面须包含完整证面;电子驾驶证需保证截图清晰",
|
||||||
|
"目标页面=" + sideLabel(drivingLicenseSide) + ",已映射字段数=0"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
long cost = System.currentTimeMillis() - start;
|
||||||
|
int mappedFieldCount = countMappedFields(data);
|
||||||
|
if (StringUtils.isNotBlank(hint)) {
|
||||||
|
log.info("[驾驶证识别] 阶段=请求结束 结论=成功(含业务提示) 目标页面={} 页面标签={} 影像模式={} "
|
||||||
|
+ "耗时={}ms 已映射字段数={} 入参摘要={} 平台回执摘要={} 对外提示摘要={}",
|
||||||
|
drivingLicenseSide, sideLabel(drivingLicenseSide), imageMode, cost, mappedFieldCount,
|
||||||
|
inputContext, buildPlatformReceiptSummary(platformResult), abbreviate(hint, 120));
|
||||||
|
} else {
|
||||||
|
log.info("[驾驶证识别] 阶段=请求结束 结论=成功 目标页面={} 页面标签={} 影像模式={} "
|
||||||
|
+ "耗时={}ms 已映射字段数={} 入参摘要={} 平台回执摘要={}",
|
||||||
|
drivingLicenseSide, sideLabel(drivingLicenseSide), imageMode, cost, mappedFieldCount,
|
||||||
|
inputContext, buildPlatformReceiptSummary(platformResult));
|
||||||
|
}
|
||||||
|
return okResult(drivingLicenseSide, hint, data);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
long cost = System.currentTimeMillis() - start;
|
||||||
|
String side = request != null ? resolveDrivingLicenseSide(request) : ApiConstants.front;
|
||||||
|
log.error("[驾驶证识别] 阶段=运行时 结论=未捕获异常 耗时={}ms 目标页面={} 页面标签={} 影像模式={} "
|
||||||
|
+ "入参摘要={} 异常类型={} 异常信息={}",
|
||||||
|
cost, side, sideLabel(side),
|
||||||
|
request != null ? resolveImageMode(request) : "未知",
|
||||||
|
buildInputLogContext(request),
|
||||||
|
e.getClass().getName(),
|
||||||
|
e.getMessage() != null ? e.getMessage() : "无", e);
|
||||||
|
return okResult(side, formatHint(
|
||||||
|
"运行时故障",
|
||||||
|
"服务端在处理识别流程时抛出未预期异常,识别结果不可用",
|
||||||
|
"请勿重复高频重试;请保存 traceId、异常发生时间及 side,由技术支持结合堆栈进一步定位",
|
||||||
|
"异常类型=" + e.getClass().getSimpleName()
|
||||||
|
+ (e.getMessage() != null ? ",摘要=" + e.getMessage() : "")
|
||||||
|
), null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private BDriverLicenseRequest toBaiduRequest(DriverLicenseRecognizeRequest request) {
|
private String validateRequest(DriverLicenseRecognizeRequest request) {
|
||||||
BDriverLicenseRequest bRequest = new BDriverLicenseRequest();
|
|
||||||
if (request == null) {
|
if (request == null) {
|
||||||
return bRequest;
|
log.info("[驾驶证识别] 阶段=入参校验 结论=拒绝 原因=Spring未绑定到请求Bean "
|
||||||
|
+ "入参摘要=request=null 计费标记=未调用平台");
|
||||||
|
return formatHint(
|
||||||
|
"入参绑定失败",
|
||||||
|
"控制器未接收到可绑定的表单对象,所有业务字段均为空",
|
||||||
|
"请确认使用 POST 提交;Content-Type 为 application/x-www-form-urlencoded;"
|
||||||
|
+ "字段名与接口文档一致(imageBase64 / imageUrl / side)",
|
||||||
|
"绑定结果=DriverLicenseRecognizeRequest 为 null"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
bRequest.setImageBase64(request.getImageBase64());
|
if (isBlank(request.getImageBase64()) && isBlank(request.getImageUrl())) {
|
||||||
bRequest.setImageUrl(request.getImageUrl());
|
log.info("[驾驶证识别] 阶段=入参校验 结论=拒绝 原因=影像双字段皆空 入参摘要={} 计费标记=未调用平台",
|
||||||
if (StringUtils.isNotBlank(request.getSide())) {
|
buildInputLogContext(request));
|
||||||
bRequest.setDrivingLicenseSide(ApiConstants.face.equals(request.getSide()) ? ApiConstants.front : request.getSide());
|
return formatHint(
|
||||||
|
"影像源缺失",
|
||||||
|
"imageBase64 与 imageUrl 均未提供,识别引擎没有可处理的图像输入",
|
||||||
|
"任选其一:① imageBase64 传 jpg/jpeg/png/bmp 的 base64(urlencode 后≤4M);"
|
||||||
|
+ "② imageUrl 传可公网直连的 HTTPS/HTTP 地址(≤1024 字符,关闭防盗链)",
|
||||||
|
"side=" + (isBlank(request.getSide()) ? "未传(将按正页处理)" : request.getSide().trim())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return bRequest;
|
if (StringUtils.isNotBlank(request.getImageBase64()) && StringUtils.isNotBlank(request.getImageUrl())) {
|
||||||
|
log.info("[驾驶证识别] 阶段=入参兼容 说明=Base64与URL同时存在,按规范丢弃URL仅保留Base64流 入参摘要={}",
|
||||||
|
buildInputLogContext(request));
|
||||||
|
}
|
||||||
|
if (resolveDrivingLicenseSide(request) == null) {
|
||||||
|
log.info("[驾驶证识别] 阶段=入参校验 结论=拒绝 原因=side非法 入参摘要={} 计费标记=未调用平台",
|
||||||
|
buildInputLogContext(request));
|
||||||
|
return formatHint(
|
||||||
|
"页面标识无效",
|
||||||
|
"参数 side 的取值无法映射到驾驶证正页或副页识别模式",
|
||||||
|
"正页(含纸质正页、电子驾驶证正页)请传 face 或 front;"
|
||||||
|
+ "副页(含实习记录等)请传 back",
|
||||||
|
"当前 side=" + request.getSide() + ",合法取值=face|front|back"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveImageMode(DriverLicenseRecognizeRequest request) {
|
||||||
|
if (request == null) {
|
||||||
|
return "未知";
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank(request.getImageBase64())) {
|
||||||
|
return StringUtils.isNotBlank(request.getImageUrl()) ? "Base64优先(已忽略URL)" : "Base64";
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank(request.getImageUrl())) {
|
||||||
|
return "URL";
|
||||||
|
}
|
||||||
|
return "无";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveDrivingLicenseSide(DriverLicenseRecognizeRequest request) {
|
||||||
|
if (request == null || isBlank(request.getSide())) {
|
||||||
|
return ApiConstants.front;
|
||||||
|
}
|
||||||
|
String side = request.getSide().trim();
|
||||||
|
if (ApiConstants.face.equals(side) || ApiConstants.front.equals(side)) {
|
||||||
|
return ApiConstants.front;
|
||||||
|
}
|
||||||
|
if (ApiConstants.back.equals(side)) {
|
||||||
|
return ApiConstants.back;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String sideLabel(String drivingLicenseSide) {
|
||||||
|
return ApiConstants.back.equals(drivingLicenseSide) ? "副页(back)" : "正页(front/face)";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildRequestContent(DriverLicenseRecognizeRequest request, String drivingLicenseSide) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if (StringUtils.isNotBlank(request.getImageBase64())) {
|
||||||
|
sb.append("&image=").append(request.getImageBase64());
|
||||||
|
} else if (StringUtils.isNotBlank(request.getImageUrl())) {
|
||||||
|
sb.append("&url=").append(request.getImageUrl());
|
||||||
|
}
|
||||||
|
sb.append("&detect_direction=false");
|
||||||
|
sb.append("&driving_license_side=").append(drivingLicenseSide);
|
||||||
|
sb.append("&unified_valid_period=false");
|
||||||
|
sb.append("&quality_warn=false");
|
||||||
|
sb.append("&risk_warn=false");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private R okResult(String drivingLicenseSide, String hint, Object data) {
|
||||||
|
if (data == null) {
|
||||||
|
data = ApiConstants.back.equals(drivingLicenseSide)
|
||||||
|
? new RecognizeDriverLicenseBackResp()
|
||||||
|
: new RecognizeDriverLicenseFaceResp();
|
||||||
|
}
|
||||||
|
R result = StringUtils.isNotBlank(hint) ? R.ok(hint) : R.ok();
|
||||||
|
return result.setData(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolvePlatformHint(Map<String, Object> platformResult, String drivingLicenseSide,
|
||||||
|
String inputContext, String imageMode) {
|
||||||
|
Object errorCodeObj = platformResult.get("error_code");
|
||||||
|
if (errorCodeObj != null) {
|
||||||
|
String errorCode = String.valueOf(errorCodeObj);
|
||||||
|
Object errorMsgObj = platformResult.get("error_msg");
|
||||||
|
String errorMsg = errorMsgObj != null ? errorMsgObj.toString() : "平台未返回文字描述";
|
||||||
|
String knownHint = PLATFORM_ERROR_HINTS.get(errorCode);
|
||||||
|
String category = resolveErrorCategory(errorCode);
|
||||||
|
log.error("[驾驶证识别] 阶段=平台回执 结论=业务拒绝 目标页面={} 页面标签={} 影像模式={} "
|
||||||
|
+ "错误分类={} 错误码={} 错误描述={} 是否命中本地错误码说明={} "
|
||||||
|
+ "入参摘要={} 平台回执摘要={}",
|
||||||
|
drivingLicenseSide, sideLabel(drivingLicenseSide), imageMode,
|
||||||
|
category, errorCode, errorMsg, knownHint != null,
|
||||||
|
inputContext, buildPlatformReceiptSummary(platformResult));
|
||||||
|
return formatHint(
|
||||||
|
category,
|
||||||
|
knownHint != null ? knownHint : "平台返回了未在本地维护的错误码,需结合错误描述人工判读",
|
||||||
|
buildPlatformErrorSuggestion(errorCode, drivingLicenseSide),
|
||||||
|
"错误码=" + errorCode + ",错误描述=" + errorMsg + ",目标页面=" + sideLabel(drivingLicenseSide)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (isWordsResultEmpty(platformResult)) {
|
||||||
|
log.info("[驾驶证识别] 阶段=结果解析 结论=空集 目标页面={} 页面标签={} 影像模式={} "
|
||||||
|
+ "入参摘要={} 平台回执摘要={} 说明=回执中不存在可遍历的结构化结果节点",
|
||||||
|
drivingLicenseSide, sideLabel(drivingLicenseSide), imageMode,
|
||||||
|
inputContext, buildPlatformReceiptSummary(platformResult));
|
||||||
|
return formatHint(
|
||||||
|
"结构化结果缺失",
|
||||||
|
"平台回执未包含可解析的识别结果集合,无法进入字段映射环节",
|
||||||
|
"优先排查:① 上传内容是否为驾驶证对应页面;② side 是否与实物一致;"
|
||||||
|
+ "③ 使用 URL 时确保平台抓取节点可访问且无 403/302 拦截",
|
||||||
|
"目标页面=" + sideLabel(drivingLicenseSide) + ",解析状态=结果集为空"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveErrorCategory(String errorCode) {
|
||||||
|
if ("17".equals(errorCode)) {
|
||||||
|
return "日配额耗尽";
|
||||||
|
}
|
||||||
|
if ("18".equals(errorCode)) {
|
||||||
|
return "并发限流";
|
||||||
|
}
|
||||||
|
if ("19".equals(errorCode)) {
|
||||||
|
return "总配额耗尽";
|
||||||
|
}
|
||||||
|
if ("110".equals(errorCode) || "111".equals(errorCode)) {
|
||||||
|
return "鉴权失效";
|
||||||
|
}
|
||||||
|
if (errorCode.startsWith("2162")) {
|
||||||
|
return "影像不合规";
|
||||||
|
}
|
||||||
|
if ("282000".equals(errorCode)) {
|
||||||
|
return "引擎内部错误";
|
||||||
|
}
|
||||||
|
return "平台业务拒绝";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildPlatformErrorSuggestion(String errorCode, String drivingLicenseSide) {
|
||||||
|
if ("17".equals(errorCode)) {
|
||||||
|
return "请安排次日再试,或联系运营提升日调用配额";
|
||||||
|
}
|
||||||
|
if ("18".equals(errorCode)) {
|
||||||
|
return "请在客户端增加限流/退避策略,避免瞬时并发过高";
|
||||||
|
}
|
||||||
|
if ("19".equals(errorCode)) {
|
||||||
|
return "请联系运营确认平台总配额与续费状态";
|
||||||
|
}
|
||||||
|
if ("110".equals(errorCode)) {
|
||||||
|
return "属服务端鉴权配置问题,需技术支持检查平台密钥与令牌缓存";
|
||||||
|
}
|
||||||
|
if ("111".equals(errorCode)) {
|
||||||
|
return "属服务端令牌过期,需技术支持重新获取并刷新缓存";
|
||||||
|
}
|
||||||
|
if ("216200".equals(errorCode)) {
|
||||||
|
return "请重新上传图片,确认 base64 未截断、未混入换行或非法字符";
|
||||||
|
}
|
||||||
|
if ("216201".equals(errorCode)) {
|
||||||
|
return "请将图片转为 jpg/jpeg/png/bmp 之一后重新编码上传";
|
||||||
|
}
|
||||||
|
if ("216202".equals(errorCode)) {
|
||||||
|
return "请压缩或裁剪图片,保证最长边≤4096px、最短边≥15px,且 urlencode 后≤4M";
|
||||||
|
}
|
||||||
|
if ("216203".equals(errorCode)) {
|
||||||
|
return "请在自然光下重拍,确保证面占画面主体且文字可辨;side=" + sideLabel(drivingLicenseSide);
|
||||||
|
}
|
||||||
|
if ("216630".equals(errorCode)) {
|
||||||
|
return "请平铺证件拍摄,避免手指遮挡关键字段;副页识别务必传 side=back";
|
||||||
|
}
|
||||||
|
if ("282000".equals(errorCode)) {
|
||||||
|
return "短暂性故障,建议 5~10 秒后单次重试,不宜连续轰炸接口";
|
||||||
|
}
|
||||||
|
return "请依据错误描述调整入参或影像后重试;持续失败请附带 traceId 联系技术支持";
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private boolean isWordsResultEmpty(Map<String, Object> platformResult) {
|
||||||
|
Object wordsResult = platformResult.get("words_result");
|
||||||
|
if (wordsResult == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (wordsResult instanceof Map) {
|
||||||
|
return ((Map<String, Object>) wordsResult).isEmpty();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isRecognizeDataEmpty(Object data) {
|
||||||
|
if (data instanceof RecognizeDriverLicenseFaceResp) {
|
||||||
|
RecognizeDriverLicenseFaceResp r = (RecognizeDriverLicenseFaceResp) data;
|
||||||
|
return isBlank(r.getLicenseNumber()) && isBlank(r.getName()) && isBlank(r.getGender())
|
||||||
|
&& isBlank(r.getNationality()) && isBlank(r.getBirthDate()) && isBlank(r.getIssueDate())
|
||||||
|
&& isBlank(r.getVehicleType()) && isBlank(r.getAddress()) && isBlank(r.getIssueUnit())
|
||||||
|
&& isBlank(r.getStartDate()) && isBlank(r.getEndDate()) && isBlank(r.getAccumulatedPoints())
|
||||||
|
&& isBlank(r.getStatus()) && isBlank(r.getArchiveNumber()) && isBlank(r.getGenerateTime())
|
||||||
|
&& isBlank(r.getCurrentTime()) && isBlank(r.getBarcodeNumber());
|
||||||
|
}
|
||||||
|
if (data instanceof RecognizeDriverLicenseBackResp) {
|
||||||
|
RecognizeDriverLicenseBackResp r = (RecognizeDriverLicenseBackResp) data;
|
||||||
|
return isBlank(r.getName()) && isBlank(r.getRecord()) && isBlank(r.getCardNumber())
|
||||||
|
&& isBlank(r.getArchiveNumber());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatHint(String category, String reason, String suggestion, String detail) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("【驾驶证识别·").append(category).append("】").append(reason);
|
||||||
|
if (StringUtils.isNotBlank(detail)) {
|
||||||
|
sb.append("|定位信息:").append(detail);
|
||||||
|
}
|
||||||
|
sb.append("|处置指引:").append(suggestion);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String abbreviate(String text, int maxLen) {
|
||||||
|
if (text == null || text.length() <= maxLen) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
return text.substring(0, maxLen) + "...";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 入参日志摘要(不打印 base64 正文,仅长度与 url 长度)
|
||||||
|
*/
|
||||||
|
private String buildInputLogContext(DriverLicenseRecognizeRequest request) {
|
||||||
|
if (request == null) {
|
||||||
|
return "request=null";
|
||||||
|
}
|
||||||
|
String sideParam = isBlank(request.getSide()) ? "未传" : request.getSide().trim();
|
||||||
|
String resolvedSide = resolveDrivingLicenseSide(request);
|
||||||
|
String targetPage = resolvedSide == null ? "无法解析" : sideLabel(resolvedSide);
|
||||||
|
return "side入参=" + sideParam
|
||||||
|
+ ",目标页面=" + targetPage
|
||||||
|
+ ",影像模式=" + resolveImageMode(request)
|
||||||
|
+ ",base64长度=" + textLength(request.getImageBase64())
|
||||||
|
+ ",url长度=" + textLength(request.getImageUrl())
|
||||||
|
+ ",url是否为空=" + isBlank(request.getImageUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平台回执日志摘要(便于失败排查)
|
||||||
|
*/
|
||||||
|
private String buildPlatformReceiptSummary(Map<String, Object> platformResult) {
|
||||||
|
if (platformResult == null) {
|
||||||
|
return "platformResult=null";
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("error_code=").append(platformResult.get("error_code"));
|
||||||
|
sb.append(",error_msg=").append(platformResult.get("error_msg"));
|
||||||
|
sb.append(",log_id=").append(platformResult.get("log_id"));
|
||||||
|
sb.append(",words_result_num=").append(platformResult.get("words_result_num"));
|
||||||
|
sb.append(",direction=").append(platformResult.get("direction"));
|
||||||
|
Object wordsResult = platformResult.get("words_result");
|
||||||
|
if (wordsResult instanceof Map) {
|
||||||
|
sb.append(",结果字段数=").append(((Map<?, ?>) wordsResult).size());
|
||||||
|
} else {
|
||||||
|
sb.append(",结果字段数=不可解析");
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int textLength(String value) {
|
||||||
|
return value == null ? 0 : value.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int countMappedFields(Object data) {
|
||||||
|
if (data instanceof RecognizeDriverLicenseFaceResp) {
|
||||||
|
RecognizeDriverLicenseFaceResp r = (RecognizeDriverLicenseFaceResp) data;
|
||||||
|
int count = 0;
|
||||||
|
if (StringUtils.isNotBlank(r.getLicenseNumber())) count++;
|
||||||
|
if (StringUtils.isNotBlank(r.getName())) count++;
|
||||||
|
if (StringUtils.isNotBlank(r.getGender())) count++;
|
||||||
|
if (StringUtils.isNotBlank(r.getNationality())) count++;
|
||||||
|
if (StringUtils.isNotBlank(r.getBirthDate())) count++;
|
||||||
|
if (StringUtils.isNotBlank(r.getIssueDate())) count++;
|
||||||
|
if (StringUtils.isNotBlank(r.getVehicleType())) count++;
|
||||||
|
if (StringUtils.isNotBlank(r.getAddress())) count++;
|
||||||
|
if (StringUtils.isNotBlank(r.getIssueUnit())) count++;
|
||||||
|
if (StringUtils.isNotBlank(r.getStartDate())) count++;
|
||||||
|
if (StringUtils.isNotBlank(r.getEndDate())) count++;
|
||||||
|
if (StringUtils.isNotBlank(r.getAccumulatedPoints())) count++;
|
||||||
|
if (StringUtils.isNotBlank(r.getStatus())) count++;
|
||||||
|
if (StringUtils.isNotBlank(r.getArchiveNumber())) count++;
|
||||||
|
if (StringUtils.isNotBlank(r.getGenerateTime())) count++;
|
||||||
|
if (StringUtils.isNotBlank(r.getCurrentTime())) count++;
|
||||||
|
if (StringUtils.isNotBlank(r.getBarcodeNumber())) count++;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
if (data instanceof RecognizeDriverLicenseBackResp) {
|
||||||
|
RecognizeDriverLicenseBackResp r = (RecognizeDriverLicenseBackResp) data;
|
||||||
|
int count = 0;
|
||||||
|
if (StringUtils.isNotBlank(r.getName())) count++;
|
||||||
|
if (StringUtils.isNotBlank(r.getRecord())) count++;
|
||||||
|
if (StringUtils.isNotBlank(r.getCardNumber())) count++;
|
||||||
|
if (StringUtils.isNotBlank(r.getArchiveNumber())) count++;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RecognizeDriverLicenseFaceResp buildFaceResp(Map<String, Object> data) {
|
||||||
|
RecognizeDriverLicenseFaceResp resp = new RecognizeDriverLicenseFaceResp();
|
||||||
|
resp.setLicenseNumber(getWords(data, "证号"));
|
||||||
|
resp.setName(getWords(data, "姓名"));
|
||||||
|
resp.setGender(getWords(data, "性别"));
|
||||||
|
resp.setNationality(getWords(data, "国籍"));
|
||||||
|
resp.setBirthDate(getWords(data, "出生日期"));
|
||||||
|
resp.setIssueDate(getWords(data, "初次领证日期"));
|
||||||
|
resp.setVehicleType(getWords(data, "准驾车型"));
|
||||||
|
resp.setAddress(getWords(data, "住址"));
|
||||||
|
resp.setIssueUnit(getWords(data, "发证单位"));
|
||||||
|
resp.setStartDate(firstNonBlank(getWords(data, "有效起始日期"), getWords(data, "有效期限")));
|
||||||
|
resp.setEndDate(firstNonBlank(getWords(data, "失效日期"), getWords(data, "至")));
|
||||||
|
resp.setAccumulatedPoints(getWords(data, "累积记分"));
|
||||||
|
resp.setStatus(getWords(data, "状态"));
|
||||||
|
resp.setArchiveNumber(getWords(data, "档案编号"));
|
||||||
|
resp.setGenerateTime(getWords(data, "生成时间"));
|
||||||
|
resp.setCurrentTime(getWords(data, "当前时间"));
|
||||||
|
resp.setBarcodeNumber(getWords(data, "条形码下编号"));
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RecognizeDriverLicenseBackResp buildBackResp(Map<String, Object> data) {
|
||||||
|
RecognizeDriverLicenseBackResp resp = new RecognizeDriverLicenseBackResp();
|
||||||
|
resp.setName(getWords(data, "姓名"));
|
||||||
|
resp.setRecord(getWords(data, "记录"));
|
||||||
|
resp.setCardNumber(getWords(data, "证号"));
|
||||||
|
resp.setArchiveNumber(getWords(data, "档案编号"));
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getWords(Map<String, Object> data, String field) {
|
||||||
|
return MapUtils.getByExpr(data, "words_result." + field + ".words");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String firstNonBlank(String first, String second) {
|
||||||
|
if (StringUtils.isNotBlank(first)) {
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
return second;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user