Jelajahi Sumber

人脸识别逻辑优化

车车 4 bulan lalu
induk
melakukan
5b08eef556
17 mengubah file dengan 456 tambahan dan 70 penghapusan
  1. 3 0
      yudao-module-iscs/src/main/java/cn/iocoder/yudao/module/iscs/controller/admin/sop/vo/SopRespVO.java
  2. 4 1
      yudao-module-iscs/src/main/java/cn/iocoder/yudao/module/iscs/service/sop/SopServiceImpl.java
  3. 6 0
      yudao-module-system/pom.xml
  4. 7 0
      yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java
  5. 7 0
      yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/usercharacteristic/UserCharacteristicController.java
  6. 33 0
      yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/usercharacteristic/vo/FaceCutVO.java
  7. 0 12
      yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/usercharacteristic/vo/UserCharacteristicPageReqVO.java
  8. 0 16
      yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/usercharacteristic/vo/UserCharacteristicRespVO.java
  9. 0 12
      yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/usercharacteristic/vo/UserCharacteristicSaveReqVO.java
  10. 0 17
      yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/user/UserCharacteristicDO.java
  11. 0 4
      yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/user/UserCharacteristicMapper.java
  12. 8 0
      yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java
  13. 44 3
      yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java
  14. 2 0
      yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/usercharacteristic/UserCharacteristicService.java
  15. 59 5
      yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/usercharacteristic/UserCharacteristicServiceImpl.java
  16. 256 0
      yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/util/login/ArcSoftMothodUtil.java
  17. 27 0
      yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/util/login/FaceMatchVO.java

+ 3 - 0
yudao-module-iscs/src/main/java/cn/iocoder/yudao/module/iscs/controller/admin/sop/vo/SopRespVO.java

@@ -92,4 +92,7 @@ public class SopRespVO {
     @Schema(description = "关联人员信息")
     private List<SopUserRespVO> sopUserList;
 
+    @Schema(description = "sop分组数据")
+    private List<SopGroupRespVO> sopGroupList;
+
 }

+ 4 - 1
yudao-module-iscs/src/main/java/cn/iocoder/yudao/module/iscs/service/sop/SopServiceImpl.java

@@ -153,11 +153,14 @@ public class SopServiceImpl extends ServiceImpl<SopMapper, SopDO> implements Sop
         // 查询SopWorkflowStep
         SopWorkflowStepPageReqVO sopWorkflowStepPageReqVO = new SopWorkflowStepPageReqVO().setSopId(id);
         List<SopWorkflowStepRespVO> sopWorkflowStepList = sopWorkflowStepService.getSopWorkflowStepList(sopWorkflowStepPageReqVO);
-
+        // 查询分组信息
+        SopGroupPageReqVO sopGroupPageReqVO = new SopGroupPageReqVO().setSopId(id);
+        List<SopGroupRespVO> sopGroupList = sopGroupService.getSopGroupList(sopGroupPageReqVO);
         // 组装数据
         bean.setSopUserList(sopUserList);
         bean.setSopPointsList(sopPointsList);
         bean.setSopStepList(sopWorkflowStepList);
+        bean.setSopGroupList(sopGroupList);
         return bean;
     }
 

+ 6 - 0
yudao-module-system/pom.xml

@@ -125,6 +125,12 @@
             <artifactId>sourceafis</artifactId>
             <version>3.13.0</version>
         </dependency>
+        <!-- 人脸 -->
+        <dependency>
+            <groupId>com.arcsoft.face</groupId>
+            <artifactId>arcsoft-sdk-face</artifactId>
+            <version>3.0.0.0</version>
+        </dependency>
 
     </dependencies>
 

+ 7 - 0
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java

@@ -190,5 +190,12 @@ public class AuthController {
         return success(authService.loginByFingerprintDat(file));
     }
 
+    @PostMapping("/loginByArcFace")
+    @PermitAll
+    @Operation(summary = "人脸登录arcsoft")
+    public CommonResult<AuthLoginRespVO> loginByArcFace(@RequestBody @Valid MultipartFile file) throws Exception {
+        return success(authService.loginByArcFace(file));
+    }
+
 
 }

