diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/OcrRecognizeRequest.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/OcrRecognizeRequest.java new file mode 100644 index 00000000..0e1aa212 --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/OcrRecognizeRequest.java @@ -0,0 +1,53 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *
+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *
+ * 1. This software is for development use only under a valid license + * from BladeX. + *
+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *
+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *
+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.transport.pojo.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 证件 OCR 识别请求(小程序)
+ *
+ * @author Chill
+ */
+@Data
+@Schema(description = "证件OCR识别请求")
+public class OcrRecognizeRequest implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ @Schema(description = "已上传图片的地址,必须是百度可访问的HTTP或HTTPS地址")
+ private String imageUrl;
+ @Schema(description = "证件类型:idcard-身份证,driver-驾驶证,vehicle-行驶证,road_transport-道路运输证")
+ private String type;
+ @Schema(description = "正副面:front或back,默认front")
+ private String side;
+
+}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/DriverAuthController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/DriverAuthController.java
index 708e02ab..9d8fb576 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/DriverAuthController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/DriverAuthController.java
@@ -31,10 +31,15 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.springblade.core.boot.ctrl.BladeController;
import org.springblade.core.tool.api.R;
+import org.springblade.transport.ocr.constant.BaiduOcrType;
+import org.springblade.transport.ocr.service.IBaiduOcrService;
+import org.springblade.transport.pojo.dto.OcrRecognizeRequest;
import org.springblade.transport.pojo.dto.PersonCertSubmitRequest;
import org.springblade.transport.pojo.dto.VehicleCertSubmitRequest;
+import org.springblade.transport.pojo.vo.BaiduOcrResultVO;
import org.springblade.transport.pojo.vo.TransportCertRecordVO;
import org.springblade.transport.service.IDriverCertService;
+import org.springblade.transport.support.OcrTypeMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
@@ -61,6 +66,7 @@ import java.util.List;
public class DriverAuthController extends BladeController {
private final IDriverCertService driverCertService;
+ private final IBaiduOcrService baiduOcrService;
// ------------------------------------------------------------ 个人认证
@@ -118,4 +124,18 @@ public class DriverAuthController extends BladeController {
return R.data(driverCertService.getRecord(id));
}
+ // ------------------------------------------------------------ OCR
+
+ @PostMapping("/ocr")
+ @ApiOperationSupport(order = 8)
+ @Operation(summary = "证件OCR识别",
+ description = "按小程序既有契约收 {imageUrl, type, side?},转调百度OCR。"
+ + "type 用小程序命名:idcard-身份证,driver-驾驶证,vehicle-行驶证,road_transport-道路运输证")
+ public R
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.transport.support;
+
+import org.springblade.transport.ocr.constant.BaiduOcrType;
+
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * 证件类型映射:小程序命名 → 百度 OCR 证件类型
+ *
+ * ⚠️ 两侧命名对「行驶证」和「驾驶证」的用词正好相反,是本类存在的主要理由:
+ *
+ * 采用白名单而非「透传 + 兜底为通用识别」还有一个原因:
+ * 从业资格证没有对应的百度接口,静默降级成通用文字识别会让前端以为识别成功。
+ *
+ * @author Chill
+ */
+public final class OcrTypeMapping {
+
+ /**
+ * 允许的证件类型:小程序命名与后端既有命名都收,其余一律拒绝
+ */
+ private static final Map
+ * 小程序目前不传该参数,按正面处理(身份证、行驶证、驾驶证的正页是常用场景)。
+ *
+ * @param side 正反面:front / back
+ * @return 规范化结果,恒为 front 或 back
+ */
+ public static String normalizeSide(String side) {
+ // 归一化结果是大写,故此处比对 "BACK"
+ return "BACK".equals(normalize(side)) ? SIDE_BACK : SIDE_FRONT;
+ }
+
+ /**
+ * 归一化:去首尾空白、去分隔符差异、统一大写
+ *
+ * @param value 原始值
+ * @return 归一化结果;空值返回空串
+ */
+ private static String normalize(String value) {
+ if (value == null) {
+ return "";
+ }
+ return value.trim()
+ .replace('-', '_')
+ .replace(' ', '_')
+ .toUpperCase(Locale.ROOT);
+ }
+
+}
diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/support/OcrTypeMappingTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/support/OcrTypeMappingTest.java
new file mode 100644
index 00000000..c88a8057
--- /dev/null
+++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/support/OcrTypeMappingTest.java
@@ -0,0 +1,105 @@
+package org.springblade.transport.support;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springblade.transport.ocr.constant.BaiduOcrType;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * 小程序证件类型 → 百度 OCR 证件类型的映射测试。
+ *
+ * 两侧用的是两套命名,且**都不太直观**:
+ * 小程序把「行驶证」叫 {@code vehicle}、把「驾驶证」叫 {@code driver}
+ * (按业务直觉命名),而后端 OCR 沿用百度接口的 {@code vehicle_license}
+ * (行驶证)与 {@code driving_license}(驾驶证)。
+ * 这两个词一一对应关系相反,映射写错会让「行驶证」被当成「驾驶证」识别,
+ * 结果是司机传了行驶证却拿到一堆驾驶证字段——而且不会报错。
+ * 故此处逐一锁住。
+ */
+class OcrTypeMappingTest {
+
+ @Test
+ @DisplayName("身份证:小程序 idcard → 后端 ID_CARD")
+ void idCard() {
+ assertEquals(BaiduOcrType.ID_CARD, OcrTypeMapping.toBaiduType("idcard"));
+ }
+
+ @Test
+ @DisplayName("驾驶证:小程序 driver → 后端 DRIVING_LICENSE(不是 vehicle_license)")
+ void drivingLicense() {
+ assertEquals(BaiduOcrType.DRIVING_LICENSE, OcrTypeMapping.toBaiduType("driver"));
+ }
+
+ @Test
+ @DisplayName("行驶证:小程序 vehicle → 后端 VEHICLE_LICENSE(不是 driving_license)")
+ void vehicleLicense() {
+ assertEquals(BaiduOcrType.VEHICLE_LICENSE, OcrTypeMapping.toBaiduType("vehicle"));
+ }
+
+ @Test
+ @DisplayName("道路运输证:小程序 road_transport → 后端 ROAD_TRANSPORT_CERTIFICATE")
+ void roadTransport() {
+ assertEquals(BaiduOcrType.ROAD_TRANSPORT_CERTIFICATE,
+ OcrTypeMapping.toBaiduType("road_transport"));
+ }
+
+ @Test
+ @DisplayName("从业资格证:后端无对应 OCR 能力,必须明确报错而不是退化成通用识别")
+ void qualificationIsNotSilentlyDowngraded() {
+ // 百度没有「从业资格证」专用接口。若这里悄悄退化成 GENERAL,
+ // 前端会拿到一堆通用文本却以为识别成功了,故要求明确失败。
+ IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> OcrTypeMapping.toBaiduType("qualification"));
+ assertTrue(ex.getMessage().contains("从业资格证"),
+ "错误信息要指明是哪个证件类型不支持,便于前端提示用户手动填写");
+ }
+
+ @Test
+ @DisplayName("后端既有命名也接受(前端或其它调用方可能直接传后端名)")
+ void acceptsBackendNames() {
+ assertEquals(BaiduOcrType.ID_CARD, OcrTypeMapping.toBaiduType("id_card"));
+ assertEquals(BaiduOcrType.DRIVING_LICENSE, OcrTypeMapping.toBaiduType("driving_license"));
+ assertEquals(BaiduOcrType.VEHICLE_LICENSE, OcrTypeMapping.toBaiduType("vehicle_license"));
+ assertEquals(BaiduOcrType.ROAD_TRANSPORT_CERTIFICATE,
+ OcrTypeMapping.toBaiduType("road_transport_certificate"));
+ }
+
+ @Test
+ @DisplayName("大小写与分隔符不敏感")
+ void caseAndSeparatorInsensitive() {
+ assertEquals(BaiduOcrType.ID_CARD, OcrTypeMapping.toBaiduType("IDCard"));
+ assertEquals(BaiduOcrType.ID_CARD, OcrTypeMapping.toBaiduType(" ID_CARD "));
+ assertEquals(BaiduOcrType.ID_CARD, OcrTypeMapping.toBaiduType("id-card"));
+ assertEquals(BaiduOcrType.ROAD_TRANSPORT_CERTIFICATE,
+ OcrTypeMapping.toBaiduType("Road-Transport"));
+ }
+
+ @Test
+ @DisplayName("未知类型:明确报错,不静默降级为通用识别")
+ void unknownTypeFails() {
+ assertThrows(IllegalArgumentException.class, () -> OcrTypeMapping.toBaiduType("truck"));
+ assertThrows(IllegalArgumentException.class, () -> OcrTypeMapping.toBaiduType("general"));
+ }
+
+ @Test
+ @DisplayName("空值:明确报错")
+ void emptyTypeFails() {
+ assertThrows(IllegalArgumentException.class, () -> OcrTypeMapping.toBaiduType(null));
+ assertThrows(IllegalArgumentException.class, () -> OcrTypeMapping.toBaiduType(""));
+ assertThrows(IllegalArgumentException.class, () -> OcrTypeMapping.toBaiduType(" "));
+ }
+
+ @Test
+ @DisplayName("正反面:小程序不传 side 时按正面处理")
+ void defaultSide() {
+ assertEquals("front", OcrTypeMapping.normalizeSide(null));
+ assertEquals("front", OcrTypeMapping.normalizeSide(""));
+ assertEquals("front", OcrTypeMapping.normalizeSide("front"));
+ assertEquals("back", OcrTypeMapping.normalizeSide("back"));
+ assertEquals("back", OcrTypeMapping.normalizeSide("BACK"));
+ }
+
+}
+ *
+ * 若把 {@code vehicle} 直接透传给百度按 {@code driving_license} 处理,或反之,
+ * 调用方不会收到任何错误——拿到的是另一种证件的字段,问题只在人工核对时才发现。
+ * 故此处用显式白名单逐一对应,而不是把请求参数直接丢给下游。
+ *
+ * 证件 小程序传 后端 OCR 用
+ * 驾驶证 {@code driver} {@code driving_license}
+ * 行驶证 {@code vehicle} {@code vehicle_license}