deason 2 rokov pred
rodič
commit
f5fea41fa6
13 zmenil súbory, kde vykonal 384 pridanie a 273 odobranie
  1. 10 2
      examcloud-starters/examcloud-face-verify-starter/src/main/java/cn/com/qmth/examcloud/starters/face/verify/common/CommonUtils.java
  2. 90 0
      examcloud-starters/examcloud-face-verify-starter/src/main/java/cn/com/qmth/examcloud/starters/face/verify/common/FaceResult.java
  3. 0 37
      examcloud-starters/examcloud-face-verify-starter/src/main/java/cn/com/qmth/examcloud/starters/face/verify/common/IResult.java
  4. 5 5
      examcloud-starters/examcloud-face-verify-starter/src/main/java/cn/com/qmth/examcloud/starters/face/verify/service/FaceVerifyService.java
  5. 21 22
      examcloud-starters/examcloud-face-verify-starter/src/main/java/cn/com/qmth/examcloud/starters/face/verify/service/baidu/BaiduApiHelper.java
  6. 11 1
      examcloud-starters/examcloud-face-verify-starter/src/main/java/cn/com/qmth/examcloud/starters/face/verify/service/baidu/BaiduFace.java
  7. 4 77
      examcloud-starters/examcloud-face-verify-starter/src/main/java/cn/com/qmth/examcloud/starters/face/verify/service/baidu/BaiduResponse.java
  8. 21 17
      examcloud-starters/examcloud-face-verify-starter/src/main/java/cn/com/qmth/examcloud/starters/face/verify/service/baidu/BaiduResult.java
  9. 9 8
      examcloud-starters/examcloud-face-verify-starter/src/main/java/cn/com/qmth/examcloud/starters/face/verify/service/faceplus/FacePlusApiHelper.java
  10. 1 1
      examcloud-starters/examcloud-face-verify-starter/src/main/java/cn/com/qmth/examcloud/starters/face/verify/service/faceplus/FacePlusFace.java
  11. 68 84
      examcloud-starters/examcloud-face-verify-starter/src/main/java/cn/com/qmth/examcloud/starters/face/verify/service/faceplus/FacePlusResponse.java
  12. 131 11
      examcloud-starters/examcloud-face-verify-starter/src/main/java/cn/com/qmth/examcloud/starters/face/verify/service/impl/FaceVerifyServiceImpl.java
  13. 13 8
      examcloud-starters/examcloud-face-verify-starter/src/test/java/cn/com/qmth/examcloud/starters/face/verify/test/FaceVerifyTest.java

+ 10 - 2
examcloud-starters/examcloud-face-verify-starter/src/main/java/cn/com/qmth/examcloud/starters/face/verify/common/CommonUtils.java

