Răsfoiți Sursa

fix: 消费服务
1.补充人脸消费的相关接口

luo.yibo@datuai.com 1 an în urmă
părinte
comite
05a6fbc6d2
18 a modificat fișierele cu 681 adăugiri și 78 ștergeri
  1. 6 0
      ruoyi-server/ruoyi-server-consume/pom.xml
  2. 111 3
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/business/ArcFaceBusiness.java
  3. 32 0
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/config/ArcFaceConfig.java
  4. 64 0
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/config/FaceEngineFactory.java
  5. 93 8
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/controller/v1/FaceCollectController.java
  6. 14 0
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/mapper/PtArcFaceFeatureMapper.java
  7. 1 0
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/mapper/PtArcFaceKeyMapper.java
  8. 27 0
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/FaceEngineService.java
  9. 0 25
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/IFaceEngineService.java
  10. 33 0
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/IPtArcFaceFeatureService.java
  11. 16 0
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/IPtArcFaceKeyService.java
  12. 9 0
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/IPtTermFaceVersionService.java
  13. 114 29
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/impl/FaceEngineServiceImpl.java
  14. 65 0
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/impl/PtArcFaceFeatureServiceImpl.java
  15. 45 0
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/impl/PtArcFaceKeyServiceImpl.java
  16. 25 2
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/impl/PtTermFaceVersionServiceImpl.java
  17. 9 10
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/impl/XfTermServiceImpl.java
  18. 17 1
      ruoyi-server/ruoyi-server-consume/src/main/resources/mapper/consume/PtArcFaceFeatureMapper.xml

+ 6 - 0
ruoyi-server/ruoyi-server-consume/pom.xml

@@ -122,6 +122,12 @@
             <groupId>org.springframework.kafka</groupId>
             <artifactId>spring-kafka</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.arcsoft.face</groupId>
+            <artifactId>arcsoft-sdk-face</artifactId>
+            <version>3.0.0.0</version>
+            <scope>compile</scope>
+        </dependency>
     </dependencies>
 
     <build>

+ 111 - 3
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/business/ArcFaceBusiness.java

@@ -2,17 +2,27 @@ package org.dromara.server.consume.business;
 
 import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.util.ObjectUtil;
+import com.arcsoft.face.toolkit.ImageInfo;
 import lombok.RequiredArgsConstructor;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.dromara.backstage.api.RemoteUserAccountService;
+import org.dromara.backstage.api.domain.vo.RemoteUserAccountVo;
 import org.dromara.common.core.utils.MapstructUtils;
-import org.dromara.server.consume.domain.vo.YcFaceFeatureVo;
+import org.dromara.server.consume.domain.bo.PtArcFaceFeatureBo;
 import org.dromara.server.consume.domain.vo.PtArcFaceFeatureVo;
+import org.dromara.server.consume.domain.vo.YcFaceFeatureVo;
+import org.dromara.server.consume.service.FaceEngineService;
 import org.dromara.server.consume.service.IPtArcFaceFeatureService;
+import org.dromara.server.consume.service.IPtArcFaceKeyService;
 import org.dromara.server.consume.service.IPtTermFaceVersionService;
 import org.springframework.stereotype.Service;
 
+import java.io.File;
 import java.util.Date;
 import java.util.List;
 
+import static com.arcsoft.face.toolkit.ImageFactory.getRGBData;
+
 /**
  * name: ArcFaceBusiness
  * package: org.dromara.server.consume.business
@@ -28,16 +38,114 @@ import java.util.List;
 public class ArcFaceBusiness {
     private final IPtTermFaceVersionService termFaceVersionService;
     private final IPtArcFaceFeatureService arcFaceFeatureService;
+    private final IPtArcFaceKeyService arcFaceKeyService;
+    private final FaceEngineService faceEngineService;
+
+    @DubboReference
+    private final RemoteUserAccountService remoteUserAccountService;
+
+    /**
+     * 全量下载人脸特征数据
+     *
+     * @return 全是数据
+     */
+    public List<YcFaceFeatureVo> getAllFeatureDataUser() {
+        List<PtArcFaceFeatureVo> faceFeatureVoList = arcFaceFeatureService.getAllFeatureDataUser();
+        return MapstructUtils.convert(faceFeatureVoList, YcFaceFeatureVo.class);
+    }
 
