完成刷脸登录一共需要我们解决如下5个问题:
●人脸注册/人脸更新
在刷脸登录之前,我们首先需要对系统中的用户进行人脸注册,将相关信息提交至人脸库,才可通过人脸识 别的相关接口进行刷脸登录操作。当用户相貌变更较大时,可通过人脸更新进行人脸信息更换。
●二维码生成
获取验证码。通过工具生成相关信息后,如特殊标志,将特殊标志写入Redis缓存,并将标记值设为”-1“,我们认定值为”-1“,即为当前标记尚未使用。调用QRCodeUtil.crateQRCode()生成二维码。
●二维码检测
前端获取二维码后,对二维码进行展现,并且前台启动定时器,定时检测特殊标记状态值。当状态值 为“1”时,表明登录成功。
●人脸检测
当用户扫码进入落地页,通过落地页打开摄像头,并且定时成像。将成像图片,通过接口提交给后端进行人 脸检测。
●人脸登录
检测成功后,即进行人脸登录,人脸登录后,改变特殊标记状态值,成功为“1”,失败为“0”。当登录成功时,进行自动登录操作,将token和userId存入到redis中。
1.环境搭建
<!-- 百度云AI API-->
<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>
2.添加配置
ai:
appId: 15191935
apiKey: cyWSHgas93Vtdmt42OwbW8pu
secretKey: yf1GusMvvLBdOnyubfLubNyod9iEDEZW
imageType: BASE64
groupId: itcast
qr:
url: https://localhost:8080/#/facelogin
3.创建二维码工具类
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;
}
}
4.创建百度云AI工具类
对于百度云AI SDK我们进行一些简单的封装,便于使用时,减少代码冗余。
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");
}
@PostConstruct
public void init() {
client = new AipFace(APP_ID, API_KEY, SECRET_KEY);
}
/**
* 人脸注册 :将用户照片存入人脸库中
*/
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;
}
}
5.在微服务创建基本的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 {
/**
* 获取刷脸登录二维码
*/
@RequestMapping(value = "/qrcode", method = RequestMethod.GET)
public Result qrcode() throws Exception {
return null;
}
/**
* 检查二维码:登录页面轮询调用此方法,根据唯一标识code判断用户登录情况
*/
@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;
}
}
6.在微服务构建基本的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;
}
//构造缓存key
private String getCacheKey(String code) {
return "qrcode_" + code;
}
}
●在构造方法中,实例化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调用后返回值处理上,进行了简单的解析,如需深化解析,可按需扩展
7.后端实现
(1)人脸注册/人脸更新:在刷脸登录之前,我们首先需要对系统中的用户进行人脸注册,将相关信息提交至人脸库,才可通过人脸识别的相关接口进行刷脸登录操作。当用户相貌变更较大时,可通过人脸更新进行人脸信息更换。
//人脸注册
@RequestMapping(value = "/register/face", method = RequestMethod.POST)
public Boolean registerFace(@RequestParam(name = "fid") String fid) throws
Exception {
SysFile sysFile = fileService.findById(fid);
String path = uploadPath + "/" + sysFile.getPath() + "/" +
sysFile.getUuidName();
byte[] bytes = Files.readAllBytes(Paths.get(path));
Boolean isSuc;
String image = Base64Utils.encodeToString(bytes);
isSuc = userService.checkFace(image);
if (isSuc) {
isSuc = baiduAiUtil.faceRegister("1", userId, image);
}
return isSuc;
}
//人脸更新
@RequestMapping(value = "/update/face", method = RequestMethod.POST)
public boolean updateFace(@RequestParam(name = "fid") String fid) throws Exception
{
SysFile sysFile = fileService.findById(fid);
String path = uploadPath + "/" + sysFile.getPath() + "/" +
sysFile.getUuidName();
byte[] bytes = Files.readAllBytes(Paths.get(path));
Boolean isSuc;
String image = Base64Utils.encodeToString(bytes);
isSuc = userService.checkFace(image);
if (isSuc) {
isSuc = baiduAiUtil.faceUpdate("1", userId, image);
}
return isSuc;
}
(2)二维码生成:获取验证码。通过工具生成相关信息后,如特殊标志,将特殊标志写入Redis缓存,并将标记值设为”-1“,我们认定值为”-1“,即为当前标记尚未使用。调用QRCodeUtil.crateQRCode()生成二维码。
controller层
/**
* 获取刷脸登录二维码
* 返回值:QRCode对象(code,image)
*
*/
@RequestMapping(value = "/qrcode", method = RequestMethod.GET)
public Result qrcode() throws Exception {
QRCode qrCode = faceLoginService.getQRCode();
return new Result(ResultCode.SUCCESS,qrCode);
}
service层
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);
}
(3)二维码检测:前端获取二维码后,对二维码进行展现,并且前台启动定时器,定时检测特殊标记状态值。当状态值为“1”时,表明登录成功。
Controller层
/**
* 检查二维码:登录页面轮询调用此方法,根据唯一标识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);
}
Service层
//根据唯一标识,查询用户是否登录成功
public FaceLoginResult checkQRCode(String code) {
String key = getCacheKey(code);
return (FaceLoginResult) redisTemplate.opsForValue().get(key);
}
(4)人脸检测/人脸登录:当用户扫码进入落地页,通过落地页打开摄像头,并且定时成像。将成像图片,通过接口提交给后端进行人脸检测。
/**
* 图像检测,判断图片中是否存在面部头像
*/
@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);
}
}
(5)检测成功后,即进行人脸登录,人脸登录后,改变特殊标记状态值,成功为“1”,失败为“0”。当登录成功时,进行自动登录操作,将token和userId存入到redis中。
Controller层
/**
* 人脸登录:根据落地页随机拍摄的面部头像进行登录
* 根据拍摄的图片调用百度云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);
}
}
Service层
//扫描二维码之后,使用拍摄照片进行登录
//返回值:登录成功之后返回的用户id
//登录失败:null
public 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) {
//获取subject
Subject 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;
}
前端实现
前端主要实现的功能是,获取二维码并展示,然后后台轮询检测刷脸登录状态,并且实现落地页相关功能(摄像头调用、定时成像、发送人脸检测和发送人脸登录请求)
(1)二维码展现
// 二维码
handlecode() {
qrcode().then(res => {
this.param.qrcode = res.data.file
this.centerDialogVisible = true
this.codeCheckInfo = res.data.code
setInterval(() => {
if (this.states === '-1') {
codeCheck({ code: res.data.code }).then(res => {
this.states = res.data.state
this.token = res.data.token
if (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)
})
}
(2)落地页调用摄像头
handleClick() {
let _this = this
if (!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 转图片file
let formData = new FormData()
formData.append('file', blob)
this.imgUrl = base64Data
checkFace(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 byteString
if (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,我们可以很轻松的实现刷脸登录功能。刷脸登录的业务流程有很多种,我们只是实现了一种借助二维码的方式,作为抛砖引玉。更多的流程和实现方式,在此不进行赘述。
附赠:
FaceLoginController
import com.baidu.aip.util.Base64Util;
import com.ihrm.common.entity.Result;
import com.ihrm.common.entity.ResultCode;
import com.ihrm.domain.system.response.FaceLoginResult;
import com.ihrm.domain.system.response.QRCode;
import com.ihrm.system.service.FaceLoginService;
import com.ihrm.system.utils.BaiduAiUtil;
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 {
@Autowired
private FaceLoginService faceLoginService;
@Autowired
private BaiduAiUtil baiduAiUtil;
/**
* 获取刷脸登录二维码
* 返回值:QRCode对象(code,image)
*
*/
@RequestMapping(value = "/qrcode", method = RequestMethod.GET)
public Result qrcode() throws Exception {
QRCode qrCode = faceLoginService.getQRCode();
return new Result(ResultCode.SUCCESS,qrCode);
}
/**
* 检查二维码:登录页面轮询调用此方法,根据唯一标识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);
}
/**
* 人脸登录:根据落地页随机拍摄的面部头像进行登录
* 根据拍摄的图片调用百度云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);
}
}
/**
* 图像检测,判断图片中是否存在面部头像
*/
@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);
}
}
}
FaceLoginService
import com.baidu.aip.util.Base64Util;
import com.ihrm.common.utils.IdWorker;
import com.ihrm.domain.system.User;
import com.ihrm.domain.system.response.FaceLoginResult;
import com.ihrm.domain.system.response.QRCode;
import com.ihrm.system.dao.UserDao;
import com.ihrm.system.utils.BaiduAiUtil;
import com.ihrm.system.utils.QRCodeUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.util.concurrent.TimeUnit;
@Service
public class FaceLoginService {
@Value("${qr.url}")
private String url;
@Autowired
private IdWorker idWorker;
@Autowired
private QRCodeUtil qrCodeUtil;
@Autowired
private BaiduAiUtil baiduAiUtil;
@Autowired
private 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);
}
//根据唯一标识,查询用户是否登录成功
public FaceLoginResult checkQRCode(String code) {
String key = getCacheKey(code);
return (FaceLoginResult) redisTemplate.opsForValue().get(key);
}
@Autowired
private UserDao userDao;
//扫描二维码之后,使用拍摄照片进行登录
//返回值:登录成功之后返回的用户id
//登录失败:null
public 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) {
//获取subject
Subject 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;
}
//构造缓存key
private String getCacheKey(String code) {
return "qrcode_" + code;
}
}
QRCode (封装二维码实体对象)
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;
}
FaceLoginResult(封装人脸登录实体对象)
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;
}
}