基于华为云 Java SDK 实现身份证 OCR 识别接口实践
前言
最近在项目里落了一个身份证 OCR 识别接口,目标很明确:
- 前端只负责上传身份证图片
- 后端统一调用华为云 OCR
RecognizeIdCard- 对外返回本项目自己的统一结构
- 尽量把华为云 SDK 的复杂度封装在服务内部
这篇文章就是我这次接入的笔记总结。
一、官方资料
本次实现参考的是华为云官方身份证识别 API 文档:
二、实现目标
我最终落地的接口如下:
- 请求地址:
POST */ocr/id-card - 请求类型:
multipart/form-data - 请求参数:
file:身份证图片文件side:可选,front、back、double_side
- 返回结构:
R<IdCardOcrResultVO>
设计思路是:
- 前端传图片文件,不直接对接华为云
- 后端把图片转成 Base64,按官方 SDK 要求组装
IdCardRequestBody - 后端统一处理参数校验、异常转换、结果映射
- 前端永远只面对项目自己的返回结构
三、为什么选 Java SDK,而不是自己拼 HTTP
华为云身份证识别本质上可以通过 HTTP API 调用,但这里我直接用了官方 Java SDK,原因有三个:
- SDK 已经封装好了鉴权、区域和请求构造,少踩坑
- SDK 的请求模型和响应模型是强类型的,开发和维护成本更低
- 后续如果继续扩展营业执照、银行卡、通用文字识别,也能沿用同一套接入方式
这次实际用到的核心类有:
OcrClientBasicCredentialsOcrRegionRecognizeIdCardRequestIdCardRequestBodyRecognizeIdCardResponse
四、Maven 依赖
我在项目里接入的是华为云 OCR SDK 3.1.125 版本。
根 pom.xml 中统一版本:
<properties>
<huaweicloud.sdk.version>3.1.125</huaweicloud.sdk.version>
</properties>
业务模块 home-society-exam-main/pom.xml 中引入依赖:
<dependency>
<groupId>com.huaweicloud.sdk</groupId>
<artifactId>huaweicloud-sdk-core</artifactId>
<version>${huaweicloud.sdk.version}</version>
</dependency>
<dependency>
<groupId>com.huaweicloud.sdk</groupId>
<artifactId>huaweicloud-sdk-ocr</artifactId>
<version>${huaweicloud.sdk.version}</version>
</dependency>
五、配置方式
为了避免把 AK/SK/region 写死在业务代码里,我把华为云配置抽到了配置文件。
配置示例:
huawei:
ocr:
ak: your-ak
sk: your-sk
region: cn-north-4
说明:
ak:华为云访问密钥 AKsk:华为云访问密钥 SKregion:OCR 区域,例如cn-north-4
这里一定不要把真实密钥提交到仓库。
六、整体调用流程
整个身份证 OCR 接口的调用链很简单:
- Controller 接收
MultipartFile - Service 校验文件是否为空、大小是否超过 7MB、图片格式是否合法、
side是否合法 - 把图片字节转成 Base64
- 用
HuaweiOcrClientUtil获取华为云OcrClient - 组装
RecognizeIdCardRequest - 调用
client.recognizeIdCard(request) - 把华为云返回的正反面结果转换成项目自己的
VO - 统一返回
R.ok(...)或R.failed(...)
七、几个关键实现点
1. 为什么要把图片转成 Base64
因为华为云官方身份证识别支持两种常见输入方式:
image:图片 Base64url:图片公网地址
我这里选择的是 image 方式,因为项目的对外接口是文件上传接口,前端直接传文件最简单。后端收到 MultipartFile 之后转为 Base64,再交给华为云 SDK。
对应代码如下:
String base64Image = Base64.getEncoder().encodeToString(file.getBytes());
然后放进请求体:
IdCardRequestBody body = new IdCardRequestBody()
.withImage(base64Image)
.withReturnVerification(Boolean.TRUE)
.withDetectCopy(Boolean.TRUE)
.withDetectReproduce(Boolean.TRUE);
2. 为什么本地限制 7MB
华为云官方文档对图片大小有要求,而 Base64 会比原始图片更大一些,所以我在本地先拦截原图大小,避免无意义请求华为云。
private static final long MAX_FILE_SIZE = 7 * 1024 * 1024L;
3. 为什么 side 为空时不传
这是为了让华为云自动判断身份证正反面,提升接口容错性。
if (StrUtil.isNotBlank(side)) {
body.withSide(side);
}
如果调用方明确知道自己上传的是正面或反面,建议传 side,能减少识别歧义。
4. 为什么要做结果兼容
华为云在不同场景下返回结构可能存在差异,有时会放在 front/back,有时会直接放在顶层字段。所以我在代码里做了两层兼容:
- 优先解析
result.getFront()和result.getBack() - 如果没有,再尝试解析顶层字段
这样能减少因为返回结构轻微差异导致的空结果问题。
5. 为什么统一封装异常
华为云 SDK 会抛出多种异常:
ConnectionExceptionRequestTimeoutExceptionServiceResponseException
如果直接把异常抛给前端,前端处理会很乱。所以我在服务层统一转换成项目自己的失败消息,这样接口行为更稳定。
八、核心代码
下面附上这次接入的核心代码,都是我当前项目里实际使用的版本。
1. 华为云配置类
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* 华为云OCR配置
*
* @author 久睡成瘾
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "huawei.ocr")
public class HuaweiOcrProperties {
/**
* 华为云访问密钥AK
*/
private String ak;
/**
* 华为云访问密钥SK
*/
private String sk;
/**
* OCR服务区域,例如 cn-north-4
*/
private String region;
}
2. 华为云客户端工具类
import cn.hutool.core.util.StrUtil;
import com.huaweicloud.sdk.core.auth.BasicCredentials;
import com.huaweicloud.sdk.core.auth.ICredential;
import com.huaweicloud.sdk.ocr.v1.OcrClient;
import com.huaweicloud.sdk.ocr.v1.region.OcrRegion;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 华为云OCR客户端工具类
*
* @author 久睡成瘾
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class HuaweiOcrClientUtil {
private final HuaweiOcrProperties huaweiOcrProperties;
/**
* 懒加载缓存OCR客户端,避免重复创建连接配置
*/
private volatile OcrClient ocrClient;
/**
* 获取OCR客户端
*
* @return OCR客户端
*/
public OcrClient getClient() {
if (ocrClient == null) {
synchronized (this) {
if (ocrClient == null) {
ocrClient = this.buildClient();
}
}
}
return ocrClient;
}
/**
* 创建OCR客户端
*
* @return OCR客户端
*/
private OcrClient buildClient() {
this.checkProperties();
try {
ICredential auth = new BasicCredentials()
.withAk(huaweiOcrProperties.getAk())
.withSk(huaweiOcrProperties.getSk());
return OcrClient.newBuilder()
.withCredential(auth)
.withRegion(OcrRegion.valueOf(huaweiOcrProperties.getRegion()))
.build();
} catch (IllegalArgumentException e) {
log.error("初始化华为云OCR客户端失败,region配置不合法:{}", huaweiOcrProperties.getRegion(), e);
throw new IllegalStateException("华为云OCR区域配置不正确,请检查huawei.ocr.region", e);
} catch (Exception e) {
log.error("初始化华为云OCR客户端失败", e);
throw new IllegalStateException("初始化华为云OCR客户端失败", e);
}
}
/**
* 校验配置是否完整
*/
private void checkProperties() {
if (StrUtil.hasBlank(huaweiOcrProperties.getAk(), huaweiOcrProperties.getSk(), huaweiOcrProperties.getRegion())) {
throw new IllegalStateException("华为云OCR配置不完整,请检查huawei.ocr.ak、huawei.ocr.sk、huawei.ocr.region");
}
}
}
3. Service 接口
import org.springframework.web.multipart.MultipartFile;
/**
* 身份证OCR服务
*
* @author 久睡成瘾
*/
public interface IdCardOcrService {
/**
* 识别身份证图片
*
* @param file 身份证图片文件
* @param side 证件面,支持front、back、double_side
* @return 识别结果
*/
R<IdCardOcrResultVO> recognizeIdCard(MultipartFile file, String side);
}
4. Service 实现
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.huaweicloud.sdk.core.exception.ConnectionException;
import com.huaweicloud.sdk.core.exception.RequestTimeoutException;
import com.huaweicloud.sdk.core.exception.ServiceResponseException;
import com.huaweicloud.sdk.ocr.v1.model.IdCardRequestBody;
import com.huaweicloud.sdk.ocr.v1.model.IdCardResult;
import com.huaweicloud.sdk.ocr.v1.model.IdcardBackResult;
import com.huaweicloud.sdk.ocr.v1.model.IdcardBackVerificationResult;
import com.huaweicloud.sdk.ocr.v1.model.IdcardFrontResult;
import com.huaweicloud.sdk.ocr.v1.model.IdcardFrontVerificationResult;
import com.huaweicloud.sdk.ocr.v1.model.IdcardVerificationResult;
import com.huaweicloud.sdk.ocr.v1.model.RecognizeIdCardRequest;
import com.huaweicloud.sdk.ocr.v1.model.RecognizeIdCardResponse;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashSet;
import java.util.Set;
/**
* 身份证OCR服务实现
*
* @author 久睡成瘾
*/
@Slf4j
@Service
@AllArgsConstructor
public class IdCardOcrServiceImpl implements IdCardOcrService {
/**
* 官方建议原图不超过7MB,这里在本地先做一层拦截
*/
private static final long MAX_FILE_SIZE = 7 * 1024 * 1024L;
/**
* 允许上传的图片后缀
*/
private static final Set<String> ALLOWED_EXTENSIONS = new HashSet<>(Arrays.asList("jpg", "jpeg", "png", "bmp", "tif", "tiff"));
/**
* 允许上传的图片Content-Type
*/
private static final Set<String> ALLOWED_CONTENT_TYPES = new HashSet<>(Arrays.asList(
"image/jpg", "image/jpeg", "image/png", "image/bmp", "image/tif", "image/tiff", "image/x-tiff"
));
/**
* 允许的身份证面参数
*/
private static final Set<String> ALLOWED_SIDES = new HashSet<>(Arrays.asList("front", "back", "double_side"));
private final HuaweiOcrClientUtil huaweiOcrClientUtil;
/**
* 识别身份证图片
*
* @param file 身份证图片
* @param side 证件面
* @return 识别结果
*/
@Override
public R<IdCardOcrResultVO> recognizeIdCard(MultipartFile file, String side) {
if (file == null || file.isEmpty()) {
return R.failed("请上传身份证图片");
}
if (file.getSize() > MAX_FILE_SIZE) {
return R.failed("身份证图片大小不能超过7MB");
}
String normalizedSide = this.normalizeSide(side);
if (StrUtil.isNotBlank(normalizedSide) && !ALLOWED_SIDES.contains(normalizedSide)) {
return R.failed("side参数仅支持front、back、double_side");
}
if (!this.isSupportedImage(file)) {
return R.failed("仅支持jpg、jpeg、png、bmp、tiff格式的身份证图片");
}
try {
String base64Image = Base64.getEncoder().encodeToString(file.getBytes());
RecognizeIdCardRequest request = this.buildRequest(base64Image, normalizedSide);
RecognizeIdCardResponse response = huaweiOcrClientUtil.getClient().recognizeIdCard(request);
IdCardOcrResultVO resultVO = this.buildResult(response == null ? null : response.getResult(), normalizedSide);
if (resultVO == null || (resultVO.getFront() == null && resultVO.getBack() == null)) {
return R.failed("身份证识别失败,未获取到有效识别结果");
}
return R.ok(resultVO, "身份证识别成功");
} catch (IllegalStateException e) {
log.error("身份证OCR配置异常:{}", e.getMessage());
return R.failed(e.getMessage());
} catch (ConnectionException e) {
log.error("调用华为云身份证OCR连接异常", e);
return R.failed("调用华为云身份证OCR失败,网络连接异常");
} catch (RequestTimeoutException e) {
log.error("调用华为云身份证OCR超时", e);
return R.failed("调用华为云身份证OCR失败,请求超时");
} catch (ServiceResponseException e) {
log.error("调用华为云身份证OCR服务异常,status:{}, requestId:{}, errorCode:{}, errorMsg:{}",
e.getHttpStatusCode(), e.getRequestId(), e.getErrorCode(), e.getErrorMsg(), e);
return R.failed("调用华为云身份证OCR失败:" + StrUtil.blankToDefault(e.getErrorMsg(), "服务异常"));
} catch (IOException e) {
log.error("读取身份证图片失败", e);
return R.failed("读取身份证图片失败");
} catch (Exception e) {
log.error("身份证OCR识别异常", e);
return R.failed("身份证识别失败,请稍后重试");
}
}
/**
* 组装华为云身份证识别请求体
*
* @param base64Image 图片Base64
* @param side 证件面
* @return 请求对象
*/
private RecognizeIdCardRequest buildRequest(String base64Image, String side) {
IdCardRequestBody body = new IdCardRequestBody()
.withImage(base64Image)
.withReturnVerification(Boolean.TRUE)
.withDetectCopy(Boolean.TRUE)
.withDetectReproduce(Boolean.TRUE);
// side为空时不传,让华为云自动判断识别面
if (StrUtil.isNotBlank(side)) {
body.withSide(side);
}
return new RecognizeIdCardRequest().withBody(body);
}
/**
* 构建统一返回结果
*
* @param result 华为云识别结果
* @param side 请求入参中的证件面
* @return 统一结果
*/
private IdCardOcrResultVO buildResult(IdCardResult result, String side) {
if (result == null) {
return null;
}
IdCardOcrResultVO resultVO = new IdCardOcrResultVO();
if (result.getFront() != null) {
resultVO.setFront(this.convertFront(result.getFront()));
}
if (result.getBack() != null) {
resultVO.setBack(this.convertBack(result.getBack()));
}
if (resultVO.getFront() != null || resultVO.getBack() != null) {
return resultVO;
}
// 兼容华为云单面识别直接返回顶层字段的情况
if (this.shouldBuildFront(result, side)) {
resultVO.setFront(this.convertFront(result));
}
if (this.shouldBuildBack(result, side)) {
resultVO.setBack(this.convertBack(result));
}
return resultVO;
}
/**
* 判断是否应按身份证正面解析
*
* @param result 华为云结果
* @param side 证件面参数
* @return 是否按正面处理
*/
private boolean shouldBuildFront(IdCardResult result, String side) {
if ("front".equals(side)) {
return true;
}
if ("back".equals(side)) {
return false;
}
return this.hasFrontData(result);
}
/**
* 判断是否应按身份证反面解析
*
* @param result 华为云结果
* @param side 证件面参数
* @return 是否按反面处理
*/
private boolean shouldBuildBack(IdCardResult result, String side) {
if ("back".equals(side)) {
return true;
}
if ("front".equals(side)) {
return false;
}
return this.hasBackData(result);
}
/**
* 判断顶层结果中是否包含正面核心字段
*
* @param result 华为云结果
* @return 是否包含正面字段
*/
private boolean hasFrontData(IdCardResult result) {
return StrUtil.isNotBlank(result.getName())
|| StrUtil.isNotBlank(result.getNumber())
|| StrUtil.isNotBlank(result.getAddress())
|| StrUtil.isNotBlank(result.getBirth())
|| StrUtil.isNotBlank(result.getEthnicity())
|| StrUtil.isNotBlank(result.getSex());
}
/**
* 判断顶层结果中是否包含反面核心字段
*
* @param result 华为云结果
* @return 是否包含反面字段
*/
private boolean hasBackData(IdCardResult result) {
return StrUtil.isNotBlank(result.getIssue())
|| StrUtil.isNotBlank(result.getValidFrom())
|| StrUtil.isNotBlank(result.getValidTo());
}
/**
* 转换身份证正面结果
*
* @param result 华为云正面结果
* @return 平台正面结果
*/
private IdCardFrontVO convertFront(IdcardFrontResult result) {
if (result == null) {
return null;
}
IdCardFrontVO frontVO = new IdCardFrontVO();
frontVO.setName(result.getName());
frontVO.setSex(result.getSex());
frontVO.setEthnicity(result.getEthnicity());
frontVO.setBirth(result.getBirth());
frontVO.setAddress(result.getAddress());
frontVO.setNumber(result.getNumber());
frontVO.setDetectCopyResult(result.getDetectCopyResult());
frontVO.setDetectReproduceResult(result.getDetectReproduceResult());
frontVO.setVerificationResult(this.convertVerification(result.getVerificationResult()));
return frontVO;
}
/**
* 转换身份证正面结果
*
* @param result 华为云顶层结果
* @return 平台正面结果
*/
private IdCardFrontVO convertFront(IdCardResult result) {
if (result == null) {
return null;
}
IdCardFrontVO frontVO = new IdCardFrontVO();
frontVO.setName(result.getName());
frontVO.setSex(result.getSex());
frontVO.setEthnicity(result.getEthnicity());
frontVO.setBirth(result.getBirth());
frontVO.setAddress(result.getAddress());
frontVO.setNumber(result.getNumber());
frontVO.setDetectCopyResult(result.getDetectCopyResult());
frontVO.setDetectReproduceResult(result.getDetectReproduceResult());
frontVO.setVerificationResult(this.convertVerification(result.getVerificationResult()));
return frontVO;
}
/**
* 转换身份证反面结果
*
* @param result 华为云反面结果
* @return 平台反面结果
*/
private IdCardBackVO convertBack(IdcardBackResult result) {
if (result == null) {
return null;
}
IdCardBackVO backVO = new IdCardBackVO();
backVO.setIssue(result.getIssue());
backVO.setValidFrom(result.getValidFrom());
backVO.setValidTo(result.getValidTo());
backVO.setDetectCopyResult(result.getDetectCopyResult());
backVO.setDetectReproduceResult(result.getDetectReproduceResult());
backVO.setVerificationResult(this.convertVerification(result.getVerificationResult()));
return backVO;
}
/**
* 转换身份证反面结果
*
* @param result 华为云顶层结果
* @return 平台反面结果
*/
private IdCardBackVO convertBack(IdCardResult result) {
if (result == null) {
return null;
}
IdCardBackVO backVO = new IdCardBackVO();
backVO.setIssue(result.getIssue());
backVO.setValidFrom(result.getValidFrom());
backVO.setValidTo(result.getValidTo());
backVO.setDetectCopyResult(result.getDetectCopyResult());
backVO.setDetectReproduceResult(result.getDetectReproduceResult());
backVO.setVerificationResult(this.convertVerification(result.getVerificationResult()));
return backVO;
}
/**
* 转换通用校验结果
*
* @param result 华为云校验结果
* @return 平台校验结果
*/
private IdCardVerificationVO convertVerification(IdcardVerificationResult result) {
if (result == null) {
return null;
}
IdCardVerificationVO verificationVO = new IdCardVerificationVO();
verificationVO.setValidNumber(result.getValidNumber());
verificationVO.setValidBirth(result.getValidBirth());
verificationVO.setValidSex(result.getValidSex());
verificationVO.setValidDate(result.getValidDate());
verificationVO.setValidValidityPeriod(result.getValidValidityPeriod());
return verificationVO;
}
/**
* 转换正面校验结果
*
* @param result 华为云校验结果
* @return 平台校验结果
*/
private IdCardVerificationVO convertVerification(IdcardFrontVerificationResult result) {
if (result == null) {
return null;
}
IdCardVerificationVO verificationVO = new IdCardVerificationVO();
verificationVO.setValidNumber(result.getValidNumber());
verificationVO.setValidBirth(result.getValidBirth());
verificationVO.setValidSex(result.getValidSex());
return verificationVO;
}
/**
* 转换反面校验结果
*
* @param result 华为云校验结果
* @return 平台校验结果
*/
private IdCardVerificationVO convertVerification(IdcardBackVerificationResult result) {
if (result == null) {
return null;
}
IdCardVerificationVO verificationVO = new IdCardVerificationVO();
verificationVO.setValidDate(result.getValidDate());
verificationVO.setValidValidityPeriod(result.getValidValidityPeriod());
return verificationVO;
}
/**
* 归一化side参数
*
* @param side 原始side参数
* @return 去空格后的side
*/
private String normalizeSide(String side) {
if (StrUtil.isBlank(side)) {
return null;
}
return StrUtil.trim(side).toLowerCase();
}
/**
* 校验文件是否为支持的图片格式
*
* @param file 上传文件
* @return 是否支持
*/
private boolean isSupportedImage(MultipartFile file) {
String originalFilename = file.getOriginalFilename();
String extension = originalFilename == null ? null : FileUtil.extName(originalFilename);
if (StrUtil.isNotBlank(extension) && ALLOWED_EXTENSIONS.contains(extension.toLowerCase())) {
return true;
}
String contentType = StrUtil.blankToDefault(file.getContentType(), "").toLowerCase();
return ALLOWED_CONTENT_TYPES.contains(contentType);
}
}
5. Controller
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
/**
* 身份证OCR Controller
*
* @author 久睡成瘾
*/
@Api(value = "idCardOcr", tags = "身份证OCR识别")
@RestController
@Slf4j
@AllArgsConstructor
@RequestMapping("${controller.prefix}/${controller.exam.prefix}/ocr")
public class IdCardOcrController {
private final IdCardOcrService idCardOcrService;
/**
* 身份证图片识别接口
*
* @param file 身份证图片
* @param side 证件面
* @return 识别结果
*/
@ApiOperation("身份证图片识别")
@ApiImplicitParams({
@ApiImplicitParam(name = "file", value = "身份证图片文件", required = true, dataType = "__file", paramType = "form"),
@ApiImplicitParam(name = "side", value = "证件面,可选front、back、double_side", dataType = "string", paramType = "form")
})
@PostMapping("/id-card")
public R<IdCardOcrResultVO> recognizeIdCard(@RequestParam(value = "file", required = false) MultipartFile file,
@RequestParam(value = "side", required = false) String side) {
return idCardOcrService.recognizeIdCard(file, side);
}
}
6. 返回对象
IdCardOcrResultVO
@Data
public class IdCardOcrResultVO {
private IdCardFrontVO front;
private IdCardBackVO back;
}
IdCardFrontVO
@Data
public class IdCardFrontVO {
private String name;
private String sex;
private String ethnicity;
private String birth;
private String address;
private String number;
private Boolean detectReproduceResult;
private Boolean detectCopyResult;
private IdCardVerificationVO verificationResult;
}
IdCardBackVO
@Data
public class IdCardBackVO {
private String issue;
private String validFrom;
private String validTo;
private Boolean detectReproduceResult;
private Boolean detectCopyResult;
private IdCardVerificationVO verificationResult;
}
IdCardVerificationVO
@Data
public class IdCardVerificationVO {
private Boolean validNumber;
private Boolean validBirth;
private Boolean validSex;
private Boolean validDate;
private Boolean validValidityPeriod;
}
九、返回结果示例
正面成功返回示例:
{
"code": 0,
"msg": "身份证识别成功",
"data": {
"front": {
"name": "test",
"sex": "男",
"ethnicity": "汉",
"birth": "20**-0*-0*",
"address": "**********",
"number": "****************",
"detectReproduceResult": false,
"detectCopyResult": false,
"verificationResult": {
"validNumber": true,
"validBirth": true,
"validSex": true,
"validDate": false,
"validValidityPeriod": false
}
},
"back": null
}
}
反面成功返回示例:
{
"code": 0,
"msg": "身份证识别成功",
"data": {
"front": null,
"back": {
"issue": "*******",
"validFrom": "20**-0*-0*",
"validTo": "20**-0*-0*",
"detectReproduceResult": false,
"detectCopyResult": false,
"verificationResult": {
"validNumber": false,
"validBirth": false,
"validSex": false,
"validDate": true,
"validValidityPeriod": true
}
}
}
}
十、这次接入里几个值得记住的点
这次做完之后,我觉得有几个点特别值得记下来:
- 不要把云厂商接口直接暴露给前端,最好后端统一收口
- 文件上传场景下,
MultipartFile -> Base64 -> withImage(...)是很自然的做法 AK/SK/region一定要从配置中读取,不要写死在代码里- 异常一定要统一转换,不要把 SDK 原始异常直接暴露给前端
- 对返回结构做一层项目自己的
VO映射,后续换供应商时会轻松很多
十一、总结
从实现难度上看,华为云身份证 OCR 的 Java SDK 接入并不复杂,真正需要花心思的其实不是“怎么调 SDK”,而是:
- 怎么把调用方式封装得足够简单
- 怎么处理异常和边界情况
- 怎么把云厂商返回转换成项目自己的稳定接口
如果只是做一个能跑通的 Demo,很快就能完成。
但如果是要放进正式业务系统,配置隔离、参数校验、异常处理和结果映射这些工作,反而更关键。
如果这篇文章对你有帮助,请 点赞、收藏、关注 一波!你的支持是我持续输出高质量技术干货的最大动力!!!
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)