刷脸登录(人工智能)

刷脸登录

理解刷脸登录的需求

理解刷脸登录的开发流程实现刷脸登录功能

浅谈人工智能

人工智能的概述

人工智能(Artificial Intelligence),英文缩写为AI。它是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学

人工智能是计算机科学的一个分支,它企图了解智能的实质,并生产出一种新的能以人类智能相似的方式做出反应 的智能机器,该领域的研究包括机器人、语言识别、图像识别、自然语言处理和专家系统等。人工智能从诞生以 来,理论和技术日益成熟,应用领域也不断扩大,可以设想,未来人工智能带来的科技产品,将会是人类智慧

的“容器”。人工智能可以对人的意识、思维的信息过程的模拟。人工智能不是人的智能,但能像人那样思考、也可 能超过人的智能。

人工智能的应用领域

随着智能家电、穿戴设备、智能机器人等产物的出现和普及,人工智能技术已经进入到生活的各个领域,引发越来 越多的关注。

基于人工智能的刷脸登录介绍

刷脸登录是基于人工智能、生物识别、3D传感、大数据风控技术,最新实现的登录形式。用户在无需输入用户名 密码的前提下,凭借“刷脸”完成登录过程。实现刷脸登录的核心是人脸处理,在人脸处理中有两个概念:

  • 人脸检测:检测图中的人脸,并为人脸标记出边框。检测出人脸后,可对人脸进行分析,获得眼、口、鼻轮 廓等72个关键点定位准确识别多种人脸属性,如性别,年龄,表情等信息
  • 人脸识别(对比):通过提取人脸的特征,计算两张人脸的相似度,从而判断是否同一个人,并给出相似度 评分。

作为中小型企业,可以采取世面上流行的人工智能产品快速的实现刷脸登录需求。目前比较流行人脸检测产品如下:

  • Face++
  • 腾讯优图
  • 科大讯飞
  • 百度云AI

百度云AI概述

概述

百度人脸识别基于深度学习的人脸识别方案,准确识别图片中的人脸信息,提供如下功能:

  • 人脸检测:精准定位图中人脸,获得眼、口、鼻等72个关键点位置,分析性别、年龄、表情等多种人脸属性
  • 人脸对比:对比两张人脸的相似度,并给出相似度评分,从而判断是否同一个人
  • 人脸搜索:针对一张人脸照片,在指定人脸集合中搜索,找出最相似的一张脸或多张人脸,并给出相似度分 值
  • 活体检测:提供离线/在线方式的活体检测能力,判断操作用户是否为真人,有效抵御照片、视频、模具等作 弊攻击
  • 视频流人脸采集:设备端离线实时监测视频流中的人脸,同时支持处理静态图片或者视频流,输出人脸图片 并进行图片质量控制

百度云AI的开发步骤

  • 注册账号创建应用
  • 搭建工程导入依赖
  • 人脸注册
  • 人脸识别

百度云AI的注册与认证

  • 注册百度云帐号

打开百度云平台:百度智能云-注册进行账号注册

  • 激活人脸识别,并创建应用

找到产品-人工智能-人脸识别激活应用,并注册应用

应用创建完成之后,进入刚刚创建的应用获取开发所需的AppID,API Key,Secret Key。

百度云API的入门

搭建环境

创建工程并导入依赖:

<dependency><groupId>com.baidu.aip</groupId><artifactId>java-sdk</artifactId><version>4.8.0</version>
</dependency>

人脸注册

用于从人脸库中新增用户,可以设定多个用户所在组,及组内用户的人脸图片典型应用场景:构建您的人脸库,如会员人脸注册已有用户补全人脸信息等。

代码实现

public class FaceTest {private AipFace client;@Beforepublic void init() {//1.创建java代码和百度云交互的client对象client = new AipFace("15246423","QFYuYWDlb5mHTA8I9XTGlgjM","mvgF3pPoOLsMadFGiemuzt0ayE3jFIOv");}//人脸注册:向百度的人脸库中添加用户人脸照片@Testpublic void testFaceRegister() throws Exception {//2.参数设置HashMap<String,String> options = new HashMap<>();options.put("quality_control","NORMAL");//图片质量  NONE  LOW  NORMAL,HIGHoptions.put("liveness_control","LOW");//活体检测options.put("action_type","replace");//活体检测//3.构造图片String path = "C:\\Users\\ThinkPad\\Desktop\\002.png";//上传的图片  两种格式 : url地址,Base64字符串形式byte[] bytes = Files.readAllBytes(Paths.get(path));String encode = Base64Util.encode(bytes);//4.调用api方法完成人脸注册/*** 参数一:(图片的url或者图片的Base64字符串),* 参数二:图片形式(URL,BASE64)* 参数三:组ID(固定字符串)* 参数四:用户ID* 参数五:hashMap中的基本参数配置*/JSONObject res = client.addUser(encode, "BASE64", "itcast", "1003", options);System.out.println(res.toString());}
}

骚戴理解:可以通过 byte[] bytes = Files.readAllBytes(Paths.get(path));来把一个图片的路径转成二进制数组,参数设置通常只需要设置两个参数即可,图片质量和活体检测的级别!人脸注册其实就是把一个图片上传到百度云的人脸库里,这就跟七牛云的图片上传差不多,甚至更简单,只需要client.addUser,然后补充五个参数即可,addUser方法的五个参数含义:

