Преглед изворни кода

fix 人脸识别,身份证识别

tjf пре 2 недеља
родитељ
комит
d89469e97c

+ 69 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/OcrController.java

@@ -0,0 +1,69 @@
+package com.ruoyi.web.controller.common;
+
+
+import com.aliyun.cloudauth20190307.models.InitFaceVerifyResponseBody;
+import com.ruoyi.common.annotation.RepeatSubmit;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.domain.IdCardVo;
+import com.ruoyi.common.core.domain.SysUserIdcardVo;
+import com.ruoyi.common.core.redis.RedisCache;
+import com.ruoyi.common.utils.IdCardUtil;
+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;
+
+/**
+ * @Author: tjf
+ * @Date: 2024/4/22 14:24
+ * @Describe:
+ */
+@RestController
+@RequestMapping("/ocr")
+public class OcrController {
+    @Autowired
+    private RedisCache redisCache;
+
+    /**
+     * 身份证识别
+     *
+     * @return
+     */
+    @PostMapping("/ocrIdCard")
+    @RepeatSubmit(interval = 1000, message = "请求过于频繁")
+    public AjaxResult ocrIdCard(@RequestBody IdCardVo idCardVo) {
+        return IdCardUtil.idCard(idCardVo.getImage(), idCardVo.getIdCardSide());
+    }
+
+
+
+
+
+    /**
+     * 人脸识别阿里云金融级实人认证
+     *
+     * @return
+     */
+    @PostMapping("/ocrSampleAliYun")
+    @RepeatSubmit(interval = 1000, message = "请求过于频繁")
+    public AjaxResult ocrSampleAliYun(@RequestBody SysUserIdcardVo sysUserIdcardVo) {
+        return IdCardUtil.ocrSampleAliYun(sysUserIdcardVo);
+    }
+
+    /**
+     * 人脸识别阿里云金融级实人认证H5
+     *
+     * @return
+     */
+    @PostMapping("/ocrSampleAliYunH")
+    @RepeatSubmit(interval = 1000, message = "请求过于频繁")
+    public AjaxResult ocrSampleAliYunH(@RequestBody SysUserIdcardVo sysUserIdcardVo) {
+        InitFaceVerifyResponseBody.InitFaceVerifyResponseBodyResultObject result = IdCardUtil.initFaceVerifyH5(sysUserIdcardVo);
+/*        String certifyId = result.getCertifyId();
+        redisCache.setCacheObject(certifyId, sysUserIdcardVo);*/
+        System.out.println("人脸识别H5跳转URL"+result.getCertifyUrl());
+        return AjaxResult.success(result);
+    }
+
+}

+ 1 - 1
ruoyi-admin/src/main/resources/application.yml

@@ -70,7 +70,7 @@ xss:
   # 过滤开关
   enabled: true
   # 排除链接(多个用逗号分隔)
