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 集成规划和总结
This commit is contained in:
kkfluous
2026-03-12 20:33:21 +08:00
parent 0706b51acd
commit 78a6cde22d
50 changed files with 3886 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
package cn.iocoder.yudao.module.ocr;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* OCR 识别模块 Application
*
* @author 芋道源码
*/
@SpringBootApplication
public class OcrServerApplication {
public static void main(String[] args) {
SpringApplication.run(OcrServerApplication.class, args);
}
}

View File

@@ -0,0 +1,63 @@
package cn.iocoder.yudao.module.ocr.api;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.StrUtil;
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;
/**
* OCR 识别 API 实现类
*
* @author 芋道源码
*/
@RestController
@Validated
@Slf4j
public class OcrApiImpl implements OcrApi {
@Resource
private OcrClientFactory ocrClientFactory;
@Override
public CommonResult<VehicleLicenseRespDTO> recognizeVehicleLicense(String imageData, String provider) {
long startTime = System.currentTimeMillis();
log.info("[recognizeVehicleLicense][开始识别行驶证provider{},数据长度:{}]",
provider, imageData != null ? imageData.length() : 0);
// Base64 解码图片数据
byte[] imageBytes = Base64.decode(imageData);
// 获取 OCR 客户端
OcrClient ocrClient;
if (StrUtil.isNotBlank(provider)) {
ocrClient = ocrClientFactory.getClient(provider);
} else {
ocrClient = ocrClientFactory.getDefaultClient();
}
// 调用识别
VehicleLicenseResult result = ocrClient.recognizeVehicleLicense(imageBytes);
// 转换为 DTO
VehicleLicenseRespDTO respDTO = BeanUtils.toBean(result, VehicleLicenseRespDTO.class);
long costTime = System.currentTimeMillis() - startTime;
log.info("[recognizeVehicleLicense][识别完成,耗时:{}msVIN{},车牌号:{}]",
costTime, respDTO.getVin(), respDTO.getPlateNo());
return success(respDTO);
}
}

View File

@@ -0,0 +1,54 @@
package cn.iocoder.yudao.module.ocr.controller.admin.ocr;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.ocr.controller.admin.ocr.vo.VehicleLicenseRespVO;
import cn.iocoder.yudao.module.ocr.convert.ocr.OcrConvert;
import cn.iocoder.yudao.module.ocr.framework.ocr.core.result.VehicleLicenseResult;
import cn.iocoder.yudao.module.ocr.service.ocr.OcrService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import jakarta.annotation.Resource;
import java.io.IOException;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
/**
* OCR 识别控制器
*/
@Tag(name = "管理后台 - OCR 识别")
@RestController
@RequestMapping("/ocr")
@Validated
@Slf4j
public class OcrController {
@Resource
private OcrService ocrService;
@PostMapping("/vehicle-license")
@Operation(summary = "识别行驶证")
@PreAuthorize("@ss.hasPermission('ocr:vehicle-license:recognize')")
public CommonResult<VehicleLicenseRespVO> recognizeVehicleLicense(
@Parameter(description = "行驶证图片文件", required = true)
@RequestParam("file") MultipartFile file,
@Parameter(description = "OCR 厂商(可选,默认使用配置的厂商)")
@RequestParam(value = "provider", required = false) String provider) throws IOException {
// 读取图片数据
byte[] imageData = file.getBytes();
// 调用识别服务
VehicleLicenseResult result = ocrService.recognizeVehicleLicense(imageData, provider);
// 转换并返回
return success(OcrConvert.INSTANCE.convert(result));
}
}

View File

