deason 2 vuotta sitten
vanhempi
commit
8cedb31a45

+ 18 - 58
examcloud-starters/examcloud-face-verify-starter/src/main/java/cn/com/qmth/examcloud/starters/face/verify/FaceVerifyProperties.java

@@ -22,21 +22,6 @@ public class FaceVerifyProperties implements Serializable {
      */
     private String facePlusSecret;
 
-    /*
-     * Face++ 人脸检测接口
-     */
-    private String facePlusFaceDetectUrl = "https://api-cn.faceplusplus.com/facepp/v3/detect";
-
-    /*
-     * Face++ 人脸比对接口
-     */
-    private String facePlusFaceCompareUrl = "https://api-cn.faceplusplus.com/facepp/v3/compare";
-
-    /*
-     * 百度 是否启用AccessToken
-     */
-    private Boolean baiduAccessTokenEnabled = true;
-
     /*
      * 百度 API Key
      */
@@ -48,20 +33,19 @@ public class FaceVerifyProperties implements Serializable {
     private String baiduSecret;
 
     /*
-     * 百度 认证接口
+     * 百度 私有化部署 是否启用
      */
-    private String baiduTokenUrl = "https://aip.baidubce.com/oauth/2.0/token";
+    private Boolean baiduLocalEnabled = false;
 
     /*
-     * 百度 人脸检测接口
+     * 百度 私有化部署 appId
      */
-    private String baiduFaceDetectUrl = "https://aip.baidubce.com/rest/2.0/face/v3/faceverify";
-    // private String baiduFaceDetectUrl = "https://aip.baidubce.com/rest/2.0/face/v3/detect";
+    private String baiduLocalAppId;
 
     /*
-     * 百度 人脸比对接口
+     * 百度 私有化部署 接口地址前缀
      */
-    private String baiduFaceCompareUrl = "https://aip.baidubce.com/rest/2.0/face/v3/match";
+    private String baiduLocalUrlPrefix;
 
     public String getFacePlusKey() {
         return facePlusKey;
@@ -79,30 +63,6 @@ public class FaceVerifyProperties implements Serializable {
         this.facePlusSecret = facePlusSecret;
     }
 
-    public String getFacePlusFaceDetectUrl() {
-        return facePlusFaceDetectUrl;
-    }
-
-    public void setFacePlusFaceDetectUrl(String facePlusFaceDetectUrl) {
-        this.facePlusFaceDetectUrl = facePlusFaceDetectUrl;
-    }
-
-    public String getFacePlusFaceCompareUrl() {
-        return facePlusFaceCompareUrl;
-    }
-
-    public void setFacePlusFaceCompareUrl(String facePlusFaceCompareUrl) {
-        this.facePlusFaceCompareUrl = facePlusFaceCompareUrl;
-    }
-
-    public Boolean getBaiduAccessTokenEnabled() {
-        return baiduAccessTokenEnabled;
-    }
-
-    public void setBaiduAccessTokenEnabled(Boolean baiduAccessTokenEnabled) {
-        this.baiduAccessTokenEnabled = baiduAccessTokenEnabled;
-    }
-
     public String getBaiduKey() {
         return baiduKey;
     }
@@ -119,28 +79,28 @@ public class FaceVerifyProperties implements Serializable {
         this.baiduSecret = baiduSecret;
     }
 
-    public String getBaiduTokenUrl() {
-        return baiduTokenUrl;
+    public Boolean getBaiduLocalEnabled() {
+        return baiduLocalEnabled;
     }
 
-    public void setBaiduTokenUrl(String baiduTokenUrl) {
-        this.baiduTokenUrl = baiduTokenUrl;
+    public void setBaiduLocalEnabled(Boolean baiduLocalEnabled) {
+        this.baiduLocalEnabled = baiduLocalEnabled;
     }
 
-    public String getBaiduFaceDetectUrl() {
-        return baiduFaceDetectUrl;
+    public String getBaiduLocalAppId() {
+        return baiduLocalAppId;
     }
 
-    public void setBaiduFaceDetectUrl(String baiduFaceDetectUrl) {
-        this.baiduFaceDetectUrl = baiduFaceDetectUrl;
+    public void setBaiduLocalAppId(String baiduLocalAppId) {
+        this.baiduLocalAppId = baiduLocalAppId;
     }
 
-    public String getBaiduFaceCompareUrl() {
-        return baiduFaceCompareUrl;
+    public String getBaiduLocalUrlPrefix() {
+        return baiduLocalUrlPrefix;
     }
 
-    public void setBaiduFaceCompareUrl(String baiduFaceCompareUrl) {
-        this.baiduFaceCompareUrl = baiduFaceCompareUrl;
+    public void setBaiduLocalUrlPrefix(String baiduLocalUrlPrefix) {
+        this.baiduLocalUrlPrefix = baiduLocalUrlPrefix;
     }
 
 }

+ 52 - 0
examcloud-starters/examcloud-face-verify-starter/src/main/java/cn/com/qmth/examcloud/starters/face/verify/common/Constants.java

@@ -0,0 +1,52 @@
+package cn.com.qmth.examcloud.starters.face.verify.common;
+
+public interface Constants {
+
+    String JSON_CONTENT_TYPE = "application/json; charset=utf-8";
+
+    /*
+     * Face++ 人脸检测接口
+     */
+    String FACEPP_FACE_DETECT_URL = "https://api-cn.faceplusplus.com/facepp/v3/detect";
+
+    /*
+     * Face++ 人脸比对接口
+     */
+    String FACEPP_FACE_COMPARE_URL = "https://api-cn.faceplusplus.com/facepp/v3/compare";
+
+    /*
+     * 百度 认证接口
+     */
+    String BAIDU_TOKEN_URL = "https://aip.baidubce.com/oauth/2.0/token";
+
+    /*
+     * 百度 人脸活体检测接口
+     */
+    String BAIDU_FACE_VERIFY_URL = "https://aip.baidubce.com/rest/2.0/face/v3/faceverify";
+
+    /*
+     * 百度 人脸检测接口
+     */
+    String BAIDU_FACE_DETECT_URL = "https://aip.baidubce.com/rest/2.0/face/v3/detect";
+
+    /*
+     * 百度 人脸比对接口
+     */
+    String BAIDU_FACE_COMPARE_URL = "https://aip.baidubce.com/rest/2.0/face/v3/match";
+
+    /*
+     * 百度 人脸活体检测接口(本地化部署)
+     */
+    String BAIDU_FACE_VERIFY_LOCAL_URL = "/face-api/v3/face/liveness";
+
+    /*
+     * 百度 人脸检测接口(本地化部署)
+     */
+    String BAIDU_FACE_DETECT_LOCAL_URL = "/face-api/v3/face/detect";
+
+    /*
+     * 百度 人脸比对接口(本地化部署)
+     */
+    String BAIDU_FACE_COMPARE_LOCAL_URL = "/face-api/v3/face/match";
+
+}

+ 22 - 0
examcloud-starters/examcloud-face-verify-starter/src/main/java/cn/com/qmth/examcloud/starters/face/verify/service/FaceVerifyService.java

@@ -2,6 +2,8 @@ package cn.com.qmth.examcloud.starters.face.verify.service;
 
 import cn.com.qmth.examcloud.starters.face.verify.common.FaceResult;
 
+import java.io.File;
+
 public interface FaceVerifyService {
 
     /**
@@ -9,19 +11,39 @@ public interface FaceVerifyService {
      */
     FaceResult faceDetectByBaidu(String imageUrl);
 
+    /**
+     * 百度 人脸检测
+     */
+    FaceResult faceDetectByBaidu(File imageFile);
+
     /**
      * 百度 人脸比对
      */
     FaceResult faceCompareByBaidu(String imageUrl, String imageUrl2);
 
+    /**
+     * 百度 人脸比对
+     */
+    FaceResult faceCompareByBaidu(File imageFile, File imageFile2);
+
     /**
      * Face++ 人脸检测
      */
     FaceResult faceDetectByFacePlus(String imageUrl);
 
+    /**
+     * Face++ 人脸检测
+     */
+    FaceResult faceDetectByFacePlus(File imageFile);
+
     /**
      * Face++ 人脸比对
      */
     FaceResult faceCompareByFacePlus(String imageUrl, String imageUrl2);
 
+    /**
+     * Face++ 人脸比对
+     */
+    FaceResult faceCompareByFacePlus(File imageFile, File imageFile2);
+
 }

+ 111 - 52
examcloud-starters/examcloud-face-verify-starter/src/main/java/cn/com/qmth/examcloud/starters/face/verify/service/baidu/BaiduApiHelper.java

@@ -1,5 +1,8 @@
 package cn.com.qmth.examcloud.starters.face.verify.service.baidu;
 
+import cn.com.qmth.examcloud.starters.face.verify.FaceVerifyProperties;
+import cn.com.qmth.examcloud.starters.face.verify.common.Constants;
+import cn.com.qmth.examcloud.starters.face.verify.common.FaceResult;
 import cn.com.qmth.examcloud.starters.face.verify.common.HttpClientBuilder;
 import cn.com.qmth.examcloud.starters.face.verify.common.JsonHelper;
 import okhttp3.*;
@@ -8,16 +11,13 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
 
 public class BaiduApiHelper {
 
     private final static Logger log = LoggerFactory.getLogger(BaiduApiHelper.class);
 
-    public static BaiduSession getAccessToken(String apiKey, String apiSecret, String apiUrl) {
+    public static BaiduSession getAccessToken(String apiKey, String apiSecret) {
         if (StringUtils.isBlank(apiKey)) {
             throw new IllegalArgumentException("Baidu apiKey must be not empty.");
         }
@@ -26,11 +26,7 @@ public class BaiduApiHelper {
             throw new IllegalArgumentException("Baidu apiSecret must be not empty.");
         }
 
-        if (StringUtils.isBlank(apiUrl)) {
-            throw new IllegalArgumentException("Baidu apiUrl must be not empty.");
-        }
-
-        String requestUrl = String.format("%s?grant_type=client_credentials&client_id=%s&client_secret=%s", apiUrl, apiKey, apiSecret);
+        String requestUrl = String.format("%s?grant_type=client_credentials&client_id=%s&client_secret=%s", Constants.BAIDU_TOKEN_URL, apiKey, apiSecret);
         Request.Builder request = new Request.Builder().url(requestUrl).get();
 
         try (Response response = HttpClientBuilder.getClient().newCall(request.build()).execute();
@@ -52,66 +48,128 @@ public class BaiduApiHelper {
         }
     }
 
-    public static BaiduResponse faceDetect(String accessToken, String apiUrl, String imageUrl) {
-        if (StringUtils.isBlank(apiUrl)) {
-            throw new IllegalArgumentException("Baidu apiUrl must be not empty.");
+    public static FaceResult faceDetect(FaceVerifyProperties properties, String jsonParams) {
+        if (StringUtils.isBlank(jsonParams)) {
+            throw new IllegalArgumentException("Baidu api params must be not empty.");
         }
 
-        if (StringUtils.isBlank(imageUrl)) {
-            throw new IllegalArgumentException("face imageUrl must be not empty.");
+        BaiduSession session = BaiduApiHelper.getAccessToken(properties.getBaiduKey(), properties.getBaiduSecret());
+        String accessToken = session.getAccess_token();
+
+        String requestUrl = String.format("%s?access_token=%s", Constants.BAIDU_FACE_DETECT_URL, accessToken);
+        RequestBody formBody = FormBody.create(MediaType.parse(Constants.JSON_CONTENT_TYPE), jsonParams);
+
+        BaiduResponse response = callApi(requestUrl, formBody);
+        return parseFaceDetectResult(response);
+    }
+
+    public static FaceResult faceDetectUseLocalApi(FaceVerifyProperties properties, String jsonParams) {
+        if (StringUtils.isBlank(jsonParams)) {
+            throw new IllegalArgumentException("Baidu api params must be not empty.");
         }
 
-        List<Map<String, String>> params = new ArrayList<>();
-        Map<String, String> imageData = new TreeMap<>();
-        // image_type: URL、BASE64、FACE_TOKEN
-        imageData.put("image_type", "URL");
-        imageData.put("image", imageUrl);
-        params.add(imageData);
-        String jsonParams = new JsonHelper().toJson(params);
+        String requestUrl = String.format("%s?appid=%s", properties.getBaiduLocalUrlPrefix() + Constants.BAIDU_FACE_DETECT_LOCAL_URL, properties.getBaiduLocalAppId());
+        RequestBody formBody = FormBody.create(MediaType.parse(Constants.JSON_CONTENT_TYPE), jsonParams);
 
-        final String contentType = "application/json; charset=utf-8";
-        RequestBody formBody = FormBody.create(MediaType.parse(contentType), jsonParams);
+        BaiduResponse response = callApi(requestUrl, formBody);
+        return parseFaceDetectResult(response);
+    }
 
-        String requestUrl = String.format("%s?access_token=%s", apiUrl, accessToken);
-        Request.Builder request = new Request.Builder().url(requestUrl).post(formBody);
+    public static FaceResult faceCompare(FaceVerifyProperties properties, String jsonParams) {
+        if (StringUtils.isBlank(jsonParams)) {
+            throw new IllegalArgumentException("Baidu api params must be not empty.");
+        }
 
-        return callApi(apiUrl, request);
+        BaiduSession session = BaiduApiHelper.getAccessToken(properties.getBaiduKey(), properties.getBaiduSecret());
+        String accessToken = session.getAccess_token();
+
+        String requestUrl = String.format("%s?access_token=%s", Constants.BAIDU_FACE_COMPARE_URL, accessToken);
+        RequestBody formBody = FormBody.create(MediaType.parse(Constants.JSON_CONTENT_TYPE), jsonParams);
+
+        BaiduResponse response = callApi(requestUrl, formBody);
+        return parseFaceCompareResult(response);
     }
 
-    public static BaiduResponse faceCompare(String accessToken, String apiUrl, String imageUrl, String imageUrl2) {
-        if (StringUtils.isBlank(apiUrl)) {
-            throw new IllegalArgumentException("Baidu apiUrl must be not empty.");
+    public static FaceResult faceCompareUseLocalApi(FaceVerifyProperties properties, String jsonParams) {
+        if (StringUtils.isBlank(jsonParams)) {
+            throw new IllegalArgumentException("Baidu api params must be not empty.");
+        }
+
+        String requestUrl = String.format("%s?appid=%s", properties.getBaiduLocalUrlPrefix() + Constants.BAIDU_FACE_COMPARE_LOCAL_URL, properties.getBaiduLocalAppId());
+        RequestBody formBody = FormBody.create(MediaType.parse(Constants.JSON_CONTENT_TYPE), jsonParams);
+
+        BaiduResponse response = callApi(requestUrl, formBody);
+        return parseFaceCompareResult(response);
+    }
+
+    private static FaceResult parseFaceDetectResult(BaiduResponse response) {
+        FaceResult result = new FaceResult();
+        result.setPass(false);
+        result.setApiLimit(false);
+
+        if (response.getError_code() != 0) {
+            result.setError(response.getError_code() + " - " + response.getError_msg());
+
+            // 请求并发超限(约10个/秒)
+            if (response.getError_code() == 18) {
+                result.setApiLimit(true);
+            }
+            return result;
         }
 
-        if (StringUtils.isBlank(imageUrl)) {
-            throw new IllegalArgumentException("face imageUrl must be not empty.");
+        BaiduResult data = response.getResult();
+        if (data != null) {
+            double faceLiveness = data.getFace_liveness() != null ? data.getFace_liveness() : 0d;
+
+            result.setScore(faceLiveness);
+            result.setThresholds(new JsonHelper().toJson(data.getThresholds()));
+
+            if (data.getFace_num() != null) {
+                result.setFaceNum(data.getFace_num());
+            } else {
+                List<BaiduFace> faces = data.getFace_list();
+                result.setFaceNum(faces != null ? faces.size() : 0);
+            }
+
+            // 活体分数值,推荐阈值0.393241
+            if (faceLiveness >= 0.39d) {
+                result.setPass(true);
+            }
         }
 
-        if (StringUtils.isBlank(imageUrl2)) {
-            throw new IllegalArgumentException("face imageUrl2 must be not empty.");
+        return result;
+    }
+
+    private static FaceResult parseFaceCompareResult(BaiduResponse response) {
+        FaceResult result = new FaceResult();
+        result.setPass(false);
+        result.setApiLimit(false);
+
+        if (response.getError_code() != 0) {
+            result.setError(response.getError_code() + " - " + response.getError_msg());
+
+            // 请求并发超限(约10个/秒)
+            if (response.getError_code() == 18) {
+                result.setApiLimit(true);
+            }
+            return result;
         }
 
-        List<Map<String, String>> params = new ArrayList<>();
-        Map<String, String> imageData = new TreeMap<>();
-        imageData.put("image_type", "URL");
-        imageData.put("image", imageUrl);
-        params.add(imageData);
-        Map<String, String> imageData2 = new TreeMap<>();
-        imageData2.put("image_type", "URL");
-        imageData2.put("image", imageUrl2);
-        params.add(imageData2);
-        String jsonParams = new JsonHelper().toJson(params);
-
-        final String contentType = "application/json; charset=utf-8";
-        RequestBody formBody = FormBody.create(MediaType.parse(contentType), jsonParams);
-
-        String requestUrl = String.format("%s?access_token=%s", apiUrl, accessToken);
-        Request.Builder request = new Request.Builder().url(requestUrl).post(formBody);
+        BaiduResult data = response.getResult();
+        if (data != null) {
+            result.setScore(data.getScore());
 
-        return callApi(apiUrl, request);
+            // 人脸相似度得分,推荐阈值80分
+            if (data.getScore() != null && data.getScore() >= 80d) {
+                result.setPass(true);
+            }
+        }
+
+        return result;
     }
 
-    private static BaiduResponse callApi(String apiUrl, Request.Builder request) {
+    private static BaiduResponse callApi(String requestUrl, RequestBody formBody) {
+        Request.Builder request = new Request.Builder().url(requestUrl).post(formBody);
         try (Response response = HttpClientBuilder.getClient().newCall(request.build()).execute();
              ResponseBody body = response.body();) {
             String bodyStr = body.string();
@@ -125,10 +183,11 @@ public class BaiduApiHelper {
                 }
             }
 
-            log.warn("[baidu] url:{} response:{} body:{}", apiUrl, response.code(), bodyStr);
+            log.warn("[baidu] url:{} response:{} body:{}", requestUrl, response.code(), bodyStr);
         } catch (IOException e) {
             log.error(e.getMessage(), e);
         }
+
         throw new RuntimeException("[baidu] 接口调用异常!");
     }
 

+ 71 - 42
examcloud-starters/examcloud-face-verify-starter/src/main/java/cn/com/qmth/examcloud/starters/face/verify/service/faceplus/FacePlusApiHelper.java

@@ -1,77 +1,105 @@
 package cn.com.qmth.examcloud.starters.face.verify.service.faceplus;
 
+import cn.com.qmth.examcloud.starters.face.verify.FaceVerifyProperties;
+import cn.com.qmth.examcloud.starters.face.verify.common.Constants;
+import cn.com.qmth.examcloud.starters.face.verify.common.FaceResult;
 import cn.com.qmth.examcloud.starters.face.verify.common.HttpClientBuilder;
 import cn.com.qmth.examcloud.starters.face.verify.common.JsonHelper;
-import okhttp3.*;
+import okhttp3.FormBody;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
 import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
+import java.util.Map;
 
 public class FacePlusApiHelper {
 
     private final static Logger log = LoggerFactory.getLogger(FacePlusApiHelper.class);
 
-    public static FacePlusResponse faceDetect(String apiKey, String apiSecret, String apiUrl, String imageUrl) {
-        if (StringUtils.isBlank(apiKey)) {
-            throw new IllegalArgumentException("Face++ apiKey must be not empty.");
-        }
+    public static FaceResult faceDetect(FaceVerifyProperties properties, Map<String, String> params) {
+        FormBody.Builder formBody = new FormBody.Builder();
+        formBody.add("api_key", properties.getFacePlusKey());
+        formBody.add("api_secret", properties.getFacePlusSecret());
 
-        if (StringUtils.isBlank(apiSecret)) {
-            throw new IllegalArgumentException("Face++ apiSecret must be not empty.");
+        for (Map.Entry<String, String> entry : params.entrySet()) {
+            formBody.add(entry.getKey(), entry.getValue());
         }
 
-        if (StringUtils.isBlank(apiUrl)) {
-            throw new IllegalArgumentException("Face++ apiUrl must be not empty.");
-        }
+        Request.Builder request = new Request.Builder().url(Constants.FACEPP_FACE_DETECT_URL).post(formBody.build());
+        FacePlusResponse response = callApi(Constants.FACEPP_FACE_DETECT_URL, request);
+
+        FaceResult result = new FaceResult();
+        result.setPass(false);
+        result.setApiLimit(false);
 
-        if (StringUtils.isBlank(imageUrl)) {
-            throw new IllegalArgumentException("face imageUrl must be not empty.");
+        if (StringUtils.isNotEmpty(response.getError_message())) {
+            result.setError(response.getError_message());
+
+            // 请求并发是否超限(约20个/秒)
+            if ("CONCURRENCY_LIMIT_EXCEEDED".equals(response.getError_message())) {
+                result.setApiLimit(true);
+            }
+            return result;
         }
 
-        MultipartBody.Builder formBody = new MultipartBody.Builder().setType(MultipartBody.FORM)
-                .addFormDataPart("api_key", apiKey)
-                .addFormDataPart("api_secret", apiSecret)
-                .addFormDataPart("image_url", imageUrl);
-        Request.Builder request = new Request.Builder().url(apiUrl).post(formBody.build());
+        int faceNum = response.getFace_num() != null ? response.getFace_num() : 0;
+        result.setFaceNum(faceNum);
 
-        return callApi(apiUrl, request);
+        if (faceNum == 1) {
+            // 是否人脸检测通过 (只有一张人脸算成功,否则算失败)
+            result.setPass(true);
+        }
+
+        return result;
     }
 
-    public static FacePlusResponse faceCompare(String apiKey, String apiSecret, String apiUrl, String imageUrl, String imageUrl2) {
-        if (StringUtils.isBlank(apiKey)) {
-            throw new IllegalArgumentException("Face++ apiKey must be not empty.");
-        }
+    public static FaceResult faceCompare(FaceVerifyProperties properties, Map<String, String> params) {
+        FormBody.Builder formBody = new FormBody.Builder();
+        formBody.add("api_key", properties.getFacePlusKey());
+        formBody.add("api_secret", properties.getFacePlusSecret());
 
-        if (StringUtils.isBlank(apiSecret)) {
-            throw new IllegalArgumentException("Face++ apiSecret must be not empty.");
+        for (Map.Entry<String, String> entry : params.entrySet()) {
+            formBody.add(entry.getKey(), entry.getValue());
         }
 
-        if (StringUtils.isBlank(apiUrl)) {
-            throw new IllegalArgumentException("Face++ apiUrl must be not empty.");
-        }
+        Request.Builder request = new Request.Builder().url(Constants.FACEPP_FACE_COMPARE_URL).post(formBody.build());
+        FacePlusResponse response = callApi(Constants.FACEPP_FACE_COMPARE_URL, request);
 
-        if (StringUtils.isBlank(imageUrl)) {
-            throw new IllegalArgumentException("face imageUrl must be not empty.");
-        }
+        FaceResult result = new FaceResult();
+        result.setPass(false);
+        result.setApiLimit(false);
+
+        if (StringUtils.isNotEmpty(response.getError_message())) {
+            result.setError(response.getError_message());
 
-        if (StringUtils.isBlank(imageUrl2)) {
-            throw new IllegalArgumentException("face imageUrl2 must be not empty.");
+            // 请求并发是否超限(约20个/秒)
+            if ("CONCURRENCY_LIMIT_EXCEEDED".equals(response.getError_message())) {
+                result.setApiLimit(true);
+            }
+            return result;
         }
 
-        RequestBody formBody = new FormBody.Builder()
-                .add("api_key", apiKey)
-                .add("api_secret", apiSecret)
-                .add("image_url1", imageUrl)
-                .add("image_url2", imageUrl2)
-                .build();
-        Request.Builder request = new Request.Builder().url(apiUrl).post(formBody);
+        FacePlusThresholds thresholds = response.getThresholds();
+        if (thresholds != null) {
+            float confidence = response.getConfidence() != null ? response.getConfidence() : 0f;
+
+            result.setScore((double) confidence);
+            result.setThresholds(new JsonHelper().toJson(thresholds));
 
-        return callApi(apiUrl, request);
+            // 是否人脸对比通过
+            if (confidence >= thresholds.getLe_4()) {
+                result.setPass(true);
+            }
+        }
+
+        return result;
     }
 
-    private static FacePlusResponse callApi(String apiUrl, Request.Builder request) {
+    private static FacePlusResponse callApi(String requestUrl, Request.Builder request) {
         try (Response response = HttpClientBuilder.getClient().newCall(request.build()).execute();
              ResponseBody body = response.body();) {
             String bodyStr = body.string();
@@ -85,10 +113,11 @@ public class FacePlusApiHelper {
                 }
             }
 
-            log.warn("[Face++] url:{} response:{} body:{}", apiUrl, response.code(), bodyStr);
+            log.warn("[Face++] url:{} response:{} body:{}", requestUrl, response.code(), bodyStr);
         } catch (IOException e) {
             log.error(e.getMessage(), e);
         }
+
         throw new RuntimeException("[Face++] 接口调用异常!");
     }
 

+ 82 - 120
examcloud-starters/examcloud-face-verify-starter/src/main/java/cn/com/qmth/examcloud/starters/face/verify/service/impl/FaceVerifyServiceImpl.java

@@ -1,18 +1,20 @@
 package cn.com.qmth.examcloud.starters.face.verify.service.impl;
 
 import cn.com.qmth.examcloud.starters.face.verify.FaceVerifyProperties;
+import cn.com.qmth.examcloud.starters.face.verify.common.CommonUtils;
 import cn.com.qmth.examcloud.starters.face.verify.common.FaceResult;
 import cn.com.qmth.examcloud.starters.face.verify.common.JsonHelper;
 import cn.com.qmth.examcloud.starters.face.verify.service.FaceVerifyService;
-import cn.com.qmth.examcloud.starters.face.verify.service.baidu.*;
+import cn.com.qmth.examcloud.starters.face.verify.service.baidu.BaiduApiHelper;
 import cn.com.qmth.examcloud.starters.face.verify.service.faceplus.FacePlusApiHelper;
-import cn.com.qmth.examcloud.starters.face.verify.service.faceplus.FacePlusResponse;
-import cn.com.qmth.examcloud.starters.face.verify.service.faceplus.FacePlusThresholds;
-import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.File;
+import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
 
 public class FaceVerifyServiceImpl implements FaceVerifyService {
 
@@ -22,149 +24,109 @@ public class FaceVerifyServiceImpl implements FaceVerifyService {
 
     @Override
     public FaceResult faceDetectByBaidu(String imageUrl) {
-        String accessToken = null;
-        if (properties.getBaiduAccessTokenEnabled()) {
-            BaiduSession session = BaiduApiHelper.getAccessToken(properties.getBaiduKey(), properties.getBaiduSecret(), properties.getBaiduTokenUrl());
-            accessToken = session.getAccess_token();
-        }
-
-        BaiduResponse response = BaiduApiHelper.faceDetect(accessToken, properties.getBaiduFaceDetectUrl(), imageUrl);
-
-        FaceResult result = new FaceResult();
-        result.setPass(false);
-        result.setApiLimit(false);
-
-        if (response.getError_code() != 0) {
-            result.setError(response.getError_code() + " - " + response.getError_msg());
+        // image_type: URL、BASE64、FACE_TOKEN,其中“本地化部署”不支持 URL 方式
+        Map<String, String> imageData = new TreeMap<>();
+        imageData.put("image_type", "URL");
+        imageData.put("image", imageUrl);
 
-            // 请求并发超限(约10个/秒)
-            if (response.getError_code() == 18) {
-                result.setApiLimit(true);
-            }
-            return result;
-        }
-
-        BaiduResult data = response.getResult();
-        if (data != null) {
-            double faceLiveness = data.getFace_liveness() != null ? data.getFace_liveness() : 0d;
+        String params = new JsonHelper().toJson(imageData);
+        return BaiduApiHelper.faceDetect(properties, params);
+    }
 
-            result.setScore(faceLiveness);
-            result.setThresholds(new JsonHelper().toJson(data.getThresholds()));
+    @Override
+    public FaceResult faceDetectByBaidu(File imageFile) {
+        String imageBase64 = CommonUtils.toBase64(imageFile);
 
-            if (data.getFace_num() != null) {
-                result.setFaceNum(data.getFace_num());
-            } else {
-                List<BaiduFace> faces = data.getFace_list();
-                result.setFaceNum(faces != null ? faces.size() : 0);
-            }
+        Map<String, String> imageData = new TreeMap<>();
+        imageData.put("image_type", "BASE64");
+        imageData.put("image", imageBase64);
 
-            // 活体分数值,推荐阈值0.393241
-            if (faceLiveness >= 0.39d) {
-                result.setPass(true);
-            }
+        String params = new JsonHelper().toJson(imageData);
+        if (properties.getBaiduLocalEnabled()) {
+            return BaiduApiHelper.faceDetectUseLocalApi(properties, params);
         }
-
-        return result;
+        return BaiduApiHelper.faceDetect(properties, params);
     }
 
     @Override
     public FaceResult faceCompareByBaidu(String imageUrl, String imageUrl2) {
-        String accessToken = null;
-        if (properties.getBaiduAccessTokenEnabled()) {
-            BaiduSession session = BaiduApiHelper.getAccessToken(properties.getBaiduKey(), properties.getBaiduSecret(), properties.getBaiduTokenUrl());
-            accessToken = session.getAccess_token();
-        }
-
-        BaiduResponse response = BaiduApiHelper.faceCompare(accessToken, properties.getBaiduFaceCompareUrl(), imageUrl, imageUrl2);
-
-        FaceResult result = new FaceResult();
-        result.setPass(false);
-        result.setApiLimit(false);
-
-        if (response.getError_code() != 0) {
-            result.setError(response.getError_code() + " - " + response.getError_msg());
-
-            // 请求并发超限(约10个/秒)
-            if (response.getError_code() == 18) {
-                result.setApiLimit(true);
-            }
-            return result;
-        }
-
-        BaiduResult data = response.getResult();
-        if (data != null) {
-            result.setScore(data.getScore());
+        List<Map<String, String>> images = new ArrayList<>();
+        Map<String, String> imageData = new TreeMap<>();
+        imageData.put("image_type", "URL");
+        imageData.put("image", imageUrl);
+        images.add(imageData);
+
+        Map<String, String> imageData2 = new TreeMap<>();
+        imageData2.put("image_type", "URL");
+        imageData2.put("image", imageUrl2);
+        images.add(imageData2);
+
+        String params = new JsonHelper().toJson(images);
+        return BaiduApiHelper.faceCompare(properties, params);
+    }
 
-            // 人脸相似度得分,推荐阈值80分
-            if (data.getScore() != null && data.getScore() >= 80d) {
-                result.setPass(true);
-            }
+    @Override
+    public FaceResult faceCompareByBaidu(File imageFile, File imageFile2) {
+        String imageBase64 = CommonUtils.toBase64(imageFile);
+        String imageBase64_2 = CommonUtils.toBase64(imageFile2);
+
+        List<Map<String, String>> images = new ArrayList<>();
+        Map<String, String> imageData = new TreeMap<>();
+        imageData.put("image_type", "BASE64");
+        imageData.put("image", imageBase64);
+        images.add(imageData);
+
+        Map<String, String> imageData2 = new TreeMap<>();
+        imageData2.put("image_type", "BASE64");
+        imageData2.put("image", imageBase64_2);
+        images.add(imageData2);
+
+        String params = new JsonHelper().toJson(images);
+        if (properties.getBaiduLocalEnabled()) {
+            return BaiduApiHelper.faceCompareUseLocalApi(properties, params);
         }
-
-        return result;
+        return BaiduApiHelper.faceCompare(properties, params);
     }
 
     @Override
     public FaceResult faceDetectByFacePlus(String imageUrl) {
-        FacePlusResponse response = FacePlusApiHelper.faceDetect(properties.getFacePlusKey(), properties.getFacePlusSecret(), properties.getFacePlusFaceDetectUrl(), imageUrl);
-
-        FaceResult result = new FaceResult();
-        result.setPass(false);
-        result.setApiLimit(false);
+        // 参数:image_url、image_base64、image_file
+        Map<String, String> params = new TreeMap<>();
+        params.put("image_url", imageUrl);
 
-        if (StringUtils.isNotEmpty(response.getError_message())) {
-            result.setError(response.getError_message());
-
-            // 请求并发是否超限(约20个/秒)
-            if ("CONCURRENCY_LIMIT_EXCEEDED".equals(response.getError_message())) {
-                result.setApiLimit(true);
-            }
-            return result;
-        }
+        return FacePlusApiHelper.faceDetect(properties, params);
+    }
 
-        int faceNum = response.getFace_num() != null ? response.getFace_num() : 0;
-        result.setFaceNum(faceNum);
+    @Override
+    public FaceResult faceDetectByFacePlus(File imageFile) {
+        String imageBase64 = CommonUtils.toBase64(imageFile);
 
-        if (faceNum == 1) {
-            // 是否人脸检测通过 (只有一张人脸算成功,否则算失败)
-            result.setPass(true);
-        }
+        Map<String, String> params = new TreeMap<>();
+        params.put("image_base64", imageBase64);
 
-        return result;
+        return FacePlusApiHelper.faceDetect(properties, params);
     }
 
     @Override
     public FaceResult faceCompareByFacePlus(String imageUrl, String imageUrl2) {
-        FacePlusResponse response = FacePlusApiHelper.faceCompare(properties.getFacePlusKey(), properties.getFacePlusSecret(), properties.getFacePlusFaceCompareUrl(), imageUrl, imageUrl2);
-
-        FaceResult result = new FaceResult();
-        result.setPass(false);
-        result.setApiLimit(false);
-
-        if (StringUtils.isNotEmpty(response.getError_message())) {
-            result.setError(response.getError_message());
-
-            // 请求并发是否超限(约20个/秒)
-            if ("CONCURRENCY_LIMIT_EXCEEDED".equals(response.getError_message())) {
-                result.setApiLimit(true);
-            }
-            return result;
-        }
+        // 参数:image_url、image_base64、image_file、face_token
+        Map<String, String> params = new TreeMap<>();
+        params.put("image_url1", imageUrl);
+        params.put("image_url2", imageUrl2);
 
-        FacePlusThresholds thresholds = response.getThresholds();
-        if (thresholds != null) {
-            float confidence = response.getConfidence() != null ? response.getConfidence() : 0f;
+        return FacePlusApiHelper.faceCompare(properties, params);
+    }
 
-            result.setScore((double) confidence);
-            result.setThresholds(new JsonHelper().toJson(thresholds));
+    @Override
+    public FaceResult faceCompareByFacePlus(File imageFile, File imageFile2) {
+        String imageBase64 = CommonUtils.toBase64(imageFile);
+        String imageBase64_2 = CommonUtils.toBase64(imageFile2);
 
-            // 是否人脸对比通过
-            if (confidence >= thresholds.getLe_4()) {
-                result.setPass(true);
-            }
-        }
+        Map<String, String> params = new TreeMap<>();
+        params.put("image_base64_1", imageBase64);
+        params.put("image_base64_2", imageBase64_2);
 
-        return result;
+        return FacePlusApiHelper.faceCompare(properties, params);
     }
 
     public void setProperties(FaceVerifyProperties properties) {

+ 17 - 6
examcloud-starters/examcloud-face-verify-starter/src/test/java/cn/com/qmth/examcloud/starters/face/verify/test/FaceVerifyTest.java

@@ -6,16 +6,20 @@ import cn.com.qmth.examcloud.starters.face.verify.common.JsonHelper;
 import cn.com.qmth.examcloud.starters.face.verify.service.impl.FaceVerifyServiceImpl;
 import org.junit.Test;
 
+import java.io.File;
+
 public class FaceVerifyTest {
 
     @Test
     public void demo() {
         FaceVerifyProperties properties = new FaceVerifyProperties();
-        // properties.setBaiduAccessTokenEnabled(false);
-        properties.setBaiduKey("xxx");
-        properties.setBaiduSecret("xxx");
-        properties.setFacePlusKey("xxx");
-        properties.setFacePlusSecret("xxx");
+        properties.setFacePlusKey("VOlRKNlCSAYIOcSLDKOaZukkqpmi-Pwo");
+        properties.setFacePlusSecret("bzMjy-JfwtVUxVDMzagFh7ggbQBC71f1");
+        properties.setBaiduKey("WkMttisvQVrqDSSSDhU2Un9X");
+        properties.setBaiduSecret("Zb83wGDg75yrU4oOkbkeQGW3KlOSHsMm");
+        properties.setBaiduLocalEnabled(true);
+        properties.setBaiduLocalAppId("test");
+        properties.setBaiduLocalUrlPrefix("http://192.168.10.102:8301");
 
         FaceVerifyServiceImpl faceVerifyService = new FaceVerifyServiceImpl();
         faceVerifyService.setProperties(properties);
@@ -25,10 +29,17 @@ public class FaceVerifyTest {
         String imageUrl = "http://5b0988e595225.cdn.sohucs.com/images/20171017/cbdb993563a94a45ab5f99202b0c9c52.jpeg";
         String imageUrl2 = "https://pix2.tvzhe.com/thumb/star/32/524/260x346.jpg";
 
+        File imageFile1 = new File("D:\\home\\test\\fds.jpg");
+        File imageFile2 = new File("D:\\home\\test\\app100001.jpg");
+
         FaceResult result = faceVerifyService.faceDetectByBaidu(imageUrl);
-        // FaceResult result = faceVerifyService.faceDetectByFacePlus(imageUrl);
+        // FaceResult result = faceVerifyService.faceDetectByBaidu(imageFile1);
         // FaceResult result = faceVerifyService.faceCompareByBaidu(imageUrl, imageUrl2);
+        // FaceResult result = faceVerifyService.faceCompareByBaidu(imageFile1, imageFile2);
+        // FaceResult result = faceVerifyService.faceDetectByFacePlus(imageUrl);
+        // FaceResult result = faceVerifyService.faceDetectByFacePlus(imageFile1);
         // FaceResult result = faceVerifyService.faceCompareByFacePlus(imageUrl, imageUrl2);
+        // FaceResult result = faceVerifyService.faceCompareByFacePlus(imageFile1, imageFile2);
 
         System.out.println("==================================================");
         System.out.println(new JsonHelper().toJson(result));