|
@@ -0,0 +1,357 @@
|
|
|
+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.model.IResult;
|
|
|
+import cn.com.qmth.examcloud.starters.face.verify.model.baidu.BaiduAccessInfo;
|
|
|
+import cn.com.qmth.examcloud.starters.face.verify.model.baidu.BaiduAccessToken;
|
|
|
+import cn.com.qmth.examcloud.starters.face.verify.model.baidu.BaiduResponseInfo;
|
|
|
+import cn.com.qmth.examcloud.starters.face.verify.model.faceplus.FacePlusResponseInfo;
|
|
|
+import cn.com.qmth.examcloud.starters.face.verify.service.FaceVerifyService;
|
|
|
+import cn.com.qmth.examcloud.starters.face.verify.utils.HttpClientBuilder;
|
|
|
+import cn.com.qmth.examcloud.starters.face.verify.utils.JsonHelper;
|
|
|
+import cn.com.qmth.examcloud.starters.face.verify.utils.StrUtils;
|
|
|
+import okhttp3.*;
|
|
|
+import org.apache.commons.io.IOUtils;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import org.springframework.util.Base64Utils;
|
|
|
+
|
|
|
+import java.io.File;
|
|
|
+import java.io.FileInputStream;
|
|
|
+import java.io.IOException;
|
|
|
+import java.io.InputStream;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.TreeMap;
|
|
|
+
|
|
|
+import static cn.com.qmth.examcloud.starters.face.verify.model.Constants.*;
|
|
|
+
|
|
|
+/**
|
|
|
+ * @author: QMTH
|
|
|
+ * @since: 2019/3/25
|
|
|
+ */
|
|
|
+public class FaceVerifyServiceImpl implements FaceVerifyService {
|
|
|
+
|
|
|
+ private final static Logger log = LoggerFactory.getLogger(FaceVerifyServiceImpl.class);
|
|
|
+
|
|
|
+ private FaceVerifyProperties properties;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public IResult detectFaceByBaidu(String targetImageUrl) {
|
|
|
+ String accessToken = this.getAccessTokenByBaidu();
|
|
|
+ return this.detectFaceByBaidu(accessToken, targetImageUrl, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public IResult detectFaceByBaidu(byte[] imageFileBytes) {
|
|
|
+ String accessToken = this.getAccessTokenByBaidu();
|
|
|
+ String base64 = Base64Utils.encodeToString(imageFileBytes);
|
|
|
+ return this.detectFaceByBaidu(accessToken, base64, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public IResult detectFaceByBaidu(File targetImageFile) {
|
|
|
+ String accessToken = this.getAccessTokenByBaidu();
|
|
|
+ String base64 = StrUtils.toBase64(targetImageFile);
|
|
|
+ return this.detectFaceByBaidu(accessToken, base64, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ private IResult detectFaceByBaidu(String accessToken, String targetImage, boolean isBases64) {
|
|
|
+ if (StringUtils.isBlank(accessToken)) {
|
|
|
+ throw new IllegalArgumentException("Baidu accessToken must be not empty.");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (StringUtils.isBlank(targetImage)) {
|
|
|
+ throw new IllegalArgumentException("targetImage must be not empty.");
|
|
|
+ }
|
|
|
+
|
|
|
+ List params = new ArrayList();
|
|
|
+ Map<String, String> values = new TreeMap<>();
|
|
|
+ if (isBases64) {
|
|
|
+ values.put("image_type", "BASE64");
|
|
|
+ } else {
|
|
|
+ values.put("image_type", "URL");
|
|
|
+ }
|
|
|
+ values.put("image", targetImage);
|
|
|
+ params.add(values);
|
|
|
+
|
|
|
+ JsonHelper jsonMapper = new JsonHelper();
|
|
|
+ String jsonParams = jsonMapper.toJson(params);
|
|
|
+
|
|
|
+ String requestUrl = String.format(BAI_DU_DETECT_FACE_URL, accessToken);
|
|
|
+ IResult result = this.callForBaidu(jsonMapper, jsonParams, requestUrl);
|
|
|
+
|
|
|
+ if (result != null && result.isTokenExpired()) {
|
|
|
+ //Token过期,则重刷
|
|
|
+ accessToken = refreshAccessTokenByBaidu();
|
|
|
+ requestUrl = String.format(BAI_DU_DETECT_FACE_URL, accessToken);
|
|
|
+ return this.callForBaidu(jsonMapper, jsonParams, requestUrl);
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public IResult detectFaceWithFaceSizeByBaidu(File targetImageFile) {
|
|
|
+ if (targetImageFile == null) {
|
|
|
+ throw new IllegalArgumentException("targetImageFile must be not empty.");
|
|
|
+ }
|
|
|
+
|
|
|
+ byte[] targetBytes;
|
|
|
+ try (InputStream targetStream = new FileInputStream(targetImageFile);) {
|
|
|
+ targetBytes = IOUtils.toByteArray(targetStream);
|
|
|
+ } catch (IOException e) {
|
|
|
+ log.error(e.getMessage(), e);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ return this.detectFaceWithFaceSizeByBaidu(targetBytes);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public IResult detectFaceWithFaceSizeByBaidu(byte[] imageFileBytes) {
|
|
|
+ String accessToken = this.getAccessTokenByBaidu();
|
|
|
+ String base64 = Base64Utils.encodeToString(imageFileBytes);
|
|
|
+ Map<String, String> params = new TreeMap<>();
|
|
|
+ params.put("max_face_num", "2");// 最多处理人脸的数目(默认值为1)
|
|
|
+ params.put("image_type", "BASE64");
|
|
|
+ params.put("image", base64);
|
|
|
+
|
|
|
+ JsonHelper jsonMapper = new JsonHelper();
|
|
|
+ String jsonParams = jsonMapper.toJson(params);
|
|
|
+
|
|
|
+ String requestUrl = String.format(BAIDU_DETECT_FACE_WITH_FACE_SIZE_URL, accessToken);
|
|
|
+ IResult result = this.callForBaidu(jsonMapper, jsonParams, requestUrl);
|
|
|
+
|
|
|
+ if (result != null && result.isTokenExpired()) {
|
|
|
+ //Token过期,则重刷
|
|
|
+ accessToken = this.refreshAccessTokenByBaidu();
|
|
|
+ requestUrl = String.format(BAIDU_DETECT_FACE_WITH_FACE_SIZE_URL, accessToken);
|
|
|
+ return this.callForBaidu(jsonMapper, jsonParams, requestUrl);
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String getAccessTokenByBaidu() {
|
|
|
+ if (BaiduAccessToken.isNotEmpty()) {
|
|
|
+ return BaiduAccessToken.getValue();
|
|
|
+ }
|
|
|
+
|
|
|
+ BaiduAccessInfo info = this.loadAccessTokenByBaidu();
|
|
|
+ if (info == null || info.getAccess_token() == null) {
|
|
|
+ throw new IllegalArgumentException("Baidu loadAccessToken fail.");
|
|
|
+ }
|
|
|
+
|
|
|
+ BaiduAccessToken.setValue(info.getAccess_token());
|
|
|
+ return info.getAccess_token();
|
|
|
+ }
|
|
|
+
|
|
|
+ private String refreshAccessTokenByBaidu() {
|
|
|
+ BaiduAccessToken.setValue(null);
|
|
|
+ return this.getAccessTokenByBaidu();
|
|
|
+ }
|
|
|
+
|
|
|
+ private BaiduAccessInfo loadAccessTokenByBaidu() {
|
|
|
+ if (StringUtils.isBlank(properties.getBaiduKey()) || StringUtils.isBlank(properties.getBaiduSecret())) {
|
|
|
+ throw new IllegalArgumentException("Baidu apiKey or apiSecret must be not empty.");
|
|
|
+ }
|
|
|
+
|
|
|
+ String requestUrl = String.format(BAI_DU_TOKEN_URL, properties.getBaiduKey(), properties.getBaiduSecret());
|
|
|
+ Request.Builder request = new Request.Builder().url(requestUrl).get();
|
|
|
+
|
|
|
+ try (Response response = HttpClientBuilder.getClient().newCall(request.build()).execute();
|
|
|
+ ResponseBody body = response.body();) {
|
|
|
+ String bodyStr = body.string();
|
|
|
+
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("[response] code: " + response.code() + " body: " + bodyStr);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (response.isSuccessful()) {
|
|
|
+ return new JsonHelper().parseJson(bodyStr, BaiduAccessInfo.class);
|
|
|
+ }
|
|
|
+ } catch (IOException e) {
|
|
|
+ log.error(e.getMessage(), e);
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private BaiduResponseInfo callForBaidu(JsonHelper jsonMapper, String jsonParams, String requestUrl) {
|
|
|
+ final String contentType = "application/json; charset=utf-8";
|
|
|
+ RequestBody formBody = FormBody.create(MediaType.parse(contentType), jsonParams);
|
|
|
+ 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();
|
|
|
+
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("[response] code: " + response.code() + " body: " + bodyStr);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (response.isSuccessful()) {
|
|
|
+ return jsonMapper.parseJson(bodyStr, BaiduResponseInfo.class);
|
|
|
+ }
|
|
|
+ } catch (IOException e) {
|
|
|
+ log.error(e.getMessage(), e);
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public IResult detectFaceByFacePlus(File targetImageFile) {
|
|
|
+ if (targetImageFile == null) {
|
|
|
+ throw new IllegalArgumentException("targetImageFile must be not empty.");
|
|
|
+ }
|
|
|
+
|
|
|
+ byte[] targetBytes;
|
|
|
+ try (InputStream targetStream = new FileInputStream(targetImageFile);) {
|
|
|
+ targetBytes = IOUtils.toByteArray(targetStream);
|
|
|
+ } catch (IOException e) {
|
|
|
+ log.error(e.getMessage());
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ return this.detectFaceByFacePlus(targetBytes, targetImageFile.getName());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public IResult detectFaceByFacePlus(byte[] imageFileBytes, String imageFileName) {
|
|
|
+ if (StringUtils.isBlank(properties.getFacePlusKey()) || StringUtils.isBlank(properties.getFacePlusSecret())) {
|
|
|
+ throw new IllegalArgumentException("Face++ apiKey or apiSecret must be not empty.");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (imageFileBytes == null || imageFileBytes.length == 0) {
|
|
|
+ throw new IllegalArgumentException("fileBytes must be not empty.");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("[request] " + FACE_PLUS_DETECT_FACE_URL + " imageFileName: " + imageFileName);
|
|
|
+ }
|
|
|
+
|
|
|
+ RequestBody targetBody = RequestBody.create(MediaType.parse("application/octet-stream"), imageFileBytes);
|
|
|
+ MultipartBody.Builder formBody = new MultipartBody.Builder().setType(MultipartBody.FORM)
|
|
|
+ .addFormDataPart("api_key", properties.getFacePlusKey())
|
|
|
+ .addFormDataPart("api_secret", properties.getFacePlusSecret())
|
|
|
+ .addFormDataPart("image_file", StrUtils.urlEncode(imageFileName), targetBody);
|
|
|
+ return callForFacePlus(FACE_PLUS_DETECT_FACE_URL, formBody.build());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public IResult compareFaceByFacePlus(String sourceImageUrl, String targetImageUrl) {
|
|
|
+ if (StringUtils.isBlank(properties.getFacePlusKey()) || StringUtils.isBlank(properties.getFacePlusSecret())) {
|
|
|
+ throw new IllegalArgumentException("Face++ apiKey or apiSecret must be not empty.");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (StringUtils.isBlank(sourceImageUrl) || StringUtils.isBlank(targetImageUrl)) {
|
|
|
+ throw new IllegalArgumentException("sourceImageUrl or targetImageUrl must be not empty.");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("[request] " + FACE_PLUS_COMPARE_FACE_URL + " sourceImageUrl: " + sourceImageUrl + " targetImageUrl: " + targetImageUrl);
|
|
|
+ }
|
|
|
+
|
|
|
+ RequestBody formBody = new FormBody.Builder()
|
|
|
+ .add("api_key", properties.getFacePlusKey())
|
|
|
+ .add("api_secret", properties.getFacePlusSecret())
|
|
|
+ .add("image_url1", sourceImageUrl)
|
|
|
+ .add("image_url2", targetImageUrl)
|
|
|
+ .build();
|
|
|
+ return callForFacePlus(FACE_PLUS_COMPARE_FACE_URL, formBody);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public IResult compareFaceByFacePlus(File sourceImageFile, File targetImageFile) {
|
|
|
+ if (StringUtils.isBlank(properties.getFacePlusKey()) || StringUtils.isBlank(properties.getFacePlusSecret())) {
|
|
|
+ throw new IllegalArgumentException("Face++ apiKey or apiSecret must be not empty.");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (sourceImageFile == null || targetImageFile == null) {
|
|
|
+ throw new IllegalArgumentException("sourceImageFile or targetImageFile must be not empty.");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("[request] " + FACE_PLUS_COMPARE_FACE_URL + " sourceImageFile: " + sourceImageFile.getName() + " targetImageFile: " + targetImageFile.getName());
|
|
|
+ }
|
|
|
+
|
|
|
+ byte[] sourceBytes, targetBytes;
|
|
|
+ try (InputStream sourceStream = new FileInputStream(sourceImageFile);
|
|
|
+ InputStream targetStream = new FileInputStream(targetImageFile);) {
|
|
|
+ sourceBytes = IOUtils.toByteArray(sourceStream);
|
|
|
+ targetBytes = IOUtils.toByteArray(targetStream);
|
|
|
+ } catch (IOException e) {
|
|
|
+ log.error(e.getMessage(), e);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ RequestBody sourceBody = RequestBody.create(MediaType.parse("application/octet-stream"), sourceBytes);
|
|
|
+ RequestBody targetBody = RequestBody.create(MediaType.parse("application/octet-stream"), targetBytes);
|
|
|
+ MultipartBody.Builder formBody = new MultipartBody.Builder().setType(MultipartBody.FORM)
|
|
|
+ .addFormDataPart("api_key", properties.getFacePlusKey())
|
|
|
+ .addFormDataPart("api_secret", properties.getFacePlusSecret())
|
|
|
+ .addFormDataPart("image_file1", StrUtils.urlEncode(sourceImageFile.getName()), sourceBody)
|
|
|
+ .addFormDataPart("image_file2", StrUtils.urlEncode(targetImageFile.getName()), targetBody);
|
|
|
+ return callForFacePlus(FACE_PLUS_COMPARE_FACE_URL, formBody.build());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public IResult compareFaceByFacePlus(String sourceImageUrl, File targetImageFile) {
|
|
|
+ if (StringUtils.isBlank(properties.getFacePlusKey()) || StringUtils.isBlank(properties.getFacePlusSecret())) {
|
|
|
+ throw new IllegalArgumentException("Face++ apiKey or apiSecret must be not empty.");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (StringUtils.isBlank(sourceImageUrl) || targetImageFile == null) {
|
|
|
+ throw new IllegalArgumentException("sourceImageUrl or targetImageFile must be not empty.");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("[request] " + FACE_PLUS_COMPARE_FACE_URL + " sourceImageUrl: " + sourceImageUrl + " targetImageFile: " + targetImageFile.getName());
|
|
|
+ }
|
|
|
+
|
|
|
+ byte[] targetBytes;
|
|
|
+ try (InputStream targetStream = new FileInputStream(targetImageFile);) {
|
|
|
+ targetBytes = IOUtils.toByteArray(targetStream);
|
|
|
+ } catch (IOException e) {
|
|
|
+ log.error(e.getMessage(), e);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ RequestBody targetBody = RequestBody.create(MediaType.parse("application/octet-stream"), targetBytes);
|
|
|
+ MultipartBody.Builder formBody = new MultipartBody.Builder().setType(MultipartBody.FORM)
|
|
|
+ .addFormDataPart("api_key", properties.getFacePlusKey())
|
|
|
+ .addFormDataPart("api_secret", properties.getFacePlusSecret())
|
|
|
+ .addFormDataPart("image_url1", sourceImageUrl)
|
|
|
+ .addFormDataPart("image_file2", StrUtils.urlEncode(targetImageFile.getName()), targetBody);
|
|
|
+ return callForFacePlus(FACE_PLUS_COMPARE_FACE_URL, formBody.build());
|
|
|
+ }
|
|
|
+
|
|
|
+ private FacePlusResponseInfo callForFacePlus(String url, RequestBody formBody) {
|
|
|
+ Request.Builder request = new Request.Builder().url(url).post(formBody);
|
|
|
+
|
|
|
+ try (Response response = HttpClientBuilder.getClient().newCall(request.build()).execute();
|
|
|
+ ResponseBody body = response.body();) {
|
|
|
+ String bodyStr = body.string();
|
|
|
+
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("[response] code: " + response.code() + " body: " + bodyStr);
|
|
|
+ }
|
|
|
+
|
|
|
+ //403有并发超限情况
|
|
|
+ if (response.code() == 403 || response.isSuccessful()) {
|
|
|
+ return new JsonHelper().parseJson(bodyStr, FacePlusResponseInfo.class);
|
|
|
+ }
|
|
|
+ } catch (IOException e) {
|
|
|
+ log.error(e.getMessage(), e);
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setProperties(FaceVerifyProperties properties) {
|
|
|
+ this.properties = properties;
|
|
|
+ }
|
|
|
+
|
|
|
+}
|