@@ -0,0 +1,57 @@
package cn.iocoder.yudao.module.ocr.controller.admin.ocr.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDate;
/**
* 行驶证识别响应 VO
*/
@Schema(description = "管理后台 - 行驶证识别响应 VO")
@Data
public class VehicleLicenseRespVO {
@Schema(description = "车辆识别代号VIN", example = "LSVAM4189E2123456")
private String vin;
@Schema(description = "号牌号码", example = "粤A12345")
private String plateNo;
@Schema(description = "品牌型号", example = "比亚迪秦PLUS DM-i")
private String brand;
@Schema(description = "车辆类型", example = "小型轿车")
private String vehicleType;
@Schema(description = "所有人", example = "张三")
private String owner;
@Schema(description = "使用性质", example = "非营运")
private String useCharacter;
@Schema(description = "发动机号码", example = "BYD123456")
private String engineNo;
@Schema(description = "注册日期", example = "2023-01-01")
private LocalDate registerDate;
@Schema(description = "发证日期", example = "2023-01-01")
private LocalDate issueDate;
@Schema(description = "检验记录", example = "2026-06")
private String inspectionRecord;
@Schema(description = "强制报废期止", example = "2035-12-31")
private LocalDate scrapDate;
@Schema(description = "整备质量kg", example = "1500")
private String curbWeight;
@Schema(description = "总质量kg", example = "1875")
private String totalMass;
@Schema(description = "核定载人数", example = "5")
private String approvedPassengerCapacity;
}

View File

@@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.ocr.convert.ocr;
import cn.iocoder.yudao.module.ocr.controller.admin.ocr.vo.VehicleLicenseRespVO;
import cn.iocoder.yudao.module.ocr.framework.ocr.core.result.VehicleLicenseResult;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* OCR 转换器
*/
@Mapper
public interface OcrConvert {
OcrConvert INSTANCE = Mappers.getMapper(OcrConvert.class);
/**
* 转换行驶证识别结果
*/
VehicleLicenseRespVO convert(VehicleLicenseResult result);
}

View File

@@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.ocr.framework.ocr.config;
import cn.iocoder.yudao.module.ocr.framework.ocr.core.client.OcrClientFactory;
import cn.iocoder.yudao.module.ocr.framework.ocr.core.client.OcrClientFactoryImpl;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
/**
* OCR 自动配置类
*/
@AutoConfiguration
@EnableConfigurationProperties(OcrProperties.class)
public class OcrAutoConfiguration {
@Bean
public OcrClientFactory ocrClientFactory(OcrProperties properties) {
OcrClientFactoryImpl factory = new OcrClientFactoryImpl(properties.getDefaultProvider());
// 初始化百度客户端
if (properties.getBaidu() != null) {
factory.createOrUpdateClient("baidu", properties.getBaidu());
}
return factory;
}
}

View File

@@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.ocr.framework.ocr.config;
import cn.iocoder.yudao.module.ocr.framework.ocr.core.client.impl.BaiduOcrClientConfig;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* OCR 配置属性
*/
@Data
@ConfigurationProperties(prefix = "ocr")
public class OcrProperties {
/**
* 默认使用的厂商
*/
private String defaultProvider = "baidu";
/**
* 百度 OCR 配置
*/
private BaiduOcrClientConfig baidu;
}

View File

@@ -0,0 +1,53 @@
package cn.iocoder.yudao.module.ocr.framework.ocr.core.client;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.module.ocr.enums.ErrorCodeConstants;
import lombok.extern.slf4j.Slf4j;
/**
* OCR 客户端的抽象类,提供模板方法
*/
@Slf4j
public abstract class AbstractOcrClient<Config extends OcrClientConfig> implements OcrClient {
/**
* 配置
*/
protected Config config;
public AbstractOcrClient(Config config) {
this.config = config;
}
/**
* 初始化
*/
public final void init() {
doInit();
log.debug("[init][OCR 客户端({}) 初始化完成]", config.getProvider());
}
/**
* 自定义初始化
*/
protected abstract void doInit();
@Override
public OcrClientConfig getConfig() {
return config;
}
/**
* 校验图片数据
*/
protected void validateImageData(byte[] imageData) {
if (imageData == null || imageData.length == 0) {
throw ServiceExceptionUtil.exception(ErrorCodeConstants.OCR_IMAGE_INVALID);
}
// 百度 OCR 限制 4MB
if (imageData.length > 4 * 1024 * 1024) {
throw ServiceExceptionUtil.exception(ErrorCodeConstants.OCR_IMAGE_TOO_LARGE);
}
}
}

View File

