百度图片识别自定义实现(替代AipOcr)

package com.visy.service;

import com.baidu.aip.util.Base64Util;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.visy.exception.ErrorEnum;
import com.visy.exception.BusinessException;
import com.visy.config.BaiduConfig;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 百度图片识别自定义实现(替代AipOcr)
 * @author visy.wang
 * @date 2022/1/19 9:24
 */
@Slf4j
@Service
public class AipOcrClient {
    private static long expireTime = 0L; //token过期时间戳
    private static String accessToken = null; //token值

    @Autowired
    private BaiduConfig baiduConfig; //百度认证信息,配置自己的apiKey和secretKey
    @Autowired
    private RestTemplate restTemplate;

    //Url管理枚举
    @Getter
    @AllArgsConstructor
    enum UrlKey {
        ACCESS_TOKEN("https://aip.baidubce.com/oauth/2.0/token", "认证地址"),
        DRIVING_LICENSE("https://aip.baidubce.com/rest/2.0/ocr/v1/driving_license", "驾驶证识别地址"),
        VEHICLE_LICENSE("https://aip.baidubce.com/rest/2.0/ocr/v1/vehicle_license", "行驶证识别地址");

        private final String url;
        private final String desc;
    }


    /**
     * 驾驶证识别
     * @param image 图片(二进制)
     * @param options 其他参数
     * @return 识别结果
     */
    public JSONObject drivingLicense(byte[] image, HashMap<String,String> options){
        options.put("image", Base64Util.encode(image));
        return request(UrlKey.DRIVING_LICENSE, options);
    }

    /**
     * 驾驶证识别
     * @param imageUrl 图片地址
     * @param options 其他参数
     * @return 识别结果
     */
    public JSONObject drivingLicense(String imageUrl, HashMap<String,String> options){
        options.put("url", imageUrl);
        return request(UrlKey.DRIVING_LICENSE, options);
    }

    /**
     * 行驶证识别
     * @param image 图片(二进制)
     * @param options 其他参数
     * @return 识别结果
     */
    public JSONObject vehicleLicense(byte[] image, HashMap<String,String> options){
        options.put("image", Base64Util.encode(image));
        return request(UrlKey.VEHICLE_LICENSE, options);
    }

    /**
     * 行驶证识别
     * @param imageUrl 图片地址
     * @param options 其他参数
     * @return 识别结果
     */
    public JSONObject vehicleLicense(String imageUrl, HashMap<String,String> options){
        options.put("url", imageUrl);
        return request(UrlKey.VEHICLE_LICENSE, options);
    }

    //带Token过期检查的request,暂不需要,现已计算过期时间并在失效前重新获取
    private JSONObject requestWithTokenCheck(UrlKey urlKey, Map<String,String> options){
        JSONObject res = request(urlKey, options);
        if(res.has("error_code")){
            int errCode = res.getInt("error_code");
            if(errCode==110 || errCode==111){
                accessToken = null; //Token失效,清除并重新获取
                return request(urlKey, options);
            }
        }
        return res;
    }

    /**
     * 请求
     * @param urlKey url地址枚举
     * @param options 其他参数
     * @return 识别结果
     */
    private JSONObject request(UrlKey urlKey, Map<String,String> options){
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

        String url = urlKey.getUrl();
        MultiValueMap<String, String> queryStr = getQueryStr(urlKey);
        UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(url).queryParams(queryStr);

        MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();
        if(Objects.nonNull(options) && !options.isEmpty()){
            options.forEach(formData::add);
        }

        HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(formData, headers);

        ResponseEntity<String> res = restTemplate.postForEntity(uriBuilder.toUriString(), request, String.class);
        if(!HttpStatus.OK.equals(res.getStatusCode())){
            throw buzError("请求失败:httpStatus=" + res.getStatusCode());
        }

        String resBody = res.getBody();

        if(StringUtils.isBlank(resBody)){
            throw buzError("响应体为空");
        }

        return new JSONObject(resBody);
    }

    private MultiValueMap<String, String> getQueryStr(UrlKey urlKey){
        MultiValueMap<String, String> queryStr = new LinkedMultiValueMap<>();
        if(UrlKey.ACCESS_TOKEN.equals(urlKey)){
            queryStr.add("grant_type", "client_credentials");
            queryStr.add("client_id", baiduConfig.getApiKey());
            queryStr.add("client_secret", baiduConfig.getSecretKey());
        }else{
            queryStr.add("access_token", getAccessToken()); //添加认证信息
        }
        return queryStr;
    }

    /**
     * 获取AccessToken
     * @return AccessToken
     */
    private String getAccessToken(){
        long now = System.currentTimeMillis();

        if(StringUtils.isNotBlank(accessToken) && now<expireTime){
            //有缓存并在有效期内,直接使用
            return accessToken;
        }

        try{
            return getAccessToken(now);
        }catch (Exception e){
            throw buzError("AccessToken获取失败:" + e.getMessage());
        }
    }

    private String getAccessToken(long now){
        log.info("正在重新获取AccessToken...");

        JSONObject body = request(UrlKey.ACCESS_TOKEN, null);

        if(body.has("error")){
            throw buzError(body.getString("error_description"));
        }

        int tokenExpires = body.getInt("expires_in"); //有效期,单位秒,默认30天
        expireTime = now + (tokenExpires-86400L)*1000L; //提前1天失效
        accessToken = body.getString("access_token"); //缓存token
        log.info("认证成功,有效期至:{}", new Date(expireTime));

        return accessToken;
    }

    /**
     * 抛出业务异常
     * @param errMsg 错误信息
     * @return 业务异常
     */
    private BusinessException buzError(String errMsg){
        //BusinessException: 自定义业务异常
        return new BusinessException(ErrorEnum.BUSINESS_ERR.getCode(), errMsg);
    }
}

package com.visy.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * @author visy.wang
 * @date 2022/1/19 9:28
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "baidu")
public class BaiduConfig {
    private String appId;
    private String apiKey;
    private String secretKey;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值