+ 7 - 0
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/usercharacteristic/UserCharacteristicController.java

@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.controller.admin.usercharacteristic;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.system.controller.admin.usercharacteristic.vo.FaceCutVO;
 import cn.iocoder.yudao.module.system.controller.admin.usercharacteristic.vo.UserCharacteristicPageReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.usercharacteristic.vo.UserCharacteristicRespVO;
 import cn.iocoder.yudao.module.system.dal.dataobject.user.UserCharacteristicDO;
@@ -62,5 +63,11 @@ public class UserCharacteristicController {
         return CommonResult.success(userCharacteristicService.insertUserFingerprintDat(file, userName));
     }
 
+    @Operation(summary = "新增人脸录入-人脸识别后裁剪存储-arcsoft")
+    @PostMapping("/insertUserFace")
+    public CommonResult<FaceCutVO> insertUserFace(MultipartFile file, String userName) throws Exception {
+        return CommonResult.success(userCharacteristicService.insertUserFace(file, userName));
+    }
+
 
 }

+ 33 - 0
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/usercharacteristic/vo/FaceCutVO.java

@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.system.controller.admin.usercharacteristic.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+
+@EqualsAndHashCode(callSuper = false)
+@Data
+@Accessors(chain = true)
+public class FaceCutVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "用户id")
+    private Long userId;
+
+    @Schema(description = "类型(1-指纹图片 2-面部图片)")
+    private String type;
+
+    @Schema(description = "内容")
+    private String content;
+
+    @Schema(description = "图片地址")
+    private String imageUrl;
+
+    @Schema(description = "图片路径")
+    private String imagePath;
+
+}
+

+ 0 - 12
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/usercharacteristic/vo/UserCharacteristicPageReqVO.java

@@ -34,18 +34,6 @@ public class UserCharacteristicPageReqVO extends PageParam {
     @Schema(description = "排序")
     private Integer orderNum;
 
-    @Schema(description = "直方图rows")
-    private Integer matRows;
-
-    @Schema(description = "直方图cols")
-    private Integer matCols;
-
-    @Schema(description = "直方图type", example = "1")
-    private Integer matType;
-
-    @Schema(description = "直方图数据")
-    private String matData;
-
     @Schema(description = "创建时间")
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
     private LocalDateTime[] createTime;

+ 0 - 16
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/usercharacteristic/vo/UserCharacteristicRespVO.java

@@ -44,22 +44,6 @@ public class UserCharacteristicRespVO {
     @ExcelProperty("排序")
     private Integer orderNum;
 
-    @Schema(description = "直方图rows")
-    @ExcelProperty("直方图rows")
-    private Integer matRows;
-
-    @Schema(description = "直方图cols")
-    @ExcelProperty("直方图cols")
-    private Integer matCols;
-
-    @Schema(description = "直方图type", example = "1")
-    @ExcelProperty("直方图type")
-    private Integer matType;
-
-    @Schema(description = "直方图数据")
-    @ExcelProperty("直方图数据")
-    private String matData;
-
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
     @ExcelProperty("创建时间")
     private LocalDateTime createTime;

+ 0 - 12
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/usercharacteristic/vo/UserCharacteristicSaveReqVO.java

@@ -36,16 +36,4 @@ public class UserCharacteristicSaveReqVO {
     @Schema(description = "排序")
     private Integer orderNum;
 
-    @Schema(description = "直方图rows")
-    private Integer matRows;
-
-    @Schema(description = "直方图cols")
-    private Integer matCols;
-
-    @Schema(description = "直方图type", example = "1")
-    private Integer matType;
-
-    @Schema(description = "直方图数据")
-    private String matData;
-
 }

+ 0 - 17
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/user/UserCharacteristicDO.java

@@ -54,22 +54,5 @@ public class UserCharacteristicDO extends BaseDO {
      * 排序
      */
     private Integer orderNum;
-    /**
-     * 直方图rows
-     */
-    private Integer matRows;
-    /**
-     * 直方图cols
-     */
-    private Integer matCols;
-    /**
-     * 直方图type
-     */
-    private Integer matType;
-    /**
-     * 直方图数据
-     */
-    private String matData;
-
 
 }

+ 0 - 4
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/user/UserCharacteristicMapper.java

@@ -28,10 +28,6 @@ public interface UserCharacteristicMapper extends BaseMapperX<UserCharacteristic
                 .eqIfPresent(UserCharacteristicDO::getImagePath, reqVO.getImagePath())
                 .eqIfPresent(UserCharacteristicDO::getRemark, reqVO.getRemark())
                 .eqIfPresent(UserCharacteristicDO::getOrderNum, reqVO.getOrderNum())
-                .eqIfPresent(UserCharacteristicDO::getMatRows, reqVO.getMatRows())
-                .eqIfPresent(UserCharacteristicDO::getMatCols, reqVO.getMatCols())
-                .eqIfPresent(UserCharacteristicDO::getMatType, reqVO.getMatType())
-                .eqIfPresent(UserCharacteristicDO::getMatData, reqVO.getMatData())
                 .betweenIfPresent(UserCharacteristicDO::getCreateTime, reqVO.getCreateTime())
                 .orderByDesc(UserCharacteristicDO::getId));
     }

