车车 4 сар өмнө
parent
commit
d76864d8e9

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

@@ -31,7 +31,9 @@ import jakarta.validation.Valid;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
 
+import java.io.IOException;
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
@@ -181,4 +183,12 @@ public class AuthController {
         return success(userService.getOnlineUser(id, username));
     }
 
+    @PostMapping("/loginByFingerprintDat")
+    @PermitAll
+    @Operation(summary = "指纹登录dat")
+    public CommonResult<AuthLoginRespVO> loginByFingerprintDat(@RequestBody @Valid MultipartFile file) throws IOException {
+        return success(authService.loginByFingerprintDat(file));
+    }
+
+
 }

+ 13 - 1
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java

@@ -4,6 +4,9 @@ import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*;
 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
 
 import jakarta.validation.Valid;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
 
 /**
  * 管理后台的认证 Service 接口
@@ -86,11 +89,20 @@ public interface AdminAuthService {
     void resetPassword(AuthResetPasswordReqVO reqVO);
 
     /**
-     * 账号登录
+     * 账号免密登录
      *
      * @param reqVO 登录信息
      * @return 登录结果
      */
     AuthLoginRespVO passwordFreeLogin(@Valid AuthPasswordFreeLoginReqVO reqVO);
 
+    /**
+     * 指纹免密登录
+     *
+     * @param file 登录信息
+     * @return 登录结果
+     */
+    AuthLoginRespVO loginByFingerprintDat(@Valid MultipartFile file) throws IOException;
+
+
 }

+ 36 - 0
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.system.service.auth;
 
+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;
@@ -15,6 +16,7 @@ import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*;
 import cn.iocoder.yudao.module.system.convert.auth.AuthConvert;
 import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.user.UserCharacteristicDO;
 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
 import cn.iocoder.yudao.module.system.enums.oauth2.OAuth2ClientConstants;
@@ -25,9 +27,13 @@ import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService;
 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.FingerprintComparisonByDat;
+import cn.iocoder.yudao.module.system.util.login.VerificationVO;
 import com.anji.captcha.model.common.ResponseModel;
 import com.anji.captcha.model.vo.CaptchaVO;
 import com.anji.captcha.service.CaptchaService;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.google.common.annotations.VisibleForTesting;
 import jakarta.annotation.Resource;
 import jakarta.validation.Validator;
