提交修改
This commit is contained in:
parent
06ac8d6445
commit
c272282d55
@ -14,6 +14,9 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@ -37,59 +40,18 @@ import java.util.Map;
|
||||
* <li>鉴权:{@link EbAuthentication}(Tencent 鉴权头,见项目网关配置)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h3>请求参数({@link DriverLicenseRecognizeRequest})</h3>
|
||||
* <table>
|
||||
* <tr><th>字段</th><th>必填</th><th>说明</th></tr>
|
||||
* <tr><td>imageBase64</td><td>与 imageUrl 二选一</td>
|
||||
* <td>图片 Base64,需 urlencode;编码后≤4M;支持 jpg/jpeg/png/bmp;优先级高于 url</td></tr>
|
||||
* <tr><td>imageUrl</td><td>与 imageBase64 二选一</td>
|
||||
* <td>图片完整 URL,≤1024 字节;需公网可访问并关闭防盗链</td></tr>
|
||||
* <tr><td>side</td><td>否,默认 face</td>
|
||||
* <td>{@code face}/{@code front}:正页/电子证正页;{@code back}:副页。
|
||||
* 映射百度参数 {@code driving_license_side=front|back}</td></tr>
|
||||
* </table>
|
||||
*
|
||||
* <h3>返回结构(统一包装 {@link R})</h3>
|
||||
* <pre>
|
||||
* {
|
||||
* "code": "200", // 成功为 200;仅入参非法时为 400
|
||||
* "msg": "...", // 成功默认文案;业务异常时写入详细提示(仍可能 code=200)
|
||||
* "traceId": "...",
|
||||
* "data": { ... } // 见下方 data 结构,失败时可能为空对象
|
||||
* }
|
||||
* </pre>
|
||||
* <p><b>返回约定(重要):</b></p>
|
||||
* <h3>返回约定(重要)</h3>
|
||||
* <ul>
|
||||
* <li>入参校验失败:{@code R.error()},<strong>未调用百度、不计费</strong></li>
|
||||
* <li>已调用百度识别链路:一律 {@code R.ok()};若图片/平台/识别异常,将说明写入 {@code msg},
|
||||
* {@code data} 可能为空或字段不全(对外不暴露「百度」字样,日志内可排查)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h3>data 结构(按 side 区分)</h3>
|
||||
* <p><b>正页 / 电子证正页</b>({@link RecognizeDriverLicenseFaceResp},side=face 或 front):</p>
|
||||
* <ul>
|
||||
* <li>纸质正页常见字段:licenseNumber 证号、name 姓名、gender 性别、nationality 国籍、
|
||||
* birthDate 出生日期、issueDate 初次领证日期、vehicleType 准驾车型、startDate 有效起始日期、
|
||||
* endDate 失效日期、address 住址、issueUnit 发证单位</li>
|
||||
* <li>电子驾驶证正页额外:accumulatedPoints 累积记分、status 状态、archiveNumber 档案编号、
|
||||
* generateTime 生成时间、currentTime 当前时间、barcodeNumber 条形码下编号</li>
|
||||
* </ul>
|
||||
* <p><b>副页</b>({@link RecognizeDriverLicenseBackResp},side=back):</p>
|
||||
* <ul>
|
||||
* <li>name 姓名、record 记录、cardNumber 证号、archiveNumber 档案编号</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>字段与百度 {@code words_result} 中中文 key 的对应关系见
|
||||
* <a href="https://cloud.baidu.com/doc/OCR/s/Vk3h7xzz7">官方返回示例</a>。</p>
|
||||
*
|
||||
* @author heyu
|
||||
* @since 1.0.0
|
||||
* @see DriverLicenseRecognizeRequest
|
||||
* @see RecognizeDriverLicenseFaceResp
|
||||
* @see RecognizeDriverLicenseBackResp
|
||||
*
|
||||
*
|
||||
* 开发者: 瞿贻晓
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@ -99,49 +61,11 @@ public class RecognizeDriverLicenseController extends BaseController {
|
||||
/** 百度驾驶证识别 API 路径,完整地址见类注释 */
|
||||
private static final String DRIVING_LICENSE_URI = "/rest/2.0/ocr/v1/driving_license";
|
||||
|
||||
/** 百度 error_code → 对外 msg 原因说明(日志与返回 msg 使用「平台」表述,注释可写百度) */
|
||||
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", "平台识别引擎内部异常,属短暂性故障,可间隔数秒后重试");
|
||||
}
|
||||
/** 入参绑定失败/page side 无法解析等错误中的"未传"展示文案 */
|
||||
private static final String SIDE_DEFAULT_HINT = "未传(将按正页处理)";
|
||||
|
||||
/**
|
||||
* 驾驶证识别
|
||||
* <p>
|
||||
* 请求示例(form 表单):
|
||||
* </p>
|
||||
* <pre>
|
||||
* POST /driver/license/recognize
|
||||
* Content-Type: application/x-www-form-urlencoded
|
||||
*
|
||||
* imageUrl=https://example.com/license.jpg&side=face
|
||||
* // 或 imageBase64=xxx&side=back
|
||||
* </pre>
|
||||
* <p>
|
||||
* 成功示例(正页,data 为 {@link RecognizeDriverLicenseFaceResp}):
|
||||
* </p>
|
||||
* <pre>
|
||||
* { "code":"200", "msg":"成功", "data": { "licenseNumber":"...", "name":"...", "vehicleType":"C1", ... } }
|
||||
* </pre>
|
||||
* <p>
|
||||
* 已调用百度但识别异常时仍返回 code=200,msg 中带处置指引,data 可能为空对象。
|
||||
* </p>
|
||||
*
|
||||
* @param request 驾驶证识别请求,字段见类注释「请求参数」
|
||||
* @return 正页/电子证正页返回 {@link RecognizeDriverLicenseFaceResp};副页返回 {@link RecognizeDriverLicenseBackResp}
|
||||
@ -150,6 +74,7 @@ public class RecognizeDriverLicenseController extends BaseController {
|
||||
@PostMapping("/recognize")
|
||||
public R recognize(DriverLicenseRecognizeRequest request) {
|
||||
long start = System.currentTimeMillis();
|
||||
RecognizeContext ctx = null;
|
||||
try {
|
||||
String validateError = validateRequest(request);
|
||||
if (validateError != null) {
|
||||
@ -158,80 +83,50 @@ public class RecognizeDriverLicenseController extends BaseController {
|
||||
return R.error(validateError);
|
||||
}
|
||||
|
||||
String drivingLicenseSide = resolveDrivingLicenseSide(request);
|
||||
String imageMode = resolveImageMode(request);
|
||||
String inputContext = buildInputLogContext(request);
|
||||
ctx = new RecognizeContext(
|
||||
resolveDrivingLicenseSide(request),
|
||||
ImageMode.of(request),
|
||||
buildInputLogContext(request));
|
||||
|
||||
String content = buildRequestContent(request, drivingLicenseSide);
|
||||
String content = buildRequestContent(request, ctx.side);
|
||||
if (isBlank(content)) {
|
||||
log.error("驾驶证识别:组装请求失败,请求里没带有效图片。识别{},{}。{}",
|
||||
sideDesc(drivingLicenseSide), imageModeDesc(imageMode), inputContext);
|
||||
return okResult(drivingLicenseSide, formatHint(
|
||||
sideDesc(ctx.side), ctx.imageMode.desc, ctx.inputLog);
|
||||
return okResult(ctx.side, formatHint(
|
||||
"报文组装异常",
|
||||
"识别请求体在序列化后未包含任何影像载荷,平台侧无法受理本次识别",
|
||||
"请核对 imageBase64 是否已 urlencode、imageUrl 是否非空字符串;"
|
||||
+ "请求头须为 application/x-www-form-urlencoded",
|
||||
"目标页面=" + sideLabel(drivingLicenseSide) + ",影像模式=" + imageMode
|
||||
"目标页面=" + sideLabel(ctx.side) + ",影像模式=" + ctx.imageMode.name()
|
||||
), null);
|
||||
}
|
||||
|
||||
int requestBodyLength = content.length();
|
||||
log.info("驾驶证识别:开始调用平台识别。识别{},{},请求大小约 {} 字节。{}",
|
||||
sideDesc(drivingLicenseSide), imageModeDesc(imageMode), requestBodyLength, inputContext);
|
||||
Map<String, Object> platformResult = requestBaidu(DRIVING_LICENSE_URI, content);
|
||||
Map<String, Object> platformResult = callPlatform(content, ctx);
|
||||
if (platformResult == null) {
|
||||
log.error("驾驶证识别:平台没有返回结果(可能网络超时、鉴权失败或响应无法解析)。识别{},{},"
|
||||
+ "请求约 {} 字节。{}",
|
||||
sideDesc(drivingLicenseSide), imageModeDesc(imageMode), requestBodyLength, inputContext);
|
||||
return okResult(drivingLicenseSide, formatHint(
|
||||
return okResult(ctx.side, formatHint(
|
||||
"服务无回执",
|
||||
"识别指令已下发,但在约定时间内未收到平台可解析的 JSON 回执",
|
||||
"建议间隔 3~5 秒重试;若连续失败,请记录 traceId、调用时刻与 side 并联系技术支持排查链路",
|
||||
"目标页面=" + sideLabel(drivingLicenseSide) + ",影像模式=" + imageMode
|
||||
"目标页面=" + sideLabel(ctx.side) + ",影像模式=" + ctx.imageMode.name()
|
||||
), null);
|
||||
}
|
||||
|
||||
String hint = resolvePlatformHint(platformResult, drivingLicenseSide, inputContext, imageMode);
|
||||
Object data = ApiConstants.back.equals(drivingLicenseSide)
|
||||
Object data = ApiConstants.back.equals(ctx.side)
|
||||
? buildBackResp(platformResult)
|
||||
: buildFaceResp(platformResult);
|
||||
String hint = resolvePlatformHint(platformResult, ctx, data);
|
||||
|
||||
if (hint == null && isRecognizeDataEmpty(data)) {
|
||||
log.info("驾驶证识别:平台有返回,但证号、姓名等字段一个都没识别出来(可能传错正/副页或图片不清晰)。"
|
||||
+ "识别{},{}。客户传的:{}。平台回执:{}",
|
||||
sideDesc(drivingLicenseSide), imageModeDesc(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,识别出 {} 个字段。"
|
||||
+ "识别{},{}。客户传的:{}。平台回执:{}。给客户的提示:{}",
|
||||
cost, mappedFieldCount, sideDesc(drivingLicenseSide), imageModeDesc(imageMode),
|
||||
inputContext, buildPlatformReceiptSummary(platformResult), abbreviate(hint, 120));
|
||||
} else {
|
||||
log.info("驾驶证识别:识别成功。耗时 {} ms,共识别出 {} 个字段。识别{},{}。客户传的:{}。平台回执:{}",
|
||||
cost, mappedFieldCount, sideDesc(drivingLicenseSide), imageModeDesc(imageMode),
|
||||
inputContext, buildPlatformReceiptSummary(platformResult));
|
||||
}
|
||||
return okResult(drivingLicenseSide, hint, data);
|
||||
logRecognizeResult(ctx, platformResult, data, hint, start);
|
||||
return okResult(ctx.side, hint, data);
|
||||
|
||||
} catch (Exception e) {
|
||||
long cost = System.currentTimeMillis() - start;
|
||||
String side = request != null ? resolveDrivingLicenseSide(request) : ApiConstants.front;
|
||||
String side = ctx != null ? ctx.side
|
||||
: (request != null ? resolveDrivingLicenseSide(request) : ApiConstants.front);
|
||||
String mode = ctx != null ? ctx.imageMode.desc
|
||||
: (request != null ? ImageMode.of(request).desc : "未知");
|
||||
log.error("驾驶证识别:程序运行出错,耗时 {} ms。识别{},{}。客户传的:{}。异常:{} - {}",
|
||||
cost, sideDesc(side),
|
||||
request != null ? imageModeDesc(resolveImageMode(request)) : "未知",
|
||||
buildInputLogContext(request),
|
||||
cost, sideDesc(side), mode, buildInputLogContext(request),
|
||||
e.getClass().getSimpleName(),
|
||||
e.getMessage() != null ? e.getMessage() : "无具体说明", e);
|
||||
return okResult(side, formatHint(
|
||||
@ -244,6 +139,86 @@ public class RecognizeDriverLicenseController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
// ===================== 流程拆分方法 =====================
|
||||
|
||||
private Map<String, Object> callPlatform(String content, RecognizeContext ctx) {
|
||||
int len = content.length();
|
||||
log.info("驾驶证识别:开始调用平台识别。识别{},{},请求大小约 {} 字节。{}",
|
||||
sideDesc(ctx.side), ctx.imageMode.desc, len, ctx.inputLog);
|
||||
Map<String, Object> result = requestBaidu(DRIVING_LICENSE_URI, content);
|
||||
if (result == null) {
|
||||
log.error("驾驶证识别:平台没有返回结果(可能网络超时、鉴权失败或响应无法解析)。"
|
||||
+ "识别{},{},请求约 {} 字节。{}",
|
||||
sideDesc(ctx.side), ctx.imageMode.desc, len, ctx.inputLog);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String resolvePlatformHint(Map<String, Object> platformResult, RecognizeContext ctx, Object data) {
|
||||
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 category = PlatformError.categoryOf(errorCode);
|
||||
log.error("驾驶证识别:平台拒绝了本次识别。[{}] 错误码 {},原因:{}。识别{},{}。客户传的:{}。{}",
|
||||
category, errorCode, errorMsg,
|
||||
sideDesc(ctx.side), ctx.imageMode.desc,
|
||||
ctx.inputLog, buildPlatformReceiptSummary(platformResult));
|
||||
return formatHint(
|
||||
category,
|
||||
PlatformError.reasonOf(errorCode),
|
||||
PlatformError.suggestionOf(errorCode, sideLabel(ctx.side)),
|
||||
"错误码=" + errorCode + ",错误描述=" + errorMsg + ",目标页面=" + sideLabel(ctx.side)
|
||||
);
|
||||
}
|
||||
if (isWordsResultEmpty(platformResult)) {
|
||||
log.info("驾驶证识别:平台返回了,但没有识别结果数据(可能不是驾驶证或 side 传错)。识别{},{}。客户传的:{}。{}",
|
||||
sideDesc(ctx.side), ctx.imageMode.desc,
|
||||
ctx.inputLog, buildPlatformReceiptSummary(platformResult));
|
||||
return formatHint(
|
||||
"结构化结果缺失",
|
||||
"平台回执未包含可解析的识别结果集合,无法进入字段映射环节",
|
||||
"优先排查:① 上传内容是否为驾驶证对应页面;② side 是否与实物一致;"
|
||||
+ "③ 使用 URL 时确保平台抓取节点可访问且无 403/302 拦截",
|
||||
"目标页面=" + sideLabel(ctx.side) + ",解析状态=结果集为空"
|
||||
);
|
||||
}
|
||||
if (isAllStringFieldsBlank(data)) {
|
||||
log.info("驾驶证识别:平台有返回,但证号、姓名等字段一个都没识别出来(可能传错正/副页或图片不清晰)。"
|
||||
+ "识别{},{}。客户传的:{}。平台回执:{}",
|
||||
sideDesc(ctx.side), ctx.imageMode.desc,
|
||||
ctx.inputLog, buildPlatformReceiptSummary(platformResult));
|
||||
return formatHint(
|
||||
"字段映射为空",
|
||||
"平台回执已通过结构校验,但证号、姓名、准驾车型等结构化字段均未命中",
|
||||
ApiConstants.back.equals(ctx.side)
|
||||
? "副页识别请确认 side=back,且画面包含「记录」「档案编号」等区域;避免裁剪或反光"
|
||||
: "正页识别请确认 side=face/front,画面须包含完整证面;电子驾驶证需保证截图清晰",
|
||||
"目标页面=" + sideLabel(ctx.side) + ",已映射字段数=0"
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void logRecognizeResult(RecognizeContext ctx, Map<String, Object> platformResult,
|
||||
Object data, String hint, long start) {
|
||||
long cost = System.currentTimeMillis() - start;
|
||||
int mapped = countNonBlankStringFields(data);
|
||||
if (StringUtils.isNotBlank(hint)) {
|
||||
log.info("驾驶证识别:处理结束(接口仍返回成功,但带了提示信息)。耗时 {} ms,识别出 {} 个字段。"
|
||||
+ "识别{},{}。客户传的:{}。平台回执:{}。给客户的提示:{}",
|
||||
cost, mapped, sideDesc(ctx.side), ctx.imageMode.desc,
|
||||
ctx.inputLog, buildPlatformReceiptSummary(platformResult), abbreviate(hint, 120));
|
||||
} else {
|
||||
log.info("驾驶证识别:识别成功。耗时 {} ms,共识别出 {} 个字段。识别{},{}。客户传的:{}。平台回执:{}",
|
||||
cost, mapped, sideDesc(ctx.side), ctx.imageMode.desc,
|
||||
ctx.inputLog, buildPlatformReceiptSummary(platformResult));
|
||||
}
|
||||
}
|
||||
|
||||
// ===================== 校验与上下文 =====================
|
||||
|
||||
private String validateRequest(DriverLicenseRecognizeRequest request) {
|
||||
if (request == null) {
|
||||
log.info("驾驶证识别:没收到任何请求参数(表单未绑定成功),直接拒绝,未调用识别、不扣费");
|
||||
@ -263,7 +238,7 @@ public class RecognizeDriverLicenseController extends BaseController {
|
||||
"imageBase64 与 imageUrl 均未提供,识别引擎没有可处理的图像输入",
|
||||
"任选其一:① imageBase64 传 jpg/jpeg/png/bmp 的 base64(urlencode 后≤4M);"
|
||||
+ "② imageUrl 传可公网直连的 HTTPS/HTTP 地址(≤1024 字符,关闭防盗链)",
|
||||
"side=" + (isBlank(request.getSide()) ? "未传(将按正页处理)" : request.getSide().trim())
|
||||
"side=" + (isBlank(request.getSide()) ? SIDE_DEFAULT_HINT : request.getSide().trim())
|
||||
);
|
||||
}
|
||||
if (StringUtils.isNotBlank(request.getImageBase64()) && StringUtils.isNotBlank(request.getImageUrl())) {
|
||||
@ -284,19 +259,6 @@ public class RecognizeDriverLicenseController extends BaseController {
|
||||
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;
|
||||
@ -311,31 +273,15 @@ public class RecognizeDriverLicenseController extends BaseController {
|
||||
return null;
|
||||
}
|
||||
|
||||
private String sideLabel(String drivingLicenseSide) {
|
||||
return ApiConstants.back.equals(drivingLicenseSide) ? "副页(back)" : "正页(front/face)";
|
||||
private String sideLabel(String side) {
|
||||
return ApiConstants.back.equals(side) ? "副页(back)" : "正页(front/face)";
|
||||
}
|
||||
|
||||
/** 日志用:正页/副页中文说明 */
|
||||
private String sideDesc(String drivingLicenseSide) {
|
||||
return ApiConstants.back.equals(drivingLicenseSide) ? "驾驶证副页" : "驾驶证正页";
|
||||
private String sideDesc(String side) {
|
||||
return ApiConstants.back.equals(side) ? "驾驶证副页" : "驾驶证正页";
|
||||
}
|
||||
|
||||
/** 日志用:图片提交方式中文说明 */
|
||||
private String imageModeDesc(String imageMode) {
|
||||
if ("Base64".equals(imageMode)) {
|
||||
return "使用 Base64 编码图片";
|
||||
}
|
||||
if ("URL".equals(imageMode)) {
|
||||
return "使用图片链接";
|
||||
}
|
||||
if (imageMode != null && imageMode.contains("Base64")) {
|
||||
return "同时传了 Base64 和链接,实际使用 Base64";
|
||||
}
|
||||
if ("无".equals(imageMode)) {
|
||||
return "未传图片";
|
||||
}
|
||||
return "图片提交方式未知";
|
||||
}
|
||||
// ===================== 请求/响应构造 =====================
|
||||
|
||||
private String buildRequestContent(DriverLicenseRecognizeRequest request, String drivingLicenseSide) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
@ -362,231 +308,6 @@ public class RecognizeDriverLicenseController extends BaseController {
|
||||
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("驾驶证识别:平台拒绝了本次识别。[{}] 错误码 {},原因:{}。识别{},{}。客户传的:{}。{}",
|
||||
category, errorCode, errorMsg,
|
||||
sideDesc(drivingLicenseSide), imageModeDesc(imageMode),
|
||||
inputContext, buildPlatformReceiptSummary(platformResult));
|
||||
return formatHint(
|
||||
category,
|
||||
knownHint != null ? knownHint : "平台返回了未在本地维护的错误码,需结合错误描述人工判读",
|
||||
buildPlatformErrorSuggestion(errorCode, drivingLicenseSide),
|
||||
"错误码=" + errorCode + ",错误描述=" + errorMsg + ",目标页面=" + sideLabel(drivingLicenseSide)
|
||||
);
|
||||
}
|
||||
if (isWordsResultEmpty(platformResult)) {
|
||||
log.info("驾驶证识别:平台返回了,但没有识别结果数据(可能不是驾驶证或 side 传错)。识别{},{}。客户传的:{}。{}",
|
||||
sideDesc(drivingLicenseSide), imageModeDesc(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 正文,只说长度)
|
||||
*/
|
||||
private String buildInputLogContext(DriverLicenseRecognizeRequest request) {
|
||||
if (request == null) {
|
||||
return "未收到请求体";
|
||||
}
|
||||
String sideParam = isBlank(request.getSide()) ? "未传(默认按正页)" : request.getSide().trim();
|
||||
String resolvedSide = resolveDrivingLicenseSide(request);
|
||||
String targetPage = resolvedSide == null ? "side 无法识别" : sideDesc(resolvedSide);
|
||||
String imageMode = resolveImageMode(request);
|
||||
int base64Len = textLength(request.getImageBase64());
|
||||
int urlLen = textLength(request.getImageUrl());
|
||||
return String.format("识别%s,side 参数=%s,%s,Base64 长度 %d 字符,图片链接长度 %d 字符",
|
||||
targetPage, sideParam, imageModeDesc(imageMode), base64Len, urlLen);
|
||||
}
|
||||
|
||||
/**
|
||||
* 平台回执日志说明(便于失败排查)
|
||||
*/
|
||||
private String buildPlatformReceiptSummary(Map<String, Object> platformResult) {
|
||||
if (platformResult == null) {
|
||||
return "平台无任何回执";
|
||||
}
|
||||
Object errorCode = platformResult.get("error_code");
|
||||
Object errorMsg = platformResult.get("error_msg");
|
||||
Object logId = platformResult.get("log_id");
|
||||
if (errorCode != null) {
|
||||
return String.format("平台返回失败,错误码 %s,说明:%s,流水号 %s",
|
||||
errorCode,
|
||||
errorMsg != null ? errorMsg : "无",
|
||||
logId != null ? logId : "无");
|
||||
}
|
||||
Object wordsResultNum = platformResult.get("words_result_num");
|
||||
Object wordsResult = platformResult.get("words_result");
|
||||
int fieldCount = 0;
|
||||
if (wordsResult instanceof Map) {
|
||||
fieldCount = ((Map<?, ?>) wordsResult).size();
|
||||
}
|
||||
return String.format("平台返回成功,识别结果约 %s 项、结构化字段 %d 个,流水号 %s",
|
||||
wordsResultNum != null ? wordsResultNum : "未知",
|
||||
fieldCount,
|
||||
logId != null ? logId : "无");
|
||||
}
|
||||
|
||||
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, "证号"));
|
||||
@ -623,9 +344,264 @@ public class RecognizeDriverLicenseController extends BaseController {
|
||||
}
|
||||
|
||||
private String firstNonBlank(String first, String second) {
|
||||
if (StringUtils.isNotBlank(first)) {
|
||||
return first;
|
||||
return StringUtils.isNotBlank(first) ? first : second;
|
||||
}
|
||||
|
||||
// ===================== 通用工具 =====================
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
/** 统计响应对象中非空 String 字段数量(基于反射,避免新增字段时遗漏维护) */
|
||||
private static int countNonBlankStringFields(Object data) {
|
||||
if (data == null) {
|
||||
return 0;
|
||||
}
|
||||
int count = 0;
|
||||
for (Field f : data.getClass().getDeclaredFields()) {
|
||||
if (!String.class.equals(f.getType())) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
f.setAccessible(true);
|
||||
Object v = f.get(data);
|
||||
if (v instanceof String && StringUtils.isNotBlank((String) v)) {
|
||||
count++;
|
||||
}
|
||||
} catch (IllegalAccessException ignored) {
|
||||
// 反射失败视为该字段为空,不影响整体统计
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private static boolean isAllStringFieldsBlank(Object data) {
|
||||
return countNonBlankStringFields(data) == 0;
|
||||
}
|
||||
|
||||
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 正文,只说长度) */
|
||||
private String buildInputLogContext(DriverLicenseRecognizeRequest request) {
|
||||
if (request == null) {
|
||||
return "未收到请求体";
|
||||
}
|
||||
String sideParam = isBlank(request.getSide()) ? "未传(默认按正页)" : request.getSide().trim();
|
||||
String resolvedSide = resolveDrivingLicenseSide(request);
|
||||
String targetPage = resolvedSide == null ? "side 无法识别" : sideDesc(resolvedSide);
|
||||
ImageMode mode = ImageMode.of(request);
|
||||
return String.format("识别%s,side 参数=%s,%s,Base64 长度 %d 字符,图片链接长度 %d 字符",
|
||||
targetPage, sideParam, mode.desc,
|
||||
textLength(request.getImageBase64()), textLength(request.getImageUrl()));
|
||||
}
|
||||
|
||||
/** 平台回执日志说明(便于失败排查) */
|
||||
private String buildPlatformReceiptSummary(Map<String, Object> platformResult) {
|
||||
if (platformResult == null) {
|
||||
return "平台无任何回执";
|
||||
}
|
||||
Object errorCode = platformResult.get("error_code");
|
||||
Object errorMsg = platformResult.get("error_msg");
|
||||
Object logId = platformResult.get("log_id");
|
||||
if (errorCode != null) {
|
||||
return String.format("平台返回失败,错误码 %s,说明:%s,流水号 %s",
|
||||
errorCode,
|
||||
errorMsg != null ? errorMsg : "无",
|
||||
logId != null ? logId : "无");
|
||||
}
|
||||
Object wordsResultNum = platformResult.get("words_result_num");
|
||||
Object wordsResult = platformResult.get("words_result");
|
||||
int fieldCount = wordsResult instanceof Map ? ((Map<?, ?>) wordsResult).size() : 0;
|
||||
return String.format("平台返回成功,识别结果约 %s 项、结构化字段 %d 个,流水号 %s",
|
||||
wordsResultNum != null ? wordsResultNum : "未知",
|
||||
fieldCount,
|
||||
logId != null ? logId : "无");
|
||||
}
|
||||
|
||||
private int textLength(String value) {
|
||||
return value == null ? 0 : value.length();
|
||||
}
|
||||
|
||||
// ===================== 内部类型 =====================
|
||||
|
||||
/**
|
||||
* 单次识别请求的上下文,集中保存 side / imageMode / 入参日志摘要,避免在主流程中反复解析。
|
||||
*/
|
||||
private static class RecognizeContext {
|
||||
final String side;
|
||||
final ImageMode imageMode;
|
||||
final String inputLog;
|
||||
|
||||
RecognizeContext(String side, ImageMode imageMode, String inputLog) {
|
||||
this.side = side;
|
||||
this.imageMode = imageMode;
|
||||
this.inputLog = inputLog;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片提交方式枚举,拆分"逻辑标识"与"展示文案",避免字符串比较散落。
|
||||
*/
|
||||
private enum ImageMode {
|
||||
BASE64("使用 Base64 编码图片"),
|
||||
URL("使用图片链接"),
|
||||
BASE64_WITH_URL_IGNORED("同时传了 Base64 和链接,实际使用 Base64"),
|
||||
NONE("未传图片"),
|
||||
UNKNOWN("图片提交方式未知");
|
||||
|
||||
final String desc;
|
||||
|
||||
ImageMode(String desc) {
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
static ImageMode of(DriverLicenseRecognizeRequest req) {
|
||||
if (req == null) {
|
||||
return UNKNOWN;
|
||||
}
|
||||
boolean hasBase64 = StringUtils.isNotBlank(req.getImageBase64());
|
||||
boolean hasUrl = StringUtils.isNotBlank(req.getImageUrl());
|
||||
if (hasBase64 && hasUrl) return BASE64_WITH_URL_IGNORED;
|
||||
if (hasBase64) return BASE64;
|
||||
if (hasUrl) return URL;
|
||||
return NONE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 平台错误码元数据。
|
||||
* <p>
|
||||
* 将原本散落在 PLATFORM_ERROR_HINTS / resolveErrorCategory / buildPlatformErrorSuggestion 三处
|
||||
* 的错误码定义合并为单一来源;新增错误码只需追加一项枚举值,避免分散维护造成的漏改。
|
||||
* </p>
|
||||
*/
|
||||
private enum PlatformError {
|
||||
QPS_DAILY_LIMIT("17", "日配额耗尽",
|
||||
"本接口当日累计调用次数已触达平台日配额上限,本次识别请求被拒绝",
|
||||
"请安排次日再试,或联系运营提升日调用配额"),
|
||||
QPS_RATE_LIMIT("18", "并发限流",
|
||||
"单位时间请求过于密集,已触发平台 QPS 限流,请拉长调用间隔后重试",
|
||||
"请在客户端增加限流/退避策略,避免瞬时并发过高"),
|
||||
QPS_TOTAL_EXHAUSTED("19", "总配额耗尽",
|
||||
"平台侧总调用配额已耗尽,需运营侧扩容或次日再试",
|
||||
"请联系运营确认平台总配额与续费状态"),
|
||||
PARAM_FORMAT("100", "平台业务拒绝",
|
||||
"提交的表单字段组合不符合驾驶证识别接口契约,请对照文档核对字段名与取值",
|
||||
null),
|
||||
AUTH_INVALID("110", "鉴权失效",
|
||||
"平台鉴权凭证校验未通过,属服务端配置异常,需运维刷新令牌",
|
||||
"属服务端鉴权配置问题,需技术支持检查平台密钥与令牌缓存"),
|
||||
AUTH_EXPIRED("111", "鉴权失效",
|
||||
"平台鉴权凭证已超过有效期,属服务端配置异常,需运维重新签发",
|
||||
"属服务端令牌过期,需技术支持重新获取并刷新缓存"),
|
||||
PARAM_ILLEGAL("216100", "影像不合规",
|
||||
"存在非法或与接口不匹配的参数项",
|
||||
null),
|
||||
IMAGE_MISSING("216101", "影像不合规",
|
||||
"影像入参缺失:未提供可用的 image 或 url 字段",
|
||||
null),
|
||||
PARAM_TOO_LONG("216103", "影像不合规",
|
||||
"单个参数字段长度超限(常见于 url 过长或 base64 体积过大)",
|
||||
null),
|
||||
APP_NOT_ENABLED("216110", "影像不合规",
|
||||
"当前应用未开通驾驶证识别能力或授权范围不包含本接口",
|
||||
null),
|
||||
IMAGE_EMPTY("216200", "影像不合规",
|
||||
"解码后的图片数据为空,上传环节可能未完成或内容损坏",
|
||||
"请重新上传图片,确认 base64 未截断、未混入换行或非法字符"),
|
||||
IMAGE_FORMAT("216201", "影像不合规",
|
||||
"图片编码格式不在允许列表,仅支持 jpg、jpeg、png、bmp",
|
||||
"请将图片转为 jpg/jpeg/png/bmp 之一后重新编码上传"),
|
||||
IMAGE_SIZE("216202", "影像不合规",
|
||||
"像素尺寸或编码后体积不满足规范(边长 15~4096px,编码后≤4M)",
|
||||
"请压缩或裁剪图片,保证最长边≤4096px、最短边≥15px,且 urlencode 后≤4M"),
|
||||
IMAGE_BLUR("216203", "影像不合规",
|
||||
"引擎无法从当前画面中稳定提取驾驶证文本,多见于模糊、过曝或非证件照",
|
||||
null), // 建议带 sideLabel 动态拼接,见 suggestionOf
|
||||
LAYOUT_FAIL("216630", "影像不合规",
|
||||
"证件版面识别失败,建议重拍并保证四角完整、无遮挡",
|
||||
"请平铺证件拍摄,避免手指遮挡关键字段;副页识别务必传 side=back"),
|
||||
ENGINE_INTERNAL("282000", "引擎内部错误",
|
||||
"平台识别引擎内部异常,属短暂性故障,可间隔数秒后重试",
|
||||
"短暂性故障,建议 5~10 秒后单次重试,不宜连续轰炸接口");
|
||||
|
||||
private static final String IMAGE_PREFIX = "2162";
|
||||
private static final String DEFAULT_CATEGORY = "平台业务拒绝";
|
||||
private static final String IMAGE_CATEGORY = "影像不合规";
|
||||
private static final String DEFAULT_REASON = "平台返回了未在本地维护的错误码,需结合错误描述人工判读";
|
||||
private static final String DEFAULT_SUGGESTION =
|
||||
"请依据错误描述调整入参或影像后重试;持续失败请附带 traceId 联系技术支持";
|
||||
|
||||
private static final Map<String, PlatformError> BY_CODE;
|
||||
static {
|
||||
Map<String, PlatformError> m = new HashMap<>();
|
||||
for (PlatformError e : EnumSet.allOf(PlatformError.class)) {
|
||||
m.put(e.code, e);
|
||||
}
|
||||
BY_CODE = Collections.unmodifiableMap(m);
|
||||
}
|
||||
|
||||
final String code;
|
||||
final String category;
|
||||
final String reason;
|
||||
final String suggestion;
|
||||
|
||||
PlatformError(String code, String category, String reason, String suggestion) {
|
||||
this.code = code;
|
||||
this.category = category;
|
||||
this.reason = reason;
|
||||
this.suggestion = suggestion;
|
||||
}
|
||||
|
||||
static String categoryOf(String code) {
|
||||
PlatformError e = BY_CODE.get(code);
|
||||
if (e != null) {
|
||||
return e.category;
|
||||
}
|
||||
if (code != null && code.startsWith(IMAGE_PREFIX)) {
|
||||
return IMAGE_CATEGORY;
|
||||
}
|
||||
return DEFAULT_CATEGORY;
|
||||
}
|
||||
|
||||
static String reasonOf(String code) {
|
||||
PlatformError e = BY_CODE.get(code);
|
||||
return e != null ? e.reason : DEFAULT_REASON;
|
||||
}
|
||||
|
||||
static String suggestionOf(String code, String sideLabel) {
|
||||
PlatformError e = BY_CODE.get(code);
|
||||
if (e == IMAGE_BLUR) {
|
||||
return "请在自然光下重拍,确保证面占画面主体且文字可辨;side=" + sideLabel;
|
||||
}
|
||||
if (e != null && e.suggestion != null) {
|
||||
return e.suggestion;
|
||||
}
|
||||
return DEFAULT_SUGGESTION;
|
||||
}
|
||||
return second;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user