+ 8 - 0
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java

@@ -104,5 +104,13 @@ public interface AdminAuthService {
      */
     AuthLoginRespVO loginByFingerprintDat(@Valid MultipartFile file) throws IOException;
 
+    /**
+     * 人脸登录arcsoft
+     *
+     * @param file 登录信息
+     * @return 登录结果
+     */
+    AuthLoginRespVO loginByArcFace(@Valid MultipartFile file) throws Exception;
+
 
 }

+ 44 - 3
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java

@@ -4,9 +4,15 @@ import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import cn.iocoder.yudao.framework.common.util.io.FileDriveLetterUtils;
 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
 import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
+import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO;
+import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClient;
+import cn.iocoder.yudao.module.infra.framework.file.core.client.local.LocalFileClientConfig;
+import cn.iocoder.yudao.module.infra.service.file.FileConfigService;
+import cn.iocoder.yudao.module.infra.service.file.FileService;
 import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO;
 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
 import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
@@ -28,6 +34,8 @@ import cn.iocoder.yudao.module.system.service.permission.RoleService;
 import cn.iocoder.yudao.module.system.service.social.SocialUserService;
 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
 import cn.iocoder.yudao.module.system.service.usercharacteristic.UserCharacteristicService;
+import cn.iocoder.yudao.module.system.util.login.ArcSoftMothodUtil;
+import cn.iocoder.yudao.module.system.util.login.FaceMatchVO;
 import cn.iocoder.yudao.module.system.util.login.FingerprintComparisonByDat;
 import cn.iocoder.yudao.module.system.util.login.VerificationVO;
 import com.anji.captcha.model.common.ResponseModel;