  • 参数一:(图片的url或者图片的Base64字符串)
  • 参数二:图片形式(URL,BASE64)
  • 参数三:组ID(固定字符串)
  • 参数四:用户ID
  • 参数五:hashMap中的基本参数配置

还需要注意的是AipFace的值要换成自己创建应用的三个参数AppID,API Key,Secret Key!

人脸注册 请求参数详情

参数名称

是否必选

类型

默认值

说明

image

String

图片信息(总数据大小应小于10M),图片上传方式根据

image_type来判断

image_type

String

图片类型 BASE64:图片的base64值,base64编码后的图片数据,需urlencode,编码后的图片大小不超过2M; URL:图片的 URL地址( 可能由于网络等原因导致下载图片时间过长);FACE_TOKEN: 人脸图片的唯一标识,调用人脸检测接口时,会为每个人脸图片赋予一个唯一的FACE_TOKEN,同一张图片多次检测得到的FACE_TOKEN 是同一个

group_id

String

用户组id(由数字、字母、下划线组成),长度限制128B

user_id

String

用户id(由数字、字母、下划线组成),长度限制128B

user_info

String

用户资料,长度限制256B

quality_control

String

NONE

图片质量控制 NONE: 不进行控制 LOW:较低的质量要求NORMAL: 一般的质量要求 HIGH: 较高的质量要求 默认NONE

liveness_control

String

NONE

活体检测控制 NONE: 不进行控制 LOW:较低的活体要求

(高通过率 低攻击拒绝率) NORMAL: 一般的活体要求(平衡的攻击拒绝率, 通过率) HIGH: 较高的活体要求(高攻击拒绝率 低通过率) 默认NONE

骚戴理解:上面表格的参数前面五个都是addUser方法的五个参数,后面的两个是参数设置的两个参数,也就是图片质量和活体检测级别这两个参数

人脸注册 返回数据参数详情

字段

必选

类型

说明

log_id

uint64

请求标识码,随机数,唯一

face_token

string

人脸图片的唯一标识

location

array

人脸在图片中的位置

+left

double

人脸区域离左边界的距离

+top

double

人脸区域离上边界的距离

+width

double

人脸区域的宽度

+height

double

人脸区域的高度

+rotation

int64

人脸框相对于竖直方向的顺时针旋转角,[-180,180]

人脸更新

用于对人脸库中指定用户,更新其下的人脸图像。

代码实现

public class FaceTest {private AipFace client;@Beforepublic void init() {//1.创建java代码和百度云交互的client对象client = new AipFace("15246423","QFYuYWDlb5mHTA8I9XTGlgjM","mvgF3pPoOLsMadFGiemuzt0ayE3jFIOv");}/***  人脸更新:更新人脸库中的照片*/@Testpublic void testFaceUpdate() throws Exception {//2.参数设置HashMap<String,String> options = new HashMap<>();options.put("quality_control","NORMAL");//图片质量  NONE  LOW  NORMAL,HIGHoptions.put("liveness_control","LOW");//活体检测options.put("action_type","replace");//3.构造图片String path = "C:\\Users\\ThinkPad\\Desktop\\002.png";//上传的图片  两种格式 : url地址,Base64字符串形式byte[] bytes = Files.readAllBytes(Paths.get(path));String encode = Base64Util.encode(bytes);//4.调用api方法完成人脸更新/*** 参数一:(图片的url或者图片的Base64字符串),* 参数二:图片形式(URL,BASE64)* 参数三:组ID(固定字符串)* 参数四:用户ID* 参数五:hashMap中的基本参数配置*/JSONObject res = client.updateUser(encode, "BASE64", "itcast", "10001", options);System.out.println(res.toString());}
}

骚戴理解:人脸更新的需求是随着人的年龄增加,人的人脸特征会发生变化,再或者整容了的人的人脸特征都是巨变,所以需要更新人脸库里的人脸信息,这个更新和注册基本一样,唯一个不同就是调用的方法不同,更新调用的是updateUser方法,其他的和人脸注册一模一样!

人脸更新 请求参数详情

参数名称

是否必选

类型

默认值

说明

image

String

图片信息(总数据大小应小于10M),图片上传方式根据

image_type来判断

image_type

String

图片类型 BASE64:图片的base64值,base64编码后的图片数据,需urlencode,编码后的图片大小不超过2M; URL:图片的 URL地址( 可能由于网络等原因导致下载图片时间过长);FACE_TOKEN: 人脸图片的唯一标识,调用人脸检测接口时,会为每个人脸图片赋予一个唯一的FACE_TOKEN,同一张图片多次检测得到的FACE_TOKEN 是同一个

group_id

String

更新指定groupid下uid对应的信息

user_id

String

用户id(由数字、字母、下划线组成),长度限制128B

user_info

String

用户资料,长度限制256B

quality_control

String

NONE

图片质量控制 NONE: 不进行控制 LOW:较低的质量要求NORMAL: 一般的质量要求 HIGH: 较高的质量要求 默认NONE

liveness_control

String

NONE

活体检测控制 NONE: 不进行控制 LOW:较低的活体要求

(高通过率 低攻击拒绝率) NORMAL: 一般的活体要求(平衡的攻击拒绝率, 通过率) HIGH: 较高的活体要求(高攻击拒绝率 低通过率) 默认NONE

人脸更新 返回数据参数详情

字段

必选

类型

说明

log_id

uint64

请求标识码,随机数,唯一

face_token

string

人脸图片的唯一标识

location

array

人脸在图片中的位置

+left

double

人脸区域离左边界的距离

+top

double

人脸区域离上边界的距离

+width

double

人脸区域的宽度

+height

double

人脸区域的高度

+rotation

int64

人脸框相对于竖直方向的顺时针旋转角,[-180,180]

人脸检测

人脸检测:检测图片中的人脸并标记出位置信息;

代码实现

public class FaceTest {private AipFace client;@Beforepublic void init() {//1.创建java代码和百度云交互的client对象client = new AipFace("15246423","QFYuYWDlb5mHTA8I9XTGlgjM","mvgF3pPoOLsMadFGiemuzt0ayE3jFIOv");}/*** 人脸检测:判断图片中是否具有面部信息*/@Testpublic void testFaceCheck() throws Exception {//构造图片String path = "C:\\Users\\ThinkPad\\Desktop\\001.png";//上传的图片  两种格式 : url地址,Base64字符串形式byte[] bytes = Files.readAllBytes(Paths.get(path));String image = Base64Util.encode(bytes);//调用api方法进行人脸检测//参数一:(图片的url或者图片的Base64字符串)//参数二:图片形式(URL,BASE64)//参数三:hashMap中的基本参数配置(null:使用默认配置)JSONObject res = client.detect(image, "BASE64", null);System.out.println(res.toString(2));}
}

骚戴理解:人脸检测其实就是看你上传的图片里面有没有人脸,注意这里不是检验人脸,只是单纯的判断检测的图片有没有人脸信息!同样调用client的detect方法判断即可,detect的三个参数:

  • 参数一:(图片的url或者图片的Base64字符串)
  • 参数二:图片形式(URL,BASE64)
  • 参数三:hashMap中的基本参数配置(null:使用默认配置)

人脸检测 请求参数详情

参数名称

是否必选

类型

默认值

说明

image

String

图片信息(总数据大小应小于10M),图片上传方式根据image_type来判断

image_type

String

图片类型 BASE64:图片的base64值,base64编码后的图片数据,需urlencode,编码后的图片大小不超过2M;URL:图片的 URL地址( 可能由于网络等原因导致下载图片时间过长);FACE_TOKEN: 人脸图片的唯一标识,调用人脸检测接口时,会为每个人脸图片赋予一个唯一的FACE_TOKEN,同一张图片多次检测得到的FACE_TOKEN是同一个

face_field

String

包括age,beauty,expression,faceshape,gender,glasses,landmark,race,quality,facetype信息 逗号分隔. 默认只返回face_token、人脸框、概率和旋转角度

max_face_num

String

1

最多处理人脸的数目,默认值为1,仅检测图片中面积最大的那个人脸;最大值10,检测图片中面积最大的几张人脸。

face_type

String

人脸的类型 LIVE表示生活照:通常为手机、相机拍摄的人像图片、或从网络获取的人像图片等IDCARD表示身份证芯片照:二代身份证内置芯片中的人像照片 WATERMARK表示带水印证件照:一般为带水印的小图,如公安网小图 CERT表示证件照片:如拍摄的身份证、工卡、护照、学生证等证件图片 默认LIVE

人脸检测 返回数据参数详情

字段

必选

类型

说明

face_num

int

检测到的图片中的人脸数量

face_list

array

人脸信息列表,具体包含的参数参考下面的列表。

+face_token

string

人脸图片的唯一标识

+location

array

人脸在图片中的位置

++left

double

人脸区域离左边界的距离

++top

double

人脸区域离上边界的距离

++width

double

人脸区域的宽度

++height

double

人脸区域的高度

++rotation

int64

人脸框相对于竖直方向的顺时针旋转角,[-180,180]

+face_probability

double

人脸置信度,范围【0~1】,代表这是一张人脸的概率,0最小、1 最大。

+angel

array

人脸旋转角度参数

++yaw

double

三维旋转之左右旋转角[-90(左), 90(右)]

++pitch

double

三维旋转之俯仰角度[-90(上), 90(下)]

++roll

double

平面内旋转角[-180(逆时针), 180(顺时针)]

+age

double

年龄 ,当face_field包含age时返回

+beauty

int64

美丑打分,范围0-100,越大表示越美。当face_fields包含beauty 时返回

+expression

array

表情,当 face_field包含expression时返回

++type

string

none:不笑;smile:微笑;laugh:大笑

++probability

double

表情置信度,范围【0~1】,0最小、1最大。

+face_shape

array

脸型,当face_field包含faceshape时返回

++type

double

square: 正方形 triangle:三角形 oval: 椭圆 heart: 心形 round:

圆形

++probability

double

置信度,范围【0~1】,代表这是人脸形状判断正确的概率,0最小、1最大。

+gender

array

性别,face_field包含gender时返回

++type

string

male:男性 female:女性

++probability

double

性别置信度,范围【0~1】,0代表概率最小、1代表最大。

字段

必选

类型

说明

+glasses

array

是否带眼镜,face_field包含glasses时返回

++type

string

none:无眼镜,common:普通眼镜,sun:墨镜

++probability

double

眼镜置信度,范围【0~1】,0代表概率最小、1代表最大。

+race

array

人种 face_field包含race时返回

++type

string

yellow: 黄种人 white: 白种人 black:黑种人 arabs: 阿拉伯人

++probability

double

人种置信度,范围【0~1】,0代表概率最小、1代表最大。

+face_type

array

真实人脸/卡通人脸 face_field包含face_type时返回

++type

string

human: 真实人脸 cartoon: 卡通人脸

++probability

double

人脸类型判断正确的置信度,范围【0~1】,0代表概率最小、1代表最大。

+landmark

array

4个关键点位置,左眼中心、右眼中心、鼻尖、嘴中心。face_field

包含landmark时返回

+landmark72

array

72个特征点位置 face_field包含landmark时返回

+quality

array

人脸质量信息。face_field包含quality时返回

++occlusion

array

人脸各部分遮挡的概率,范围[0~1],0表示完整,1表示不完整

+++left_eye

double

左眼遮挡比例

+++right_eye

double

右眼遮挡比例

+++nose

double

鼻子遮挡比例

+++mouth

double

嘴巴遮挡比例

+++left_cheek

double

左脸颊遮挡比例

+++right_cheek

double

右脸颊遮挡比例

+++chin

double

下巴遮挡比例

++blur

double

人脸模糊程度,范围[0~1],0表示清晰,1表示模糊

++illumination

double

取值范围在[0~255], 表示脸部区域的光照程度 越大表示光照越好

++completeness

int64

人脸完整度,0或1, 0为人脸溢出图像边界,1为人脸都在图像边界内

+parsing_info

string

人脸分层结果 结果数据是使用gzip压缩后再base64编码 使用前需base64解码后再解压缩 原数据格式为string 形如0,0,0,0,0,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,…

人脸搜索

在指定人脸集合中,找到最相似的人脸

代码实现

public class FaceTest {private AipFace client;@Beforepublic void init() {//1.创建java代码和百度云交互的client对象client = new AipFace("15246423","QFYuYWDlb5mHTA8I9XTGlgjM","mvgF3pPoOLsMadFGiemuzt0ayE3jFIOv");}/*** 人脸搜索:根据用户上传的图片和指定人脸库中的所有人脸进行比较,*          获取相似度最高的一个或者某几个的评分**  说明:返回值(数据,只需要第一条,相似度最高的数据)*       score:相似度评分(80分以上可以认为是同一个人)*/@Testpublic void testFaceSearch() throws Exception {//构造图片String path = "C:\\Users\\ThinkPad\\Desktop\\003.png";byte[] bytes = Files.readAllBytes(Paths.get(path));String image = Base64Util.encode(bytes);//人脸搜索JSONObject res = client.search(image, "BASE64", "itcast", null);System.out.println(res.toString(2));}
}

骚戴理解:人脸搜索才是真正的检验上传的图片和图片库里的所有图片的相似度,会返回一个数组,然后按照相似度从高到低排序,这里只需要获取第一个最高的评分,也就在最像的,只要返回的score大于80分就可以认为是同一个人!同样也是调用client的search()方法校验即可,注意search方法的四个参数的含义:

  • 参数一:(图片的url或者图片的Base64字符串)
  • 参数二:图片形式(URL,BASE64)
  • 参数三:组ID(固定字符串)
  • 参数四:hashMap中的基本参数配置

人脸搜索 请求参数详情

参数名称

是否必选

类型

默认值

说明

image

String

图片信息(总数据大小应小于10M),图片上传方式根据

image_type来判断

image_type

String

图片类型 BASE64:图片的base64值,base64编码后的图片数据,需urlencode,编码后的图片大小不超过2M; URL:图片的 URL地址( 可能由于网络等原因导致下载图片时间过长);FACE_TOKEN: 人脸图片的唯一标识,调用人脸检测接口时,会为每个人脸图片赋予一个唯一的FACE_TOKEN,同一张图片多次检测得到的FACE_TOKEN 是同一个

group_id_list

String

从指定的group中进行查找 用逗号分隔,上限20个

quality_control

String

NONE

图片质量控制 NONE: 不进行控制 LOW:较低的质量要求NORMAL: 一般的质量要求 HIGH: 较高的质量要求 默认NONE

liveness_control

String

NONE

活体检测控制 NONE: 不进行控制 LOW:较低的活体要求

(高通过率 低攻击拒绝率) NORMAL: 一般的活体要求(平衡的攻击拒绝率, 通过率) HIGH: 较高的活体要求(高攻击拒绝率 低通过率) 默认NONE

user_id

String

当需要对特定用户进行比对时,指定user_id进行比对。即人脸认证功能。

max_user_num

String

查找后返回的用户数量。返回相似度最高的几个用户,默认为1,最多返回20个。

人脸搜索 返回数据参数详情

字段

必选

类型

说明

face_token

string

人脸标志

user_list

array

匹配的用户信息列表

+group_id

string

用户所属的group_id

+user_id

string

用户的user_id

+user_info

string

注册用户时携带的user_info

+score

float

用户的匹配得分,推荐阈值80分

二维码生成

导入依赖

<dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.2.1</version>
</dependency>
<dependency><groupId>com.google.zxing</groupId><artifactId>javase</artifactId><version>3.2.1</version>
</dependency>

测试案例

保存到本地图片测试案例

package cn.itcast.test;import com.baidu.aip.util.Base64Util;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.nio.file.Path;
import java.util.Base64;public class QRCodeTest {public static void main(String[] args) throws Exception {//1.二维码中的信息String content = "http://www.itcast.cn";//2.通过zxing生成二维码(保存到本地图片,支持以data url的形式体现)//创建QRCodeWriter对象QRCodeWriter writer = new QRCodeWriter();//基本配置/*** 1.二维码信息* 2.图片类型* 3.宽度* 4.长度*/BitMatrix bt = writer.encode(content, BarcodeFormat.QR_CODE, 200, 200);//保存二维码到本地Path path = new File("C:\\Users\\ThinkPad\\Desktop\\test.png").toPath();MatrixToImageWriter.writeToPath(bt,"png",path);}}

骚戴理解:生成二维码的核心就是QRCodeWriter对象的encode方法,只需要知道encode的四个参数的含义即可,BarcodeFormat.QR_CODE表示图片类型是二维码类型,BitMatrix可以理解为就是一个二维码对象,MatrixToImageWriter其实就是把二维码对象转化成图片的输出流工具

生成dataUrl形式的二维码

package cn.itcast.test;import com.baidu.aip.util.Base64Util;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.nio.file.Path;
import java.util.Base64;public class QRCodeTest {//生成dataUrl形式的二维码public static void main(String[] args) throws Exception {//1.二维码中的信息String content = "http://www.itcast.cn";//2.通过zxing生成二维码(保存到本地图片,支持以data url的形式体现)//创建QRCodeWriter对象QRCodeWriter writer = new QRCodeWriter();//基本配置BitMatrix bt = writer.encode(content, BarcodeFormat.QR_CODE, 200, 200);//创建ByteArrayOutputstreamByteArrayOutputStream os = new ByteArrayOutputStream();//将二维码数据以byte数组的形式保存到ByteArrayOutputstream/*** 1:image对象* 2:图片格式* 3:Outputstream*/BufferedImage image = MatrixToImageWriter.toBufferedImage(bt);ImageIO.write(image,"png",os);//对byte数组进行base64处理String encode = Base64Util.encode(os.toByteArray());System.out.println(new String("data:image/png;base64,"+encode));}
}

骚戴理解:这段代码的作用是将给定的字符串内容生成一个二维码,并将二维码转换为图片格式,最后将图片以base64编码的形式保存到字符串中。整体思路就是先把BitMatrix转成BufferedImage,然后通过ImageIO工具类的write方法把BufferedImage转成ByteArrayOutputStream,在把ByteArrayOutputStream转成Data url!

具体解释如下:

1. `BitMatrix bt = writer.encode(content, BarcodeFormat.QR_CODE, 200, 200);`:使用ZXing库中的`writer`对象将给定的字符串`content`编码成一个二维码,`BarcodeFormat.QR_CODE`表示生成的二维码类型为QR码,`200`和`200`分别表示生成的二维码的宽度和高度为200像素。

2. `ByteArrayOutputStream os = new ByteArrayOutputStream();`:创建一个`ByteArrayOutputStream`对象,用于将二维码数据以byte数组的形式保存。

3. `BufferedImage image = MatrixToImageWriter.toBufferedImage(bt);`:将`BitMatrix`对象转换为`BufferedImage`对象,以便后续将其保存为图片格式。

4. `ImageIO.write(image,"png",os);`:将`BufferedImage`对象以PNG格式写入到`ByteArrayOutputStream`对象中。

5. `String encode = Base64Util.encode(os.toByteArray());`:将`ByteArrayOutputStream`对象中的byte数组以base64编码的形式保存到字符串`encode`中,最终返回该字符串。其中,`Base64Util`是一个自定义的工具类,用于将byte数组转换为base64编码的字符串。

刷脸登录实现

需求分析

为了用户登录的便捷,我们在系统中增加刷脸登录的功能,大致流程如下图:

  • 用户在登录页面触发刷脸登录功能
  • 在该页面中弹出一个二维码,此二维码是后台即时生成,包含特殊标志(但本质上是一个URL链接),后续登录流程将会使用此标志。用户对该二维码进行扫描,并在扫描端(手机或PC,注:此处不建议使用微信扫描)浏览器打开落地页。
  • 打开落地页时,授权使用摄像头,并进行人脸识别,识别成功后,关闭落地页。
  • 识别成功后,登录页面自动检测到成功标识,并获取相关信息,进入系统主页。
  • 技术点
    • 二维码生成
    • 百度云AI
    • Redis
    • 前端摄像头调用

骚戴理解:这里通过上面的图片来更好的理解整个实现流程,简单说就是前端刷脸登录的时候会生成一个唯一的二维码,这个二维码有一个唯一标识,然后把标识存Redis里,初始化为-1,表示状态为未扫描,用户扫描二维码后需要拍照进行人脸识别,然后根据识别结果修改Redis里的状态,识别成功就修改为1,识别失败就修改为0,然后再写一个轮询接口(相当于是一个无限的while循环),隔一会去Redis看看状态,如果状态是1就表示登录成功,如果是0或者-1就登录失败

搭建环境

(1) 引入坐标

<!-- 人脸识别-->
<dependency><groupId>com.baidu.aip</groupId><artifactId>java-sdk</artifactId><version>4.8.0</version>
</dependency>
<!-- 二维码生成-->
<dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.2.1</version>
</dependency>
<dependency><groupId>com.google.zxing</groupId><artifactId>javase</artifactId><version>3.2.1</version>
</dependency>
  • 添加配置
ai:appId: 33876611apiKey: djvLRHxONnWYUpjX6ykggd3nsecretKey: iw9T8FvStFtHLwqIMWiouulVifC6f9wBimageType: BASE64groupId: itcast
qr:url: http://localhost:8080/#/facelogin

骚戴理解:配置文件里的groupId就是设置的人脸库分组的名称!

  • 创建基本的工程结构

在系统微服务中构建基本的Controller代码

package com.ihrm.system.controller;import com.ihrm.common.entity.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Base64Utils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;@RestController
@RequestMapping("/sys/faceLogin")
public class FaceLoginController {/*** 获取刷脸登录二维码返回值:QRCode对象(code,image)*/@RequestMapping(value = "/qrcode", method = RequestMethod.GET)public Result qrcode() throws Exception {return null;}/*** 检查二维码:登录页面轮询调用此方法,根据唯一标识code判断用户登录情况*     查询二维码扫描状态*         返回值:FaceLoginResult*             state :-1,0,1 (userId和token)*/@RequestMapping(value = "/qrcode/{code}", method = RequestMethod.GET)public Result qrcodeCeck(@PathVariable(name = "code") String code) throws Exception {return null;}/*** 人脸登录:根据落地页随机拍摄的面部头像进行登录*          根据拍摄的图片调用百度云AI进行检索查找*/@RequestMapping(value = "/{code}", method = RequestMethod.POST)public Result loginByFace(@PathVariable(name = "code") String code, @RequestParam(name = "file") MultipartFile attachment) throws Exception {return null;}/*** 图像检测,判断图片中是否存在面部头像*/@RequestMapping(value = "/checkFace", method = RequestMethod.POST)public Result checkFace(@RequestParam(name = "file") MultipartFile attachment) throws Exception {return null;}}
  • 在系统微服务中构建基本的Service代码
package com.ihrm.system.service;import com.ihrm.domain.system.response.FaceLoginResult;
import com.ihrm.domain.system.response.QRCode;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;@Service
public class FaceLoginService {@Value("${qr.url}")private String url;//创建二维码public QRCode getQRCode() throws Exception {return null;}//根据唯一标识,查询用户是否登录成功public FaceLoginResult checkQRCode(String code) {return null;}//扫描二维码之后,使用拍摄照片进行登录public String loginByFace(String code, MultipartFile attachment) throws Exception {return null;}//构造缓存keyprivate String getCacheKey(String code) {return "qrcode_" + code;}
}
  • FaceLoginResult实体类
package com.ihrm.domain.system.response;import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;import java.io.Serializable;@Data
@NoArgsConstructor
@ToString
public class FaceLoginResult implements Serializable {private static final long serialVersionUID = -1616426041373762391L;/*** 二维码使用状态*/private String state;/*** 登录信息*/private String token;/*** 用户ID*/private String userId;public FaceLoginResult(String state, String token, String userId) {this.state = state;this.token = token;this.userId = userId;}public FaceLoginResult(String state) {this.state = state;}
}
  • QRCode实体类
package com.ihrm.domain.system.response;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;@Data
@AllArgsConstructor
@NoArgsConstructor
public class QRCode implements Serializable {private static final long serialVersionUID = 4375387973088123002L;/*** 随机生成码*/private String code;/*** Base64 二维码文件*/private String file;
}

二维码生成工具类

package com.ihrm.system.utils;import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.sun.org.apache.xml.internal.security.utils.Base64;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;@Component
public class QRCodeUtil {/*** 生成Base64 二维码*/public String crateQRCode(String content) throws IOException {System.out.println(content);ByteArrayOutputStream os = new ByteArrayOutputStream();try {QRCodeWriter writer = new QRCodeWriter();BitMatrix bitMatrix = writer.encode(content, BarcodeFormat.QR_CODE, 200, 200);BufferedImage bufferedImage = MatrixToImageWriter.toBufferedImage(bitMatrix);ImageIO.write(bufferedImage, "png", os);//添加图片标识return new String("data:image/png;base64," + Base64.encode(os.toByteArray()));} catch (Exception e) {e.printStackTrace();} finally {os.close();}return null;}}

在QRCodeUtil类头添加 @Component 注解,使用时可通过 @Autowired 来自动装配。

百度云人脸识别API工具类

对于百度云AI SDK我们进行一些简单的封装,便于使用时,减少代码冗余。

package com.ihrm.system.utils;import com.baidu.aip.face.AipFace;
import org.json.JSONArray;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import java.util.HashMap;@Component
public class BaiduAiUtil {@Value("${ai.appId}")private String APP_ID;@Value("${ai.apiKey}")private String API_KEY;@Value("${ai.secretKey}")private String SECRET_KEY;@Value("${ai.imageType}")private String IMAGE_TYPE;@Value("${ai.groupId}")private String groupId;private AipFace client;private HashMap<String, String> options = new HashMap<String, String>();public BaiduAiUtil() {options.put("quality_control", "NORMAL");options.put("liveness_control", "LOW");}@PostConstructpublic void init() {client = new AipFace(APP_ID, API_KEY, SECRET_KEY);}//判断用户是否已经注册了面部信息public Boolean faceExist(String userId) {//返回error_code:0 (存在),非0:不存在JSONObject res = client.getUser(userId, groupId, null);Integer errorCode = res.getInt("error_code");return errorCode == 0 ? true : false;}/***  人脸注册 :将用户照片存入人脸库中*/public Boolean faceRegister(String userId, String image) {// 人脸注册JSONObject res = client.addUser(image, IMAGE_TYPE, groupId, userId, options);Integer errorCode = res.getInt("error_code");return errorCode == 0 ? true : false;}/***  人脸更新 :更新人脸库中的用户照片*/public Boolean faceUpdate(String userId, String image) {// 人脸更新JSONObject res = client.updateUser(image, IMAGE_TYPE, groupId, userId, options);Integer errorCode = res.getInt("error_code");return errorCode == 0 ? true : false;}/*** 人脸检测:判断上传图片中是否具有面部头像*/public Boolean faceCheck(String image) {JSONObject res = client.detect(image, IMAGE_TYPE, options);if (res.has("error_code") && res.getInt("error_code") == 0) {JSONObject resultObject = res.getJSONObject("result");Integer faceNum = resultObject.getInt("face_num");//检测的人脸数量return faceNum == 1?true:false;}else{return false;}}/***  人脸查找:查找人脸库中最相似的人脸并返回数据*          处理:用户的匹配得分(score)大于80分,即可认为是同一个用户*/public String faceSearch(String image) {JSONObject res = client.search(image, IMAGE_TYPE, groupId, options);if (res.has("error_code") && res.getInt("error_code") == 0) {JSONObject result = res.getJSONObject("result");JSONArray userList = result.getJSONArray("user_list");if (userList.length() > 0) {JSONObject user = userList.getJSONObject(0);double score = user.getDouble("score");if(score > 80) {return user.getString("user_id");}}}return null;}
}

骚戴理解:注意百度云API工具类里的image是Base64加密后的,所以在使用工具类之前要对图片进行Base64处理,也就是调用 String encode = Base64.encode(file.getBytes());来处理

  • 在构造方法中,实例化client。通过client,可以调用SDK中包含的各种API。
  • APP_ID, API_KEY, SECRET_KEY在文中第一段中所述位置获取,如没有正确配置,会直接导致API调用失败。
  • 据官方文档所示,我们大致创建了faceRegister()、faceUpdate()、faceCheck()、faceSearch()四个方法。
    • 人脸注册 faceRegister(groupId, userId, image)
    • groupId:用于人脸库区分人群标识,自定义即可,人脸库会根据提交的groupId,将用户分组
    • userId:人脸库中的用户标识,同组不可重复,自定义即可(通常为系统中用户的唯一标识)
    • image:Base64 用户图片
    • 人脸更新 faceUpdate(groupId, userId, image)
    • 参数解释同人脸注册
    • 该方法用于发生变化时,更新人脸信息人 脸 检 测 faceCheck(image)
    • image:Base64 用户图片
    • 该方法用于人脸注册、人脸更新和人脸登录前使用
    • 目前采用的方案是检测出人脸数大于0即可,如需深化需求,可按需扩展人脸登录faceSearch(image)
    • image:Base64 用户图片
    • 该方法使用的是百度云AI 人脸搜索方法,目前采用的方式是匹配度最高的结果,即要登录的用户

同样的,在BaiduAiUtil类头添加 @Component 注解,使用时可通过 @Autowired 来自动装配。在API调用后返回值处理上,进行了简单的解析,如需深化解析,可按需扩展。

功能实现

完成刷脸登录一共需要我们解决如下5个问题:

  • 人脸注册/人脸更新

在刷脸登录之前,我们首先需要对系统中的用户进行人脸注册,将相关信息提交至人脸库,才可通过人脸识 别的相关接口进行刷脸登录操作。当用户相貌变更较大时,可通过人脸更新进行人脸信息更换。

  • 二维码生成

获取验证码。通过工具生成相关信息后,如特殊标志,将特殊标志写入Redis缓存,并将标记值设为”-1“,我 们认定值为”-1“,即为当前标记尚未使用。调用QRCodeUtil.crateQRCode()生成二维码。

  • 二维码检测

前端获取二维码后,对二维码进行展现,并且前台启动定时器,定时检测特殊标记状态值。当状态值 为“1”时,表明登录成功。

  • 图像检查

检查图像里面有没有人脸信息

  • 人脸检测

当用户扫码进入落地页,通过落地页打开摄像头,并且定时成像。将成像图片,通过接口提交给后端进行人 脸检测。

  • 人脸登录

检测成功后,即进行人脸登录,人脸登录后,改变特殊标记状态值,成功为“1”,失败为“0”。当登录成功时, 进行自动登录操作,将token和userId存入到redis中。

后端实现

人脸注册/人脸更新

在刷脸登录之前,我们首先需要对系统中的用户进行人脸注册,将相关信息提交至人脸库,才可通过人脸识别的相关接口进行刷脸登录操作。当用户相貌变更较大时,可通过人脸更新进行人脸信息更换。

  • 修改UserService的uploadImage方法
    /*** 上传用户头像---使用七牛云存储* @param id* @param file* @return*/public String uploadImage(String id, MultipartFile file) throws IOException {User user = userDao.findById(id).get();//上传图片到七牛云String image = new QiniuUploadUtil().upload(user.getId(),file.getBytes());if (image!=null){user.setStaffPhoto(image);userDao.save(user);}//上传图片到百度云人脸库Boolean exist = baiduAiUtil.faceExist(id);String imgBase64 = Base64.encode(file.getBytes());if (exist){baiduAiUtil.faceUpdate(id,imgBase64);}else {baiduAiUtil.faceRegister(id,imgBase64);}return image;}

骚戴理解:人脸注册在本系统里是在员工详细信息里面上传员工照片的时候就把照片上传到七牛云的同时还上传到百度云的人脸库里面!还需要注意的一个地方就是调用baiduAiUtil的方法传入的是Base64的url,不用加前缀,所以先要执行String imgBase64 = Base64.encode(file.getBytes());这行代码,一开始我还以为要加一个前缀 "data:image/jpg;base64,",加前缀的话是一个完整的data url路径,可以直接在浏览器访问,但是实际上百度云API只需要Base64后面加密的那部分即可

二维码生成

获取验证码。通过工具生成相关信息后,如特殊标志,将特殊标志写入Redis缓存,并将标记值设为”-1“,我们认定值为”-1“,即为当前标记尚未使用。调用QRCodeUtil.crateQRCode()生成二维码。

FaceLoginController:

@Autowiredprivate FaceLoginService faceLoginService;/*** 获取刷脸登录二维码*/@RequestMapping(value = "/qrcode", method = RequestMethod.GET)public Result qrcode() throws Exception {QRCode qrCode = faceLoginService.getQRCode();return new Result(ResultCode.SUCCESS,qrCode);}

FaceLoginService

    @Value("${qr.url}")private String url;@Autowiredprivate IdWorker idWorker;@Autowiredprivate QRCodeUtil qrCodeUtil;@Autowiredprivate BaiduAiUtil baiduAiUtil;@Autowiredprivate RedisTemplate redisTemplate;//创建二维码public QRCode getQRCode() throws Exception {//1.创建唯一标识String code = idWorker.nextId() + "";//2.生成二维码(url地址)String content = url+"?code="+code;System.out.println(content);String file = qrCodeUtil.crateQRCode(content);System.out.println(file);//3.存入当前二维码状态(存入redis)FaceLoginResult result = new FaceLoginResult("-1");redisTemplate.boundValueOps(getCacheKey(code)).set(result,10, TimeUnit.MINUTES);//状态对象,失效时间,单位return new QRCode(code,file);}//构造缓存keyprivate String getCacheKey(String code) {return "qrcode_" + code;}

骚戴理解:这里注意放到Redis里面的Key是"qrcode_" + code,code是唯一标识,value值是FaceLoginResult对象,表示二维码的状态,需要导入Redis的依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置Redis的配置文件

spring:redis:host: 127.0.0.1port: 6379

测试一下二维码生成,直接访问生成的这个路径

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADIAQAAAACFI5MzAAABYUlEQVR42u2XwYrDMAxEDfotg35doN8SeGfklE1b9ubZU9yUxn4XeTSW1bH+GuMhD3mIisQYtjKm15zeEw1JPJk1PK+JioRhEo5QLPdERvhwXU3K14Kg05WEcFqBI4ZPrc8RmiJf48s75wgH/BGGVf86C+cIUhYDfm9/UFURKR4sZGwVzpatMBHJtnpBSi7GLYLThB+uwIvtERXpEPZ3XskTEGjq1w/FLRMRJA3GMJa+LDjFVQRTlIpwbrbsruhZAh9ys136yspVpGg+On5OhDBNRGhD65spacib48+SruJGx8emIsJdQlUIipc3Rc8Sbq6nWH1X9CxB5kanDDx3GjWkS4WzdUACdwASgv1F3xhb1hSRVtXpeOQNbyrSN/qqq93KcBFhU0LHd+1jvVCRsKs36ar00ameJbEdz8Z4mpL0kfotsBJC08Mi6E+4V1MROuTleNxQ5SLy/Nd8yEP+jfwAQKalavReoooAAAAASUVORK5CYII=

二维码检测

前端获取二维码后,对二维码进行展现,并且前台启动定时器,定时检测特殊标记状态值。当状态值为“1”时,表明登录成功。

FaceLoginController

    /*** 检查二维码:登录页面轮询调用此方法,根据唯一标识code判断用户登录情况*      查询二维码扫描状态*          返回值:FaceLoginResult*              state :-1,0,1 (userId和token)*/@RequestMapping(value = "/qrcode/{code}", method = RequestMethod.GET)public Result qrcodeCeck(@PathVariable(name = "code") String code) throws Exception {FaceLoginResult checkQRCode = faceLoginService.checkQRCode(code);return new Result(ResultCode.SUCCESS,checkQRCode);}

FaceLoginService

人脸检测/人脸登录:当用户扫码进入落地页,通过落地页打开摄像头,并且定时成像。将成像图片,通过接 口提交给后端进行人脸检测。

//根据唯一标识,查询用户是否登录成功public FaceLoginResult checkQRCode(String code) {String key = getCacheKey(code);return (FaceLoginResult) redisTemplate.opsForValue().get(key);}//构造缓存keyprivate String getCacheKey(String code) {return "qrcode_" + code;}

骚戴理解:二维码检测其实就是前端不断的发这个检测的请求,所谓的检测其实就是从Redis里面去获取唯一的code这个key的值,看这个值的状态是多少,如果是-1就是还未扫二维码,1就是登录成功,0就是登录失败!

图像检测

检测上传的图片有没有人脸信息

FaceLoginController:

    /*** 图像检测,判断图片中是否存在面部头像*/@RequestMapping(value = "/checkFace", method = RequestMethod.POST)public Result checkFace(@RequestParam(name = "file") MultipartFile attachment) throws Exception {String image = Base64Util.encode(attachment.getBytes());Boolean aBoolean = baiduAiUtil.faceCheck(image);if (aBoolean) {return new Result(ResultCode.SUCCESS);}else{return new Result(ResultCode.FAIL);}}

人脸检测

检测成功后,即进行人脸登录,人脸登录后,改变特殊标记状态值,成功为“1”,失败为“0”。当登录成功时,进行自动登录操作,将token和userId存入到redis中。

FaceLoginController

    /*** 人脸登录:根据落地页随机拍摄的面部头像进行登录*          根据拍摄的图片调用百度云AI进行检索查找*/@RequestMapping(value = "/{code}", method = RequestMethod.POST)public Result loginByFace(@PathVariable(name = "code") String code, @RequestParam(name = "file") MultipartFile attachment) throws Exception {//人脸登录获取用户id(不为null登录成功)String userId = faceLoginService.loginByFace(code, attachment);if(userId != null) {return new Result(ResultCode.SUCCESS);}else{return new Result(ResultCode.FAIL);}}

FaceLoginService

    @Autowiredprivate UserDao userDao;//扫描二维码之后,使用拍摄照片进行登录//返回值:登录成功之后返回的用户id//登录失败:nullpublic String loginByFace(String code, MultipartFile attachment) throws Exception {//1.调用百度云AI查询当前的用户String userId = baiduAiUtil.faceSearch(Base64Util.encode(attachment.getBytes()));//2.自动登录(token)FaceLoginResult result = new FaceLoginResult("0");if(userId != null) {//自己模拟登录//查询用户对象User user = userDao.findById(userId).get();if(user != null) {//获取subjectSubject subject = SecurityUtils.getSubject();//调用login方法登录subject.login(new UsernamePasswordToken(user.getMobile(),user.getPassword()));//获取token(sessionId)String token = subject.getSession().getId()+"";result = new FaceLoginResult("1",token,userId);}}//3.修改二维码的状态redisTemplate.boundValueOps(getCacheKey(code)).set(result,10,TimeUnit.MINUTES);return userId;}

骚戴理解:理清楚整个刷脸登录的逻辑,首先,生成二维码,把二维码的唯一标识code返回给前端,再把这个code存到Redis里,状态设置为-1,然后前端会把在发其他请求的时候每次带上整个code,前端设置定时任务,不断的发请求轮询查看二维码的状态,每次发这个请求的时候会去Redis里根据code去取对应的值,查看对应的状态,如果是-1就继续轮询,如果是1就表示登录成功,0就表示登录失败,在用户刷脸登录的时候,会调用摄像头刷脸登录,刷脸登录后就调用接口去判断有没有人脸信息,然后去百度云的人脸库里看看有没有这个人脸信息,如果匹配上了就模拟登录,修改Redis里的状态为1,然后就自动登录了,需要注意的是扫描二维码无法登录,只能靠生成的链接,然后访问链接,这个链接要是http请求才能访问,如果是https就无法访问这个链接

前端实现

前端主要实现的功能是,获取二维码并展示,然后后台轮询检测刷脸登录状态,并且实现落地页相关功能(摄像头 调用、定时成像、发送人脸检测和发送人脸登录请求)

  • 二维码展现
// 二维码handlecode() {qrcode().then(res => {this.param.qrcode = res.data.filethis.centerDialogVisible = truethis.codeCheckInfo = res.data.codesetInterval(() => {if (this.states === '-1') {codeCheck({ code: res.data.code }).then(res => {this.states = res.data.statethis.token = res.data.tokenif (this.states === '0') {// 登录this.$store.dispatch('LoginByCode', res.data.token).then(() => {this.$router.push({ path: '/' })}).catch(() => {})} if (this.states === '1') {// 关闭this.centerDialogVisible = false}})}}, 1000 * 10)})}
  • 落地页调用摄像头
handleClick() {let _this = thisif (!this.vdstate) {return false}if (!_this.states) {// 注册拍照按钮的单击事件let video = this.$refs['vd']let canvas = this.$refs['cav']// let form = this.$refs["myForm"];let context = canvas.getContext('2d')// 绘制画面context.drawImage(video, 0, 0, 200, 200)let base64Data = canvas.toDataURL('image/jpg')// 封装blob对象let blob = this.dataURItoBlob(base64Data, 'camera.jpg') // base64 转图片filelet formData = new FormData()formData.append('file', blob)this.imgUrl = base64DatacheckFace(formData).then(res => {if (res.data.isSuc) {axios({method: 'post',url: '/api/frame/facelogin/' + this.$route.query.code,data: formData}).then(function(response) {console.log(response)_this.states = true_this.canvasShow = false_this.tipShow = true// _this.$message.success('验证通过' + '!')}).catch(function(error) {console.log(error)})} else {return false}})}},dataURItoBlob(base64Data) {var byteStringif (base64Data.split(',')[0].indexOf('base64') >= 0)byteString = atob(base64Data.split(',')[1])else byteString = unescape(base64Data.split(',')[1])var mimeString = base64Data.split(',')[0].split(':')[1].split(';')[0]var ia = new Uint8Array(byteString.length)for (var i = 0; i < byteString.length; i++) {ia[i] = byteString.charCodeAt(i)}return new Blob([ia], { type: mimeString })}}

总结

通过上述的步骤,可以实现一个刷脸登录的功能,其核心在于百度云AI的使用。通过合理的使用百度云AI SDK提供的相关API,我们可以很轻松的实现刷脸登录功能。刷脸登录的业务流程有很多种,我们只是实现了一种借助二维码的方式,作为抛砖引玉。更多的流程和实现方式,在此不进行赘述。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/77407.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

渗透-01:DNS原理和HTML字符编码-HTML实体编码

一、DNS概念 DNS (Domain Name System 的缩写)就是根据域名查出IP地址(常用) DNS分类&#xff1a; 正向解析&#xff1a;已知域名解析IP反向解析&#xff1a;已知IP解析对应的域名 二、查询过程 工具软件dig可以显示整个查询过程 [rootnode01 ~]# dig baidu.com; <<>&…

【项目 计网3】Socket介绍 4.9字节序 4.10字节序转换函数

文章目录 4.8 Socket介绍4.9字节序简介字节序举例 4.10字节序转换函数 4.8 Socket介绍 所谓 socket&#xff08;套接字&#xff09;&#xff0c;就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端&#xff0c;提供了应用层进…

windows开机运行jar

windows开机自启动jar包&#xff1a; 一、保存bat批处理文件 echo off %1 mshta vbscript:CreateObject("WScript.Shell").Run("%~s0 ::",0,FALSE)(window.close)&&exit java -jar E:\projects\ruoyi-admin.jar > E:\server.log 2>&1 &…

测试平台——项目模块模型类设计

这里写目录标题 一、项目应用1、项目包含接口&#xff1a;2、创建子应用3、项目模块设计a、模型类设计b、序列化器类设计c、视图类设计 4、接口模块设计a、模型类设计b、序列化器类设计c、视图类设计 5、环境模块设计6、DRF中的通用过滤6.1、设置过滤器后端 一、项目应用 1、项…

GEE:谐波模型在遥感影像中的应用(季节性变化的拟合与可视化)

作者:CSDN @ _养乐多_ 谐波模型是一种常用的工具,用于拟合和分析影像数据中的周期性和季节性变化。本文将介绍如何使用Google Earth Engine平台实现谐波模型,通过对Landsat影像进行处理和拟合,展示季节性变化的拟合结果,并通过图表和地图可视化展示数据。 谐波模型是一种…

LabVIEW开发多材料摩擦电测量控制系统

LabVIEW开发多材料摩擦电测量控制系统 摩擦电效应是两个物体摩擦在一起&#xff0c;电荷从一个物体转移到另一个物体的现象&#xff0c;从而导致两个物体携带相等和相反的电荷。接触和充电是主导该过程的两个关键因素。当静电荷累积到一定水平时&#xff0c;可能会出现放电现象…

一起学算法(选择排序篇)

距离上次更新已经很久了&#xff0c;以前都是非常认真的写笔记进行知识分享&#xff0c;但是带来的情况并不是很好&#xff0c;一度认为发博客是没有意义的&#xff0c;但是这几天想了很多&#xff0c;已经失去了当时写博客的初心了&#xff0c;但是我觉得应该做点有意义的事&a…

sql 参数自动替换

需求&#xff1a;看日志时&#xff0c;有的sql 非常的长&#xff0c;参数比较多&#xff0c;无法直接在sql 客户端工具执行&#xff0c;如果一个一个的把问号占位符替换为参数太麻烦&#xff0c;因此写个html 小工具&#xff0c;批量替换&#xff1a; 代码&#xff1a; <!…

【项目 线程4】3.12生产者消费者模型 3.13条件变量 3.14信号量 C++实现生产者消费者模型

3.12生产者消费者模型 生产者消费者模型中的对象&#xff1a; 1、生产者 2、消费者 3、容器 若容器已满&#xff0c;生产者阻塞在这&#xff0c;通知消费者去消费&#xff1b;若容器已空&#xff0c;则消费者阻塞&#xff0c;通知生产者去生产。生产者可以有多个&#xff0c;消…

HTML5中的data-*属性

介绍&#xff1a; data-*全局属性是一类被称为自定义数据属性的属性&#xff0c;它赋予我们在所有 HTML 元素上嵌入自定义数据属性的能力。 data-*的使用 <div class"child" data-name"小红" data-age"18"></div> 在js里有两种获…

JavaEE——网络初识 (简单介绍两种协议以及网络通信的基础概念)

文章目录 一、简单了解网络发展二、网络通信基础认识三、利用UDP举例解释网络信息传输 一、简单了解网络发展 总的来讲&#xff0c;网络的发展史就是&#xff0c;先是一小部分的计算机之间连接通信&#xff0c;随着技术发展&#xff0c;逐渐扩大范围&#xff0c;形成了我们当前…

【unity】Pico VR 开发笔记(视角移动)

【unity】Pico VR 开发笔记&#xff08;视角移动&#xff09; 视角移动是简单的基础功能&#xff0c;这里区别于头显定位获得的小范围位移&#xff0c;是长距离不影响安全边界的位移方式。的常见的位移方式有两种&#xff0c;其一是触发后瞬间传送到指定位置&#xff0c;其次是…

TBB库中实现协程(coroutine)的源码说明

源码请见: https://github.com/oneapi-src/oneTBB/blob/master/src/tbb/co_context.h 在windows系统&#xff0c;TBB(也就是intel 的 oneTBB库)&#xff0c;通过windwos fiber(纤程)来实现协程(coroutine)。 创建一个协程,代码很简洁: inline void create_coroutine(corouti…

图论-简明导读

计算机图论是计算机科学中的一个重要分支&#xff0c;它主要研究图的性质和结构&#xff0c;以及如何在计算机上有效地存储、处理和操作这些图。本文将总结计算机图论的核心知识点。 一、基本概念 计算机图论中的基本概念包括图、节点、边等。图是由节点和边构成的数据结构&am…

前端技术搭建五子棋游戏(内含源码)

The sand accumulates to form a pagoda ✨ 写在前面✨ 功能介绍✨ 页面搭建✨ 样式设置✨ 逻辑部分 ✨ 写在前面 上周我们实通过前端基础实现了拼图游戏&#xff0c;今天还是继续按照我们原定的节奏来带领大家完成一个五子棋游戏&#xff0c;功能也比较简单简单&#xff0c;也…

MCUXpresso for VS Code -- 基于VSCode开发RT1176

MCUXpresso for VS Code 是nxp推出插件&#xff0c;旗下MCX LPC, Kinetis和i.MX rt等MCU&#xff0c;都能在VS Code平台进行嵌入式开发。功能框图如下&#xff1a; 前期准备&#xff1a; 软件环境: windows(实际可以跨系统&#xff0c;linux和mac没有测试) VS Code ninja CMa…

elementui Cascader 级联选择使用心得

相信大家对于elementui并不陌生&#xff0c;作为适配Vue的优秀UI框架之一&#xff0c;一直被所有的开发者痛并快乐着。今天要记录的就是里边的主角之一Cascader。 首先先介绍一下Cascader ---> 当一个数据集合有清晰的层级结构时&#xff0c;可通过级联选择器逐级查看并选择…

树莓派安装ubuntu

ubuntu包下载 从ubuntu 官网下载镜像&#xff1a;https://cn.ubuntu.com/blog/build-raspberry-pi-desktop-ubuntu 按个人需求下载&#xff0c;可以首先使用 桌面版22.04 LTS版本&#xff1b; 烧录 从树莓派管官网下载image烧录工具&#xff1a;https://www.raspberrypi.c…

任务15、MidJourney视频(Video)参数动态上线,制作惊艳动画短片

15.1 任务概述 本次任务将帮助你掌握Midjourney中的Video参数,并利用这些参数创作出令人惊艳的绘画作品。通过学习Video参数的基本概念和功能,以及案例的实际应用,你将学会如何正确设置和调整这些参数,从而达到你所期望的绘画效果。最终,你将运用所学知识,生成香奈儿模特…

ensp-单臂路由

ensp-单臂路由 日期&#xff1a;6-26 &#x1f4ce;单臂路由.zip&#x1f4ce;单臂路由.docx