百度OCR:证件识别
目录
一、编写目的
二、准备工作
2.1 OCR密钥
三、代码实现
3.1 配置文件
3.2 请求接收封装
3.3 请求响应封装
3.4 服务类参数初始化
3.5 服务类实现
3.6 解析结果
3.7 定义Web接口
四 测试效果
五、总结
欢迎来到盹猫🐱的博客
本篇文章主要介绍了
[百度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: APPIDapi_key: API_KEYsecret_key: SECRET_KEYaccess_token_url: https://aip.baidubce.com/oauth/2.0/tokenocr: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 实例*/@PostConstructpublic 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,内容如下:
@Overridepublic Object recognizeImage(BaiduOcrRequestDTO requestDTO) throws IOException {try {// 获取访问令牌String accessToken = getAccessToken();// 构建请求 URL 和 BodyString 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 {@AutowiredOcrService 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示例代码,但上述代码对数据进行了更好的解析和封装。如果你需要身份证和银行卡信息识别,可以直接拿来使用。希望可以帮到你。
如果你对区块链内容感兴趣可以查看我的专栏:小试牛刀-区块链
感谢您的关注和收藏!!!!!!