java腾讯云人脸核身移动浮层H5接入

腾讯云人脸核身文档

最近公司有业务需求,需要对企业微信中的小程序添加人脸识别功能,一般的人脸核身是对app中添加sdk完成的,考虑到业务需要,采用腾讯云的移动浮层H5接入,废话不多说,直接上代码。
在这里插入图片描述
这边,这3步已经满足了我们的需要。

1、配置文件

nonce是自定义的随机字符串,redirectUrl是验证成功后回调的页面

# 腾讯人脸识别配置
tencentFaceVerify:
  appId: ******
  appSecret: ***********************
  version: 1.0.0
  nonce: ************
  redirectUrl: https://*****/index

2、工具类



import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Base64;

public class Base64Util {

    private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    public Base64Util() {
    }

    public static String encode(String source) {
        if (!isEmpty(source)) {
            byte[] bytes = source.getBytes(DEFAULT_CHARSET);
            String asB64 = Base64.getEncoder().encodeToString(bytes);
            return asB64;
        } else {
            return source;
        }
    }

    public static String decode(String source) {
        if (!isEmpty(source)) {
            byte[] bytes = Base64.getDecoder().decode(source);
            String target = new String(bytes, DEFAULT_CHARSET);
            return target;
        } else {
            return source;
        }
    }

    private static boolean isEmpty(String str) {
        return str == null || str.length() == 0;
    }

    /**
     * 对字节数组字符串进行Base64解码并生成图片
     * @param base64
     * @param path
     * @return
     */
    public static boolean base64ToImage(String base64, String path) {
        if (base64 == null) {
            return false;
        }

        BASE64Decoder decoder = new BASE64Decoder();
        try {
            // Base64解码
            byte[] bytes = decoder.decodeBuffer(base64);
            for (int i = 0; i < bytes.length; ++i) {
                if (bytes[i] < 0) {// 调整异常数据
                    bytes[i] += 256;
                }
            }

            // 生成jpeg图片
            OutputStream out = new FileOutputStream(path);
            out.write(bytes);
            out.flush();
            out.close();
            return true;
        } catch (Exception e) {
            return false;
        }
    }


    /**
     * 将一张网络图片转化成Base64字符串
     * @param imgURL
     * @return
     */
    public static String GetImageStrFromUrl(String imgURL) {
        ByteArrayOutputStream data = new ByteArrayOutputStream();
        try {
            // 创建URL
            URL url = new URL(imgURL);
            byte[] by = new byte[1024];
            // 创建链接
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(5000);
            InputStream is = conn.getInputStream();
            // 将内容读取内存中
            int len = -1;
            while ((len = is.read(by)) != -1) {
                data.write(by, 0, len);
            }
            // 关闭流
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 对字节数组Base64编码
        BASE64Encoder encoder = new BASE64Encoder();
        return encoder.encode(data.toByteArray());
    }

}

package com.lynkco.lcem.util;

import java.text.SimpleDateFormat;
import java.util.UUID;

public class SerialUtil {

    private static final String DATE_FORMAT = "yyyyMMdd";

    private SerialUtil (){}

    public static String getBatchNo(String prefix) {
        SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
        StringBuffer batchNo = new StringBuffer(prefix);
        batchNo.append(sdf.format(System.currentTimeMillis()))
                .append((int)((Math.random()*9+1)*100000));
        return batchNo.toString();
    }

    public static String getUUID() {
        String uuid = UUID.randomUUID().toString();
        return uuid.replace("-", "");
    }


}

3、实体类

/**
 * @author :xuwei
 * @description:人脸入参,参考https://cloud.tencent.com/document/product/1007/61073
 * @date :2022/8/25 21:43
 */
@Data
@ApiModel
public class TencentFaceVerifyParam {

    @ApiModelProperty(name="sourcePhotoStr", value = "比对源照片")
    private String sourcePhotoStr;

    @ApiModelProperty(name="sourcePhotoType", value = "比对源照片类型参数值为1 时是:水纹正脸照 2时是:高清正脸照")
    private String sourcePhotoType;

    @ApiModelProperty(name="liveInterType", value = "参数值为1时,表示仅使用实时检测模式, " +
            "参数值非1或不入参,表示优先使用实时检测模式,如遇不兼容情况,自动降级为视频录制模式")
    private String liveInterType;
}


/**
 * @author :xuwei
 * @description:人脸入参,参考https://cloud.tencent.com/document/product/1007/61073
 * @date :2022/8/25 21:43
 */
@Data
@Builder
public class TencentFaceVerifyDto {
    private String appId;
    private String orderNo;
    private String userId;
    private String sourcePhotoStr;
    private String sourcePhotoType;
    private String liveInterType;
    private String version;
    private String sign;
    private String nonce;
    private String ticket;

}

/**
 * @author :xuwei
 * @description:人脸出参,参考https://cloud.tencent.com/document/product/1007/61073
 *              https://cloud.tencent.com/document/product/1007/61074
 * @date :2022/8/25 21:43
 */
@Data
public class TencentFaceVerifyResult {

    private String bizSeqNo;
    private String transactionTime;
    private String orderNo;
    private String faceId;
    private String optimalDomain;
    private String success;