-  excludes: /system/notice,/new/news,/new/news/put
+  excludes: /system/notice,/manage/news,/manage/news/put
   # 匹配链接
   urlPatterns: /system/*,/monitor/*,/tool/*
 

+ 24 - 1
ruoyi-common/pom.xml

@@ -22,7 +22,30 @@
     </properties>
 
     <dependencies>
-
+        <!--hutool工具-->
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>5.8.27</version>
+        </dependency>
+        <!--阿里云人脸识别-->
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>cloudauth20190307</artifactId>
+            <version>2.0.8</version>
+        </dependency>
+        <!--身份验证依赖-->
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>credentials-java</artifactId>
+            <version>LATEST</version>
+        </dependency>
+        <!--阿里短信服务-->
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>dysmsapi20170525</artifactId>
+            <version>2.0.24</version>
+        </dependency>
         <!-- Spring框架基本的核心工具 -->
         <dependency>
             <groupId>org.springframework</groupId>

+ 3 - 0
ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java

@@ -173,6 +173,9 @@ public class Constants {
 
     public static final String Y = "Y";
     public static final String N = "N";
+    public static final String FRONT = "front";
+    public static final String BACK = "back";
+    public static final String RZDB = "RZDB";
     public static final String ZERO = "0";
     public static final String ONE = "1";
     public static final String TWO = "2";

+ 71 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/IdCardVo.java

@@ -0,0 +1,71 @@
+package com.ruoyi.common.core.domain;
+
+import java.io.Serializable;
+
+/**
+ * @Author: tjf
+ * @Date: 2024/4/23 14:58
+ * @Describe:
+ */
+public class IdCardVo implements Serializable {
+    private static final long serialVersionUID = 1L;
+    /**
+     * 身份证号码
+     */
+    private String idCard;
+    /**
+     * 姓名
+     */
+    private String name;
+    /**
+     * 图片
+     */
+    private String image;
+    /**
+     *-front:身份证含照片的一面
+     * -back:身份证带国徽的一面
+     */
+    private String idCardSide;
+
+    public String getIdCardSide() {
+        return idCardSide;
+    }
+
+    public void setIdCardSide(String idCardSide) {
+        this.idCardSide = idCardSide;
+    }
+
+    public String getIdCard() {
+        return idCard;
+    }
+
+    public void setIdCard(String idCard) {
+        this.idCard = idCard;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getImage() {
+        return image;
+    }
+
+    public void setImage(String image) {
+        this.image = image;
+    }
+
+    @Override
+    public String toString() {
+        return "IdCardVo{" +
+                "idCard='" + idCard + '\'' +
+                ", name='" + name + '\'' +
+                ", image='" + image + '\'' +
+                ", idCardSide='" + idCardSide + '\'' +
+                '}';
+    }
+}

+ 203 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/SysUserIdcardVo.java

@@ -0,0 +1,203 @@
+package com.ruoyi.common.core.domain;
+
+import com.ruoyi.common.annotation.Excel;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+/**
+ * 用户身份证信息对象 sys_user_idcard
+ * 
+ * @author boman
+ * @date 2024-04-22
+ */
+public class SysUserIdcardVo extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 用户ID */
+    private Long userId;
+
+    /** 身份证号码 */
+    private String idCard;
+
+    /** 真实姓名 */
+    @Excel(name = "真实姓名")
+    private String realName;
+
+    /** 失效日期 */
+    @Excel(name = "失效日期")
+    private String expirationDate;
+
+    /** 手机号码 */
+    @Excel(name = "手机号码")
+    private String phonenumber;
+
+    /** 居住地址 */
+    @Excel(name = "居住地址")
+    private String address;
+
+    /** 正面地址 */
+    @Excel(name = "正面地址")
+    private String front;
+
+    /** 反面地址 */
+    @Excel(name = "反面地址")
+    private String back;
+
+    /** 删除标志(0代表存在 2代表删除) */
+    private String delFlag;
+
+    /**
+     * 图片
+     */
+    private String image;
+    /**
+     * 认证结果
+     */
+    private String result;
+
+    /**
+     * MetaInfo环境参数,需要通过客户端SDK获取
+     *
+     * {"zimVer":"3.0.0","appVersion": "1","bioMetaInfo": "4.1.0:1150****,0","appName": "com.aliyun.antcloudauth","deviceType": "ios","osVersion": "iOS 10.3.2","apdidToken": "","deviceModel": "iPhone9,1"}
+     */
+    private String metaInfo;
+    /**
+     *     您的业务页面回跳的目标地址。
+     */
+    private String returnUrl;
+
+    public String getReturnUrl() {
+        return returnUrl;
+    }
+
+    public void setReturnUrl(String returnUrl) {
+        this.returnUrl = returnUrl;
+    }
+
+    public String getMetaInfo() {
+        return metaInfo;
+    }
+
+    public void setMetaInfo(String metaInfo) {
+        this.metaInfo = metaInfo;
+    }
+
+    public String getImage() {
+        return image;
+    }
+
+    public void setImage(String image) {
+        this.image = image;
+    }
+
+    public String getResult() {
+        return result;
+    }
+
+    public void setResult(String result) {
+        this.result = result;
+    }
+
+    public void setUserId(Long userId)
+    {
+        this.userId = userId;
+    }
+
+    public Long getUserId() 
+    {
+        return userId;
+    }
+    public void setIdCard(String idCard) 
+    {
+        this.idCard = idCard;
+    }
+
+    public String getIdCard() 
+    {
+        return idCard;
+    }
+    public void setRealName(String realName) 
+    {
+        this.realName = realName;
+    }
+
+    public String getRealName() 
+    {
+        return realName;
+    }
+    public void setExpirationDate(String expirationDate) 
+    {
+        this.expirationDate = expirationDate;
+    }
+
+    public String getExpirationDate() 
+    {
+        return expirationDate;
+    }
+    public void setPhonenumber(String phonenumber) 
+    {
+        this.phonenumber = phonenumber;
+    }
+
+    public String getPhonenumber() 
+    {
+        return phonenumber;
+    }
+    public void setAddress(String address) 
+    {
+        this.address = address;
+    }
+
+    public String getAddress() 
+    {
+        return address;
+    }
+    public void setFront(String front) 
+    {
+        this.front = front;
+    }
+
+    public String getFront() 
+    {
+        return front;
+    }
+    public void setBack(String back) 
+    {
+        this.back = back;
+    }
+
+    public String getBack() 
+    {
+        return back;
+    }
+    public void setDelFlag(String delFlag) 
+    {
+        this.delFlag = delFlag;
+    }
+
+    public String getDelFlag() 
+    {
+        return delFlag;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("userId", getUserId())
+            .append("idCard", getIdCard())
+            .append("realName", getRealName())
+            .append("expirationDate", getExpirationDate())
+            .append("phonenumber", getPhonenumber())
+            .append("address", getAddress())
+            .append("front", getFront())
+            .append("back", getBack())
+            .append("delFlag", getDelFlag())
+            .append("createBy", getCreateBy())
+            .append("createTime", getCreateTime())
+            .append("updateBy", getUpdateBy())
+            .append("updateTime", getUpdateTime())
+            .append("remark", getRemark())
+            .toString();
+    }
+}