+    /**
+     * 获取设备的增量人脸特征数据
+     *
+     * @param termNo 设备机号
+     * @return 增量数据
+     */
     public List<YcFaceFeatureVo> getIncrementFeatureDataUser(Integer termNo) {
         Long versionTime = termFaceVersionService.getFaceVersionByTerm(termNo);
-        if(ObjectUtil.isEmpty(versionTime) || versionTime==0L){
+        if (ObjectUtil.isEmpty(versionTime) || versionTime == 0L) {
             // 如果没有该值或为0,那么认为是初次调用,因此返回全量数据
-            return List.of();
+            this.getAllFeatureDataUser();
         }
 
         Date lastDown = DateUtil.date(versionTime);
         List<PtArcFaceFeatureVo> faceFeatureVoList = arcFaceFeatureService.getIncrFeatureDataUser(lastDown);
         return MapstructUtils.convert(faceFeatureVoList, YcFaceFeatureVo.class);
     }
+
+    /**
+     * 获取指定人员的人脸特征数据
+     *
+     * @param userId 人员Id
+     * @return 人脸特征数据
+     */
+    public YcFaceFeatureVo getOneFeatureDataUser(Long userId) {
+        PtArcFaceFeatureVo vo = arcFaceFeatureService.getOneFeatureDataUser(userId);
+        // 人脸照片
+        ImageInfo imageInfo = null;
+        // 获取人员照片地址
+        RemoteUserAccountVo accountVo = remoteUserAccountService.getUserAccountVoById(userId);
+        String photoUrl = ObjectUtil.isEmpty(accountVo.getPhoto()) ? "" : accountVo.getPhoto();
+
+        // 如果不存在或者已更新,则重新生成特征码
+        if (ObjectUtil.isEmpty(vo) || ObjectUtil.notEqual(photoUrl, vo.getPhotoUrl())) {
+            String fileUrl = photoUrl;
+            File imageFile = new File(fileUrl);
+            if (!imageFile.exists()) {
+                return null;
+            }
+            imageInfo = getRGBData(imageFile);
+        }
+        if (imageInfo == null) {
+            return null;
+        }
+        // 生成特征码数据
+        String featureData = faceEngineService.createFeatureData(imageInfo);
+        if (ObjectUtil.isEmpty(featureData)) {
+            return null;
+        }
+        if (ObjectUtil.isEmpty(vo)) {
+            PtArcFaceFeatureBo bo = new PtArcFaceFeatureBo();
+            bo.setUserId(userId);
+            bo.setPhoto(photoUrl);
+            bo.setFeatureData(featureData);
+            arcFaceFeatureService.insertByBo(bo);
+        } else if (ObjectUtil.notEqual(photoUrl, vo.getPhotoUrl())) {
+            arcFaceFeatureService.updateFeatureDataByUserId(userId, featureData, photoUrl);
+        }
+        // 重新从数据中获取数据
+        vo = arcFaceFeatureService.getOneFeatureDataUser(userId);
+        return MapstructUtils.convert(vo, YcFaceFeatureVo.class);
+    }
+
+    /**
+     * 根据设备序列号获取设备和人脸识别授权码
+     *
+     * @param termSn 设备机号
+     * @return 人脸识别授权码
+     */
+    public String getArcFaceKeyByTermSn(String termSn) {
+        return arcFaceKeyService.getArcFaceKeyByTermSn(termSn);
+    }
+
+    /**
+     * 更新指定设备的人脸识别授权码
+     *
+     * @param termSn     设备机号
+     * @param arcFaceKey 人脸识别授权码
+     * @return 受影响记录条数
+     */
+    public Integer updateTermArcFaceKey(String termSn, String arcFaceKey) {
+        return arcFaceKeyService.updateTermArcFaceKey(termSn, arcFaceKey);
+    }
+
+    /**
+     * 更新指定设备的人脸特征下载时间
+     *
+     * @param termNo      设备机号
+     * @param versionTime 版本时间
+     * @return 受影响纪录条数
+     */
+    public Integer updateFaceVersionByTerm(Integer termNo, Long versionTime) {
+        return termFaceVersionService.updateFaceVersionByTerm(termNo, versionTime);
+    }
 }

+ 32 - 0
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/config/ArcFaceConfig.java

@@ -0,0 +1,32 @@
+package org.dromara.server.consume.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Configuration class for ArcFace SDK, used to manage and provide configuration properties required for the initialization and operation of the
+ * ArcFace face recognition engine.
+ * This class is annotated with {@link Configuration} and {@link ConfigurationProperties} to allow it to be automatically loaded by Spring and bind
+ * the application's configuration properties to its fields.
+ * The properties are prefixed with "arcconfig.arcface-sdk" in the application's configuration file.
+ */
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "arcconfig.arcface-sdk")
+public class ArcFaceConfig {
+    // 获取配置参数
+    public String sdkLibPath = "";
+
+    public String appId = "";
+
+    public String sdkKey = "";
+
+    public Integer detectPooSize = 5;
+
+    public Integer comparePooSize = 5;
+
+    // 离线激活文件
+    public String activeFile = "";
+
+}

+ 64 - 0
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/config/FaceEngineFactory.java