@@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.ocr.framework.ocr.core.client;
import cn.iocoder.yudao.module.ocr.framework.ocr.core.result.VehicleLicenseResult;
/**
* OCR 客户端接口
*/
public interface OcrClient {
/**
* 获取客户端配置
*/
OcrClientConfig getConfig();
/**
* 识别行驶证
*
* @param imageData 图片数据(字节数组)
* @return 识别结果
*/
VehicleLicenseResult recognizeVehicleLicense(byte[] imageData);
}

View File

@@ -0,0 +1,15 @@
package cn.iocoder.yudao.module.ocr.framework.ocr.core.client;
import lombok.Data;
/**
* OCR 客户端配置接口
*/
public interface OcrClientConfig {
/**
* 获取厂商编码
*/
String getProvider();
}

View File

@@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.ocr.framework.ocr.core.client;
/**
* OCR 客户端工厂接口
*/
public interface OcrClientFactory {
/**
* 获取默认的 OCR 客户端
*/
OcrClient getDefaultClient();
/**
* 根据厂商获取 OCR 客户端
*
* @param provider 厂商编码
*/
OcrClient getClient(String provider);
}

View File

@@ -0,0 +1,79 @@
package cn.iocoder.yudao.module.ocr.framework.ocr.core.client;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.module.ocr.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.ocr.framework.ocr.core.client.impl.BaiduOcrClient;
import cn.iocoder.yudao.module.ocr.framework.ocr.core.client.impl.BaiduOcrClientConfig;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* OCR 客户端工厂实现类
*/
@Slf4j
public class OcrClientFactoryImpl implements OcrClientFactory {
/**
* OCR 客户端 Map
* key厂商编码
*/
private final ConcurrentMap<String, OcrClient> clients = new ConcurrentHashMap<>();
/**
* 默认厂商
*/
private String defaultProvider;
public OcrClientFactoryImpl(String defaultProvider) {
this.defaultProvider = defaultProvider;
}
@Override
public OcrClient getDefaultClient() {
return getClient(defaultProvider);
}
@Override
public OcrClient getClient(String provider) {
OcrClient client = clients.get(provider);
if (client == null) {
log.error("[getClient][厂商({}) 找不到客户端]", provider);
throw ServiceExceptionUtil.exception(ErrorCodeConstants.OCR_PROVIDER_NOT_SUPPORTED);
}
return client;
}
/**
* 创建或更新客户端
*/
public <Config extends OcrClientConfig> void createOrUpdateClient(String provider, Config config) {
OcrClient client = clients.get(provider);
if (client == null) {
client = createClient(provider, config);
if (client instanceof AbstractOcrClient) {
((AbstractOcrClient<?>) client).init();
}
clients.put(provider, client);
log.info("[createOrUpdateClient][创建 OCR 客户端成功,厂商:{}]", provider);
}
}
/**
* 创建客户端
*/
@SuppressWarnings("unchecked")
private <Config extends OcrClientConfig> OcrClient createClient(String provider, Config config) {
switch (provider) {
case "baidu":
Assert.isInstanceOf(BaiduOcrClientConfig.class, config, "百度 OCR 配置类型错误");
return new BaiduOcrClient((BaiduOcrClientConfig) config);
// 预留其他厂商
default:
throw ServiceExceptionUtil.exception(ErrorCodeConstants.OCR_PROVIDER_NOT_SUPPORTED);
}
}
}

View File

