web人脸登录

1,需要腾讯人脸第三方
腾讯人脸第三方申请步骤
2,相关表结构设计

create database faceDB;

use faceDB;
-- 人脸表
create table face(
fid int primary key auto_increment COMMENT '主键',
face_base longtext COMMENT '图片数据 base_64编码',
create_time datetime COMMENT '插入时间',
vef_num int COMMENT '验证次数',
face_name varchar(100) COMMENT '人脸名称',
remark varchar(200) COMMENT '人脸备注',
face_status int COMMENT '人脸是否可用,(0==可用,1,不可用)',
update_extend1 varchar(300) COMMENT '扩展字段1',
update_extend2 varchar(300) COMMENT '扩展字段2',
update_extend3 varchar(300) COMMENT '扩展字段3'
);
-- 验证日志表
create table face_vef_log(
lid int primary key auto_increment COMMENT '主键',
vef_time datetime COMMENT '验证时间',
vef_code int COMMENT '返回code',
vef_msg varchar(200) COMMENT '返回的消息',
login_name varchar(100) COMMENT '验证人'
);

3,相关配置

tencentcloudapi:
  # 你的 secretId
  secretId: 你腾讯云的secretId
  # 你的 secretKey
  secretKey: 你腾讯云的secretKey
  # 请求官方地址 不变
  endpoint: iai.tencentcloudapi.com
  #地域 可不变
  region: ap-shanghai
package com.face.config;

import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.face.utils.JwtUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

/**
 * @author tanyongpeng
 * <p>des</p>
 **/
@Component
@Slf4j
public class FaceConfig implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String face_token = request.getHeader("face_token");
        Map<String,Object> infoMap = new HashMap<>();
        try{
            //token验证
            DecodedJWT tokenInfo = JwtUtils.getTokenInfo(face_token);
            //添加返回信息
            infoMap.put("msg","验证成功");
            return true;
        }catch (SignatureVerificationException e){
            e.printStackTrace();
            infoMap.put("msg","无效签名");
        }catch (TokenExpiredException e){
            infoMap.put("msg","token已过期");
        }catch (AlgorithmMismatchException e){
            infoMap.put("msg","算法不一致");
        }catch (Exception e){
            infoMap.put("msg","无效签名");
        }
        ObjectMapper objectMapper = new ObjectMapper();
        String mapInfoJson = objectMapper.writeValueAsString(infoMap);
        response.setContentType("application/json;charset=UTF-8");
        response.setCharacterEncoding("UTF-8");
        PrintWriter writer = response.getWriter();
        writer.write(mapInfoJson);
        return false;
    }
}
package com.face.server;

import com.face.bean.result.FaceResult;
import com.face.utils.TimeUtils;
import com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import com.tencentcloudapi.common.profile.ClientProfile;
import com.tencentcloudapi.common.profile.HttpProfile;
import com.tencentcloudapi.iai.v20180301.IaiClient;
import com.tencentcloudapi.iai.v20180301.models.CompareFaceRequest;
import com.tencentcloudapi.iai.v20180301.models.CompareFaceResponse;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

/**
 * @author tanyongpeng
 * <p>调用腾讯接口</p>
 **/
@Component
@Data
@Slf4j
public class FaceContrastServer {

    @Value("${tencentcloudapi.secretId}")
    private String secretId;
    @Value("${tencentcloudapi.secretKey}")
    private String secretKey;
    @Value("${tencentcloudapi.endpoint}")
    private String endpoint;
    @Value("${tencentcloudapi.region}")
    private String region;
	//第一张是数据库图片,第二张是登录时验证图片
    public FaceResult faceContrast(String imageA, String imageB){
        FaceResult faceResult = new FaceResult();
        try{
            Credential cred = new Credential(secretId, secretKey);
            HttpProfile httpProfile = new HttpProfile();
            httpProfile.setEndpoint(endpoint);
            ClientProfile clientProfile = new ClientProfile();
            clientProfile.setHttpProfile(httpProfile);
            IaiClient client = new IaiClient(cred, region, clientProfile);
            CompareFaceRequest req = new CompareFaceRequest();
            req.setImageA(imageA);
            req.setImageB(imageB);
            CompareFaceResponse resp = client.CompareFace(req);
            faceResult.setScore(resp.getScore());
            faceResult.setCode(FaceResult.SUCCESS_CODE);
        } catch (TencentCloudSDKException e) {
            faceResult.setCode(FaceResult.FACE_ERROR);
            faceResult.setMsg(e.getMessage());
        }
        return faceResult;
    }
}

4,人脸注册,登录后端代码

package com.face.service.impl;