@@ -37,7 +43,11 @@ import org.springframework.beans.factory.annotation.Value;
 import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
 
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
 import java.util.Objects;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -73,6 +83,9 @@ public class AdminAuthServiceImpl implements AdminAuthService {
     private RoleService roleService;
     @Resource
     private StringRedisTemplate stringRedisTemplate;
+    @Resource
+    private UserCharacteristicService userCharacteristicService;
+
 
     /**
      * 验证码的开关,默认为 true
@@ -392,4 +405,27 @@ public class AdminAuthServiceImpl implements AdminAuthService {
         // addAuthenticate(user, username);
         return user;
     }
+
+    @Override
+    public AuthLoginRespVO loginByFingerprintDat(MultipartFile file) throws IOException {
+        Assert.isTrue(file.getSize() > 0, "输入的指纹不能为空!");
+        // 获取所有的用户指纹
+        List<UserCharacteristicDO> list = userCharacteristicService.list(Wrappers.<UserCharacteristicDO>lambdaQuery()
+                .eq(UserCharacteristicDO::getType, "1"));
+        Assert.isFalse(list.isEmpty(), "指纹库中暂无您的指纹信息!");
+        List<String> collect = list.stream().map(UserCharacteristicDO::getContent).toList();
+        // 通过指纹获取最相似的用户
+        VerificationVO verificationVO = FingerprintComparisonByDat.completableFutureComparison(file, new HashSet<>(collect));
+        Assert.notNull(verificationVO, "无法根据指纹确定您的身份,请通过其它方式登录!");
+        String fingerprint = verificationVO.getFingerprint();
+        UserCharacteristicDO one = userCharacteristicService.getOne(Wrappers.<UserCharacteristicDO>lambdaQuery()
+                .eq(UserCharacteristicDO::getContent, fingerprint)
+                .last("limit 1"));
+        AdminUserDO sysUser = userService.getById(one.getUserId());
+        // 开始无密登录
+        AuthLoginRespVO authLoginRespVO = passwordFreeLogin(new AuthPasswordFreeLoginReqVO().setUsername(sysUser.getUsername()));
+        return authLoginRespVO;
+    }
+
+
 }

+ 113 - 0
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/util/login/FingerprintComparisonByDat.java

@@ -0,0 +1,113 @@
+package cn.iocoder.yudao.module.system.util.login;
+
+
+import cn.hutool.core.util.ObjectUtil;
+import com.google.common.collect.Lists;
+import com.machinezoo.sourceafis.FingerprintImage;
+import com.machinezoo.sourceafis.FingerprintMatcher;
+import com.machinezoo.sourceafis.FingerprintTemplate;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * <p>
+ * FingerprintComparison<br>
+ * 指纹比对算法(dat)
+ * </p>
+ *
+ * @author CGJ
+ * @version 1.0
+ * @since 2025年03月07日 11:51
+ */
+@Slf4j
+public class FingerprintComparisonByDat {
+
+    // 相似度门槛
+    private static final double THRESHOLD = 40; // 相当于错误率是0.01%,THRESHOLD越高,错误率越低
+    private static final ExecutorService THREAD_POOL_EXECUTOR = Executors.newFixedThreadPool(4); // 线程池
+
+    public static VerificationVO completableFutureComparison(final MultipartFile file, final Set<String> matcherDat) throws IOException {
+        // 转成list
+        List<String> matcherImgList = new ArrayList<>(matcherDat);
+        // 切分四等份
+        int denominator = 1;
+        if (matcherImgList.size() >= 4) {
+            denominator = 4;
+        }
+        List<List<String>> averageMatcherImgList = Lists.partition(matcherImgList, matcherImgList.size() / denominator);
+        // 开始解析输入的指纹到指纹模板
+        byte[] bytes = file.getBytes();
+        // 创建FingerprintImage对象
+        FingerprintImage fingerprintImage = new FingerprintImage()
+                .dpi(500) // 设置DPI,具体值取决于你的图像分辨率
+                .decode(bytes);
+        // 提取指纹特征值
+        FingerprintTemplate fingerprintTemplate = new FingerprintTemplate(fingerprintImage);
+        // 构建四个线程进行处理,防止人员过多对比速度太慢
+        CompletableFuture<VerificationVO>[] completableFutureArray = averageMatcherImgList.stream().map(
+                partitionFingerprint -> CompletableFuture.supplyAsync(
+                        () -> comparison(fingerprintTemplate, new ArrayList<>(partitionFingerprint)), THREAD_POOL_EXECUTOR)
+        ).toArray(CompletableFuture[]::new);
+        // 等待所有任务执行完
+        CompletableFuture.allOf(completableFutureArray).join();
+        List<VerificationVO> verificationList = new ArrayList<>();
+        for (CompletableFuture<VerificationVO> completableFuture : completableFutureArray) {
+            try {
+                VerificationVO verification = completableFuture.get();
+                if (ObjectUtil.isNotEmpty(verification)) {
+                    verificationList.add(verification);
+                }
+            } catch (InterruptedException | ExecutionException e) {
+                log.error(e.getMessage(), e);
+            }
+        }
+        // 找出最匹配的指纹(匹配度最高)
+        if (ObjectUtil.isNotEmpty(verificationList)) {
+            VerificationVO max = verificationList.stream().max(Comparator.comparing(VerificationVO::getScore)).get();
+            log.info("相似值最佳的的指纹:{},分数{}", max.getFingerprint(), max.getScore());
+            return max;
+        }
+        return null;
+    }
+
+    public static VerificationVO comparison(final FingerprintTemplate fingerprintTemplate, final List<String> matcherDatList) {
+        try {
+            if (!matcherDatList.isEmpty()) {
+                // 输入的被验证指纹
+                FingerprintMatcher matcher = new FingerprintMatcher(fingerprintTemplate);
+                // 最匹配的指纹
+                String match = null;
+                // 峰值
+                double high = 0;
+                for (String matcherDat : matcherDatList) {
+                    // 输入的指纹和指纹库中的指纹进行对比,找到当前线程中的分数最高的指纹
+                    FingerprintTemplate matcherTemp = new FingerprintTemplate().deserialize(new String(Files.readAllBytes(Paths.get(matcherDat))));
+                    double score = matcher.match(matcherTemp);
+                    if (score > high) {
+                        high = score;
+                        match = matcherDat;
+                    }
+                }
+                return high >= THRESHOLD ? new VerificationVO().setFingerprint(match).setScore(high) : null;
+            }
+        } catch (Exception e) {
+            log.error("指纹比对异常:{}", e.getMessage());
+        }
+        return null;
+    }
+
+}
+
+

+ 39 - 0
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/util/login/VerificationVO.java

@@ -0,0 +1,39 @@
+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 VerificationVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "人员编号")
+    private Long userId;
+
+    @Schema(description = "指纹图片地址")
+    private String fingerprintImg;
+
+    @Schema(description = "指纹信息")
+    private String fingerprint;
+
+    @Schema(description = "指纹模板信息")
+    private byte[] fingerprintTemplate;
+
+    @Schema(description = "指纹信息")
+    private String fingerprintHex;
+
+    @Schema(description = "排序")
+    private Integer sort;
+
+    @Schema(description = "得分")
+    private Double score;
+
+}
+