+ 72 - 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/FileUtil.java

@@ -0,0 +1,72 @@
+package com.ruoyi.common.utils;
+
+import java.io.*;
+
+/**
+ * 文件读取工具类
+ */
+public class FileUtil {
+
+    /**
+     * 读取文件内容,作为字符串返回
+     */
+    public static String readFileAsString(String filePath) throws IOException {
+        File file = new File(filePath);
+        if (!file.exists()) {
+            throw new FileNotFoundException(filePath);
+        } 
+
+        if (file.length() > 1024 * 1024 * 1024) {
+            throw new IOException("File is too large");
+        } 
+
+        StringBuilder sb = new StringBuilder((int) (file.length()));
+        // 创建字节输入流  
+        FileInputStream fis = new FileInputStream(filePath);  
+        // 创建一个长度为10240的Buffer
+        byte[] bbuf = new byte[10240];  
+        // 用于保存实际读取的字节数  
+        int hasRead = 0;  
+        while ( (hasRead = fis.read(bbuf)) > 0 ) {  
+            sb.append(new String(bbuf, 0, hasRead));  
+        }  
+        fis.close();  
+        return sb.toString();
+    }
+
+    /**
+     * 根据文件路径读取byte[] 数组
+     */
+    public static byte[] readFileByBytes(String filePath) throws IOException {
+        File file = new File(filePath);
+        if (!file.exists()) {
+            throw new FileNotFoundException(filePath);
+        } else {
+            ByteArrayOutputStream bos = new ByteArrayOutputStream((int) file.length());
+            BufferedInputStream in = null;
+
+            try {
+                in = new BufferedInputStream(new FileInputStream(file));
+                short bufSize = 1024;
+                byte[] buffer = new byte[bufSize];
+                int len1;
+                while (-1 != (len1 = in.read(buffer, 0, bufSize))) {
+                    bos.write(buffer, 0, len1);
+                }
+
+                byte[] var7 = bos.toByteArray();
+                return var7;
+            } finally {
+                try {
+                    if (in != null) {
+                        in.close();
+                    }
+                } catch (IOException var14) {
+                    var14.printStackTrace();
+                }
+
+                bos.close();
+            }
+        }
+    }
+}

+ 77 - 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/HttpUtils.java

@@ -0,0 +1,77 @@
+package com.ruoyi.common.utils;
+
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * http 工具类
+ */
+public class HttpUtils {
+
+    public static String post(String requestUrl, String accessToken, String params)
+            throws Exception {
+        String contentType = "application/x-www-form-urlencoded";
+        return HttpUtils.post(requestUrl, accessToken, contentType, params);
+    }
+
+    public static String post(String requestUrl, String accessToken, String contentType, String params)
+            throws Exception {
+        String encoding = "UTF-8";
+        if (requestUrl.contains("nlp")) {
+            encoding = "GBK";
+        }
+        return HttpUtils.post(requestUrl, accessToken, contentType, params, encoding);
+    }
+
+    public static String post(String requestUrl, String accessToken, String contentType, String params, String encoding)
+            throws Exception {
+        String url = requestUrl + "?access_token=" + accessToken;
+        return HttpUtils.postGeneralUrl(url, contentType, params, encoding);
+    }
+
+    public static String postGeneralUrl(String generalUrl, String contentType, String params, String encoding)
+            throws Exception {
+        URL url = new URL(generalUrl);
+        // 打开和URL之间的连接
+        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+        connection.setRequestMethod("POST");
+        // 设置通用的请求属性
+        connection.setRequestProperty("Content-Type", contentType);
+        connection.setRequestProperty("Connection", "Keep-Alive");
+        connection.setUseCaches(false);
+        connection.setDoOutput(true);
+        connection.setDoInput(true);
+
+        // 得到请求的输出流对象
+        DataOutputStream out = new DataOutputStream(connection.getOutputStream());
+        out.write(params.getBytes(encoding));
+        out.flush();
+        out.close();
+
+        // 建立实际的连接
+        connection.connect();
+        // 获取所有响应头字段
+        Map<String, List<String>> headers = connection.getHeaderFields();
+        // 遍历所有的响应头字段
+        for (String key : headers.keySet()) {
+            System.err.println(key + "--->" + headers.get(key));
+        }
+        // 定义 BufferedReader输入流来读取URL的响应
+        BufferedReader in = null;
+        in = new BufferedReader(
+                new InputStreamReader(connection.getInputStream(), encoding));
+        String result = "";
+        String getLine;
+        while ((getLine = in.readLine()) != null) {
+            result += getLine;
+        }
+        in.close();
+        System.err.println("result:" + result);
+        return result;
+    }
+}

