fix: [登录]人脸识别

This commit is contained in:
ovo 2025-05-11 00:18:37 +08:00
parent 2ce9a4c2cd
commit 58c762dcbc
14 changed files with 184 additions and 293 deletions

View File

@ -12,14 +12,21 @@ public class SecurityConstants {
* 这些路径可以直接访问不需要认证
*/
public static final List<String> WHITE_LIST = List.of(
"/bs/user/login",
"/bs/user/register",
"/bs/user/getEmailCode",
"/bs/user/getPhoneCode",
"/bs/user/verifyFace",
"/challenge",
"/ws/**",
"/faceTest", "/compareFaces",
"/minio/**",
"/bs/user/getEmailCode",
"/exam/api/paper/**",
"/bs/user/login",
"/bs/user/register",
"/bs/courses/**",
"/api/common/**", //公共接口
"/demo/**", // 测试接口

View File

@ -1,18 +1,27 @@
package com.guwan.backend.controller;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.RandomUtil;
import com.arcsoft.face.FaceInfo;
import com.arcsoft.face.enums.ExtractType;
import com.arcsoft.face.toolkit.ImageFactory;
import com.arcsoft.face.toolkit.ImageInfo;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.guwan.backend.annotation.OperationLog;
import com.guwan.backend.common.Result;
import com.guwan.backend.face.dto.FaceRecognitionResDTO;
import com.guwan.backend.face.entity.UserCompareInfo;
import com.guwan.backend.face.service.FaceEngineService;
import com.guwan.backend.face.util.Base64Util;
import com.guwan.backend.face.util.UserInfo;
import com.guwan.backend.face.util.UserRamGroup;
import com.guwan.backend.mapper.UserMapper;
import com.guwan.backend.pojo.dto.user.*;
import com.guwan.backend.pojo.entity.User;
import com.guwan.backend.service.EmailService;
import com.guwan.backend.service.UserService;
import com.guwan.backend.util.MinioUtil;
import com.guwan.backend.util.RedisUtils;
import com.guwan.backend.util.SecurityUtil;
import com.guwan.backend.util.SmsUtils;
import com.guwan.backend.util.*;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -22,6 +31,10 @@ import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.thymeleaf.context.Context;
import java.io.InputStream;
import java.util.Base64;
import java.util.List;
@Slf4j
@RestController
@RequestMapping("/bs/user")
@ -41,6 +54,10 @@ public class BSUserController {
private final UserMapper userMapper;
private final FaceEngineService faceEngineService;
private final JwtUtil jwtUtil;
@PostMapping("/register")
public Result<UserDTO> register(@RequestBody @Valid BSRegisterDTO request) {
try {
@ -98,10 +115,8 @@ public class BSUserController {
return Result.error(e.getMessage());
}
}
@OperationLog(description = "用户注册时获取验证码")
@PostMapping("/getEmailCode")
public Result getEmailCode(@RequestBody @Valid EmailDto emailDto) {
@ -156,9 +171,93 @@ public class BSUserController {
return Result.success(url);
}
@PostMapping("/updateFace")
public Result updateFace(@RequestParam("file") MultipartFile file) throws Exception {
Long currentUserId = securityUtil.getCurrentUserId();
User user = userMapper.selectById(currentUserId);
String fileName = minioUtil.uploadFile("photo", file, "face");
String url = minioUtil.getUrl(minioUtil.getFileUrl
("photo", fileName));
InputStream fileInputStream = minioUtil.getFileInputStream("photo", fileName);
//java 9+
byte[] bytes = fileInputStream.readAllBytes();
//先检测人脸 再提取特征
ImageInfo rgbData = ImageFactory.getRGBData(bytes);
//先检测人脸
List<FaceInfo> faceInfoList = faceEngineService.detectFaces(rgbData);
if (faceInfoList != null && !faceInfoList.isEmpty()) {
FaceInfo faceInfo = faceInfoList.getFirst();
byte[] feature = faceEngineService.extractFaceFeature(rgbData, faceInfo, ExtractType.RECOGNIZE);
if (feature != null) {
String faceFeature = Base64.getEncoder().encodeToString(feature);
UserInfo userInfo = new UserCompareInfo();
userInfo.setFaceId(String.valueOf(currentUserId));
userInfo.setName(user.getUsername());
userInfo.setFaceFeature(Base64Util.base64ToBytes(faceFeature));
//注册到内存缓存中
UserRamGroup.addUser(userInfo, "Guwan");
userMapper.update(new LambdaUpdateWrapper<User>()
.eq(User::getId, currentUserId).set(User::getFaceUrl, url)
.set(User::getFeature, faceFeature));
}
}
return Result.success(url);
}
@PostMapping("/verifyFace")
public Result verifyFace(@RequestParam("imageData") String imageData) throws Exception {
UserCompareInfo userCompareInfo = new UserCompareInfo();
List<UserInfo> userInfoList = UserRamGroup.getUserList("Guwan");
String name = minioUtil.uploadBase64Image("photo", imageData, "facetemp");
InputStream fileInputStream = minioUtil.getFileInputStream("photo", name);
byte[] bytes = fileInputStream.readAllBytes();
ImageInfo rgbData = ImageFactory.getRGBData(bytes);
List<FaceInfo> faceInfoList = faceEngineService.detectFaces(rgbData);
if (faceInfoList != null && !faceInfoList.isEmpty()) {
FaceInfo faceInfo = faceInfoList.getFirst();
byte[] featureBytes = faceEngineService.extractFaceFeature(rgbData,
faceInfo, ExtractType.REGISTER);
List<UserCompareInfo> userCompareInfoList = faceEngineService
.faceRecognition(featureBytes, userInfoList, 0.8f);
if (userCompareInfoList != null && !userCompareInfoList.isEmpty()) {
userCompareInfo = userCompareInfoList.getFirst();
}
}
String faceId = userCompareInfo.getFaceId(); //也就是人员ID
String userId = faceId;
if (userId != null) {
String token = jwtUtil.generateToken(Long.valueOf(userId));
return Result.success(token);
} else {
return Result.success();
}
}
@PostMapping("/password/reset")

View File

@ -1,81 +0,0 @@
package com.guwan.backend.controller;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.api.command.ExecCreateCmdResponse;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.core.command.ExecStartResultCallback;
import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
import lombok.RequiredArgsConstructor;
import java.io.ByteArrayOutputStream;
import java.net.URI;
import java.util.concurrent.TimeUnit;
@RequiredArgsConstructor
public class DockerCppRunner {
private static final String IMAGE_NAME = "cpp-runner";
private static final int TIMEOUT_SECONDS = 10;
private final DockerClient dockerClient;
public static void main(String[] args) throws Exception {
DockerClient dockerClient = DockerClientBuilder.getInstance()
.withDockerHttpClient(
new ApacheDockerHttpClient.Builder()
.dockerHost(URI.create("tcp://localhost:2375"))
.build()
).build();
try {
// 创建容器
CreateContainerResponse container = dockerClient.createContainerCmd(IMAGE_NAME)
.withTty(true)
.exec();
String containerId = container.getId();
// 启动容器
dockerClient.startContainerCmd(containerId).exec();
// 写入 C++ 代码到容器内
String cppCode = "#include <iostream>\n"
+ "int main() { std::cout << \"Hello Docker!\\n\"; return 0; }";
// 将代码写入容器中的文件
dockerClient.execCreateCmd(containerId)
.withCmd("sh", "-c", "echo '" + cppCode + "' > /app/main.cpp")
.exec();
// 编译 C++ 代码
execCommand(dockerClient, containerId, "g++ /app/main.cpp -o /app/main");
// 运行编译后的程序
String output = execCommand(dockerClient, containerId, "/app/main");
System.out.println("程序输出: " + output);
} finally {
// 清理容器
dockerClient.listContainersCmd().withShowAll(true).exec().forEach(c ->
dockerClient.removeContainerCmd(c.getId()).withForce(true).exec());
dockerClient.close();
}
}
private static String execCommand(DockerClient dockerClient, String containerId, String command)
throws Exception {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ExecCreateCmdResponse exec = dockerClient.execCreateCmd(containerId)
.withCmd("sh", "-c", command)
.withAttachStdout(true)
.withAttachStderr(true)
.exec();
dockerClient.execStartCmd(exec.getId())
.exec(new ExecStartResultCallback(outputStream, System.err))
.awaitCompletion(TIMEOUT_SECONDS, TimeUnit.SECONDS);
return outputStream.toString().trim();
}
}

View File

@ -81,6 +81,10 @@ public class FaceController {
// //这边注册到内存缓存中也可以根据业务注册到数据库中
// UserRamCache.addUser(userInfo);
//entity.setFeature(Base64.getEncoder().encodeToString(featureBytes));
String s = Base64.getEncoder().encodeToString(featureBytes);
byte[] featureBytes1 = Base64.getDecoder().decode(s);
System.out.println(Base64.getEncoder().encodeToString(featureBytes));
}else{
log.error("图片不合格,未检测到人脸");

View File

@ -10,7 +10,5 @@ import org.springframework.web.bind.annotation.RestController;
@RequiredArgsConstructor
public class SetController {
@GetMapping
public void setAuthentication(){
}
public void setAuthentication(){}
}

View File

@ -1,19 +1,14 @@
package com.guwan.backend.face;/*
package com.guwan.face;
package com.guwan.backend.face;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.guwan.config.GlobalValue;
import com.guwan.face.entity.UserCompareInfo;
import com.guwan.face.service.FaceEngineService;
import com.guwan.face.util.Base64Util;
import com.guwan.face.util.UserInfo;
import com.guwan.face.util.UserRamGroup;
import com.guwan.backend.face.entity.UserCompareInfo;
import com.guwan.backend.face.service.FaceEngineService;
import com.guwan.backend.face.util.Base64Util;
import com.guwan.backend.face.util.UserInfo;
import com.guwan.backend.face.util.UserRamGroup;
import com.guwan.backend.pojo.entity.User;
import com.guwan.backend.service.UserService;
import lombok.extern.slf4j.Slf4j;
import net.shapelight.modules.ten.entity.TenCellEntity;
import net.shapelight.modules.ten.entity.TenPersonEntity;
import net.shapelight.modules.ten.service.TenCellService;
import net.shapelight.modules.ten.service.TenPersonService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
@ -29,92 +24,38 @@ public class FaceEngineAutoRun implements ApplicationRunner {
@Autowired
private FaceEngineService faceEngineService;
@Autowired
private TenPersonService tenPersonService;
@Autowired
private TenCellService tenCellService;
@Autowired
private GlobalValue globalValue;
private UserService userService;
@Override
public void run(ApplicationArguments args) throws Exception {
// 任务初始化
log.debug("服务启动。。。。。初始化人脸库");
// Map<String, String> fileMap = Maps.newHashMap();
// fileMap.put("zhao1", "赵丽颖");
// fileMap.put("yang1", "杨紫");
// fileMap.put("baixue", "白雪");
// fileMap.put("chenchuang", "陈创");
// for (String f : fileMap.keySet()) {
// ClassPathResource resource = new ClassPathResource("static/images/" + f + ".jpg");
// InputStream inputStream = resource.getInputStream();
// ImageInfo rgbData = ImageFactory.getRGBData(inputStream);
// List<FaceInfo> faceInfoList = faceEngineService.detectFaces(rgbData);
// if (CollectionUtil.isNotEmpty(faceInfoList)) {
// byte[] feature = faceEngineService.extractFaceFeature(rgbData, faceInfoList.get(0), ExtractType.REGISTER);
// UserRamCache.UserInfo userInfo = new UserCompareInfo();
// userInfo.setFaceId(f);
// userInfo.setName(fileMap.get(f));
// userInfo.setFaceFeature(feature);
// //这边注册到内存缓存中也可以根据业务注册到数据库中
// UserRamCache.addUser(userInfo);
// }
// }
log.debug("服务启动。开始初始化人脸库");
UserRamGroup.addCell("Guwan");
int userCount = Math.toIntExact(userService.getCount());
// int count = tenPersonService.findCount();
// int pageSize = 1000;
// int page = count/pageSize;
// if(count%1000!=0){
// page = page+1;
// }
// int faceCount = 0;
// for (int i = 0; i < page; i++) {
// int start = i*1000;
// List<TenPersonEntity> listPage = tenPersonService.listPage(start,1000);
// for(TenPersonEntity personEntity: listPage){
// if(personEntity.getFeature()!=null && personEntity.getFeature().length()>0){
// UserRamCache.UserInfo userInfo = new UserCompareInfo();
// userInfo.setFaceId(personEntity.getPersonId()+"");
// userInfo.setName(personEntity.getName());
// userInfo.setFaceFeature(Base64Util.base64ToBytes(personEntity.getFeature()));
// //这边注册到内存缓存中
// UserRamCache.addUser(userInfo);
// faceCount++;
// }
// }
// }
List<TenCellEntity> cellList = tenCellService.list(new QueryWrapper<TenCellEntity>()
.eq("tenant_id",globalValue.getTenantId())
.eq("delete_flag",0));
for(TenCellEntity cellEntity: cellList){
String cellId = cellEntity.getCellId()+"";
UserRamGroup.addCell(cellId);
UserRamGroup.addOrgId(cellEntity.getOrgId(),cellId);
int count = tenPersonService.findCount(cellId);
int pageSize = 1000;
int page = count/pageSize;
if(count%1000!=0){
page = page+1;
}
int faceCount = 0;
for (int i = 0; i < page; i++) {
int start = i*1000;
List<TenPersonEntity> listPage = tenPersonService.listPage(start,1000, cellId);
for(TenPersonEntity personEntity: listPage){
if(personEntity.getFeature()!=null && personEntity.getFeature().length()>0){
UserInfo userInfo = new UserCompareInfo();
userInfo.setFaceId(personEntity.getPersonId()+"");
userInfo.setName(personEntity.getName());
userInfo.setFaceFeature(Base64Util.base64ToBytes(personEntity.getFeature()));
//这边注册到内存缓存中
UserRamGroup.addUser(userInfo,cellId);
faceCount++;
}
int pageSize = 1000;
int page = userCount / pageSize;
if(userCount % 1000!=0){
page = page + 1;
}
int faceCount = 0;
for (int i = 0; i < page; i++) {
int start = i * 1000;
List<User> listPage = userService.listPage(start, 1000);
for(User user: listPage){
if(user.getFeature() != null && !user.getFeature().isEmpty()){
UserInfo userInfo = new UserCompareInfo();
userInfo.setFaceId(String.valueOf(user.getId()));
userInfo.setName(user.getUsername());
userInfo.setFaceFeature(Base64Util.base64ToBytes(user.getFeature()));
//这边注册到内存缓存中
UserRamGroup.addUser(userInfo,"Guwan");
faceCount++;
}
}
log.debug(cellEntity.getName()+":初始化人脸库完成,共 "+faceCount+"");
}
log.debug("初始化人脸库完成,共 "+faceCount+"");
}
}
*/

View File

@ -25,4 +25,5 @@ public class UserDTO {
private Integer status;
private String token;
private String role;
private String faceUrl;
}

View File

@ -37,4 +37,6 @@ public class User {
private Integer status;
private String feature;
private String faceUrl;
}

View File

@ -1,33 +0,0 @@
package com.guwan.backend.pojo.entity;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class UserRead {
/**
* 用户id
*/
private Long userId;
/**
* 图书id
*/
private Long bookId;
/**
* 用户上次阅读节位置
*/
private Integer sectionId;
private Long thisReadingTime;
/**
* 该书累计阅读时间
*/
private Long cumulativeReadingTime;
/**
* 上次阅读时间
*/
private LocalDateTime lastReadTime;
}

View File

@ -1,42 +0,0 @@
package com.guwan.backend.pojo.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("video")
public class Video {
@TableId(type = IdType.AUTO)
private Long id;
private String title; // 视频标题
private String description; // 视频描述
private String url; // 视频URL
private String coverUrl; // 封面图URL
private Long duration; // 视频时长
private Long size; // 文件大小字节
private String status; // 状态DRAFT-草稿PUBLISHED-已发布DELETED-已删除
private Long userId; // 上传用户ID
private String username; // 上传用户名
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedTime;
private Integer viewCount; // 观看次数
private Integer likeCount; // 点赞次数
private String tags; // 标签逗号分隔
}

View File

@ -1,19 +0,0 @@
package com.guwan.backend.pojo.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("video_like")
public class VideoLike {
@TableId(type = IdType.AUTO)
private Long id;
private Long videoId;
private Long userId;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdTime;
}

View File

@ -1,6 +1,9 @@
package com.guwan.backend.service;
import com.guwan.backend.pojo.dto.user.*;
import com.guwan.backend.pojo.entity.User;
import java.util.List;
public interface UserService {
/**
@ -22,9 +25,12 @@ public interface UserService {
UserDTO updateUserInfo(UserDTO userDTO);
void resetPassword(ChangePasswordDTO changePasswordDTO);
public String refreshToken(String token);
String refreshToken(String token);
UserDTO findByUsername(String username);
UserDTO findByEmail(String email);
UserDTO findByPhone(String phone);
Long getCount();
List<User> listPage(int page, int size);
}

View File

@ -2,6 +2,7 @@ package com.guwan.backend.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.guwan.backend.annotation.OperationLog;
import com.guwan.backend.common.BusinessException;
@ -28,7 +29,6 @@ import org.slf4j.MDC;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
@Slf4j
@Service
@ -224,7 +224,6 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
if(changePasswordDTO.getChangeWay().equals(UserEnums.ACCOUNT.getValue())){
User user = this.lambdaQuery().eq(User::getUsername, changePasswordDTO.getInfo()).one();
//matches传入密码 库中密码
@ -289,6 +288,20 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
return convertToDTO(user);
}
@Override
public Long getCount() {
return userMapper.selectCount(new LambdaQueryWrapper<>());
}
@Override
public List<User> listPage(int page, int size) {
Page<User> userPage = new Page<>(page, size);
return userMapper.selectPage(userPage, new LambdaQueryWrapper<>()).getRecords();
}
private User findUserByUsername(String username) {
UserDTO userDTO = findByUsername(username);
return userDTO != null ? convertToEntity(userDTO) : null;

View File

@ -1,17 +1,14 @@
// This file is auto-generated, don't edit it. Thanks.
package com.guwan.backend.util;
import cn.hutool.core.util.RandomUtil;
import com.aliyun.tea.*;
import java.util.Random;
public class SmsUtils {
// public static void main(String[] args) throws Exception {
// sendMessage("17653478621");
// }
public static void main(String[] args) throws Exception {
sendMessage("17653478621", "123456");
}
/**
@ -47,8 +44,6 @@ public class SmsUtils {
// 复制代码运行请自行打印 API 的返回值
client.sendSmsWithOptions(sendSmsRequest, runtime);
} catch (TeaException error) {
// 此处仅做打印展示请谨慎对待异常处理在工程项目中切勿直接忽略异常
// 错误 message