import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.face.bean.Face;
import com.face.bean.result.FaceResult;
import com.face.mapper.FaceMapper;
import com.face.server.FaceContrastServer;
import com.face.service.FaceService;
import com.face.utils.JwtUtils;
import com.face.utils.TimeUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* @author typsusan
* @description 针对表【face】的数据库操作Service实现
* @createDate 2022-07-17 03:33:50
*/
@Service
public class FaceServiceImpl extends ServiceImpl<FaceMapper, Face>
    implements FaceService {

    @Autowired
    FaceContrastServer faceContrastServer;

    @Override
    public FaceResult vef(String imageBase) {
        imageBase = JSONUtil.parseObj(imageBase).getStr("imageBase");
        List<Face> faceList = lambdaQuery().orderByDesc(Face::getVefNum).list();
        FaceResult faceState = null;
        // 如果人脸库为空,则第一次登录为录入人脸
        if (faceList.size() == 0){
            return initFace(imageBase);
        }else {
            int faceLength = faceList.size();
            for (Face face : faceList) {
                FaceResult faceResult = faceContrastServer.faceContrast(face.getFaceBase(), imageBase);
                // 是否比对成功
                if (faceResult.getCode() == FaceResult.SUCCESS_CODE ){
                    // 相似度是否大于80
                    if (faceResult.getScore() > FaceResult.SATISFY_SCORE){
                        if (face.getFaceStatus() == 0){
                            // 成功
                            lambdaUpdate().set(Face::getVefNum,face.getVefNum()+1).eq(Face::getFid,face.getFid()).update();
                            faceResult.setMsg(TimeUtils.timeQuantum()+"好,"+face.getFaceName());
                            faceResult.setName(face.getFaceName());
                            Map<String,String> map = new HashMap<>();
                            map.put("score",String.valueOf(faceResult.getScore()));
                            map.put("faceName",faceResult.getName());
                            faceResult.setToken(JwtUtils.genereteToken(map));
                            return faceResult;
                        }else {
                            // 失败 人脸被禁用
                            lambdaUpdate().set(Face::getVefNum,face.getVefNum()+1).eq(Face::getFid,face.getFid()).update();
                            faceResult.setMsg(face.getFaceName()+",当前人脸被禁用");
                            faceResult.setName(face.getFaceName());
                            faceResult.setCode(FaceResult.FORBIDDEN_FACE);
                            faceState = faceResult;
                            // 就算上一张人脸被禁用还得往下执行
                            // 可能当前用户存在多张人脸,
                            if (faceLength == 1){
                                return faceResult;
                            }
                            faceLength --;
                        }
                    }else {
                        // 人脸库没有检测到人脸
                        if (faceLength == 1){
                            // 判断当前人脸是否被禁用,如被禁用,提示被禁用
                            // 禁用优先级大于 没有检测到人脸
                            return faceState != null?faceState:FaceResult.error(FaceResult.NOT_FOUND_FACE,"人脸库不存在该人脸",faceResult.getScore());
                        }
                        faceLength --;
                    }
                }else {
                    // 接口返回异常
                    return faceResult;
                }
            }
        }
        // 空异常
        return FaceResult.error(FaceResult.NULL_ERROR,"空指针异常");
    }


    public FaceResult initFace(String imageBase){
        FaceResult faceResult = new FaceResult();
        Face face = new Face();
        face.setFaceBase(imageBase);
        face.setCreateTime(new Date());
        face.setVefNum(0);
        face.setFaceName("admin");
        face.setFaceStatus(0);
        boolean save = save(face);
        faceResult.setCode(FaceResult.INIT_FACE);
        faceResult.setMsg("人脸初始化"+(save?"成功":"失败")+","+(save?"请验证登录":"请稍后再试"));
        faceResult.setName(face.getFaceName());
        return faceResult;
    }
}

5,前端代码

/**
 * 获取 浏览器 拍照的权限
 */
/**
 * 获取浏览器权限
 * @param option
 */