+ 634 - 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/IdCardUtil.java

@@ -0,0 +1,634 @@
+package com.ruoyi.common.utils;
+
+/**
+ * @Author: tjf
+ * @Date: 2024/4/17 9:35
+ * @Describe:
+ */
+
+import cn.hutool.http.HttpUtil;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.aliyun.cloudauth20190307.Client;
+import com.aliyun.cloudauth20190307.models.InitFaceVerifyRequest;
+import com.aliyun.cloudauth20190307.models.InitFaceVerifyResponse;
+import com.aliyun.cloudauth20190307.models.InitFaceVerifyResponseBody;
+import com.aliyun.teaopenapi.models.Config;
+import com.aliyun.teautil.models.RuntimeOptions;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.domain.SysUserIdcardVo;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.*;
+
+import static com.ruoyi.common.constant.Constants.BACK;
+import static com.ruoyi.common.constant.Constants.FRONT;
+
+public class IdCardUtil {
+    /**
+     * 重要提示代码中所需工具类
+     * FileUtil,HttpUtil请从
+     * https://ai.baidu.com/file/658A35ABAB2D404FBF903F64D47C1F72
+     * https://ai.baidu.com/file/544D677F5D4E4F17B4122FBD60DB82B3
+     * 下载
+     * <p>
+     * https://cloud.baidu.com/doc/OCR/s/rk3h7xzck 文档地址
+     */
+
+    // aes key 从console控制台获取 身份证识别使用
+    static String aesKey = "ce539dc069ad0908";
+
+    static byte[] originAesKey = null;
+
+    /**
+     * 身份证识别
+     * {
+     * "log_id": "1559208562721579319",
+     * "direction": 0,
+     * "image_status": "normal",
+     * "photo": "/9j/4AAQSkZJRgABA......",
+     * "photo_location": {
+     * "width": 1189,
+     * "top": 638,
+     * "left": 2248,
+     * "height": 1483
+     * },
+     * "card_image": "/9j/4AAQSkZJRgABA......",
+     * "card_location": {
+     * "top": 328,
+     * "left": 275,
+     * "width": 1329,
+     * "height": 571
+     * },
+     * "words_result": {
+     * "住址": {
+     * "location": {
+     * "left": 267,
+     * "top": 453,
+     * "width": 459,
+     * "height": 99
+     * },
+     * "words": "南京市江宁区弘景大道3889号"
+     * },
+     * "公民身份号码": {
+     * "location": {
+     * "left": 443,
+     * "top": 681,
+     * "width": 589,
+     * "height": 45
+     * },
+     * "words": "330881199904173914"
+     * },
+     * "出生": {
+     * "location": {
+     * "left": 270,
+     * "top": 355,
+     * "width": 357,
+     * "height": 45
+     * },
+     * "words": "19990417"
+     * },
+     * "姓名": {
+     * "location": {
+     * "left": 267,
+     * "top": 176,
+     * "width": 152,
+     * "height": 50
+     * },
+     * "words": "伍云龙"
+     * },
+     * "性别": {
+     * "location": {
+     * "left": 269,
+     * "top": 262,
+     * "width": 33,
+     * "height": 52
+     * },
+     * "words": "男"
+     * },
+     * "民族": {
+     * "location": {
+     * "left": 492,
+     * "top": 279,
+     * "width": 30,
+     * "height": 37
+     * },
+     * "words": "汉"
+     * }
+     * },
+     * "words_result_num": 6
+     * }
+     * <p>
+     * <p>
+     * {
+     * "words_result": {
+     * "失效日期": {
+     * "words": "20390711",
+     * "location": {
+     * "top": 445,
+     * "left": 523,
+     * "width": 153,
+     * "height": 38
+     * }
+     * },
+     * "签发机关": {
+     * "words": "陆丰市公安局",
+     * "location": {
+     * "top": 377,
+     * "left": 339,
+     * "width": 195,
+     * "height": 38
+     * }
+     * },
+     * "签发日期": {
+     * "words": "20190606",
+     * "location": {
+     * "top": 445,
+     * "left": 343,
+     * "width": 152,
+     * "height": 38
+     * }
+     * }
+     * },
+     * "log_id": "1559208562721579328",
+     * "words_result_num": 3,
+     * "error_code": 0,
+     * "image_status": "normal"
+     * }
+     *
+     * @return
+     */
+    public static AjaxResult idCard(String image, String idCardSide) {
+        try {
+            // 文件路径
+            byte[] imgData = FileUtil.readFileByBytes(image);
+
+            String imgStr = encryptImg(aesKey, imgData);
+
+            String imgParam = URLEncoder.encode(imgStr, "UTF-8");
+            String url = "https://aip.baidubce.com/rest/2.0/ocr/v1/idcard";
+            //-front:身份证含照片的一面
+            //-back:身份证带国徽的一面
+            //自动检测身份证正反面,如果传参指定方向与图片相反,支持正常识别,返回参数image_status字段为"reversed_side"
+            String param = "id_card_side=" + idCardSide +
+                    "&image=" + imgParam +
+                    "&AESEncry=" + true;
+            String accessToken = getAccessToken("TvvuZOOh7MgnlDFnw11ln67n", "CY47OI0eKAzYBD2LO55SM3OITzsyq6DK");
+            String encryptResult = HttpUtils.post(url, accessToken, param);
+            String decryptResult = parseResult(encryptResult);
+            JSONObject jsonObject = JSON.parseObject(decryptResult);
+            String wordsResult = jsonObject.getString("words_result");
+            Map<String, Object> map = new HashMap<>(3);
+            if (StringUtils.isNotEmpty(wordsResult)) {
+                JSONObject jsonObjectWordsResult = JSON.parseObject(wordsResult);
+                if (FRONT.equals(idCardSide)) {
+                    String name = JSON.parseObject(jsonObjectWordsResult.getString("姓名")).getString("words");
+                    String address = JSON.parseObject(jsonObjectWordsResult.getString("住址")).getString("words");
+                    String idCard = JSON.parseObject(jsonObjectWordsResult.getString("公民身份号码")).getString("words");
+                    map.put("realName", name);
+                    map.put("address", address);
+                    map.put("idCard", idCard);
+                } else if (BACK.equals(idCardSide)) {
+                    String date = JSON.parseObject(jsonObjectWordsResult.getString("失效日期")).getString("words");
+                    map.put("expirationDate", date);
+                }
+            }
+            return AjaxResult.success(map);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return AjaxResult.error();
+    }
+
+    /**
+     * 加密图片
+     *
+     * @param aesKey
+     * @param imgData
+     * @return
+     * @throws Exception
+     */
+    private static String encryptImg(String aesKey, byte[] imgData) throws Exception {
+        originAesKey = AesKeyUtil.parseAesKey(aesKey);
+        byte[] encImgBytes = AesUtil.encrypt(imgData, originAesKey);
+        String imgStr = Base64Util.encodeBase64(encImgBytes);
+        return imgStr;
+    }
+
+    /**
+     * 解密结果
+     *
+     * @param encryptResult
+     * @return
+     * @throws Exception
+     */
+    private static String parseResult(String encryptResult) throws Exception {
+        JSONObject obj = JSONObject.parseObject(encryptResult);
+        String result = obj.getString("result");
+        byte[] arr = Base64Util.decodeBase64(result);
+        String decryptResult = new String(AesUtil.decrypt(arr, originAesKey));
+        return decryptResult;
+    }
+
+
+    static class AesKeyUtil {
+        private static final String HEX = "0123456789abcdef";
+
+        /**
+         * 获得原生的128位的aeskey
+         * 因为一个byte位8位最后生成的byte数组长度为16
+         * <p>
+         * 16 * 8 = 128
+         *
+         * @param hex
+         * @return
+         */
+        public static byte[] parseAesKey(String hex) throws Exception {
+            char[] data = hex.toCharArray();
+            if (data.length != 16) {
+                throw new Exception(" ase key illegal ");
+            }
+            return decode(hex.toCharArray());
+        }
+
+        private static byte[] decode(char[] data) throws IllegalArgumentException {
+            int len = data.length;
+
+            byte[] out = new byte[len];
+
+            for (int i = 0; i < len; i++) {
+                int f = toDigit(data[i]);
+                out[i] = (byte) (f);
+            }
+            return out;
+        }
+
+        private static int toDigit(char ch) {
+            return HEX.indexOf(ch);
+        }
+    }
+
+    static class AesUtil {
+
+        private static final String ALGORITHM = "AES";
+
+        private static final String ALGORITHM_STR = "AES/ECB/PKCS5Padding";
+
+        /**
+         * aes 加密
+         */
+        private static byte[] encrypt(byte[] src, byte[] aesKey) throws Exception {
+            Cipher cipher = getCipher(aesKey, Cipher.ENCRYPT_MODE);
+            byte[] ret = cipher.doFinal(src);
+            return ret;
+        }
+
+        /**
+         * aes 解密
+         */
+        public static byte[] decrypt(byte[] src, byte[] aesKey) throws Exception {
+            Cipher cipher = getCipher(aesKey, Cipher.DECRYPT_MODE);
+            byte[] original = cipher.doFinal(src);
+            return original;
+        }
+
+        private static Cipher getCipher(byte[] aesKey, int mode) throws Exception {
+            SecretKeySpec secretKeySpec = new SecretKeySpec(aesKey, ALGORITHM);
+            Cipher cipher = Cipher.getInstance(ALGORITHM_STR);
+            cipher.init(mode, secretKeySpec);
+            return cipher;
+        }
+    }
+
+    static class Base64Util {
+
+        private static Base64.Encoder ENCODER = Base64.getEncoder();
+
+        // base64 加密
+        private static Base64.Decoder DECODER = Base64.getDecoder();
+
+        /**
+         * base64加密
+         *
+         * @param arr
+         * @return
+         */
+        private static String encodeBase64(byte[] arr) {
+            String base64 = null;
+            try {
+                base64 = ENCODER.encodeToString(arr);
+            } catch (Exception e) {
+            }
+            return base64;
+        }
+
+        /**
+         * base64解密
+         *
+         * @param str
+         * @return
+         */
+        public static byte[] decodeBase64(String str) {
+            byte[] encodeBase64 = new byte[0];
+            try {
+                encodeBase64 = DECODER.decode(str);
+            } catch (Exception e) {
+            }
+            return encodeBase64;
+        }
+    }
+
+
+    /**
+     * 人脸识别
+     *
+     * @throws IOException
+     */
+    public static AjaxResult sample(SysUserIdcardVo sysUserIdcardVo) {
+        String image = sysUserIdcardVo.getImage();
+        String idCardNumber = sysUserIdcardVo.getIdCard();
+        String name = sysUserIdcardVo.getRealName();
+        if (StringUtils.isEmpty(image) || StringUtils.isEmpty(idCardNumber) || StringUtils.isEmpty(name)) {
+            return AjaxResult.error("参数不完整");
+        }
+        // image 可以通过 getFileContentAsBase64("C:\fakepath\1675840208844.png") 方法获取,如果Content-Type是application/x-www-form-urlencoded时,第二个参数传true
+        HashMap<String, Object> paramMap = new HashMap<>();
+        paramMap.put("access_token", getAccessToken("OmTDmo9hv4wll1ReYiRNJEfz", "vqnio9p7C17nbrkVar0MrHU4KOz2OXOr"));
+        paramMap.put("image", getFileContentAsBase64(image, false));
+        paramMap.put("image_type", "BASE64");
+        paramMap.put("id_card_number", idCardNumber);
+        paramMap.put("name", name);
+        paramMap.put("spoofing_control", "NORMAL");
+        paramMap.put("quality_control", "NORMAL");
+        //返回值
+        String post = HttpUtil.post("https://aip.baidubce.com/rest/2.0/face/v4/mingjing/verify", paramMap);
+        /**
+         * {
+         *     "log_id": 1370579072568000512,
+         *     "result": {
+         *         "score": 40.884,
+         *         "verify_status": 0
+         *     },
+         *     "dec_image": "/9j/4AAQSkZJRgABAgAAAQABAAD",
+         *     "risk_level": "3",
+         *     "risk_tag": [
+         *         "若判断为有风险,则会有风险标签json 数组告知风险类型,如:general_inject"
+         *     ]
+         * }
+         */
+        JSONObject jsonObject = JSONObject.parseObject(post);
+        Map<String, Object> map = new HashMap<>();
+        return AjaxResult.success(map);
+    }
+
+    /**
+     * 获取文件base64编码
+     *
+     * @param path      文件路径
+     * @param urlEncode 如果Content-Type是application/x-www-form-urlencoded时,传true
+     * @return base64编码信息,不带文件头
+     * @throws IOException IO异常
+     */
+    static String getFileContentAsBase64(String path, boolean urlEncode) {
+        byte[] b = new byte[0];
+        try {
+            b = Files.readAllBytes(Paths.get(path));
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        String base64 = Base64.getEncoder().encodeToString(b);
+        if (urlEncode) {
+            try {
+                base64 = URLEncoder.encode(base64, "utf-8");
+            } catch (UnsupportedEncodingException e) {
+                e.printStackTrace();
+            }
+        }
+        return base64;
+    }
+
+    /**
+     * 获取百度开放平台的AccessToken
+     * client_id: 必须参数,应用的API Key;
+     * client_secret: 必须参数,应用的Secret Key;不同的功能不同
+     *
+     * @return
+     */
+    public static String getAccessToken(String apiKey, String secretKey) {
+        String url = "https://aip.baidubce.com/oauth/2.0/token";
+        /**
+         *grant_type: 必须参数,固定为client_credentials;
+         *client_id: 必须参数,应用的API Key;
+         *client_secret: 必须参数,应用的Secret Key;
+         */
+        HashMap<String, Object> paramMap = new HashMap<>(3);
+        paramMap.put("grant_type", "client_credentials");
+        paramMap.put("client_id", apiKey);
+        paramMap.put("client_secret", secretKey);
+        String post = cn.hutool.http.HttpUtil.post(url, paramMap);
+        JSONObject jsonObject = JSONObject.parseObject(post);
+        String accessToken = jsonObject.getString("access_token");
+        return accessToken;
+    }
+
+
+    /**
+     * 人脸识别阿里云金融级实人认证
+     *
+     * @throws IOException
+     */
+    public static AjaxResult ocrSampleAliYun(SysUserIdcardVo sysUserIdcardVo) {
+
+        return AjaxResult.success(initFaceVerify(sysUserIdcardVo));
+    }
+
+    /**
+     * 阿里云调用人脸识别
+     * 每次开始认证前通过本接口获取CertifyId,用来串联认证请求中的各个接口。
+     */
+    public static Object initFaceVerify(SysUserIdcardVo sysUserIdcardVo) {
+        InitFaceVerifyRequest request = new InitFaceVerifyRequest();
+        // 请输入场景ID+L。1000009979场景id固定值
+        request.setSceneId(1000009979L);
+        // 设置商户请求的唯一标识。
+        request.setOuterOrderNo(UUID.randomUUID().toString().replaceAll("-", ""));
+        // 认证方案。
+        request.setProductCode("ID_PRO");
+        // 模式。要进行活体检测的类型。取值:
+        //LIVENESS(默认):眨眼动作活体检测。
+        //
+        //PHOTINUS_LIVENESS:眨眼动作活体+炫彩活体双重检测。
+        //
+        //MULTI_ACTION:多动作活体检测。当前为眨眼+任意摇头检测。
+        request.setModel("PHOTINUS_LIVENESS");
+        //不同证件类型,取值均为IDENTITY_CARD
+        request.setCertType("IDENTITY_CARD");
+        //您的终端用户的真实姓名
+        request.setCertName(sysUserIdcardVo.getRealName());
+        //您的终端用户的证件号码。
+        request.setCertNo(sysUserIdcardVo.getIdCard());
+        // MetaInfo环境参数。
+        request.setMetaInfo(sysUserIdcardVo.getMetaInfo());
+        //业务页面回跳的目标地址。
+        String returnUrl = sysUserIdcardVo.getReturnUrl();
+        if (StringUtils.isNotEmpty(returnUrl)) {
+            //H5的model不一样
+            request.setModel("MULTI_ACTION");
+            request.setReturnUrl(returnUrl);
+        }
+        //request.setMobile("130xxxxxxxx");
+        //request.setIp("114.xxx.xxx.xxx");
+        //request.setUserId("12345xxxx");
+        //request.setCallbackUrl("https://www.aliyundoc.com");
+        //request.setCallbackToken("xxxxx");
+        // 如需开启个人信息加密传输。
+        //request.setEncryptType("SM2");
+        //request.setCertName("BCRD/7ZkNy7Q*****M1BMBezZe8GaYHrLwyJv558w==");
+        //request.setCertNo("BMjsstxK3S4b1YH*****Pet8ECObfxmLN92SLsNg==");
+
+        // 推荐,支持服务路由。
+        InitFaceVerifyResponse response = initFaceVerifyAutoRoute(request);
+
+        // 不支持服务自动路由。
+        //InitFaceVerifyResponse response = initFaceVerify("cloudauth.cn-shanghai.aliyuncs.com", request);
+
+        response.getBody().getRequestId();
+        String certifyId = response.getBody().getResultObject().getCertifyId();
+
+        InitFaceVerifyResponseBody.InitFaceVerifyResponseBodyResultObject resultObject = response.getBody().getResultObject();
+
+        System.out.println(response.getBody().getRequestId());
+        System.out.println("人脸识别跳转URL"+resultObject.getCertifyUrl());
+        System.out.println(response.getBody().getCode());
+        System.out.println(response.getBody().getMessage());
+        System.out.println(response.getBody().getResultObject() == null ? null
+                : response.getBody().getResultObject().getCertifyId());
+        return resultObject;
+    }
+
+    /**
+     * 阿里云调用人脸识别H5
+     * 每次开始认证前通过本接口获取CertifyId,用来串联认证请求中的各个接口。
+     */
+    public static InitFaceVerifyResponseBody.InitFaceVerifyResponseBodyResultObject initFaceVerifyH5(SysUserIdcardVo sysUserIdcardVo) {
+        InitFaceVerifyRequest request = new InitFaceVerifyRequest();
+        // 请输入场景ID+L。1000009979场景id固定值
+        request.setSceneId(1000009979L);
+        // 设置商户请求的唯一标识。
+        request.setOuterOrderNo(UUID.randomUUID().toString().replaceAll("-", ""));
+        // 认证方案。
+        request.setProductCode("ID_PRO");
+        // 模式。要进行活体检测的类型。取值:
+        //LIVENESS(默认):眨眼动作活体检测。
+        //
+        //PHOTINUS_LIVENESS:眨眼动作活体+炫彩活体双重检测。
+        //
+        //MULTI_ACTION:多动作活体检测。当前为眨眼+任意摇头检测。
+        request.setModel("PHOTINUS_LIVENESS");
+        //不同证件类型,取值均为IDENTITY_CARD
+        request.setCertType("IDENTITY_CARD");
+        //您的终端用户的真实姓名
+        request.setCertName(sysUserIdcardVo.getRealName());
+        //您的终端用户的证件号码。
+        request.setCertNo(sysUserIdcardVo.getIdCard());
+        // MetaInfo环境参数。
+        request.setMetaInfo(sysUserIdcardVo.getMetaInfo());
+        request.setCertifyUrlStyle("L");
+        //业务页面回跳的目标地址。
+        String returnUrl = sysUserIdcardVo.getReturnUrl();
+        if (StringUtils.isNotEmpty(returnUrl)) {
+            //H5的model不一样
+            request.setModel("MULTI_ACTION");
+            request.setReturnUrl(returnUrl);
+        }
+        //request.setMobile("130xxxxxxxx");
+        //request.setIp("114.xxx.xxx.xxx");
+        //request.setUserId("12345xxxx");
+        //request.setCallbackUrl("https://www.aliyundoc.com");
+        //request.setCallbackToken("xxxxx");
+        // 如需开启个人信息加密传输。
+        //request.setEncryptType("SM2");
+        //request.setCertName("BCRD/7ZkNy7Q*****M1BMBezZe8GaYHrLwyJv558w==");
+        //request.setCertNo("BMjsstxK3S4b1YH*****Pet8ECObfxmLN92SLsNg==");
+
+        // 推荐,支持服务路由。
+        InitFaceVerifyResponse response = initFaceVerifyAutoRoute(request);
+
+        // 不支持服务自动路由。
+        //InitFaceVerifyResponse response = initFaceVerify("cloudauth.cn-shanghai.aliyuncs.com", request);
+
+        InitFaceVerifyResponseBody.InitFaceVerifyResponseBodyResultObject resultObject = response.getBody().getResultObject();
+
+        System.out.println(response.getBody().getRequestId());
+        System.out.println(response.getBody().getCode());
+        System.out.println(response.getBody().getMessage());
+        System.out.println(response.getBody().getResultObject() == null ? null
+                : response.getBody().getResultObject().getCertifyId());
+        return resultObject;
+    }
+    private static InitFaceVerifyResponse initFaceVerifyAutoRoute(InitFaceVerifyRequest request) {
+        // 第一个为主区域Endpoint,第二个为备区域Endpoint。
+        List<String> endpoints = Arrays.asList("cloudauth.cn-shanghai.aliyuncs.com", "cloudauth.cn-beijing.aliyuncs.com");
+        InitFaceVerifyResponse lastResponse = null;
+        for (int i = 0; i < endpoints.size(); i++) {
+            try {
+                InitFaceVerifyResponse response = initFaceVerify(endpoints.get(i), request);
+                lastResponse = response;
+
+                // 服务端错误,切换到下个区域调用。
+                if (response != null) {
+                    if (500 == response.getStatusCode()) {
+                        continue;
+                    }
+                    if (response.getBody() != null) {
+                        if ("500".equals(response.getBody().getCode())) {
+                            continue;
+                        }
+                    }
+                }
+
+                // 正常返回
+                return lastResponse;
+            } catch (Exception e) {
+                e.printStackTrace();
+                if (i == endpoints.size() - 1) {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+
+        return lastResponse;
+    }
+
+    private static InitFaceVerifyResponse initFaceVerify(String endpoint, InitFaceVerifyRequest request)
+            throws Exception {
+        // 阿里云账号AccessKey拥有所有API的访问权限,建议您使用RAM用户进行API访问或日常运维。
+        // 强烈建议不要把AccessKey ID和AccessKey Secret保存到工程代码里,否则可能导致AccessKey泄露,威胁您账号下所有资源的安全。
+        // 本示例通过阿里云Credentials工具从环境变量中读取AccessKey,来实现API访问的身份验证。如何配置环境变量,请参见https://help.aliyun.com/document_detail/378657.html。
+        Config credentialConfig = new Config();
+        credentialConfig.setType("access_key");
+        //使用阿里云RAM用户的AK。最小的权限
+        credentialConfig.setAccessKeyId("LTAI5tCwkQRjuJYzbohaveEz");
+        credentialConfig.setAccessKeySecret("8VW0H8GL7hmm5td8cYxO49FcVLPzOj");
+        credentialConfig.setEndpoint(endpoint);
+        Client client = new Client(credentialConfig);
+        // 设置http代理。
+        //config.setHttpProxy("http://xx.xx.xx.xx:xxxx");
+        // 设置https代理。
+        //config.setHttpsProxy("https://xx.xx.xx.xx:xxxx");
+
+
+        // 创建RuntimeObject实例并设置运行参数。
+        RuntimeOptions runtime = new RuntimeOptions();
+        runtime.readTimeout = 10000;
+        runtime.connectTimeout = 10000;
+
+        return client.initFaceVerifyWithOptions(request, runtime);
+    }
+}