|
@@ -0,0 +1,409 @@
|
|
|
+package cn.com.qmth.examcloud.web.facepp;
|
|
|
+
|
|
|
+import cn.com.qmth.examcloud.commons.exception.ExamCloudRuntimeException;
|
|
|
+import cn.com.qmth.examcloud.commons.exception.StatusException;
|
|
|
+import cn.com.qmth.examcloud.commons.helpers.JsonHttpResponseHolder;
|
|
|
+import cn.com.qmth.examcloud.web.bootstrap.PropertyHolder;
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
+import org.apache.commons.codec.binary.Base64;
|
|
|
+import org.apache.commons.compress.utils.IOUtils;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.apache.http.HttpEntity;
|
|
|
+import org.apache.http.HttpStatus;
|
|
|
+import org.apache.http.client.config.CookieSpecs;
|
|
|
+import org.apache.http.client.config.RequestConfig;
|
|
|
+import org.apache.http.client.methods.CloseableHttpResponse;
|
|
|
+import org.apache.http.client.methods.HttpGet;
|
|
|
+import org.apache.http.client.methods.HttpPost;
|
|
|
+import org.apache.http.entity.mime.MultipartEntityBuilder;
|
|
|
+import org.apache.http.impl.client.CloseableHttpClient;
|
|
|
+import org.apache.http.impl.client.HttpClients;
|
|
|
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
|
|
+import org.apache.http.util.EntityUtils;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+
|
|
|
+/**
|
|
|
+ * face++ 客户端
|
|
|
+ *
|
|
|
+ * @author WANGWEI
|
|
|
+ * @date 2019年9月16日
|
|
|
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
|
|
|
+ */
|
|
|
+public class FaceppClient {
|
|
|
+
|
|
|
+ private static final Logger LOG = LoggerFactory.getLogger(FaceppClient.class);
|
|
|
+
|
|
|
+ private static CloseableHttpClient httpclient;
|
|
|
+
|
|
|
+ private static RequestConfig requestConfig;
|
|
|
+
|
|
|
+ private static FaceppClient faceppClient;
|
|
|
+
|
|
|
+ private static String apiKey;
|
|
|
+
|
|
|
+ private static String apiSecret;
|
|
|
+
|
|
|
+ private static String compareUrl = "https://api-cn.faceplusplus.com/facepp/v3/compare";
|
|
|
+
|
|
|
+ private FaceppClient() {
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取单例
|
|
|
+ *
|
|
|
+ * @return
|
|
|
+ * @author WANGWEI
|
|
|
+ */
|
|
|
+ public static FaceppClient getClient() {
|
|
|
+ if (null == faceppClient) {
|
|
|
+ synchronized (FaceppClient.class) {
|
|
|
+ if (null == faceppClient) {
|
|
|
+ faceppClient = new FaceppClient();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return faceppClient;
|
|
|
+ }
|
|
|
+
|
|
|
+ static {
|
|
|
+ PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(60,
|
|
|
+ TimeUnit.SECONDS);
|
|
|
+ cm.setValidateAfterInactivity(1000);
|
|
|
+ cm.setMaxTotal(8000);
|
|
|
+ cm.setDefaultMaxPerRoute(200);
|
|
|
+
|
|
|
+ requestConfig = RequestConfig.custom().setConnectionRequestTimeout(500)
|
|
|
+ .setSocketTimeout(20000).setConnectTimeout(20000)
|
|
|
+ .setCookieSpec(CookieSpecs.IGNORE_COOKIES).build();
|
|
|
+
|
|
|
+ httpclient = HttpClients.custom().setConnectionManager(cm).disableAutomaticRetries()
|
|
|
+ .setDefaultRequestConfig(requestConfig).build();
|
|
|
+
|
|
|
+ apiKey = PropertyHolder.getString("$facepp.apiKey");
|
|
|
+ apiSecret = PropertyHolder.getString("$facepp.apiSecret");
|
|
|
+
|
|
|
+ if (StringUtils.isBlank(apiKey)) {
|
|
|
+ LOG.error("'facepp.apiKey' is not configured");
|
|
|
+ }
|
|
|
+ if (StringUtils.isBlank(apiSecret)) {
|
|
|
+ LOG.error("'facepp.apiSecret' is not configured");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 人脸识别
|
|
|
+ *
|
|
|
+ * @param faceToken
|
|
|
+ * @param imageUrl
|
|
|
+ * @return
|
|
|
+ * @author WANGWEI
|
|
|
+ */
|
|
|
+ public JsonHttpResponseHolder compareWithTokenAndImageUrl(String faceToken, String imageUrl) {
|
|
|
+
|
|
|
+ if (LOG.isDebugEnabled()) {
|
|
|
+ LOG.debug("[Face++ Request]. faceToken=" + faceToken + "; imageUrl=" + imageUrl);
|
|
|
+ }
|
|
|
+
|
|
|
+ String url = PropertyHolder.getString("facepp.compare.url", compareUrl);
|
|
|
+
|
|
|
+ HttpPost httpPost = new HttpPost(url);
|
|
|
+ httpPost.setConfig(FaceppClient.requestConfig);
|
|
|
+
|
|
|
+ MultipartEntityBuilder builder = MultipartEntityBuilder.create();
|
|
|
+ builder.addTextBody("api_key", apiKey);
|
|
|
+ builder.addTextBody("api_secret", apiSecret);
|
|
|
+ builder.addTextBody("face_token1", faceToken);
|
|
|
+ builder.addTextBody("image_url2", imageUrl);
|
|
|
+ HttpEntity httpEntity = builder.build();
|
|
|
+
|
|
|
+ httpPost.setEntity(httpEntity);
|
|
|
+
|
|
|
+ CloseableHttpResponse response = null;
|
|
|
+ JsonHttpResponseHolder responseHolder = null;
|
|
|
+ long s = System.currentTimeMillis();
|
|
|
+ try {
|
|
|
+
|
|
|
+ response = httpclient.execute(httpPost);
|
|
|
+ int statusCode = response.getStatusLine().getStatusCode();
|
|
|
+ String entityStr = EntityUtils.toString(response.getEntity(), "UTF-8");
|
|
|
+ JSONObject obj = JSON.parseObject(entityStr);
|
|
|
+ responseHolder = new JsonHttpResponseHolder(statusCode, obj);
|
|
|
+
|
|
|
+ if (HttpStatus.SC_OK != responseHolder.getStatusCode()) {
|
|
|
+ LOG.error("[Face++ Response]. statusCode=" + statusCode + "; responseEntity="
|
|
|
+ + entityStr);
|
|
|
+ } else {
|
|
|
+ if (LOG.isDebugEnabled()) {
|
|
|
+ LOG.debug("[Face++ Response]. statusCode=" + statusCode + "; responseEntity="
|
|
|
+ + entityStr);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ LOG.error("[Face++ FAIL]. cost " + (System.currentTimeMillis() - s) + " ms.", e);
|
|
|
+ throw new ExamCloudRuntimeException(e);
|
|
|
+ } finally {
|
|
|
+ IOUtils.closeQuietly(response);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (LOG.isDebugEnabled()) {
|
|
|
+ LOG.debug("[Face++]. faceToken=" + faceToken + "; imageUrl=" + imageUrl + "; cost "
|
|
|
+ + (System.currentTimeMillis() - s) + " ms.");
|
|
|
+ }
|
|
|
+
|
|
|
+ return responseHolder;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 人脸识别
|
|
|
+ *
|
|
|
+ * @param faceToken
|
|
|
+ * @param imageBase64
|
|
|
+ * @return
|
|
|
+ * @author WANGWEI
|
|
|
+ */
|
|
|
+ public JsonHttpResponseHolder compareWithTokenAndBase64(String faceToken, String imageBase64) {
|
|
|
+
|
|
|
+ if (LOG.isDebugEnabled()) {
|
|
|
+ LOG.debug("[Face++ Request]. faceToken=" + faceToken + "; imageBase64=?");
|
|
|
+ }
|
|
|
+
|
|
|
+ String url = PropertyHolder.getString("facepp.compare.url", compareUrl);
|
|
|
+
|
|
|
+ HttpPost httpPost = new HttpPost(url);
|
|
|
+ httpPost.setConfig(FaceppClient.requestConfig);
|
|
|
+
|
|
|
+ MultipartEntityBuilder builder = MultipartEntityBuilder.create();
|
|
|
+ builder.addTextBody("api_key", apiKey);
|
|
|
+ builder.addTextBody("api_secret", apiSecret);
|
|
|
+ builder.addTextBody("face_token1", faceToken);
|
|
|
+ builder.addTextBody("image_base64_2", imageBase64);
|
|
|
+ HttpEntity httpEntity = builder.build();
|
|
|
+
|
|
|
+ httpPost.setEntity(httpEntity);
|
|
|
+
|
|
|
+ CloseableHttpResponse response = null;
|
|
|
+ JsonHttpResponseHolder responseHolder = null;
|
|
|
+ long s = System.currentTimeMillis();
|
|
|
+ try {
|
|
|
+
|
|
|
+ response = httpclient.execute(httpPost);
|
|
|
+ int statusCode = response.getStatusLine().getStatusCode();
|
|
|
+ String entityStr = EntityUtils.toString(response.getEntity(), "UTF-8");
|
|
|
+ JSONObject obj = JSON.parseObject(entityStr);
|
|
|
+ responseHolder = new JsonHttpResponseHolder(statusCode, obj);
|
|
|
+
|
|
|
+ if (HttpStatus.SC_OK != responseHolder.getStatusCode()) {
|
|
|
+ LOG.error("[Face++ Response]. statusCode=" + statusCode + "; responseEntity="
|
|
|
+ + entityStr);
|
|
|
+ } else {
|
|
|
+ if (LOG.isDebugEnabled()) {
|
|
|
+ LOG.debug("[Face++ Response]. statusCode=" + statusCode + "; responseEntity="
|
|
|
+ + entityStr);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ LOG.error("[Face++ FAIL]. cost " + (System.currentTimeMillis() - s) + " ms.", e);
|
|
|
+ throw new ExamCloudRuntimeException(e);
|
|
|
+ } finally {
|
|
|
+ IOUtils.closeQuietly(response);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (LOG.isDebugEnabled()) {
|
|
|
+ LOG.debug("[Face++]. faceToken=" + faceToken + "; imageBase64=?; cost "
|
|
|
+ + (System.currentTimeMillis() - s) + " ms.");
|
|
|
+ }
|
|
|
+
|
|
|
+ return responseHolder;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 人脸识别
|
|
|
+ *
|
|
|
+ * @param imageUrl1
|
|
|
+ * @param imageUrl2
|
|
|
+ * @return
|
|
|
+ * @author WANGWEI
|
|
|
+ */
|
|
|
+ public JsonHttpResponseHolder compareWithImageUrl(String imageUrl1, String imageUrl2) {
|
|
|
+
|
|
|
+ if (LOG.isDebugEnabled()) {
|
|
|
+ LOG.debug("[Face++ Request]. imageUrl1=" + imageUrl1 + "; imageUrl2=" + imageUrl2);
|
|
|
+ }
|
|
|
+
|
|
|
+ String url = PropertyHolder.getString("facepp.compare.url", compareUrl);
|
|
|
+
|
|
|
+ HttpPost httpPost = new HttpPost(url);
|
|
|
+ httpPost.setConfig(FaceppClient.requestConfig);
|
|
|
+
|
|
|
+ MultipartEntityBuilder builder = MultipartEntityBuilder.create();
|
|
|
+ builder.addTextBody("api_key", apiKey);
|
|
|
+ builder.addTextBody("api_secret", apiSecret);
|
|
|
+ builder.addTextBody("image_url1", imageUrl1);
|
|
|
+ builder.addTextBody("image_url2", imageUrl2);
|
|
|
+ HttpEntity httpEntity = builder.build();
|
|
|
+
|
|
|
+ httpPost.setEntity(httpEntity);
|
|
|
+
|
|
|
+ CloseableHttpResponse response = null;
|
|
|
+ JsonHttpResponseHolder responseHolder = null;
|
|
|
+ long s = System.currentTimeMillis();
|
|
|
+ try {
|
|
|
+
|
|
|
+ response = httpclient.execute(httpPost);
|
|
|
+ int statusCode = response.getStatusLine().getStatusCode();
|
|
|
+ String entityStr = EntityUtils.toString(response.getEntity(), "UTF-8");
|
|
|
+ JSONObject obj = JSON.parseObject(entityStr);
|
|
|
+ responseHolder = new JsonHttpResponseHolder(statusCode, obj);
|
|
|
+
|
|
|
+ if (HttpStatus.SC_OK != responseHolder.getStatusCode()) {
|
|
|
+ LOG.error("[Face++ Response]. statusCode=" + statusCode + "; responseEntity="
|
|
|
+ + entityStr);
|
|
|
+ } else {
|
|
|
+ if (LOG.isDebugEnabled()) {
|
|
|
+ LOG.debug("[Face++ Response]. statusCode=" + statusCode + "; responseEntity="
|
|
|
+ + entityStr);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ LOG.error("[Face++ FAIL]. cost " + (System.currentTimeMillis() - s) + " ms.", e);
|
|
|
+ throw new ExamCloudRuntimeException(e);
|
|
|
+ } finally {
|
|
|
+ IOUtils.closeQuietly(response);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (LOG.isDebugEnabled()) {
|
|
|
+ LOG.debug("[Face++]. image_url1=" + imageUrl1 + "; imageUrl2=" + imageUrl2 + "; cost "
|
|
|
+ + (System.currentTimeMillis() - s) + " ms.");
|
|
|
+ }
|
|
|
+
|
|
|
+ return responseHolder;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 人脸识别<br>
|
|
|
+ * 优先使用主地址调用face++<br>
|
|
|
+ * 主地址无效时,使用备用地址调用face++.<br>
|
|
|
+ * 备用地址也无效时,使用备用地址下载数据,使用下载数据的base64加密串调用face++<br>
|
|
|
+ *
|
|
|
+ * @param faceToken face++预存照片
|
|
|
+ * @param imageUrl 主地址
|
|
|
+ * @param backupImageUrl 备用地址
|
|
|
+ * @return
|
|
|
+ * @throws StatusException code为801,802,803表示图片地址无效
|
|
|
+ * @author WANGWEI
|
|
|
+ */
|
|
|
+ public JsonHttpResponseHolder compareWithTokenAndImageUrl(String faceToken, String imageUrl,
|
|
|
+ String backupImageUrl) throws StatusException {
|
|
|
+
|
|
|
+ if (LOG.isDebugEnabled()) {
|
|
|
+ LOG.debug("[Face++ Request]. faceToken=" + faceToken + "; imageUrl=" + imageUrl
|
|
|
+ + "; backupImageUrl=" + backupImageUrl);
|
|
|
+ }
|
|
|
+
|
|
|
+ JsonHttpResponseHolder responseHolder = null;
|
|
|
+
|
|
|
+ boolean exceptionWhenUsingImageUrl = false;
|
|
|
+ boolean exceptionWhenUsingBackupImageUrl = false;
|
|
|
+
|
|
|
+ try {
|
|
|
+ responseHolder = compareWithTokenAndImageUrl(faceToken, imageUrl);
|
|
|
+ } catch (ExamCloudRuntimeException e) {
|
|
|
+ exceptionWhenUsingImageUrl = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (exceptionWhenUsingImageUrl) {
|
|
|
+ try {
|
|
|
+ responseHolder = compareWithTokenAndImageUrl(faceToken, backupImageUrl);
|
|
|
+ } catch (ExamCloudRuntimeException e) {
|
|
|
+ exceptionWhenUsingBackupImageUrl = true;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (HttpStatus.SC_OK == responseHolder.getStatusCode()) {
|
|
|
+ return responseHolder;
|
|
|
+ }
|
|
|
+
|
|
|
+ String errMsg = responseHolder.getRespBody().getString("error_message");
|
|
|
+ if (retry(errMsg)) {
|
|
|
+ try {
|
|
|
+ responseHolder = compareWithTokenAndImageUrl(faceToken, backupImageUrl);
|
|
|
+ } catch (ExamCloudRuntimeException e) {
|
|
|
+ exceptionWhenUsingBackupImageUrl = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if (HttpStatus.SC_OK == responseHolder.getStatusCode()) {
|
|
|
+ return responseHolder;
|
|
|
+ }
|
|
|
+
|
|
|
+ String errMsg = responseHolder.getRespBody().getString("error_message");
|
|
|
+
|
|
|
+ if (exceptionWhenUsingBackupImageUrl || retry(errMsg)) {
|
|
|
+ HttpGet get = new HttpGet(backupImageUrl);
|
|
|
+ get.setConfig(FaceppClient.requestConfig);
|
|
|
+ CloseableHttpResponse response = null;
|
|
|
+ String imageBase64 = null;
|
|
|
+ long s = System.currentTimeMillis();
|
|
|
+ try {
|
|
|
+ response = httpclient.execute(get);
|
|
|
+
|
|
|
+ if (HttpStatus.SC_OK != response.getStatusLine().getStatusCode()) {
|
|
|
+ throw new StatusException("801",
|
|
|
+ "fail to download image file. url=" + backupImageUrl);
|
|
|
+ }
|
|
|
+
|
|
|
+ byte[] byteArray = EntityUtils.toByteArray(response.getEntity());
|
|
|
+ if (100 > byteArray.length) {
|
|
|
+ throw new StatusException("802", "invalid image size. url=" + backupImageUrl);
|
|
|
+ }
|
|
|
+
|
|
|
+ imageBase64 = Base64.encodeBase64String(byteArray);
|
|
|
+
|
|
|
+ } catch (StatusException e) {
|
|
|
+ LOG.error("fail to download image file. url=" + backupImageUrl, e);
|
|
|
+ throw e;
|
|
|
+ } catch (Exception e) {
|
|
|
+ LOG.error("fail to download image file. url=" + backupImageUrl, e);
|
|
|
+ throw new StatusException("803", "fail to download file. url=" + backupImageUrl, e);
|
|
|
+ } finally {
|
|
|
+ IOUtils.closeQuietly(response);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (LOG.isDebugEnabled()) {
|
|
|
+ LOG.debug("download image file successfully; url=" + backupImageUrl + "; cost "
|
|
|
+ + (System.currentTimeMillis() - s) + " ms.");
|
|
|
+ }
|
|
|
+
|
|
|
+ responseHolder = compareWithTokenAndBase64(faceToken, imageBase64);
|
|
|
+ }
|
|
|
+
|
|
|
+ return responseHolder;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 是否重试
|
|
|
+ *
|
|
|
+ * @param errMsg
|
|
|
+ * @return
|
|
|
+ * @author WANGWEI
|
|
|
+ */
|
|
|
+ private boolean retry(String errMsg) {
|
|
|
+ if (null != errMsg) {
|
|
|
+ if (errMsg.startsWith("INVALID_IMAGE_URL")
|
|
|
+ || errMsg.startsWith("IMAGE_DOWNLOAD_TIMEOUT")) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+}
|