目录
欢迎来到盹猫🐱的博客
本篇文章主要介绍了
[百度OCR:证件识别]
❤博主广交技术好友,喜欢文章的可以关注一下❤
一、编写目的
本篇文章是记录SpringBoot调用百度OCR识别身份证和银行卡信息服务接口的实现步骤,通过测试,识别速度快,识别信息准确。该功能可以用在方便用户认证、注册、用户信息更新等方面,为方便日后使用和查询,在这里对实现流程进行记录,希望可以帮到有需要的开发者。
二、准备工作
2.1 OCR密钥
可以在OCR文字识别_免费试用_图片转文字-百度AI开放平台 进行账号的注册,如果已经有账号可以直接登录。点击立即使用进入百度控制台,开通[身份证识别]和[银行卡识别]两个功能:
这里有1000次的免费使用,当然付费该功能也很便宜。
在应用列表功能中,单击创建一个应用,创建一个包含OCR识别功能的应用,当然可以选取全部功能,这样所有功能就都可以使用了。
在应用列表中复制已创建应用的AppID、APIKey、Secret Key,在后续的application.yml配置文件中需要用到。
三、代码实现
3.1 配置文件
创建一个基础的SpringBoot项目,并在配置文件中将已申请的AppID、APIKey、Secret Key 添加到application.yml文件(没有则在resources目录下进行创建)中,由于百度是通过access_token进行接口调用,在这里将access_token的获取地址和OCR功能请求地址一并配置,具体内容如下:
baidu:
app_id: APPID
api_key: API_KEY
secret_key: SECRET_KEY
access_token_url: https://aip.baidubce.com/oauth/2.0/token
ocr:
base_url: https://aip.baidubce.com/rest/2.0/ocr/v1
3.2 请求接收封装
要实现的功能是用户可以对请求的识别的实体卡进行指定,同时可以指定识别正面和反面,所以这边先定期请求数据的接收(也就是接收POST的JSON数据),实体内容如下:
package com.uav.models;
import com.uav.common.validator.group.UpdateGroup;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
@Data
public class BaiduOcrRequestDTO {
/**正反面*/
@Pattern(regexp = "^(front|back)$",
message = "side 只能是 'front' 或 'back'",
groups = UpdateGroup.class) // 可选:分组校验
private String side;
@NotEmpty(message = "图片url不能为空")
private String url;
@Pattern(regexp = "^(idcard|bankcard)$",
message = "type 只能是 'idcard' 或 'bankcard'",
groups = UpdateGroup.class)
private String type;
}
3.3 请求响应封装
返回的数据可能是身份证,也可能是银行卡,所以这边需要定义两个请求响应的封装,内容如下:
银行卡响应
package com.uav.models;
import lombok.Data;
@Data
public class BankCardDTO {
private String validDate; // 有效期
private String bankCardNumber; // 银行卡号
private String bankName; // 银行名称
private int bankCardType; // 卡类型
private String holderName; // 持卡人姓名
}
身份证响应
package com.uav.models;
import lombok.Data;
@Data
public class OcrIdCardDTO {
private String name; // 姓名
private String nation; // 民族
private String address; // 住址
private String idNumber; // 公民身份号码
private String birthDate; // 出生日期
private String gender; // 性别
// 反面信息
private String expiryDate; // 失效日期
private String issuingAuthority; // 签发机关
private String issueDate; // 签发日期
}
3.4 服务类参数初始化
因为请求有两个类型,先进行识别类型枚举RecognizeType的定义,它可以在代码编写过程中减少硬编码,增加可维护性,内容如下:
/**
* 支持的识别类型枚举
*/
public enum RecognizeType {
IDCARD("idcard"),
BANKCARD("bankcard");
private final String type;
RecognizeType(String type) {
this.type = type;
}
public String getType() {
return type;
}
/**
* 根据类型字符串获取枚举值
*
* @param type 类型字符串
* @return 对应的枚举值,如果不存在则抛出异常
*/
public static RecognizeType fromString(String type) {
for (RecognizeType recognizeType : values()) {
if (recognizeType.getType().equalsIgnoreCase(type)) {
return recognizeType;
}
}
throw new IllegalArgumentException("不支持的识别类型: " + type);
}
}
同样的,我们需要用到Http请求,这里使用OKhttpClient进行请求,同时将之前定义的参数通过@Value进行注入,内容如下:
@Value("${baidu.api_key}")
private String API_KEY;
@Value("${baidu.secret_key}")
private String SECRET_KEY;
@Value("${baidu.access_token_url}")
private String ACCESS_TOKEN_URL;
@Value("${baidu.ocr.base_url}")
private String ORC_BASE_URL;
// 使用 final 确保线程安全,并在 @PostConstruct 中初始化
private OkHttpClient httpClient;
/**
* 初始化 OkHttpClient 实例
*/
@PostConstruct
public void init() {
httpClient = new OkHttpClient.Builder()
.readTimeout(300, TimeUnit.SECONDS)
.build();
}
3.5 服务类实现
在服务类的实现时,需要先获取百度的access_token然后进行OCR接口的请求,所以这里先定义一个获取access_token的方法,内容如下:
private String getAccessToken() throws IOException {
RequestBody body = RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"),
"grant_type=client_credentials&client_id=" + API_KEY + "&client_secret=" + SECRET_KEY);
Request request = new Request.Builder()
.url(ACCESS_TOKEN_URL)
.post(body)
.addHeader("Content-Type", "application/x-www-form-urlencoded")
.build();
try (Response response = httpClient.newCall(request).execute()) {
if (!response.isSuccessful()) {
log.error("获取访问令牌失败,状态码: {}, 响应: {}", response.code(), response.body() != null ? response.body().string() : "null");
throw new SysException("获取访问令牌失败,状态码: " + response.code());
}
String responseBody = Objects.requireNonNull(response.body()).string();
JSONObject jsonResponse = JSON.parseObject(responseBody);
if (!jsonResponse.containsKey("access_token")) {
log.error("访问令牌响应中缺少 access_token 字段: {}", responseBody);
throw new SysException("访问令牌响应格式错误");
}
return jsonResponse.getString("access_token");
} catch (Exception e) {
log.error("获取访问令牌过程中发生错误", e);
throw new SysException("获取访问令牌失败,请重试!", e);
}
}
然后开始编写识别图片方法,该方法的接收参数为一个请求封装的参数,即上面定义的BaiduOcrRequestDTO,内容如下:
@Override
public Object recognizeImage(BaiduOcrRequestDTO requestDTO) throws IOException {
try {
// 获取访问令牌
String accessToken = getAccessToken();
// 构建请求 URL 和 Body
String requestUrl = buildRequestUrl(requestDTO.getType(), accessToken);
String bodyContent = buildRequestBody(requestDTO);
RequestBody body = RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"), bodyContent);
// 构建请求
Request request = new Request.Builder()
.url(requestUrl)
.post(body)
.addHeader("Content-Type", "application/x-www-form-urlencoded")
.addHeader("Accept", "application/json")
.build();
// 发送请求并获取响应
Response response = httpClient.newCall(request).execute();
if (!response.isSuccessful()) {
log.error("OCR 请求失败,状态码: {}, 响应: {}", response.code(), response.body() != null ? response.body().string() : "null");
throw new SysException("OCR 请求失败,状态码: " + response.code());
}
String result = Objects.requireNonNull(response.body()).string();
log.debug("OCR 响应结果: {}", result);
// 解析结果
return new OcrParser().parseCardInfo(result, requestDTO.getType(), requestDTO.getSide());
} catch (Exception e) {
log.error("OCR 识别过程中发生错误,请求数据: {}", requestDTO, e);
throw new SysException("OCR 识别失败,请重试!", e);
}
}
3.6 解析结果
百度OCR返回的为json字符串,但是其中有很多并不需要的信息,这样返回到前端并不利用阅读和解析,同时占用大量带宽,所以这里使用自定义的OcrParser 工具类对响应的数据进行解析,将其转换为上面提到的请求响应参数格式,内容如下:
package com.uav.common.util;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.uav.common.exception.SysException;
import com.uav.models.BankCardDTO;
import com.uav.models.OcrIdCardDTO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
/**
* OCR 解析工具类,用于解析身份证和银行卡信息。
*/
@Slf4j
public class OcrParser {
/**
* 识别类型枚举
*/
public enum CardType {
IDCARD("idcard"),
BANKCARD("bankcard");
private final String type;
CardType(String type) {
this.type = type;
}
public String getType() {
return type;
}
/**
* 根据类型字符串获取枚举值
*
* @param type 类型字符串
* @return 对应的枚举值,如果不存在则抛出异常
*/
public static CardType fromString(String type) {
for (CardType cardType : values()) {
if (cardType.getType().equalsIgnoreCase(type)) {
return cardType;
}
}
throw new IllegalArgumentException("不支持的识别类型: " + type);
}
}
/**
* 解析卡片信息
*
* @param cardJson JSON 字符串
* @param type 识别类型("idcard" 或 "bankcard")
* @param side 仅对身份证有效,"front" 表示正面,其他表示反面
* @return 解析后的 DTO 对象
* @throws SysException 如果解析失败
*/
public Object parseCardInfo(String cardJson, String type, String side) {
try {
JSONObject root = JSON.parseObject(cardJson);
CardType cardType = CardType.fromString(type);
switch (cardType) {
case IDCARD:
return parseIdCardInfo(root, side);
case BANKCARD:
return parseBankCardInfo(root);
default:
throw new SysException("不支持的识别类型: " + type);
}
} catch (IllegalArgumentException e) {
throw new SysException("不支持的识别类型: " + type, e);
} catch (Exception e) {
log.error("解析卡片信息失败,JSON: {}, 类型: {}, 方向: {}", cardJson, type, side, e);
throw new SysException("识别解析格式错误,请重新尝试!", e);
}
}
/**
* 解析身份证信息
*
* @param root JSON 根对象
* @param side "front" 表示正面,其他表示反面
* @return OcrIdCardDTO 对象
* @throws SysException 如果解析失败
*/
private OcrIdCardDTO parseIdCardInfo(JSONObject root, String side) throws SysException {
JSONObject wordsResult = root.getJSONObject("words_result");
if (wordsResult == null) {
throw new SysException("JSON 中缺少 'words_result' 字段");
}
OcrIdCardDTO idCardInfo = new OcrIdCardDTO();
if (StringUtils.equalsIgnoreCase(side, "front")) {
parseFrontSide(wordsResult, idCardInfo);
} else {
parseBackSide(wordsResult, idCardInfo);
}
return idCardInfo;
}
/**
* 解析身份证正面信息
*
* @param wordsResult JSON 中的 words_result 对象
* @param idCardInfo 目标 DTO 对象
* @throws SysException 如果解析失败
*/
private void parseFrontSide(JSONObject wordsResult, OcrIdCardDTO idCardInfo) throws SysException {
try {
idCardInfo.setName(getWord(wordsResult, "姓名"));
idCardInfo.setNation(getWord(wordsResult, "民族"));
idCardInfo.setAddress(getWord(wordsResult, "住址"));
idCardInfo.setIdNumber(getWord(wordsResult, "公民身份号码"));
idCardInfo.setBirthDate(getWord(wordsResult, "出生"));
idCardInfo.setGender(getWord(wordsResult, "性别"));
} catch (Exception e) {
throw new SysException("解析身份证正面信息失败", e);
}
}
/**
* 解析身份证反面信息
*
* @param wordsResult JSON 中的 words_result 对象
* @param idCardInfo 目标 DTO 对象
* @throws SysException 如果解析失败
*/
private void parseBackSide(JSONObject wordsResult, OcrIdCardDTO idCardInfo) throws SysException {
try {
idCardInfo.setExpiryDate(getWord(wordsResult, "失效日期"));
idCardInfo.setIssuingAuthority(getWord(wordsResult, "签发机关"));
idCardInfo.setIssueDate(getWord(wordsResult, "签发日期"));
} catch (Exception e) {
throw new SysException("解析身份证反面信息失败", e);
}
}
/**
* 安全地从 words_result 中获取指定字段的 words 值
*
* @param wordsResult JSON 中的 words_result 对象
* @param fieldName 字段名称
* @return 对应的 words 值
* @throws SysException 如果字段不存在或解析失败
*/
private String getWord(JSONObject wordsResult, String fieldName) throws SysException {
JSONObject field = wordsResult.getJSONObject(fieldName);
if (field == null) {
throw new SysException("缺少字段: " + fieldName);
}
String words = field.getString("words");
if (StringUtils.isBlank(words)) {
throw new SysException("字段 '" + fieldName + "' 的 words 值为空");
}
return words;
}
/**
* 解析银行卡信息
*
* @param root JSON 根对象
* @return BankCardDTO 对象
* @throws SysException 如果解析失败
*/
private BankCardDTO parseBankCardInfo(JSONObject root) throws SysException {
try {
JSONObject result = root.getJSONObject("result");
if (result == null) {
throw new SysException("JSON 中缺少 'result' 字段");
}
BankCardDTO bankCardDTO = new BankCardDTO();
bankCardDTO.setValidDate(result.getString("valid_date"));
bankCardDTO.setBankCardNumber(result.getString("bank_card_number"));
bankCardDTO.setBankName(result.getString("bank_name"));
bankCardDTO.setBankCardType(result.getInteger("bank_card_type"));
bankCardDTO.setHolderName(result.getString("holder_name"));
// 可选:对银行卡号进行脱敏处理
if (StringUtils.isNotBlank(bankCardDTO.getBankCardNumber())) {
bankCardDTO.setBankCardNumber(bankCardDTO.getBankCardNumber());
}
return bankCardDTO;
} catch (Exception e) {
throw new SysException("解析银行卡信息失败", e);
}
}
/**
* 对银行卡号进行脱敏处理
*
* @param cardNumber 原始银行卡号
* @return 脱敏后的银行卡号
*/
private String maskBankCardNumber(String cardNumber) {
if (StringUtils.isBlank(cardNumber)) {
return cardNumber;
}
// 假设银行卡号为 16-19 位,保留前4位和后4位,中间用 * 替换
int length = cardNumber.length();
if (length <= 8) {
return "****"; // 如果长度不足,返回固定脱敏
}
return cardNumber.substring(0, 4) + "****" + cardNumber.substring(length - 4);
}
}
3.7 定义Web接口
定义一个OcrController接口,对前端页面发送的数据进行响应,直接调用已编写好的识别服务,内容如下:
package com.uav.controller.ocr;
import com.uav.common.util.Result;
import com.uav.common.validator.ValidatorUtils;
import com.uav.common.validator.group.UpdateGroup;
import com.uav.models.BaiduOcrRequestDTO;
import com.uav.service.OcrService;
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;
import org.springframework.web.bind.annotation.RestController;
import java.io.*;
@RequestMapping("/ocr")
@RestController
public class OcrController {
@Autowired
OcrService ocrService;
@PostMapping(value = "/idcard/recognize")
public Result<Object> recognizeImage(@RequestBody BaiduOcrRequestDTO requestDTO) throws IOException {
ValidatorUtils.validateEntity(requestDTO, UpdateGroup.class);
return new Result<>().ok(ocrService.recognizeImage(requestDTO));
}
}
四 测试效果
使用Apifox测试一下接口,效果图如下:
五、总结
上述内容即为百度OCR:证件识别的全部过程了,虽然百度官方也有API示例代码,但上述代码对数据进行了更好的解析和封装。如果你需要身份证和银行卡信息识别,可以直接拿来使用。希望可以帮到你。
如果你对区块链内容感兴趣可以查看我的专栏:小试牛刀-区块链
感谢您的关注和收藏!!!!!!