generated from dxfeng/secondev-wugang-dxfeng
feat(portal): 添加OCR识别身份证、银行卡功能
- 在 ApplicationConfigConstant 中添加 OCR 相关的配置常量 - 新增 BankCardRecognitionAction 类实现银行卡识别功能- 新增 IDCardRecognitionAction 类实现身份证识别功能 - 添加 DataConvertMapper 接口和对应的 XML 文件用于数据转换
This commit is contained in:
parent
b4835fc6a9
commit
01daf17c15
|
|
@ -0,0 +1,99 @@
|
|||
package com.weaver.seconddev.portal.action.entry;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.weaver.common.base.entity.result.WeaResult;
|
||||
import com.weaver.esb.api.rpc.EsbServerlessRpcRemoteInterface;
|
||||
import com.weaver.eteams.file.client.file.FileData;
|
||||
import com.weaver.eteams.file.client.file.FileObj;
|
||||
import com.weaver.file.ud.api.FileDownloadService;
|
||||
import com.weaver.seconddev.portal.constant.ApplicationConfigConstant;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author:dxfeng
|
||||
* @createTime: 2025/07/29
|
||||
* @version: 1.0
|
||||
*/
|
||||
@Slf4j
|
||||
@Service("bankCardRecognitionAction")
|
||||
public class BankCardRecognitionAction implements EsbServerlessRpcRemoteInterface {
|
||||
|
||||
@Autowired
|
||||
FileDownloadService fileDownloadService;
|
||||
|
||||
@Override
|
||||
public WeaResult<Map<String, Object>> execute(Map<String, Object> params) {
|
||||
Map<String, Object> returnMap = new HashMap<>();
|
||||
Long fileId = Convert.toLong(params.get("fileId"), null);
|
||||
log.error("fileId==" + fileId);
|
||||
if (null == fileId) {
|
||||
return WeaResult.success(returnMap);
|
||||
}
|
||||
FileData fileData = fileDownloadService.downloadFile(fileId);
|
||||
FileObj fileObj = fileData.getFileObj();
|
||||
InputStream inputStream = fileData.getInputStream();
|
||||
String fileName = fileObj.getName();
|
||||
log.error("fileName==" + fileName);
|
||||
try {
|
||||
log.error("inputStream==" + inputStream.available());
|
||||
} catch (IOException e) {
|
||||
log.error("inputStream获取异常", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
String response = callBankCardOcrApi(inputStream, fileName);
|
||||
log.error("response==" + response);
|
||||
// 正面响应数据
|
||||
JSONObject jsonObject = JSONObject.parseObject(response);
|
||||
if (jsonObject.getBoolean("isSuccess")) {
|
||||
JSONObject data = jsonObject.getJSONObject("data");
|
||||
JSONArray resultArray = data.getJSONArray("result");
|
||||
if (resultArray.size() > 0) {
|
||||
JSONObject result = resultArray.getJSONObject(0);
|
||||
returnMap.put("org", result.get("org"));
|
||||
returnMap.put("number", result.get("number"));
|
||||
returnMap.put("valid_thru", result.get("valid_thru"));
|
||||
returnMap.put("type", result.get("type"));
|
||||
returnMap.put("valid_from", result.get("valid_from"));
|
||||
returnMap.put("holder", result.get("holder"));
|
||||
}
|
||||
|
||||
}
|
||||
log.error("returnMap==" + JSON.toJSONString(returnMap));
|
||||
return WeaResult.success(returnMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用千里聆身份证识别接口
|
||||
*
|
||||
* @param inputStream 文件输入流
|
||||
* @param fileName 文件名
|
||||
* @return
|
||||
*/
|
||||
public static String callBankCardOcrApi(InputStream inputStream, String fileName) {
|
||||
byte[] bytes = IoUtil.readBytes(inputStream);
|
||||
long currentTime = System.currentTimeMillis();
|
||||
HttpResponse response = HttpRequest.post(ApplicationConfigConstant.BANK_CARD_OCR_URL)
|
||||
.header("Content-Type", "multipart/form-data")
|
||||
.header("sign", IDCardRecognitionAction.getSign(currentTime))
|
||||
.header("appId", ApplicationConfigConstant.OCR_APP_ID)
|
||||
.header("timestamp", String.valueOf(currentTime))
|
||||
.form("image_file", bytes, fileName)
|
||||
.execute();
|
||||
return response.body();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
package com.weaver.seconddev.portal.action.entry;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.weaver.common.base.entity.result.WeaResult;
|
||||
import com.weaver.esb.api.rpc.EsbServerlessRpcRemoteInterface;
|
||||
import com.weaver.eteams.file.client.file.FileData;
|
||||
import com.weaver.eteams.file.client.file.FileObj;
|
||||
import com.weaver.file.ud.api.FileDownloadService;
|
||||
import com.weaver.seconddev.portal.constant.ApplicationConfigConstant;
|
||||
import com.weaver.seconddev.portal.entity.param.BaseParam;
|
||||
import com.weaver.seconddev.portal.mapper.dictionary.DataConvertMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.Period;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author:dxfeng
|
||||
* @createTime: 2025/07/29
|
||||
* @version: 1.0
|
||||
*/
|
||||
@Slf4j
|
||||
@Service("idCardRecognitionAction")
|
||||
public class IDCardRecognitionAction implements EsbServerlessRpcRemoteInterface {
|
||||
|
||||
@Autowired
|
||||
FileDownloadService fileDownloadService;
|
||||
@Autowired
|
||||
DataConvertMapper dataConvertMapper;
|
||||
|
||||
BaseParam baseParam = new BaseParam();
|
||||
|
||||
@Override
|
||||
public WeaResult<Map<String, Object>> execute(Map<String, Object> params) {
|
||||
Map<String, Object> returnMap = new HashMap<>();
|
||||
Long fileId = Convert.toLong(params.get("fileId"), null);
|
||||
log.error("fileId==" + fileId);
|
||||
String nationalityConvert = Convert.toStr(params.get("nationalityConvert"), "");
|
||||
log.error("nationalityConvert==" + nationalityConvert);
|
||||
if (null == fileId) {
|
||||
return WeaResult.success(returnMap);
|
||||
}
|
||||
FileData fileData = fileDownloadService.downloadFile(fileId);
|
||||
FileObj fileObj = fileData.getFileObj();
|
||||
InputStream inputStream = fileData.getInputStream();
|
||||
String fileName = fileObj.getName();
|
||||
log.error("fileName==" + fileName);
|
||||
try {
|
||||
log.error("inputStream==" + inputStream.available());
|
||||
} catch (IOException e) {
|
||||
log.error("inputStream获取异常", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
String response = callIdCardOcrApi(inputStream, fileName);
|
||||
// 正面响应数据
|
||||
//String response = "{\"isSuccess\": true, \"data\": {\"page_num\": \"1\", \"result\": [{\"姓名\": \"王某某\", \"性别\": \"男\", \"民族\": \"汉\", \"出生\": \"1989年3月21日\", \"住址\": \"上海市浦东新区塘桥街道蓝村路xxx号\", \"公民身份号码\": \"370112198903217890\", \"标签\": \"头像面\"}]}, \"status_code\": 5200}";
|
||||
log.error("response==" + response);
|
||||
|
||||
JSONObject jsonObject = JSONObject.parseObject(response);
|
||||
if (jsonObject.getBoolean("isSuccess")) {
|
||||
JSONObject data = jsonObject.getJSONObject("data");
|
||||
JSONArray resultArray = data.getJSONArray("result");
|
||||
if (resultArray.size() > 0) {
|
||||
JSONObject result = resultArray.getJSONObject(0);
|
||||
returnMap.put("name", result.getString("姓名"));
|
||||
returnMap.put("sex", result.getString("性别"));
|
||||
String nation = result.getString("民族");
|
||||
if (StringUtils.isNotBlank(nation) && StringUtils.isNotBlank(nationalityConvert)) {
|
||||
Long nationId = dataConvertMapper.getIdByName(baseParam, "nation", nation + "族");
|
||||
log.error("nationId==" + nationId);
|
||||
nation = Convert.toStr(nationId, "");
|
||||
log.error("nation==" + nation);
|
||||
|
||||
}
|
||||
returnMap.put("nation", nation);
|
||||
|
||||
returnMap.put("birthday", Convert.toStr(result.getString("出生"), "").replace("年", "-").replace("月", "-").replace("日", "-"));
|
||||
returnMap.put("address", result.getString("住址"));
|
||||
String idNumber = result.getString("公民身份号码");
|
||||
returnMap.put("idCard", idNumber);
|
||||
// 根据身份证号,计算年龄、 性别
|
||||
if (StringUtils.isNotBlank(idNumber)) {
|
||||
returnMap.put("age", getAge(idNumber));
|
||||
returnMap.put("gender", getGender(idNumber));
|
||||
}
|
||||
|
||||
returnMap.put("issueAuthority", result.getString("签发机关"));
|
||||
returnMap.put("validity", result.getString("有效期限"));
|
||||
}
|
||||
}
|
||||
log.error("returnMap==" + JSON.toJSONString(returnMap));
|
||||
return WeaResult.success(returnMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用千里聆身份证识别接口
|
||||
*
|
||||
* @param inputStream 文件输入流
|
||||
* @param fileName 文件名
|
||||
* @return
|
||||
*/
|
||||
public static String callIdCardOcrApi(InputStream inputStream, String fileName) {
|
||||
byte[] bytes = IoUtil.readBytes(inputStream);
|
||||
long currentTime = System.currentTimeMillis();
|
||||
HttpResponse response = HttpRequest.post(ApplicationConfigConstant.ID_CARD_OCR_URL)
|
||||
.header("Content-Type", "multipart/form-data")
|
||||
.header("sign", getSign(currentTime))
|
||||
.header("appId", ApplicationConfigConstant.OCR_APP_ID)
|
||||
.header("timestamp", String.valueOf(currentTime))
|
||||
.form("img", bytes, fileName)
|
||||
.execute();
|
||||
return response.body();
|
||||
}
|
||||
|
||||
/**
|
||||
* 千里聆签名
|
||||
*
|
||||
* @param timestamp 当前时间戳(毫秒数)
|
||||
* @return
|
||||
*/
|
||||
public static String getSign(long timestamp) {
|
||||
try {
|
||||
MessageDigest md5 = MessageDigest.getInstance("MD5");
|
||||
md5.update(ApplicationConfigConstant.OCR_APP_ID.getBytes());
|
||||
md5.update((timestamp + "").getBytes());
|
||||
md5.update(ApplicationConfigConstant.OCR_APP_SECRET.getBytes());
|
||||
byte[] bytes = md5.digest();
|
||||
return (new BigInteger(1, bytes)).toString(16);
|
||||
} catch (NoSuchAlgorithmException var8) {
|
||||
throw new RuntimeException("不支持的加密算法", var8);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据身份证号获取性别
|
||||
*
|
||||
* @param idCard 身份证号
|
||||
* @return 性别:"男"或"女"
|
||||
* @throws IllegalArgumentException 身份证号不合法时抛出
|
||||
*/
|
||||
public static String getGender(String idCard) {
|
||||
// 校验身份证号长度
|
||||
if (idCard == null || (idCard.length() != 18 && idCard.length() != 15)) {
|
||||
throw new IllegalArgumentException("身份证号长度不合法");
|
||||
}
|
||||
|
||||
// 18位身份证取第17位,15位身份证取第15位
|
||||
char genderChar;
|
||||
if (idCard.length() == 18) {
|
||||
genderChar = idCard.charAt(16);
|
||||
} else {
|
||||
genderChar = idCard.charAt(14);
|
||||
}
|
||||
|
||||
// 奇数为男,偶数为女
|
||||
return (Integer.parseInt(String.valueOf(genderChar)) % 2 == 1) ? "male" : "female";
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据身份证号计算年龄
|
||||
*
|
||||
* @param idCard 身份证号
|
||||
* @return 年龄
|
||||
* @throws IllegalArgumentException 身份证号不合法时抛出
|
||||
*/
|
||||
public static int getAge(String idCard) {
|
||||
// 校验身份证号长度
|
||||
if (idCard == null || (idCard.length() != 18 && idCard.length() != 15)) {
|
||||
throw new IllegalArgumentException("身份证号长度不合法");
|
||||
}
|
||||
|
||||
// 解析出生日期
|
||||
LocalDate birthDate;
|
||||
if (idCard.length() == 18) {
|
||||
// 18位身份证:第7-14位为出生日期(yyyyMMdd)
|
||||
String birthStr = idCard.substring(6, 14);
|
||||
birthDate = LocalDate.parse(birthStr, DateTimeFormatter.ofPattern("yyyyMMdd"));
|
||||
} else {
|
||||
// 15位身份证:第7-12位为出生日期(yyMMdd),默认19xx年
|
||||
String birthStr = "19" + idCard.substring(6, 12);
|
||||
birthDate = LocalDate.parse(birthStr, DateTimeFormatter.ofPattern("yyyyMMdd"));
|
||||
}
|
||||
|
||||
// 计算与当前日期的差距
|
||||
LocalDate now = LocalDate.now();
|
||||
Period period = Period.between(birthDate, now);
|
||||
|
||||
return period.getYears();
|
||||
}
|
||||
}
|
||||
|
|
@ -23,4 +23,17 @@ public class ApplicationConfigConstant {
|
|||
* 组织中心APP Secret
|
||||
*/
|
||||
public static final String ORGANIZATION_APP_SECRET = "332ed6328a15f6189efa4d2ac5935bc1";
|
||||
|
||||
|
||||
/**
|
||||
* 千里聆服务APP ID
|
||||
*/
|
||||
public static final String OCR_APP_ID = "6ou6wvl8";
|
||||
/**
|
||||
* 千里聆服务APP SECRET
|
||||
*/
|
||||
public static final String OCR_APP_SECRET = "53fc247ffe4f3e8d6c96a5d0a9a222a7";
|
||||
|
||||
public static final String ID_CARD_OCR_URL = "https://open.easst.cn/openapi/rest/common/idcardocr";
|
||||
public static final String BANK_CARD_OCR_URL = "https://open.easst.cn/openapi/rest/common/bank_card_ocr";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
package com.weaver.seconddev.portal.mapper.dictionary;
|
||||
|
||||
import com.weaver.seconddev.portal.entity.param.BaseParam;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
* @author:dxfeng
|
||||
* @createTime: 2025/07/29
|
||||
* @version: 1.0
|
||||
*/
|
||||
@Mapper
|
||||
public interface DataConvertMapper {
|
||||
/**
|
||||
* 根据名称、类型获取ID
|
||||
*
|
||||
* @param param
|
||||
* @param type
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
Long getIdByName(@Param("param") BaseParam param, @Param("type") String type, @Param("name") String name);
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.weaver.seconddev.portal.mapper.dictionary.DataConvertMapper">
|
||||
|
||||
|
||||
<select id="getIdByName" resultType="java.lang.Long">
|
||||
select t1.id from ${param.e10_other_business}.hr_dictionary_setting t1
|
||||
where t1.delete_type = 0 and t1.tenant_key = #{param.tenantKey}
|
||||
and t1.type = #{type}
|
||||
and t1.name = #{name}
|
||||
</select>
|
||||
</mapper>
|
||||
Loading…
Reference in New Issue