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
|
* 组织中心APP Secret
|
||||||
*/
|
*/
|
||||||
public static final String ORGANIZATION_APP_SECRET = "332ed6328a15f6189efa4d2ac5935bc1";
|
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