@@ -11,14 +11,22 @@ import java.io.InputStream;
 import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
 import java.nio.file.Files;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 public class CommonUtils {
 
     private final static Logger log = LoggerFactory.getLogger(CommonUtils.class);
 
     public static String toBase64(File imageFile) {
-        if (imageFile == null) {
-            return "";
+        if (imageFile == null || !imageFile.exists()) {
+            throw new IllegalArgumentException("文件不存在!");
+        }
+
+        String regex = "\\.(?:jpg|jpeg|png)$";
+        Matcher matcher = Pattern.compile(regex).matcher(imageFile.getName().toLowerCase());
+        if (!matcher.find()) {
+            throw new IllegalArgumentException("文件格式不正确,仅支持(jpg、png)!");
         }
 
         try (InputStream is = Files.newInputStream(imageFile.toPath());) {

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

@@ -0,0 +1,90 @@
+package cn.com.qmth.examcloud.starters.face.verify.common;
+
+import java.io.Serializable;
+
+/**
+ * 人脸接口结果
+ */
+public class FaceResult implements Serializable {
+
+    private static final long serialVersionUID = -6981807543872661368L;
+
+    /**
+     * 是否通过
+     */
+    private boolean pass;
+
+    /**
+     * 接口是否并发超限
+     */
+    private boolean apiLimit;
+
+    /**
+     * 接口错误信息
+     */
+    private String error;
+
+    /**
+     * 分值
+     */
+    private Double score;
+
+    /**
+     * 阈值(JSON)
+     */
+    private String thresholds;
+
+    /**
+     * 人脸数
+     */
+    private Integer faceNum;
+
+    public boolean isPass() {
+        return pass;
+    }
+
+    public void setPass(boolean pass) {
+        this.pass = pass;
+    }
+
+    public boolean isApiLimit() {
+        return apiLimit;
+    }
+
+    public void setApiLimit(boolean apiLimit) {
+        this.apiLimit = apiLimit;
+    }
+
+    public String getError() {
+        return error;
+    }
+
+    public void setError(String error) {
+        this.error = error;
+    }
+
+    public Double getScore() {
+        return score;
+    }
+
+    public void setScore(Double score) {
+        this.score = score;
+    }
+
+    public String getThresholds() {
+        return thresholds;
+    }
+
+    public void setThresholds(String thresholds) {
+        this.thresholds = thresholds;
+    }
+
+    public Integer getFaceNum() {
+        return faceNum;
+    }
+
+    public void setFaceNum(Integer faceNum) {
+        this.faceNum = faceNum;
+    }
+
+}

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

@@ -1,37 +0,0 @@
-package cn.com.qmth.examcloud.starters.face.verify.common;
-
-import java.io.Serializable;
-
-public interface IResult extends Serializable {
-
-    /**
-     * 是否人脸检测通过
-     */
-    boolean isDetectPass();
-
-    /**
-     * 是否人脸对比通过
-     */
-    boolean isComparePass();
-
-    /**
-     * 置信度
-     */
-    Double getConfidenceValue();
-
-    /**
-     * 置信度阈值(JSON)
-     */
-    String getThresholdValue();
-
-    /**
-     * 人脸数
-     */
-    int getFaceSize();
-
-    /**
-     * 请求并发是否超限
-     */
-    boolean requestOutOfLimit();
-
-}

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

@@ -1,27 +1,27 @@
 package cn.com.qmth.examcloud.starters.face.verify.service;
 
-import cn.com.qmth.examcloud.starters.face.verify.common.IResult;
+import cn.com.qmth.examcloud.starters.face.verify.common.FaceResult;
 
 public interface FaceVerifyService {
 
     /**
      * 百度 人脸检测
      */
-    IResult faceDetectByBaidu(String imageUrl);
+    FaceResult faceDetectByBaidu(String imageUrl);
 
     /**
      * 百度 人脸比对
      */
-    IResult faceCompareByBaidu(String imageUrl, String imageUrl2);
+    FaceResult faceCompareByBaidu(String imageUrl, String imageUrl2);
 
     /**
      * Face++ 人脸检测
      */
-    IResult faceDetectByFacePlus(String imageUrl);
+    FaceResult faceDetectByFacePlus(String imageUrl);
 
     /**
      * Face++ 人脸比对
      */
-    IResult faceCompareByFacePlus(String imageUrl, String imageUrl2);
+    FaceResult faceCompareByFacePlus(String imageUrl, String imageUrl2);
 
 }

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

@@ -33,25 +33,23 @@ public class BaiduApiHelper {
         String requestUrl = String.format("%s?grant_type=client_credentials&client_id=%s&client_secret=%s", apiUrl, apiKey, apiSecret);
         Request.Builder request = new Request.Builder().url(requestUrl).get();
 
-        BaiduSession result = null;
         try (Response response = HttpClientBuilder.getClient().newCall(request.build()).execute();
              ResponseBody body = response.body();) {
             String bodyStr = body.string();
 
             if (response.isSuccessful()) {
-                result = new JsonHelper().parseJson(bodyStr, BaiduSession.class);
-            } else {
-                log.warn("[baidu] url:{} response:{} body:{}", requestUrl, response.code(), bodyStr);
+                BaiduSession session = new JsonHelper().parseJson(bodyStr, BaiduSession.class);
+                if (session != null) {
+                    return session;
+                }
             }
-        } catch (IOException e) {
-            log.error(e.getMessage(), e);
-        }
 
-        if (result == null) {
+            log.warn("[baidu] url:{} response:{} body:{}", requestUrl, response.code(), bodyStr);
             throw new RuntimeException("获取 Baidu AccessToken 失败!");
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+            throw new RuntimeException("获取 Baidu AccessToken 异常!");
         }
-
-        return result;
     }
 
     public static BaiduResponse faceDetect(String accessToken, String apiUrl, String imageUrl) {
@@ -63,11 +61,9 @@ public class BaiduApiHelper {
             throw new IllegalArgumentException("face imageUrl must be not empty.");
         }
 
-        String requestUrl = String.format("%s?access_token=%s", apiUrl, accessToken);
-
         List<Map<String, String>> params = new ArrayList<>();
         Map<String, String> imageData = new TreeMap<>();
-        // imageData.put("image_type", "BASE64");
+        // image_type: URL、BASE64、FACE_TOKEN
         imageData.put("image_type", "URL");
         imageData.put("image", imageUrl);
         params.add(imageData);
@@ -75,6 +71,8 @@ public class BaiduApiHelper {
 
         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);
 
         return callApi(apiUrl, request);
@@ -93,8 +91,6 @@ public class BaiduApiHelper {
             throw new IllegalArgumentException("face imageUrl2 must be not empty.");
         }
 
-        String requestUrl = String.format("%s?access_token=%s", apiUrl, accessToken);
-
         List<Map<String, String>> params = new ArrayList<>();
         Map<String, String> imageData = new TreeMap<>();
         imageData.put("image_type", "URL");
@@ -108,29 +104,32 @@ public class BaiduApiHelper {
 
         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);
 
         return callApi(apiUrl, request);
     }
 
     private static BaiduResponse callApi(String apiUrl, Request.Builder request) {
-        BaiduResponse result = null;
         try (Response response = HttpClientBuilder.getClient().newCall(request.build()).execute();
              ResponseBody body = response.body();) {
             String bodyStr = body.string();
 
-            System.out.println(bodyStr);
+            System.out.println(response.code() + " - " + bodyStr);
 
             if (response.isSuccessful()) {
-                result = new JsonHelper().parseJson(bodyStr, BaiduResponse.class);
-            } else {
-                log.warn("[baidu] url:{} response:{} body:{}", apiUrl, response.code(), bodyStr);
+                BaiduResponse result = new JsonHelper().parseJson(bodyStr, BaiduResponse.class);
+                if (result != null) {
+                    return result;
+                }
             }
+
+            log.warn("[baidu] url:{} response:{} body:{}", apiUrl, response.code(), bodyStr);
         } catch (IOException e) {
             log.error(e.getMessage(), e);
         }
-
-        return result;
+        throw new RuntimeException("[baidu] 接口调用异常!");
     }
 
 }

+ 11 - 1
examcloud-starters/examcloud-face-verify-starter/src/main/java/cn/com/qmth/examcloud/starters/face/verify/service/baidu/BaiduFace.java

@@ -10,6 +10,8 @@ public class BaiduFace implements Serializable {
 
     private Double face_probability;//人脸置信度(代表这是一张人脸的概率,0最小、1最大)
 
+    private Double spoofing;//是否为合成图
+
     public String getFace_token() {
         return face_token;
     }
@@ -26,4 +28,12 @@ public class BaiduFace implements Serializable {
         this.face_probability = face_probability;
     }
 
-}
+    public Double getSpoofing() {
+        return spoofing;
+    }
+
+    public void setSpoofing(Double spoofing) {
+        this.spoofing = spoofing;
+    }
+
+}

+ 4 - 77
examcloud-starters/examcloud-face-verify-starter/src/main/java/cn/com/qmth/examcloud/starters/face/verify/service/baidu/BaiduResponse.java

@@ -1,82 +1,17 @@
 package cn.com.qmth.examcloud.starters.face.verify.service.baidu;
 
-import cn.com.qmth.examcloud.starters.face.verify.common.IResult;
-import cn.com.qmth.examcloud.starters.face.verify.common.JsonHelper;
+import java.io.Serializable;
 
-public class BaiduResponse implements IResult {
+public class BaiduResponse implements Serializable {
 
-    private static final long serialVersionUID = 1L;
+    private static final long serialVersionUID = -6768622614771865340L;
 
     private Integer error_code;
 
     private String error_msg;
 
-    private Long log_id;
-
     private BaiduResult result;
 
-    @Override
-    public boolean isDetectPass() {
-        //是否人脸活体检测通过 (默认推荐值:0.393241)
-        if (getError_code() == 0 && result != null && result.getFace_liveness() > 0.39) {
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    public boolean isComparePass() {
-        //是否人脸识别对比通过(暂未实现)
-        return false;
-    }
-
-    @Override
-    public Double getConfidenceValue() {
-        //置信度
-        if (result != null) {
-            return result.getFace_liveness();
-        }
-        return null;
-    }
-
-    @Override
-    public String getThresholdValue() {
-        //置信度阈值(JSON)
-        if (result != null && result.getThresholds() != null) {
-            return new JsonHelper().toJson(result.getThresholds());
-        }
-        return null;
-    }
-
-    @Override
-    public int getFaceSize() {
-        //人脸数
-        if (getError_code() == 0 && result != null) {
-            return result.getFace_num();
-        }
-        return 0;
-    }
-
-    @Override
-    public boolean requestOutOfLimit() {
-        //请求并发是否超限(默认12个/秒)
-        if (getError_code() == 18) {
-            return true;//百度错误码为18
-        }
-        return false;
-    }
-
-    public boolean isTokenExpired() {
-        // Token默认有效期为30天
-        if (getError_code() == 110 || getError_code() == 111) {
-            // 已失效或过期
-            return true;
-        }
-
-        // Token有效
-        return false;
-    }
-
     public Integer getError_code() {
         if (error_code == null) {
             return -1;
@@ -96,14 +31,6 @@ public class BaiduResponse implements IResult {
         this.error_msg = error_msg;
     }
 
-    public Long getLog_id() {
-        return log_id;
-    }
-
-    public void setLog_id(Long log_id) {
-        this.log_id = log_id;
-    }
-
     public BaiduResult getResult() {
         return result;
     }
@@ -112,4 +39,4 @@ public class BaiduResponse implements IResult {
         this.result = result;
     }
 
-}
+}

+ 21 - 17
examcloud-starters/examcloud-face-verify-starter/src/main/java/cn/com/qmth/examcloud/starters/face/verify/service/baidu/BaiduResult.java

@@ -7,50 +7,54 @@ public class BaiduResult implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
-    private Double face_liveness;//活体分数值
+    private Double face_liveness;//活体分数值,推荐阈值0.393241
+
+    private Double score;//人脸相似度得分,推荐阈值80分
 
     private Integer face_num;//人脸数
 
     private List<BaiduFace> face_list;//人脸信息列表
 
-    private BaiduThresholds thresholds;//置信度阈值(误识率越低,准确率越高)
+    private BaiduThresholds thresholds;//阈值(误识率越低,准确率越高)
 
     public Double getFace_liveness() {
-        if (face_liveness == null) {
-            return 0D;
-        }
         return face_liveness;
     }
 
-    public Integer getFace_num() {
-        if (face_num == null) {
-            return 0;
-        }
-        return face_num;
+    public void setFace_liveness(Double face_liveness) {
+        this.face_liveness = face_liveness;
     }
 
-    public List<BaiduFace> getFace_list() {
-        return face_list;
+    public Double getScore() {
+        return score;
     }
 
-    public BaiduThresholds getThresholds() {
-        return thresholds;
+    public void setScore(Double score) {
+        this.score = score;
     }
 
-    public void setFace_liveness(Double face_liveness) {
-        this.face_liveness = face_liveness;
+    public Integer getFace_num() {
+        return face_num;
     }
 
     public void setFace_num(Integer face_num) {
         this.face_num = face_num;
     }
 
+    public List<BaiduFace> getFace_list() {
+        return face_list;
+    }
+
     public void setFace_list(List<BaiduFace> face_list) {
         this.face_list = face_list;
     }
 
+    public BaiduThresholds getThresholds() {
+        return thresholds;
+    }
+
     public void setThresholds(BaiduThresholds thresholds) {
         this.thresholds = thresholds;
     }
 
-}
+}

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

@@ -72,23 +72,24 @@ public class FacePlusApiHelper {
     }
 
     private static FacePlusResponse callApi(String apiUrl, Request.Builder request) {
-        FacePlusResponse result = null;
         try (Response response = HttpClientBuilder.getClient().newCall(request.build()).execute();
              ResponseBody body = response.body();) {
             String bodyStr = body.string();
 
-            System.out.println(bodyStr);
+            System.out.println(response.code() + " - " + bodyStr);
 
-            if (response.code() == 403 || response.isSuccessful()) {
-                result = new JsonHelper().parseJson(bodyStr, FacePlusResponse.class);
-            } else {
-                log.warn("[Face++] url:{} response:{} body:{}", apiUrl, response.code(), bodyStr);
+            if (response.isSuccessful() || response.code() == 401 || response.code() == 403) {
+                FacePlusResponse result = new JsonHelper().parseJson(bodyStr, FacePlusResponse.class);
+                if (result != null) {
+                    return result;
+                }
             }
+
+            log.warn("[Face++] url:{} response:{} body:{}", apiUrl, response.code(), bodyStr);
         } catch (IOException e) {
             log.error(e.getMessage(), e);
         }
-
-        return result;
+        throw new RuntimeException("[Face++] 接口调用异常!");
     }
 
 }

+ 1 - 1
examcloud-starters/examcloud-face-verify-starter/src/main/java/cn/com/qmth/examcloud/starters/face/verify/service/faceplus/FacePlusFace.java

@@ -16,4 +16,4 @@ public class FacePlusFace implements Serializable {
         this.face_token = face_token;
     }
 
-}
+}

+ 68 - 84
examcloud-starters/examcloud-face-verify-starter/src/main/java/cn/com/qmth/examcloud/starters/face/verify/service/faceplus/FacePlusResponse.java

@@ -1,91 +1,35 @@
 package cn.com.qmth.examcloud.starters.face.verify.service.faceplus;
 
-import cn.com.qmth.examcloud.starters.face.verify.common.IResult;
-import cn.com.qmth.examcloud.starters.face.verify.common.JsonHelper;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
+import java.io.Serializable;
 import java.util.List;
 
-public class FacePlusResponse implements IResult {
-
-    private static final long serialVersionUID = 1L;
+public class FacePlusResponse implements Serializable {
 
-    private static Logger log = LoggerFactory.getLogger(FacePlusResponse.class);
+    private static final long serialVersionUID = 2598047933260512423L;
 
     private String request_id;
 
-    private Float confidence;
-
-    private FacePlusThresholds thresholds;
-
     private Integer time_used;
 
     private String error_message;
 
     private String image_id;
 
-    private List<FacePlusFace> faces;
+    private String image_id1;
 
-    @Override
-    public boolean isDetectPass() {
-        //是否人脸活体检测通过 (只有一张人脸算成功,否则算失败)
-        if (faces != null) {
-            log.info("faces size:" + faces.size());
+    private String image_id2;
 
-            if (faces.size() == 1) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    @Override
-    public boolean isComparePass() {
-        //是否人脸识别对比通过
-        if (confidence != null && thresholds != null && confidence >= thresholds.getLe_4()) {
-            return true;
-        }
-        return false;
-    }
+    private List<FacePlusFace> faces;
 
-    @Override
-    public Double getConfidenceValue() {
-        //置信度
-        if (confidence != null) {
-            return confidence.doubleValue();
-        }
-        return null;
-    }
+    private List<FacePlusFace> faces1;
 
-    @Override
-    public String getThresholdValue() {
-        //置信度阈值(JSON)
-        if (thresholds != null) {
-            return new JsonHelper().toJson(thresholds);
-        }
-        return null;
-    }
+    private List<FacePlusFace> faces2;
 
-    @Override
-    public int getFaceSize() {
-        //人脸数
-        if (faces == null) {
-            return 0;
-        }
-        return faces.size();
-    }
+    private Integer face_num;//人脸数
 
-    @Override
-    public boolean requestOutOfLimit() {
-        //请求并发是否超限(默认20个/秒)
-        if ("CONCURRENCY_LIMIT_EXCEEDED".equals(getError_message())) {
-            return true;//Face++错误码为CONCURRENCY_LIMIT_EXCEEDED
-        }
+    private Float confidence;//置信度
 
-        return false;
-    }
+    private FacePlusThresholds thresholds;//置信度阈值
 
     public String getRequest_id() {
         return request_id;
@@ -95,22 +39,6 @@ public class FacePlusResponse implements IResult {
         this.request_id = request_id;
     }
 
-    public Float getConfidence() {
-        return confidence;
-    }
-
-    public void setConfidence(Float confidence) {
-        this.confidence = confidence;
-    }
-
-    public FacePlusThresholds getThresholds() {
-        return thresholds;
-    }
-
-    public void setThresholds(FacePlusThresholds thresholds) {
-        this.thresholds = thresholds;
-    }
-
     public Integer getTime_used() {
         return time_used;
     }
@@ -135,6 +63,22 @@ public class FacePlusResponse implements IResult {
         this.image_id = image_id;
     }
 
+    public String getImage_id1() {
+        return image_id1;
+    }
+
+    public void setImage_id1(String image_id1) {
+        this.image_id1 = image_id1;
+    }
+
+    public String getImage_id2() {
+        return image_id2;
+    }
+
+    public void setImage_id2(String image_id2) {
+        this.image_id2 = image_id2;
+    }
+
     public List<FacePlusFace> getFaces() {
         return faces;
     }
@@ -143,4 +87,44 @@ public class FacePlusResponse implements IResult {
         this.faces = faces;
     }
 
-}
+    public List<FacePlusFace> getFaces1() {
+        return faces1;
+    }
+
+    public void setFaces1(List<FacePlusFace> faces1) {
+        this.faces1 = faces1;
+    }
+
+    public List<FacePlusFace> getFaces2() {
+        return faces2;
+    }
+
+    public void setFaces2(List<FacePlusFace> faces2) {
+        this.faces2 = faces2;
+    }
+
+    public Integer getFace_num() {
+        return face_num;
+    }
+
+    public void setFace_num(Integer face_num) {
+        this.face_num = face_num;
+    }
+
+    public Float getConfidence() {
+        return confidence;
+    }
+
+    public void setConfidence(Float confidence) {
+        this.confidence = confidence;
+    }
+
+    public FacePlusThresholds getThresholds() {
+        return thresholds;
+    }
+
+    public void setThresholds(FacePlusThresholds thresholds) {
+        this.thresholds = thresholds;
+    }
+
+}

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

@@ -1,14 +1,19 @@
 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.IResult;
+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.BaiduApiHelper;
-import cn.com.qmth.examcloud.starters.face.verify.service.baidu.BaiduSession;
+import cn.com.qmth.examcloud.starters.face.verify.service.baidu.*;
 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.util.List;
+
 public class FaceVerifyServiceImpl implements FaceVerifyService {
 
     private final static Logger log = LoggerFactory.getLogger(FaceVerifyServiceImpl.class);
@@ -16,35 +21,150 @@ public class FaceVerifyServiceImpl implements FaceVerifyService {
     private FaceVerifyProperties properties;
 
     @Override
-    public IResult faceDetectByBaidu(String imageUrl) {
+    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();
         }
 
-        return BaiduApiHelper.faceDetect(accessToken, properties.getBaiduFaceDetectUrl(), imageUrl);
+        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());
+
+            // 请求并发超限(约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;
+
+            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);
+            }
+        }
+
+        return result;
     }
 
     @Override
-    public IResult faceCompareByBaidu(String imageUrl, String imageUrl2) {
+    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();
         }
 
-        return BaiduApiHelper.faceCompare(accessToken, properties.getBaiduFaceCompareUrl(), imageUrl, imageUrl2);
+        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());
+
+            // 人脸相似度得分,推荐阈值80分
+            if (data.getScore() != null && data.getScore() >= 80d) {
+                result.setPass(true);
+            }
+        }
+
+        return result;
     }
 
     @Override
-    public IResult faceDetectByFacePlus(String imageUrl) {
-        return FacePlusApiHelper.faceDetect(properties.getFacePlusKey(), properties.getFacePlusSecret(), properties.getFacePlusFaceDetectUrl(), imageUrl);
+    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);
+
+        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;
+        }
+
+        int faceNum = response.getFace_num() != null ? response.getFace_num() : 0;
+        result.setFaceNum(faceNum);
+
+        if (faceNum == 1) {
+            // 是否人脸检测通过 (只有一张人脸算成功,否则算失败)
+            result.setPass(true);
+        }
+
+        return result;
     }
 
     @Override
-    public IResult faceCompareByFacePlus(String imageUrl, String imageUrl2) {
-        return FacePlusApiHelper.faceCompare(properties.getFacePlusKey(), properties.getFacePlusSecret(), properties.getFacePlusFaceCompareUrl(), imageUrl, imageUrl2);
+    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;
+        }
+
+        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));
+
+            // 是否人脸对比通过
+            if (confidence >= thresholds.getLe_4()) {
+                result.setPass(true);
+            }
+        }
+
+        return result;
     }
 
     public void setProperties(FaceVerifyProperties properties) {

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

@@ -1,7 +1,7 @@
 package cn.com.qmth.examcloud.starters.face.verify.test;
 
 import cn.com.qmth.examcloud.starters.face.verify.FaceVerifyProperties;
-import cn.com.qmth.examcloud.starters.face.verify.common.IResult;
+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.impl.FaceVerifyServiceImpl;
 import org.junit.Test;
@@ -11,6 +11,7 @@ public class FaceVerifyTest {
     @Test
     public void demo() {
         FaceVerifyProperties properties = new FaceVerifyProperties();
+        // properties.setBaiduAccessTokenEnabled(false);
         properties.setBaiduKey("xxx");
         properties.setBaiduSecret("xxx");
         properties.setFacePlusKey("xxx");
@@ -19,14 +20,18 @@ public class FaceVerifyTest {
         FaceVerifyServiceImpl faceVerifyService = new FaceVerifyServiceImpl();
         faceVerifyService.setProperties(properties);
 
-        String imageUrl = "https://ecs-test-static.qmth.com.cn/student_base_photo/0/675692/1653361979769.jpg";
-        String imageUrl2 = "https://ecs-test-static.qmth.com.cn/student_base_photo/0/675664/1664182785685.jpg";
+        // String imageUrl = "https://img0.baidu.com/it/u=2750657437,2761718651&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=749";
+        // String imageUrl = "https://img2.baidu.com/it/u=1657567997,3247312681&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=666";
+        String imageUrl = "http://5b0988e595225.cdn.sohucs.com/images/20171017/cbdb993563a94a45ab5f99202b0c9c52.jpeg";
+        String imageUrl2 = "https://pix2.tvzhe.com/thumb/star/32/524/260x346.jpg";
 
-        // IResult result = faceVerifyService.faceDetectByBaidu(imageUrl);
-        // IResult result = faceVerifyService.faceCompareByBaidu(imageUrl, imageUrl2);
-        // IResult result = faceVerifyService.faceDetectByFacePlus(imageUrl);
-        // IResult result = faceVerifyService.faceCompareByFacePlus(imageUrl, imageUrl2);
-        // System.out.println(new JsonHelper().toJson(result));
+        FaceResult result = faceVerifyService.faceDetectByBaidu(imageUrl);
+        // FaceResult result = faceVerifyService.faceDetectByFacePlus(imageUrl);
+        // FaceResult result = faceVerifyService.faceCompareByBaidu(imageUrl, imageUrl2);
+        // FaceResult result = faceVerifyService.faceCompareByFacePlus(imageUrl, imageUrl2);
+
+        System.out.println("==================================================");
+        System.out.println(new JsonHelper().toJson(result));
     }
 
 }