@@ -0,0 +1,64 @@
+package org.dromara.server.consume.config;
+
+import com.arcsoft.face.EngineConfiguration;
+import com.arcsoft.face.FaceEngine;
+import com.arcsoft.face.enums.ErrorInfo;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.pool2.BasePooledObjectFactory;
+import org.apache.commons.pool2.PooledObject;
+import org.apache.commons.pool2.impl.DefaultPooledObject;
+import org.dromara.common.core.exception.ServiceException;
+
+/**
+ * @author flysheep
+ * @date 2020/7/27 0027
+ * @time 15:31
+ */
+@Slf4j
+public class FaceEngineFactory extends BasePooledObjectFactory<FaceEngine> {
+    private final String libPath;
+    private final String appId;
+    private final String sdkKey;
+    private final EngineConfiguration engineConfiguration;
+    private String activeKey;
+    private String activeFile;
+
+    public FaceEngineFactory(String libPath, String appId, String sdkKey, String activeKey,
+                             EngineConfiguration engineConfiguration, String activeFile) {
+        this.appId = appId;
+        this.sdkKey = sdkKey;
+        this.activeKey = activeKey;
+        this.libPath = libPath;
+        this.engineConfiguration = engineConfiguration;
+        this.activeFile = activeFile;
+    }
+
+    @Override
+    public FaceEngine create() {
+
+        FaceEngine faceEngine = new FaceEngine(libPath);
+        int activeCode = faceEngine.activeOnline(appId, sdkKey);
+        if (activeCode != ErrorInfo.MOK.getValue() && activeCode != ErrorInfo.MERR_ASF_ALREADY_ACTIVATED.getValue()) {
+            log.error("引擎激活失败{}", activeCode);
+            throw new ServiceException("引擎激活失败: " + activeCode);
+        }
+        int initCode = faceEngine.init(engineConfiguration);
+        if (initCode != ErrorInfo.MOK.getValue()) {
+            log.error("引擎初始化失败{}", initCode);
+            throw new ServiceException("引擎初始化失败: " + initCode);
+        }
+        return faceEngine;
+    }
+
+    @Override
+    public PooledObject<FaceEngine> wrap(FaceEngine faceEngine) {
+        return new DefaultPooledObject<>(faceEngine);
+    }
+
+    @Override
+    public void destroyObject(PooledObject<FaceEngine> pool) throws Exception {
+        FaceEngine faceEngine = pool.getObject();
+        int result = faceEngine.unInit();
+        super.destroyObject(pool);
+    }
+}

+ 93 - 8
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/controller/v1/FaceCollectController.java

@@ -1,14 +1,14 @@
 package org.dromara.server.consume.controller.v1;
 
+import cn.hutool.core.util.ObjectUtil;
 import lombok.RequiredArgsConstructor;
 import org.dromara.common.core.api.ReturnResult;
+import org.dromara.common.core.utils.StringUtils;
 import org.dromara.server.consume.business.ArcFaceBusiness;
 import org.dromara.server.consume.domain.vo.YcFaceFeatureVo;