    private String webankAppId;
    private String userId;
    private String version;
    private String nonce;
    private String sign;
    private String redirectUrl;
}

4、处理代码

controller


import com.lynkco.lcem.common.BaseResult;
import com.lynkco.lcem.common.config.LoginUserUtil;
import com.lynkco.lcem.dto.tencentFaceVerify.TencentFaceVerifyDto;
import com.lynkco.lcem.dto.tencentFaceVerify.TencentFaceVerifyParam;
import com.lynkco.lcem.dto.tencentFaceVerify.TencentFaceVerifyResult;
import com.lynkco.lcem.service.common.TencentFaceVerifyService;
import com.lynkco.lcem.util.ObjectUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

/**
 * tencent实人认证
 *
 * @author xuwei
 * @title: TencentFaceVerifyController
 * @projectName lcem-server
 * @description: TODO
 * @date 2022/8/23 09:00
 */
@RefreshScope
@Slf4j
@RestController
@RequestMapping("/tencentFaceVerify")
@Api(value = "腾讯实人认证", tags = {"腾讯实人认证"})
public class TencentFaceVerifyController {

    @Autowired
    TencentFaceVerifyService tencentFaceVerifyService;


    @ApiOperation("上送身份信息")
    @PostMapping("/getAdvFaceId")
    public BaseResult getAdvFaceId(@RequestBody TencentFaceVerifyParam param, HttpServletRequest request) {
        String userId = LoginUserUtil.getCurrentUserId(request);
        TencentFaceVerifyDto dto = TencentFaceVerifyDto.builder().sourcePhotoStr(param.getSourcePhotoStr())
                .sourcePhotoType(param.getSourcePhotoType()).liveInterType(param.getLiveInterType())
                .userId(userId).build();
        String advFaceId = tencentFaceVerifyService.getAdvFaceId(dto);
        if (!ObjectUtils.isEmpty(advFaceId)){
            return BaseResult.ok(advFaceId);
        }
        return BaseResult.ok(null);
    }


}

service

public interface TencentFaceVerifyService {

   /**
    * 上送身份信息
    * @param param
    * @return
    */
   String getAdvFaceId(TencentFaceVerifyDto param);

}

serviceImpl



import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Charsets;
import com.google.common.hash.Hashing;
import com.lynkco.lcem.dto.tencentFaceVerify.TencentFaceVerifyDto;
import com.lynkco.lcem.dto.tencentFaceVerify.TencentFaceVerifyResult;
import com.lynkco.lcem.exception.BusinessException;
import com.lynkco.lcem.util.Base64Util;
import com.lynkco.lcem.util.HttpClientUtil;
import com.lynkco.lcem.util.SerialUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;

/**
 * @author :xuwei
 * @description:TODO
 * @date :2022/8/24 16:39
 */
@RefreshScope
@Service
public class TencentFaceVerifyServiceImpl implements TencentFaceVerifyService {

    @Value("${tencentFaceVerify.appId}")
    private String appId;
    @Value("${tencentFaceVerify.appSecret}")
    private String appSecret;
    @Value("${tencentFaceVerify.version}")
    private String version;
    @Value("${tencentFaceVerify.nonce}")
    private String nonce;
    @Value("${tencentFaceVerify.redirectUrl}")
    private String redirectUrl;


    /**
     * 上送身份信息
     * 参考文档:https://cloud.tencent.com/document/product/1007/61073
     * @param param
     * @return
     */
    @Override
    public String getAdvFaceId(TencentFaceVerifyDto param) {
        String accessToken = getAccessToken();
        String signTickets = getSignTickets(accessToken);

        String encode = Base64Util.GetImageStrFromUrl(param.getSourcePhotoStr());
        param.setSign(beforeSign(param.getUserId(), signTickets));
        param.setSourcePhotoStr(encode);
        param.setOrderNo(SerialUtil.getUUID());
        param.setAppId(appId);
        param.setVersion(version);
        param.setNonce(nonce);

        try {
            String jsonStr = JSONUtil.toJsonStr(param);
            String result = HttpClientUtil.doPostJson("https://kyc.qcloud.com/api/server/getAdvFaceId", jsonStr, null);
            Map<String, Object> resultMap = JSONObject.parseObject(result, Map.class);
            if (resultMap.get("code").equals("0")) {
                TencentFaceVerifyResult verifyResult = JSONObject.parseObject(resultMap.get("result").toString(), TencentFaceVerifyResult.class);
                verifyResult.setUserId(param.getUserId());
                //启动 H5 人脸核身地址
                String url = getUrl(param, accessToken, verifyResult);

                return url;
            }
        } catch (Exception e) {
            throw new BusinessException("后台上送身份信息失败:" + e.getMessage());
        }
        return null;
    }

