rjquwhtn
This commit is contained in:
parent
25e68efd21
commit
ed11a1871b
@ -20,7 +20,6 @@ import java.util.Map;
|
||||
@SuppressWarnings({"unchecked", "deprecation"})
|
||||
public class BaseController {
|
||||
|
||||
private static final String VEHICLE_LICENSE_URI = "/rest/2.0/ocr/v1/vehicle_license";
|
||||
private static final String BAIDU_API_KEY = "zs9oN4gSuoS3eK8dVJg6jyKh";
|
||||
private static final String BAIDU_SECRET_KEY = "uHIRXkj6rbW1eXy8eRVCeP1e3cRQKXay";
|
||||
private static final OkHttpClient HTTP_CLIENT = new OkHttpClient().newBuilder().build();
|
||||
@ -56,7 +55,7 @@ public class BaseController {
|
||||
return value == null || value.trim().length() == 0;
|
||||
}
|
||||
|
||||
protected Map<String, Object> requestBaidu(String content) {
|
||||
protected Map<String, Object> requestBaidu(String uri, String content) {
|
||||
String result = null;
|
||||
try {
|
||||
if (StringUtils.isBlank(content)) {
|
||||
@ -67,7 +66,7 @@ public class BaseController {
|
||||
content = content.substring(1);
|
||||
}
|
||||
|
||||
Request baiduRequest = createBaiduRequest(content);
|
||||
Request baiduRequest = createBaiduRequest(uri, content);
|
||||
try (Response response = HTTP_CLIENT.newCall(baiduRequest).execute()) {
|
||||
result = response.body().string();
|
||||
return com.alibaba.fastjson.JSONObject.parseObject(result, Map.class);
|
||||
@ -81,11 +80,11 @@ public class BaseController {
|
||||
return null;
|
||||
}
|
||||
|
||||
private Request createBaiduRequest(String content) {
|
||||
private Request createBaiduRequest(String uri, String content) {
|
||||
MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
|
||||
okhttp3.RequestBody body = okhttp3.RequestBody.create(mediaType, content);
|
||||
return new Request.Builder()
|
||||
.url("https://aip.baidubce.com/" + VEHICLE_LICENSE_URI + "?access_token=" + getBaiduAccessToken())
|
||||
.url("https://aip.baidubce.com/" + uri + "?access_token=" + getBaiduAccessToken())
|
||||
.method("POST", body)
|
||||
.addHeader("Content-Type", "application/x-www-form-urlencoded")
|
||||
.addHeader("Accept", "application/json")
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
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.data.annotation.EbAuthentication;
|
||||
import com.heyu.api.data.constants.ApiConstants;
|
||||
import com.heyu.api.data.utils.ApiR;
|
||||
import com.heyu.api.data.utils.MapUtils;
|
||||
import com.heyu.api.data.utils.R;
|
||||
import com.heyu.api.data.utils.StringUtils;
|
||||
@ -13,7 +11,6 @@ import com.heyu.api.request.car.DriverLicenseRecognizeRequest;
|
||||
import com.heyu.api.resp.car.RecognizeDriverLicenseBackResp;
|
||||
import com.heyu.api.resp.car.RecognizeDriverLicenseFaceResp;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@ -37,37 +34,32 @@ import java.util.Map;
|
||||
@SuppressWarnings("unchecked")
|
||||
public class RecognizeDriverLicenseController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private BDriverLicenseHandle bDriverLicenseHandle;
|
||||
private static final String DRIVER_LICENSE_URI = "/rest/2.0/ocr/v1/driving_license";
|
||||
|
||||
@EbAuthentication(tencent = ApiConstants.TENCENT_AUTH)
|
||||
@PostMapping("/recognize")
|
||||
public R recognize(@RequestBody DriverLicenseRecognizeRequest request) {
|
||||
public R<RecognizeDriverLicenseFaceResp> recognize(@RequestBody DriverLicenseRecognizeRequest request) {
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
if (request == null) {
|
||||
return R.error("请求参数不能为空");
|
||||
}
|
||||
if (isBlank(request.getImageBase64()) && isBlank(request.getImageUrl())) {
|
||||
return R.error("imageBase64和imageUrl不能同时为空");
|
||||
}
|
||||
log.info("驾驶证识别-参数校验耗时:{}ms", System.currentTimeMillis() - start);
|
||||
|
||||
long t1 = System.currentTimeMillis();
|
||||
BDriverLicenseRequest bRequest = toBaiduRequest(request);
|
||||
log.info("驾驶证识别-构建百度请求耗时:{}ms", System.currentTimeMillis() - t1);
|
||||
String checkMsg = checkRequest(bRequest);
|
||||
if (!isBlank(checkMsg)) {
|
||||
return R.error(checkMsg);
|
||||
}
|
||||
log.info("驾驶证识别-参数校验及构建请求耗时:{}ms", System.currentTimeMillis() - t1);
|
||||
|
||||
long t2 = System.currentTimeMillis();
|
||||
ApiR<Map> apiR = bDriverLicenseHandle.handle(bRequest);
|
||||
Map<String, Object> data = requestBaidu(DRIVER_LICENSE_URI, getContent(bRequest));
|
||||
long baiduCost = System.currentTimeMillis() - t2;
|
||||
log.info("驾驶证识别-调用百度OCR接口耗时:{}ms", baiduCost);
|
||||
|
||||
if (apiR == null || !apiR.isSuccess() || apiR.getData() == null) {
|
||||
return R.error(thirdError(apiR));
|
||||
if (data == null) {
|
||||
log.info("驾驶证识别-识别失败, 耗时:{}ms", System.currentTimeMillis() - start);
|
||||
return R.error("驾驶证识别失败");
|
||||
}
|
||||
|
||||
long t3 = System.currentTimeMillis();
|
||||
Map<String, Object> data = apiR.getData();
|
||||
Object resp;
|
||||
if (ApiConstants.face.equals(request.getSide())) {
|
||||
resp = toFaceResp(data);
|
||||
@ -82,6 +74,9 @@ public class RecognizeDriverLicenseController extends BaseController {
|
||||
|
||||
private BDriverLicenseRequest toBaiduRequest(DriverLicenseRecognizeRequest request) {
|
||||
BDriverLicenseRequest bRequest = new BDriverLicenseRequest();
|
||||
if (request == null) {
|
||||
return bRequest;
|
||||
}
|
||||
bRequest.setImageBase64(request.getImageBase64());
|
||||
bRequest.setImageUrl(request.getImageUrl());
|
||||
|
||||
@ -96,6 +91,76 @@ public class RecognizeDriverLicenseController extends BaseController {
|
||||
return bRequest;
|
||||
}
|
||||
|
||||
private String checkRequest(BDriverLicenseRequest request) {
|
||||
if (!hasImage(request)) {
|
||||
return "imageUrl和imageBase64不能同时为空";
|
||||
}
|
||||
if (checkNotTrueFalse(request.getDetectDirection())) {
|
||||
return "detectDirection 必须传 " + ApiConstants.trueOrFalse
|
||||
+ ",false:默认值,不检测朝向, true:检测朝向";
|
||||
}
|
||||
if (checkNotFrontBack(request.getDrivingLicenseSide())) {
|
||||
return "drivingLicenseSide 必须传 " + ApiConstants.frontOrback
|
||||
+ ",front:识别驾驶证正页、电子驾驶证正页, back:识别驾驶证副页";
|
||||
}
|
||||
if (checkNotTrueFalse(request.getUnifiedValidPeriod())) {
|
||||
return "unifiedValidPeriod 必须传 " + ApiConstants.trueOrFalse
|
||||
+ ",false:默认值,不进行归一化处理, true:归一化格式输出";
|
||||
}
|
||||
if (checkNotTrueFalse(request.getQualityWarn())) {
|
||||
return "qualityWarn 必须传 " + ApiConstants.trueOrFalse
|
||||
+ ",false:不输出质量告警信息, true:输出遮挡、不完整质量告警信息";
|
||||
}
|
||||
if (checkNotTrueFalse(request.getRiskWarn())) {
|
||||
return "riskWarn 必须传 " + ApiConstants.trueOrFalse
|
||||
+ ",false:不输出风险告警信息, true:开启,输出驾驶正复印、翻拍、PS等告警信息";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean hasImage(BDriverLicenseRequest request) {
|
||||
return request != null && (!isBlank(request.getImageUrl()) || !isBlank(request.getImageBase64()));
|
||||
}
|
||||
|
||||
private String getContent(BDriverLicenseRequest request) {
|
||||
StringBuffer sb = getImageContent(request);
|
||||
if (StringUtils.isNotBlank(request.getDetectDirection())) {
|
||||
sb.append("&detect_direction=").append(request.getDetectDirection());
|
||||
}
|
||||
if (StringUtils.isNotBlank(request.getDrivingLicenseSide())) {
|
||||
sb.append("&driving_license_side=").append(request.getDrivingLicenseSide());
|
||||
}
|
||||
if (StringUtils.isNotBlank(request.getUnifiedValidPeriod())) {
|
||||
sb.append("&unified_valid_period=").append(request.getUnifiedValidPeriod());
|
||||
}
|
||||
if (StringUtils.isNotBlank(request.getQualityWarn())) {
|
||||
sb.append("&quality_warn=").append(request.getQualityWarn());
|
||||
}
|
||||
if (StringUtils.isNotBlank(request.getRiskWarn())) {
|
||||
sb.append("&risk_warn=").append(request.getRiskWarn());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private StringBuffer getImageContent(BDriverLicenseRequest request) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
if (StringUtils.isNotBlank(request.getImageBase64())) {
|
||||
sb.append("&image=").append(request.getImageBase64());
|
||||
}
|
||||
if (StringUtils.isNotBlank(request.getImageUrl())) {
|
||||
sb.append("&url=").append(request.getImageUrl());
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
private boolean checkNotTrueFalse(String value) {
|
||||
return !ApiConstants.trueOrFalse.contains(value);
|
||||
}
|
||||
|
||||
private boolean checkNotFrontBack(String value) {
|
||||
return !ApiConstants.frontOrback.contains(value);
|
||||
}
|
||||
|
||||
private RecognizeDriverLicenseFaceResp toFaceResp(Map<String, Object> data) {
|
||||
RecognizeDriverLicenseFaceResp resp = new RecognizeDriverLicenseFaceResp();
|
||||
|
||||
|
||||
@ -3,7 +3,6 @@ package com.heyu.api.controller.car;
|
||||
import com.heyu.api.baidu.request.traffic.BVehicleLicenseRequest;
|
||||
import com.heyu.api.controller.BaseController;
|
||||
import com.heyu.api.data.annotation.EbAuthentication;
|
||||
import com.heyu.api.data.annotation.NotIntercept;
|
||||
import com.heyu.api.data.constants.ApiConstants;
|
||||
import com.heyu.api.data.utils.MapUtils;
|
||||
import com.heyu.api.data.utils.R;
|
||||
@ -43,6 +42,8 @@ import java.util.Map;
|
||||
@SuppressWarnings("unchecked")
|
||||
public class RecognizeDrivingLicenseController extends BaseController {
|
||||
|
||||
private static final String VEHICLE_LICENSE_URI = "/rest/2.0/ocr/v1/vehicle_license";
|
||||
|
||||
/**
|
||||
* 行驶证识别接口
|
||||
* 支持行驶证正反面识别,使用百度云OCR接口
|
||||
@ -64,7 +65,7 @@ public class RecognizeDrivingLicenseController extends BaseController {
|
||||
log.info("行驶证识别-参数校验及构建请求耗时:{}ms", System.currentTimeMillis() - t1);
|
||||
|
||||
long t2 = System.currentTimeMillis();
|
||||
Map<String, Object> data = requestBaidu(getContent(bVehicleLicenseRequest));
|
||||
Map<String, Object> data = requestBaidu(VEHICLE_LICENSE_URI, getContent(bVehicleLicenseRequest));
|
||||
long baiduCost = System.currentTimeMillis() - t2;
|
||||
log.info("行驶证识别-调用百度OCR接口耗时:{}ms", baiduCost);
|
||||
|
||||
|
||||
@ -1,17 +1,15 @@
|
||||
package com.heyu.api.controller.car;
|
||||
|
||||
import com.heyu.api.baidu.handle.traffic.BLicensePlateHandle;
|
||||
import com.heyu.api.baidu.request.traffic.BLicensePlateRequest;
|
||||
import com.heyu.api.baidu.response.traffic.BLicensePlateResp;
|
||||
import com.heyu.api.controller.BaseController;
|
||||
import com.heyu.api.data.annotation.NotIntercept;
|
||||
import com.heyu.api.data.utils.ApiR;
|
||||
import com.heyu.api.data.utils.MapUtils;
|
||||
import com.heyu.api.data.utils.R;
|
||||
import com.heyu.api.data.utils.StringUtils;
|
||||
import com.heyu.api.request.car.LicensePlateRecognizeRequest;
|
||||
import com.heyu.api.resp.car.RecognizeLicensePlateResp;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@ -19,8 +17,8 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 车牌识别(百度OCR)
|
||||
@ -35,56 +33,52 @@ import java.util.List;
|
||||
@RestController
|
||||
@RequestMapping("/car/license/plate")
|
||||
@NotIntercept
|
||||
@SuppressWarnings("unchecked")
|
||||
public class RecognizeLicensePlateController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private BLicensePlateHandle bLicensePlateHandle;
|
||||
private static final String LICENSE_PLATE_URI = "/rest/2.0/ocr/v1/license_plate";
|
||||
|
||||
@PostMapping("/recognize")
|
||||
public R recognize(@RequestBody LicensePlateRecognizeRequest request) {
|
||||
public R<RecognizeLicensePlateResp> recognize(@RequestBody LicensePlateRecognizeRequest request) {
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
if (request == null) {
|
||||
return R.error("请求参数不能为空");
|
||||
}
|
||||
if (isBlank(request.getImageBase64()) && isBlank(request.getImageUrl())) {
|
||||
return R.error("imageBase64和imageUrl不能同时为空");
|
||||
}
|
||||
log.info("车牌识别-参数校验耗时:{}ms", System.currentTimeMillis() - start);
|
||||
|
||||
long t1 = System.currentTimeMillis();
|
||||
BLicensePlateRequest bRequest = toBaiduRequest(request);
|
||||
log.info("车牌识别-构建百度请求耗时:{}ms", System.currentTimeMillis() - t1);
|
||||
String checkMsg = checkRequest(bRequest);
|
||||
if (!isBlank(checkMsg)) {
|
||||
return R.error(checkMsg);
|
||||
}
|
||||
log.info("车牌识别-参数校验及构建请求耗时:{}ms", System.currentTimeMillis() - t1);
|
||||
|
||||
long t2 = System.currentTimeMillis();
|
||||
ApiR<BLicensePlateResp> apiR = bLicensePlateHandle.handle(bRequest);
|
||||
Map<String, Object> data = requestBaidu(LICENSE_PLATE_URI, getContent(bRequest));
|
||||
long baiduCost = System.currentTimeMillis() - t2;
|
||||
log.info("车牌识别-调用百度OCR接口耗时:{}ms", baiduCost);
|
||||
|
||||
if (apiR == null || !apiR.isSuccess() || apiR.getData() == null) {
|
||||
return R.error(thirdError(apiR));
|
||||
if (data == null) {
|
||||
log.info("车牌识别-识别失败, 耗时:{}ms", System.currentTimeMillis() - start);
|
||||
return R.error("车牌识别失败");
|
||||
}
|
||||
|
||||
long t3 = System.currentTimeMillis();
|
||||
BLicensePlateResp bResp = apiR.getData();
|
||||
List<BLicensePlateResp.WordsResultDTO> plates = bResp.getWordsResult();
|
||||
List<Map<String, Object>> plates = MapUtils.getByExpr(data, "words_result");
|
||||
if (CollectionUtils.isEmpty(plates)) {
|
||||
log.info("车牌识别-未识别到车牌信息, 耗时:{}ms", System.currentTimeMillis() - start);
|
||||
return R.error("未识别到车牌信息");
|
||||
}
|
||||
|
||||
// 取第一张车牌
|
||||
BLicensePlateResp.WordsResultDTO plate = plates.get(0);
|
||||
Map<String, Object> plate = plates.get(0);
|
||||
RecognizeLicensePlateResp resp = new RecognizeLicensePlateResp();
|
||||
resp.setPlateNumber(plate.getNumber());
|
||||
resp.setPlateType(mapPlateColor(plate.getColor()));
|
||||
resp.setPlateNumber((String) plate.get("number"));
|
||||
resp.setPlateType(mapPlateColor((String) plate.get("color")));
|
||||
|
||||
// 百度返回字符级置信度列表,取最小值作为整体置信度(保守策略),平均值作为车牌类型置信度
|
||||
List<Double> probabilities = plate.getProbability();
|
||||
List<Number> probabilities = (List<Number>) plate.get("probability");
|
||||
if (CollectionUtils.isNotEmpty(probabilities)) {
|
||||
double minConf = probabilities.stream().mapToDouble(Double::doubleValue).min().orElse(0.0);
|
||||
double minConf = probabilities.stream().mapToDouble(Number::doubleValue).min().orElse(0.0);
|
||||
resp.setConfidence((float) minConf);
|
||||
double avgConf = probabilities.stream().mapToDouble(Double::doubleValue).average().orElse(0.0);
|
||||
double avgConf = probabilities.stream().mapToDouble(Number::doubleValue).average().orElse(0.0);
|
||||
resp.setPlateTypeConfidence((float) avgConf);
|
||||
}
|
||||
log.info("车牌识别-结果映射耗时:{}ms", System.currentTimeMillis() - t3);
|
||||
@ -95,20 +89,81 @@ public class RecognizeLicensePlateController extends BaseController {
|
||||
|
||||
private BLicensePlateRequest toBaiduRequest(LicensePlateRecognizeRequest request) {
|
||||
BLicensePlateRequest bRequest = new BLicensePlateRequest();
|
||||
if (request == null) {
|
||||
return bRequest;
|
||||
}
|
||||
// base64编码后进行urlencode是百度API的要求,因为base64的+/=在form-urlencoded中有特殊含义
|
||||
if (isNotBlank(request.getImageBase64())) {
|
||||
if (StringUtils.isNotBlank(request.getImageBase64())) {
|
||||
bRequest.setImageBase64(encodeUrl(request.getImageBase64()));
|
||||
}
|
||||
// imageUrl 中可能包含 & 等特殊字符,需URL编码后再传给百度API,否则会被解析为form分隔符
|
||||
if (isNotBlank(request.getImageUrl())) {
|
||||
if (StringUtils.isNotBlank(request.getImageUrl())) {
|
||||
bRequest.setImageUrl(encodeUrl(request.getImageUrl()));
|
||||
}
|
||||
// detectComplete和detectRisk在BLicensePlateRequest中无默认值,handle的check()要求必须为true/false
|
||||
// detectComplete和detectRisk在BLicensePlateRequest中无默认值,下方check()要求必须为true/false
|
||||
bRequest.setDetectComplete("false");
|
||||
bRequest.setDetectRisk("false");
|
||||
return bRequest;
|
||||
}
|
||||
|
||||
private String checkRequest(BLicensePlateRequest request) {
|
||||
if (!hasImage(request)) {
|
||||
return "imageUrl和imageBase64不能同时为空";
|
||||
}
|
||||
if (checkNotTrueFalse(request.getMultiDetect())) {
|
||||
return "multiDetect必须为true/false, 是否检测多张车牌,默认为false,当置为true的时候可以对一张图片内的多张车牌进行识别";
|
||||
}
|
||||
if (checkNotTrueFalse(request.getMultiScale())) {
|
||||
return "multiScale必须为true/false, 在高拍等车牌较小的场景下可开启,默认为false";
|
||||
}
|
||||
if (checkNotTrueFalse(request.getDetectComplete())) {
|
||||
return "detectComplete必须为true/false, 是否开启车牌遮挡检测功能";
|
||||
}
|
||||
if (checkNotTrueFalse(request.getDetectRisk())) {
|
||||
return "detectRisk必须为true/false, 是否开启车牌PS检测功能";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean hasImage(BLicensePlateRequest request) {
|
||||
return request != null && (!isBlank(request.getImageUrl()) || !isBlank(request.getImageBase64()));
|
||||
}
|
||||
|
||||
private String getContent(BLicensePlateRequest request) {
|
||||
StringBuffer sb = getImageContent(request);
|
||||
if (StringUtils.isNotBlank(request.getMultiDetect())) {
|
||||
sb.append("&multi_detect=").append(request.getMultiDetect());
|
||||
}
|
||||
if (StringUtils.isNotBlank(request.getMultiScale())) {
|
||||
sb.append("&multi_scale=").append(request.getMultiScale());
|
||||
}
|
||||
if (StringUtils.isNotBlank(request.getDetectComplete())) {
|
||||
sb.append("&detect_complete=").append(request.getDetectComplete());
|
||||
}
|
||||
if (StringUtils.isNotBlank(request.getDetectRisk())) {
|
||||
sb.append("&detect_risk=").append(request.getDetectRisk());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private StringBuffer getImageContent(BLicensePlateRequest request) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
if (StringUtils.isNotBlank(request.getImageBase64())) {
|
||||
sb.append("&image=").append(request.getImageBase64());
|
||||
}
|
||||
if (StringUtils.isNotBlank(request.getImageUrl())) {
|
||||
sb.append("&url=").append(request.getImageUrl());
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
private boolean checkNotTrueFalse(String value) {
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
return !"true".equals(value) && !"false".equals(value);
|
||||
}
|
||||
|
||||
private String encodeUrl(String url) {
|
||||
try {
|
||||
return URLEncoder.encode(url, "UTF-8");
|
||||
@ -118,10 +173,6 @@ public class RecognizeLicensePlateController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isNotBlank(String value) {
|
||||
return value != null && value.trim().length() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将百度车牌颜色映射为中文车牌类型
|
||||
*/
|
||||
|
||||
@ -1,16 +1,13 @@
|
||||
package com.heyu.api.controller.car;
|
||||
|
||||
import com.heyu.api.baidu.handle.financial.BTaxiReceiptHandle;
|
||||
import com.heyu.api.baidu.request.financial.BTaxiReceiptRequest;
|
||||
import com.heyu.api.baidu.response.financial.BTaxiReceiptResp;
|
||||
import com.heyu.api.controller.BaseController;
|
||||
import com.heyu.api.data.annotation.NotIntercept;
|
||||
import com.heyu.api.data.utils.ApiR;
|
||||
import com.heyu.api.data.utils.MapUtils;
|
||||
import com.heyu.api.data.utils.R;
|
||||
import com.heyu.api.data.utils.StringUtils;
|
||||
import com.heyu.api.request.car.TaxiInvoiceRecognizeRequest;
|
||||
import com.heyu.api.resp.car.RecognizeTaxiInvoiceResp;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@ -18,6 +15,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* https://console.bce.baidu.com/support/#/api?product=AI&project=文字识别&parent=财务票据OCR&api=rest%2F2.0%2Focr%2Fv1%2Ftaxi_receipt&method=post
|
||||
@ -26,49 +24,69 @@ import java.net.URLEncoder;
|
||||
*
|
||||
* 支持识别全国各大城市的出租车票的全部字段,包括发票号码、发票代码、电话、日期、金额、
|
||||
* 上下车时间、里程等。
|
||||
*
|
||||
* 接口返回参数:
|
||||
* <pre>
|
||||
* 参数 是否必须 类型 说明
|
||||
* log_id 是 uint64 请求标识码,随机数,唯一。
|
||||
* words_result_num 是 uint32 识别结果数,表示 words_result 的元素个数
|
||||
* words_result 是 object{} 识别结果数组
|
||||
* + InvoiceCode 是 string 发票代码
|
||||
* + InvoiceNum 是 string 发票号码
|
||||
* + TaxiNum 是 string 车牌号
|
||||
* + Date 是 string 日期
|
||||
* + Time 是 string 上下车时间
|
||||
* + PickupTime 是 string 上车时间
|
||||
* + DropoffTime 是 string 下车时间
|
||||
* + Fare 是 string 金额
|
||||
* + FuelOilSurcharge 是 string 燃油附加费
|
||||
* + CallServiceSurcharge 是 string 叫车服务费
|
||||
* + TotalFare 是 string 总金额
|
||||
* + Location 是 string 开票城市
|
||||
* + Province 是 string 省
|
||||
* + City 是 string 市
|
||||
* + PricePerkm 是 string 单价
|
||||
* + Distance 是 string 里程
|
||||
* pdf_file_size 否 string 传入PDF文件的总页数,当 pdf_file 参数有效时返回该字段
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/taxi/invoice")
|
||||
@NotIntercept
|
||||
@SuppressWarnings("unchecked")
|
||||
public class RecognizeTaxiInvoiceController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private BTaxiReceiptHandle bTaxiReceiptHandle;
|
||||
private static final String TAXI_RECEIPT_URI = "/rest/2.0/ocr/v1/taxi_receipt";
|
||||
|
||||
@PostMapping("/recognize")
|
||||
public R recognize(@RequestBody TaxiInvoiceRecognizeRequest request) {
|
||||
public R<RecognizeTaxiInvoiceResp> recognize(@RequestBody TaxiInvoiceRecognizeRequest request) {
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
if (request == null) {
|
||||
return R.error("请求参数不能为空");
|
||||
}
|
||||
if (isBlank(request.getImageBase64()) && isBlank(request.getImageUrl())) {
|
||||
return R.error("imageBase64和imageUrl不能同时为空");
|
||||
}
|
||||
log.info("出租车发票识别-参数校验耗时:{}ms", System.currentTimeMillis() - start);
|
||||
|
||||
long t1 = System.currentTimeMillis();
|
||||
BTaxiReceiptRequest bRequest = toBaiduRequest(request);
|
||||
log.info("出租车发票识别-构建百度请求耗时:{}ms", System.currentTimeMillis() - t1);
|
||||
String checkMsg = checkRequest(bRequest);
|
||||
if (!isBlank(checkMsg)) {
|
||||
return R.error(checkMsg);
|
||||
}
|
||||
log.info("出租车发票识别-参数校验及构建请求耗时:{}ms", System.currentTimeMillis() - t1);
|
||||
|
||||
long t2 = System.currentTimeMillis();
|
||||
ApiR<BTaxiReceiptResp> apiR = bTaxiReceiptHandle.handle(bRequest);
|
||||
Map<String, Object> data = requestBaidu(TAXI_RECEIPT_URI, getContent(bRequest));
|
||||
long baiduCost = System.currentTimeMillis() - t2;
|
||||
log.info("出租车发票识别-调用百度OCR接口耗时:{}ms", baiduCost);
|
||||
|
||||
if (apiR == null || !apiR.isSuccess() || apiR.getData() == null) {
|
||||
return R.error(thirdError(apiR));
|
||||
if (data == null) {
|
||||
log.info("出租车发票识别-识别失败, 耗时:{}ms", System.currentTimeMillis() - start);
|
||||
return R.error("出租车发票识别失败");
|
||||
}
|
||||
|
||||
long t3 = System.currentTimeMillis();
|
||||
BTaxiReceiptResp bResp = apiR.getData();
|
||||
BTaxiReceiptResp.WordsResultDTO wordsResult = bResp.getWordsResult();
|
||||
Map<String, Object> wordsResult = MapUtils.getByExpr(data, "words_result");
|
||||
if (wordsResult == null) {
|
||||
log.info("出租车发票识别-未识别到发票信息, 耗时:{}ms", System.currentTimeMillis() - start);
|
||||
return R.error("未识别到出租车发票信息");
|
||||
}
|
||||
|
||||
long t3 = System.currentTimeMillis();
|
||||
RecognizeTaxiInvoiceResp resp = toResp(wordsResult);
|
||||
log.info("出租车发票识别-结果映射耗时:{}ms", System.currentTimeMillis() - t3);
|
||||
|
||||
@ -78,33 +96,62 @@ public class RecognizeTaxiInvoiceController extends BaseController {
|
||||
|
||||
private BTaxiReceiptRequest toBaiduRequest(TaxiInvoiceRecognizeRequest request) {
|
||||
BTaxiReceiptRequest bRequest = new BTaxiReceiptRequest();
|
||||
if (isNotBlank(request.getImageBase64())) {
|
||||
if (request == null) {
|
||||
return bRequest;
|
||||
}
|
||||
if (StringUtils.isNotBlank(request.getImageBase64())) {
|
||||
bRequest.setImageBase64(encodeParam(request.getImageBase64()));
|
||||
}
|
||||
if (isNotBlank(request.getImageUrl())) {
|
||||
if (StringUtils.isNotBlank(request.getImageUrl())) {
|
||||
bRequest.setImageUrl(encodeParam(request.getImageUrl()));
|
||||
}
|
||||
return bRequest;
|
||||
}
|
||||
|
||||
private RecognizeTaxiInvoiceResp toResp(BTaxiReceiptResp.WordsResultDTO wordsResult) {
|
||||
private String checkRequest(BTaxiReceiptRequest request) {
|
||||
if (!hasImage(request)) {
|
||||
return "imageUrl和imageBase64不能同时为空";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean hasImage(BTaxiReceiptRequest request) {
|
||||
return request != null && (!isBlank(request.getImageUrl()) || !isBlank(request.getImageBase64()));
|
||||
}
|
||||
|
||||
private String getContent(BTaxiReceiptRequest request) {
|
||||
return getImageContent(request).toString();
|
||||
}
|
||||
|
||||
private StringBuffer getImageContent(BTaxiReceiptRequest request) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
if (StringUtils.isNotBlank(request.getImageBase64())) {
|
||||
sb.append("&image=").append(request.getImageBase64());
|
||||
}
|
||||
if (StringUtils.isNotBlank(request.getImageUrl())) {
|
||||
sb.append("&url=").append(request.getImageUrl());
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
private RecognizeTaxiInvoiceResp toResp(Map<String, Object> wordsResult) {
|
||||
RecognizeTaxiInvoiceResp resp = new RecognizeTaxiInvoiceResp();
|
||||
resp.setDate(wordsResult.getDate());
|
||||
resp.setFare(wordsResult.getFare());
|
||||
resp.setLocation(wordsResult.getLocation());
|
||||
resp.setInvoiceCode(wordsResult.getInvoiceCode());
|
||||
resp.setInvoiceNum(wordsResult.getInvoiceNum());
|
||||
resp.setTaxiNum(wordsResult.getTaxiNum());
|
||||
resp.setTime(wordsResult.getTime());
|
||||
resp.setPickupTime(wordsResult.getPickupTime());
|
||||
resp.setDropoffTime(wordsResult.getDropoffTime());
|
||||
resp.setFuelOilSurcharge(wordsResult.getFuelOilSurcharge());
|
||||
resp.setCallServiceSurcharge(wordsResult.getCallServiceSurcharge());
|
||||
resp.setTotalFare(wordsResult.getTotalFare());
|
||||
resp.setProvince(wordsResult.getProvince());
|
||||
resp.setCity(wordsResult.getCity());
|
||||
resp.setPricePerkm(wordsResult.getPricePerkm());
|
||||
resp.setDistance(wordsResult.getDistance());
|
||||
resp.setDate((String) wordsResult.get("Date"));
|
||||
resp.setFare((String) wordsResult.get("Fare"));
|
||||
resp.setLocation((String) wordsResult.get("Location"));
|
||||
resp.setInvoiceCode((String) wordsResult.get("InvoiceCode"));
|
||||
resp.setInvoiceNum((String) wordsResult.get("InvoiceNum"));
|
||||
resp.setTaxiNum((String) wordsResult.get("TaxiNum"));
|
||||
resp.setTime((String) wordsResult.get("Time"));
|
||||
resp.setPickupTime((String) wordsResult.get("PickupTime"));
|
||||
resp.setDropoffTime((String) wordsResult.get("DropoffTime"));
|
||||
resp.setFuelOilSurcharge((String) wordsResult.get("FuelOilSurcharge"));
|
||||
resp.setCallServiceSurcharge((String) wordsResult.get("CallServiceSurcharge"));
|
||||
resp.setTotalFare((String) wordsResult.get("TotalFare"));
|
||||
resp.setProvince((String) wordsResult.get("Province"));
|
||||
resp.setCity((String) wordsResult.get("City"));
|
||||
resp.setPricePerkm((String) wordsResult.get("PricePerkm"));
|
||||
resp.setDistance((String) wordsResult.get("Distance"));
|
||||
return resp;
|
||||
}
|
||||
|
||||
@ -116,8 +163,4 @@ public class RecognizeTaxiInvoiceController extends BaseController {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isNotBlank(String value) {
|
||||
return value != null && value.trim().length() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,16 +1,14 @@
|
||||
package com.heyu.api.controller.car;
|
||||
|
||||
import com.heyu.api.baidu.handle.financial.BTrainTicketHandle;
|
||||
import com.heyu.api.baidu.request.financial.BTrainTicketRequest;
|
||||
import com.heyu.api.baidu.response.financial.BTrainTicketResp;
|
||||
import com.heyu.api.controller.BaseController;
|
||||
import com.heyu.api.data.annotation.NotIntercept;
|
||||
import com.heyu.api.data.utils.ApiR;
|
||||
import com.heyu.api.data.utils.MapUtils;
|
||||
import com.heyu.api.data.utils.R;
|
||||
import com.heyu.api.data.utils.StringUtils;
|
||||
import com.heyu.api.request.car.TrainTicketRecognizeRequest;
|
||||
import com.heyu.api.resp.car.RecognizeTrainTicketResp;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@ -18,6 +16,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* https://console.bce.baidu.com/support/#/api?product=AI&project=文字识别&parent=财务票据OCR&api=rest%2F2.0%2Focr%2Fv1%2Ftrain_ticket&method=post
|
||||
@ -31,44 +30,40 @@ import java.net.URLEncoder;
|
||||
@RestController
|
||||
@RequestMapping("/train/ticket")
|
||||
@NotIntercept
|
||||
@SuppressWarnings("unchecked")
|
||||
public class RecognizeTrainTicketController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private BTrainTicketHandle bTrainTicketHandle;
|
||||
private static final String TRAIN_TICKET_URI = "/rest/2.0/ocr/v1/train_ticket";
|
||||
|
||||
@PostMapping("/recognize")
|
||||
public R recognize(@RequestBody TrainTicketRecognizeRequest request) {
|
||||
public R<RecognizeTrainTicketResp> recognize(@RequestBody TrainTicketRecognizeRequest request) {
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
if (request == null) {
|
||||
return R.error("请求参数不能为空");
|
||||
}
|
||||
if (isBlank(request.getImageBase64()) && isBlank(request.getImageUrl())) {
|
||||
return R.error("imageBase64和imageUrl不能同时为空");
|
||||
}
|
||||
log.info("火车票识别-参数校验耗时:{}ms", System.currentTimeMillis() - start);
|
||||
|
||||
long t1 = System.currentTimeMillis();
|
||||
BTrainTicketRequest bRequest = toBaiduRequest(request);
|
||||
log.info("火车票识别-构建百度请求耗时:{}ms", System.currentTimeMillis() - t1);
|
||||
String checkMsg = checkRequest(bRequest);
|
||||
if (!isBlank(checkMsg)) {
|
||||
return R.error(checkMsg);
|
||||
}
|
||||
log.info("火车票识别-参数校验及构建请求耗时:{}ms", System.currentTimeMillis() - t1);
|
||||
|
||||
long t2 = System.currentTimeMillis();
|
||||
ApiR<BTrainTicketResp> apiR = bTrainTicketHandle.handle(bRequest);
|
||||
Map<String, Object> data = requestBaidu(TRAIN_TICKET_URI, getContent(bRequest));
|
||||
long baiduCost = System.currentTimeMillis() - t2;
|
||||
log.info("火车票识别-调用百度OCR接口耗时:{}ms", baiduCost);
|
||||
|
||||
if (apiR == null || !apiR.isSuccess() || apiR.getData() == null) {
|
||||
return R.error(thirdError(apiR));
|
||||
if (data == null) {
|
||||
log.info("火车票识别-识别失败, 耗时:{}ms", System.currentTimeMillis() - start);
|
||||
return R.error("火车票识别失败");
|
||||
}
|
||||
|
||||
long t3 = System.currentTimeMillis();
|
||||
BTrainTicketResp bResp = apiR.getData();
|
||||
BTrainTicketResp.WordsResultDTO wordsResult = bResp.getWordsResult();
|
||||
Map<String, Object> wordsResult = MapUtils.getByExpr(data, "words_result");
|
||||
if (wordsResult == null) {
|
||||
log.info("火车票识别-未识别到火车票信息, 耗时:{}ms", System.currentTimeMillis() - start);
|
||||
return R.error("未识别到火车票信息");
|
||||
}
|
||||
|
||||
long t3 = System.currentTimeMillis();
|
||||
RecognizeTrainTicketResp resp = toResp(wordsResult);
|
||||
log.info("火车票识别-结果映射耗时:{}ms", System.currentTimeMillis() - t3);
|
||||
|
||||
@ -78,44 +73,76 @@ public class RecognizeTrainTicketController extends BaseController {
|
||||
|
||||
private BTrainTicketRequest toBaiduRequest(TrainTicketRecognizeRequest request) {
|
||||
BTrainTicketRequest bRequest = new BTrainTicketRequest();
|
||||
if (isNotBlank(request.getImageBase64())) {
|
||||
if (request == null) {
|
||||
return bRequest;
|
||||
}
|
||||
if (StringUtils.isNotBlank(request.getImageBase64())) {
|
||||
bRequest.setImageBase64(encodeParam(request.getImageBase64()));
|
||||
}
|
||||
if (isNotBlank(request.getImageUrl())) {
|
||||
if (StringUtils.isNotBlank(request.getImageUrl())) {
|
||||
bRequest.setImageUrl(encodeParam(request.getImageUrl()));
|
||||
}
|
||||
return bRequest;
|
||||
}
|
||||
|
||||
private RecognizeTrainTicketResp toResp(BTrainTicketResp.WordsResultDTO words) {
|
||||
private String checkRequest(BTrainTicketRequest request) {
|
||||
if (!hasImage(request)) {
|
||||
return "imageUrl和imageBase64不能同时为空";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean hasImage(BTrainTicketRequest request) {
|
||||
return request != null && (!isBlank(request.getImageUrl()) || !isBlank(request.getImageBase64()));
|
||||
}
|
||||
|
||||
private String getContent(BTrainTicketRequest request) {
|
||||
return getImageContent(request).toString();
|
||||
}
|
||||
|
||||
private StringBuffer getImageContent(BTrainTicketRequest request) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
if (StringUtils.isNotBlank(request.getImageBase64())) {
|
||||
sb.append("&image=").append(request.getImageBase64());
|
||||
}
|
||||
if (StringUtils.isNotBlank(request.getImageUrl())) {
|
||||
sb.append("&url=").append(request.getImageUrl());
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
private RecognizeTrainTicketResp toResp(Map<String, Object> words) {
|
||||
RecognizeTrainTicketResp resp = new RecognizeTrainTicketResp();
|
||||
|
||||
resp.setName(words.getName());
|
||||
resp.setNumber(words.getTrainNum());
|
||||
resp.setDepartureStation(words.getStartingStation());
|
||||
resp.setDestination(words.getDestinationStation());
|
||||
resp.setLevel(words.getSeatCategory());
|
||||
resp.setSeat(words.getSeatNum());
|
||||
resp.setName((String) words.get("name"));
|
||||
resp.setNumber((String) words.get("train_num"));
|
||||
resp.setDepartureStation((String) words.get("starting_station"));
|
||||
resp.setDestination((String) words.get("destination_station"));
|
||||
resp.setLevel((String) words.get("seat_category"));
|
||||
resp.setSeat((String) words.get("seat_num"));
|
||||
|
||||
// 日期和时间合并:百度返回独立date和time字段,合并为"2017年08月05日 22:09开"格式
|
||||
String date = (String) words.get("date");
|
||||
String time = (String) words.get("time");
|
||||
StringBuilder dateTime = new StringBuilder();
|
||||
if (isNotBlank(words.getDate())) {
|
||||
dateTime.append(words.getDate());
|
||||
if (StringUtils.isNotBlank(date)) {
|
||||
dateTime.append(date);
|
||||
}
|
||||
if (isNotBlank(words.getTime())) {
|
||||
if (StringUtils.isNotBlank(time)) {
|
||||
if (dateTime.length() > 0) {
|
||||
dateTime.append(" ");
|
||||
}
|
||||
dateTime.append(words.getTime());
|
||||
dateTime.append(time);
|
||||
}
|
||||
resp.setDate(dateTime.length() > 0 ? dateTime.toString() : null);
|
||||
|
||||
// 票价:百度返回字符串,转为Float
|
||||
if (isNotBlank(words.getTicketRates())) {
|
||||
String ticketRates = (String) words.get("ticket_rates");
|
||||
if (StringUtils.isNotBlank(ticketRates)) {
|
||||
try {
|
||||
resp.setPrice(Float.parseFloat(words.getTicketRates()));
|
||||
resp.setPrice(Float.parseFloat(ticketRates));
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("票价解析失败:{}", words.getTicketRates());
|
||||
log.warn("票价解析失败:{}", ticketRates);
|
||||
resp.setPrice(null);
|
||||
}
|
||||
}
|
||||
@ -131,8 +158,4 @@ public class RecognizeTrainTicketController extends BaseController {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isNotBlank(String value) {
|
||||
return value != null && value.trim().length() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,24 +5,69 @@ import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 出租车发票识别响应(百度OCR)
|
||||
*
|
||||
* 接口返回参数:
|
||||
* <pre>
|
||||
* 参数 是否必须 类型 说明
|
||||
* log_id 是 uint64 请求标识码,随机数,唯一。
|
||||
* words_result_num 是 uint32 识别结果数,表示 words_result 的元素个数
|
||||
* words_result 是 object{} 识别结果数组
|
||||
* + InvoiceCode 是 string 发票代码
|
||||
* + InvoiceNum 是 string 发票号码
|
||||
* + TaxiNum 是 string 车牌号
|
||||
* + Date 是 string 日期
|
||||
* + Time 是 string 上下车时间
|
||||
* + PickupTime 是 string 上车时间
|
||||
* + DropoffTime 是 string 下车时间
|
||||
* + Fare 是 string 金额
|
||||
* + FuelOilSurcharge 是 string 燃油附加费
|
||||
* + CallServiceSurcharge 是 string 叫车服务费
|
||||
* + TotalFare 是 string 总金额
|
||||
* + Location 是 string 开票城市
|
||||
* + Province 是 string 省
|
||||
* + City 是 string 市
|
||||
* + PricePerkm 是 string 单价
|
||||
* + Distance 是 string 里程
|
||||
* pdf_file_size 否 string 传入PDF文件的总页数,当 pdf_file 参数有效时返回该字段
|
||||
* </pre>
|
||||
*/
|
||||
@Data
|
||||
public class RecognizeTaxiInvoiceResp extends BaseResp {
|
||||
|
||||
/** 日期 (对应百度返回字段 Date) */
|
||||
private String date;
|
||||
/** 金额 (对应百度返回字段 Fare) */
|
||||
private String fare;
|
||||
/** 开票城市 (对应百度返回字段 Location) */
|
||||
private String location;
|
||||
/** 发票代码 (对应百度返回字段 InvoiceCode) */
|
||||
private String invoiceCode;
|
||||
/** 发票号码 (对应百度返回字段 InvoiceNum) */
|
||||
private String invoiceNum;
|
||||
/** 车牌号 (对应百度返回字段 TaxiNum) */
|
||||
private String taxiNum;
|
||||
/** 上下车时间 (对应百度返回字段 Time) */
|
||||
private String time;
|
||||
/** 上车时间 (对应百度返回字段 PickupTime) */
|
||||
private String pickupTime;
|
||||
/** 下车时间 (对应百度返回字段 DropoffTime) */
|
||||
private String dropoffTime;
|
||||
/** 燃油附加费 (对应百度返回字段 FuelOilSurcharge) */
|
||||
private String fuelOilSurcharge;
|
||||
/** 叫车服务费 (对应百度返回字段 CallServiceSurcharge) */
|
||||
private String callServiceSurcharge;
|
||||
/** 总金额 (对应百度返回字段 TotalFare) */
|
||||
private String totalFare;
|
||||
/** 省 (对应百度返回字段 Province) */
|
||||
private String province;
|
||||
/** 市 (对应百度返回字段 City) */
|
||||
private String city;
|
||||
/** 单价 (对应百度返回字段 PricePerkm) */
|
||||
private String pricePerkm;
|
||||
/** 里程 (对应百度返回字段 Distance) */
|
||||
private String distance;
|
||||
/** 原始识别条目列表(保留扩展字段) */
|
||||
private List<String> rawItems;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user