fix: 自定义标签
This commit is contained in:
51
backend-idcard/README.md
Normal file
51
backend-idcard/README.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# 身份证识别(百度 OCR + AES)
|
||||
|
||||
调用百度身份证 OCR 接口,对图片做 AES 加密上传,对返回的 `result` 密文做 Base64 解码 + AES 解密后解析出所需字段。
|
||||
|
||||
## 依赖
|
||||
|
||||
- JDK 8+
|
||||
- [fastjson](https://github.com/alibaba/fastjson)(解析返回 JSON)
|
||||
|
||||
## 配置
|
||||
|
||||
从百度控制台获取:
|
||||
|
||||
- **access_token**:OAuth2 获取(或使用 API Key + Secret Key 换 token)
|
||||
- **aesKey**:16 位 hex 字符串(控制台身份证 OCR 安全设置里)
|
||||
|
||||
## 使用示例
|
||||
|
||||
```java
|
||||
// 构造识别器(accessToken、aesKey 建议从配置文件或环境变量读取)
|
||||
IdcardRecognizer recognizer = new IdcardRecognizer(accessToken, aesKey);
|
||||
|
||||
// 方式一:本地文件
|
||||
IdcardRecognizer.IdcardResult result = recognizer.recognize("/path/to/idcard_front.jpg", "front");
|
||||
if (result != null) {
|
||||
System.out.println("姓名: " + result.getName());
|
||||
System.out.println("身份证号: " + result.getIdNumber());
|
||||
System.out.println("出生: " + result.getBirth());
|
||||
System.out.println("住址: " + result.getAddress());
|
||||
// ...
|
||||
}
|
||||
|
||||
// 方式二:上传的图片字节(如 MultipartFile.getBytes())
|
||||
byte[] imgBytes = ...;
|
||||
IdcardRecognizer.IdcardResult back = recognizer.recognize(imgBytes, "back");
|
||||
if (back != null) {
|
||||
System.out.println("签发机关: " + back.getIssueAuthority());
|
||||
System.out.println("有效期限: " + back.getValidDate());
|
||||
}
|
||||
```
|
||||
|
||||
## 返回字段说明
|
||||
|
||||
- **正面 (side=front)**:`name` 姓名、`gender` 性别、`nation` 民族、`birth` 出生、`address` 住址、`idNumber` 公民身份号码
|
||||
- **反面 (side=back)**:`issueAuthority` 签发机关、`validDate` 有效期限
|
||||
|
||||
按百度接口约定,请求时 `id_card_side` 传 `front` 或 `back`。
|
||||
|
||||
## 集成到现有后端
|
||||
|
||||
将 `com.ydoyun.ocr` 包拷贝到你的项目中,并加入 fastjson 依赖即可;若项目已使用 Spring,可把 `IdcardRecognizer` 做成 Bean,`accessToken`、`aesKey` 从 `@Value` 或配置类注入。
|
||||
27
backend-idcard/pom.xml
Normal file
27
backend-idcard/pom.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.ydoyun</groupId>
|
||||
<artifactId>idcard-ocr</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<packaging>jar</packaging>
|
||||
<name>Idcard OCR (Baidu)</name>
|
||||
<description>百度身份证 OCR 调用(AES 加解密)</description>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>2.0.43</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,316 @@
|
||||
package cn.iocoder.yudao.module.car.baidu;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 获取 token + 身份证识别(百度 OCR,AES 加解密)。
|
||||
*/
|
||||
public class IdcardRecognizer {
|
||||
|
||||
private static final String IDCARD_OCR_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/idcard";
|
||||
|
||||
private final String accessToken;
|
||||
private byte[] originAesKey;
|
||||
|
||||
public IdcardRecognizer(String accessToken, String aesKey) {
|
||||
this.accessToken = accessToken;
|
||||
try {
|
||||
this.originAesKey = AesKeyUtil.parseAesKey(aesKey);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("aesKey 非法,需 16 位 hex", e);
|
||||
}
|
||||
}
|
||||
|
||||
// ===================== 获取 token(你原来的逻辑) =====================
|
||||
|
||||
/**
|
||||
* 获取权限token
|
||||
* @return 返回示例:
|
||||
* {
|
||||
* "access_token": "24.460da4889caad24cccdb1fea17221975.2592000.1491995545.282335-1234567",
|
||||
* "expires_in": 2592000
|
||||
* }
|
||||
*/
|
||||
public static String getAuth() {
|
||||
String clientId = "mYchXvYyrwXbTscZ0HWfR88s";
|
||||
String clientSecret = "AD4QSVg7OMP0GGkcqxkDSwrn7V7rkShN";
|
||||
return getAuth(clientId, clientSecret);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取API访问token
|
||||
* @param ak - 百度云官网获取的 API Key
|
||||
* @param sk - 百度云官网获取的 Securet Key
|
||||
* @return assess_token
|
||||
*/
|
||||
public static String getAuth(String ak, String sk) {
|
||||
String authHost = "https://aip.baidubce.com/oauth/2.0/token?";
|
||||
String getAccessTokenUrl = authHost
|
||||
+ "grant_type=client_credentials"
|
||||
+ "&client_id=" + ak
|
||||
+ "&client_secret=" + sk;
|
||||
try {
|
||||
URL realUrl = new URL(getAccessTokenUrl);
|
||||
HttpURLConnection connection = (HttpURLConnection) realUrl.openConnection();
|
||||
connection.setRequestMethod("GET");
|
||||
connection.connect();
|
||||
Map<String, List<String>> map = connection.getHeaderFields();
|
||||
for (String key : map.keySet()) {
|
||||
System.err.println(key + "--->" + map.get(key));
|
||||
}
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||
String result = "";
|
||||
String line;
|
||||
while ((line = in.readLine()) != null) {
|
||||
result += line;
|
||||
}
|
||||
System.err.println("result:" + result);
|
||||
JSONObject jsonObject = JSONObject.parseObject(result);
|
||||
String access_token = jsonObject.getString("access_token");
|
||||
return access_token;
|
||||
} catch (Exception e) {
|
||||
System.err.printf("获取token失败!");
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ===================== 身份证识别(AES 加解密) =====================
|
||||
|
||||
/**
|
||||
* 识别身份证图片(本地文件路径)
|
||||
* @param filePath 图片本地路径
|
||||
* @param side 正面 "front" 或反面 "back"
|
||||
* @return 解析后的身份证字段,识别失败返回 null
|
||||
*/
|
||||
public IdcardResult recognize(String filePath, String side) throws Exception {
|
||||
byte[] imgData = Files.readAllBytes(Paths.get(filePath));
|
||||
return recognize(imgData, side);
|
||||
}
|
||||
|
||||
/**
|
||||
* 识别身份证图片(字节数组,适合上传场景)
|
||||
* @param imgData 图片字节
|
||||
* @param side 正面 "front" 或反面 "back"
|
||||
* @return 解析后的身份证字段,识别失败返回 null
|
||||
*/
|
||||
public IdcardResult recognize(byte[] imgData, String side) throws Exception {
|
||||
String plainJson = recognizePlainJson(imgData, side);
|
||||
return (plainJson == null || plainJson.isEmpty()) ? null : parseToResult(plainJson, side);
|
||||
}
|
||||
|
||||
/**
|
||||
* 识别并返回解密后的明文 JSON(便于调试/落库)。
|
||||
*/
|
||||
public String recognizePlainJson(byte[] imgData, String side) throws Exception {
|
||||
String imgStr = encryptImg(imgData);
|
||||
String imgParam = URLEncoder.encode(imgStr, "UTF-8");
|
||||
String body = "id_card_side=" + side + "&image=" + imgParam + "&AESEncry=true";
|
||||
|
||||
String rawResponse = HttpUtil.post(IDCARD_OCR_URL, accessToken, body);
|
||||
if (rawResponse == null || rawResponse.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return decryptResult(rawResponse);
|
||||
}
|
||||
|
||||
private String encryptImg(byte[] imgData) throws Exception {
|
||||
byte[] encImgBytes = AesUtil.encrypt(imgData, originAesKey);
|
||||
return Base64.getEncoder().encodeToString(encImgBytes);
|
||||
}
|
||||
|
||||
private String decryptResult(String encryptResponse) throws Exception {
|
||||
JSONObject obj = JSON.parseObject(encryptResponse);
|
||||
String result = obj.getString("result");
|
||||
if (result == null) return null;
|
||||
byte[] arr = Base64.getDecoder().decode(result);
|
||||
return new String(AesUtil.decrypt(arr, originAesKey), "UTF-8");
|
||||
}
|
||||
|
||||
private IdcardResult parseToResult(String plainJson, String side) {
|
||||
JSONObject root = JSON.parseObject(plainJson);
|
||||
JSONObject wordsResult = root.getJSONObject("words_result");
|
||||
if (wordsResult == null) return null;
|
||||
|
||||
IdcardResult result = new IdcardResult();
|
||||
result.setSide("front".equalsIgnoreCase(side) ? "front" : "back");
|
||||
|
||||
if ("front".equalsIgnoreCase(side)) {
|
||||
result.setName(getWords(wordsResult, "姓名"));
|
||||
result.setGender(getWords(wordsResult, "性别"));
|
||||
result.setNation(getWords(wordsResult, "民族"));
|
||||
result.setBirth(getWords(wordsResult, "出生"));
|
||||
result.setAddress(getWords(wordsResult, "住址"));
|
||||
result.setIdNumber(getWords(wordsResult, "公民身份号码"));
|
||||
} else {
|
||||
result.setIssueAuthority(getWords(wordsResult, "签发机关"));
|
||||
result.setValidDate(getWords(wordsResult, "有效期限"));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String getWords(JSONObject wordsResult, String key) {
|
||||
if (wordsResult == null) return null;
|
||||
JSONObject item = wordsResult.getJSONObject(key);
|
||||
return item == null ? null : item.getString("words");
|
||||
}
|
||||
|
||||
// --------------- 结果 DTO ---------------
|
||||
|
||||
public static class IdcardResult {
|
||||
private String side;
|
||||
private String name;
|
||||
private String gender;
|
||||
private String nation;
|
||||
private String birth;
|
||||
private String address;
|
||||
private String idNumber;
|
||||
private String issueAuthority;
|
||||
private String validDate;
|
||||
|
||||
public String getSide() { return side; }
|
||||
public void setSide(String side) { this.side = side; }
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
public String getGender() { return gender; }
|
||||
public void setGender(String gender) { this.gender = gender; }
|
||||
public String getNation() { return nation; }
|
||||
public void setNation(String nation) { this.nation = nation; }
|
||||
public String getBirth() { return birth; }
|
||||
public void setBirth(String birth) { this.birth = birth; }
|
||||
public String getAddress() { return address; }
|
||||
public void setAddress(String address) { this.address = address; }
|
||||
public String getIdNumber() { return idNumber; }
|
||||
public void setIdNumber(String idNumber) { this.idNumber = idNumber; }
|
||||
public String getIssueAuthority() { return issueAuthority; }
|
||||
public void setIssueAuthority(String issueAuthority) { this.issueAuthority = issueAuthority; }
|
||||
public String getValidDate() { return validDate; }
|
||||
public void setValidDate(String validDate) { this.validDate = validDate; }
|
||||
}
|
||||
|
||||
// --------------- 工具类 ---------------
|
||||
|
||||
static class HttpUtil {
|
||||
static String post(String url, String accessToken, String formBody) throws IOException {
|
||||
String fullUrl = url + "?access_token=" + URLEncoder.encode(accessToken, "UTF-8");
|
||||
byte[] bodyBytes = formBody.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
HttpURLConnection conn = (HttpURLConnection) new URL(fullUrl).openConnection();
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setConnectTimeout(15000);
|
||||
conn.setReadTimeout(30000);
|
||||
conn.setDoOutput(true);
|
||||
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
|
||||
conn.setRequestProperty("Accept", "application/json");
|
||||
|
||||
OutputStream os = conn.getOutputStream();
|
||||
try {
|
||||
os.write(bodyBytes);
|
||||
} finally {
|
||||
os.close();
|
||||
}
|
||||
|
||||
int code = conn.getResponseCode();
|
||||
InputStream is = (code >= 200 && code < 300) ? conn.getInputStream() : conn.getErrorStream();
|
||||
if (is == null) return null;
|
||||
try {
|
||||
byte[] bytes = readAllBytes(is);
|
||||
return new String(bytes, "UTF-8");
|
||||
} finally {
|
||||
conn.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] readAllBytes(InputStream in) throws IOException {
|
||||
byte[] buf = new byte[4096];
|
||||
int n;
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
while ((n = in.read(buf)) >= 0) {
|
||||
if (n > 0) bos.write(buf, 0, n);
|
||||
}
|
||||
return bos.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
static class AesKeyUtil {
|
||||
private static final String HEX = "0123456789abcdef";
|
||||
|
||||
static byte[] parseAesKey(String hex) throws Exception {
|
||||
if (hex == null || hex.length() != 16) {
|
||||
throw new Exception("aes key 需为 16 位");
|
||||
}
|
||||
char[] data = hex.toCharArray();
|
||||
byte[] out = new byte[data.length];
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
int f = HEX.indexOf(data[i]);
|
||||
if (f < 0) throw new Exception("aes key 需为 hex 字符");
|
||||
out[i] = (byte) f;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
static class AesUtil {
|
||||
private static final String ALGORITHM = "AES";
|
||||
private static final String ALGORITHM_STR = "AES/ECB/PKCS5Padding";
|
||||
|
||||
static byte[] encrypt(byte[] src, byte[] aesKey) throws Exception {
|
||||
Cipher cipher = getCipher(aesKey, Cipher.ENCRYPT_MODE);
|
||||
return cipher.doFinal(src);
|
||||
}
|
||||
|
||||
static byte[] decrypt(byte[] src, byte[] aesKey) throws Exception {
|
||||
Cipher cipher = getCipher(aesKey, Cipher.DECRYPT_MODE);
|
||||
return cipher.doFinal(src);
|
||||
}
|
||||
|
||||
private static Cipher getCipher(byte[] aesKey, int mode) throws Exception {
|
||||
SecretKeySpec spec = new SecretKeySpec(aesKey, ALGORITHM);
|
||||
Cipher cipher = Cipher.getInstance(ALGORITHM_STR);
|
||||
cipher.init(mode, spec);
|
||||
return cipher;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------- 测试 main(改下面三个变量后直接运行) ---------------
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
// 1. 用你原来的 getAuth 拿 token
|
||||
String accessToken = getAuth();
|
||||
if (accessToken == null) {
|
||||
System.out.println("获取 access_token 失败");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 从控制台拿 16 位 aesKey(hex 字符串)
|
||||
String aesKey = "填写16位aesKey";
|
||||
String filePath = "填写身份证图片路径";
|
||||
String side = "front"; // front 正面 / back 反面
|
||||
|
||||
IdcardRecognizer recognizer = new IdcardRecognizer(accessToken, aesKey);
|
||||
IdcardResult result = recognizer.recognize(filePath, side);
|
||||
|
||||
System.out.println("========== 识别结果 ==========");
|
||||
System.out.println(JSON.toJSONString(result, true));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
package com.ydoyun.ocr;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* 身份证识别器:调用百度 OCR 身份证接口(AES 加密传输),解析并返回所需字段。
|
||||
* 需配置:accessToken、aesKey(16 位 hex),可从百度控制台获取。
|
||||
*/
|
||||
public class IdcardRecognizer {
|
||||
|
||||
private static final String IDCARD_OCR_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/idcard";
|
||||
|
||||
private final String accessToken;
|
||||
private byte[] originAesKey;
|
||||
|
||||
public IdcardRecognizer(String accessToken, String aesKey) {
|
||||
this.accessToken = accessToken;
|
||||
try {
|
||||
this.originAesKey = AesKeyUtil.parseAesKey(aesKey);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("aesKey 非法,需 16 位 hex", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 识别身份证图片(本地文件路径)
|
||||
*
|
||||
* @param filePath 图片本地路径
|
||||
* @param side 正面 "front" 或反面 "back"
|
||||
* @return 解析后的身份证字段,识别失败返回 null
|
||||
*/
|
||||
public IdcardResult recognize(String filePath, String side) throws IOException, Exception {
|
||||
byte[] imgData = Files.readAllBytes(Paths.get(filePath));
|
||||
return recognize(imgData, side);
|
||||
}
|
||||
|
||||
/**
|
||||
* 识别身份证图片(字节数组,适合上传场景)
|
||||
*
|
||||
* @param imgData 图片字节
|
||||
* @param side 正面 "front" 或反面 "back"
|
||||
* @return 解析后的身份证字段,识别失败返回 null
|
||||
*/
|
||||
public IdcardResult recognize(byte[] imgData, String side) throws Exception {
|
||||
String plainJson = recognizePlainJson(imgData, side);
|
||||
return (plainJson == null || plainJson.isEmpty()) ? null : parseToResult(plainJson, side);
|
||||
}
|
||||
|
||||
/**
|
||||
* 识别并返回解密后的明文 JSON(便于调试/落库/自行解析)。
|
||||
*/
|
||||
public String recognizePlainJson(byte[] imgData, String side) throws Exception {
|
||||
String imgStr = encryptImg(imgData);
|
||||
String imgParam = URLEncoder.encode(imgStr, StandardCharsets.UTF_8.name());
|
||||
String body = "id_card_side=" + side + "&image=" + imgParam + "&AESEncry=true";
|
||||
|
||||
String rawResponse = HttpUtil.post(IDCARD_OCR_URL, accessToken, body);
|
||||
if (rawResponse == null || rawResponse.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return decryptResult(rawResponse);
|
||||
}
|
||||
|
||||
private String encryptImg(byte[] imgData) throws Exception {
|
||||
byte[] encImgBytes = AesUtil.encrypt(imgData, originAesKey);
|
||||
return Base64.getEncoder().encodeToString(encImgBytes);
|
||||
}
|
||||
|
||||
private String decryptResult(String encryptResponse) throws Exception {
|
||||
JSONObject obj = JSON.parseObject(encryptResponse);
|
||||
String result = obj.getString("result");
|
||||
if (result == null) return null;
|
||||
byte[] arr = Base64.getDecoder().decode(result);
|
||||
return new String(AesUtil.decrypt(arr, originAesKey), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private IdcardResult parseToResult(String plainJson, String side) {
|
||||
JSONObject root = JSON.parseObject(plainJson);
|
||||
JSONObject wordsResult = root.getJSONObject("words_result");
|
||||
if (wordsResult == null) return null;
|
||||
|
||||
IdcardResult result = new IdcardResult();
|
||||
result.setSide("front".equalsIgnoreCase(side) ? "front" : "back");
|
||||
|
||||
if ("front".equalsIgnoreCase(side)) {
|
||||
result.setName(getWords(wordsResult, "姓名"));
|
||||
result.setGender(getWords(wordsResult, "性别"));
|
||||
result.setNation(getWords(wordsResult, "民族"));
|
||||
result.setBirth(getWords(wordsResult, "出生"));
|
||||
result.setAddress(getWords(wordsResult, "住址"));
|
||||
result.setIdNumber(getWords(wordsResult, "公民身份号码"));
|
||||
} else {
|
||||
result.setIssueAuthority(getWords(wordsResult, "签发机关"));
|
||||
result.setValidDate(getWords(wordsResult, "有效期限"));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String getWords(JSONObject wordsResult, String key) {
|
||||
if (wordsResult == null) return null;
|
||||
JSONObject item = wordsResult.getJSONObject(key);
|
||||
return item == null ? null : item.getString("words");
|
||||
}
|
||||
|
||||
// --------------- 结果 DTO ---------------
|
||||
|
||||
public static class IdcardResult {
|
||||
private String side;
|
||||
private String name;
|
||||
private String gender;
|
||||
private String nation;
|
||||
private String birth;
|
||||
private String address;
|
||||
private String idNumber;
|
||||
private String issueAuthority;
|
||||
private String validDate;
|
||||
|
||||
public String getSide() { return side; }
|
||||
public void setSide(String side) { this.side = side; }
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
public String getGender() { return gender; }
|
||||
public void setGender(String gender) { this.gender = gender; }
|
||||
public String getNation() { return nation; }
|
||||
public void setNation(String nation) { this.nation = nation; }
|
||||
public String getBirth() { return birth; }
|
||||
public void setBirth(String birth) { this.birth = birth; }
|
||||
public String getAddress() { return address; }
|
||||
public void setAddress(String address) { this.address = address; }
|
||||
public String getIdNumber() { return idNumber; }
|
||||
public void setIdNumber(String idNumber) { this.idNumber = idNumber; }
|
||||
public String getIssueAuthority() { return issueAuthority; }
|
||||
public void setIssueAuthority(String issueAuthority) { this.issueAuthority = issueAuthority; }
|
||||
public String getValidDate() { return validDate; }
|
||||
public void setValidDate(String validDate) { this.validDate = validDate; }
|
||||
}
|
||||
|
||||
// --------------- 工具类 ---------------
|
||||
|
||||
static class HttpUtil {
|
||||
static String post(String url, String accessToken, String formBody) throws IOException {
|
||||
String fullUrl = url + "?access_token=" + URLEncoder.encode(accessToken, "UTF-8");
|
||||
byte[] bodyBytes = formBody.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
HttpURLConnection conn = (HttpURLConnection) new URL(fullUrl).openConnection();
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setConnectTimeout(15_000);
|
||||
conn.setReadTimeout(30_000);
|
||||
conn.setDoOutput(true);
|
||||
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
|
||||
conn.setRequestProperty("Accept", "application/json");
|
||||
|
||||
try (OutputStream os = conn.getOutputStream()) {
|
||||
os.write(bodyBytes);
|
||||
}
|
||||
|
||||
int code = conn.getResponseCode();
|
||||
InputStream is = (code >= 200 && code < 300) ? conn.getInputStream() : conn.getErrorStream();
|
||||
if (is == null) return null;
|
||||
try (InputStream in = is) {
|
||||
byte[] bytes = readAllBytes(in);
|
||||
return new String(bytes, StandardCharsets.UTF_8);
|
||||
} finally {
|
||||
conn.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] readAllBytes(InputStream in) throws IOException {
|
||||
byte[] buf = new byte[4096];
|
||||
int n;
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
while ((n = in.read(buf)) >= 0) {
|
||||
if (n > 0) bos.write(buf, 0, n);
|
||||
}
|
||||
return bos.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
static class AesKeyUtil {
|
||||
private static final String HEX = "0123456789abcdef";
|
||||
|
||||
static byte[] parseAesKey(String hex) throws Exception {
|
||||
if (hex == null || hex.length() != 16) {
|
||||
throw new Exception("aes key 需为 16 位");
|
||||
}
|
||||
char[] data = hex.toCharArray();
|
||||
byte[] out = new byte[data.length];
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
int f = HEX.indexOf(data[i]);
|
||||
if (f < 0) throw new Exception("aes key 需为 hex 字符");
|
||||
out[i] = (byte) f;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
static class AesUtil {
|
||||
private static final String ALGORITHM = "AES";
|
||||
private static final String ALGORITHM_STR = "AES/ECB/PKCS5Padding";
|
||||
|
||||
static byte[] encrypt(byte[] src, byte[] aesKey) throws Exception {
|
||||
Cipher cipher = getCipher(aesKey, Cipher.ENCRYPT_MODE);
|
||||
return cipher.doFinal(src);
|
||||
}
|
||||
|
||||
static byte[] decrypt(byte[] src, byte[] aesKey) throws Exception {
|
||||
Cipher cipher = getCipher(aesKey, Cipher.DECRYPT_MODE);
|
||||
return cipher.doFinal(src);
|
||||
}
|
||||
|
||||
private static Cipher getCipher(byte[] aesKey, int mode) throws Exception {
|
||||
SecretKeySpec spec = new SecretKeySpec(aesKey, ALGORITHM);
|
||||
Cipher cipher = Cipher.getInstance(ALGORITHM_STR);
|
||||
cipher.init(mode, spec);
|
||||
return cipher;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地快速测试入口(JDK8 可运行,直接改下面四个变量再运行)。
|
||||
*/
|
||||
public static void main(String[] args) throws Exception {
|
||||
// TODO:替换成你自己的配置
|
||||
String accessToken = "填写你的 access_token";
|
||||
String aesKey = "填写 16 位 aesKey(hex 字符串)";
|
||||
String side = "front"; // front 正面 / back 反面
|
||||
String filePath = "填写身份证图片本地路径";
|
||||
|
||||
IdcardRecognizer recognizer = new IdcardRecognizer(accessToken, aesKey);
|
||||
IdcardResult result = recognizer.recognize(filePath, side);
|
||||
System.out.println(JSON.toJSONString(result, true));
|
||||
|
||||
// 如果你要看解密后的明文 JSON(排查字段/定位问题),放开下面三行:
|
||||
// byte[] img = Files.readAllBytes(Paths.get(filePath));
|
||||
// String plain = recognizer.recognizePlainJson(img, side);
|
||||
// System.out.println(plain);
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
backend-idcard/target/idcard-ocr-1.0.0.jar
Normal file
BIN
backend-idcard/target/idcard-ocr-1.0.0.jar
Normal file
Binary file not shown.
3
backend-idcard/target/maven-archiver/pom.properties
Normal file
3
backend-idcard/target/maven-archiver/pom.properties
Normal file
@@ -0,0 +1,3 @@
|
||||
artifactId=idcard-ocr
|
||||
groupId=com.ydoyun
|
||||
version=1.0.0
|
||||
@@ -0,0 +1,5 @@
|
||||
com/ydoyun/ocr/IdcardRecognizer$HttpUtil.class
|
||||
com/ydoyun/ocr/IdcardRecognizer$AesKeyUtil.class
|
||||
com/ydoyun/ocr/IdcardRecognizer$IdcardResult.class
|
||||
com/ydoyun/ocr/IdcardRecognizer$AesUtil.class
|
||||
com/ydoyun/ocr/IdcardRecognizer.class
|
||||
@@ -0,0 +1 @@
|
||||
/Users/ouhaolan/project/百盛科技/报表/yudao-ui-admin-vue3/backend-idcard/src/main/java/com/ydoyun/ocr/IdcardRecognizer.java
|
||||
Reference in New Issue
Block a user