    /**
     * 启动 H5 人脸核身地址
     * 参考文档:https://cloud.tencent.com/document/product/1007/61074
     * @param param
     * @param accessToken
     * @param verifyResult
     * @return
     * @throws UnsupportedEncodingException
     */
    private String getUrl(TencentFaceVerifyDto param, String accessToken, TencentFaceVerifyResult verifyResult) throws UnsupportedEncodingException {
        String nonceTickets = getNonceTickets(accessToken, param.getUserId());
        String sign = afterSign(verifyResult, nonceTickets);
        String encodeUrl = URLEncoder.encode(redirectUrl, "UTF-8");
        String url = "https://" + verifyResult.getOptimalDomain() + "/api/web/login?webankAppId=" + appId
                + "&version=" + version + "&nonce=" + nonce + "&orderNo=" + verifyResult.getOrderNo() +
                "&faceId=" + verifyResult.getFaceId() + "&url=" + encodeUrl + "&from=browser&userId=" + param.getUserId()
                + "&sign=" + sign + "&redirectType=1";
        return url;
    }


    /**
     * 获取 Access Token
     * 参考文档:https://cloud.tencent.com/document/product/1007/37304
     * @return
     */
    public String getAccessToken() {
        Map<String, String> map = new HashMap<>();
        map.put("app_id", appId);
        map.put("secret", appSecret);
        map.put("grant_type", "client_credential");
        map.put("version", version);

        String result = HttpClientUtil.doGet("https://miniprogram-kyc.tencentcloudapi.com/api/oauth2/access_token", map);
        Map<String, Object> resultMap = JSONObject.parseObject(result, Map.class);
        if (resultMap.get("code").equals("0")) {
            Object access_token = resultMap.get("access_token");
            return access_token.toString();
        }
        return null;
    }

    /**
     * 获取 SIGN ticket
     * 参考文档:https://cloud.tencent.com/document/product/1007/37305
     * @param accessToken
     * @return
     */
    public String getSignTickets(String accessToken) {
        Map<String, String> map = new HashMap<>();
        map.put("app_id", appId);
        map.put("access_token", accessToken);
        map.put("type", "SIGN");
        map.put("version", version);

        String result = HttpClientUtil.doGet("https://miniprogram-kyc.tencentcloudapi.com/api/oauth2/api_ticket", map);
        Map<String, Object> resultMap = JSONObject.parseObject(result, Map.class);
        if (resultMap.get("code").equals("0")) {
            List<Map> tickets = JSONObject.parseArray(resultMap.get("tickets").toString(), Map.class);
            Object ticket = tickets.get(0).get("value");
            return ticket.toString();
        }
        return null;
    }

    /**
     * 获取 NONCE ticket
     * 参考文档:https://cloud.tencent.com/document/product/1007/37306
     * @param accessToken
     * @param userId
     * @return
     */
    public String getNonceTickets(String accessToken, String userId) {
        Map<String, String> map = new HashMap<>();
        map.put("app_id", appId);
        map.put("access_token", accessToken);
        map.put("type", "NONCE");
        map.put("version", version);
        map.put("user_id", userId);

        String result = HttpClientUtil.doGet("https://miniprogram-kyc.tencentcloudapi.com/api/oauth2/api_ticket", map);
        Map<String, Object> resultMap = JSONObject.parseObject(result, Map.class);
        if (resultMap.get("code").equals("0")) {
            List<Map> tickets = JSONObject.parseArray(resultMap.get("tickets").toString(), Map.class);
            Object ticket = tickets.get(0).get("value");
            return ticket.toString();
        }
        return null;
    }

    /**
     * 启动 H5 人脸核身所需签名
     * @param verifyResult
     * @param ticket
     * @return
     */
    public String afterSign(TencentFaceVerifyResult verifyResult, String ticket) {
        List<String> values = new ArrayList<>();
        values.add(version);
        values.add(verifyResult.getOrderNo());
        values.add(appId);
        values.add(verifyResult.getFaceId());
        values.add(nonce);
        values.add(verifyResult.getUserId());

        return sign(values, ticket);
    }

    /**
     * 后台上送身份信息所需签名
     * @param userId
     * @param ticket
     * @return
     */
    public String beforeSign(String userId, String ticket) {
        List<String> values = new ArrayList<>();
        values.add(version);
        values.add(appId);
        values.add(nonce);
        values.add(userId);
        return sign(values, ticket);
    }

    /**
     * 获取签名 参考文档 https://cloud.tencent.com/document/product/1007/57640
     * @param values
     * @param ticket
     * @return
     */
    public static String sign(List<String> values, String ticket) {
        if (values == null) {
            throw new NullPointerException("values is null");
        }
        values.removeAll(Collections.singleton(null));
        values.add(ticket);
        java.util.Collections.sort(values);
        StringBuilder sb = new StringBuilder();
        for (String s : values) {
            sb.append(s);
        }

        return Hashing.sha1().hashString(sb,
                Charsets.UTF_8).toString().toUpperCase();
    }


}

在获取启动 H5 人脸核身地址时,将地址返回给前端,让他直接调就行。或许自己可以直接在浏览器打开那个地址,如果成功的话,会调到腾讯云的人脸核身页面。
这里用来比对的原图片是网络图片,如果是本地图片的话,直接将本地图片base64后encode就行。另外,回调的地址也要encode一下。不然会有40010错误码出现。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码

)">
< <上一篇
下一篇>>