百度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: 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示例代码,但上述代码对数据进行了更好的解析和封装。如果你需要身份证和银行卡信息识别,可以直接拿来使用。希望可以帮到你。

如果你对区块链内容感兴趣可以查看我的专栏:小试牛刀-区块链

感谢您的关注和收藏!!!!!!

        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

盹猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值