@@ -80,12 +88,17 @@ public class AdminAuthServiceImpl implements AdminAuthService {
     @Resource
     private SmsCodeApi smsCodeApi;
     @Resource
+    private FileConfigService fileConfigService;
+    @Resource
+    private FileService fileService;
+    @Resource
     private RoleService roleService;
     @Resource
     private StringRedisTemplate stringRedisTemplate;
     @Resource
     private UserCharacteristicService userCharacteristicService;
-
+    @Value("${local-file.face-path}")
+    private String facePath;
 
     /**
      * 验证码的开关,默认为 true
@@ -423,9 +436,37 @@ public class AdminAuthServiceImpl implements AdminAuthService {
                 .last("limit 1"));
         AdminUserDO sysUser = userService.getById(one.getUserId());
         // 开始无密登录
-        AuthLoginRespVO authLoginRespVO = passwordFreeLogin(new AuthPasswordFreeLoginReqVO().setUsername(sysUser.getUsername()));
-        return authLoginRespVO;
+        return passwordFreeLogin(new AuthPasswordFreeLoginReqVO().setUsername(sysUser.getUsername()));
     }
 
+    @Override
+    public AuthLoginRespVO loginByArcFace(MultipartFile file) throws Exception {
+
+        Assert.isTrue(file.getSize() > 0, "输入的人脸不能为空!");
+        // 获取所有的用户人脸
+        List<UserCharacteristicDO> list = userCharacteristicService.list(Wrappers.<UserCharacteristicDO>lambdaQuery()
+                .eq(UserCharacteristicDO::getType, "2"));
+        Assert.isFalse(list.isEmpty(), "人脸库中暂无您的人脸信息!");
+        List<String> collect = list.stream().map(UserCharacteristicDO::getContent).toList();
+        // D:
+        String driveLetter = FileDriveLetterUtils.getDriveLetter();
+        FileClient fileClient = fileConfigService.getFileClientByName("生物识别存储器");
+        FileConfigDO fileConfig = fileConfigService.getFileConfig(fileClient.getId());
+        LocalFileClientConfig config = (LocalFileClientConfig) fileConfig.getConfig();
+        // /home
+        String basePath = config.getBasePath();
+        // 本地路径
+        String localUrl = driveLetter + basePath + "/" + facePath + "/0/";
+        // 通过人脸获取最相似的用户
+        FaceMatchVO faceMatchVO = ArcSoftMothodUtil.completableFutureComparison(file, new HashSet<>(collect), localUrl);
+        Assert.notNull(faceMatchVO, "无法根据人脸确定您的身份,请通过其它方式登录!");
+        String okFace = faceMatchVO.getContent();
+        UserCharacteristicDO one = userCharacteristicService.getOne(Wrappers.<UserCharacteristicDO>lambdaQuery()
+                .eq(UserCharacteristicDO::getContent, okFace)
+                .last("limit 1"));
+        AdminUserDO sysUser = userService.getById(one.getUserId());
+        // 开始无密登录
+        return passwordFreeLogin(new AuthPasswordFreeLoginReqVO().setUsername(sysUser.getUsername()));
+    }
 
 }

+ 2 - 0
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/usercharacteristic/UserCharacteristicService.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.system.service.usercharacteristic;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.system.controller.admin.usercharacteristic.vo.FaceCutVO;
 import cn.iocoder.yudao.module.system.controller.admin.usercharacteristic.vo.UserCharacteristicPageReqVO;
 import cn.iocoder.yudao.module.system.dal.dataobject.user.UserCharacteristicDO;
 import com.baomidou.mybatisplus.extension.service.IService;
@@ -40,5 +41,6 @@ public interface UserCharacteristicService extends IService<UserCharacteristicDO
 
     Boolean insertUserFingerprintDat(MultipartFile file, String userName) throws Exception;
 
+    FaceCutVO insertUserFace(MultipartFile file, String userName) throws Exception;
 
 }

+ 59 - 5
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/usercharacteristic/UserCharacteristicServiceImpl.java

@@ -10,6 +10,7 @@ import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClient;
 import cn.iocoder.yudao.module.infra.framework.file.core.client.local.LocalFileClientConfig;
 import cn.iocoder.yudao.module.infra.service.file.FileConfigService;
 import cn.iocoder.yudao.module.infra.service.file.FileService;
+import cn.iocoder.yudao.module.system.controller.admin.usercharacteristic.vo.FaceCutVO;
 import cn.iocoder.yudao.module.system.controller.admin.usercharacteristic.vo.UserCharacteristicPageReqVO;
 import cn.iocoder.yudao.module.system.dal.dataobject.attribute.AttributeDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
@@ -17,6 +18,7 @@ import cn.iocoder.yudao.module.system.dal.dataobject.user.UserCharacteristicDO;
 import cn.iocoder.yudao.module.system.dal.mysql.user.UserCharacteristicMapper;
 import cn.iocoder.yudao.module.system.service.attribute.AttributeService;
 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
+import cn.iocoder.yudao.module.system.util.login.ArcSoftMothodUtil;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.machinezoo.sourceafis.FingerprintTemplate;
@@ -57,6 +59,9 @@ public class UserCharacteristicServiceImpl extends ServiceImpl<UserCharacteristi
     @Value("${local-file.finger-path}")
     private String fingerPath;
 
+    @Value("${local-file.face-path}")
+    private String facePath;
+
     @Override
     public void deleteUserCharacteristicListByIds(List<Long> ids) {
         // 删除
@@ -93,10 +98,6 @@ public class UserCharacteristicServiceImpl extends ServiceImpl<UserCharacteristi
             int i = Integer.parseInt(sysAttrValue);
             Assert.isFalse(count >= i, "该人员的指纹录入已上限,最大" + i + "条!");
         }
-        FileClient fileClient = fileConfigService.getFileClientByName("生物识别存储器");
-        FileConfigDO fileConfig = fileConfigService.getFileConfig(fileClient.getId());
-        LocalFileClientConfig config = (LocalFileClientConfig) fileConfig.getConfig();
-        String basePath = config.getBasePath();
         // 上传文件
         FileUploadReqVO fileUploadReqVO = new FileUploadReqVO();
         fileUploadReqVO.setFile(file);
@@ -108,8 +109,13 @@ public class UserCharacteristicServiceImpl extends ServiceImpl<UserCharacteristi
         String imageUrl = fileDO.getUrl();
         String datPath = null;
         try {
-            // 盘符
+            // 盘符 D:
             String driveLetter = FileDriveLetterUtils.getDriveLetter();
+            // 基础路径/home
+            FileClient fileClient = fileConfigService.getFileClientByName("生物识别存储器");
+            FileConfigDO fileConfig = fileConfigService.getFileConfig(fileClient.getId());
+            LocalFileClientConfig config = (LocalFileClientConfig) fileConfig.getConfig();
+            String basePath = config.getBasePath();
             // 时间戳
             byte[] fingerprintData = extractFingerprintTemplate(driveLetter + basePath + "/" + fileDO.getPath());
             // 保存到文件
@@ -186,4 +192,52 @@ public class UserCharacteristicServiceImpl extends ServiceImpl<UserCharacteristi
         return path.substring(0, lastSlashIndex + 1); // 包含斜杠
     }
 
+    @Override
+    public FaceCutVO insertUserFace(MultipartFile file, String userName) throws Exception {
+        Assert.notBlank(userName, "请告知我这是哪个用户的人脸!");
+        Assert.isTrue(file.getSize() > 0, "人脸信息不能为空!");
+        AdminUserDO user = adminUserService.getOne(Wrappers.<AdminUserDO>lambdaQuery()
+                .eq(AdminUserDO::getUsername, userName));
+        Assert.isFalse(user == null, "系统中无该用户!");
+        Long userId = user.getId();
+        AttributeDO one = attributeService.getAttributeByKey("sys.face.limit");
+        String sysAttrValue = null;
+        if (one != null) {
+            sysAttrValue = one.getSysAttrValue();
+        }
+        if (StringUtils.isNotBlank(sysAttrValue)) {
+            // 检查人员人脸上限
+            long count = count(Wrappers.<UserCharacteristicDO>lambdaQuery()
+                    .eq(UserCharacteristicDO::getUserId, userId)
+                    .eq(UserCharacteristicDO::getType, "2"));
+            int i = Integer.parseInt(sysAttrValue);
+            Assert.isFalse(count >= i, "该人员的人脸录入已上限,最大" + i + "条!");
+        }
+        // D:
+        String driveLetter = FileDriveLetterUtils.getDriveLetter();
+        FileClient fileClient = fileConfigService.getFileClientByName("生物识别存储器");
+        FileConfigDO fileConfig = fileConfigService.getFileConfig(fileClient.getId());
+        LocalFileClientConfig config = (LocalFileClientConfig) fileConfig.getConfig();
+        // /home
+        String basePath = config.getBasePath();
+        // 上传文件
+        FileUploadReqVO fileUploadReqVO = new FileUploadReqVO();
+        fileUploadReqVO.setFile(file);
+        fileUploadReqVO.setDirectory(facePath + "/" + userId);
+        FileDO fileDO = fileService.uploadLocalFile(fileUploadReqVO);
+        // 本地路径
+        String localUrl = driveLetter + basePath + "/" + fileDO.getPath();
+
+        FaceCutVO faceCutVO = ArcSoftMothodUtil.saveArcData(localUrl, fileDO.getUrl());
+        UserCharacteristicDO characteristicDO = new UserCharacteristicDO();
+        characteristicDO.setUserId(userId);
+        characteristicDO.setType("2");
+        characteristicDO.setContent(faceCutVO.getContent());
+        characteristicDO.setOrderNum(0);
+        characteristicDO.setImageUrl(faceCutVO.getImageUrl());
+        characteristicDO.setImagePath(faceCutVO.getImagePath());
+        save(characteristicDO);
+        return null;
+    }
+
 }

+ 256 - 0
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/util/login/ArcSoftMothodUtil.java

@@ -0,0 +1,256 @@
+package cn.iocoder.yudao.module.system.util.login;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.ObjectUtil;
+import cn.iocoder.yudao.framework.common.util.io.FileDriveLetterUtils;
+import cn.iocoder.yudao.module.system.controller.admin.usercharacteristic.vo.FaceCutVO;
+import com.arcsoft.face.*;
+import com.arcsoft.face.enums.DetectMode;
+import com.arcsoft.face.enums.DetectOrient;
+import com.arcsoft.face.enums.ErrorInfo;
+import com.arcsoft.face.toolkit.ImageInfo;
+import com.google.common.collect.Lists;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static com.arcsoft.face.toolkit.ImageFactory.getRGBData;
+
+/**
+ * 面部裁剪工具
+ *
+ * @author cgj
+ */
+@Slf4j
+public class ArcSoftMothodUtil {
+
+
+    static FaceEngine faceEngine;
+
+    static {
+        //从官网获取
+        String appId;
+        String sdkKey;
+        String libarcsoftFaceDllPath;
+        String driveLetter = FileDriveLetterUtils.getDriveLetter();
+        if (StringUtils.isBlank(driveLetter)) {
+            // 在linux
+            appId = "FTN3G4pk8n2RKwjD955sRapRjbYQFefwhHd4sBZMYEz6";
+            sdkKey = "BjJomNU2bQc2SYhT7NNqwvFd1t6RRNv5DTZNYd8pMH19";
+            libarcsoftFaceDllPath = "/usr/lib/ArcSoft_ArcFace_Java_Linux_x64_V3.0/libs/LINUX64";
+        } else {
+            // 在win
+            appId = "FTN3G4pk8n2RKwjD955sRapRjbYQFefwhHd4sBZMYEz6";
+            sdkKey = "BjJomNU2bQc2SYhT7NNqwvFd9zfc72Q7nneh75r3NT3x";
+            libarcsoftFaceDllPath = "D:\\work\\app\\appinstall\\ArcSoft_ArcFace_Java_Windows_x64_V3.0\\libs\\WIN64";
+        }
+        faceEngine = new FaceEngine(libarcsoftFaceDllPath);
+        //激活引擎
+        int errorCode = faceEngine.activeOnline(appId, sdkKey);
+
+        if (errorCode != ErrorInfo.MOK.getValue() && errorCode != ErrorInfo.MERR_ASF_ALREADY_ACTIVATED.getValue()) {
+            System.out.println("引擎激活失败");
+        }
+
+
+        ActiveFileInfo activeFileInfo = new ActiveFileInfo();
+        errorCode = faceEngine.getActiveFileInfo(activeFileInfo);
+        if (errorCode != ErrorInfo.MOK.getValue() && errorCode != ErrorInfo.MERR_ASF_ALREADY_ACTIVATED.getValue()) {
+            System.out.println("获取激活文件信息失败");
+        }
+
+        //引擎配置
+        EngineConfiguration engineConfiguration = new EngineConfiguration();
+        engineConfiguration.setDetectMode(DetectMode.ASF_DETECT_MODE_IMAGE);
+        engineConfiguration.setDetectFaceOrientPriority(DetectOrient.ASF_OP_ALL_OUT);
+        engineConfiguration.setDetectFaceMaxNum(10);
+        engineConfiguration.setDetectFaceScaleVal(16);
+        //功能配置
+        FunctionConfiguration functionConfiguration = new FunctionConfiguration();
+        functionConfiguration.setSupportAge(true);
+        functionConfiguration.setSupportFace3dAngle(true);
+        functionConfiguration.setSupportFaceDetect(true);
+        functionConfiguration.setSupportFaceRecognition(true);
+        functionConfiguration.setSupportGender(true);
+        functionConfiguration.setSupportLiveness(true);
+        functionConfiguration.setSupportIRLiveness(true);
+        engineConfiguration.setFunctionConfiguration(functionConfiguration);
+
+        //初始化引擎
+        errorCode = faceEngine.init(engineConfiguration);
+
+        if (errorCode != ErrorInfo.MOK.getValue()) {
+            System.out.println("初始化引擎失败");
+        }
+    }
+
+
+    /**
+     * 图片人脸检测
+     */
+    public static FaceCutVO saveArcData( String imageUrl, String imagePath) {
+        int errorCode;
+        // 3--------------------------开始读取照片的特征值-------------------------------
+        ImageInfo imageInfo = getRGBData(new File(imagePath));
+        List<FaceInfo> faceInfoList = new ArrayList<>();
+        errorCode = faceEngine.detectFaces(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList);
+        Assert.isFalse(errorCode != ErrorInfo.MOK.getValue(), "获取人脸失败!");
+
+        //特征提取
+        FaceFeature faceFeature = new FaceFeature();
+        errorCode = faceEngine.extractFaceFeature(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList.get(0), faceFeature);
+        Assert.isFalse(errorCode != ErrorInfo.MOK.getValue(), "获取人脸特征值失败!");
+        byte[] featureData = faceFeature.getFeatureData();
+        // 4------------------------返回解析的数据---------------------
+        FaceCutVO faceCutVO = new FaceCutVO();
+        faceCutVO.setType("2");
+        faceCutVO.setContent(Arrays.toString(featureData));
+        faceCutVO.setImageUrl(imageUrl);
+        faceCutVO.setImagePath(imagePath);
+        return faceCutVO;
+    }
+
+
+    // 相似度门槛
+    private static final double THRESHOLD = 0.8; // THRESHOLD越高,错误率越低,阈值[0,1]
+    private static final ExecutorService THREAD_POOL_EXECUTOR = Executors.newFixedThreadPool(4); // 线程池
+
+    public static FaceMatchVO completableFutureComparison(final MultipartFile file, final Set<String> matcher, String basePath) throws IOException {
+        int errorCode;
+        // 时间戳
+        long currentTimestamp = System.currentTimeMillis();
+        // 确保目录存在,不存在则创建一个
+        Path uploadPath = Paths.get(basePath);
+        if (!Files.exists(uploadPath)) {
+            Files.createDirectories(uploadPath);
+        }
+        // 获取文件名并构建目标路径
+        String fileName = basePath + currentTimestamp + "_" + file.getOriginalFilename();
+        Path targetLocation = uploadPath.resolve(fileName);
+        // 将文件写入目标路径
+        file.transferTo(targetLocation.toFile());
+        try {
+            //人脸检测
+            ImageInfo imageInfo = getRGBData(new File(fileName));
+            List<FaceInfo> faceInfoList = new ArrayList<>();
+            errorCode = faceEngine.detectFaces(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList);
+            Assert.isFalse(errorCode != ErrorInfo.MOK.getValue(), "未识别到人脸!");
+
+            //特征提取
+            FaceFeature faceFeature = new FaceFeature();
+            errorCode = faceEngine.extractFaceFeature(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList.get(0), faceFeature);
+            Assert.isFalse(errorCode != ErrorInfo.MOK.getValue(), "获取人脸特征值失败!");
+            byte[] featureData = faceFeature.getFeatureData();
+
+            // 转成list
+            List<String> matcherFeatureDataList = new ArrayList<>(matcher);
+
+            // 切分四等份
+            int denominator = 1;
+            if (matcherFeatureDataList.size() >= 4) {
+                denominator = 4;
+            }
+            List<List<String>> averageMatcherFeatureDataList = Lists.partition(matcherFeatureDataList, matcherFeatureDataList.size() / denominator);
+
+            // 构建四个线程进行处理,防止人员过多对比速度太慢
+            CompletableFuture<FaceMatchVO>[] completableFutureArray = averageMatcherFeatureDataList.stream().map(partitionFeatureData -> CompletableFuture.supplyAsync(() -> comparison(featureData, new ArrayList<>(partitionFeatureData)), THREAD_POOL_EXECUTOR)).toArray(CompletableFuture[]::new);
+            // 等待所有任务执行完
+            CompletableFuture.allOf(completableFutureArray).join();
+            List<FaceMatchVO> verificationList = new ArrayList<>();
+            for (CompletableFuture<FaceMatchVO> completableFuture : completableFutureArray) {
+                try {
+                    FaceMatchVO verification = completableFuture.get();
+                    if (ObjectUtil.isNotEmpty(verification)) {
+                        verificationList.add(verification);
+                    }
+                } catch (InterruptedException | ExecutionException e) {
+                    log.error(e.getMessage(), e);
+                }
+            }
+            // 找出最匹配的人脸(匹配度最高)
+            if (ObjectUtil.isNotEmpty(verificationList)) {
+                FaceMatchVO max = verificationList.stream().max(Comparator.comparing(FaceMatchVO::getScore)).get();
+                log.info("相似值最佳的的人脸:{},分数{}", max.getContent(), max.getScore());
+                return max;
+            }
+        } catch (Exception e) {
+            log.error("人脸比对异常:{}", e.getMessage());
+        } finally {
+            //特征值提取完毕后清除数据
+            removeFile(fileName);
+        }
+        return null;
+    }
+
+    public static FaceMatchVO comparison(final byte[] featureData, final List<String> matcherFeatureDataList) {
+        try {
+            if (!matcherFeatureDataList.isEmpty()) {
+                // 最匹配的人脸
+                String match = null;
+                // 峰值
+                double high = 0;
+                FaceFeature targetFaceFeature = new FaceFeature();
+                targetFaceFeature.setFeatureData(featureData);
+                FaceFeature sourceFaceFeature = new FaceFeature();
+                FaceSimilar faceSimilar = new FaceSimilar();
+                for (String matcherData : matcherFeatureDataList) {
+                    // 输入的人脸和人脸库中的人脸进行对比,找到当前线程中的分数最高的人脸
+                    byte[] matchData = strToBytes(matcherData);
+                    //特征比对
+                    sourceFaceFeature.setFeatureData(matchData);
+                    int errorCode = faceEngine.compareFaceFeature(targetFaceFeature, sourceFaceFeature, faceSimilar);
+                    Assert.isFalse(errorCode != ErrorInfo.MOK.getValue(), "人脸对比异常" + errorCode + "!");
+                    // 相似度
+                    float score = faceSimilar.getScore();
+                    if (score > high) {
+                        high = score;
+                        match = matcherData;
+                    }
+                }
+                return high >= THRESHOLD ? new FaceMatchVO().setContent(match).setScore(high) : null;
+            }
+        } catch (Exception e) {
+            log.error("人脸比对异常:{}", e.getMessage());
+        }
+        return null;
+    }
+
+    private static byte[] strToBytes(String string) {
+        // Remove the brackets and spaces from the string
+        string = string.substring(1, string.length() - 1).replaceAll("\\s", "");
+        // Split the string by commas to get each number as a string
+        String[] numbers = string.split(",");
+        // Create a new byte array with the same length as the original
+        byte[] newBytes = new byte[numbers.length];
+        // Convert each string number back to a byte and store it in the new array
+        for (int i = 0; i < numbers.length; i++) {
+            newBytes[i] = Byte.parseByte(numbers[i]);
+        }
+        // Print the new byte array to verify it matches the original
+        // System.out.println("New byte array: " + Arrays.toString(newBytes));
+        return newBytes;
+    }
+
+    private static void removeFile(String path) {
+        try {
+            File file = new File(path);
+            if (!file.delete()) {
+                log.error("DAT文件删除失败: " + path);
+            }
+        } catch (Exception e) {
+            log.error("存在异常文件:{}", e);
+        }
+    }
+
+}

+ 27 - 0
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/util/login/FaceMatchVO.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.system.util.login;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+
+@EqualsAndHashCode(callSuper = false)
+@Data
+@Accessors(chain = true)
+public class FaceMatchVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "人员编号")
+    private Long userId;
+
+    @Schema(description = "内容")
+    private String content;
+
+    @Schema(description = "得分")
+    private Double score;
+
+}
+