@@ -0,0 +1,169 @@
package cn.iocoder.yudao.module.ocr.framework.ocr.core.client.impl;
import cn.hutool.core.date.DateUtil;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.module.ocr.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.ocr.framework.ocr.core.client.AbstractOcrClient;
import cn.iocoder.yudao.module.ocr.framework.ocr.core.result.VehicleLicenseResult;
import com.baidu.aip.ocr.AipOcr;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONObject;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
/**
* 百度 OCR 客户端实现
*/
@Slf4j
public class BaiduOcrClient extends AbstractOcrClient<BaiduOcrClientConfig> {
private AipOcr client;
public BaiduOcrClient(BaiduOcrClientConfig config) {
super(config);
}
@Override
protected void doInit() {
this.client = new AipOcr(config.getAppId(), config.getApiKey(), config.getSecretKey());
// 设置超时时间
this.client.setConnectionTimeoutInMillis(5000);
this.client.setSocketTimeoutInMillis(30000);
}
@Override
public VehicleLicenseResult recognizeVehicleLicense(byte[] imageData) {
// 校验图片
validateImageData(imageData);
try {
// 调用百度 OCR API
JSONObject response = client.vehicleLicense(imageData, null);
// 检查错误
if (response.has("error_code")) {
log.error("[recognizeVehicleLicense][百度 OCR 识别失败,错误码:{},错误信息:{}]",
response.getInt("error_code"), response.getString("error_msg"));
throw ServiceExceptionUtil.exception(ErrorCodeConstants.OCR_RECOGNIZE_FAILED);
}
// 解析结果
return parseVehicleLicenseResult(response);
} catch (Exception e) {
log.error("[recognizeVehicleLicense][百度 OCR 识别异常]", e);
throw ServiceExceptionUtil.exception(ErrorCodeConstants.OCR_RECOGNIZE_FAILED);
}
}
/**
* 解析行驶证识别结果
*/
private VehicleLicenseResult parseVehicleLicenseResult(JSONObject response) {
VehicleLicenseResult result = new VehicleLicenseResult();
JSONObject wordsResult = response.getJSONObject("words_result");
if (wordsResult == null) {
return result;
}
// 车辆识别代号
if (wordsResult.has("车辆识别代号")) {
result.setVin(wordsResult.getJSONObject("车辆识别代号").getString("words"));
}
// 号牌号码
if (wordsResult.has("号牌号码")) {
result.setPlateNo(wordsResult.getJSONObject("号牌号码").getString("words"));
}
// 品牌型号
if (wordsResult.has("品牌型号")) {
result.setBrand(wordsResult.getJSONObject("品牌型号").getString("words"));
}
// 车辆类型
if (wordsResult.has("车辆类型")) {
result.setVehicleType(wordsResult.getJSONObject("车辆类型").getString("words"));
}
// 所有人
if (wordsResult.has("所有人")) {
result.setOwner(wordsResult.getJSONObject("所有人").getString("words"));
}
// 使用性质
if (wordsResult.has("使用性质")) {
result.setUseCharacter(wordsResult.getJSONObject("使用性质").getString("words"));
}
// 发动机号码
if (wordsResult.has("发动机号码")) {
result.setEngineNo(wordsResult.getJSONObject("发动机号码").getString("words"));
}
// 注册日期
if (wordsResult.has("注册日期")) {
String registerDate = wordsResult.getJSONObject("注册日期").getString("words");
result.setRegisterDate(parseDate(registerDate));
}
// 发证日期
if (wordsResult.has("发证日期")) {
String issueDate = wordsResult.getJSONObject("发证日期").getString("words");
result.setIssueDate(parseDate(issueDate));
}
// 检验记录
if (wordsResult.has("检验记录")) {
result.setInspectionRecord(wordsResult.getJSONObject("检验记录").getString("words"));
}
// 强制报废期止
if (wordsResult.has("强制报废期止")) {
String scrapDate = wordsResult.getJSONObject("强制报废期止").getString("words");
result.setScrapDate(parseDate(scrapDate));
}
// 整备质量
if (wordsResult.has("整备质量")) {
result.setCurbWeight(wordsResult.getJSONObject("整备质量").getString("words"));
}
// 总质量
if (wordsResult.has("总质量")) {
result.setTotalMass(wordsResult.getJSONObject("总质量").getString("words"));
}
// 核定载人数
if (wordsResult.has("核定载人数")) {
result.setApprovedPassengerCapacity(wordsResult.getJSONObject("核定载人数").getString("words"));
}
return result;
}
/**
* 解析日期字符串
*/
private LocalDate parseDate(String dateStr) {
if (dateStr == null || dateStr.trim().isEmpty()) {
return null;
}
try {
// 百度 OCR 返回格式2020-01-01 或 20200101
dateStr = dateStr.replaceAll("[年月]", "-").replaceAll("", "");
if (dateStr.length() == 8 && !dateStr.contains("-")) {
// 20200101 格式
return LocalDate.parse(dateStr, DateTimeFormatter.ofPattern("yyyyMMdd"));
} else {
// 2020-01-01 格式
return LocalDate.parse(dateStr, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
}
} catch (Exception e) {
log.warn("[parseDate][日期解析失败:{}]", dateStr, e);
return null;
}
}
}

View File

@@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.ocr.framework.ocr.core.client.impl;
import cn.iocoder.yudao.module.ocr.framework.ocr.core.client.OcrClientConfig;
import lombok.Data;
import jakarta.validation.constraints.NotEmpty;
/**
* 百度 OCR 客户端配置
*/
@Data
public class BaiduOcrClientConfig implements OcrClientConfig {
/**
* 应用 ID
*/
@NotEmpty(message = "百度 OCR AppId 不能为空")
private String appId;
/**
* API Key
*/
@NotEmpty(message = "百度 OCR ApiKey 不能为空")
private String apiKey;
/**
* Secret Key
*/
@NotEmpty(message = "百度 OCR SecretKey 不能为空")
private String secretKey;
@Override
public String getProvider() {
return "baidu";
}
}

View File

@@ -0,0 +1,83 @@
package cn.iocoder.yudao.module.ocr.framework.ocr.core.result;
import lombok.Data;
import java.time.LocalDate;
/**
* 行驶证识别结果
*/
@Data
public class VehicleLicenseResult {
/**
* 车辆识别代号VIN
*/
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;
/**
* 整备质量kg
*/
private String curbWeight;
/**
* 总质量kg
*/
private String totalMass;
/**
* 核定载人数
*/
private String approvedPassengerCapacity;
}

View File

@@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.ocr.service.ocr;
import cn.iocoder.yudao.module.ocr.framework.ocr.core.result.VehicleLicenseResult;
/**
* OCR 服务接口
*/
public interface OcrService {
/**
* 识别行驶证
*
* @param imageData 图片数据
* @return 识别结果
*/
VehicleLicenseResult recognizeVehicleLicense(byte[] imageData);
/**
* 识别行驶证(指定厂商)
*
* @param imageData 图片数据
* @param provider 厂商编码
* @return 识别结果
*/
VehicleLicenseResult recognizeVehicleLicense(byte[] imageData, String provider);
}

View File

@@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.ocr.service.ocr;
import cn.hutool.core.util.StrUtil;
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.stereotype.Service;
import jakarta.annotation.Resource;
/**
* OCR 服务实现类
*/
@Service
@Slf4j
public class OcrServiceImpl implements OcrService {
@Resource
private OcrClientFactory ocrClientFactory;
@Override
public VehicleLicenseResult recognizeVehicleLicense(byte[] imageData) {
// 使用默认厂商
OcrClient client = ocrClientFactory.getDefaultClient();
return client.recognizeVehicleLicense(imageData);
}
@Override
public VehicleLicenseResult recognizeVehicleLicense(byte[] imageData, String provider) {
// 使用指定厂商
OcrClient client = StrUtil.isNotBlank(provider)
? ocrClientFactory.getClient(provider)
: ocrClientFactory.getDefaultClient();
return client.recognizeVehicleLicense(imageData);
}
}

View File

@@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.iocoder.yudao.module.ocr.framework.ocr.config.OcrAutoConfiguration

View File

@@ -0,0 +1,35 @@
spring:
application:
name: ocr-server
profiles:
active: dev
# 允许 Bean 覆盖
main:
allow-bean-definition-overriding: true
config:
import:
- optional:nacos:common-dev.yaml
- optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml
cloud:
nacos:
server-addr: ${NACOS_ADDR:localhost:8848}
namespace: ${NACOS_NAMESPACE:dev}
username: ${NACOS_USERNAME:nacos}
password: ${NACOS_PASSWORD:nacos}
discovery:
namespace: ${NACOS_NAMESPACE:dev}
config:
namespace: ${NACOS_NAMESPACE:dev}
file-extension: yaml
# OCR 配置
ocr:
default-provider: baidu
baidu:
app-id: ${OCR_BAIDU_APP_ID:7506572}
api-key: ${OCR_BAIDU_API_KEY:wnQhaotHBbq9LRf8LbuNDNiK}
secret-key: ${OCR_BAIDU_SECRET_KEY:wzORcuENPSwBJxh0zrUwH1s05tA9vEfq}

View File

@@ -0,0 +1,65 @@
package cn.iocoder.yudao.module.ocr.framework.ocr.core.client.impl;
import cn.iocoder.yudao.module.ocr.framework.ocr.core.result.VehicleLicenseResult;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
/**
* 百度 OCR 本地测试
*/
@Slf4j
public class BaiduOcrClientLocalTest {
public static void main(String[] args) throws IOException {
// 配置百度 OCR
BaiduOcrClientConfig config = new BaiduOcrClientConfig();
config.setAppId("7506572");
config.setApiKey("wnQhaotHBbq9LRf8LbuNDNiK");
config.setSecretKey("wzORcuENPSwBJxh0zrUwH1s05tA9vEfq");
// 创建客户端
BaiduOcrClient client = new BaiduOcrClient(config);
client.init();
log.info("百度 OCR 客户端初始化成功");
// 读取测试图片
String imagePath = "/Users/kkfluous/Projects/ai-coding/ln-oneos/ext/a55d670c0f36be8f8a0f713c3fcd4091.jpg";
byte[] imageData = Files.readAllBytes(Paths.get(imagePath));
log.info("读取图片成功,大小:{} bytes", imageData.length);
// 执行识别
log.info("开始识别行驶证...");
long startTime = System.currentTimeMillis();
try {
VehicleLicenseResult result = client.recognizeVehicleLicense(imageData);
long costTime = System.currentTimeMillis() - startTime;
log.info("识别成功!耗时:{} ms", costTime);
log.info("========== 识别结果 ==========");
log.info("车辆识别代号VIN: {}", result.getVin());
log.info("号牌号码: {}", result.getPlateNo());
log.info("品牌型号: {}", result.getBrand());
log.info("车辆类型: {}", result.getVehicleType());
log.info("所有人: {}", result.getOwner());
log.info("使用性质: {}", result.getUseCharacter());
log.info("发动机号码: {}", result.getEngineNo());
log.info("注册日期: {}", result.getRegisterDate());
log.info("发证日期: {}", result.getIssueDate());
log.info("检验记录: {}", result.getInspectionRecord());
log.info("强制报废期止: {}", result.getScrapDate());
log.info("整备质量: {}", result.getCurbWeight());
log.info("总质量: {}", result.getTotalMass());
log.info("核定载人数: {}", result.getApprovedPassengerCapacity());
log.info("==============================");
} catch (Exception e) {
log.error("识别失败", e);
}
}
}

View File

@@ -0,0 +1,49 @@
package cn.iocoder.yudao.module.ocr.framework.ocr.core.client.impl;
import cn.iocoder.yudao.module.ocr.framework.ocr.core.result.VehicleLicenseResult;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import static org.junit.jupiter.api.Assertions.*;
/**
* 百度 OCR 客户端测试
*
* 注意:此测试需要真实的百度 OCR 凭证和测试图片,默认禁用
*/
@Slf4j
@Disabled("需要配置真实的百度 OCR 凭证")
class BaiduOcrClientTest {
@Test
void testRecognizeVehicleLicense() throws IOException {
// 配置百度 OCR
BaiduOcrClientConfig config = new BaiduOcrClientConfig();
config.setAppId("your-app-id");
config.setApiKey("your-api-key");
config.setSecretKey("your-secret-key");
// 创建客户端
BaiduOcrClient client = new BaiduOcrClient(config);
client.init();
// 读取测试图片
byte[] imageData = Files.readAllBytes(Paths.get("path/to/vehicle-license.jpg"));
// 执行识别
VehicleLicenseResult result = client.recognizeVehicleLicense(imageData);
// 验证结果
assertNotNull(result);
assertNotNull(result.getVin());
assertNotNull(result.getPlateNo());
log.info("识别结果VIN={}, 车牌号={}", result.getVin(), result.getPlateNo());
}
}