function getCamera(option) {
    option.thisCancas = document.getElementById(option.canvasId);
    option.thisContext = option.thisCancas.getContext("2d");
    option.thisVideo = document.getElementById(option.videoId);
    option.thisVideo.style.display = "block";
    // 获取媒体属性,旧版本浏览器可能不支持mediaDevices,我们首先设置一个空对象
    if (navigator.mediaDevices === undefined) {
        navigator.mediaDevices = {};
    }
    // 一些浏览器实现了部分mediaDevices,我们不能只分配一个对象
    // 使用getUserMedia,因为它会覆盖现有的属性。
    // 这里,如果缺少getUserMedia属性,就添加它。
    if (navigator.mediaDevices.getUserMedia === undefined) {
        navigator.mediaDevices.getUserMedia = function (constraints) {
            // 首先获取现存的getUserMedia(如果存在)
            var getUserMedia =
                navigator.webkitGetUserMedia ||
                navigator.mozGetUserMedia ||
                navigator.getUserMedia;
            // 有些浏览器不支持,会返回错误信息
            // 保持接口一致
            if (!getUserMedia) {
                //不存在则报错
                return Promise.reject(
                    new Error("getUserMedia is not implemented in this browser")
                );
            }
            // 否则,使用Promise将调用包装到旧的navigator.getUserMedia
            return new Promise(function (resolve, reject) {
                getUserMedia.call(navigator, constraints, resolve, reject);
            });
        };
    }
    var constraints = {
        audio: false,
        video: {
            width: option.videoWidth,
            height: option.videoHeight,
            transform: "scaleX(-1)",
        },
    };
    navigator.mediaDevices
        .getUserMedia(constraints)
        .then(function (stream) {
            // 旧的浏览器可能没有srcObject
            if ("srcObject" in option.thisVideo) {
                option.thisVideo.srcObject = stream;
            } else {
                // 避免在新的浏览器中使用它,因为它正在被弃用。
                option.thisVideo.src = window.URL.createObjectURL(stream);
            }
            option.thisVideo.onloadedmetadata = function (e) {
                option.thisVideo.play();
            };
        })
        .catch((err) => {
            console.log(err);
        });
    return option
}

/**
 * 绘制图片
 * 返回格式为base64
 * @param option
 */
function draw(option){
    option.thisContext.drawImage(option.thisVideo,0,0,option.videoWidth,option.videoHeight);
    return option.thisCancas.toDataURL("image/png");
}

export default {
    getCamera,
    draw
}
<template>
  <div>
    <div class="login">
    </div>


    <!--登录中间块-->
    <div class="login-mid">
      <div class="login-mid-top">
        <div class="shadow-top-left"></div>
        <div class="shadow-top-right"></div>
      </div>
      <div class="login-mid-mid">

        <!--捕获人脸区域-->
        <div class="videoCamera-canvasCamera">
              <video
                  id="videoCamera"
                  :width="videoWidth"
                  :height="videoHeight"
                  autoplay
              ></video>
              <canvas
                  style="display: none"
                  id="canvasCamera"
                  :width="videoWidth"
                  :height="videoHeight"
              ></canvas>

          <!--人脸特效区域-->
          <div v-if="faceImgState" class="face-special-effects-2"></div>
          <div v-else class="face-special-effects"></div>

        </div>

        <!--按钮区域-->
        <div class="face-btn">
          <button @click="faceVef()">{{faceImgState?'正在识别中...':'开始识别'}}</button>
        </div>

        <!--消息区域-->
        <div class="msg">
            <div class="server-msg">{{msg}}</div>
            <div class="welcome">Welcome to face recognition</div>
        </div>

      </div>
      <div class="login-mid-bot">
        <div class="shadow-bot-left"></div>
        <div class="shadow-bot-right"></div>
      </div>
    </div>


  </div>

</template>
<script>
import $camera from '../../camera/index.js'
export default {
  data() {
    return {
      videoWidth: 200,
      videoHeight: 200,
      msg:'',
      faceImgState:false,
      faceOption:{}
    };
  },
  mounted() {
    //调用摄像头
    this.faceOption = $camera.getCamera({
      videoWidth: 200,
      videoHeight: 200,
      thisCancas: null,
      thisContext: null,
      thisVideo: null,
      canvasId:'canvasCamera',
      videoId:'videoCamera'
    });

    //this.getCompetence();
  },
  methods: {
    // 调用后台接口
    faceVef(){
      // 开始绘制图片
      let imageBase = $camera.draw(this.faceOption)
      if (this.faceImgState){
        return
      }
      this.faceImgState = true
      if (imageBase === '' || imageBase === null || imageBase === undefined){
        this.$message.error("图片数据为空")
      }else {
        this.$http.post("/face/vef",{imageBase}).then(res =>{
          console.log(res)
          this.faceImgState = false
          // 跳转首页
          if (res.data.code === 200){
            // 关闭摄像头
            this.faceOption.thisVideo.srcObject.getTracks()[0].stop();
            localStorage.setItem("face_token",res.data.token);
            localStorage.setItem("username",res.data.name);
            this.$message.success(res.data.msg)
            this.$router.push("/home")
          }
          if (res.data.code === 201){
            this.$message.success(res.data.msg)
          }
        },onerror =>{
          this.faceImgState = false
        })
      }
    }
  },
};
</script>
<style>
@import "./index.css";
</style>

6,实现实例

参考:项目后端地址face-easy
参考:项目前端地址face-ui

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值