Files
oneos-backend/yudao-module-ocr/OCR_API_INTEGRATION_PLAN.md
kkfluous 78a6cde22d feat: 实现 OCR 模块和车辆上牌管理功能
- 新增 yudao-module-ocr 模块
  - OCR API 模块:定义 Feign 接口和 DTO
  - OCR Server 模块:实现行驶证识别功能
  - 集成百度 OCR SDK
  - 支持多厂商扩展(百度/腾讯/阿里云)

- 新增车辆上牌管理功能
  - 数据库表:asset_vehicle_registration
  - 完整的 CRUD 接口
  - 行驶证识别接口(集成 OCR)
  - 车辆匹配功能(根据 VIN)
  - 确认上牌功能(更新车辆信息)

- 技术实现
  - 遵循 BPM/System 模块的 RPC API 模式
  - 使用 Feign 实现服务间调用
  - Base64 编码传输图片数据
  - 统一返回格式 CommonResult<T>

- 文档
  - OCR 模块使用文档
  - OCR 部署指南
  - 车辆上牌管理总结
  - API 集成规划和总结
2026-03-12 20:33:21 +08:00

303 lines
9.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# OCR 模块 API 集成规划
## 目标
按照 BPM/System 模块的 API 集成模式,将 OCR 模块改造为标准的 RPC API 服务,供其他模块(如 Asset 模块)通过 Feign 调用。
## 当前架构分析
### BPM/System 模块的 API 模式
**API 模块yudao-module-xxx-api**
- 定义 Feign 接口(如 `BpmProcessInstanceApi.java`
- 使用 `@FeignClient(name = ApiConstants.NAME)` 注解
- 定义 API 常量(`ApiConstants.java`
- 定义 DTO 对象(用于 RPC 传输)
**Server 模块yudao-module-xxx-server**
- 实现 API 接口(如 `BpmProcessInstanceApiImpl.java`
- 使用 `@RestController` 注解(提供 RESTful API
- 注入 Service 层,调用业务逻辑
- 返回 `CommonResult<T>` 包装结果
## OCR 模块改造规划
### Phase 1: 重构 OCR API 模块
#### 1.1 创建 ApiConstants
**文件**: `yudao-module-ocr-api/src/main/java/cn/iocoder/yudao/module/ocr/enums/ApiConstants.java`
```java
package cn.iocoder.yudao.module.ocr.enums;
import cn.iocoder.yudao.framework.common.enums.RpcConstants;
public class ApiConstants {
/**
* 服务名(需要和 spring.application.name 保持一致)
*/
public static final String NAME = "ocr-server";
public static final String PREFIX = RpcConstants.RPC_API_PREFIX + "/ocr";
public static final String VERSION = "1.0.0";
}
```
#### 1.2 创建 DTO 对象
**文件**: `yudao-module-ocr-api/src/main/java/cn/iocoder/yudao/module/ocr/api/dto/VehicleLicenseRespDTO.java`
```java
package cn.iocoder.yudao.module.ocr.api.dto;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDate;
@Data
public class VehicleLicenseRespDTO implements Serializable {
private String vin;
private String plateNo;
private String brand;
private String vehicleType;
private String owner;
private String useCharacter;
private String engineNo;
private LocalDate registerDate;
private LocalDate issueDate;
private String inspectionRecord;
private LocalDate scrapDate;
private String curbWeight;
private String totalMass;
private String approvedPassengerCapacity;
}
```
#### 1.3 创建 Feign API 接口
**文件**: `yudao-module-ocr-api/src/main/java/cn/iocoder/yudao/module/ocr/api/OcrApi.java`
```java
package cn.iocoder.yudao.module.ocr.api;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.ocr.api.dto.VehicleLicenseRespDTO;
import cn.iocoder.yudao.module.ocr.enums.ApiConstants;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = ApiConstants.NAME)
@Tag(name = "RPC 服务 - OCR 识别")
public interface OcrApi {
String PREFIX = ApiConstants.PREFIX + "/recognition";
@PostMapping(PREFIX + "/vehicle-license")
@Operation(summary = "识别行驶证(提供给内部模块)")
@Parameter(name = "imageData", description = "图片数据Base64编码", required = true)
@Parameter(name = "provider", description = "OCR厂商可选", example = "baidu")
CommonResult<VehicleLicenseRespDTO> recognizeVehicleLicense(
@RequestParam("imageData") String imageData,
@RequestParam(value = "provider", required = false) String provider);
}
```
### Phase 2: 实现 OCR API
#### 2.1 创建 API 实现类
**文件**: `yudao-module-ocr-server/src/main/java/cn/iocoder/yudao/module/ocr/api/OcrApiImpl.java`
```java
package cn.iocoder.yudao.module.ocr.api;
import cn.hutool.core.codec.Base64;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.ocr.api.dto.VehicleLicenseRespDTO;
import cn.iocoder.yudao.module.ocr.framework.ocr.core.client.OcrClient;
import cn.iocoder.yudao.module.ocr.framework.ocr.core.client.OcrClientFactory;
import cn.iocoder.yudao.module.ocr.framework.ocr.core.result.VehicleLicenseResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import jakarta.annotation.Resource;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@RestController
@Validated
@Slf4j
public class OcrApiImpl implements OcrApi {
@Resource
private OcrClientFactory ocrClientFactory;
@Override
public CommonResult<VehicleLicenseRespDTO> recognizeVehicleLicense(String imageData, String provider) {
log.info("[recognizeVehicleLicense][开始识别行驶证provider{}]", provider);
// Base64 解码图片数据
byte[] imageBytes = Base64.decode(imageData);
// 获取 OCR 客户端
OcrClient ocrClient = provider != null
? ocrClientFactory.getClient(provider)
: ocrClientFactory.getDefaultClient();
// 调用识别
VehicleLicenseResult result = ocrClient.recognizeVehicleLicense(imageBytes);
// 转换为 DTO
VehicleLicenseRespDTO respDTO = BeanUtils.toBean(result, VehicleLicenseRespDTO.class);
log.info("[recognizeVehicleLicense][识别完成VIN{},车牌号:{}]",
respDTO.getVin(), respDTO.getPlateNo());
return success(respDTO);
}
}
```
### Phase 3: Asset 模块集成 OCR API
#### 3.1 添加 OCR API 依赖
**文件**: `yudao-module-asset/yudao-module-asset-server/pom.xml`
```xml
<!-- OCR 模块 API -->
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-module-ocr-api</artifactId>
<version>${revision}</version>
</dependency>
```
#### 3.2 修改 VehicleRegistrationServiceImpl
**文件**: `VehicleRegistrationServiceImpl.java`
```java
@Service
@Validated
@Slf4j
public class VehicleRegistrationServiceImpl implements VehicleRegistrationService {
@Resource
private VehicleRegistrationMapper vehicleRegistrationMapper;
@Resource
private VehicleBaseMapper vehicleBaseMapper;
@Resource
private OcrApi ocrApi; // 注入 OCR API
@Override
public VehicleLicenseRecognizeRespVO recognizeVehicleLicense(byte[] imageData) {
long startTime = System.currentTimeMillis();
// Base64 编码图片数据
String imageDataBase64 = Base64.encode(imageData);
// 调用 OCR API
CommonResult<VehicleLicenseRespDTO> result = ocrApi.recognizeVehicleLicense(imageDataBase64, null);
if (!result.isSuccess()) {
throw exception(new ErrorCode(500, "OCR识别失败" + result.getMsg()));
}
VehicleLicenseRespDTO ocrResult = result.getData();
long costTime = System.currentTimeMillis() - startTime;
// 转换为响应 VO
VehicleLicenseRecognizeRespVO respVO = new VehicleLicenseRecognizeRespVO();
BeanUtil.copyProperties(ocrResult, respVO);
respVO.setOcrProvider("baidu");
respVO.setOcrCostTime((int) costTime);
// 根据 VIN 查找车辆
if (StrUtil.isNotBlank(ocrResult.getVin())) {
VehicleBaseDO vehicle = vehicleBaseMapper.selectOne(new LambdaQueryWrapper<VehicleBaseDO>()
.eq(VehicleBaseDO::getVin, ocrResult.getVin())
.last("LIMIT 1"));
if (vehicle != null) {
respVO.setVehicleId(vehicle.getId());
respVO.setVehicleModelId(vehicle.getVehicleModelId());
respVO.setMatchMethod("exact");
respVO.setMatchConfidence(new BigDecimal("100.00"));
log.info("[recognizeVehicleLicense][找到匹配车辆车辆ID{}]", vehicle.getId());
} else {
respVO.setMatchMethod("none");
respVO.setMatchConfidence(BigDecimal.ZERO);
log.warn("[recognizeVehicleLicense][未找到匹配车辆VIN{}]", ocrResult.getVin());
}
}
return respVO;
}
// ... 其他方法保持不变
}
```
## 实施步骤
### Step 1: 重构 OCR API 模块
1. ✅ 创建 `ApiConstants.java`
2. ✅ 创建 `VehicleLicenseRespDTO.java`
3. ✅ 创建 `OcrApi.java` 接口
4. ✅ 移除现有的错误码常量(已在 api 模块中)
### Step 2: 实现 OCR API
1. ✅ 创建 `OcrApiImpl.java`
2. ✅ 实现 `recognizeVehicleLicense` 方法
3. ✅ 添加日志和异常处理
### Step 3: 集成到 Asset 模块
1. ✅ 修改 `VehicleRegistrationServiceImpl.java`
2. ✅ 注入 `OcrApi`
3. ✅ 实现车辆匹配逻辑
4. ✅ 测试端到端流程
### Step 4: 测试验证
1. ✅ 单元测试
2. ✅ 集成测试
3. ✅ 端到端测试
## 关键改动点
### 1. 图片传输方式
- **原方案**: 直接传输 `byte[]`(不适合 Feign
- **新方案**: Base64 编码后传输 `String`(适合 HTTP/Feign
### 2. 返回值类型
- **原方案**: 直接返回 `VehicleLicenseResult`(内部类)
- **新方案**: 返回 `CommonResult<VehicleLicenseRespDTO>`(标准 RPC 响应)
### 3. 模块依赖
- **原方案**: Asset 模块依赖 OCR Server 模块(耦合)
- **新方案**: Asset 模块只依赖 OCR API 模块(解耦)
## 优势
1. **标准化**: 遵循项目统一的 RPC API 模式
2. **解耦**: Asset 模块不依赖 OCR 的实现细节
3. **可扩展**: 其他模块也可以轻松集成 OCR 服务
4. **可测试**: 可以 Mock OcrApi 进行单元测试
5. **可维护**: 清晰的模块边界和职责划分
## 注意事项
1. **服务名配置**: 确保 `ocr-server``spring.application.name``ApiConstants.NAME` 一致
2. **Feign 配置**: 确保 Feign 客户端配置正确(超时、重试等)
3. **图片大小限制**: Base64 编码后会增大约 33%,需要注意 HTTP 请求大小限制
4. **性能考虑**: 对于大图片,考虑使用文件服务 + URL 传递方式
---
**规划版本**: v1.0
**创建日期**: 2026-03-12
**状态**: 待实施