1.修改请求报错
2.base64格式测试没有通过
This commit is contained in:
parent
c272282d55
commit
1af445bcd8
@ -11,6 +11,7 @@ import com.heyu.api.resp.car.RecognizeDriverLicenseBackResp;
|
|||||||
import com.heyu.api.resp.car.RecognizeDriverLicenseFaceResp;
|
import com.heyu.api.resp.car.RecognizeDriverLicenseFaceResp;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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.RequestBody;
|
||||||
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;
|
||||||
|
|
||||||
@ -72,10 +73,11 @@ public class RecognizeDriverLicenseController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
@EbAuthentication(tencent = ApiConstants.TENCENT_AUTH)
|
@EbAuthentication(tencent = ApiConstants.TENCENT_AUTH)
|
||||||
@PostMapping("/recognize")
|
@PostMapping("/recognize")
|
||||||
public R recognize(DriverLicenseRecognizeRequest request) {
|
public R recognize(@RequestBody DriverLicenseRecognizeRequest request) {
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
RecognizeContext ctx = null;
|
RecognizeContext ctx = null;
|
||||||
try {
|
try {
|
||||||
|
// ---------- 步骤①:参数校验(不调百度、不扣费) ----------
|
||||||
String validateError = validateRequest(request);
|
String validateError = validateRequest(request);
|
||||||
if (validateError != null) {
|
if (validateError != null) {
|
||||||
log.info("驾驶证识别:参数检查没通过,直接返回错误(还没调识别、不扣费)。{} 返回给客户:{}",
|
log.info("驾驶证识别:参数检查没通过,直接返回错误(还没调识别、不扣费)。{} 返回给客户:{}",
|
||||||
@ -83,11 +85,13 @@ public class RecognizeDriverLicenseController extends BaseController {
|
|||||||
return R.error(validateError);
|
return R.error(validateError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------- 步骤②:构建上下文(side、图片模式、入参摘要) ----------
|
||||||
ctx = new RecognizeContext(
|
ctx = new RecognizeContext(
|
||||||
resolveDrivingLicenseSide(request),
|
resolveDrivingLicenseSide(request),
|
||||||
ImageMode.of(request),
|
ImageMode.of(request),
|
||||||
buildInputLogContext(request));
|
buildInputLogContext(request));
|
||||||
|
|
||||||
|
// ---------- 步骤③:组装百度 API 请求体 ----------
|
||||||
String content = buildRequestContent(request, ctx.side);
|
String content = buildRequestContent(request, ctx.side);
|
||||||
if (isBlank(content)) {
|
if (isBlank(content)) {
|
||||||
log.error("驾驶证识别:组装请求失败,请求里没带有效图片。识别{},{}。{}",
|
log.error("驾驶证识别:组装请求失败,请求里没带有效图片。识别{},{}。{}",
|
||||||
@ -101,6 +105,7 @@ public class RecognizeDriverLicenseController extends BaseController {
|
|||||||
), null);
|
), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------- 步骤④:调用百度平台识别 ----------
|
||||||
Map<String, Object> platformResult = callPlatform(content, ctx);
|
Map<String, Object> platformResult = callPlatform(content, ctx);
|
||||||
if (platformResult == null) {
|
if (platformResult == null) {
|
||||||
return okResult(ctx.side, formatHint(
|
return okResult(ctx.side, formatHint(
|
||||||
@ -111,11 +116,13 @@ public class RecognizeDriverLicenseController extends BaseController {
|
|||||||
), null);
|
), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------- 步骤⑤:解析平台结果 → 根据 side 构建正页/副页响应 ----------
|
||||||
Object data = ApiConstants.back.equals(ctx.side)
|
Object data = ApiConstants.back.equals(ctx.side)
|
||||||
? buildBackResp(platformResult)
|
? buildBackResp(platformResult)
|
||||||
: buildFaceResp(platformResult);
|
: buildFaceResp(platformResult);
|
||||||
String hint = resolvePlatformHint(platformResult, ctx, data);
|
String hint = resolvePlatformHint(platformResult, ctx, data);
|
||||||
|
|
||||||
|
// ---------- 步骤⑥:日志记录 & 返回 ----------
|
||||||
logRecognizeResult(ctx, platformResult, data, hint, start);
|
logRecognizeResult(ctx, platformResult, data, hint, start);
|
||||||
return okResult(ctx.side, hint, data);
|
return okResult(ctx.side, hint, data);
|
||||||
|
|
||||||
@ -141,6 +148,14 @@ public class RecognizeDriverLicenseController extends BaseController {
|
|||||||
|
|
||||||
// ===================== 流程拆分方法 =====================
|
// ===================== 流程拆分方法 =====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用百度云驾驶证识别接口,记录调用日志并返回平台原始响应。
|
||||||
|
*
|
||||||
|
* @param content 已拼装好的 POST 请求体
|
||||||
|
* @param ctx 上下文(side、图片模式、入参摘要)
|
||||||
|
* @return 平台返回的 JSON 解析后的 Map;超时/鉴权/解析失败时返回 null
|
||||||
|
* @see #requestBaidu(String, String)
|
||||||
|
*/
|
||||||
private Map<String, Object> callPlatform(String content, RecognizeContext ctx) {
|
private Map<String, Object> callPlatform(String content, RecognizeContext ctx) {
|
||||||
int len = content.length();
|
int len = content.length();
|
||||||
log.info("驾驶证识别:开始调用平台识别。识别{},{},请求大小约 {} 字节。{}",
|
log.info("驾驶证识别:开始调用平台识别。识别{},{},请求大小约 {} 字节。{}",
|
||||||
@ -154,6 +169,21 @@ public class RecognizeDriverLicenseController extends BaseController {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析平台返回结果,判断是否需要向客户返回提示信息。
|
||||||
|
* <p>判断顺序:</p>
|
||||||
|
* <ol>
|
||||||
|
* <li>error_code 存在 → 平台业务拒绝(配额/鉴权/影像不合规)</li>
|
||||||
|
* <li>words_result 为空 → 平台未识别出内容</li>
|
||||||
|
* <li>响应对象所有字段均为空 → 平台有返回但字段映射后全为空</li>
|
||||||
|
* <li>以上均不命中 → 返回 null(识别正常,无需额外提示)</li>
|
||||||
|
* </ol>
|
||||||
|
*
|
||||||
|
* @param platformResult 百度平台原始返回
|
||||||
|
* @param ctx 识别上下文
|
||||||
|
* @param data 映射后的响应对象
|
||||||
|
* @return 提示文案,正常识别返回 null
|
||||||
|
*/
|
||||||
private String resolvePlatformHint(Map<String, Object> platformResult, RecognizeContext ctx, Object data) {
|
private String resolvePlatformHint(Map<String, Object> platformResult, RecognizeContext ctx, Object data) {
|
||||||
Object errorCodeObj = platformResult.get("error_code");
|
Object errorCodeObj = platformResult.get("error_code");
|
||||||
if (errorCodeObj != null) {
|
if (errorCodeObj != null) {
|
||||||
@ -201,6 +231,16 @@ public class RecognizeDriverLicenseController extends BaseController {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录本次识别的最终结果日志(含耗时、识别字段数量)。
|
||||||
|
* 区分"带提示"与"完全成功"两种日志级别,方便后续检索。
|
||||||
|
*
|
||||||
|
* @param ctx 识别上下文
|
||||||
|
* @param platformResult 平台原始返回(用于提取回执摘要)
|
||||||
|
* @param data 映射后的响应对象
|
||||||
|
* @param hint 提示文案(可为 null)
|
||||||
|
* @param start 起始时间戳 ms,用于计算总耗时
|
||||||
|
*/
|
||||||
private void logRecognizeResult(RecognizeContext ctx, Map<String, Object> platformResult,
|
private void logRecognizeResult(RecognizeContext ctx, Map<String, Object> platformResult,
|
||||||
Object data, String hint, long start) {
|
Object data, String hint, long start) {
|
||||||
long cost = System.currentTimeMillis() - start;
|
long cost = System.currentTimeMillis() - start;
|
||||||
@ -259,6 +299,18 @@ public class RecognizeDriverLicenseController extends BaseController {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析 side 参数为标准化的正页/副页标识。
|
||||||
|
* <ul>
|
||||||
|
* <li>空/未传 → 默认正页 ({@link ApiConstants#front})</li>
|
||||||
|
* <li>face 或 front → 正页</li>
|
||||||
|
* <li>back → 副页</li>
|
||||||
|
* <li>其他 → {@code null}(表示无效)</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param request 入参对象
|
||||||
|
* @return {@link ApiConstants#front}、{@link ApiConstants#back} 或 {@code null}
|
||||||
|
*/
|
||||||
private String resolveDrivingLicenseSide(DriverLicenseRecognizeRequest request) {
|
private String resolveDrivingLicenseSide(DriverLicenseRecognizeRequest request) {
|
||||||
if (request == null || isBlank(request.getSide())) {
|
if (request == null || isBlank(request.getSide())) {
|
||||||
return ApiConstants.front;
|
return ApiConstants.front;
|
||||||
@ -273,16 +325,39 @@ public class RecognizeDriverLicenseController extends BaseController {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 side 对应的用户可读标签(带取值说明),用于日志与提示文案。
|
||||||
|
*
|
||||||
|
* @param side 标准化后的 side(front / back)
|
||||||
|
* @return 如 "副页(back)" 或 "正页(front/face)"
|
||||||
|
*/
|
||||||
private String sideLabel(String side) {
|
private String sideLabel(String side) {
|
||||||
return ApiConstants.back.equals(side) ? "副页(back)" : "正页(front/face)";
|
return ApiConstants.back.equals(side) ? "副页(back)" : "正页(front/face)";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 side 对应的中文描述,用于日志输出。
|
||||||
|
*
|
||||||
|
* @param side 标准化后的 side(front / back)
|
||||||
|
* @return 如 "驾驶证副页" 或 "驾驶证正页"
|
||||||
|
*/
|
||||||
private String sideDesc(String side) {
|
private String sideDesc(String side) {
|
||||||
return ApiConstants.back.equals(side) ? "驾驶证副页" : "驾驶证正页";
|
return ApiConstants.back.equals(side) ? "驾驶证副页" : "驾驶证正页";
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===================== 请求/响应构造 =====================
|
// ===================== 请求/响应构造 =====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造调用百度驾驶证的 POST 请求体(application/x-www-form-urlencoded 格式)。
|
||||||
|
* <ul>
|
||||||
|
* <li>优先使用 Base64(image 参数),备选 URL(url 参数)</li>
|
||||||
|
* <li>始终携带固定的百度参数:detect_direction、driving_license_side、unified_valid_period 等</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param request 客户入参
|
||||||
|
* @param drivingLicenseSide 标准化后的 side(front / back)
|
||||||
|
* @return 请求体字符串;若既无 Base64 也无 URL 则返回空字符串
|
||||||
|
*/
|
||||||
private String buildRequestContent(DriverLicenseRecognizeRequest request, String drivingLicenseSide) {
|
private String buildRequestContent(DriverLicenseRecognizeRequest request, String drivingLicenseSide) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
if (StringUtils.isNotBlank(request.getImageBase64())) {
|
if (StringUtils.isNotBlank(request.getImageBase64())) {
|
||||||
@ -298,6 +373,18 @@ public class RecognizeDriverLicenseController extends BaseController {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一构造接口返回体。
|
||||||
|
* <ul>
|
||||||
|
* <li>当 data 为 null 时,根据 side 自动创建对应类型的空响应对象(保证字段不为 null)</li>
|
||||||
|
* <li>hint 非空时追加到 R.ok() 的 msg 中,为空则使用默认成功消息</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param drivingLicenseSide 标准化 side,决定空响应类型
|
||||||
|
* @param hint 提示文案(成功时可为 null)
|
||||||
|
* @param data 识别对象,可为 null
|
||||||
|
* @return 统一响应体
|
||||||
|
*/
|
||||||
private R okResult(String drivingLicenseSide, String hint, Object data) {
|
private R okResult(String drivingLicenseSide, String hint, Object data) {
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
data = ApiConstants.back.equals(drivingLicenseSide)
|
data = ApiConstants.back.equals(drivingLicenseSide)
|
||||||
@ -308,6 +395,14 @@ public class RecognizeDriverLicenseController extends BaseController {
|
|||||||
return result.setData(data);
|
return result.setData(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将平台返回的 words_result 映射为驾驶证正页响应对象 {@link RecognizeDriverLicenseFaceResp}。
|
||||||
|
* 逐字段从嵌套 Map 中提取:words_result.{字段名}.words
|
||||||
|
*
|
||||||
|
* @param data 百度平台返回的原始结果
|
||||||
|
* @return 正页响应对象
|
||||||
|
* @see #getWords(Map, String)
|
||||||
|
*/
|
||||||
private RecognizeDriverLicenseFaceResp buildFaceResp(Map<String, Object> data) {
|
private RecognizeDriverLicenseFaceResp buildFaceResp(Map<String, Object> data) {
|
||||||
RecognizeDriverLicenseFaceResp resp = new RecognizeDriverLicenseFaceResp();
|
RecognizeDriverLicenseFaceResp resp = new RecognizeDriverLicenseFaceResp();
|
||||||
resp.setLicenseNumber(getWords(data, "证号"));
|
resp.setLicenseNumber(getWords(data, "证号"));
|
||||||
@ -330,6 +425,13 @@ public class RecognizeDriverLicenseController extends BaseController {
|
|||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将平台返回的 words_result 映射为驾驶证副页响应对象 {@link RecognizeDriverLicenseBackResp}。
|
||||||
|
* 副页仅包含 姓名、记录、证号、档案编号 四个字段。
|
||||||
|
*
|
||||||
|
* @param data 百度平台返回的原始结果
|
||||||
|
* @return 副页响应对象
|
||||||
|
*/
|
||||||
private RecognizeDriverLicenseBackResp buildBackResp(Map<String, Object> data) {
|
private RecognizeDriverLicenseBackResp buildBackResp(Map<String, Object> data) {
|
||||||
RecognizeDriverLicenseBackResp resp = new RecognizeDriverLicenseBackResp();
|
RecognizeDriverLicenseBackResp resp = new RecognizeDriverLicenseBackResp();
|
||||||
resp.setName(getWords(data, "姓名"));
|
resp.setName(getWords(data, "姓名"));
|
||||||
@ -339,10 +441,26 @@ public class RecognizeDriverLicenseController extends BaseController {
|
|||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 Baidu 返回结果的 words_result 中提取指定字段的文本。
|
||||||
|
* <p>访问路径:words_result.{field}.words</p>
|
||||||
|
*
|
||||||
|
* @param data Baidu 返回的原始 Map
|
||||||
|
* @param field 字段中文名(如 "证号"、"姓名")
|
||||||
|
* @return 识别的文字内容,未命中或为空时返回 null
|
||||||
|
*/
|
||||||
private String getWords(Map<String, Object> data, String field) {
|
private String getWords(Map<String, Object> data, String field) {
|
||||||
return MapUtils.getByExpr(data, "words_result." + field + ".words");
|
return MapUtils.getByExpr(data, "words_result." + field + ".words");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取第一个非空字符串,均空时返回 null。
|
||||||
|
* 用于处理百度返回中字段名不一致的场景(如有效期限可能叫"有效起始日期"也可能叫"有效期限")。
|
||||||
|
*
|
||||||
|
* @param first 优先值
|
||||||
|
* @param second 备选值
|
||||||
|
* @return 非空字符串或 null
|
||||||
|
*/
|
||||||
private String firstNonBlank(String first, String second) {
|
private String firstNonBlank(String first, String second) {
|
||||||
return StringUtils.isNotBlank(first) ? first : second;
|
return StringUtils.isNotBlank(first) ? first : second;
|
||||||
}
|
}
|
||||||
@ -384,10 +502,31 @@ public class RecognizeDriverLicenseController extends BaseController {
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断响应对象的所有 String 字段是否均为空。
|
||||||
|
* 快速检查是否完全没有识出任何字段,用于决定是否追加"字段映射为空"提示。
|
||||||
|
*
|
||||||
|
* @param data 正页或副页响应对象
|
||||||
|
* @return 所有 String 字段均空则返回 true
|
||||||
|
* @see #countNonBlankStringFields(Object)
|
||||||
|
*/
|
||||||
private static boolean isAllStringFieldsBlank(Object data) {
|
private static boolean isAllStringFieldsBlank(Object data) {
|
||||||
return countNonBlankStringFields(data) == 0;
|
return countNonBlankStringFields(data) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化为统一的提示文案,由四部分拼接而成:
|
||||||
|
* <pre>
|
||||||
|
* 【驾驶证识别·{category}】{reason}|定位信息:{detail}|处置指引:{suggestion}
|
||||||
|
* </pre>
|
||||||
|
* detail 可空,空时跳过"定位信息"部分。
|
||||||
|
*
|
||||||
|
* @param category 问题分类(如"影像不合规"、"鉴权失效")
|
||||||
|
* @param reason 问题原因描述
|
||||||
|
* @param suggestion 处置建议
|
||||||
|
* @param detail 定位辅助信息(可为空)
|
||||||
|
* @return 格式化的提示字符串
|
||||||
|
*/
|
||||||
private String formatHint(String category, String reason, String suggestion, String detail) {
|
private String formatHint(String category, String reason, String suggestion, String detail) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("【驾驶证识别·").append(category).append("】").append(reason);
|
sb.append("【驾驶证识别·").append(category).append("】").append(reason);
|
||||||
@ -398,6 +537,14 @@ public class RecognizeDriverLicenseController extends BaseController {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 截断字符串至指定长度,超出部分以 "..." 替代。
|
||||||
|
* 防止日志或提示文案中出现超长文本(如 Base64 片段、详细堆栈)。
|
||||||
|
*
|
||||||
|
* @param text 原始字符串
|
||||||
|
* @param maxLen 最大保留字符数
|
||||||
|
* @return 截断后的字符串,null 或未超长则返回原值
|
||||||
|
*/
|
||||||
private String abbreviate(String text, int maxLen) {
|
private String abbreviate(String text, int maxLen) {
|
||||||
if (text == null || text.length() <= maxLen) {
|
if (text == null || text.length() <= maxLen) {
|
||||||
return text;
|
return text;
|
||||||
@ -442,6 +589,13 @@ public class RecognizeDriverLicenseController extends BaseController {
|
|||||||
logId != null ? logId : "无");
|
logId != null ? logId : "无");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字符串长度,null 安全。
|
||||||
|
* 用于 buildInputLogContext 中简述客户传入的 Base64 / URL 大小,避免打印全文。
|
||||||
|
*
|
||||||
|
* @param value 字符串
|
||||||
|
* @return 长度;null 返回 0
|
||||||
|
*/
|
||||||
private int textLength(String value) {
|
private int textLength(String value) {
|
||||||
return value == null ? 0 : value.length();
|
return value == null ? 0 : value.length();
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user