提交修改
This commit is contained in:
parent
b6e2a376e6
commit
4dd5a1cb18
@ -0,0 +1,195 @@
|
||||
package com.heyu.api.data.utils;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 影像入参解析工具:自动识别 Base64 与 HTTP(S) 图片链接,并输出可供表单提交使用的编码值。
|
||||
*/
|
||||
public final class ImageInputUtils {
|
||||
|
||||
private static final Pattern PERCENT_ENCODED = Pattern.compile("%[0-9A-Fa-f]{2}");
|
||||
|
||||
private ImageInputUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 影像入参类型
|
||||
*/
|
||||
public enum ImageInputType {
|
||||
/** Base64 编码图片(可带 data:image 前缀) */
|
||||
BASE64("使用 Base64 编码图片"),
|
||||
/** HTTP/HTTPS 图片链接 */
|
||||
URL("使用图片链接");
|
||||
|
||||
private final String desc;
|
||||
|
||||
ImageInputType(String desc) {
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析后的影像入参
|
||||
*/
|
||||
public static final class ResolvedImageInput {
|
||||
private final ImageInputType type;
|
||||
/** 去除 data: 前缀、URL 解码后的原始值(日志用,不含表单编码) */
|
||||
private final String rawValue;
|
||||
/** 可直接拼接到 application/x-www-form-urlencoded 请求体的值 */
|
||||
private final String formValue;
|
||||
|
||||
ResolvedImageInput(ImageInputType type, String rawValue, String formValue) {
|
||||
this.type = type;
|
||||
this.rawValue = rawValue;
|
||||
this.formValue = formValue;
|
||||
}
|
||||
|
||||
public ImageInputType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getRawValue() {
|
||||
return rawValue;
|
||||
}
|
||||
|
||||
public String getFormValue() {
|
||||
return formValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断字符串是 Base64 影像还是 HTTP(S) 图片链接。
|
||||
* <p>规则:以 {@code http://} 或 {@code https://} 开头(含已 urlencode 的形式)视为链接,否则视为 Base64。</p>
|
||||
*
|
||||
* @param input 用户传入的影像字符串
|
||||
* @return 入参类型;空串返回 {@code null}
|
||||
*/
|
||||
public static ImageInputType detectType(String input) {
|
||||
if (StringUtils.isBlank(input)) {
|
||||
return null;
|
||||
}
|
||||
String trimmed = input.trim();
|
||||
if (trimmed.regionMatches(true, 0, "data:", 0, 5)) {
|
||||
return ImageInputType.BASE64;
|
||||
}
|
||||
if (isHttpUrl(trimmed) || isHttpUrl(tryUrlDecode(trimmed))) {
|
||||
return ImageInputType.URL;
|
||||
}
|
||||
return ImageInputType.BASE64;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析影像入参:识别类型、剥离 data: 前缀,并在未 urlencode 时自动编码为表单可用值。
|
||||
*
|
||||
* @param input 用户传入的影像字符串
|
||||
* @return 解析结果;入参为空时返回 {@code null}
|
||||
*/
|
||||
public static ResolvedImageInput resolve(String input) {
|
||||
if (StringUtils.isBlank(input)) {
|
||||
return null;
|
||||
}
|
||||
String trimmed = input.trim();
|
||||
ImageInputType type = detectType(trimmed);
|
||||
if (type == ImageInputType.URL) {
|
||||
String rawUrl = normalizeHttpUrl(trimmed);
|
||||
return new ResolvedImageInput(type, rawUrl, encodeForFormBody(trimmed, rawUrl));
|
||||
}
|
||||
String rawBase64 = stripDataUriPrefix(trimmed);
|
||||
return new ResolvedImageInput(type, rawBase64, encodeForFormBody(trimmed, rawBase64));
|
||||
}
|
||||
|
||||
private static String normalizeHttpUrl(String input) {
|
||||
if (isHttpUrl(input)) {
|
||||
return input;
|
||||
}
|
||||
String decoded = tryUrlDecode(input);
|
||||
return isHttpUrl(decoded) ? decoded : input;
|
||||
}
|
||||
|
||||
private static boolean isHttpUrl(String value) {
|
||||
if (StringUtils.isBlank(value)) {
|
||||
return false;
|
||||
}
|
||||
return value.regionMatches(true, 0, "http://", 0, 7)
|
||||
|| value.regionMatches(true, 0, "https://", 0, 8);
|
||||
}
|
||||
|
||||
private static String stripDataUriPrefix(String input) {
|
||||
if (!input.regionMatches(true, 0, "data:", 0, 5)) {
|
||||
return input;
|
||||
}
|
||||
int base64Idx = input.indexOf("base64,");
|
||||
if (base64Idx >= 0) {
|
||||
return input.substring(base64Idx + "base64,".length());
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* 若入参尚未做表单 urlencode,则进行编码;已编码则原样返回。
|
||||
*/
|
||||
private static String encodeForFormBody(String original, String rawValue) {
|
||||
if (appearsFormUrlEncoded(original, rawValue)) {
|
||||
return original.trim();
|
||||
}
|
||||
try {
|
||||
return URLEncoder.encode(rawValue, StandardCharsets.UTF_8.name());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
return rawValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否已做过表单 urlencode(避免二次编码)。
|
||||
*/
|
||||
private static boolean appearsFormUrlEncoded(String original, String rawValue) {
|
||||
String trimmed = original.trim();
|
||||
if (!PERCENT_ENCODED.matcher(trimmed).find()) {
|
||||
return false;
|
||||
}
|
||||
if (trimmed.equals(rawValue)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
String decoded = URLDecoder.decode(trimmed, StandardCharsets.UTF_8.name());
|
||||
return decoded.equals(rawValue);
|
||||
} catch (IllegalArgumentException | UnsupportedEncodingException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static String tryUrlDecode(String value) {
|
||||
if (StringUtils.isBlank(value) || !PERCENT_ENCODED.matcher(value).find()) {
|
||||
return value;
|
||||
}
|
||||
try {
|
||||
return URLDecoder.decode(value, StandardCharsets.UTF_8.name());
|
||||
} catch (IllegalArgumentException | UnsupportedEncodingException ex) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 粗略校验 Base64 字符串是否可解码(仅用于可选校验,非强制)。
|
||||
*/
|
||||
public static boolean isValidBase64(String base64) {
|
||||
if (StringUtils.isBlank(base64)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
Base64.getDecoder().decode(base64);
|
||||
return true;
|
||||
} catch (IllegalArgumentException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,8 @@ package com.heyu.api.controller.car;
|
||||
import com.heyu.api.controller.BaseController;
|
||||
import com.heyu.api.data.annotation.EbAuthentication;
|
||||
import com.heyu.api.data.constants.ApiConstants;
|
||||
import com.heyu.api.data.utils.ImageInputUtils;
|
||||
import com.heyu.api.data.utils.ImageInputUtils.ResolvedImageInput;
|
||||
import com.heyu.api.data.utils.MapUtils;
|
||||
import com.heyu.api.data.utils.R;
|
||||
import com.heyu.api.data.utils.StringUtils;
|
||||
@ -81,27 +83,29 @@ public class RecognizeDriverLicenseController extends BaseController {
|
||||
String validateError = validateRequest(request);
|
||||
if (validateError != null) {
|
||||
log.info("驾驶证识别:参数检查没通过,直接返回错误(还没调识别、不扣费)。{} 返回给客户:{}",
|
||||
buildInputLogContext(request), abbreviate(validateError, 120));
|
||||
buildInputLogContext(request, ImageInputUtils.resolve(request.getImageUrlOrBase64())),
|
||||
abbreviate(validateError, 120));
|
||||
return R.error(validateError);
|
||||
}
|
||||
|
||||
// ---------- 步骤②:构建上下文(side、图片模式、入参摘要) ----------
|
||||
// ---------- 步骤②:解析影像入参 & 构建上下文 ----------
|
||||
ResolvedImageInput imageInput = ImageInputUtils.resolve(request.getImageUrlOrBase64());
|
||||
ctx = new RecognizeContext(
|
||||
resolveDrivingLicenseSide(request),
|
||||
ImageMode.of(request),
|
||||
buildInputLogContext(request));
|
||||
imageInput,
|
||||
buildInputLogContext(request, imageInput));
|
||||
|
||||
// ---------- 步骤③:组装百度 API 请求体 ----------
|
||||
String content = buildRequestContent(request, ctx.side);
|
||||
String content = buildRequestContent(imageInput, ctx.side);
|
||||
if (isBlank(content)) {
|
||||
log.error("驾驶证识别:组装请求失败,请求里没带有效图片。识别{},{}。{}",
|
||||
sideDesc(ctx.side), ctx.imageMode.desc, ctx.inputLog);
|
||||
sideDesc(ctx.side), ctx.imageInput.getType().getDesc(), ctx.inputLog);
|
||||
return okResult(ctx.side, formatHint(
|
||||
"报文组装异常",
|
||||
"识别请求体在序列化后未包含任何影像载荷,平台侧无法受理本次识别",
|
||||
"请核对 imageBase64 是否已 urlencode、imageUrl 是否非空字符串;"
|
||||
"请核对 imageUrlOrBase64 是否非空且为有效 Base64 或 HTTP(S) 链接;"
|
||||
+ "请求头须为 application/x-www-form-urlencoded",
|
||||
"目标页面=" + sideLabel(ctx.side) + ",影像模式=" + ctx.imageMode.name()
|
||||
"目标页面=" + sideLabel(ctx.side) + ",影像模式=" + ctx.imageInput.getType().name()
|
||||
), null);
|
||||
}
|
||||
|
||||
@ -112,7 +116,7 @@ public class RecognizeDriverLicenseController extends BaseController {
|
||||
"服务无回执",
|
||||
"识别指令已下发,但在约定时间内未收到平台可解析的 JSON 回执",
|
||||
"建议间隔 3~5 秒重试;若连续失败,请记录 traceId、调用时刻与 side 并联系技术支持排查链路",
|
||||
"目标页面=" + sideLabel(ctx.side) + ",影像模式=" + ctx.imageMode.name()
|
||||
"目标页面=" + sideLabel(ctx.side) + ",影像模式=" + ctx.imageInput.getType().name()
|
||||
), null);
|
||||
}
|
||||
|
||||
@ -130,10 +134,12 @@ public class RecognizeDriverLicenseController extends BaseController {
|
||||
long cost = System.currentTimeMillis() - start;
|
||||
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 : "未知");
|
||||
ResolvedImageInput fallbackImage = request != null
|
||||
? ImageInputUtils.resolve(request.getImageUrlOrBase64()) : null;
|
||||
String mode = ctx != null ? ctx.imageInput.getType().getDesc()
|
||||
: (fallbackImage != null ? fallbackImage.getType().getDesc() : "未知");
|
||||
log.error("驾驶证识别:程序运行出错,耗时 {} ms。识别{},{}。客户传的:{}。异常:{} - {}",
|
||||
cost, sideDesc(side), mode, buildInputLogContext(request),
|
||||
cost, sideDesc(side), mode, buildInputLogContext(request, fallbackImage),
|
||||
e.getClass().getSimpleName(),
|
||||
e.getMessage() != null ? e.getMessage() : "无具体说明", e);
|
||||
return okResult(side, formatHint(
|
||||
@ -159,12 +165,12 @@ 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);
|
||||
sideDesc(ctx.side), ctx.imageInput.getType().getDesc(), 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);
|
||||
sideDesc(ctx.side), ctx.imageInput.getType().getDesc(), len, ctx.inputLog);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -193,7 +199,7 @@ public class RecognizeDriverLicenseController extends BaseController {
|
||||
String category = PlatformError.categoryOf(errorCode);
|
||||
log.error("驾驶证识别:平台拒绝了本次识别。[{}] 错误码 {},原因:{}。识别{},{}。客户传的:{}。{}",
|
||||
category, errorCode, errorMsg,
|
||||
sideDesc(ctx.side), ctx.imageMode.desc,
|
||||
sideDesc(ctx.side), ctx.imageInput.getType().getDesc(),
|
||||
ctx.inputLog, buildPlatformReceiptSummary(platformResult));
|
||||
return formatHint(
|
||||
category,
|
||||
@ -204,7 +210,7 @@ public class RecognizeDriverLicenseController extends BaseController {
|
||||
}
|
||||
if (isWordsResultEmpty(platformResult)) {
|
||||
log.info("驾驶证识别:平台返回了,但没有识别结果数据(可能不是驾驶证或 side 传错)。识别{},{}。客户传的:{}。{}",
|
||||
sideDesc(ctx.side), ctx.imageMode.desc,
|
||||
sideDesc(ctx.side), ctx.imageInput.getType().getDesc(),
|
||||
ctx.inputLog, buildPlatformReceiptSummary(platformResult));
|
||||
return formatHint(
|
||||
"结构化结果缺失",
|
||||
@ -217,7 +223,7 @@ public class RecognizeDriverLicenseController extends BaseController {
|
||||
if (isAllStringFieldsBlank(data)) {
|
||||
log.info("驾驶证识别:平台有返回,但证号、姓名等字段一个都没识别出来(可能传错正/副页或图片不清晰)。"
|
||||
+ "识别{},{}。客户传的:{}。平台回执:{}",
|
||||
sideDesc(ctx.side), ctx.imageMode.desc,
|
||||
sideDesc(ctx.side), ctx.imageInput.getType().getDesc(),
|
||||
ctx.inputLog, buildPlatformReceiptSummary(platformResult));
|
||||
return formatHint(
|
||||
"字段映射为空",
|
||||
@ -248,11 +254,11 @@ public class RecognizeDriverLicenseController extends BaseController {
|
||||
if (StringUtils.isNotBlank(hint)) {
|
||||
log.info("驾驶证识别:处理结束(接口仍返回成功,但带了提示信息)。耗时 {} ms,识别出 {} 个字段。"
|
||||
+ "识别{},{}。客户传的:{}。平台回执:{}。给客户的提示:{}",
|
||||
cost, mapped, sideDesc(ctx.side), ctx.imageMode.desc,
|
||||
cost, mapped, sideDesc(ctx.side), ctx.imageInput.getType().getDesc(),
|
||||
ctx.inputLog, buildPlatformReceiptSummary(platformResult), abbreviate(hint, 120));
|
||||
} else {
|
||||
log.info("驾驶证识别:识别成功。耗时 {} ms,共识别出 {} 个字段。识别{},{}。客户传的:{}。平台回执:{}",
|
||||
cost, mapped, sideDesc(ctx.side), ctx.imageMode.desc,
|
||||
cost, mapped, sideDesc(ctx.side), ctx.imageInput.getType().getDesc(),
|
||||
ctx.inputLog, buildPlatformReceiptSummary(platformResult));
|
||||
}
|
||||
}
|
||||
@ -265,29 +271,35 @@ public class RecognizeDriverLicenseController extends BaseController {
|
||||
return formatHint(
|
||||
"入参绑定失败",
|
||||
"控制器未接收到可绑定的表单对象,所有业务字段均为空",
|
||||
"请确认使用 POST 提交;Content-Type 为 application/x-www-form-urlencoded;"
|
||||
+ "字段名与接口文档一致(imageBase64 / imageUrl / side)",
|
||||
"请确认使用 POST 提交;Content-Type 为 application/json 或 application/x-www-form-urlencoded;"
|
||||
+ "字段名与接口文档一致(imageUrlOrBase64 / side)",
|
||||
"绑定结果=DriverLicenseRecognizeRequest 为 null"
|
||||
);
|
||||
}
|
||||
if (isBlank(request.getImageBase64()) && isBlank(request.getImageUrl())) {
|
||||
log.info("驾驶证识别:没传图片(imageBase64 和 imageUrl 都为空),直接拒绝,未调用识别、不扣费。{}",
|
||||
buildInputLogContext(request));
|
||||
if (isBlank(request.getImageUrlOrBase64())) {
|
||||
log.info("驾驶证识别:没传图片(imageUrlOrBase64 为空),直接拒绝,未调用识别、不扣费。{}",
|
||||
buildInputLogContext(request, null));
|
||||
return formatHint(
|
||||
"影像源缺失",
|
||||
"imageBase64 与 imageUrl 均未提供,识别引擎没有可处理的图像输入",
|
||||
"任选其一:① imageBase64 传 jpg/jpeg/png/bmp 的 base64(urlencode 后≤4M);"
|
||||
+ "② imageUrl 传可公网直连的 HTTPS/HTTP 地址(≤1024 字符,关闭防盗链)",
|
||||
"imageUrlOrBase64 未提供,识别引擎没有可处理的图像输入",
|
||||
"请传 HTTP/HTTPS 图片链接,或 jpg/jpeg/png/bmp 的 Base64 字符串(可带 data:image 前缀);"
|
||||
+ "未 urlencode 时由服务端自动编码",
|
||||
"side=" + (isBlank(request.getSide()) ? SIDE_DEFAULT_HINT : request.getSide().trim())
|
||||
);
|
||||
}
|
||||
if (StringUtils.isNotBlank(request.getImageBase64()) && StringUtils.isNotBlank(request.getImageUrl())) {
|
||||
log.info("驾驶证识别:客户同时传了 Base64 和图片链接,按规则只用 Base64,忽略链接。{}",
|
||||
buildInputLogContext(request));
|
||||
if (ImageInputUtils.resolve(request.getImageUrlOrBase64()) == null) {
|
||||
log.info("驾驶证识别:imageUrlOrBase64 无法解析为有效影像入参,直接拒绝,未调用识别、不扣费。{}",
|
||||
buildInputLogContext(request, null));
|
||||
return formatHint(
|
||||
"影像入参无效",
|
||||
"imageUrlOrBase64 内容为空或无法识别为 Base64 / HTTP(S) 链接",
|
||||
"链接须以 http:// 或 https:// 开头;Base64 须为有效编码字符串",
|
||||
"imageUrlOrBase64 长度=" + textLength(request.getImageUrlOrBase64())
|
||||
);
|
||||
}
|
||||
if (resolveDrivingLicenseSide(request) == null) {
|
||||
log.info("驾驶证识别:side 参数写错了(只支持 face/front 正页、back 副页),直接拒绝,未调用识别、不扣费。{}",
|
||||
buildInputLogContext(request));
|
||||
buildInputLogContext(request, ImageInputUtils.resolve(request.getImageUrlOrBase64())));
|
||||
return formatHint(
|
||||
"页面标识无效",
|
||||
"参数 side 的取值无法映射到驾驶证正页或副页识别模式",
|
||||
@ -350,20 +362,23 @@ public class RecognizeDriverLicenseController extends BaseController {
|
||||
/**
|
||||
* 构造调用百度驾驶证的 POST 请求体(application/x-www-form-urlencoded 格式)。
|
||||
* <ul>
|
||||
* <li>优先使用 Base64(image 参数),备选 URL(url 参数)</li>
|
||||
* <li>Base64 使用 image 参数,HTTP(S) 链接使用 url 参数(均已表单 urlencode)</li>
|
||||
* <li>始终携带固定的百度参数:detect_direction、driving_license_side、unified_valid_period 等</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param request 客户入参
|
||||
* @param imageInput 解析后的影像入参
|
||||
* @param drivingLicenseSide 标准化后的 side(front / back)
|
||||
* @return 请求体字符串;若既无 Base64 也无 URL 则返回空字符串
|
||||
* @return 请求体字符串;imageInput 为空时返回空字符串
|
||||
*/
|
||||
private String buildRequestContent(DriverLicenseRecognizeRequest request, String drivingLicenseSide) {
|
||||
private String buildRequestContent(ResolvedImageInput imageInput, 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());
|
||||
if (imageInput == null || StringUtils.isBlank(imageInput.getFormValue())) {
|
||||
return sb.toString();
|
||||
}
|
||||
if (ImageInputUtils.ImageInputType.URL == imageInput.getType()) {
|
||||
sb.append("&url=").append(imageInput.getFormValue());
|
||||
} else {
|
||||
sb.append("&image=").append(imageInput.getFormValue());
|
||||
}
|
||||
sb.append("&detect_direction=false");
|
||||
sb.append("&driving_license_side=").append(drivingLicenseSide);
|
||||
@ -552,18 +567,18 @@ public class RecognizeDriverLicenseController extends BaseController {
|
||||
return text.substring(0, maxLen) + "...";
|
||||
}
|
||||
|
||||
/** 入参日志说明(不打印 base64 正文,只说长度) */
|
||||
private String buildInputLogContext(DriverLicenseRecognizeRequest request) {
|
||||
/** 入参日志说明(不打印影像正文,只说类型与长度) */
|
||||
private String buildInputLogContext(DriverLicenseRecognizeRequest request, ResolvedImageInput imageInput) {
|
||||
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()));
|
||||
String modeDesc = imageInput != null ? imageInput.getType().getDesc() : "影像未解析";
|
||||
int rawLen = imageInput != null ? textLength(imageInput.getRawValue()) : textLength(request.getImageUrlOrBase64());
|
||||
return String.format("识别%s,side 参数=%s,%s,影像原始长度 %d 字符",
|
||||
targetPage, sideParam, modeDesc, rawLen);
|
||||
}
|
||||
|
||||
/** 平台回执日志说明(便于失败排查) */
|
||||
@ -603,49 +618,20 @@ public class RecognizeDriverLicenseController extends BaseController {
|
||||
// ===================== 内部类型 =====================
|
||||
|
||||
/**
|
||||
* 单次识别请求的上下文,集中保存 side / imageMode / 入参日志摘要,避免在主流程中反复解析。
|
||||
* 单次识别请求的上下文,集中保存 side / 影像入参 / 入参日志摘要,避免在主流程中反复解析。
|
||||
*/
|
||||
private static class RecognizeContext {
|
||||
final String side;
|
||||
final ImageMode imageMode;
|
||||
final ResolvedImageInput imageInput;
|
||||
final String inputLog;
|
||||
|
||||
RecognizeContext(String side, ImageMode imageMode, String inputLog) {
|
||||
RecognizeContext(String side, ResolvedImageInput imageInput, String inputLog) {
|
||||
this.side = side;
|
||||
this.imageMode = imageMode;
|
||||
this.imageInput = imageInput;
|
||||
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>
|
||||
|
||||
@ -11,17 +11,15 @@ import lombok.Data;
|
||||
public class DriverLicenseRecognizeRequest {
|
||||
|
||||
/**
|
||||
* 图像数据,base64编码后进行urlencode,要求base64编码和urlencode后大小不超过4M
|
||||
* 支持jpg/jpeg/png/bmp格式
|
||||
* 和url二选一
|
||||
* 影像入参(二选一能力已合并为单一字段):
|
||||
* <ul>
|
||||
* <li>HTTP/HTTPS 图片链接(长度建议≤1024 字符,未 urlencode 时由服务端自动编码)</li>
|
||||
* <li>图片 Base64 字符串(支持 jpg/jpeg/png/bmp;可带 data:image/...;base64, 前缀;
|
||||
* 编码后 urlencode 前建议≤4M,未 urlencode 时由服务端自动编码)</li>
|
||||
* </ul>
|
||||
* 服务端根据内容自动识别为链接或 Base64。
|
||||
*/
|
||||
private String imageBase64;
|
||||
|
||||
/**
|
||||
* 图片完整URL,URL长度不超过1024字节
|
||||
* 和imageBase64二选一
|
||||
*/
|
||||
private String imageUrl;
|
||||
private String imageUrlOrBase64;
|
||||
|
||||
/**
|
||||
* face:识别驾驶证正页、电子驾驶证正页(默认)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user