-import org.dromara.server.consume.service.IFaceEngineService;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -30,12 +30,10 @@ public class FaceCollectController {
     private final ArcFaceBusiness faceBusiness;
     /**
      * 增量下载人脸特征数据
-     * 建议设备每10分钟,从后台获取一次增量数据
-     * 后台会根据设备当前的人脸权限版本号,来取出有更新的人脸数据(若人脸版本号不存在,则返回全量人脸数据)
      *
      * @param termNo 设备机号
      *
-     * @return
+     * @return 人脸数据
      */
     @GetMapping(value = "/incrFeature/{termNo}")
     public ReturnResult getIncrementFeatureData(@PathVariable("termNo") Integer termNo) {
@@ -46,4 +44,91 @@ public class FaceCollectController {
 
         return new ReturnResult(true,1, "获取增量数据成功!", featureData);
     }
+
+    /**
+     * 全量下载人脸特征数据
+     *
+     * @return 人脸数据
+     */
+    @GetMapping(value = "/allFeature")
+    public ReturnResult getAllFeatureDataUser() {
+        List<YcFaceFeatureVo> featureData = faceBusiness.getAllFeatureDataUser();
+        if (featureData == null) {
+            featureData = new ArrayList<>();
+        }
+
+        return new ReturnResult(true, 1, "获取全量数据成功!", featureData);
+    }
+
+    /**
+     * 更新设备的权限版本值
+     *
+     * @param termNo      机号
+     * @param versionTime 最后请求人脸数据的时间戳
+     * @return 更新结果
+     */
+    @PostMapping("/faceVersion/{termNo}/{versionTime}")
+    public ReturnResult updateFaceVersionByTerm(@PathVariable("termNo") Integer termNo,
+                                                @PathVariable("versionTime") Long versionTime) {
+        int iCount = faceBusiness.updateFaceVersionByTerm(termNo, versionTime);
+        if (iCount < 0) {
+            return new ReturnResult(false, 1, "更新设备授权版本失败!", iCount);
+        }
+        return new ReturnResult(true, 1, "更新设备授权版本失败!", iCount);
+    }
+
+    /**
+     * 获取一个虹软人脸识别授权码
+     * 先从termSn查询已存在的key(设备app卸载重装的情况),若无注册,则取一个最新的
+     * termSn
+     *
+     * @return 授权码
+     */
+    @GetMapping(value = "/getArcFaceKey/{termSn}")
+    public ReturnResult getArcFaceKey(@PathVariable("termSn") String termSn) {
+
+        String key = faceBusiness.getArcFaceKeyByTermSn(termSn);
+
+        if (StringUtils.isEmpty(key)) {
+            return new ReturnResult(false, 1, "获取人脸key失败!", null);
+        }
+        return new ReturnResult(true, 1, "获取人脸key成功!", key);
+    }
+
+    /**
+     * 更新安卓设备注册人脸授权码的绑定关系
+     *
+     * @param termSn 设备序列号
+     * @param key    授权码的值
+     * @return 更新结果
+     */
+    @PostMapping("/useArcFaceKey/{termSn}/{key}")
+    public ReturnResult updateArcFaceKeyUse(@PathVariable("termSn") String termSn,
+                                            @PathVariable("key") String key) {
+
+        int result = faceBusiness.updateTermArcFaceKey(termSn, key);
+        if (result <= 0) {
+            return new ReturnResult(false, 1, "更新设备授权码失败!", result);
+        }
+        return new ReturnResult(true, 1, "更新设备授权码失败!", result);
+    }
+
+    /**
+     * 获取单用户人脸特征码信息
+     * 若没注册,则去找到图片并生成特征码
+     *
+     * @param userId 人员Id
+     * @return 人脸特征
+     */
+    @GetMapping("/singleFeature/{userId}")
+    public Object getOneFeatureDataUser(@PathVariable("userId") Long userId) {
+        if (ObjectUtil.isEmpty(userId)) {
+            return new ResponseEntity<Object>(new ReturnResult(false, 1, "传入的userId为null", null), HttpStatus.NOT_FOUND);
+        }
+        YcFaceFeatureVo vo = faceBusiness.getOneFeatureDataUser(userId);
+        if (ObjectUtil.isEmpty(vo)) {
+            return new ResponseEntity<Object>(new ReturnResult(false, 1, "图片信息有误,请重新上传", null), HttpStatus.NOT_FOUND);
+        }
+        return vo;
+    }
 }

+ 14 - 0
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/mapper/PtArcFaceFeatureMapper.java

@@ -19,5 +19,19 @@ import java.util.List;
  * @since JDK 1.8
  */
 public interface PtArcFaceFeatureMapper extends BaseMapperPlus<PtArcFaceFeature, PtArcFaceFeatureVo> {
+    /**
+     * 获取指定设备的增量人脸特征数据
+     *
+     * @param lastTime 上次获取时间
+     * @return 增量数据
+     */
     List<PtArcFaceFeatureVo> getIncrementFeatureDataUser(@Param("lastTime") Date lastTime);
+
+    /**
+     * 获取全量人脸特征数据
+     *
+     * @return 全量数据
+     */
+
+    List<PtArcFaceFeatureVo> getAllFeatureDataUser();
 }

+ 1 - 0
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/mapper/PtArcFaceKeyMapper.java

@@ -15,4 +15,5 @@ import org.dromara.server.consume.domain.vo.PtArcFaceKeyVo;
  * @since JDK 1.8
  */
 public interface PtArcFaceKeyMapper extends BaseMapperPlus<PtArcFaceKey, PtArcFaceKeyVo> {
+
 }

+ 27 - 0
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/FaceEngineService.java

@@ -0,0 +1,27 @@
+package org.dromara.server.consume.service;
+
+import com.arcsoft.face.FaceInfo;
+import com.arcsoft.face.toolkit.ImageInfo;
+
+import java.util.List;
+
+
+/**
+ * name: FaceEngineService
+ * package: org.dromara.server.consume.service
+ * description: 虹软人脸误别服务
+ * date: 2025-02-26 16:10:01 16:10
+ *
+ * @author luoyibo
+ * @version 0.1
+ * @since JDK 1.8
+ */
+public interface FaceEngineService {
+
+    // 检测脸部信息
+    List<FaceInfo> detectFaces(ImageInfo imageInfo);
+
+    // 生成特征码
+    String createFeatureData(ImageInfo imageInfo);
+
+}

+ 0 - 25
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/IFaceEngineService.java

@@ -1,25 +0,0 @@
-package org.dromara.server.consume.service;
-
-import org.dromara.server.consume.domain.vo.YcFaceFeatureVo;
-
-import java.util.List;
-
-/**
- * name: IFaceEngineService
- * package: org.dromara.server.consume.service
- * description: 人脸消费相关服务
- * date: 2025-02-26 14:03:43 14:03
- *
- * @author luoyibo
- * @version 0.1
- * @since JDK 1.8
- */
-public interface IFaceEngineService {
-
-    /**
-     * 增量获取人脸特征数据
-     * @param termNo 消费机编号
-     * @return 人脸特征数据
-     */
-    List<YcFaceFeatureVo> getIncrementFeatureDataUser(Integer termNo);
-}

+ 33 - 0
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/IPtArcFaceFeatureService.java

@@ -24,4 +24,37 @@ public interface IPtArcFaceFeatureService {
      * @return 人脸特征数据
      */
     List<PtArcFaceFeatureVo> getIncrFeatureDataUser(Date lastTime);
+
+    /**
+     * 获取全量人脸特征数据
+     *
+     * @return 全量数据
+     */
+    List<PtArcFaceFeatureVo> getAllFeatureDataUser();
+
+    /**
+     * 获取指定人员的人脸特征数据
+     *
+     * @param userId 人员Id
+     * @return 人脸特征数据
+     */
+    PtArcFaceFeatureVo getOneFeatureDataUser(Long userId);
+
+    /**
+     * 更新指定用户的人脸特征数据和照片URL。
+     *
+     * @param userId      用户ID
+     * @param featureData 人脸特征数据
+     * @param photoUrl    照片URL
+     * @return 更新操作的结果,成功返回1,失败返回0或负数
+     */
+    Integer updateFeatureDataByUserId(Long userId, String featureData, String photoUrl);
+
+    /**
+     * 插入人脸特征业务对象到数据库。
+     *
+     * @param bo 人脸特征业务对象,包含特征Id、人员Id、人员照片地址和人脸特征数据等信息
+     * @return 插入操作的结果,成功返回1,失败返回0或负数
+     */
+    Integer insertByBo(PtArcFaceFeatureBo bo);
 }

+ 16 - 0
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/IPtArcFaceKeyService.java

@@ -11,4 +11,20 @@ package org.dromara.server.consume.service;
  * @since JDK 1.8
  */
 public interface IPtArcFaceKeyService {
+    /**
+     * 根据设备序列号获取设备和人脸识别授权码
+     *
+     * @param termSn 设备机号
+     * @return 人脸识别授权码
+     */
+    String getArcFaceKeyByTermSn(String termSn);
+
+    /**
+     * 更新指定设备的人脸识别授权码
+     *
+     * @param termSn     设备机号
+     * @param arcFaceKey 人脸识别授权码
+     * @return 受影响记录条数
+     */
+    Integer updateTermArcFaceKey(String termSn, String arcFaceKey);
 }

+ 9 - 0
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/IPtTermFaceVersionService.java

@@ -18,4 +18,13 @@ public interface IPtTermFaceVersionService {
      * @return 版本时间
      */
     Long getFaceVersionByTerm(Integer termNo);
+
+    /**
+     * 更新指定设备的人脸特征下载时间
+     *
+     * @param termNo      设备机号
+     * @param versionTime 版本时间
+     * @return 受影响纪录条数
+     */
+    Integer updateFaceVersionByTerm(Integer termNo, Long versionTime);
 }

+ 114 - 29
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/impl/FaceEngineServiceImpl.java

@@ -1,44 +1,129 @@
 package org.dromara.server.consume.service.impl;
 
-import cn.hutool.core.date.DateUtil;
-import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.codec.Base64;
+import com.arcsoft.face.*;
+import com.arcsoft.face.enums.DetectMode;
+import com.arcsoft.face.enums.DetectOrient;
+import com.arcsoft.face.toolkit.ImageInfo;
 import lombok.RequiredArgsConstructor;
-import org.dromara.server.consume.domain.vo.YcFaceFeatureVo;
-import org.dromara.server.consume.service.IFaceEngineService;
-import org.dromara.server.consume.service.IPtTermFaceVersionService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.pool2.impl.GenericObjectPool;
+import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
+import org.dromara.server.consume.config.ArcFaceConfig;
+import org.dromara.server.consume.config.FaceEngineFactory;
+import org.dromara.server.consume.service.FaceEngineService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
 
-import java.util.Date;
+import javax.annotation.PostConstruct;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
- * name: FaceEngineServiceImpl
- * package: org.dromara.server.consume.service.impl
- * description: 人脸消费相关服务实现
- * date: 2025-02-26 14:08:27 14:08
- *
- * @author luoyibo
- * @version 0.1
- * @since JDK 1.8
+ * @author flysheep
+ * @date 2020/7/27 0027
+ * @time 15:40
  */
-@RequiredArgsConstructor
 @Service
-public class FaceEngineServiceImpl implements IFaceEngineService {
-    private final IPtTermFaceVersionService termFaceVersionService;
-    /**
-     * 增量获取人脸特征数据
-     * @param termNo 消费机编号
-     * @return 人脸特征数据
-     */
+@Slf4j
+@RequiredArgsConstructor
+public class FaceEngineServiceImpl implements FaceEngineService {
+    public final static Logger logger = LoggerFactory.getLogger(FaceEngineServiceImpl.class);
+    private final ArcFaceConfig arcFaceConfig;
+    // 通用人脸识别引擎池
+    private GenericObjectPool<FaceEngine> faceEngineGeneralPool;
+
+    // 引擎配置
+    @PostConstruct
+    public void init() {
+        GenericObjectPoolConfig detectPoolConfig = new GenericObjectPoolConfig();
+        detectPoolConfig.setMaxIdle(arcFaceConfig.getDetectPooSize());
+        detectPoolConfig.setMaxTotal(arcFaceConfig.getDetectPooSize());
+        detectPoolConfig.setMinIdle(arcFaceConfig.getDetectPooSize());
+        detectPoolConfig.setLifo(false);
+        EngineConfiguration detectCfg = new EngineConfiguration();
+        FunctionConfiguration detectFunctionCfg = new FunctionConfiguration();
+        detectFunctionCfg.setSupportFaceDetect(true);// 开启人脸检测功能
+        detectFunctionCfg.setSupportFaceRecognition(true);// 开启人脸识别功能
+        detectFunctionCfg.setSupportAge(true);// 开启年龄检测功能
+        detectFunctionCfg.setSupportGender(true);// 开启性别检测功能
+        detectFunctionCfg.setSupportLiveness(true);// 开启活体检测功能
+
+        detectCfg.setFunctionConfiguration(detectFunctionCfg);
+        detectCfg.setDetectMode(DetectMode.ASF_DETECT_MODE_IMAGE);// 图片检测模式,如果是连续帧的视频流图片,那么改成VIDEO模式
+        detectCfg.setDetectFaceOrientPriority(DetectOrient.ASF_OP_0_ONLY);// 人脸旋转角度
+        faceEngineGeneralPool = new GenericObjectPool(new FaceEngineFactory(arcFaceConfig.getSdkLibPath(),
+                                                                            arcFaceConfig.getAppId(), arcFaceConfig.getSdkKey(), null, detectCfg,
+                                                                            arcFaceConfig.activeFile), detectPoolConfig);// 底层库算法对象池
+
+    }
+
+    // 人脸检测
     @Override
-    public List<YcFaceFeatureVo> getIncrementFeatureDataUser(Integer termNo) {
-        Long versionTime = termFaceVersionService.getFaceVersionByTerm(termNo);
-        if(ObjectUtil.isEmpty(versionTime) || versionTime==0L){
-            // 如果没有该值或为0,那么认为是初次调用,因此返回全量数据
-            return List.of();
+    public List<FaceInfo> detectFaces(ImageInfo imageInfo) {
+        // 参数判断
+        if (imageInfo == null)
+            return null;
+        // FaceEngine人脸引擎类
+        FaceEngine faceEngine = null;
+        try {
+            // 这里进行获取
+            faceEngine = faceEngineGeneralPool.borrowObject();
+            if (faceEngine == null) {
+                return null;
+            }
+            List<FaceInfo> faceInfoList = new ArrayList<>();
+            // 我们进行人脸检测
+
+            int errorCode = faceEngine.detectFaces(imageInfo.getImageData(), imageInfo.getWidth(),
+                                                   imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList);
+            if (errorCode == 0) {
+                return faceInfoList;
+            }
+        } catch (Exception e) {
+            logger.error(e.getMessage());
+            logger.error(Arrays.toString(e.getStackTrace()));
+        } finally {
+            if (faceEngine != null) {
+                // 释放引擎对象
+                faceEngineGeneralPool.returnObject(faceEngine);
+            }
         }
+        return null;
+    }
 
-        Date lastDown = DateUtil.date(versionTime);
-        return List.of();
+    // 生成特征码
+    // 这里还需要改,关于图片处理
+    @Override
+    public String createFeatureData(ImageInfo imageInfo) {
+        // 获取脸部算法
+        // FaceEngine人脸引擎类
+        FaceEngine faceEngine = null;
+        try {
+            // 这里进行获取人脸引擎
+            faceEngine = faceEngineGeneralPool.borrowObject();
+            List<FaceInfo> faceInfoList = new ArrayList<FaceInfo>();
+            // 人脸检测返回值
+            int code = faceEngine.detectFaces(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(),
+                                              imageInfo.getImageFormat(), faceInfoList);
+            if (code == 0 && !faceInfoList.isEmpty()) {
+                FaceFeature faceFeature = new FaceFeature();
+                faceEngine.extractFaceFeature(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(),
+                                              imageInfo.getImageFormat(), faceInfoList.get(0), faceFeature);
+                return Base64.encode(faceFeature.getFeatureData());
+            }
+        } catch (Exception e) {
+            logger.error(e.getMessage());
+            logger.error(Arrays.toString(e.getStackTrace()));
+        } finally {
+            if (faceEngine != null) {
+                // 释放引擎对象
+                faceEngineGeneralPool.returnObject(faceEngine);
+            }
+        }
+        return null;
     }
+
 }

+ 65 - 0
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/impl/PtArcFaceFeatureServiceImpl.java

@@ -1,6 +1,10 @@
 package org.dromara.server.consume.service.impl;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.server.consume.domain.PtArcFaceFeature;
 import org.dromara.server.consume.domain.bo.PtArcFaceFeatureBo;
 import org.dromara.server.consume.domain.vo.PtArcFaceFeatureVo;
 import org.dromara.server.consume.mapper.PtArcFaceFeatureMapper;
@@ -25,8 +29,69 @@ import java.util.List;
 public class PtArcFaceFeatureServiceImpl implements IPtArcFaceFeatureService {
     private final PtArcFaceFeatureMapper baseMapper;
 
+    /**
+     * 获取上次下载后更新的人脸特征数据
+     *
+     * @param lastTime 上次更新时间
+     * @return 人脸特征数据
+     */
     @Override
     public List<PtArcFaceFeatureVo> getIncrFeatureDataUser(Date lastTime) {
         return baseMapper.getIncrementFeatureDataUser(lastTime);
     }
+
+    /**
+     * 获取全量人脸特征数据
+     *
+     * @return 全量数据
+     */
+    @Override
+    public List<PtArcFaceFeatureVo> getAllFeatureDataUser() {
+        return baseMapper.getAllFeatureDataUser();
+    }
+
+    /**
+     * 获取指定人员的人脸特征数据
+     *
+     * @param userId 人员Id
+     * @return 人脸特征数据
+     */
+    @Override
+    public PtArcFaceFeatureVo getOneFeatureDataUser(Long userId) {
+        return baseMapper.selectVoOne(new LambdaQueryWrapper<PtArcFaceFeature>()
+                                          .eq(PtArcFaceFeature::getUserId, userId));
+    }
+
+    /**
+     * 更新指定用户的人脸特征数据和照片URL。
+     *
+     * @param userId      用户ID
+     * @param featureData 人脸特征数据
+     * @param photoUrl    照片URL
+     * @return 更新操作的结果,成功返回1,失败返回0或负数
+     */
+    @Override
+    public Integer updateFeatureDataByUserId(Long userId, String featureData, String photoUrl) {
+        LambdaUpdateWrapper<PtArcFaceFeature> luw = new LambdaUpdateWrapper<PtArcFaceFeature>()
+                                                        .set(PtArcFaceFeature::getFeatureData, featureData)
+                                                        .set(PtArcFaceFeature::getPhoto, photoUrl)
+                                                        .eq(PtArcFaceFeature::getUserId, userId);
+        return baseMapper.update(null, luw);
+    }
+
+    /**
+     * 插入人脸特征业务对象到数据库。
+     *
+     * @param bo 人脸特征业务对象,包含特征Id、人员Id、人员照片地址和人脸特征数据等信息
+     * @return 插入操作的结果,成功返回1,失败返回0或负数
+     */
+    @Override
+    public Integer insertByBo(PtArcFaceFeatureBo bo) {
+        PtArcFaceFeature entity = MapstructUtils.convert(bo, PtArcFaceFeature.class);
+        int iCount = baseMapper.insert(entity);
+        if (iCount > 0) {
+            bo.setFeatureId(entity != null ? entity.getFeatureId() : null);
+        }
+        return iCount;
+    }
 }

+ 45 - 0
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/impl/PtArcFaceKeyServiceImpl.java

@@ -1,9 +1,17 @@
 package org.dromara.server.consume.service.impl;
 
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import lombok.RequiredArgsConstructor;
+import org.dromara.server.consume.domain.PtArcFaceKey;
+import org.dromara.server.consume.domain.vo.PtArcFaceKeyVo;
+import org.dromara.server.consume.mapper.PtArcFaceKeyMapper;
 import org.dromara.server.consume.service.IPtArcFaceKeyService;
 import org.springframework.stereotype.Service;
 
+import java.util.List;
+
 /**
  * name: PtArcFaceKeyServiceImpl
  * package: org.dromara.server.consume.service.impl
@@ -17,4 +25,41 @@ import org.springframework.stereotype.Service;
 @RequiredArgsConstructor
 @Service
 public class PtArcFaceKeyServiceImpl implements IPtArcFaceKeyService {
+    private final PtArcFaceKeyMapper baseMapper;
+
+    /**
+     * 根据设备序列号获取设备和人脸识别授权码
+     *
+     * @param termSn 设备机号
+     * @return 人脸识别授权码
+     */
+    @Override
+    public String getArcFaceKeyByTermSn(String termSn) {
+        List<PtArcFaceKeyVo> list = baseMapper.selectVoList();
+        PtArcFaceKeyVo vo = list.stream().filter(p -> p.getTermSn().equals(termSn))
+                                .findFirst().orElse(null);
+        if (ObjectUtil.isEmpty(vo)) {
+            vo = list.stream().filter(p -> ObjectUtil.isEmpty(p.getTermSn()))
+                     .findFirst().orElse(null);
+        }
+        if (vo != null) {
+            return vo.getActiveKey();
+        }
+        return "";
+    }
+
+    /**
+     * 更新指定设备的人脸识别授权码
+     *
+     * @param termSn     设备机号
+     * @param arcFaceKey 人脸识别授权码
+     * @return 受影响记录条数
+     */
+    @Override
+    public Integer updateTermArcFaceKey(String termSn, String arcFaceKey) {
+        return baseMapper.update(null, new LambdaUpdateWrapper<PtArcFaceKey>()
+                                           .set(PtArcFaceKey::getTermSn, termSn)
+                                           .set(PtArcFaceKey::getRegisterTime, DateUtil.date())
+                                           .eq(PtArcFaceKey::getActiveKey, arcFaceKey));
+    }
 }

+ 25 - 2
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/impl/PtTermFaceVersionServiceImpl.java

@@ -2,12 +2,11 @@ package org.dromara.server.consume.service.impl;
 
 import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import lombok.RequiredArgsConstructor;
 import org.dromara.server.consume.domain.PtTermFaceVersion;
-import org.dromara.server.consume.domain.bo.PtTermFaceVersionBo;
 import org.dromara.server.consume.domain.vo.PtTermFaceVersionVo;
 import org.dromara.server.consume.mapper.PtTermFaceVersionMapper;
-import org.dromara.server.consume.mapper.XfCardLimitedMapper;
 import org.dromara.server.consume.service.IPtTermFaceVersionService;
 import org.springframework.stereotype.Service;
 
@@ -40,4 +39,28 @@ public class PtTermFaceVersionServiceImpl implements IPtTermFaceVersionService {
         }
         return 0L;
     }
+
+    /**
+     * 更新指定设备的人脸特征下载时间
+     *
+     * @param termNo      设备机号
+     * @param versionTime 版本时间
+     * @return 受影响纪录条数
+     */
+    @Override
+    public Integer updateFaceVersionByTerm(Integer termNo, Long versionTime) {
+        PtTermFaceVersionVo vo = baseMapper.selectVoOne(new LambdaQueryWrapper<PtTermFaceVersion>()
+                                                            .eq(PtTermFaceVersion::getTermNo, termNo));
+        if (ObjectUtil.isEmpty(vo)) {
+            PtTermFaceVersion entity = new PtTermFaceVersion();
+            entity.setVersionTime(versionTime);
+            entity.setTermNo(Long.valueOf(termNo));
+            return baseMapper.insert(entity);
+        } else {
+            LambdaUpdateWrapper<PtTermFaceVersion> luw = new LambdaUpdateWrapper<PtTermFaceVersion>()
+                                                             .set(PtTermFaceVersion::getVersionTime, versionTime)
+                                                             .eq(PtTermFaceVersion::getTermNo, termNo);
+            return baseMapper.update(null, luw);
+        }
+    }
 }

+ 9 - 10
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/impl/XfTermServiceImpl.java

@@ -22,6 +22,7 @@ import org.dromara.server.consume.service.IXfTermService;
 import org.springframework.stereotype.Service;
 
 import java.util.List;
+import java.util.Objects;
 
 /**
  * 消费设备Service业务层处理
@@ -103,20 +104,18 @@ public class XfTermServiceImpl implements IXfTermService {
      * @return 消费设备设备
      */
     @Override
-
     public XfTermVo queryVoOneByNo(Long termNo) {
-        // XfTermVo vo;
-        // List<XfTermVo> redisList = RedisUtils.getCacheList(CacheNames.PT_TERM);
-        // if (CollectionUtil.isNotEmpty(redisList)) {
-        //     vo = redisList.stream().filter(p -> Objects.equals(p.getTermNo(), termNo)).findFirst().orElse(null);
-        //     if (ObjUtil.isNotNull(vo)) {
-        //         return vo;
-        //     }
-        // }
+        XfTermVo vo;
+        List<XfTermVo> redisList = RedisUtils.getCacheList(CacheNames.PT_TERM_LIST);
+        if (CollectionUtil.isNotEmpty(redisList)) {
+            vo = redisList.stream().filter(p -> Objects.equals(p.getTermNo(), termNo)).findFirst().orElse(null);
+            if (ObjUtil.isNotNull(vo)) {
+                return vo;
+            }
+        }
         XfTermBo bo = new XfTermBo();
         bo.setTermNo(termNo);
         bo.setTenantId(defaultConfig.getTenantId());
-
         return this.queryVoOneByBo(bo);
     }
 

+ 17 - 1
ruoyi-server/ruoyi-server-consume/src/main/resources/mapper/consume/PtArcFaceFeatureMapper.xml

@@ -20,6 +20,22 @@
     <select id="getIncrementFeatureDataUser" resultType="org.dromara.server.consume.domain.vo.PtArcFaceFeatureVo">
         SELECT tpua.user_id,tpua.user_no,tpua.user_numb,tpua.real_name,tpaff.photo_url,tpaff.feature_data,tpaff.feature_id FROM t_pt_arcFaceFeature tpaff
         INNER JOIN t_pt_userAccount tpua ON tpua.user_id=tpaff.user_id
-        WHERE tpaff.update_time>#{lastTime}
+        WHERE tpaff.update_time > #{lastTime}
+          AND tpua.del_flag = '0'
+        ORDER BY tpua.user_no
+    </select>
+
+    <select id="getAllFeatureDataUser" resultType="org.dromara.server.consume.domain.vo.PtArcFaceFeatureVo">
+        SELECT tpua.user_id,
+               tpua.user_no,
+               tpua.user_numb,
+               tpua.real_name,
+               tpaff.photo_url,
+               tpaff.feature_data,
+               tpaff.feature_id
+        FROM t_pt_arcFaceFeature tpaff
+                 INNER JOIN t_pt_userAccount tpua ON tpua.user_id = tpaff.user_id
+        WHERE tpua.del_flag = '0'
+        ORDER BY tpua.user_no
     </select>
 </mapper>