This commit is contained in:
quyixiao 2026-05-21 09:27:57 +08:00
parent 25e68efd21
commit ed11a1871b
7 changed files with 377 additions and 150 deletions

View File

@ -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")

View File

@ -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();

View File

@ -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);

View File

@ -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;
}
/**
* 将百度车牌颜色映射为中文车牌类型
*/

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}