HP 2 сар өмнө
parent
commit
bef9936e89
100 өөрчлөгдсөн 4747 нэмэгдсэн , 700 устгасан
  1. 1 0
      ArcFace64.dat
  2. 14 5
      ktg-admin/pom.xml
  3. 3 3
      ktg-admin/src/main/java/com/ktg/GuoRuanApplication.java
  4. 3 3
      ktg-admin/src/main/java/com/ktg/GuoRuanServletInitializer.java
  5. 200 68
      ktg-admin/src/main/java/com/ktg/web/controller/common/CommonController.java
  6. 34 0
      ktg-admin/src/main/java/com/ktg/web/controller/common/OrderController.java
  7. 128 0
      ktg-admin/src/main/java/com/ktg/web/controller/iscs/ResApiController.java
  8. 8 0
      ktg-admin/src/main/java/com/ktg/web/controller/iscs/SysTeamController.java
  9. 8 54
      ktg-admin/src/main/java/com/ktg/web/controller/monitor/SysUserOnlineController.java
  10. 97 0
      ktg-admin/src/main/java/com/ktg/web/controller/system/NotifyMessageController.java
  11. 91 0
      ktg-admin/src/main/java/com/ktg/web/controller/system/NotifyTemplateController.java
  12. 7 8
      ktg-admin/src/main/java/com/ktg/web/controller/system/SysAutoCodePartController.java
  13. 1 1
      ktg-admin/src/main/java/com/ktg/web/controller/system/SysAutoCodeRuleController.java
  14. 81 23
      ktg-admin/src/main/java/com/ktg/web/controller/system/SysLoginController.java
  15. 21 11
      ktg-admin/src/main/java/com/ktg/web/controller/system/SysProfileController.java
  16. 45 61
      ktg-admin/src/main/java/com/ktg/web/controller/system/SysRoleController.java
  17. 116 0
      ktg-admin/src/main/java/com/ktg/web/controller/system/SysUploadFileController.java
  18. 150 0
      ktg-admin/src/main/java/com/ktg/web/controller/system/SysUserCharacteristicController.java
  19. 231 24
      ktg-admin/src/main/java/com/ktg/web/controller/system/SysUserController.java
  20. 12 9
      ktg-admin/src/main/resources/application-druid.yml
  21. 68 9
      ktg-admin/src/main/resources/application.yml
  22. 2 2
      ktg-admin/src/main/resources/banner.txt
  23. 50 3
      ktg-common/pom.xml
  24. 22 0
      ktg-common/src/main/java/com/ktg/common/annotation/MarsDataScope.java
  25. 14 1
      ktg-common/src/main/java/com/ktg/common/config/RuoYiConfig.java
  26. 9 1
      ktg-common/src/main/java/com/ktg/common/constant/Constants.java
  27. 3 4
      ktg-common/src/main/java/com/ktg/common/constant/UserConstants.java
  28. 24 5
      ktg-common/src/main/java/com/ktg/common/core/domain/BaseEntity.java
  29. 31 6
      ktg-common/src/main/java/com/ktg/common/core/domain/entity/SysRole.java
  30. 94 20
      ktg-common/src/main/java/com/ktg/common/core/domain/entity/SysUser.java
  31. 27 2
      ktg-common/src/main/java/com/ktg/common/core/domain/model/BaseBean.java
  32. 19 0
      ktg-common/src/main/java/com/ktg/common/core/domain/model/FingerprintLoginBody.java
  33. 11 8
      ktg-common/src/main/java/com/ktg/common/core/domain/model/LoginUser.java
  34. 15 7
      ktg-common/src/main/java/com/ktg/common/core/redis/RedisCache.java
  35. 96 56
      ktg-common/src/main/java/com/ktg/common/utils/DateUtils.java
  36. 20 0
      ktg-common/src/main/java/com/ktg/common/utils/FileDriveLetterUtils.java
  37. 119 0
      ktg-common/src/main/java/com/ktg/common/utils/FingerprintComparisonByDat.java
  38. 110 0
      ktg-common/src/main/java/com/ktg/common/utils/FingerprintComparisonByImg.java
  39. 63 0
      ktg-common/src/main/java/com/ktg/common/utils/PDFToImgUtil.java
  40. 344 0
      ktg-common/src/main/java/com/ktg/common/utils/face/ArcSoftMothodUtil.java
  41. 156 0
      ktg-common/src/main/java/com/ktg/common/utils/face/FaceCutUtil.java
  42. 195 0
      ktg-common/src/main/java/com/ktg/common/utils/face/FaceCutUtil1.java
  43. 170 0
      ktg-common/src/main/java/com/ktg/common/utils/face/FaceEngineTest.java
  44. 111 0
      ktg-common/src/main/java/com/ktg/common/utils/face/FaceImgMatchUtil.java
  45. 138 0
      ktg-common/src/main/java/com/ktg/common/utils/face/FaceMatchUtil.java
  46. 117 0
      ktg-common/src/main/java/com/ktg/common/utils/face/FaceMatchUtil1.java
  47. 46 0
      ktg-common/src/main/java/com/ktg/common/utils/face/MatChangeUtil.java
  48. 8 8
      ktg-common/src/main/java/com/ktg/common/utils/file/FileUploadUtils.java
  49. 62 0
      ktg-common/src/main/java/com/ktg/common/utils/obj/BeanUtils.java
  50. 63 0
      ktg-common/src/main/java/com/ktg/common/utils/obj/ObjectUtils.java
  51. 67 0
      ktg-common/src/main/java/com/ktg/common/utils/obj/PageUtils.java
  52. 61 94
      ktg-common/src/main/java/com/ktg/common/utils/poi/ExcelUtil.java
  53. 58 0
      ktg-common/src/main/java/com/ktg/common/utils/rocketmq/RocketMQAutoConfiguration.java
  54. 140 0
      ktg-common/src/main/java/com/ktg/common/utils/rocketmq/RocketMQConsumerManager.java
  55. 52 0
      ktg-common/src/main/java/com/ktg/common/utils/rocketmq/RocketMQHandlerConfig.java
  56. 135 0
      ktg-common/src/main/java/com/ktg/common/utils/rocketmq/RocketMQProducerManager.java
  57. 50 0
      ktg-common/src/main/java/com/ktg/common/utils/rocketmq/RocketMQProperties.java
  58. 45 0
      ktg-common/src/main/java/com/ktg/common/vo/FaceCutVO.java
  59. 27 0
      ktg-common/src/main/java/com/ktg/common/vo/FaceMatchVO.java
  60. 39 0
      ktg-common/src/main/java/com/ktg/common/vo/VerificationVO.java
  61. 20 43
      ktg-framework/src/main/java/com/ktg/framework/aspectj/DataScopeAspect.java
  62. 2 2
      ktg-framework/src/main/java/com/ktg/framework/aspectj/DataSourceAspect.java
  63. 6 6
      ktg-framework/src/main/java/com/ktg/framework/aspectj/LogAspect.java
  64. 146 0
      ktg-framework/src/main/java/com/ktg/framework/aspectj/MarsDataScopeAspect.java
  65. 1 1
      ktg-framework/src/main/java/com/ktg/framework/aspectj/RateLimiterAspect.java
  66. 1 1
      ktg-framework/src/main/java/com/ktg/framework/config/ApplicationConfig.java
  67. 2 2
      ktg-framework/src/main/java/com/ktg/framework/config/CaptchaConfig.java
  68. 4 4
      ktg-framework/src/main/java/com/ktg/framework/config/DruidConfig.java
  69. 2 2
      ktg-framework/src/main/java/com/ktg/framework/config/FastJson2JsonRedisSerializer.java
  70. 1 1
      ktg-framework/src/main/java/com/ktg/framework/config/FilterConfig.java
  71. 3 3
      ktg-framework/src/main/java/com/ktg/framework/config/KaptchaTextCreator.java
  72. 15 13
      ktg-framework/src/main/java/com/ktg/framework/config/MyBatisConfig.java
  73. 21 2
      ktg-framework/src/main/java/com/ktg/framework/config/MyMetaObjectHandler.java
  74. 2 2
      ktg-framework/src/main/java/com/ktg/framework/config/RedisConfig.java
  75. 3 3
      ktg-framework/src/main/java/com/ktg/framework/config/ResourcesConfig.java
  76. 13 6
      ktg-framework/src/main/java/com/ktg/framework/config/SecurityConfig.java
  77. 3 3
      ktg-framework/src/main/java/com/ktg/framework/config/ServerConfig.java
  78. 1 1
      ktg-framework/src/main/java/com/ktg/framework/config/ThreadPoolConfig.java
  79. 41 0
      ktg-framework/src/main/java/com/ktg/framework/config/WebMvcConfig.java
  80. 2 2
      ktg-framework/src/main/java/com/ktg/framework/config/properties/DruidProperties.java
  81. 3 3
      ktg-framework/src/main/java/com/ktg/framework/datasource/DynamicDataSource.java
  82. 2 2
      ktg-framework/src/main/java/com/ktg/framework/datasource/DynamicDataSourceContextHolder.java
  83. 2 2
      ktg-framework/src/main/java/com/ktg/framework/interceptor/impl/SameUrlDataInterceptor.java
  84. 3 3
      ktg-framework/src/main/java/com/ktg/framework/manager/AsyncManager.java
  85. 1 1
      ktg-framework/src/main/java/com/ktg/framework/manager/ShutdownManager.java
  86. 4 4
      ktg-framework/src/main/java/com/ktg/framework/manager/factory/AsyncFactory.java
  87. 2 2
      ktg-framework/src/main/java/com/ktg/framework/security/filter/JwtAuthenticationTokenFilter.java
  88. 2 2
      ktg-framework/src/main/java/com/ktg/framework/security/handle/AuthenticationEntryPointImpl.java
  89. 14 15
      ktg-framework/src/main/java/com/ktg/framework/security/handle/LogoutSuccessHandlerImpl.java
  90. 4 4
      ktg-framework/src/main/java/com/ktg/framework/web/domain/Server.java
  91. 2 2
      ktg-framework/src/main/java/com/ktg/framework/web/domain/server/Cpu.java
  92. 2 2
      ktg-framework/src/main/java/com/ktg/framework/web/domain/server/Jvm.java
  93. 2 2
      ktg-framework/src/main/java/com/ktg/framework/web/domain/server/Mem.java
  94. 2 2
      ktg-framework/src/main/java/com/ktg/framework/web/domain/server/Sys.java
  95. 2 2
      ktg-framework/src/main/java/com/ktg/framework/web/domain/server/SysFile.java
  96. 2 2
      ktg-framework/src/main/java/com/ktg/framework/web/exception/GlobalExceptionHandler.java
  97. 36 0
      ktg-framework/src/main/java/com/ktg/framework/web/service/IscsService.java
  98. 6 6
      ktg-framework/src/main/java/com/ktg/framework/web/service/PermissionService.java
  99. 231 47
      ktg-framework/src/main/java/com/ktg/framework/web/service/SysLoginService.java
  100. 4 4
      ktg-framework/src/main/java/com/ktg/framework/web/service/SysPermissionService.java

+ 1 - 0
ArcFace64.dat

@@ -0,0 +1 @@
+EWEPEPEOGMGTELIZJUGECKIUJDBCJTCNISGPBNHLJTJUBHEWGNAKGEGAIOHJDQAJGNCFDRFZJEDMJTGFFXHQJHCPAZJACCJJHUGIAEAWELCPAHJPDUEPAPHZHIEKBRELICAEFGBKBLBQIVAHGKICDPCFIKFOJHFSHQGPJOGXHRBWELDPBJDDAOGUFVJVCFGAIAFJCLJOIGJNDXECATHZEZACCSCYHIDRHRCACUBMAIIWFWDIIYAAFJDSERHMELACAMFPAVGCIJJQECDJAOAKDQJQEAFUDZEBGYIPATDFBPDWCMDNDYCNCLHZEHFGEVETEPCUBJBZFDJTBGEHHUIDGZBOIZEPGFFLEHCGAOHVGJIBBDIDHMFQDZIVDECXDJECIEFECLGZAVBECSCPHRJRCDGMGJINJBDLDLDDBKEZCBDMARISFJBJJFITECGQHFELIDGQGQDSIWIHIEIPCHFHIMASGUFABIFZJPDDDBAZCTIBIREPFCFXFGJJJGJSIUGJJRGJEYCPGZIUBDGLEVADHNCWGLCFIFFXCKIVGEHYHPITCCFYFCGKDBGQGMJNAZJVHGDHAGINCSCJCZGPITESEEJDHWCYCHAGDWDNIHGCCQBIGZHXBLJJFUEOHGEZJKBQEKAXFUFOERCTEMFQIOJAHOJGHKBCIBBAHMBJJTHRIVCXBJDSAQCZCOJUCAICCMFQACFEHEBCFFISBKDLFDFOELJAESFYFMCHAPIWDHBFAEBYAIFZBKJDAKGPFTDIIZDNCIHJEBCBIYGCHIBLISHZGAATAJFHGPBIIDCYATHRBXEYHMCLHMIGCWHJAAFNAVCFGVGTCPCNHRFXHHDQCUAMGWHEDKCNAVIEGVIJDWGEBCANFCHEHFHCJLGZEBBDBOIUEWHXAODGIRFUAUAKBMDBAEIIBDHJAKJGDXBDGPDWCTENEFJJFNGDIAGYEEDTBFAXGZGSAOGPANDNGIGGHJCOGDILADJIHQFBIAIZHNCLIEETFSDMGUBMCEBQICDWABDQDTGQFJAEIUGPBZGXJRIRBZACFYEJBQCYEKDVBGDYIICACFBPHZFWBNGMCAHLIFFJEICUDFGYEDEJEMBLJDDWCAFYHWJSECHXAWFNJKCAHAFLDXGRHHBZGOAXINGIFKFQFHDRIEFOIQFXBQGSGIFPIMEJFGBTAXIXFUCKDHCABUIKFZ

+ 14 - 5
ktg-admin/pom.xml

@@ -9,7 +9,7 @@
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <packaging>jar</packaging>
-    <artifactId>ktg-admin</artifactId>
+    <artifactId>iscs-mars-server</artifactId>
 
     <description>
         web服务入口
@@ -18,11 +18,12 @@
     <dependencies>
 
         <!-- spring-boot-devtools -->
-        <dependency>
+        <!-- 表示依赖不会传递 -->
+        <!--<dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-devtools</artifactId>
-            <optional>true</optional> <!-- 表示依赖不会传递 -->
-        </dependency>
+            <optional>true</optional>
+        </dependency>-->
 
         <!-- swagger3-->
         <dependency>
@@ -38,9 +39,17 @@
         </dependency>
 
          <!-- Mysql驱动包 -->
-        <dependency>
+        <!--<dependency>
             <groupId>mysql</groupId>
             <artifactId>mysql-connector-java</artifactId>
+        </dependency>-->
+
+        <!-- sqlServer驱动包 -->
+        <dependency>
+            <groupId>com.microsoft.sqlserver</groupId>
+            <artifactId>mssql-jdbc</artifactId>
+            <version>12.6.2.jre8</version> <!-- 根据实际需求选择版本 -->
+            <scope>runtime</scope> <!-- 避免传递依赖污染 -->
         </dependency>
 
         <!-- 核心模块-->

+ 3 - 3
ktg-admin/src/main/java/com/ktg/RuoYiApplication.java → ktg-admin/src/main/java/com/ktg/GuoRuanApplication.java

@@ -17,13 +17,13 @@ import org.springframework.scheduling.annotation.EnableAsync;
 @EnableAsync
 @ImportResource("classpath:ureport-console-context.xml")
 @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
-public class RuoYiApplication
+public class GuoRuanApplication
 {
     public static void main(String[] args)
     {
         // System.setProperty("spring.devtools.restart.enabled", "false");
-        SpringApplication.run(RuoYiApplication.class, args);
-        System.out.println("(♥◠‿◠)ノ゙  国软管理系统启动成功   ლ(´ڡ`ლ)゙  \n");
+        SpringApplication.run(GuoRuanApplication.class, args);
+        System.out.println("(♥◠‿◠)ノ゙  博士管理系统启动成功   ლ(´ڡ`ლ)゙  \n");
     }
 
     @Bean

+ 3 - 3
ktg-admin/src/main/java/com/ktg/RuoYiServletInitializer.java → ktg-admin/src/main/java/com/ktg/GuoRuanServletInitializer.java

@@ -5,14 +5,14 @@ import org.springframework.boot.web.servlet.support.SpringBootServletInitializer
 
 /**
  * web容器中进行部署
- * 
+ *
  * @author ruoyi
  */
-public class RuoYiServletInitializer extends SpringBootServletInitializer
+public class GuoRuanServletInitializer extends SpringBootServletInitializer
 {
     @Override
     protected SpringApplicationBuilder configure(SpringApplicationBuilder application)
     {
-        return application.sources(RuoYiApplication.class);
+        return application.sources(GuoRuanApplication.class);
     }
 }

+ 200 - 68
ktg-admin/src/main/java/com/ktg/web/controller/common/CommonController.java

@@ -1,56 +1,73 @@
 package com.ktg.web.controller.common;
 
-import java.util.ArrayList;
-import java.util.List;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
+import com.aliyun.oss.OSS;
+import com.aliyun.oss.OSSClientBuilder;
+import com.aliyun.oss.OSSException;
+import com.aliyun.oss.model.PutObjectRequest;
 import com.ktg.common.config.RuoYiConfig;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.MediaType;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.multipart.MultipartFile;
 import com.ktg.common.constant.Constants;
 import com.ktg.common.core.domain.AjaxResult;
+import com.ktg.common.utils.FileDriveLetterUtils;
 import com.ktg.common.utils.StringUtils;
 import com.ktg.common.utils.file.FileUploadUtils;
 import com.ktg.common.utils.file.FileUtils;
 import com.ktg.framework.config.ServerConfig;
+import com.ktg.iscs.domain.IsSystemAttribute;
+import com.ktg.iscs.service.IIsSystemAttributeService;
+import com.ktg.system.domain.SysUploadFile;
+import com.ktg.system.service.ISysUploadFileService;
+import io.jsonwebtoken.lang.Assert;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 /**
  * 通用请求处理
- * 
+ *
  * @author ruoyi
  */
+@Api(tags = "通用请求处理-文件")
 @RestController
 @RequestMapping("/common")
-public class CommonController
-{
+public class CommonController {
     private static final Logger log = LoggerFactory.getLogger(CommonController.class);
 
     @Autowired
     private ServerConfig serverConfig;
+    @Autowired
+    private ISysUploadFileService iSysUploadFileService;
+    @Value("${ktg-mes.prod}")
+    private String prodApi;
+    @Autowired
+    private IIsSystemAttributeService isSystemAttributeService;
 
     private static final String FILE_DELIMETER = ",";
 
     /**
      * 通用下载请求
-     * 
+     *
      * @param fileName 文件名称
-     * @param delete 是否删除
+     * @param delete   是否删除
      */
+    @ApiOperation("通用下载请求")
     @GetMapping("/download")
-    public void fileDownload(String fileName, Boolean delete, HttpServletResponse response, HttpServletRequest request)
-    {
-        try
-        {
-            if (!FileUtils.checkAllowDownload(fileName))
-            {
+    public void fileDownload(String fileName, Boolean delete, HttpServletResponse response, HttpServletRequest request) {
+        try {
+            if (!FileUtils.checkAllowDownload(fileName)) {
                 throw new Exception(StringUtils.format("文件名称({})非法,不允许下载。 ", fileName));
             }
             String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1);
@@ -59,13 +76,10 @@ public class CommonController
             response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
             FileUtils.setAttachmentResponseHeader(response, realFileName);
             FileUtils.writeBytes(filePath, response.getOutputStream());
-            if (delete)
-            {
+            if (delete) {
                 FileUtils.deleteFile(filePath);
             }
-        }
-        catch (Exception e)
-        {
+        } catch (Exception e) {
             log.error("下载文件失败", e);
         }
     }
@@ -73,45 +87,81 @@ public class CommonController
     /**
      * 通用上传请求(单个)
      */
+    @ApiOperation("通用上传请求(单个)")
     @PostMapping("/upload")
-    public AjaxResult uploadFile(MultipartFile file) throws Exception
-    {
-        try
-        {
-            // 上传文件路径
-            String filePath = RuoYiConfig.getUploadPath();
-            // 上传并返回新文件名称
-            String fileName = FileUploadUtils.upload(filePath, file);
-            String url = serverConfig.getUrl() + fileName;
+    public AjaxResult uploadFile(MultipartFile file) throws Exception {
+        try {
+            String url = null;
+            String fileName = null;
+            String newFileName = null;
+            String originalFilename = file.getOriginalFilename();
+            String absolutePath = null;
+            IsSystemAttribute isSystemAttributeByKey = isSystemAttributeService.getIsSystemAttributeByKey("sys.upload.type");
+            if (isSystemAttributeByKey == null || isSystemAttributeByKey.getSysAttrValue().equals("0")) {
+                // 上传文件路径
+                String filePath = RuoYiConfig.getUploadPath();
+                // 上传并返回新文件名称
+                fileName = FileUploadUtils.upload(filePath, file);
+                if (StringUtils.isNotBlank(prodApi)) {
+                    url = serverConfig.getUrl() + prodApi + fileName;
+                    absolutePath = fileName.replace("/profile/upload", filePath);
+                    newFileName = FileUtils.getName(fileName);
+                } else {
+                    url = serverConfig.getUrl() + fileName;
+                    absolutePath = FileDriveLetterUtils.getDriveLetter() + fileName.replace("/profile/upload", filePath);
+                    newFileName = FileUtils.getName(fileName);
+                }
+            } else if (isSystemAttributeByKey.getSysAttrValue().equals("1")) {
+                Map<String, String> map = uploadByOss(file);
+                url = map.get("url");
+                absolutePath = map.get("newFileName");
+                fileName = map.get("fileName");
+                newFileName = map.get("newFileName");
+                originalFilename = map.get("originalFilename");
+            }
             AjaxResult ajax = AjaxResult.success();
             ajax.put("url", url);
             ajax.put("fileName", fileName);
-            ajax.put("newFileName", FileUtils.getName(fileName));
+            ajax.put("newFileName", newFileName);
             ajax.put("originalFilename", file.getOriginalFilename());
+
+            // 2.开始新增文件上传信息
+            SysUploadFile sysFile = new SysUploadFile();
+            sysFile.setName(originalFilename);
+            sysFile.setPath(absolutePath);
+            sysFile.setUrl(url);
+            sysFile.setType(file.getContentType());
+            sysFile.setSize(file.getSize());
+            iSysUploadFileService.insertSysUploadFile(sysFile);
             return ajax;
-        }
-        catch (Exception e)
-        {
+        } catch (Exception e) {
             return AjaxResult.error(e.getMessage());
         }
     }
 
+    /*public static void main(String[] args) {
+        String path = System.getProperty("user.dir");
+        System.out.println("path: " + path);
+        String driveLetter = path.substring(0, 2);
+        System.out.println("Drive letter: " + driveLetter);
+        String os = System.getProperty("os.name").toLowerCase();
+        System.out.println("os: " + os);
+    }
+
     /**
      * 通用上传请求(多个)
      */
+    @ApiOperation("通用上传请求(多个)")
     @PostMapping("/uploads")
-    public AjaxResult uploadFiles(List<MultipartFile> files) throws Exception
-    {
-        try
-        {
+    public AjaxResult uploadFiles(List<MultipartFile> files) throws Exception {
+        try {
             // 上传文件路径
             String filePath = RuoYiConfig.getUploadPath();
             List<String> urls = new ArrayList<String>();
             List<String> fileNames = new ArrayList<String>();
             List<String> newFileNames = new ArrayList<String>();
             List<String> originalFilenames = new ArrayList<String>();
-            for (MultipartFile file : files)
-            {
+            for (MultipartFile file : files) {
                 // 上传并返回新文件名称
                 String fileName = FileUploadUtils.upload(filePath, file);
                 String url = serverConfig.getUrl() + fileName;
@@ -119,6 +169,14 @@ public class CommonController
                 fileNames.add(fileName);
                 newFileNames.add(FileUtils.getName(fileName));
                 originalFilenames.add(file.getOriginalFilename());
+                // 2.开始新增文件上传信息
+                SysUploadFile sysFile = new SysUploadFile();
+                sysFile.setName(fileName);
+                sysFile.setPath(filePath);
+                sysFile.setUrl(url);
+                sysFile.setType(file.getContentType());
+                sysFile.setSize(file.getSize());
+                iSysUploadFileService.insertSysUploadFile(sysFile);
             }
             AjaxResult ajax = AjaxResult.success();
             ajax.put("urls", StringUtils.join(urls, FILE_DELIMETER));
@@ -126,9 +184,7 @@ public class CommonController
             ajax.put("newFileNames", StringUtils.join(newFileNames, FILE_DELIMETER));
             ajax.put("originalFilenames", StringUtils.join(originalFilenames, FILE_DELIMETER));
             return ajax;
-        }
-        catch (Exception e)
-        {
+        } catch (Exception e) {
             return AjaxResult.error(e.getMessage());
         }
     }
@@ -136,14 +192,12 @@ public class CommonController
     /**
      * 本地资源通用下载
      */
+    @ApiOperation("本地资源通用下载")
     @GetMapping("/download/resource")
     public void resourceDownload(String resource, HttpServletRequest request, HttpServletResponse response)
-            throws Exception
-    {
-        try
-        {
-            if (!FileUtils.checkAllowDownload(resource))
-            {
+            throws Exception {
+        try {
+            if (!FileUtils.checkAllowDownload(resource)) {
                 throw new Exception(StringUtils.format("资源文件({})非法,不允许下载。 ", resource));
             }
             // 本地资源路径
@@ -155,26 +209,104 @@ public class CommonController
             response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
             FileUtils.setAttachmentResponseHeader(response, downloadName);
             FileUtils.writeBytes(downloadPath, response.getOutputStream());
-        }
-        catch (Exception e)
-        {
+        } catch (Exception e) {
             log.error("下载文件失败", e);
         }
     }
 
+    @ApiOperation("monio上传")
     @PostMapping("/uploadMinio")
-    public AjaxResult uploadFileMinio(MultipartFile file) throws Exception{
-        try{
+    public AjaxResult uploadFileMinio(MultipartFile file) throws Exception {
+        try {
             String fileName = FileUploadUtils.uploadMinio(file);
             AjaxResult rt = AjaxResult.success();
-            rt.put("url",fileName);
-            rt.put("fileName",fileName);
-            rt.put("newFileName",FileUtils.getName(fileName));
-            rt.put("originalFileName",file.getOriginalFilename());
+            rt.put("url", fileName);
+            rt.put("fileName", fileName);
+            rt.put("newFileName", FileUtils.getName(fileName));
+            rt.put("originalFileName", file.getOriginalFilename());
             return rt;
-        }catch (Exception e){
+        } catch (Exception e) {
             return AjaxResult.error(e.getMessage());
         }
     }
 
+    // 配置参数(实际项目中建议从配置文件读取)
+    private static final String ENDPOINT = "https://oss-cn-hangzhou.aliyuncs.com";
+    private static final String ACCESS_KEY = "LTAI5t9wtz1o6ZJE5pwa2QGk";
+    private static final String SECRET_KEY = "JUXGuxCb8in7mYfYrHtsqZfwhnvvPv";
+    private static final String BUCKET_NAME = "iscs-dev";
+
+    @ApiOperation("oss文件上传")
+    @PostMapping("/upload-oss")
+    public AjaxResult uploadFileByOss(@RequestParam("file") MultipartFile file) {
+        Map<String, String> map = uploadByOss(file);
+        return AjaxResult.success(map);
+    }
+
+    public Map<String, String> uploadByOss(MultipartFile file) {
+        // 生成唯一文件名
+        String originalFilename = file.getOriginalFilename();
+        // String fileExtension = originalFilename.substring(originalFilename.lastIndexOf("."));
+        String uniqueFileName = RuoYiConfig.getUploadPath().replaceFirst("/", "") + "/" + FileUploadUtils.extractFilename(file);
+
+        // 初始化OSS客户端
+        OSS ossClient = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY, SECRET_KEY);
+        try {
+            // 创建上传请求
+            PutObjectRequest request = new PutObjectRequest(BUCKET_NAME, uniqueFileName, file.getInputStream());
+
+            // 上传文件(默认私有权限,如需公共读可设置ACL)
+            ossClient.putObject(request);
+
+            // 返回文件访问URL(需要拼接Bucket域名)
+            String fileUrl = "https://" + BUCKET_NAME + "." + ENDPOINT.replace("https://", "") + "/" + uniqueFileName;
+            HashMap<String, String> map = new HashMap<>();
+            map.put("url", fileUrl);
+            map.put("fileName", originalFilename);
+            map.put("newFileName", uniqueFileName);
+            map.put("originalFilename", originalFilename);
+            return map;
+        } catch (IOException e) {
+            Assert.isTrue(false, "上传失败:" + e.getMessage());
+        } catch (Exception e) {
+            Assert.isTrue(false, "OSS异常:" + e.getMessage());
+        } finally {
+            // 关闭客户端
+            if (ossClient != null) {
+                ossClient.shutdown();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 删除OSS文件接口
+     *
+     * @param fileName 要删除的文件名(包含路径)
+     * @return 操作结果
+     */
+    @ApiOperation("删除OSS文件接口")
+    @DeleteMapping("/delete-oss")
+    public AjaxResult deleteFile(@RequestParam String fileName) {
+        // 初始化OSS客户端
+        OSS ossClient = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY, SECRET_KEY);
+
+        try {
+            // 删除文件
+            ossClient.deleteObject(BUCKET_NAME, fileName);
+            return AjaxResult.success("删除成功:" + fileName);
+        } catch (OSSException e) {
+            // OSS相关异常(如文件不存在、权限不足等)
+            return AjaxResult.error("删除失败:" + e.getErrorMessage());
+        } catch (Exception e) {
+            // 其他异常(如网络问题)
+            return AjaxResult.error("删除异常:" + e.getMessage());
+        } finally {
+            // 关闭客户端连接
+            if (ossClient != null) {
+                ossClient.shutdown();
+            }
+        }
+    }
+
 }

+ 34 - 0
ktg-admin/src/main/java/com/ktg/web/controller/common/OrderController.java

@@ -0,0 +1,34 @@
+package com.ktg.web.controller.common;
+
+import com.ktg.common.utils.rocketmq.RocketMQProducerManager;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@Api(tags = "mq测试")
+@RestController
+@RequestMapping("/test")
+public class OrderController {
+    private final RocketMQProducerManager producerManager;
+
+    // 通过构造函数注入生产者管理器
+    public OrderController(RocketMQProducerManager producerManager) {
+        this.producerManager = producerManager;
+    }
+
+    @ApiOperation("发送消息测试")
+    @GetMapping("/create-order")
+    public String createOrder() throws Exception {
+        // 使用order-producer发送订单创建消息
+        producerManager.sendSync(
+                "order-producer", // 生产者名称
+                "order_topic",    // Topic
+                "create",         // Tag
+                "order_123",      // Key
+                "订单创建内容".getBytes() // 消息体
+        );
+        return "Order created";
+    }
+}

+ 128 - 0
ktg-admin/src/main/java/com/ktg/web/controller/iscs/ResApiController.java

@@ -0,0 +1,128 @@
+package com.ktg.web.controller.iscs;
+
+import cn.hutool.core.lang.Assert;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.ktg.common.annotation.Log;
+import com.ktg.common.core.controller.BaseController;
+import com.ktg.common.enums.BusinessType;
+import com.ktg.common.exception.job.TaskException;
+import com.ktg.common.pojo.CommonResult;
+import com.ktg.common.utils.StringUtils;
+import com.ktg.iscs.domain.IsCheckTask;
+import com.ktg.iscs.service.IIsCheckTaskService;
+import com.ktg.quartz.domain.SysJob;
+import com.ktg.quartz.service.ISysJobService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.v3.oas.annotations.Parameter;
+import org.quartz.SchedulerException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 小组Controller
+ *
+ * @author cc
+ * @date 2024-09-13
+ */
+@Api(tags = "业务交叉api")
+@RestController
+@RequestMapping("/iscs/resapi")
+public class ResApiController extends BaseController
+{
+
+    @Autowired
+    private ISysJobService iSysJobService;
+
+    @Autowired
+    private IIsCheckTaskService iIsCheckTaskService;
+
+    @Transactional
+    @ApiOperation("新增检查任务-交叉定时任务")
+    @PreAuthorize("@ss.hasPermi('iscs:resapi:add')")
+    @Log(title = "检查任务", businessType = BusinessType.INSERT)
+    @PostMapping("/insertIsCheckTask")
+    public CommonResult<Boolean> insertIsCheckTask(@RequestBody @Parameter(name = "isCheckTask", description = "新增数据类,放到body") IsCheckTask isCheckTask) throws SchedulerException, TaskException {
+        // 1.检查数据
+        Assert.notBlank(isCheckTask.getCheckName(), "任务名称不可为空!");
+        Assert.notBlank(isCheckTask.getCabinetIdStr(), "请选择需要检查的物资柜信息!");
+        Assert.notBlank(isCheckTask.getUserIdStr(), "请选择参与人员信息!");
+        Assert.notBlank(isCheckTask.getCron(), "请告知我cron!");
+        // 2.开始新增定检查信息,但是缺失定时任务的关联信息,所以后面需要更新一下
+        iIsCheckTaskService.save(isCheckTask);
+        // 3.新增定时任务
+        SysJob sysJob = new SysJob();
+        sysJob.setJobName(isCheckTask.getCheckName());
+        sysJob.setJobGroup("DEFAULT");
+        sysJob.setInvokeTarget("sendEmailsTask.checkMaterialsCabinet("+ isCheckTask.getCheckId() + ")");
+        sysJob.setCronExpression(isCheckTask.getCron());
+        sysJob.setMisfirePolicy("3");
+        sysJob.setConcurrent("1");
+        sysJob.setStatus("1");
+        iSysJobService.insertJob(sysJob);
+        // 4.更新定检查信息
+        iIsCheckTaskService.update(Wrappers.<IsCheckTask>lambdaUpdate()
+                .eq(IsCheckTask::getCheckId, isCheckTask.getCheckId())
+                .set(IsCheckTask::getJobId, sysJob.getJobId()));
+        return CommonResult.success(true);
+    }
+
+    @Transactional
+    @ApiOperation("修改检查任务-交叉定时任务")
+    @PreAuthorize("@ss.hasPermi('iscs:resapi:edit')")
+    @Log(title = "检查任务", businessType = BusinessType.UPDATE)
+    @PostMapping("/updateIsCheckTask")
+    public CommonResult<Boolean> updateIsCheckTask(@RequestBody @Parameter(name = "isCheckTask", description = "修改数据类,放到body") IsCheckTask isCheckTask)
+    {
+        // 1.检查数据
+        Assert.notNull(isCheckTask.getCheckId(), "checkId不可为空!");
+        IsCheckTask checkTask = iIsCheckTaskService.getById(isCheckTask.getCheckId());
+        Assert.notNull(checkTask, "数据不存在!");
+        // 2.开始更新操作
+        iIsCheckTaskService.update(Wrappers.<IsCheckTask>lambdaUpdate()
+                .eq(IsCheckTask::getCheckId, isCheckTask.getCheckId())
+                .set(IsCheckTask::getUpdateTime, new Date())
+                .set(StringUtils.isNotBlank(isCheckTask.getCheckName()), IsCheckTask::getCheckName, isCheckTask.getCheckName())
+                .set(StringUtils.isNotBlank(isCheckTask.getCabinetIdStr()), IsCheckTask::getCabinetIdStr, isCheckTask.getCabinetIdStr())
+                .set(StringUtils.isNotBlank(isCheckTask.getUserIdStr()), IsCheckTask::getUserIdStr, isCheckTask.getUserIdStr())
+                .set(StringUtils.isNotBlank(isCheckTask.getCron()), IsCheckTask::getCron, isCheckTask.getCron()));
+        // 3.如果cron变更,则需要变更sysJob
+        if (StringUtils.isNotBlank(isCheckTask.getCron())) {
+            iSysJobService.update(Wrappers.<SysJob>lambdaUpdate()
+                    .eq(SysJob::getJobId, checkTask.getJobId())
+                    .set(SysJob::getCronExpression, isCheckTask.getCron()));
+        }
+        return CommonResult.success(true);
+    }
+
+    @Transactional
+    @ApiOperation("删除检查任务")
+    @PreAuthorize("@ss.hasPermi('iscs:resapi:remove')")
+    @Log(title = "检查任务", businessType = BusinessType.DELETE)
+    @PostMapping("/deleteIsCheckTaskByCheckIds")
+    public CommonResult<Boolean> deleteIsCheckTaskByCheckIds(String checkIds) throws SchedulerException {
+        Assert.notBlank(checkIds, "请选择需要删除的数据!");
+        List<String> longIds = Arrays.asList(checkIds);
+        // 1.获取数据在删除
+        List<IsCheckTask> list = iIsCheckTaskService.list(Wrappers.<IsCheckTask>lambdaQuery()
+                .in(IsCheckTask::getCheckId, longIds));
+        if (!list.isEmpty()) {
+            // 删除IsCheckTask
+            iIsCheckTaskService.removeByIds(longIds);
+            List<Long> jobIds = list.stream().map(IsCheckTask::getJobId).collect(Collectors.toList());
+            iSysJobService.deleteJobByIds(jobIds.toArray(new Long[0]));
+        }
+        return CommonResult.success(true);
+    }
+
+}

+ 8 - 0
ktg-admin/src/main/java/com/ktg/web/controller/iscs/SysTeamController.java

@@ -117,4 +117,12 @@ public class SysTeamController extends BaseController
         boolean b = sysTeamService.removeByIds(Arrays.asList(ids));
         return CommonResult.success(b);
     }
+
+    @ApiOperation("测试")
+    @GetMapping("/testaaaaaaaa")
+    public CommonResult<Boolean> testaaaaaaaa()
+    {
+        return CommonResult.success(true);
+    }
+
 }

+ 8 - 54
ktg-admin/src/main/java/com/ktg/web/controller/monitor/SysUserOnlineController.java

@@ -1,31 +1,21 @@
 package com.ktg.web.controller.monitor;
 
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.DeleteMapping;
-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 com.ktg.common.annotation.Log;
-import com.ktg.common.constant.Constants;
 import com.ktg.common.core.controller.BaseController;
 import com.ktg.common.core.domain.AjaxResult;
-import com.ktg.common.core.domain.model.LoginUser;
 import com.ktg.common.core.page.TableDataInfo;
-import com.ktg.common.core.redis.RedisCache;
 import com.ktg.common.enums.BusinessType;
-import com.ktg.common.utils.StringUtils;
 import com.ktg.system.domain.SysUserOnline;
 import com.ktg.system.service.ISysUserOnlineService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
 
 /**
  * 在线用户监控
- * 
+ *
  * @author ruoyi
  */
 @RestController
@@ -35,46 +25,11 @@ public class SysUserOnlineController extends BaseController
     @Autowired
     private ISysUserOnlineService userOnlineService;
 
-    @Autowired
-    private RedisCache redisCache;
-
     @PreAuthorize("@ss.hasPermi('monitor:online:list')")
     @GetMapping("/list")
     public TableDataInfo list(String ipaddr, String userName)
     {
-        Collection<String> keys = redisCache.keys(Constants.LOGIN_TOKEN_KEY + "*");
-        List<SysUserOnline> userOnlineList = new ArrayList<SysUserOnline>();
-        for (String key : keys)
-        {
-            LoginUser user = redisCache.getCacheObject(key);
-            if (StringUtils.isNotEmpty(ipaddr) && StringUtils.isNotEmpty(userName))
-            {
-                if (StringUtils.equals(ipaddr, user.getIpaddr()) && StringUtils.equals(userName, user.getUsername()))
-                {
-                    userOnlineList.add(userOnlineService.selectOnlineByInfo(ipaddr, userName, user));
-                }
-            }
-            else if (StringUtils.isNotEmpty(ipaddr))
-            {
-                if (StringUtils.equals(ipaddr, user.getIpaddr()))
-                {
-                    userOnlineList.add(userOnlineService.selectOnlineByIpaddr(ipaddr, user));
-                }
-            }
-            else if (StringUtils.isNotEmpty(userName) && StringUtils.isNotNull(user.getUser()))
-            {
-                if (StringUtils.equals(userName, user.getUsername()))
-                {
-                    userOnlineList.add(userOnlineService.selectOnlineByUserName(userName, user));
-                }
-            }
-            else
-            {
-                userOnlineList.add(userOnlineService.loginUserToUserOnline(user));
-            }
-        }
-        Collections.reverse(userOnlineList);
-        userOnlineList.removeAll(Collections.singleton(null));
+        List<SysUserOnline> userOnlineList = userOnlineService.getOnlineList(ipaddr, userName);
         return getDataTable(userOnlineList);
     }
 
@@ -86,7 +41,6 @@ public class SysUserOnlineController extends BaseController
     @DeleteMapping("/{tokenId}")
     public AjaxResult forceLogout(@PathVariable String tokenId)
     {
-        redisCache.deleteObject(Constants.LOGIN_TOKEN_KEY + tokenId);
-        return AjaxResult.success();
+        return AjaxResult.success(userOnlineService.forceLogout(tokenId));
     }
 }

+ 97 - 0
ktg-admin/src/main/java/com/ktg/web/controller/system/NotifyMessageController.java

@@ -0,0 +1,97 @@
+package com.ktg.web.controller.system;
+
+import com.ktg.common.pojo.CommonResult;
+import com.ktg.common.pojo.PageResult;
+import com.ktg.common.utils.SecurityUtils;
+import com.ktg.common.utils.bean.BeanUtils;
+import com.ktg.system.domain.NotifyMessageDO;
+import com.ktg.system.domain.message.NotifyMessageMyPageReqVO;
+import com.ktg.system.domain.message.NotifyMessagePageReqVO;
+import com.ktg.system.domain.message.NotifyMessageRespVO;
+import com.ktg.system.service.NotifyMessageService;
+import com.ktg.system.strategy.UserTypeEnum;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.v3.oas.annotations.Parameter;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.List;
+
+import static com.ktg.common.pojo.CommonResult.success;
+
+
+@Api(tags = "管理后台 - 我的站内信")
+@RestController
+@RequestMapping("/system/notify-message")
+@Validated
+public class NotifyMessageController {
+
+    @Resource
+    private NotifyMessageService notifyMessageService;
+
+    // ========== 管理所有的站内信 ==========
+
+    @GetMapping("/get")
+    @ApiOperation("获得站内信")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    //@PreAuthorize("@ss.hasPermission('system:notify-message:query')")
+    public CommonResult<NotifyMessageRespVO> getNotifyMessage(@RequestParam("id") Long id) {
+        NotifyMessageDO message = notifyMessageService.getNotifyMessage(id);
+        return success(BeanUtils.toBean(message, NotifyMessageRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @ApiOperation("获得站内信分页")
+    // @PreAuthorize("@ss.hasPermission('system:notify-message:query')")
+    public CommonResult<PageResult<NotifyMessageRespVO>> getNotifyMessagePage(@Valid NotifyMessagePageReqVO pageVO) {
+        PageResult<NotifyMessageDO> pageResult = notifyMessageService.getNotifyMessagePage(pageVO);
+        return success(BeanUtils.toBean(pageResult, NotifyMessageRespVO.class));
+    }
+
+    // ========== 查看自己的站内信 ==========
+
+    @GetMapping("/my-page")
+    @ApiOperation("获得我的站内信分页")
+    public CommonResult<PageResult<NotifyMessageRespVO>> getMyMyNotifyMessagePage(@Valid NotifyMessageMyPageReqVO pageVO) {
+        PageResult<NotifyMessageDO> pageResult = notifyMessageService.getMyMyNotifyMessagePage(pageVO,
+                SecurityUtils.getLoginUser().getUserId(), UserTypeEnum.ADMIN.getValue());
+        return success(BeanUtils.toBean(pageResult, NotifyMessageRespVO.class));
+    }
+
+    @PutMapping("/update-read")
+    @ApiOperation("标记站内信为已读")
+    @Parameter(name = "ids", description = "编号列表", required = true, example = "1024,2048")
+    public CommonResult<Boolean> updateNotifyMessageRead(@RequestParam("ids") List<Long> ids) {
+        notifyMessageService.updateNotifyMessageRead(ids, SecurityUtils.getLoginUser().getUserId(), UserTypeEnum.ADMIN.getValue());
+        return success(Boolean.TRUE);
+    }
+
+    @PutMapping("/update-all-read")
+    @ApiOperation("标记所有站内信为已读")
+    public CommonResult<Boolean> updateAllNotifyMessageRead() {
+        notifyMessageService.updateAllNotifyMessageRead(SecurityUtils.getLoginUser().getUserId(), UserTypeEnum.ADMIN.getValue());
+        return success(Boolean.TRUE);
+    }
+
+    @GetMapping("/get-unread-list")
+    @ApiOperation("获取当前用户的最新站内信列表,默认 10 条")
+    @Parameter(name = "size", description = "10")
+    public CommonResult<List<NotifyMessageRespVO>> getUnreadNotifyMessageList(
+            @RequestParam(name = "size", defaultValue = "10") Integer size) {
+        List<NotifyMessageDO> list = notifyMessageService.getUnreadNotifyMessageList(
+                SecurityUtils.getLoginUser().getUserId(), UserTypeEnum.ADMIN.getValue(), size);
+        return success(BeanUtils.toBean(list, NotifyMessageRespVO.class));
+    }
+
+    @GetMapping("/get-unread-count")
+    @ApiOperation("获得当前用户的未读站内信数量")
+    // @ApiAccessLog(enable = false) // 由于前端会不断轮询该接口,记录日志没有意义
+    public CommonResult<Long> getUnreadNotifyMessageCount() {
+        return success(notifyMessageService.getUnreadNotifyMessageCount(
+                SecurityUtils.getLoginUser().getUserId(), UserTypeEnum.ADMIN.getValue()));
+    }
+
+}

+ 91 - 0
ktg-admin/src/main/java/com/ktg/web/controller/system/NotifyTemplateController.java

@@ -0,0 +1,91 @@
+package com.ktg.web.controller.system;
+
+import com.ktg.common.pojo.CommonResult;
+import com.ktg.common.pojo.PageResult;
+import com.ktg.common.utils.bean.BeanUtils;
+import com.ktg.system.domain.NotifyTemplateDO;
+import com.ktg.system.domain.template.NotifyTemplatePageReqVO;
+import com.ktg.system.domain.template.NotifyTemplateRespVO;
+import com.ktg.system.domain.template.NotifyTemplateSaveReqVO;
+import com.ktg.system.domain.template.NotifyTemplateSendReqVO;
+import com.ktg.system.service.NotifySendService;
+import com.ktg.system.service.NotifyTemplateService;
+import com.ktg.system.strategy.UserTypeEnum;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.v3.oas.annotations.Parameter;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static com.ktg.common.pojo.CommonResult.success;
+
+
+@Api(tags = "管理后台 - 站内信模版")
+@RestController
+@RequestMapping("/system/notify-template")
+@Validated
+public class NotifyTemplateController {
+
+    @Resource
+    private NotifyTemplateService notifyTemplateService;
+
+    @Resource
+    private NotifySendService notifySendService;
+
+    @PostMapping("/create")
+    @ApiOperation("创建站内信模版")
+    // @PreAuthorize("@ss.hasPermission('system:notify-template:create')")
+    public CommonResult<Long> createNotifyTemplate(@Valid @RequestBody NotifyTemplateSaveReqVO createReqVO) {
+        return success(notifyTemplateService.createNotifyTemplate(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @ApiOperation("更新站内信模版")
+    // @PreAuthorize("@ss.hasPermission('system:notify-template:update')")
+    public CommonResult<Boolean> updateNotifyTemplate(@Valid @RequestBody NotifyTemplateSaveReqVO updateReqVO) {
+        notifyTemplateService.updateNotifyTemplate(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @ApiOperation("删除站内信模版")
+    @Parameter(name = "id", description = "编号", required = true)
+    // @PreAuthorize("@ss.hasPermission('system:notify-template:delete')")
+    public CommonResult<Boolean> deleteNotifyTemplate(@RequestParam("id") Long id) {
+        notifyTemplateService.deleteNotifyTemplate(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @ApiOperation("获得站内信模版")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    // @PreAuthorize("@ss.hasPermission('system:notify-template:query')")
+    public CommonResult<NotifyTemplateRespVO> getNotifyTemplate(@RequestParam("id") Long id) {
+        NotifyTemplateDO template = notifyTemplateService.getNotifyTemplate(id);
+        return success(BeanUtils.toBean(template, NotifyTemplateRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @ApiOperation("获得站内信模版分页")
+    // @PreAuthorize("@ss.hasPermission('system:notify-template:query')")
+    public CommonResult<PageResult<NotifyTemplateRespVO>> getNotifyTemplatePage(@Valid NotifyTemplatePageReqVO pageVO) {
+        PageResult<NotifyTemplateDO> pageResult = notifyTemplateService.getNotifyTemplatePage(pageVO);
+        return success(BeanUtils.toBean(pageResult, NotifyTemplateRespVO.class));
+    }
+
+    @PostMapping("/send-notify")
+    @ApiOperation("发送站内信")
+    // @PreAuthorize("@ss.hasPermission('system:notify-template:send-notify')")
+    public CommonResult<Long> sendNotify(@Valid @RequestBody NotifyTemplateSendReqVO sendReqVO) {
+        if (UserTypeEnum.MEMBER.getValue().equals(sendReqVO.getUserType())) {
+            return success(notifySendService.sendSingleNotifyToMember(sendReqVO.getUserId(),
+                    sendReqVO.getTemplateCode(), sendReqVO.getTemplateParams()));
+        } else {
+            return success(notifySendService.sendSingleNotifyToAdmin(sendReqVO.getUserId(),
+                    sendReqVO.getTemplateCode(), sendReqVO.getTemplateParams()));
+        }
+    }
+}

+ 7 - 8
ktg-admin/src/main/java/com/ktg/web/controller/system/SysAutoCodePartController.java

@@ -9,7 +9,6 @@ import com.ktg.common.core.page.TableDataInfo;
 import com.ktg.common.enums.BusinessType;
 import com.ktg.system.service.IAutoCodePartService;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
@@ -22,7 +21,7 @@ public class SysAutoCodePartController extends BaseController {
     @Autowired
     private IAutoCodePartService iAutoCodePartService;
 
-    @PreAuthorize("@ss.hasPermi('system:autocode:part:list')")
+    // @PreAuthorize("@ss.hasPermi('system:autocode:part:list')")
     @GetMapping("/list")
     public TableDataInfo list(SysAutoCodePart sysAutoCodePart){
         startPage();
@@ -30,37 +29,37 @@ public class SysAutoCodePartController extends BaseController {
         return getDataTable(parts);
     }
 
-    @PreAuthorize("@ss.hasPermi('system:autocode:part:query')")
+    // @PreAuthorize("@ss.hasPermi('system:autocode:part:query')")
     @GetMapping("/{partId}")
     public AjaxResult getInfo(@PathVariable Long partId){
         return AjaxResult.success(iAutoCodePartService.findById(partId));
     }
 
 
-    @PreAuthorize("@ss.hasPermi('system:autocode:part:insert')")
+    // @PreAuthorize("@ss.hasPermi('system:autocode:part:insert')")
     @Log(title = "新增编码生产规则组成部分",businessType = BusinessType.INSERT)
     @PostMapping
     public AjaxResult add(@Validated @RequestBody SysAutoCodePart part){
         if(UserConstants.NOT_UNIQUE.equals(iAutoCodePartService.checkPartUnique(part))){
-            return AjaxResult.error("规则组成不唯一,检查组成编码、组成名称、组成序号");
+            return AjaxResult.error("规则组成不唯一,检查组成编码、组成名称、组成序号");
         }
         part.setCreateBy(getUsername());
         return toAjax(iAutoCodePartService.insertPart(part));
     }
 
-    @PreAuthorize("@ss.hasPermi('system:autocode:part:update')")
+    // @PreAuthorize("@ss.hasPermi('system:autocode:part:update')")
     @Log(title = "更新物料编码",businessType = BusinessType.UPDATE)
     @PutMapping
     public AjaxResult update(@Validated @RequestBody SysAutoCodePart sysAutoCodePart){
         if(UserConstants.NOT_UNIQUE.equals(iAutoCodePartService.checkPartUnique(sysAutoCodePart))){
-            return AjaxResult.error("规则组成不唯一,检查组成编码、组成名称、组成序号");
+            return AjaxResult.error("规则组成不唯一,检查组成编码、组成名称、组成序号");
         }
         sysAutoCodePart.setUpdateBy(getUsername());
         return toAjax(iAutoCodePartService.updatePart(sysAutoCodePart));
     }
 
 
-    @PreAuthorize("@ss.hasPermi('system:autocode:part:remove')")
+    // @PreAuthorize("@ss.hasPermi('system:autocode:part:remove')")
     @Log(title = "删除物料编码",businessType = BusinessType.DELETE)
     @DeleteMapping("/{partIds}")
     public AjaxResult delete(@PathVariable Long[] partIds){

+ 1 - 1
ktg-admin/src/main/java/com/ktg/web/controller/system/SysAutoCodeRuleController.java

@@ -30,7 +30,7 @@ public class SysAutoCodeRuleController extends BaseController {
         return getDataTable(rules);
     }
 
-    @PreAuthorize("@ss.hasPermi('system:autocode:rule:query')")
+    // @PreAuthorize("@ss.hasPermi('system:autocode:rule:query')")
     @GetMapping("/{ruleId}")
     public AjaxResult getInfo(@PathVariable Long ruleId){
         return AjaxResult.success(iAutoCodeRuleService.findById(ruleId));

+ 81 - 23
ktg-admin/src/main/java/com/ktg/web/controller/system/SysLoginController.java

@@ -1,86 +1,144 @@
 package com.ktg.web.controller.system;
 
-import java.util.List;
-import java.util.Set;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RestController;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.ktg.common.constant.Constants;
 import com.ktg.common.core.domain.AjaxResult;
 import com.ktg.common.core.domain.entity.SysMenu;
 import com.ktg.common.core.domain.entity.SysUser;
 import com.ktg.common.core.domain.model.LoginBody;
 import com.ktg.common.utils.SecurityUtils;
+import com.ktg.framework.web.service.IscsService;
 import com.ktg.framework.web.service.SysLoginService;
 import com.ktg.framework.web.service.SysPermissionService;
 import com.ktg.system.service.ISysMenuService;
+import com.ktg.system.service.ISysUserService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
 
 /**
  * 登录验证
- * 
+ *
  * @author ruoyi
  */
+@Api(tags = "登录验证")
 @RestController
-public class SysLoginController
-{
+public class SysLoginController {
     @Autowired
     private SysLoginService loginService;
-
     @Autowired
     private ISysMenuService menuService;
-
     @Autowired
     private SysPermissionService permissionService;
+    @Autowired
+    private ISysUserService iSysUserService;
+    @Autowired
+    private IscsService iscsService;
+
 
     /**
      * 登录方法
-     * 
+     *
      * @param loginBody 登录信息
      * @return 结果
      */
+    @ApiOperation("系统用户登录")
     @PostMapping("/login")
-    public AjaxResult login(@RequestBody LoginBody loginBody)
-    {
+    public AjaxResult login(@RequestBody LoginBody loginBody) {
         AjaxResult ajax = AjaxResult.success();
         // 生成令牌
-        String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),
-                loginBody.getUuid());
+        String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(), loginBody.getUuid());
+        ajax.put(Constants.TOKEN, token);
+        SysUser user = iSysUserService.getOne(Wrappers.<SysUser>lambdaQuery().eq(SysUser::getUserName, loginBody.getUsername()));
+        ajax.put("nickName", user.getNickName());
+        return ajax;
+    }
+
+    /**
+     * 机柜登录,防止多机柜操作,只允许同时在线一个
+     *
+     * @param loginBody
+     * @return
+     */
+    @PostMapping("/machineLogin")
+    public AjaxResult machineLogin(@RequestBody LoginBody loginBody) {
+        AjaxResult ajax = AjaxResult.success();
+        String token = loginService.machineLogin(loginBody);
         ajax.put(Constants.TOKEN, token);
         return ajax;
     }
 
     /**
      * 获取用户信息
-     * 
+     *
      * @return 用户信息
      */
+    @ApiOperation("获取用户信息")
     @GetMapping("getInfo")
-    public AjaxResult getInfo()
-    {
+    public AjaxResult getInfo() {
         SysUser user = SecurityUtils.getLoginUser().getUser();
         // 角色集合
         Set<String> roles = permissionService.getRolePermission(user);
         // 权限集合
         Set<String> permissions = permissionService.getMenuPermission(user);
         AjaxResult ajax = AjaxResult.success();
+        // 用户卡nfc
+        Set<String> userCardList = iscsService.getUserCardList(user.getUserId());
         ajax.put("user", user);
         ajax.put("roles", roles);
         ajax.put("permissions", permissions);
+        ajax.put("userCardList", userCardList);
         return ajax;
     }
 
     /**
      * 获取路由信息
-     * 
+     *
      * @return 路由信息
      */
+    @ApiOperation("获取路由信息")
     @GetMapping("getRouters")
-    public AjaxResult getRouters()
-    {
+    public AjaxResult getRouters() {
         Long userId = SecurityUtils.getUserId();
         List<SysMenu> menus = menuService.selectMenuTreeByUserId(userId);
         return AjaxResult.success(menuService.buildMenus(menus));
     }
+
+    /*@ApiOperation("系统用户登录-指纹验证登录")
+    @PostMapping("/loginByFingerprint")
+    public AjaxResult loginByFingerprint(@RequestBody FingerprintLoginBody loginBody) {
+        AjaxResult ajaxResult = loginService.loginByFingerprint(loginBody);
+        return ajaxResult;
+    }*/
+
+    @ApiOperation("系统用户登录-指纹验证登录dat")
+    @PostMapping("/loginByFingerprintDat")
+    public AjaxResult loginByFingerprintDat(MultipartFile file) throws IOException {
+        AjaxResult ajaxResult = loginService.loginByFingerprintDat(file);
+        // AjaxResult ajaxResult = loginService.loginByArcFace(file);
+        return ajaxResult;
+    }
+
+    @ApiOperation("系统用户登录-face登录-opencv")
+    @PostMapping("/loginByFace")
+    public AjaxResult loginByFace(MultipartFile file) throws IOException {
+        AjaxResult ajaxResult = loginService.loginByFace(file);
+        return ajaxResult;
+    }
+
+    @ApiOperation("系统用户登录-face登录-arcsoft")
+    @PostMapping("/loginByArcFace")
+    public AjaxResult loginByArcFace(MultipartFile file) throws IOException {
+        AjaxResult ajaxResult = loginService.loginByArcFace(file);
+        return ajaxResult;
+    }
 }

+ 21 - 11
ktg-admin/src/main/java/com/ktg/web/controller/system/SysProfileController.java

@@ -1,15 +1,5 @@
 package com.ktg.web.controller.system;
 
-import java.io.IOException;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.multipart.MultipartFile;
 import com.ktg.common.annotation.Log;
 import com.ktg.common.config.RuoYiConfig;
 import com.ktg.common.constant.UserConstants;
@@ -21,12 +11,20 @@ import com.ktg.common.enums.BusinessType;
 import com.ktg.common.utils.SecurityUtils;
 import com.ktg.common.utils.StringUtils;
 import com.ktg.common.utils.file.FileUploadUtils;
+import com.ktg.framework.config.ServerConfig;
 import com.ktg.framework.web.service.TokenService;
+import com.ktg.system.domain.SysUploadFile;
+import com.ktg.system.service.ISysUploadFileService;
 import com.ktg.system.service.ISysUserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
 
 /**
  * 个人信息 业务处理
- * 
+ *
  * @author ruoyi
  */
 @RestController
@@ -38,6 +36,10 @@ public class SysProfileController extends BaseController
 
     @Autowired
     private TokenService tokenService;
+    @Autowired
+    private ServerConfig serverConfig;
+    @Autowired
+    private ISysUploadFileService iSysUploadFileService;
 
     /**
      * 个人信息
@@ -134,6 +136,14 @@ public class SysProfileController extends BaseController
                 // 更新缓存用户头像
                 loginUser.getUser().setAvatar(avatar);
                 tokenService.setLoginUser(loginUser);
+                // 2.开始新增文件上传信息
+                SysUploadFile sysFile = new SysUploadFile();
+                sysFile.setName(avatar);
+                sysFile.setPath(RuoYiConfig.getAvatarPath());
+                sysFile.setUrl(serverConfig.getUrl() + avatar);
+                sysFile.setType(file.getContentType());
+                sysFile.setSize(file.getSize());
+                iSysUploadFileService.insertSysUploadFile(sysFile);
                 return ajax;
             }
         }

+ 45 - 61
ktg-admin/src/main/java/com/ktg/web/controller/system/SysRoleController.java

@@ -1,18 +1,7 @@
 package com.ktg.web.controller.system;
 
-import java.util.List;
-import javax.servlet.http.HttpServletResponse;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import cn.hutool.core.lang.Assert;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.ktg.common.annotation.Log;
 import com.ktg.common.constant.UserConstants;
 import com.ktg.common.core.controller.BaseController;
@@ -29,32 +18,37 @@ import com.ktg.framework.web.service.TokenService;
 import com.ktg.system.domain.SysUserRole;
 import com.ktg.system.service.ISysRoleService;
 import com.ktg.system.service.ISysUserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
 
 /**
  * 角色信息
- * 
+ *
  * @author ruoyi
  */
 @RestController
 @RequestMapping("/system/role")
-public class SysRoleController extends BaseController
-{
+public class SysRoleController extends BaseController {
     @Autowired
     private ISysRoleService roleService;
 
     @Autowired
     private TokenService tokenService;
-    
+
     @Autowired
     private SysPermissionService permissionService;
-    
+
     @Autowired
     private ISysUserService userService;
 
     @PreAuthorize("@ss.hasPermi('system:role:list')")
     @GetMapping("/list")
-    public TableDataInfo list(SysRole role)
-    {
+    public TableDataInfo list(SysRole role) {
         startPage();
         List<SysRole> list = roleService.selectRoleList(role);
         return getDataTable(list);
@@ -63,8 +57,7 @@ public class SysRoleController extends BaseController
     @Log(title = "角色管理", businessType = BusinessType.EXPORT)
     @PreAuthorize("@ss.hasPermi('system:role:export')")
     @PostMapping("/export")
-    public void export(HttpServletResponse response, SysRole role)
-    {
+    public void export(HttpServletResponse response, SysRole role) {
         List<SysRole> list = roleService.selectRoleList(role);
         ExcelUtil<SysRole> util = new ExcelUtil<SysRole>(SysRole.class);
         util.exportExcel(response, list, "角色数据");
@@ -75,8 +68,7 @@ public class SysRoleController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('system:role:query')")
     @GetMapping(value = "/{roleId}")
-    public AjaxResult getInfo(@PathVariable Long roleId)
-    {
+    public AjaxResult getInfo(@PathVariable Long roleId) {
         roleService.checkRoleDataScope(roleId);
         return AjaxResult.success(roleService.selectRoleById(roleId));
     }
@@ -87,14 +79,10 @@ public class SysRoleController extends BaseController
     @PreAuthorize("@ss.hasPermi('system:role:add')")
     @Log(title = "角色管理", businessType = BusinessType.INSERT)
     @PostMapping
-    public AjaxResult add(@Validated @RequestBody SysRole role)
-    {
-        if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleNameUnique(role)))
-        {
+    public AjaxResult add(@Validated @RequestBody SysRole role) {
+        if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleNameUnique(role))) {
             return AjaxResult.error("新增角色'" + role.getRoleName() + "'失败,角色名称已存在");
-        }
-        else if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleKeyUnique(role)))
-        {
+        } else if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleKeyUnique(role))) {
             return AjaxResult.error("新增角色'" + role.getRoleName() + "'失败,角色权限已存在");
         }
         role.setCreateBy(getUsername());
@@ -108,26 +96,20 @@ public class SysRoleController extends BaseController
     @PreAuthorize("@ss.hasPermi('system:role:edit')")
     @Log(title = "角色管理", businessType = BusinessType.UPDATE)
     @PutMapping
-    public AjaxResult edit(@Validated @RequestBody SysRole role)
-    {
+    public AjaxResult edit(@Validated @RequestBody SysRole role) {
         roleService.checkRoleAllowed(role);
         roleService.checkRoleDataScope(role.getRoleId());
-        if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleNameUnique(role)))
-        {
+        if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleNameUnique(role))) {
             return AjaxResult.error("修改角色'" + role.getRoleName() + "'失败,角色名称已存在");
-        }
-        else if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleKeyUnique(role)))
-        {
+        } else if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleKeyUnique(role))) {
             return AjaxResult.error("修改角色'" + role.getRoleName() + "'失败,角色权限已存在");
         }
         role.setUpdateBy(getUsername());
-        
-        if (roleService.updateRole(role) > 0)
-        {
+
+        if (roleService.updateRole(role) > 0) {
             // 更新缓存用户权限
             LoginUser loginUser = getLoginUser();
-            if (StringUtils.isNotNull(loginUser.getUser()) && !loginUser.getUser().isAdmin())
-            {
+            if (StringUtils.isNotNull(loginUser.getUser()) && !loginUser.getUser().isAdmin()) {
                 loginUser.setPermissions(permissionService.getMenuPermission(loginUser.getUser()));
                 loginUser.setUser(userService.selectUserByUserName(loginUser.getUser().getUserName()));
                 tokenService.setLoginUser(loginUser);
@@ -143,8 +125,7 @@ public class SysRoleController extends BaseController
     @PreAuthorize("@ss.hasPermi('system:role:edit')")
     @Log(title = "角色管理", businessType = BusinessType.UPDATE)
     @PutMapping("/dataScope")
-    public AjaxResult dataScope(@RequestBody SysRole role)
-    {
+    public AjaxResult dataScope(@RequestBody SysRole role) {
         roleService.checkRoleAllowed(role);
         roleService.checkRoleDataScope(role.getRoleId());
         return toAjax(roleService.authDataScope(role));
@@ -156,8 +137,7 @@ public class SysRoleController extends BaseController
     @PreAuthorize("@ss.hasPermi('system:role:edit')")
     @Log(title = "角色管理", businessType = BusinessType.UPDATE)
     @PutMapping("/changeStatus")
-    public AjaxResult changeStatus(@RequestBody SysRole role)
-    {
+    public AjaxResult changeStatus(@RequestBody SysRole role) {
         roleService.checkRoleAllowed(role);
         roleService.checkRoleDataScope(role.getRoleId());
         role.setUpdateBy(getUsername());
@@ -170,8 +150,7 @@ public class SysRoleController extends BaseController
     @PreAuthorize("@ss.hasPermi('system:role:remove')")
     @Log(title = "角色管理", businessType = BusinessType.DELETE)
     @DeleteMapping("/{roleIds}")
-    public AjaxResult remove(@PathVariable Long[] roleIds)
-    {
+    public AjaxResult remove(@PathVariable Long[] roleIds) {
         return toAjax(roleService.deleteRoleByIds(roleIds));
     }
 
@@ -180,8 +159,7 @@ public class SysRoleController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('system:role:query')")
     @GetMapping("/optionselect")
-    public AjaxResult optionselect()
-    {
+    public AjaxResult optionselect() {
         return AjaxResult.success(roleService.selectRoleAll());
     }
 
@@ -190,8 +168,18 @@ public class SysRoleController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('system:role:list')")
     @GetMapping("/authUser/allocatedList")
-    public TableDataInfo allocatedList(SysUser user)
-    {
+    public TableDataInfo allocatedList(SysUser user) {
+        startPage();
+        List<SysUser> list = userService.selectAllocatedList(user);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('system:role:list')")
+    @GetMapping("/authUser/allocatedListByRoleKey")
+    public TableDataInfo allocatedListByRoleKey(SysUser user) {
+        Assert.notBlank(user.getRoleKey(), "字符权限不可为空!");
+        SysRole sysRole = roleService.getOne(Wrappers.<SysRole>lambdaQuery().eq(SysRole::getRoleKey, user.getRoleKey()));
+        user.setRoleId(sysRole.getRoleId());
         startPage();
         List<SysUser> list = userService.selectAllocatedList(user);
         return getDataTable(list);
@@ -202,8 +190,7 @@ public class SysRoleController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('system:role:list')")
     @GetMapping("/authUser/unallocatedList")
-    public TableDataInfo unallocatedList(SysUser user)
-    {
+    public TableDataInfo unallocatedList(SysUser user) {
         startPage();
         List<SysUser> list = userService.selectUnallocatedList(user);
         return getDataTable(list);
@@ -215,8 +202,7 @@ public class SysRoleController extends BaseController
     @PreAuthorize("@ss.hasPermi('system:role:edit')")
     @Log(title = "角色管理", businessType = BusinessType.GRANT)
     @PutMapping("/authUser/cancel")
-    public AjaxResult cancelAuthUser(@RequestBody SysUserRole userRole)
-    {
+    public AjaxResult cancelAuthUser(@RequestBody SysUserRole userRole) {
         return toAjax(roleService.deleteAuthUser(userRole));
     }
 
@@ -226,8 +212,7 @@ public class SysRoleController extends BaseController
     @PreAuthorize("@ss.hasPermi('system:role:edit')")
     @Log(title = "角色管理", businessType = BusinessType.GRANT)
     @PutMapping("/authUser/cancelAll")
-    public AjaxResult cancelAuthUserAll(Long roleId, Long[] userIds)
-    {
+    public AjaxResult cancelAuthUserAll(Long roleId, Long[] userIds) {
         return toAjax(roleService.deleteAuthUsers(roleId, userIds));
     }
 
@@ -237,8 +222,7 @@ public class SysRoleController extends BaseController
     @PreAuthorize("@ss.hasPermi('system:role:edit')")
     @Log(title = "角色管理", businessType = BusinessType.GRANT)
     @PutMapping("/authUser/selectAll")
-    public AjaxResult selectAuthUserAll(Long roleId, Long[] userIds)
-    {
+    public AjaxResult selectAuthUserAll(Long roleId, Long[] userIds) {
         roleService.checkRoleDataScope(roleId);
         return toAjax(roleService.insertAuthUsers(roleId, userIds));
     }

+ 116 - 0
ktg-admin/src/main/java/com/ktg/web/controller/system/SysUploadFileController.java

@@ -0,0 +1,116 @@
+package com.ktg.web.controller.system;
+
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ktg.common.annotation.Log;
+import com.ktg.common.core.controller.BaseController;
+import com.ktg.common.enums.BusinessType;
+import com.ktg.common.pojo.CommonResult;
+import com.ktg.common.utils.poi.ExcelUtil;
+import com.ktg.system.domain.SysUploadFile;
+import com.ktg.system.service.ISysUploadFileService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 文件Controller
+ *
+ * @author cgj
+ * @date 2024-10-22
+ */
+@Api(tags = "文件")
+@RestController
+@RequestMapping("/system/file")
+public class SysUploadFileController extends BaseController
+{
+    @Autowired
+    private ISysUploadFileService sysUploadFileService;
+
+    /**
+     * 查询文件分页
+     */
+    @ApiOperation("查询文件-分页")
+    @Parameters({
+            @Parameter(name = "page", description = "Page"),
+            @Parameter(name = "sysUploadFile", description = "实体参数")
+    })
+    @PreAuthorize("@ss.hasPermi('system:file:list')")
+    @GetMapping("/getSysUploadFilePage")
+    public CommonResult<Page<SysUploadFile>> getSysUploadFilePage(Page<SysUploadFile> page, SysUploadFile sysUploadFile)
+    {
+        Page<SysUploadFile> result = sysUploadFileService.page(page, Wrappers.<SysUploadFile>lambdaQuery()
+                .orderByDesc(SysUploadFile::getId));
+        return CommonResult.success(result);
+    }
+
+    /**
+     * 导出文件列表
+     */
+    @ApiOperation("导出文件列表")
+    @Parameter(name = "sysUploadFile", description = "实体参数")
+    @PreAuthorize("@ss.hasPermi('system:file:export')")
+    @Log(title = "文件", businessType = BusinessType.EXPORT)
+    @PostMapping("/exportSysUploadFile")
+    public void exportSysUploadFile(HttpServletResponse response, SysUploadFile sysUploadFile)
+    {
+        List<SysUploadFile> list = sysUploadFileService.selectSysUploadFileList(sysUploadFile);
+        ExcelUtil<SysUploadFile> util = new ExcelUtil<SysUploadFile>(SysUploadFile.class);
+        util.exportExcel(response, list, "文件数据");
+    }
+
+    /**
+     * 获取文件详细信息
+     */
+    @ApiOperation("获取文件详细信息")
+    @Parameter(name = "id", description = "id")
+    @PreAuthorize("@ss.hasPermi('system:file:query')")
+    @GetMapping(value = "/selectSysUploadFileById")
+    public CommonResult<SysUploadFile> selectSysUploadFileById(Long id)
+    {
+        return CommonResult.success(sysUploadFileService.selectSysUploadFileById(id));
+    }
+
+    /**
+     * 新增文件
+     */
+    @ApiOperation("新增文件")
+    @PreAuthorize("@ss.hasPermi('system:file:add')")
+    @Log(title = "文件", businessType = BusinessType.INSERT)
+    @PostMapping("/insertSysUploadFile")
+    public CommonResult<Boolean> insertSysUploadFile(@RequestBody @Parameter(name = "sysUploadFile", description = "新增数据类,放到body") SysUploadFile sysUploadFile)
+    {
+        return CommonResult.success(sysUploadFileService.insertSysUploadFile(sysUploadFile) == 1);
+    }
+
+    /**
+     * 修改文件
+     */
+    @ApiOperation("修改文件")
+    @PreAuthorize("@ss.hasPermi('system:file:edit')")
+    @Log(title = "文件", businessType = BusinessType.UPDATE)
+    @PostMapping("/updateSysUploadFile")
+    public CommonResult<Boolean> updateSysUploadFile(@RequestBody @Parameter(name = "sysUploadFile", description = "修改数据类,放到body") SysUploadFile sysUploadFile)
+    {
+        return CommonResult.success(sysUploadFileService.updateSysUploadFile(sysUploadFile) == 1);
+    }
+
+    /**
+     * 删除文件
+     */
+    @ApiOperation("删除文件")
+    @PreAuthorize("@ss.hasPermi('system:file:remove')")
+    @Log(title = "文件", businessType = BusinessType.DELETE)
+	@PostMapping("/deleteSysUploadFileByIds")
+    public CommonResult<Boolean> deleteSysUploadFileByIds(String ids)
+    {
+        return CommonResult.success(sysUploadFileService.deleteSysUploadFileByIds(ids) != 0);
+    }
+}

+ 150 - 0
ktg-admin/src/main/java/com/ktg/web/controller/system/SysUserCharacteristicController.java

@@ -0,0 +1,150 @@
+package com.ktg.web.controller.system;
+
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ktg.common.annotation.Log;
+import com.ktg.common.core.controller.BaseController;
+import com.ktg.common.enums.BusinessType;
+import com.ktg.common.pojo.CommonResult;
+import com.ktg.common.utils.poi.ExcelUtil;
+import com.ktg.common.vo.FaceCutVO;
+import com.ktg.framework.config.ServerConfig;
+import com.ktg.iscs.domain.IsSystemAttribute;
+import com.ktg.iscs.service.IIsSystemAttributeService;
+import com.ktg.system.domain.SysUserCharacteristic;
+import com.ktg.system.service.ISysUserCharacteristicService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * 用户特征(指纹、面部)Controller
+ *
+ * @author cgj
+ * @date 2025-03-10
+ */
+@Api(tags = "用户特征(指纹、面部)")
+@Slf4j
+@RestController
+@RequestMapping("/system/user/characteristic")
+public class SysUserCharacteristicController extends BaseController
+{
+    @Autowired
+    private ISysUserCharacteristicService sysUserCharacteristicService;
+    @Autowired
+    private IIsSystemAttributeService isSystemAttributeService;
+    @Autowired
+    private ServerConfig serverConfig;
+
+    @ApiOperation("查询用户特征(指纹、面部)-分页")
+    @Parameters({
+            @Parameter(name = "page", description = "Page"),
+            @Parameter(name = "sysUserCharacteristic", description = "实体参数")
+    })
+    @GetMapping("/getSysUserCharacteristicPage")
+    public CommonResult<Page<SysUserCharacteristic>> getSysUserCharacteristicPage(Page<SysUserCharacteristic> page, SysUserCharacteristic sysUserCharacteristic)
+    {
+        Page<SysUserCharacteristic> result = sysUserCharacteristicService.getSysUserCharacteristicPage(page, sysUserCharacteristic);
+        return CommonResult.success(result);
+    }
+
+    @ApiOperation("导出用户特征(指纹、面部)列表")
+    @Parameter(name = "sysUserCharacteristic", description = "实体参数")
+    @Log(title = "用户特征(指纹、面部)", businessType = BusinessType.EXPORT)
+    @PostMapping("/exportSysUserCharacteristic")
+    public void exportSysUserCharacteristic(HttpServletResponse response, SysUserCharacteristic sysUserCharacteristic)
+    {
+        Page<SysUserCharacteristic> page = new Page<>();
+        page.setSize(-1);
+        page.setCurrent(1);
+        List<SysUserCharacteristic> list = sysUserCharacteristicService.page(page, Wrappers.<SysUserCharacteristic>lambdaQuery()
+                .orderByDesc(SysUserCharacteristic::getRecordId)).getRecords();
+        ExcelUtil<SysUserCharacteristic> util = new ExcelUtil<SysUserCharacteristic>(SysUserCharacteristic.class);
+        util.exportExcel(response, list, "用户特征(指纹、面部)数据");
+    }
+
+    @ApiOperation("获取用户特征(指纹、面部)详细信息")
+    @Parameter(name = "recordId", description = "recordId")
+    @GetMapping(value = "/selectSysUserCharacteristicById")
+    public CommonResult<SysUserCharacteristic> selectSysUserCharacteristicById(Long recordId)
+    {
+        return CommonResult.success(sysUserCharacteristicService.getById(recordId));
+    }
+
+    @ApiOperation("新增指纹录入-指纹图片转成dat存储")
+    // @PreAuthorize("@ss.hasPermi('iscs:characteristic:add')")
+    @Log(title = "新增指纹录入-指纹图片转成dat存储", businessType = BusinessType.INSERT)
+    @PostMapping("/insertUserFingerprintDat")
+    public CommonResult<Long> insertUserFingerprintDat(MultipartFile file, String userName, String group) throws IOException {
+        IsSystemAttribute one = isSystemAttributeService.getIsSystemAttributeByKey("sys.fingerprint.limit");
+        String sysAttrValue = null;
+        if (one != null) {
+            sysAttrValue = one.getSysAttrValue();
+        }
+        String url = serverConfig.getUrl();
+        return CommonResult.success(sysUserCharacteristicService.insertUserFingerprintDat(file, userName, sysAttrValue, url, group));
+        // return CommonResult.success(sysUserCharacteristicService.insertUserFace(file, userName, sysAttrValue, url));
+    }
+    @ApiOperation("修改用户特征(指纹、面部)")
+    @Log(title = "用户特征(指纹、面部)", businessType = BusinessType.UPDATE)
+    @PostMapping("/updateSysUserCharacteristic")
+    public CommonResult<Boolean> updateSysUserCharacteristic(@RequestBody @Parameter(name = "sysUserCharacteristic", description = "修改数据类,放到body") SysUserCharacteristic sysUserCharacteristic)
+    {
+        return CommonResult.success(sysUserCharacteristicService.updateById(sysUserCharacteristic));
+    }
+
+    @ApiOperation("删除用户特征(指纹、面部)")
+    @Log(title = "用户特征(指纹、面部)", businessType = BusinessType.DELETE)
+	@PostMapping("/deleteSysUserCharacteristicByRecordIds")
+    public CommonResult<Boolean> deleteSysUserCharacteristicByRecordIds(String recordIds)
+    {
+        return CommonResult.success(sysUserCharacteristicService.deleteSysUserCharacteristicByRecordIds(recordIds));
+    }
+
+    /*@ApiOperation("删除用户特征(指纹、面部)")
+    @PreAuthorize("@ss.hasPermi('iscs:characteristic:remove')")
+    @Log(title = "用户特征(指纹、面部)", businessType = BusinessType.DELETE)
+    @GetMapping("/removeSysUserCharacteristicByRecordIds")
+    public CommonResult<Boolean> removeSysUserCharacteristicByRecordIds(String recordIds)
+    {
+        return CommonResult.success(sysUserCharacteristicService.deleteSysUserCharacteristicByRecordIds(recordIds));
+    }*/
+
+    @ApiOperation("新增人脸录入-人脸识别后裁剪存储-opencv")
+    // @PreAuthorize("@ss.hasPermi('iscs:characteristic:add')")
+    @Log(title = "新增人脸录入-人脸识别后裁剪存储", businessType = BusinessType.INSERT)
+    @PostMapping("/insertUserCutFace")
+    public CommonResult<FaceCutVO> insertUserCutFace(MultipartFile file, String userName) throws IOException {
+        IsSystemAttribute one = isSystemAttributeService.getIsSystemAttributeByKey("sys.face.limit");
+        String sysAttrValue = null;
+        if (one != null) {
+            sysAttrValue = one.getSysAttrValue();
+        }
+        String url = serverConfig.getUrl();
+        return CommonResult.success(sysUserCharacteristicService.insertUserCutFace(file, userName, sysAttrValue, url));
+    }
+
+    @ApiOperation("新增人脸录入-人脸识别后裁剪存储-arcsoft")
+    // @PreAuthorize("@ss.hasPermi('iscs:characteristic:add')")
+    @Log(title = "新增人脸录入-人脸识别后裁剪存储-arcsoft", businessType = BusinessType.INSERT)
+    @PostMapping("/insertUserFace")
+    public CommonResult<FaceCutVO> insertUserFace(MultipartFile file, String userName) throws IOException {
+        IsSystemAttribute one = isSystemAttributeService.getIsSystemAttributeByKey("sys.face.limit");
+        String sysAttrValue = null;
+        if (one != null) {
+            sysAttrValue = one.getSysAttrValue();
+        }
+        String url = serverConfig.getUrl();
+        return CommonResult.success(sysUserCharacteristicService.insertUserFace(file, userName, sysAttrValue, url));
+    }
+
+}

+ 231 - 24
ktg-admin/src/main/java/com/ktg/web/controller/system/SysUserController.java

@@ -1,21 +1,7 @@
 package com.ktg.web.controller.system;
 
-import java.util.List;
-import java.util.stream.Collectors;
-import javax.servlet.http.HttpServletResponse;
-import org.apache.commons.lang3.ArrayUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.multipart.MultipartFile;
+import cn.hutool.core.lang.Assert;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.ktg.common.annotation.Log;
 import com.ktg.common.constant.UserConstants;
 import com.ktg.common.core.controller.BaseController;
@@ -24,18 +10,49 @@ import com.ktg.common.core.domain.entity.SysRole;
 import com.ktg.common.core.domain.entity.SysUser;
 import com.ktg.common.core.page.TableDataInfo;
 import com.ktg.common.enums.BusinessType;
+import com.ktg.common.exception.ServiceException;
 import com.ktg.common.utils.SecurityUtils;
 import com.ktg.common.utils.StringUtils;
+import com.ktg.common.utils.bean.BeanValidators;
 import com.ktg.common.utils.poi.ExcelUtil;
+import com.ktg.iscs.domain.IsUnit;
+import com.ktg.iscs.domain.IsUserUnit;
+import com.ktg.iscs.domain.IsUserWorkstation;
+import com.ktg.iscs.service.IIsUnitService;
+import com.ktg.iscs.service.IIsUserUnitService;
+import com.ktg.iscs.service.IIsUserWorkstationService;
+import com.ktg.system.domain.SysUserRole;
+import com.ktg.system.mapper.SysUserRoleMapper;
+import com.ktg.system.service.ISysConfigService;
 import com.ktg.system.service.ISysPostService;
 import com.ktg.system.service.ISysRoleService;
 import com.ktg.system.service.ISysUserService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Validator;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * 用户信息
- * 
+ *
  * @author ruoyi
  */
+@Slf4j
+@Api(tags = "用户管理")
 @RestController
 @RequestMapping("/system/user")
 public class SysUserController extends BaseController
@@ -48,6 +65,20 @@ public class SysUserController extends BaseController
 
     @Autowired
     private ISysPostService postService;
+    @Autowired
+    private IIsUserWorkstationService iIsUserWorkstationService;
+    @Autowired
+    private IIsUserUnitService iIsUserUnitService;
+    @Autowired
+    private IIsUnitService iIsUnitService;
+    @Autowired
+    private ISysConfigService configService;
+    @Autowired
+    protected Validator validator;
+    @Autowired
+    protected ISysRoleService iSysRoleService;
+    @Autowired
+    private SysUserRoleMapper userRoleMapper;
 
     /**
      * 获取用户列表
@@ -55,7 +86,6 @@ public class SysUserController extends BaseController
     @GetMapping("/list")
     public TableDataInfo list(SysUser user)
     {
-        startPage();
         List<SysUser> list = userService.selectUserList(user);
         return getDataTable(list);
     }
@@ -65,23 +95,119 @@ public class SysUserController extends BaseController
     @PostMapping("/export")
     public void export(HttpServletResponse response, SysUser user)
     {
+        user.setB(false);
         List<SysUser> list = userService.selectUserList(user);
         ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class);
         util.exportExcel(response, list, "用户数据");
     }
 
-    @Log(title = "用户管理", businessType = BusinessType.IMPORT)
+    @Transactional
+    @Log(title = "用户管理-导入", businessType = BusinessType.IMPORT)
     @PreAuthorize("@ss.hasPermi('system:user:import')")
     @PostMapping("/importData")
     public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception
     {
+        String filename = file.getOriginalFilename();
+        String extension = FilenameUtils.getExtension(filename);
+        // 文件不是 .xlsx 或 .xls
+        Assert.isFalse(!"xls".equalsIgnoreCase(extension) && !"xlsx".equalsIgnoreCase(extension), "文件不是xls或xlsx,请重新上传!");
         ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class);
         List<SysUser> userList = util.importExcel(file.getInputStream());
         String operName = getUsername();
-        String message = userService.importUser(userList, updateSupport, operName);
-        return AjaxResult.success(message);
+        // String message = userService.importUser(userList, updateSupport, operName);
+
+        if (StringUtils.isNull(userList) || userList.size() == 0) {
+            throw new ServiceException("导入用户数据不能为空!");
+        }
+        int successNum = 0;
+        int failureNum = 0;
+        StringBuilder successMsg = new StringBuilder();
+        StringBuilder failureMsg = new StringBuilder();
+        String password = configService.selectConfigByKey("sys.user.initPassword");
+        for (SysUser user : userList) {
+            try {
+                // 验证是否存在这个用户
+                SysUser u = userService.selectUserByUserName(user.getUserName());
+                if (StringUtils.isNull(u)) {
+                    BeanValidators.validateWithException(validator, user);
+                    user.setPassword(SecurityUtils.encryptPassword(password));
+                    user.setCreateBy(operName);
+                    userService.insertUser(user);
+                    // 新增单位和角色关联关系
+                    addUserUnit(user, false);
+                    successNum++;
+                    successMsg.append("<br/>" + successNum + "、账号 " + user.getUserName() + " 导入成功");
+                } else if (updateSupport) {
+                    BeanValidators.validateWithException(validator, user);
+                    user.setUpdateBy(operName);
+                    userService.updateUser(user);
+                    // 先删除再新增
+                    addUserUnit(user, true);
+                    successNum++;
+                    successMsg.append("<br/>" + successNum + "、账号 " + user.getUserName() + " 更新成功");
+                } else {
+                    failureNum++;
+                    failureMsg.append("<br/>" + failureNum + "、账号 " + user.getUserName() + " 已存在");
+                }
+            } catch (Exception e) {
+                failureNum++;
+                String msg = "<br/>" + failureNum + "、账号 " + user.getUserName() + " 导入失败:";
+                failureMsg.append(msg + e.getMessage());
+                log.error(msg, e);
+            }
+        }
+        if (failureNum > 0) {
+            failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
+            throw new ServiceException(failureMsg.toString());
+        } else {
+            successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:");
+        }
+        return AjaxResult.success(successMsg.toString());
+    }
+
+    public Boolean addUserUnit(SysUser user, Boolean b) {
+        // b新增不需清理以前数据,更新需要
+        // 检查单位是否存在
+        if (StringUtils.isNotBlank(user.getUnitName())) {
+            IsUnit unit = iIsUnitService.getOne(Wrappers.<IsUnit>lambdaQuery()
+                    .eq(IsUnit::getUnitName, user.getUnitName()));
+            if (unit != null) {
+                if (b) {
+                    // 更新时先清理
+                    iIsUserUnitService.remove(Wrappers.<IsUserUnit>lambdaQuery().eq(IsUserUnit::getUserId, user.getUserId()));
+                }
+                IsUserUnit isUserUnit = new IsUserUnit();
+                isUserUnit.setUserId(user.getUserId());
+                isUserUnit.setUnitId(unit.getUnitId());
+                iIsUserUnitService.save(isUserUnit);
+            }
+        }
+        // 检查角色是否存在
+        if (StringUtils.isNotBlank(user.getRoleName())) {
+            List<String> roleNames = Arrays.asList(user.getRoleName().split(",")).stream()
+                    .map(String::trim) // 去除前后空格
+                    .map(s -> s.replaceAll("\\s+", "")) // 去除所有空白字符(包括空格、制表符、换行符等)
+                    .collect(Collectors.toList());
+            if (!roleNames.isEmpty()) {
+                List<SysRole> sysRoles = iSysRoleService.list(Wrappers.<SysRole>lambdaQuery().in(SysRole::getRoleName, roleNames));
+                if (b) {
+                    // 更新时先清理
+                    userRoleMapper.delete(Wrappers.<SysUserRole>lambdaQuery().eq(SysUserRole::getUserId, user.getUserId()));
+                }
+                ArrayList<SysUserRole> sysUserRoles = new ArrayList<>();
+                for (SysRole sysRole : sysRoles) {
+                    SysUserRole sysUserRole = new SysUserRole();
+                    sysUserRole.setUserId(user.getUserId());
+                    sysUserRole.setRoleId(sysRole.getRoleId());
+                    sysUserRoles.add(sysUserRole);
+                }
+                userRoleMapper.insertBatch(sysUserRoles);
+            }
+        }
+        return true;
     }
 
+    @ApiOperation("用户数据导入模板")
     @PostMapping("/importTemplate")
     public void importTemplate(HttpServletResponse response)
     {
@@ -107,6 +233,8 @@ public class SysUserController extends BaseController
             ajax.put(AjaxResult.DATA_TAG, sysUser);
             ajax.put("postIds", postService.selectPostListByUserId(userId));
             ajax.put("roleIds", sysUser.getRoles().stream().map(SysRole::getRoleId).collect(Collectors.toList()));
+            ajax.put("workstationIds", iIsUserWorkstationService.selectWorkstationListByUserId(userId));
+            ajax.put("unitIds", iIsUserUnitService.getUnitIds(userId));
         }
         return ajax;
     }
@@ -114,6 +242,7 @@ public class SysUserController extends BaseController
     /**
      * 新增用户
      */
+    @Transactional
     @PreAuthorize("@ss.hasPermi('system:user:add')")
     @Log(title = "用户管理", businessType = BusinessType.INSERT)
     @PostMapping
@@ -135,12 +264,37 @@ public class SysUserController extends BaseController
         }
         user.setCreateBy(getUsername());
         user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));
-        return toAjax(userService.insertUser(user));
+        user.setKeyCode(user.getKeyCode());
+        userService.insertUser(user);
+        // 关联岗位
+        if (user.getWorkstationIds() != null && user.getWorkstationIds().length > 0) {
+            ArrayList<IsUserWorkstation> isUserWorkstations = new ArrayList<>();
+            for (Long workstationId : user.getWorkstationIds()) {
+                IsUserWorkstation isUserWorkstation = new IsUserWorkstation();
+                isUserWorkstation.setUserId(user.getUserId());
+                isUserWorkstation.setWorkstationId(workstationId);
+                isUserWorkstations.add(isUserWorkstation);
+            }
+            iIsUserWorkstationService.saveBatch(isUserWorkstations);
+        }
+        // 关联单位
+        if (user.getUnitIds() != null && user.getUnitIds().length > 0) {
+            ArrayList<IsUserUnit> userUnits = new ArrayList<>();
+            for (Long unitId : user.getUnitIds()) {
+                IsUserUnit isUserUnit = new IsUserUnit();
+                isUserUnit.setUserId(user.getUserId());
+                isUserUnit.setUnitId(unitId);
+                userUnits.add(isUserUnit);
+            }
+            iIsUserUnitService.saveBatch(userUnits);
+        }
+        return toAjax(true);
     }
 
     /**
      * 修改用户
      */
+    @Transactional
     @PreAuthorize("@ss.hasPermi('system:user:edit')")
     @Log(title = "用户管理", businessType = BusinessType.UPDATE)
     @PutMapping
@@ -159,12 +313,40 @@ public class SysUserController extends BaseController
             return AjaxResult.error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在");
         }
         user.setUpdateBy(getUsername());
-        return toAjax(userService.updateUser(user));
+        userService.updateUser(user);
+        // 关联岗位
+        if (user.getWorkstationIds() != null && user.getWorkstationIds().length > 0) {
+            iIsUserWorkstationService.remove(Wrappers.<IsUserWorkstation>lambdaQuery()
+                    .eq(IsUserWorkstation::getUserId, user.getUserId()));
+            List<IsUserWorkstation> isUserWorkstations = new ArrayList<>();
+            for (Long workstationId : user.getWorkstationIds()) {
+                IsUserWorkstation isUserWorkstation = new IsUserWorkstation();
+                isUserWorkstation.setUserId(user.getUserId());
+                isUserWorkstation.setWorkstationId(workstationId);
+                isUserWorkstations.add(isUserWorkstation);
+            }
+            iIsUserWorkstationService.saveBatch(isUserWorkstations);
+        }
+        // 关联单位
+        if (user.getUnitIds() != null && user.getUnitIds().length > 0) {
+            iIsUserUnitService.remove(Wrappers.<IsUserUnit>lambdaQuery()
+                    .eq(IsUserUnit::getUserId, user.getUserId()));
+            List<IsUserUnit> userUnits = new ArrayList<>();
+            for (Long unitId : user.getUnitIds()) {
+                IsUserUnit isUserUnit = new IsUserUnit();
+                isUserUnit.setUserId(user.getUserId());
+                isUserUnit.setUnitId(unitId);
+                userUnits.add(isUserUnit);
+            }
+            iIsUserUnitService.saveBatch(userUnits);
+        }
+        return toAjax(true);
     }
 
     /**
      * 删除用户
      */
+    @Transactional
     @PreAuthorize("@ss.hasPermi('system:user:remove')")
     @Log(title = "用户管理", businessType = BusinessType.DELETE)
     @DeleteMapping("/{userIds}")
@@ -174,7 +356,18 @@ public class SysUserController extends BaseController
         {
             return error("当前用户不能删除");
         }
-        return toAjax(userService.deleteUserByIds(userIds));
+        userService.deleteUserByIds(userIds);
+        // 关联岗位
+        if (userIds.length > 0) {
+            iIsUserWorkstationService.remove(Wrappers.<IsUserWorkstation>lambdaQuery()
+                    .in(IsUserWorkstation::getUserId, userIds));
+        }
+        // 关联单位
+        if (userIds.length > 0) {
+            iIsUserUnitService.remove(Wrappers.<IsUserUnit>lambdaQuery()
+                    .in(IsUserUnit::getUserId, userIds));
+        }
+        return toAjax(true);
     }
 
     /**
@@ -192,6 +385,20 @@ public class SysUserController extends BaseController
         return toAjax(userService.resetPwd(user));
     }
 
+    /**
+     * 重置钥匙密码
+     */
+    @PreAuthorize("@ss.hasPermi('system:user:resetPwd')")
+    @Log(title = "用户管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/resetKeyCode")
+    public AjaxResult resetKeyCode(@RequestBody SysUser user)
+    {
+        userService.checkUserAllowed(user);
+        userService.checkUserDataScope(user.getUserId());
+        user.setKeyCode(user.getKeyCode());
+        return toAjax(userService.resetKeyCode(user));
+    }
+
     /**
      * 状态修改
      */

+ 12 - 9
ktg-admin/src/main/resources/application-druid.yml

@@ -1,21 +1,23 @@
 # 开发环境配置
 server:
     # 服务器的HTTP端口,默认为9090
-    port: 9090
+    port: 9190
 # 数据源配置
 spring:
     datasource:
         type: com.alibaba.druid.pool.DruidDataSource
-        driverClassName: com.mysql.cj.jdbc.Driver
+        #driverClassName: com.mysql.cj.jdbc.Driver
+        driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver # &#9888;️ 必改驱动类
         druid:
             # 主库数据源
             master:
-                url: jdbc:mysql://36.133.35.52:3306/iscs_dev?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
-                username: guoruandev
-                password: guoruan@#$devNUM1
-                #url: jdbc:mysql://100.113.202.103:3306/iscs_dev?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
-                #username: guoruandev
-                #password: guoruan@#$devNUM1
+                #url: jdbc:mysql://120.27.232.27:3306/iscs_dev_mars?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&rewriteBatchedStatements=true
+                url: jdbc:sqlserver://120.27.232.27:1433;DatabaseName=iscs_pro_mars;encrypt=false
+                username: sa
+                password: Mars2025
+                #url: jdbc:mysql://192.168.0.150:3306/iscs_dev_mars?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&rewriteBatchedStatements=true
+                #username: root
+                #password: Pm123456
             # 从库数据源
             slave:
                 # 从数据源开关/默认关闭
@@ -38,7 +40,8 @@ spring:
             # 配置一个连接在池中最大生存的时间,单位是毫秒
             maxEvictableIdleTimeMillis: 900000
             # 配置检测连接是否有效
-            validationQuery: SELECT 1 FROM DUAL
+            #validationQuery: SELECT 1 FROM DUAL
+            validationQuery: SELECT 1
             testWhileIdle: true
             testOnBorrow: false
             testOnReturn: false

+ 68 - 9
ktg-admin/src/main/resources/application.yml

@@ -3,17 +3,28 @@ ktg-mes:
   # 名称
   name: ktg
   # 版本
-  version: 3.8.2
+  version: mars-1.0.2
   # 版权年份
   copyrightYear: 2022
   # 实例演示开关
   demoEnabled: true
   # 文件路径 示例( Windows配置D:/ktg/uploadPath,Linux配置 /home/ktg/uploadPath)
   profile: /home/iscs
+  # 部署到服务器时需要开启
+  #prod: /prod-api    #服务器nginx时开启
+  prod:
   # 获取ip地址开关
-  addressEnabled: false
+  addressEnabled: true
   # 验证码类型 math 数组计算 char 字符验证
   captchaType: math
+  # 一个账号允许的同时在线数量,设置为0/null时表示无限制
+  onlineMax: 10
+  # 工作人角色
+  jtcolocker: jtcolocker
+  # 平台八大步骤
+  eightSteps: eightSteps
+  # 安卓八大步骤
+  androidEightSteps: androidEightSteps
 
 # 开发环境配置
 server:
@@ -49,9 +60,9 @@ spring:
   servlet:
      multipart:
        # 单个文件大小
-       max-file-size:  10MB
+       max-file-size:  5000MB
        # 设置总上传的文件大小
-       max-request-size:  20MB
+       max-request-size:  5000MB
   # 服务模块
   devtools:
     restart:
@@ -60,13 +71,13 @@ spring:
   # redis 配置
   redis:
     # 地址
-    host: localhost
+    host: 127.0.0.1
     # 端口,默认为6379
     port: 6379
     # 数据库索引
     database: 0
     # 密码
-    password:
+    password: mars2025
     # 连接超时时间
     timeout: 10s
     lettuce:
@@ -87,7 +98,7 @@ token:
     # 令牌密钥
     secret: abcdefghijklmnopqrstuvwxyz
     # 令牌有效期(默认30分钟)
-    expireTime: 30
+    expireTime: 120
 
 # MyBatis配置
 #mybatis:
@@ -110,9 +121,13 @@ mybatis-plus:
 
 # PageHelper分页插件
 pagehelper:
-  helperDialect: mysql
+  #helperDialect: mysql
+  #supportMethodsArguments: true
+  #params: count=countSql
+  helperDialect: sqlserver
   supportMethodsArguments: true
-  params: count=countSql
+  reasonable: true
+  #params: count=countSql
 
 # Swagger配置
 swagger:
@@ -137,3 +152,47 @@ minio:
   accessKey: your_key
   secretKey: your_secret
   bucketName: ktg-mes
+
+#邮件配置
+mail:
+  # 邮箱服务地址
+  #hostIp: 192.168.28.97
+  # 系统发件人邮箱地址
+  #fromMail: cgj@192.168.28.97
+  # HMailServer默认使用端口25
+  #port: 25
+  # 如果需要身份验证,设置为true
+  #auth: false
+  # 设置邮件发送人名称
+  #name: 系统管理员
+  # 设置公司名称
+  #company: 玛氏
+  # 检查计划邮件模板设定
+  #checkPlan: CHECK_PLAN
+  # 物资到期提醒模板
+  #reminder: MATERIALS_EXPIRATION_REMINDER
+  # 物资逾期告警模板
+  #alarm: MATERIALS_OVERDUE_ALARM
+
+### MQ CONFIG
+rocketmq:
+  namesrvAddr: localhost:9876 # NameServer地址
+  producers: # 生产者配置
+    order-producer: # 订单生产者
+      group: order_producer_group # 生产者组名
+      instanceName: order-producer-instance # 实例名称
+    payment-producer: # 支付生产者
+      group: payment_producer_group
+      retryTimesWhenSendFailed: 5 # 发送失败重试次数
+  consumers: # 消费者配置
+    order-consumer: # 订单消费者
+      group: order_consumer_group
+      topic: order_topic # 订阅的Topic
+      tags: "create || pay" # 订阅的Tag表达式
+      consumeThreadMin: 10 # 最小消费线程数
+      enabled: false # 是否启用
+    payment-consumer: # 支付消费者
+      group: payment_consumer_group
+      topic: payment_topic
+      tags: "*" # 订阅所有Tag
+      enabled: false

+ 2 - 2
ktg-admin/src/main/resources/banner.txt

@@ -1,4 +1,4 @@
-Application Version: ${ktg.version}
+Application Version: ${ktg-mes.version}
 Spring Boot Version: ${spring-boot.version}
                                           _        _ _
                                           | |      (_|_)
@@ -7,4 +7,4 @@ Spring Boot Version: ${spring-boot.version}
  | (_| | |_| | (_) | |  | |_| | (_| | | | |   <  __/ | |
   \__, |\__,_|\___/|_|   \__,_|\__,_|_| |_|_|\_\___| |_|
    __/ |                                          _/ |
-  |___/                                          |__/      --GuoRuan to be number one (国软最牛逼)
+  |___/                                          |__/      -- To be number one (博士)

+ 50 - 3
ktg-common/pom.xml

@@ -26,7 +26,6 @@
         </dependency>
 
 
-
         <!-- Spring框架基本的核心工具 -->
         <dependency>
             <groupId>org.springframework</groupId>
@@ -174,12 +173,12 @@
         <dependency>
             <groupId>com.baomidou</groupId>
             <artifactId>mybatis-plus-core</artifactId>
-            <version>3.5.7</version>
+            <version>3.5.4</version>
         </dependency>
         <dependency>
             <groupId>com.github.yulichang</groupId>
             <artifactId>mybatis-plus-join-core</artifactId>
-            <version>1.4.13</version>
+            <version>1.4.10</version>
         </dependency>
         <dependency>
             <groupId>io.swagger.core.v3</groupId>
@@ -187,6 +186,54 @@
             <version>2.2.20</version>
             <scope>compile</scope>
         </dependency>
+        <dependency>
+            <groupId>io.swagger</groupId>
+            <artifactId>swagger-annotations</artifactId>
+            <version>1.6.2</version>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.pdfbox</groupId>
+            <artifactId>pdfbox</artifactId>
+            <version>2.0.24</version>
+        </dependency>
+
+        <!-- 指纹 -->
+        <dependency>
+            <groupId>com.machinezoo.sourceafis</groupId>
+            <artifactId>sourceafis</artifactId>
+            <version>3.13.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.openpnp</groupId>
+            <artifactId>opencv</artifactId>
+            <version>4.7.0-0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.arcsoft.face</groupId>
+            <artifactId>arcsoft-sdk-face</artifactId>
+            <version>3.0.0.0</version>
+        </dependency>
+
+        <!--<dependency>
+            <groupId>com.arcsoft.face</groupId>
+            <artifactId>arcsoft-sdk-face</artifactId>
+            <version>4.2.1.0</version>
+        </dependency>-->
+
+        <dependency>
+            <groupId>com.aliyun.oss</groupId>
+            <artifactId>aliyun-sdk-oss</artifactId>
+            <version>3.17.4</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.rocketmq</groupId>
+            <artifactId>rocketmq-spring-boot-starter</artifactId>
+            <version>2.2.3</version>
+        </dependency>
 
     </dependencies>
 

+ 22 - 0
ktg-common/src/main/java/com/ktg/common/annotation/MarsDataScope.java

@@ -0,0 +1,22 @@
+package com.ktg.common.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * mars岗位数据权限过滤注解
+ *
+ * @author cgj
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface MarsDataScope
+{
+    /**
+     * 岗位表的别名
+     */
+    public String workstationAlias() default "";
+
+    public String userAlias() default "";
+
+}

+ 14 - 1
ktg-common/src/main/java/com/ktg/common/config/RuoYiConfig.java

@@ -5,7 +5,7 @@ import org.springframework.stereotype.Component;
 
 /**
  * 读取项目相关配置
- * 
+ *
  * @author ruoyi
  */
 @Component
@@ -33,6 +33,9 @@ public class RuoYiConfig
     /** 验证码类型 */
     private static String captchaType;
 
+    /** 请求占位符 */
+    private static String prod;
+
     public String getName()
     {
         return name;
@@ -101,6 +104,16 @@ public class RuoYiConfig
         RuoYiConfig.captchaType = captchaType;
     }
 
+    public static String getProd()
+    {
+        return prod;
+    }
+
+    public void setProd(String prod)
+    {
+        RuoYiConfig.prod = prod;
+    }
+
     /**
      * 获取导入上传路径
      */

+ 9 - 1
ktg-common/src/main/java/com/ktg/common/constant/Constants.java

@@ -4,7 +4,7 @@ import io.jsonwebtoken.Claims;
 
 /**
  * 通用常量信息
- * 
+ *
  * @author ruoyi
  */
 public class Constants
@@ -69,6 +69,11 @@ public class Constants
      */
     public static final String LOGIN_TOKEN_KEY = "login_tokens:";
 
+    /**
+     * 机柜登录用户 redis key
+     */
+    public static final String CABINET_LOGIN_TOKEN_KEY = "cabinet_login_tokens:";
+
     /**
      * 防重提交 redis key
      */
@@ -164,4 +169,7 @@ public class Constants
      */
     public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml",
             "org.springframework", "org.apache", "com.ruoyi.common.utils.file" };
+
+
+    public static final String SYS_ATTR = "sys_attr:";
 }

+ 3 - 4
ktg-common/src/main/java/com/ktg/common/constant/UserConstants.java

@@ -1,10 +1,8 @@
 package com.ktg.common.constant;
 
-import org.omg.CORBA.PUBLIC_MEMBER;
-
 /**
  * 用户常量信息
- * 
+ *
  * @author ruoyi
  */
 public class UserConstants
@@ -57,7 +55,7 @@ public class UserConstants
 
     /** Layout组件标识 */
     public final static String LAYOUT = "Layout";
-    
+
     /** ParentView组件标识 */
     public final static String PARENT_VIEW = "ParentView";
 
@@ -104,6 +102,7 @@ public class UserConstants
     public static final String TRANSFER_CODE ="TRANSFER_CODE"; //移库
     public static final String STOCKTAKING_CODE ="STOCKTAKING_CODE"; //盘库单
     public static final String FEEDBACK_CODE ="FEEDBACK_CODE"; //报工单
+    public static final String HARDWARE_TYPE_CODE="HARDWARE_TYPE_CODE"; //硬件类型
 
     /**
      * 单据的状态类型

+ 24 - 5
ktg-common/src/main/java/com/ktg/common/core/domain/BaseEntity.java

@@ -1,13 +1,14 @@
 package com.ktg.common.core.domain;
 
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+
 import java.io.Serializable;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
 
-import com.baomidou.mybatisplus.annotation.TableField;
-import com.fasterxml.jackson.annotation.JsonFormat;
-
 /**
  * Entity基类
  *
@@ -25,14 +26,14 @@ public class BaseEntity implements Serializable
     private String createBy;
 
     /** 创建时间 */
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(timezone="GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
     private Date createTime;
 
     /** 更新者 */
     private String updateBy;
 
     /** 更新时间 */
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(timezone="GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
     private Date updateTime;
 
     /** 备注 */
@@ -42,6 +43,24 @@ public class BaseEntity implements Serializable
     @TableField(exist = false)
     private Map<String, Object> params;
 
+    @ApiModelProperty(value = "权限数据")
+    @TableField(exist = false)
+    private Map<String, Object> paramMap;
+
+    public Map<String, Object> getParamMap()
+    {
+        if (paramMap == null)
+        {
+            paramMap = new HashMap<>();
+        }
+        return paramMap;
+    }
+
+    public void setParamMap(Map<String, Object> paramMap)
+    {
+        this.paramMap = paramMap;
+    }
+
     public String getSearchValue()
     {
         return searchValue;

+ 31 - 6
ktg-common/src/main/java/com/ktg/common/core/domain/entity/SysRole.java

@@ -1,18 +1,22 @@
 package com.ktg.common.core.domain.entity;
 
-import javax.validation.constraints.NotBlank;
-import javax.validation.constraints.Size;
-import org.apache.commons.lang3.builder.ToStringBuilder;
-import org.apache.commons.lang3.builder.ToStringStyle;
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.ktg.common.annotation.Excel;
 import com.ktg.common.annotation.Excel.ColumnType;
 import com.ktg.common.core.domain.BaseEntity;
+import lombok.Data;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
 
 /**
  * 角色表 sys_role
- * 
+ *
  * @author ruoyi
  */
+@Data
 public class SysRole extends BaseEntity
 {
     private static final long serialVersionUID = 1L;
@@ -37,6 +41,9 @@ public class SysRole extends BaseEntity
     @Excel(name = "数据范围", readConverterExp = "1=所有数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限,5=仅本人数据权限")
     private String dataScope;
 
+    @Excel(name = "数据范围", readConverterExp = "1=所有数据权限,2=自定义数据权限,3=本岗位数据权限,4=本岗位及以下数据权限,5=仅本人数据权限")
+    private String marsDataScope;
+
     /** 菜单树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示) */
     private boolean menuCheckStrictly;
 
@@ -51,14 +58,21 @@ public class SysRole extends BaseEntity
     private String delFlag;
 
     /** 用户是否存在此角色标识 默认不存在 */
+    @TableField(exist = false)
     private boolean flag = false;
 
     /** 菜单组 */
+    @TableField(exist = false)
     private Long[] menuIds;
 
     /** 部门组(数据权限) */
+    @TableField(exist = false)
     private Long[] deptIds;
 
+    /** 岗位组(mars数据权限) */
+    @TableField(exist = false)
+    private Long[] workstationIds;
+
     public SysRole()
     {
 
@@ -134,6 +148,16 @@ public class SysRole extends BaseEntity
         this.dataScope = dataScope;
     }
 
+    public String getMarsDataScope()
+    {
+        return marsDataScope;
+    }
+
+    public void setMarsDataScope(String marsDataScope)
+    {
+        this.marsDataScope = marsDataScope;
+    }
+
     public boolean isMenuCheckStrictly()
     {
         return menuCheckStrictly;
@@ -203,7 +227,7 @@ public class SysRole extends BaseEntity
     {
         this.deptIds = deptIds;
     }
-    
+
     @Override
     public String toString() {
         return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
@@ -212,6 +236,7 @@ public class SysRole extends BaseEntity
             .append("roleKey", getRoleKey())
             .append("roleSort", getRoleSort())
             .append("dataScope", getDataScope())
+            .append("marsDataScope", getMarsDataScope())
             .append("menuCheckStrictly", isMenuCheckStrictly())
             .append("deptCheckStrictly", isDeptCheckStrictly())
             .append("status", getStatus())

+ 94 - 20
ktg-common/src/main/java/com/ktg/common/core/domain/entity/SysUser.java

@@ -1,45 +1,53 @@
 package com.ktg.common.core.domain.entity;
 
-import java.util.Date;
-import java.util.List;
-import javax.validation.constraints.*;
-
-import com.ktg.common.core.domain.entity.SysDept;
-import com.ktg.common.core.domain.entity.SysRole;
-import org.apache.commons.lang3.builder.ToStringBuilder;
-import org.apache.commons.lang3.builder.ToStringStyle;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.ktg.common.annotation.Excel;
-import com.ktg.common.annotation.Excel.ColumnType;
 import com.ktg.common.annotation.Excel.Type;
-import com.ktg.common.annotation.Excels;
 import com.ktg.common.core.domain.BaseEntity;
 import com.ktg.common.xss.Xss;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import javax.validation.constraints.Email;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
 
 /**
  * 用户对象 sys_user
- * 
+ *
  * @author ktg
  */
+@EqualsAndHashCode(callSuper = true)
+@Data
 public class SysUser extends BaseEntity
 {
     private static final long serialVersionUID = 1L;
 
     /** 用户ID */
-    @Excel(name = "用户序号", cellType = ColumnType.NUMERIC, prompt = "用户编号")
+    @TableId(type = IdType.AUTO)
+    // @Excel(name = "人员编号", cellType = ColumnType.NUMERIC, prompt = "人员编号")
     private Long userId;
 
     /** 部门ID */
-    @Excel(name = "部门编号", type = Type.IMPORT)
+    // @Excel(name = "部门编号", type = Type.IMPORT)
     private Long deptId;
 
     /** 用户账号 */
-    @Excel(name = "登录名称")
+    @Excel(name = "工号")
     private String userName;
 
     /** 用户昵称 */
-    @Excel(name = "用户名称")
+    @Excel(name = "姓名")
     private String nickName;
 
     /** 用户邮箱 */
@@ -47,11 +55,11 @@ public class SysUser extends BaseEntity
     private String email;
 
     /** 手机号码 */
-    @Excel(name = "手机号码")
+    @Excel(name = "联系电话")
     private String phonenumber;
 
     /** 用户性别 */
-    @Excel(name = "用户性别", readConverterExp = "0=男,1=女,2=未知")
+    @Excel(name = "用户性别(男/女)", readConverterExp = "0=男,1=女,2=未知")
     private String sex;
 
     /** 用户头像 */
@@ -60,11 +68,15 @@ public class SysUser extends BaseEntity
     /** 密码 */
     private String password;
 
+    /** 钥匙密码 */
+    private String keyCode;
+
     /** 盐加密 */
+    @TableField(exist = false)
     private String salt;
 
     /** 帐号状态(0正常 1停用) */
-    @Excel(name = "帐号状态", readConverterExp = "0=正常,1=停用")
+    @Excel(name = "帐号状态(正常/停用)", readConverterExp = "0=正常,1=停用")
     private String status;
 
     /** 删除标志(0代表存在 2代表删除) */
@@ -79,24 +91,66 @@ public class SysUser extends BaseEntity
     private Date loginDate;
 
     /** 部门对象 */
-    @Excels({
+    /*@Excels({
         @Excel(name = "部门名称", targetAttr = "deptName", type = Type.EXPORT),
         @Excel(name = "部门负责人", targetAttr = "leader", type = Type.EXPORT)
-    })
+    })*/
+    @TableField(exist = false)
     private SysDept dept;
 
     /** 角色对象 */
+    @TableField(exist = false)
     private List<SysRole> roles;
 
     /** 角色组 */
+    @TableField(exist = false)
     private Long[] roleIds;
 
     /** 岗位组 */
+    @TableField(exist = false)
     private Long[] postIds;
 
+    /** mars岗位组 */
+    @TableField(exist = false)
+    private Long[] workstationIds;
+
+    /** 单位组 */
+    @TableField(exist = false)
+    private Long[] unitIds;
+
     /** 角色ID */
+    @TableField(exist = false)
     private Long roleId;
 
+    @TableField(exist = false)
+    private String roleKey;
+
+    @ApiModelProperty(value = "单位ID")
+    @TableField(exist = false)
+    private Long unitId;
+
+    @ApiModelProperty(value = "岗位ID")
+    @TableField(exist = false)
+    private Long workstationId;
+
+    @ApiModelProperty(value = "单位")
+    @TableField(exist = false)
+    @Excel(name = "单位")
+    private String unitName;
+
+    @ApiModelProperty(value = "角色")
+    @TableField(exist = false)
+    @Excel(name = "角色")
+    private String roleName;
+
+    @ApiModelProperty(value = "查询条件的用户id")
+    @TableField(exist = false)
+    private Set<Long> userIds;
+
+    // 是否需要执行分页,为导出
+    @TableField(exist = false)
+    private Boolean b;
+
     public SysUser()
     {
 
@@ -217,6 +271,16 @@ public class SysUser extends BaseEntity
         this.password = password;
     }
 
+    public String getKeyCode()
+    {
+        return keyCode;
+    }
+
+    public void setKeyCode(String keyCode)
+    {
+        this.keyCode = keyCode;
+    }
+
     public String getSalt()
     {
         return salt;
@@ -317,6 +381,16 @@ public class SysUser extends BaseEntity
         this.roleId = roleId;
     }
 
+    public String getRoleKey()
+    {
+        return roleKey;
+    }
+
+    public void setRoleKey(String roleKey)
+    {
+        this.roleKey = roleKey;
+    }
+
     @Override
     public String toString() {
         return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)

+ 27 - 2
ktg-common/src/main/java/com/ktg/common/core/domain/model/BaseBean.java

@@ -1,11 +1,16 @@
 package com.ktg.common.core.domain.model;
 
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
 import java.io.Serializable;
 import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * Bean基类
@@ -18,18 +23,20 @@ public class BaseBean implements Serializable
     private static final long serialVersionUID = 1L;
 
     @Schema(description = "创建人")
+    @TableField(fill = FieldFill.INSERT)
     private String createBy;
 
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(timezone="GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
     @Schema(description = "创建时间")
     private Date createTime;
 
     /** 更新者 */
     @Schema(description = "更新者")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
     private String updateBy;
 
     /** 更新时间 */
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(timezone="GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
     @Schema(description = "更新时间")
     private Date updateTime;
 
@@ -37,4 +44,22 @@ public class BaseBean implements Serializable
     @Schema(description = "备注")
     private String remark;
 
+    @ApiModelProperty(value = "权限数据")
+    @TableField(exist = false)
+    private Map<String, Object> paramMap;
+
+    public Map<String, Object> getParamMap()
+    {
+        if (paramMap == null)
+        {
+            paramMap = new HashMap<>();
+        }
+        return paramMap;
+    }
+
+    public void setParamMap(Map<String, Object> paramMap)
+    {
+        this.paramMap = paramMap;
+    }
+
 }

+ 19 - 0
ktg-common/src/main/java/com/ktg/common/core/domain/model/FingerprintLoginBody.java

@@ -0,0 +1,19 @@
+package com.ktg.common.core.domain.model;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 指纹登录参数
+ *
+ * @author cgj
+ * @date 2025-01-14
+ */
+@Data
+public class FingerprintLoginBody
+{
+
+    @ApiModelProperty(value = "输入的指纹地址")
+    private String inputImg;
+
+}

+ 11 - 8
ktg-common/src/main/java/com/ktg/common/core/domain/model/LoginUser.java

@@ -1,17 +1,20 @@
 package com.ktg.common.core.domain.model;
 
-import java.util.Collection;
-import java.util.Set;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.userdetails.UserDetails;
 import com.alibaba.fastjson.annotation.JSONField;
 import com.ktg.common.core.domain.entity.SysUser;
+import lombok.Data;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.Collection;
+import java.util.Set;
 
 /**
  * 登录用户身份权限
- * 
+ *
  * @author ruoyi
  */
+@Data
 public class LoginUser implements UserDetails
 {
     private static final long serialVersionUID = 1L;
@@ -144,7 +147,7 @@ public class LoginUser implements UserDetails
 
     /**
      * 指定用户是否解锁,锁定的用户无法进行身份验证
-     * 
+     *
      * @return
      */
     @JSONField(serialize = false)
@@ -156,7 +159,7 @@ public class LoginUser implements UserDetails
 
     /**
      * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
-     * 
+     *
      * @return
      */
     @JSONField(serialize = false)
@@ -168,7 +171,7 @@ public class LoginUser implements UserDetails
 
     /**
      * 是否可用 ,禁用的用户不能身份验证
-     * 
+     *
      * @return
      */
     @JSONField(serialize = false)

+ 15 - 7
ktg-common/src/main/java/com/ktg/common/core/redis/RedisCache.java

@@ -1,11 +1,5 @@
 package com.ktg.common.core.redis;
 
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.BoundSetOperations;
 import org.springframework.data.redis.core.HashOperations;
@@ -13,6 +7,9 @@ import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.core.ValueOperations;
 import org.springframework.stereotype.Component;
 
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
 /**
  * spring redis 工具类
  *
@@ -74,6 +71,17 @@ public class RedisCache
         return redisTemplate.expire(key, timeout, unit);
     }
 
+    /**
+     * 获取有效时间
+     *
+     * @param key Redis键
+     * @return 过期时间,单位秒
+     */
+    public Long getTtl(final String key)
+    {
+        return redisTemplate.getExpire(key);
+    }
+
     /**
      * 获得缓存的基本对象。
      *
@@ -211,7 +219,7 @@ public class RedisCache
 
     /**
      * 删除Hash中的数据
-     * 
+     *
      * @param key
      * @param hKey
      */

+ 96 - 56
ktg-common/src/main/java/com/ktg/common/utils/DateUtils.java

@@ -1,23 +1,20 @@
 package com.ktg.common.utils;
 
+import org.apache.commons.lang3.time.DateFormatUtils;
+
 import java.lang.management.ManagementFactory;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.time.LocalTime;
-import java.time.ZoneId;
-import java.time.ZonedDateTime;
+import java.time.*;
+import java.time.format.DateTimeFormatter;
 import java.util.Date;
-import org.apache.commons.lang3.time.DateFormatUtils;
 
 /**
  * 时间工具类
- * 
+ *
  * @author ruoyi
  */
-public class DateUtils extends org.apache.commons.lang3.time.DateUtils
-{
+public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
     public static String YYYY = "yyyy";
 
     public static String YYYY_MM = "yyyy-MM";
@@ -29,63 +26,52 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
     public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
 
     private static String[] parsePatterns = {
-            "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM", 
+            "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
             "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
             "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};
 
     /**
      * 获取当前Date型日期
-     * 
+     *
      * @return Date() 当前日期
      */
-    public static Date getNowDate()
-    {
+    public static Date getNowDate() {
         return new Date();
     }
 
     /**
      * 获取当前日期, 默认格式为yyyy-MM-dd
-     * 
+     *
      * @return String
      */
-    public static String getDate()
-    {
+    public static String getDate() {
         return dateTimeNow(YYYY_MM_DD);
     }
 
-    public static final String getTime()
-    {
+    public static final String getTime() {
         return dateTimeNow(YYYY_MM_DD_HH_MM_SS);
     }
 
-    public static final String dateTimeNow()
-    {
+    public static final String dateTimeNow() {
         return dateTimeNow(YYYYMMDDHHMMSS);
     }
 
-    public static final String dateTimeNow(final String format)
-    {
+    public static final String dateTimeNow(final String format) {
         return parseDateToStr(format, new Date());
     }
 
-    public static final String dateTime(final Date date)
-    {
+    public static final String dateTime(final Date date) {
         return parseDateToStr(YYYY_MM_DD, date);
     }
 
-    public static final String parseDateToStr(final String format, final Date date)
-    {
+    public static final String parseDateToStr(final String format, final Date date) {
         return new SimpleDateFormat(format).format(date);
     }
 
-    public static final Date dateTime(final String format, final String ts)
-    {
-        try
-        {
+    public static final Date dateTime(final String format, final String ts) {
+        try {
             return new SimpleDateFormat(format).parse(ts);
-        }
-        catch (ParseException e)
-        {
+        } catch (ParseException e) {
             throw new RuntimeException(e);
         }
     }
@@ -93,8 +79,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
     /**
      * 日期路径 即年/月/日 如2018/08/08
      */
-    public static final String datePath()
-    {
+    public static final String datePath() {
         Date now = new Date();
         return DateFormatUtils.format(now, "yyyy/MM/dd");
     }
@@ -102,8 +87,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
     /**
      * 日期路径 即年/月/日 如20180808
      */
-    public static final String dateTime()
-    {
+    public static final String dateTime() {
         Date now = new Date();
         return DateFormatUtils.format(now, "yyyyMMdd");
     }
@@ -111,18 +95,13 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
     /**
      * 日期型字符串转化为日期 格式
      */
-    public static Date parseDate(Object str)
-    {
-        if (str == null)
-        {
+    public static Date parseDate(Object str) {
+        if (str == null) {
             return null;
         }
-        try
-        {
+        try {
             return parseDate(str.toString(), parsePatterns);
-        }
-        catch (ParseException e)
-        {
+        } catch (ParseException e) {
             return null;
         }
     }
@@ -130,8 +109,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
     /**
      * 获取服务器启动时间
      */
-    public static Date getServerStartDate()
-    {
+    public static Date getServerStartDate() {
         long time = ManagementFactory.getRuntimeMXBean().getStartTime();
         return new Date(time);
     }
@@ -139,16 +117,14 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
     /**
      * 计算相差天数
      */
-    public static int differentDaysByMillisecond(Date date1, Date date2)
-    {
+    public static int differentDaysByMillisecond(Date date1, Date date2) {
         return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24)));
     }
 
     /**
      * 计算两个时间差
      */
-    public static String getDatePoor(Date endDate, Date nowDate)
-    {
+    public static String getDatePoor(Date endDate, Date nowDate) {
         long nd = 1000 * 24 * 60 * 60;
         long nh = 1000 * 60 * 60;
         long nm = 1000 * 60;
@@ -166,11 +142,53 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
         return day + "天" + hour + "小时" + min + "分钟";
     }
 
+
+    /**
+     * 计算设定时间和当前时间相差秒数
+     *
+     * @param endDate
+     * @return
+     */
+    public static Long getDatePoorSec(Date endDate) {
+        // 定义日期时间格式
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+        // 设定时间 planTime
+        String planTimeStr = DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", endDate);
+        LocalDateTime planTime = LocalDateTime.parse(planTimeStr, formatter);
+        // 获取当前时间
+        LocalDateTime currentTime = LocalDateTime.now();
+        // 计算两个时间点之间的持续时间
+        Duration duration = Duration.between(currentTime, planTime);
+        // 获取秒数差
+        long secondsDifference = duration.getSeconds();
+        return secondsDifference;
+    }
+
+    /**
+     * 根据时间和需要的秒数,生成新的时间
+     *
+     * @param date
+     * @param additionalSeconds
+     * @return
+     */
+    public static Date getNewDateTime(Date date, long additionalSeconds) {
+
+        // 将Date对象转换为毫秒数
+        long milliseconds = date.getTime();
+        // 将毫秒数转换为秒数
+        long seconds = milliseconds / 1000;
+        // 加上一定的秒数,例如加60秒
+        long newSeconds = seconds + additionalSeconds;
+        // 将新的秒数转换回毫秒数
+        long newMilliseconds = newSeconds * 1000;
+        // 创建一个新的Date对象
+        return new Date(newMilliseconds);
+    }
+
     /**
      * 增加 LocalDateTime ==> Date
      */
-    public static Date toDate(LocalDateTime temporalAccessor)
-    {
+    public static Date toDate(LocalDateTime temporalAccessor) {
         ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault());
         return Date.from(zdt.toInstant());
     }
@@ -178,10 +196,32 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
     /**
      * 增加 LocalDate ==> Date
      */
-    public static Date toDate(LocalDate temporalAccessor)
-    {
+    public static Date toDate(LocalDate temporalAccessor) {
         LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0));
         ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
         return Date.from(zdt.toInstant());
     }
+
+    /**
+     * 输入1-7获取包括当日的下一个周几的日期
+     */
+    public static String getWeekDayDate(int weekDay) {
+        int dayOfWeekInput = weekDay;
+        // 将输入转换为DayOfWeek枚举
+        DayOfWeek inputDayOfWeek = DayOfWeek.of(dayOfWeekInput);
+        // 获取当前日期
+        LocalDate today = LocalDate.now();
+        // 计算下一个指定周几的日期
+        LocalDate nextSpecifiedDay = today.with(inputDayOfWeek);
+        if (nextSpecifiedDay.isBefore(today)) {
+            nextSpecifiedDay = nextSpecifiedDay.plusWeeks(1);
+        }
+        // 格式化日期为YYYY-MM-dd格式
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+        String formattedDate = nextSpecifiedDay.format(formatter);
+        // 输出结果
+        System.out.println("下一个" + inputDayOfWeek + "的日期是: " + formattedDate);
+        return formattedDate;
+    }
+
 }

+ 20 - 0
ktg-common/src/main/java/com/ktg/common/utils/FileDriveLetterUtils.java

@@ -0,0 +1,20 @@
+package com.ktg.common.utils;
+
+/**
+ * 文件盘符
+ *
+ * @author cgj
+ */
+public class FileDriveLetterUtils {
+
+    public static String getDriveLetter() {
+        String path = System.getProperty("user.dir");
+        String driveLetter = path.substring(0, 2);
+        String os = System.getProperty("os.name").toLowerCase();
+        if (os.contains("linux")) {
+            driveLetter = "";
+        }
+        return driveLetter;
+    }
+
+}

+ 119 - 0
ktg-common/src/main/java/com/ktg/common/utils/FingerprintComparisonByDat.java

@@ -0,0 +1,119 @@
+package com.ktg.common.utils;
+
+
+import cn.hutool.core.util.ObjectUtil;
+import com.google.common.collect.Lists;
+import com.ktg.common.vo.VerificationVO;
+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.File;
+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) {
+                    // 输入的指纹和指纹库中的指纹进行对比,找到当前线程中的分数最高的指纹
+                    boolean exists = new File(matcherDat).exists();
+                    if (!exists) {
+                        continue;
+                    }
+                    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;
+    }
+
+}
+
+

+ 110 - 0
ktg-common/src/main/java/com/ktg/common/utils/FingerprintComparisonByImg.java

@@ -0,0 +1,110 @@
+package com.ktg.common.utils;
+
+
+import cn.hutool.core.util.ObjectUtil;
+import com.google.common.collect.Lists;
+import com.ktg.common.vo.VerificationVO;
+import com.machinezoo.sourceafis.FingerprintImage;
+import com.machinezoo.sourceafis.FingerprintImageOptions;
+import com.machinezoo.sourceafis.FingerprintMatcher;
+import com.machinezoo.sourceafis.FingerprintTemplate;
+import lombok.extern.slf4j.Slf4j;
+
+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>
+ * 指纹比对算法(图片)
+ * </p>
+ *
+ * @author CGJ
+ * @version 1.0
+ * @since 2025年03月07日 11:51
+ */
+@Slf4j
+public class FingerprintComparisonByImg {
+
+    // 相似度门槛
+    private static final double THRESHOLD = 40; // 相当于错误率是0.01%,THRESHOLD越高,错误率越低
+    private static final ExecutorService THREAD_POOL_EXECUTOR = Executors.newFixedThreadPool(4); // 线程池
+
+    public static VerificationVO completableFutureComparison(final String inputImg, final Set<String> matcherImg) {
+        // 转成list
+        List<String> matcherImgList = new ArrayList<>(matcherImg);
+        // 切分四等份
+        int denominator = 1;
+        if (matcherImgList.size() >= 4) {
+            denominator = 4;
+        }
+        List<List<String>> averageMatcherImgList = Lists.partition(matcherImgList, matcherImgList.size() / denominator);
+        // 构建四个线程进行处理,防止人员过多对比速度太慢
+        CompletableFuture<VerificationVO>[] completableFutureArray = averageMatcherImgList.stream().map(
+                partitionFingerprint -> CompletableFuture.supplyAsync(
+                        () -> comparison(inputImg, 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.getFingerprintImg(), max.getScore());
+            return max;
+        }
+        return null;
+    }
+
+    public static VerificationVO comparison(final String inputImg, final List<String> matcherImgList) {
+        try {
+            // 开始解析输入的指纹到指纹模板
+            byte[] bytes = Files.readAllBytes(Paths.get(inputImg));
+            FingerprintImageOptions dpi = new FingerprintImageOptions().dpi(500);
+            FingerprintImage fingerprintImage = new FingerprintImage(bytes, dpi);
+            FingerprintTemplate fingerprintTemplate = new FingerprintTemplate(fingerprintImage);
+
+            if (!matcherImgList.isEmpty()) {
+                // 输入的被验证指纹
+                FingerprintMatcher matcher = new FingerprintMatcher(fingerprintTemplate);
+                // 最匹配的指纹
+                String match = null;
+                // 峰值
+                double high = 0;
+                for (String matcherImg : matcherImgList) {
+                    // 输入的指纹和指纹库中的指纹进行对比,找到当前线程中的分数最高的指纹
+                    double score = matcher.match(new FingerprintTemplate(new FingerprintImage(Files.readAllBytes(Paths.get(matcherImg)), dpi)));
+                    if (score > high) {
+                        high = score;
+                        match = matcherImg;
+                    }
+                }
+                return high >= THRESHOLD ? new VerificationVO().setFingerprintImg(match).setScore(high) : null;
+            }
+        } catch (Exception e) {
+            log.error("指纹比对异常:{}", e.getMessage());
+        }
+        return null;
+    }
+
+}
+
+

+ 63 - 0
ktg-common/src/main/java/com/ktg/common/utils/PDFToImgUtil.java

@@ -0,0 +1,63 @@
+package com.ktg.common.utils;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.rendering.PDFRenderer;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * PDF转换成长图片工具
+ *
+ * @author cgj
+ */
+public class PDFToImgUtil {
+
+    public static String PDFToLongImgUtil(String pdfFilePath) {
+        String outputFilePath = pdfFilePath.replace(".pdf", ".png"); // 输出文件路径
+        try (PDDocument document = PDDocument.load(new File(pdfFilePath))) {
+            PDFRenderer pdfRenderer = new PDFRenderer(document);
+
+            int totalHeight = 0;
+            int maxWidth = 0;
+            int dpi = 120; // 设置DPI
+
+            // 获取每页图片的尺寸,并计算总高度和最大宽度
+            BufferedImage[] images = new BufferedImage[document.getNumberOfPages()];
+            for (int page = 0; page < document.getNumberOfPages(); ++page) {
+                images[page] = pdfRenderer.renderImageWithDPI(page, dpi);
+                totalHeight += images[page].getHeight();
+                maxWidth = Math.max(maxWidth, images[page].getWidth());
+            }
+
+            // 创建一个新的BufferedImage来存储合并后的图片
+            BufferedImage longImage = new BufferedImage(maxWidth, totalHeight, BufferedImage.TYPE_INT_RGB);
+            Graphics2D g2d = longImage.createGraphics();
+            try {
+                g2d.setColor(Color.WHITE); // 设置背景颜色为白色
+                g2d.fillRect(0, 0, maxWidth, totalHeight);
+
+                // 绘制每页图片到新的BufferedImage上
+                int currentY = 0;
+                for (BufferedImage image : images) {
+                    g2d.drawImage(image, 0, currentY, null);
+                    currentY += image.getHeight();
+                    image.flush();
+                }
+            } finally {
+                g2d.dispose(); // 确保Graphics2D对象被正确释放
+            }
+
+            // 保存合并后的图片
+            ImageIO.write(longImage, "PNG", new File(outputFilePath));
+            System.out.println("PDF转换为长图片成功!");
+        } catch (IOException e) {
+            System.err.println("发生错误: " + e.getMessage());
+        }
+        return outputFilePath;
+    }
+
+}

+ 344 - 0
ktg-common/src/main/java/com/ktg/common/utils/face/ArcSoftMothodUtil.java

@@ -0,0 +1,344 @@
+package com.ktg.common.utils.face;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.ObjectUtil;
+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 com.ktg.common.config.RuoYiConfig;
+import com.ktg.common.utils.FileDriveLetterUtils;
+import com.ktg.common.utils.StringUtils;
+import com.ktg.common.vo.FaceCutVO;
+import com.ktg.common.vo.FaceMatchVO;
+import lombok.extern.slf4j.Slf4j;
+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 prodApi = RuoYiConfig.getProd();
+        if (StringUtils.isNotBlank(prodApi)) {
+            // 在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";
+            libarcsoftFaceDllPath = "D:\\work\\app\\appinstall\\ArcSoft_ArcFacePro_windows_x64_java_V4.2\\libs\\WIN64";
+        }
+        faceEngine = new FaceEngine(libarcsoftFaceDllPath);
+        //激活引擎
+        // 联网激活
+        int errorCode = faceEngine.activeOnline(appId, sdkKey);
+        // 离线激活
+        // int errorCode = faceEngine.activeOffline("D:\\work\\app\\appinstall\\ArcSoft_ArcFacePro_windows_x64_java_V4.2\\86M111YWX11JB4BZ.dat");
+
+        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(MultipartFile file, Long userId, String url) throws IOException {
+        int errorCode;
+        String prodApi = RuoYiConfig.getProd();
+        // 1.判断这个文件是否有效
+        Assert.isFalse(file.isEmpty(), "请上传人脸文件!");
+        // 2.-----------------开始存储上传的照片--------------------
+        // 人脸存储基础路径
+        String profile = RuoYiConfig.getProfile();
+        String basePath = FileDriveLetterUtils.getDriveLetter() + profile + "/face/" + userId + "/";
+        /*if (StringUtils.isBlank(prodApi)) {
+            basePath = "C:" + basePath;
+        }*/
+        // 原文件转存后的路径
+        String imagePath;
+        // 原文件转存后的前端请求路径
+        String imageUrl = null;
+
+        // 时间戳
+        long currentTimestamp = System.currentTimeMillis();
+        // 设置文件路径
+        String filePath = basePath;
+        // 确保目录存在,不存在则创建一个
+        Path uploadPath = Paths.get(filePath);
+        if (!Files.exists(uploadPath)) {
+            Files.createDirectories(uploadPath);
+        }
+        // 获取文件名并构建目标路径
+        String fileName = userId + "_" + currentTimestamp + "_" + file.getOriginalFilename();
+        Path targetLocation = uploadPath.resolve(fileName);
+        imagePath = filePath + fileName;
+        // 获取前端的请求路径地址
+        /*if (imagePath.contains("C:" + profile)) {
+            imageUrl = imagePath.replace("C:" + profile, url + "/profile");
+        } else if (imagePath.contains(profile)) {
+            imageUrl = imagePath.replace(profile, url + "/prod-api" + "/profile");
+        }*/
+        if (StringUtils.isBlank(prodApi)) {
+            imageUrl = imagePath.replace(FileDriveLetterUtils.getDriveLetter() + profile, url + "/profile");
+        } else {
+            imageUrl = imagePath.replace(profile, url + "/prod-api" + "/profile");
+        }
+        // 将文件写入目标路径
+        file.transferTo(targetLocation.toFile());
+
+        // 3--------------------------开始读取照片的特征值-------------------------------
+        ImageInfo imageInfo = null;
+        try {
+            imageInfo = getRGBData(new File(imagePath));
+        } catch (Exception e) {
+            // 如果解析失败 直接删除人脸文件
+            new File(imagePath).delete();
+            Assert.isTrue(false, "人脸文件无法解析!");
+        }
+        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);
+        // 离线激活
+        // errorCode = faceEngine.extractFaceFeature(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList.get(0), ExtractType.RECOGNIZE, 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) throws IOException {
+        int errorCode;
+        String prodApi = RuoYiConfig.getProd();
+        // 提取当前人脸的特征值,比对文件零时存储,人脸存储基础路径
+        String profile = RuoYiConfig.getProfile();
+        String basePath = FileDriveLetterUtils.getDriveLetter() + profile + "/face/" + 0 + "/";
+        /*if (StringUtils.isBlank(prodApi)) {
+            basePath = "C:" + basePath;
+        }*/
+        // 时间戳
+        long currentTimestamp = System.currentTimeMillis();
+        String filePath = basePath;
+        // 确保目录存在,不存在则创建一个
+        Path uploadPath = Paths.get(filePath);
+        if (!Files.exists(uploadPath)) {
+            Files.createDirectories(uploadPath);
+        }
+        // 获取文件名并构建目标路径
+        String fileName = filePath + 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);
+            // 离线激活
+            // errorCode = faceEngine.extractFaceFeature(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList.get(0), ExtractType.RECOGNIZE, 0, faceFeature);
+            Assert.isFalse(errorCode != ErrorInfo.MOK.getValue(), "获取人脸特征值失败!");
+            byte[] featureData = faceFeature.getFeatureData();
+
+            // 转成list
+            List<String> matcherFeatureDataList = new ArrayList<>(matcher);
+            /*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 errorCode1 = faceEngine.compareFaceFeature(targetFaceFeature, sourceFaceFeature, faceSimilar);
+                Assert.isFalse(errorCode1 != ErrorInfo.MOK.getValue(), "人脸对比异常" + errorCode1 + "!");
+                // 相似度
+                float score = faceSimilar.getScore();
+                if (score > THRESHOLD) {
+                    return new FaceMatchVO().setContent(matcherData).setScore((double) score);
+                }
+            }*/
+
+            // 切分四等份
+            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);
+        }
+    }
+
+}

+ 156 - 0
ktg-common/src/main/java/com/ktg/common/utils/face/FaceCutUtil.java

@@ -0,0 +1,156 @@
+package com.ktg.common.utils.face;
+
+import cn.hutool.core.lang.Assert;
+import com.ktg.common.config.RuoYiConfig;
+import com.ktg.common.utils.FileDriveLetterUtils;
+import com.ktg.common.utils.StringUtils;
+import com.ktg.common.vo.FaceCutVO;
+import org.opencv.core.*;
+import org.opencv.imgcodecs.Imgcodecs;
+import org.opencv.imgproc.Imgproc;
+import org.opencv.objdetect.CascadeClassifier;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/**
+ * 面部裁剪工具
+ *
+ * @author cgj
+ */
+public class FaceCutUtil
+{
+
+
+
+    // 初始化人脸探测器
+    static CascadeClassifier faceDetector;
+    /**
+     * 图片人脸检测
+     */
+    public static FaceCutVO imageFaceDetection(MultipartFile file, Long userId, String url) throws IOException {
+        String prodApi = RuoYiConfig.getProd();
+        // ------------------------加载OpenCV本地库--------------------------------------------
+        // System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
+        String loadPath = "/guoruan/app/opencv/build/java/x64/opencv_java470.dll";
+        if (StringUtils.isBlank(prodApi)) {
+            loadPath = "C:/work/app/install/opencv/build/java/x64/opencv_java470.dll";
+        }
+        System.load(loadPath);
+        // 从配置文件lbpcascade_frontalface.xml中创建一个人脸识别器,文件位于opencv安装目录中
+        String faceDetectorPath = "/guoruan/app/opencv/sources/data/haarcascades/haarcascade_frontalface_alt.xml";
+        if (StringUtils.isBlank(prodApi)) {
+            faceDetectorPath = "C:/work/app/install/opencv/sources/data/haarcascades/haarcascade_frontalface_alt.xml";
+        }
+        faceDetector = new CascadeClassifier(faceDetectorPath);
+
+        // -------------------- 定义文件的基础属性----------------------------
+        // 人脸存储基础路径
+        String profile = RuoYiConfig.getProfile();
+        String basePath = FileDriveLetterUtils.getDriveLetter() + profile + "/face/" + userId +"/";
+        /*if (StringUtils.isBlank(prodApi)) {
+            basePath = "C:" + basePath;
+        }*/
+        // 原文件裁剪后的路径
+        String content;
+        // 原文件转存后的路径
+        String imagePath;
+        // 原文件转存后的前端请求路径
+        String imageUrl = null;
+
+        // 时间戳
+        long currentTimestamp = System.currentTimeMillis();
+        // 设置文件路径
+        String filePath = basePath ;
+        // 确保目录存在,不存在则创建一个
+        Path uploadPath = Paths.get(filePath);
+        if (!Files.exists(uploadPath)) {
+            Files.createDirectories(uploadPath);
+        }
+        // 获取文件名并构建目标路径
+        String fileName = userId + "_" + currentTimestamp + "_" + file.getOriginalFilename();
+        Path targetLocation = uploadPath.resolve(fileName);
+        imagePath = filePath + fileName;
+        // 获取前端的请求路径地址
+        /*if (imagePath.contains("C:" + profile)) {
+            imageUrl = imagePath.replace("C:" + profile, url + "/profile");
+        } else if (imagePath.contains(profile)) {
+            imageUrl = imagePath.replace(profile, url + "/prod-api" + "/profile");
+        }*/
+        if (StringUtils.isBlank(prodApi)) {
+            imageUrl = imagePath.replace(FileDriveLetterUtils.getDriveLetter() + profile, url + "/profile");
+        } else {
+            imageUrl = imagePath.replace(FileDriveLetterUtils.getDriveLetter() + profile, url + "/prod-api" + "/profile");
+        }
+        // 将文件写入目标路径
+        file.transferTo(targetLocation.toFile());
+
+        // 读取测试图片
+        Mat image = Imgcodecs.imread(imagePath);
+        if (image.empty()) {
+            throw new RuntimeException("图片内存为空");
+        }
+
+        // 检测脸部
+        MatOfRect face = new MatOfRect();
+        // 检测图像中的人脸
+        faceDetector.detectMultiScale(image, face);
+        // 匹配Rect矩阵
+        Rect[] rects = face.toArray();
+        System.out.println("识别人脸个数: " + rects.length);
+        Assert.isFalse(rects.length > 1, "识别到多张人脸,请重新调整位置!");
+
+        // 识别图片中的所有人脸并分别保存
+        // int i = 1;
+        // 上面做了限制,现在其实只会有一张人脸
+        FaceCutVO faceCutVO = new FaceCutVO();
+        for (Rect rect : face.toArray()) {
+            Imgproc.rectangle(image, new Point(rect.x, rect.y), new Point(rect.x + rect.width, rect.y + rect.height), new Scalar(0, 255, 0), 3);
+            // 进行图片裁剪
+            String cutImgName = userId + "_" + currentTimestamp + "_cut.jpg";
+            content = filePath + cutImgName;
+            imageCut(imagePath, content, rect.x, rect.y, rect.width, rect.height);
+            // i++;
+            faceCutVO.setUserId(userId);
+            faceCutVO.setType("2");
+            faceCutVO.setContent(content);
+            faceCutVO.setImageUrl(imageUrl);
+            faceCutVO.setImagePath(imagePath);
+        }
+        // ---------------------以下为前端的裁剪动画展示--------------------
+        // 图片中人脸画框保存到本地
+        // Imgcodecs.imwrite("C:/work/file/test1.png", image);
+        // 展示图片
+        // HighGui.imshow("人脸识别", image);
+        // HighGui.waitKey(0);
+
+        return faceCutVO;
+    }
+
+    /**
+     * 裁剪人脸
+     *
+     * @param readPath 读取文件路径
+     * @param outPath  写出文件路径
+     * @param x        坐标X
+     * @param y        坐标Y
+     * @param width    截图宽度
+     * @param height   截图长度
+     */
+    public static void imageCut(String readPath, String outPath, int x, int y, int width, int height) {
+        // 原始图像
+        Mat image = Imgcodecs.imread(readPath);
+        // 截取的区域
+        Rect rect = new Rect(x, y, width, height);
+        Mat sub = new Mat(image,rect);
+        // Mat sub = image.submat(rect);
+        Mat mat = new Mat();
+        Size size = new Size(width, height);
+        // 人脸进行截图并保存
+        Imgproc.resize(sub, mat, size);
+        Imgcodecs.imwrite(outPath, mat);
+    }
+}

+ 195 - 0
ktg-common/src/main/java/com/ktg/common/utils/face/FaceCutUtil1.java

@@ -0,0 +1,195 @@
+package com.ktg.common.utils.face;
+
+import cn.hutool.core.lang.Assert;
+import com.ktg.common.config.RuoYiConfig;
+import com.ktg.common.utils.FileDriveLetterUtils;
+import com.ktg.common.utils.StringUtils;
+import com.ktg.common.vo.FaceCutVO;
+import org.opencv.core.*;
+import org.opencv.imgcodecs.Imgcodecs;
+import org.opencv.imgproc.Imgproc;
+import org.opencv.objdetect.CascadeClassifier;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collections;
+
+/**
+ * 面部裁剪工具
+ *
+ * @author cgj
+ */
+public class FaceCutUtil1
+{
+
+
+    // 初始化人脸探测器
+    static CascadeClassifier faceDetector;
+    /**
+     * 图片人脸检测
+     */
+    public static FaceCutVO imageFaceDetection(MultipartFile file, Long userId, String url) throws IOException {
+        String prodApi = RuoYiConfig.getProd();
+        // ------------------------加载OpenCV本地库--------------------------------------------
+        // System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
+        String loadPath = "/guoruan/app/opencv/build/java/x64/opencv_java470.dll";
+        if (StringUtils.isBlank(prodApi)) {
+            loadPath = "C:/work/app/install/opencv/build/java/x64/opencv_java470.dll";
+        }
+        System.load(loadPath);
+        // 从配置文件lbpcascade_frontalface.xml中创建一个人脸识别器,文件位于opencv安装目录中
+        String faceDetectorPath = "/guoruan/app/opencv/sources/data/haarcascades/haarcascade_frontalface_alt.xml";
+        if (StringUtils.isBlank(prodApi)) {
+            faceDetectorPath = "C:/work/app/install/opencv/sources/data/haarcascades/haarcascade_frontalface_alt.xml";
+        }
+        faceDetector = new CascadeClassifier(faceDetectorPath);
+
+        // -------------------- 定义文件的基础属性----------------------------
+        // 人脸存储基础路径
+        String profile = RuoYiConfig.getProfile();
+        String basePath = FileDriveLetterUtils.getDriveLetter() + profile + "/face/" + userId +"/";
+        /*if (StringUtils.isBlank(prodApi)) {
+            basePath = "C:" + basePath;
+        }*/
+        // 原文件裁剪后的路径
+        String content = null;
+        // 原文件转存后的路径
+        String imagePath;
+        // 原文件转存后的前端请求路径
+        String imageUrl = null;
+
+        // 时间戳
+        long currentTimestamp = System.currentTimeMillis();
+        // 设置文件路径
+        String filePath = basePath ;
+        // 确保目录存在,不存在则创建一个
+        Path uploadPath = Paths.get(filePath);
+        if (!Files.exists(uploadPath)) {
+            Files.createDirectories(uploadPath);
+        }
+        // 获取文件名并构建目标路径
+        String fileName = userId + "_" + currentTimestamp + "_" + file.getOriginalFilename();
+        Path targetLocation = uploadPath.resolve(fileName);
+        imagePath = filePath + fileName;
+        // 获取前端的请求路径地址
+        /*if (imagePath.contains("C:" + profile)) {
+            imageUrl = imagePath.replace("C:" + profile, url + "/profile");
+        } else if (imagePath.contains(profile)) {
+            imageUrl = imagePath.replace(profile, url + "/prod-api" + "/profile");
+        }*/
+        if (StringUtils.isBlank(prodApi)) {
+            imageUrl = imagePath.replace(FileDriveLetterUtils.getDriveLetter() + profile, url + "/profile");
+        } else {
+            imageUrl = imagePath.replace(FileDriveLetterUtils.getDriveLetter() + profile, url + "/prod-api" + "/profile");
+        }
+        // 将文件写入目标路径
+        file.transferTo(targetLocation.toFile());
+
+        // 读取测试图片
+        Mat image = Imgcodecs.imread(imagePath);
+        if (image.empty()) {
+            throw new RuntimeException("图片内存为空");
+        }
+
+        // 检测脸部
+        MatOfRect face = new MatOfRect();
+        // 检测图像中的人脸
+        faceDetector.detectMultiScale(image, face);
+        // 匹配Rect矩阵
+        Rect[] rects = face.toArray();
+        System.out.println("识别人脸个数: " + rects.length);
+        Assert.isFalse(rects.length > 1, "识别到多张人脸,请重新调整位置!");
+
+        // 识别图片中的所有人脸并分别保存
+        // int i = 1;
+        // 上面做了限制,现在其实只会有一张人脸
+        FaceCutVO faceCutVO = new FaceCutVO();
+        for (Rect rect : face.toArray()) {
+            Imgproc.rectangle(image, new Point(rect.x, rect.y), new Point(rect.x + rect.width, rect.y + rect.height), new Scalar(0, 255, 0), 3);
+            // 进行图片裁剪
+            String cutImgName = userId + "_" + currentTimestamp + "_cut.jpg";
+            content = filePath + cutImgName;
+            imageCut(imagePath, content, rect.x, rect.y, rect.width, rect.height);
+            // i++;
+            faceCutVO.setUserId(userId);
+            faceCutVO.setType("2");
+            faceCutVO.setContent(content);
+            faceCutVO.setImageUrl(imageUrl);
+            faceCutVO.setImagePath(imagePath);
+        }
+        // ---------------------以下为前端的裁剪动画展示--------------------
+        // 图片中人脸画框保存到本地
+        // Imgcodecs.imwrite("C:/work/file/test1.png", image);
+        // 展示图片
+        // HighGui.imshow("人脸识别", image);
+        // HighGui.waitKey(0);
+
+        // ---------------------------开始计算裁剪后的人脸直方图-----------------
+        Mat mat2 = conv_Mat(content); // 将路径对应的图像转换为灰度图像的Mat对象
+        if (mat2 == null) {
+            return null; // 如果转换失败,返回null
+        }
+        Mat hist2 = computeHistogram(mat2); // 计算第二个图像的直方图
+        faceCutVO.setMatData(MatChangeUtil.matToStr(hist2));
+        faceCutVO.setMatRows(hist2.rows());
+        faceCutVO.setMatCols(hist2.cols());
+        faceCutVO.setMatType(hist2.type());
+        return faceCutVO;
+    }
+
+    /**
+     * 裁剪人脸
+     *
+     * @param readPath 读取文件路径
+     * @param outPath  写出文件路径
+     * @param x        坐标X
+     * @param y        坐标Y
+     * @param width    截图宽度
+     * @param height   截图长度
+     */
+    public static void imageCut(String readPath, String outPath, int x, int y, int width, int height) {
+        // 原始图像
+        Mat image = Imgcodecs.imread(readPath);
+        // 截取的区域
+        Rect rect = new Rect(x, y, width, height);
+        // Mat sub = new Mat(image,rect);
+        Mat sub = image.submat(rect);
+        Mat mat = new Mat();
+        Size size = new Size(width, height);
+        // 人脸进行截图并保存
+        Imgproc.resize(sub, mat, size);
+        Imgcodecs.imwrite(outPath, mat);
+    }
+
+    /**
+     * 灰度化人脸并返回Mat对象
+     */
+    public static Mat conv_Mat(String imgPath) {
+        Mat mat1 = Imgcodecs.imread(imgPath); // 读取图像文件并转换为Mat对象
+        Mat mat2 = new Mat(); // 创建一个新的Mat对象用于存储灰度图像
+        Imgproc.cvtColor(mat1, mat2, Imgcodecs.IMREAD_COLOR); // 将彩色图像转换为灰度图像
+        MatOfRect faceDetections = new MatOfRect(); // 创建用于存储检测到的人脸区域的MatOfRect对象
+        faceDetector.detectMultiScale(mat1, faceDetections); // 检测人脸区域
+        for (Rect rect : faceDetections.toArray()) { // 遍历检测到的人脸区域
+            return new Mat(mat1, rect); // 返回包含人脸区域的子Mat对象
+        }
+        return null; // 如果没有检测到人脸,返回null
+    }
+
+    /**
+     * 计算直方图
+     */
+    private static Mat computeHistogram(Mat mat) {
+        Mat hist = new Mat(); // 创建用于存储直方图的Mat对象
+        MatOfFloat ranges = new MatOfFloat(0f, 256f); // 定义直方图的范围
+        MatOfInt histSize = new MatOfInt(100); // 定义直方图的大小(越大越精确,但计算速度越慢)
+        Imgproc.calcHist(Collections.singletonList(mat), new MatOfInt(0), new Mat(), hist, histSize, ranges); // 计算直方图
+        Core.normalize(hist, hist, 0, 1, Core.NORM_MINMAX); // 归一化直方图,使其值在0到1之间
+        return hist; // 返回计算好的直方图
+    }
+
+
+}

+ 170 - 0
ktg-common/src/main/java/com/ktg/common/utils/face/FaceEngineTest.java

@@ -0,0 +1,170 @@
+/*package com.ktg.common.utils.face;
+
+import com.arcsoft.face.*;
+import com.arcsoft.face.enums.DetectMode;
+import com.arcsoft.face.enums.DetectModel;
+import com.arcsoft.face.enums.DetectOrient;
+import com.arcsoft.face.enums.ErrorInfo;
+import com.arcsoft.face.toolkit.ImageInfo;
+import com.arcsoft.face.toolkit.ImageInfoEx;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.arcsoft.face.toolkit.ImageFactory.getGrayData;
+import static com.arcsoft.face.toolkit.ImageFactory.getRGBData;
+
+public class FaceEngineTest {
+
+
+    public static void main(String[] args) {
+
+        //从官网获取
+        String appId = "5j9Uw8b5t9svFzVyVjBrCXtizjojgnjXJrNAg64UUYU4";
+        String sdkKey = "7yGfT9CQVmTrXfBmmPYeJTK3YTREQSTbM4XNVjPWzRbj";
+        FaceEngine faceEngine = new FaceEngine("D:\\work\\app\\appinstall\\ArcSoft_ArcFacePro_windows_x64_java_V4.2\\libs\\WIN64");
+        //激活引擎
+        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("初始化引擎失败");
+        }
+
+
+        //人脸检测
+        ImageInfo imageInfo = getRGBData(new File("d:\\aaa.jpg"));
+        List<FaceInfo> faceInfoList = new ArrayList<FaceInfo>();
+        errorCode = faceEngine.detectFaces(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList);
+        System.out.println(faceInfoList);
+
+        //特征提取
+        FaceFeature faceFeature = new FaceFeature();
+        errorCode = faceEngine.extractFaceFeature(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList.get(0), faceFeature);
+        System.out.println("特征值大小:" + faceFeature.getFeatureData().length);
+
+        //人脸检测2
+        ImageInfo imageInfo2 = getRGBData(new File("d:\\ccc.jpg"));
+        List<FaceInfo> faceInfoList2 = new ArrayList<FaceInfo>();
+        errorCode = faceEngine.detectFaces(imageInfo2.getImageData(), imageInfo2.getWidth(), imageInfo2.getHeight(),imageInfo2.getImageFormat(), faceInfoList2);
+        System.out.println(faceInfoList2);
+
+        //特征提取2
+        FaceFeature faceFeature2 = new FaceFeature();
+        errorCode = faceEngine.extractFaceFeature(imageInfo2.getImageData(), imageInfo2.getWidth(), imageInfo2.getHeight(), imageInfo2.getImageFormat(), faceInfoList2.get(0), faceFeature2);
+        System.out.println("特征值大小:" + faceFeature2.getFeatureData().length);
+
+        //特征比对
+        FaceFeature targetFaceFeature = new FaceFeature();
+        targetFaceFeature.setFeatureData(faceFeature.getFeatureData());
+        FaceFeature sourceFaceFeature = new FaceFeature();
+        sourceFaceFeature.setFeatureData(faceFeature2.getFeatureData());
+        FaceSimilar faceSimilar = new FaceSimilar();
+
+        errorCode = faceEngine.compareFaceFeature(targetFaceFeature, sourceFaceFeature, faceSimilar);
+
+        System.out.println("相似度:" + faceSimilar.getScore());
+
+        //设置活体测试
+        errorCode = faceEngine.setLivenessParam(0.5f, 0.7f);
+        //人脸属性检测
+        FunctionConfiguration configuration = new FunctionConfiguration();
+        configuration.setSupportAge(true);
+        configuration.setSupportFace3dAngle(true);
+        configuration.setSupportGender(true);
+        configuration.setSupportLiveness(true);
+        errorCode = faceEngine.process(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList, configuration);
+
+
+        //性别检测
+        List<GenderInfo> genderInfoList = new ArrayList<GenderInfo>();
+        errorCode = faceEngine.getGender(genderInfoList);
+        System.out.println("性别:" + genderInfoList.get(0).getGender());
+
+        //年龄检测
+        List<AgeInfo> ageInfoList = new ArrayList<AgeInfo>();
+        errorCode = faceEngine.getAge(ageInfoList);
+        System.out.println("年龄:" + ageInfoList.get(0).getAge());
+
+        //3D信息检测
+        List<Face3DAngle> face3DAngleList = new ArrayList<Face3DAngle>();
+        errorCode = faceEngine.getFace3DAngle(face3DAngleList);
+        System.out.println("3D角度:" + face3DAngleList.get(0).getPitch() + "," + face3DAngleList.get(0).getRoll() + "," + face3DAngleList.get(0).getYaw());
+
+        //活体检测
+        List<LivenessInfo> livenessInfoList = new ArrayList<LivenessInfo>();
+        errorCode = faceEngine.getLiveness(livenessInfoList);
+        System.out.println("活体:" + livenessInfoList.get(0).getLiveness());
+
+
+        //IR属性处理
+        ImageInfo imageInfoGray = getGrayData(new File("d:\\IR_480p.jpg"));
+        List<FaceInfo> faceInfoListGray = new ArrayList<FaceInfo>();
+        errorCode = faceEngine.detectFaces(imageInfoGray.getImageData(), imageInfoGray.getWidth(), imageInfoGray.getHeight(), imageInfoGray.getImageFormat(), faceInfoListGray);
+
+        FunctionConfiguration configuration2 = new FunctionConfiguration();
+        configuration2.setSupportIRLiveness(true);
+        errorCode = faceEngine.processIr(imageInfoGray.getImageData(), imageInfoGray.getWidth(), imageInfoGray.getHeight(), imageInfoGray.getImageFormat(), faceInfoListGray, configuration2);
+        //IR活体检测
+        List<IrLivenessInfo> irLivenessInfo = new ArrayList<>();
+        errorCode = faceEngine.getLivenessIr(irLivenessInfo);
+        System.out.println("IR活体:" + irLivenessInfo.get(0).getLiveness());
+
+        ImageInfoEx imageInfoEx = new ImageInfoEx();
+        imageInfoEx.setHeight(imageInfo.getHeight());
+        imageInfoEx.setWidth(imageInfo.getWidth());
+        imageInfoEx.setImageFormat(imageInfo.getImageFormat());
+        imageInfoEx.setImageDataPlanes(new byte[][]{imageInfo.getImageData()});
+        imageInfoEx.setImageStrides(new int[]{imageInfo.getWidth() * 3});
+        List<FaceInfo> faceInfoList1 = new ArrayList<>();
+        errorCode = faceEngine.detectFaces(imageInfoEx, DetectModel.ASF_DETECT_MODEL_RGB, faceInfoList1);
+
+        FunctionConfiguration fun = new FunctionConfiguration();
+        fun.setSupportAge(true);
+        errorCode = faceEngine.process(imageInfoEx, faceInfoList1, functionConfiguration);
+        List<AgeInfo> ageInfoList1 = new ArrayList<>();
+        int age = faceEngine.getAge(ageInfoList1);
+        System.out.println("年龄:" + ageInfoList1.get(0).getAge());
+
+        FaceFeature feature = new FaceFeature();
+        errorCode = faceEngine.extractFaceFeature(imageInfoEx, faceInfoList1.get(0), feature);
+
+
+        //引擎卸载
+        //errorCode = faceEngine.unInit();
+
+    }
+}*/
+

+ 111 - 0
ktg-common/src/main/java/com/ktg/common/utils/face/FaceImgMatchUtil.java

@@ -0,0 +1,111 @@
+package com.ktg.common.utils.face;
+
+
+import com.ktg.common.config.RuoYiConfig;
+import com.ktg.common.utils.StringUtils;
+import com.ktg.common.vo.FaceMatchVO;
+import org.opencv.core.*;
+import org.opencv.imgcodecs.Imgcodecs;
+import org.opencv.imgproc.Imgproc;
+import org.opencv.objdetect.CascadeClassifier;
+
+import java.util.Arrays;
+
+/**
+ * 面部对比工具
+ *
+ * @author ruoyi
+ */
+public class FaceImgMatchUtil {
+
+
+    // 加载OpenCV本地库
+    static {
+        String prodApi = RuoYiConfig.getProd();
+        String loadPath = "/guoruan/app/opencv/build/java/x64/opencv_java470.dll";
+        if (StringUtils.isBlank(prodApi)) {
+            loadPath = "C:/work/app/install/opencv/build/java/x64/opencv_java470.dll";
+        }
+        System.load(loadPath);
+    }
+
+    // 初始化人脸探测器
+    private static CascadeClassifier faceDetector;
+
+    static {
+        String prodApi = RuoYiConfig.getProd();
+        String faceDetectorPath = "/guoruan/app/opencv/sources/data/haarcascades/haarcascade_frontalface_alt.xml";
+        if (StringUtils.isBlank(prodApi)) {
+            faceDetectorPath = "C:/work/app/install/opencv/sources/data/haarcascades/haarcascade_frontalface_alt.xml";
+        }
+        faceDetector = new CascadeClassifier(faceDetectorPath);
+    }
+
+    public static void main(String[] args) {
+        FaceMatchVO vo = faceRecognitionComparison("C:/work/file/11.jpg", "C:/work/file/13.jpg");
+        System.out.println("对比结果:" + vo.getScore());
+        if (vo.getScore() > 0.85) {
+            System.out.println("人脸匹配成功1");
+        } else {
+            System.out.println("人脸不匹配识别1");
+        }
+
+        FaceMatchVO vo1 = faceRecognitionComparison("C:/work/file/12.jpg", "C:/work/file/13.jpg");
+        System.out.println("对比结果:" + vo1.getScore());
+        if (vo1.getScore() > 0.85) {
+            System.out.println("人脸匹配成功2");
+        } else {
+            System.out.println("人脸不匹配识别2");
+        }
+        // 终止当前运行的 Java 虚拟机。
+        System.exit(0);
+    }
+
+
+    /**
+     * 人脸识别比对
+     */
+    public static FaceMatchVO faceRecognitionComparison(String image1, String image2) {
+        Mat mat1 = conv_mat(image1);
+        Mat mat2 = conv_mat(image2);
+        Mat mat3 = new Mat();
+        Mat mat4 = new Mat();
+        // 颜色范围
+        MatOfFloat ranges = new MatOfFloat(0f, 256f);
+        // 直方图大小, 越大匹配越精确 (越慢)
+        MatOfInt histSize = new MatOfInt(1000);
+
+        Imgproc.calcHist(Arrays.asList(mat1), new MatOfInt(0), new Mat(), mat3, histSize, ranges);
+
+        // 比较两个密集或两个稀疏直方图
+
+        Imgproc.calcHist(Arrays.asList(mat2), new MatOfInt(0), new Mat(), mat4, histSize, ranges);
+        double v = Imgproc.compareHist(mat3, mat4, Imgproc.CV_COMP_CORREL);
+        if (v > 0.80) {
+            return new FaceMatchVO().setContent(image2).setScore(v);
+        }
+        return null;
+    }
+
+    /**
+     * 灰度化人脸
+     */
+    public static Mat conv_mat(String img) {
+        // 读取图像
+        Mat mat1 = Imgcodecs.imread(img);
+        Mat mat2 = new Mat();
+        // 灰度化:将图像从一种颜色空间转换为另一种颜色空间
+        Imgproc.cvtColor(mat1, mat2, Imgproc.COLOR_BGR2GRAY);
+        // 探测人脸:检测到的对象作为矩形列表返回
+        MatOfRect faceDetections = new MatOfRect();
+        faceDetector.detectMultiScale(mat1, faceDetections);
+        // rect中人脸图片的范围
+        for (Rect rect : faceDetections.toArray()) {
+            Mat face = new Mat(mat1, rect);
+            return face;
+        }
+        return null;
+    }
+
+
+}

+ 138 - 0
ktg-common/src/main/java/com/ktg/common/utils/face/FaceMatchUtil.java

@@ -0,0 +1,138 @@
+package com.ktg.common.utils.face;
+
+
+import com.ktg.common.config.RuoYiConfig;
+import com.ktg.common.utils.StringUtils;
+import com.ktg.common.vo.FaceMatchVO;
+import org.opencv.core.*;
+import org.opencv.imgcodecs.Imgcodecs;
+import org.opencv.imgproc.Imgproc;
+import org.opencv.objdetect.CascadeClassifier;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+/**
+ * 面部对比工具
+ *
+ * @author ruoyi
+ */
+public class FaceMatchUtil
+{
+
+
+    // 加载OpenCV本地库
+    static {
+        String prodApi = RuoYiConfig.getProd();
+        String loadPath = "/guoruan/app/opencv/build/java/x64/opencv_java470.dll";
+        if (StringUtils.isBlank(prodApi)) {
+            loadPath = "C:/work/app/install/opencv/build/java/x64/opencv_java470.dll";
+        }
+        System.load(loadPath);
+    }
+
+    // 初始化人脸探测器
+    private static CascadeClassifier faceDetector;
+
+    static {
+        String prodApi = RuoYiConfig.getProd();
+        String faceDetectorPath = "/guoruan/app/opencv/sources/data/haarcascades/haarcascade_frontalface_alt.xml";
+        if (StringUtils.isBlank(prodApi)) {
+            faceDetectorPath = "C:/work/app/install/opencv/sources/data/haarcascades/haarcascade_frontalface_alt.xml";
+        }
+        faceDetector = new CascadeClassifier(faceDetectorPath);
+    }
+
+    /**
+     * 人脸识别对比
+     */
+    public static FaceMatchVO faceRecognitionComparison(MultipartFile mfile, List<String> image2) throws IOException {
+        Mat mat1 = conv_mat(mfile); // 转换为灰度图像的Mat对象
+        Mat hist1 = computeHistogram(mat1); // 计算第一个图像的直方图
+        // 记录结束时间
+        long endTime1 = System.currentTimeMillis();
+        ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); // 创建线程池
+        List<Future<FaceMatchVO>> futures = new ArrayList<>(); // 存储Future对象的列表
+
+        for (String imgPath : image2) { // 遍历第二个图像集合
+            futures.add(executorService.submit(() -> {
+                Mat mat2 = conv_Mat(imgPath); // 将路径对应的图像转换为灰度图像的Mat对象
+                if (mat2 == null) {
+                    return null; // 如果转换失败,返回null
+                }
+                Mat hist2 = computeHistogram(mat2); // 计算第二个图像的直方图
+                double v = Imgproc.compareHist(hist1, hist2, Imgproc.CV_COMP_CORREL); // 比较两个直方图的相似度
+                if (v > 0.80) { // 如果相似度大于0.80,认为匹配成功
+                    return new FaceMatchVO().setContent(imgPath).setScore(v); // 返回匹配结果
+                } else {
+                    return null; // 否则返回null
+                }
+            }));
+        }
+
+        try {
+            for (Future<FaceMatchVO> future : futures) { // 遍历所有Future对象
+                FaceMatchVO result = future.get(); // 获取执行结果
+                if (result != null) { // 如果结果不为空,说明找到了匹配的图像
+                    executorService.shutdownNow(); // 立即关闭线程池
+                    return result; // 返回匹配结果
+                }
+            }
+        } catch (InterruptedException | ExecutionException e) { // 捕获异常
+            e.printStackTrace(); // 打印异常信息
+        } finally {
+            executorService.shutdown(); // 确保线程池最终被关闭
+        }
+        return null; // 如果没有找到匹配的图像,返回null
+    }
+
+    /**
+     * 灰度化人脸并返回Mat对象
+     */
+    public static Mat conv_mat(MultipartFile mfile) throws IOException {
+        byte[] bytes = mfile.getBytes(); // 获取文件字节数组
+        Mat mat1 = Imgcodecs.imdecode(new MatOfByte(bytes), Imgcodecs.IMREAD_COLOR); // 解码为彩色图像的Mat对象
+        Mat mat2 = new Mat(); // 创建一个新的Mat对象用于存储灰度图像
+        Imgproc.cvtColor(mat1, mat2, Imgcodecs.IMREAD_COLOR); // 将彩色图像转换为灰度图像
+        MatOfRect faceDetections = new MatOfRect(); // 创建用于存储检测到的人脸区域的MatOfRect对象
+        faceDetector.detectMultiScale(mat1, faceDetections); // 检测人脸区域
+        for (Rect rect : faceDetections.toArray()) { // 遍历检测到的人脸区域
+            return new Mat(mat1, rect); // 返回包含人脸区域的子Mat对象
+        }
+        return null; // 如果没有检测到人脸,返回null
+    }
+
+    /**
+     * 灰度化人脸并返回Mat对象
+     */
+    public static Mat conv_Mat(String imgPath) {
+        Mat mat1 = Imgcodecs.imread(imgPath); // 读取图像文件并转换为Mat对象
+        Mat mat2 = new Mat(); // 创建一个新的Mat对象用于存储灰度图像
+        Imgproc.cvtColor(mat1, mat2, Imgcodecs.IMREAD_COLOR); // 将彩色图像转换为灰度图像
+        MatOfRect faceDetections = new MatOfRect(); // 创建用于存储检测到的人脸区域的MatOfRect对象
+        faceDetector.detectMultiScale(mat1, faceDetections); // 检测人脸区域
+        for (Rect rect : faceDetections.toArray()) { // 遍历检测到的人脸区域
+            return new Mat(mat1, rect); // 返回包含人脸区域的子Mat对象
+        }
+        return null; // 如果没有检测到人脸,返回null
+    }
+
+    /**
+     * 计算直方图
+     */
+    private static Mat computeHistogram(Mat mat) {
+        Mat hist = new Mat(); // 创建用于存储直方图的Mat对象
+        MatOfFloat ranges = new MatOfFloat(0f, 256f); // 定义直方图的范围
+        MatOfInt histSize = new MatOfInt(100); // 定义直方图的大小(越大越精确,但计算速度越慢)
+        Imgproc.calcHist(Collections.singletonList(mat), new MatOfInt(0), new Mat(), hist, histSize, ranges); // 计算直方图
+        Core.normalize(hist, hist, 0, 1, Core.NORM_MINMAX); // 归一化直方图,使其值在0到1之间
+        return hist; // 返回计算好的直方图
+    }
+}

+ 117 - 0
ktg-common/src/main/java/com/ktg/common/utils/face/FaceMatchUtil1.java

@@ -0,0 +1,117 @@
+package com.ktg.common.utils.face;
+
+
+import com.ktg.common.config.RuoYiConfig;
+import com.ktg.common.utils.StringUtils;
+import com.ktg.common.vo.FaceCutVO;
+import com.ktg.common.vo.FaceMatchVO;
+import org.opencv.core.*;
+import org.opencv.imgcodecs.Imgcodecs;
+import org.opencv.imgproc.Imgproc;
+import org.opencv.objdetect.CascadeClassifier;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 面部对比工具
+ *
+ * @author ruoyi
+ */
+public class FaceMatchUtil1
+{
+
+
+    // 加载OpenCV本地库
+    static {
+        String prodApi = RuoYiConfig.getProd();
+        String loadPath = "/guoruan/app/opencv/build/java/x64/opencv_java470.dll";
+        if (StringUtils.isBlank(prodApi)) {
+            loadPath = "C:/work/app/install/opencv/build/java/x64/opencv_java470.dll";
+        }
+        System.load(loadPath);
+    }
+
+    // 初始化人脸探测器
+    private static CascadeClassifier faceDetector;
+
+    static {
+        String prodApi = RuoYiConfig.getProd();
+        String faceDetectorPath = "/guoruan/app/opencv/sources/data/haarcascades/haarcascade_frontalface_alt.xml";
+        if (StringUtils.isBlank(prodApi)) {
+            faceDetectorPath = "C:/work/app/install/opencv/sources/data/haarcascades/haarcascade_frontalface_alt.xml";
+        }
+        faceDetector = new CascadeClassifier(faceDetectorPath);
+    }
+
+    /**
+     * 人脸识别对比
+     */
+    public static FaceMatchVO faceRecognitionComparison(MultipartFile mfile, List<FaceCutVO> list) throws IOException {
+        Mat mat1 = conv_mat(mfile); // 转换为灰度图像的Mat对象
+        Mat hist1 = computeHistogram(mat1); // 计算第一个图像的直方图
+
+        // ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); // 创建线程池
+        // List<Future<FaceMatchVO>> futures = new ArrayList<>(); // 存储Future对象的列表
+
+        for (FaceCutVO vo : list) { // 遍历第二个图像集合
+            // futures.add(executorService.submit(() -> {
+                Mat hist2 = MatChangeUtil.strToMat(vo.getMatData(), vo.getMatRows(), vo.getMatCols(), vo.getMatType());
+            System.out.println("hist2--------/n" + hist2.dump());
+                // Mat hist2 = computeHistogram(mat2); // 计算第二个图像的直方图
+                double v = Imgproc.compareHist(hist1, hist2, Imgproc.CV_COMP_CORREL); // 比较两个直方图的相似度
+                if (v > 0.80) { // 如果相似度大于0.80,认为匹配成功
+                    return new FaceMatchVO().setContent(vo.getContent()).setScore(v); // 返回匹配结果
+                } /*else {
+                    return null; // 否则返回null
+                }*/
+            // }));
+        }
+
+        /*try {
+            for (Future<FaceMatchVO> future : futures) { // 遍历所有Future对象
+                FaceMatchVO result = future.get(); // 获取执行结果
+                if (result != null) { // 如果结果不为空,说明找到了匹配的图像
+                    executorService.shutdownNow(); // 立即关闭线程池
+                    return result; // 返回匹配结果
+                }
+            }
+        } catch (InterruptedException | ExecutionException e) { // 捕获异常
+            e.printStackTrace(); // 打印异常信息
+        } finally {
+            executorService.shutdown(); // 确保线程池最终被关闭
+        }*/
+        return null; // 如果没有找到匹配的图像,返回null
+    }
+
+    /**
+     * 灰度化人脸并返回Mat对象
+     */
+    public static Mat conv_mat(MultipartFile mfile) throws IOException {
+        byte[] bytes = mfile.getBytes(); // 获取文件字节数组
+        Mat mat1 = Imgcodecs.imdecode(new MatOfByte(bytes), Imgcodecs.IMREAD_COLOR); // 解码为彩色图像的Mat对象
+        Mat mat2 = new Mat(); // 创建一个新的Mat对象用于存储灰度图像
+        Imgproc.cvtColor(mat1, mat2, Imgcodecs.IMREAD_COLOR); // 将彩色图像转换为灰度图像
+        MatOfRect faceDetections = new MatOfRect(); // 创建用于存储检测到的人脸区域的MatOfRect对象
+        faceDetector.detectMultiScale(mat1, faceDetections); // 检测人脸区域
+        for (Rect rect : faceDetections.toArray()) { // 遍历检测到的人脸区域
+            return new Mat(mat1, rect); // 返回包含人脸区域的子Mat对象
+        }
+        return null; // 如果没有检测到人脸,返回null
+    }
+
+
+    /**
+     * 计算直方图
+     */
+    private static Mat computeHistogram(Mat mat) {
+        Mat hist = new Mat(); // 创建用于存储直方图的Mat对象
+        MatOfFloat ranges = new MatOfFloat(0f, 256f); // 定义直方图的范围
+        MatOfInt histSize = new MatOfInt(100); // 定义直方图的大小(越大越精确,但计算速度越慢)
+        Imgproc.calcHist(Collections.singletonList(mat), new MatOfInt(0), new Mat(), hist, histSize, ranges); // 计算直方图
+        Core.normalize(hist, hist, 0, 1, Core.NORM_MINMAX); // 归一化直方图,使其值在0到1之间
+        return hist; // 返回计算好的直方图
+    }
+}

+ 46 - 0
ktg-common/src/main/java/com/ktg/common/utils/face/MatChangeUtil.java

@@ -0,0 +1,46 @@
+package com.ktg.common.utils.face;
+
+import com.ktg.common.config.RuoYiConfig;
+import com.ktg.common.utils.StringUtils;
+import org.opencv.core.Mat;
+import org.opencv.core.MatOfByte;
+import org.opencv.imgcodecs.Imgcodecs;
+
+import java.util.Base64;
+
+/**
+ *
+ *
+ * @author cgj
+ */
+public class MatChangeUtil
+{
+
+    // 加载OpenCV本地库
+    static {
+        String prodApi = RuoYiConfig.getProd();
+        String loadPath = "/guoruan/app/opencv/build/java/x64/opencv_java470.dll";
+        if (StringUtils.isBlank(prodApi)) {
+            loadPath = "C:/work/app/install/opencv/build/java/x64/opencv_java470.dll";
+        }
+        System.load(loadPath);
+    }
+    public static String matToStr(Mat mat) {
+        // 将MAT对象编码为图像格式的字节数组
+        MatOfByte matOfByte = new MatOfByte();
+        Imgcodecs.imencode(".jpg", mat, matOfByte);
+        byte[] byteArray = matOfByte.toArray();
+        // 将字节数组转换为Base64编码的字符串
+        return Base64.getEncoder().encodeToString(byteArray);
+    }
+
+
+    public static Mat strToMat(String base64String, int rows, int cols, int type) {
+        // 将Base64编码的字符串解码为字节数组
+        byte[] byteArray = Base64.getDecoder().decode(base64String);
+        // 将字节数组转换为MAT对象
+        MatOfByte matOfByte = new MatOfByte(byteArray);
+        return Imgcodecs.imdecode(matOfByte, Imgcodecs.IMREAD_UNCHANGED);
+    }
+
+}

+ 8 - 8
ktg-common/src/main/java/com/ktg/common/utils/file/FileUploadUtils.java

@@ -1,14 +1,7 @@
 package com.ktg.common.utils.file;
 
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Paths;
-import java.util.Objects;
-
 import com.ktg.common.config.MinioConfig;
 import com.ktg.common.config.RuoYiConfig;
-import org.apache.commons.io.FilenameUtils;
-import org.springframework.web.multipart.MultipartFile;
 import com.ktg.common.constant.Constants;
 import com.ktg.common.exception.file.FileNameLengthLimitExceededException;
 import com.ktg.common.exception.file.FileSizeLimitExceededException;
@@ -16,6 +9,13 @@ import com.ktg.common.exception.file.InvalidExtensionException;
 import com.ktg.common.utils.DateUtils;
 import com.ktg.common.utils.StringUtils;
 import com.ktg.common.utils.uuid.Seq;
+import org.apache.commons.io.FilenameUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.Objects;
 
 /**
  * 文件上传工具类
@@ -27,7 +27,7 @@ public class FileUploadUtils
     /**
      * 默认大小 50M
      */
-    public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024;
+    public static final long DEFAULT_MAX_SIZE = 500 * 1024 * 1024;
 
     /**
      * 默认的文件名最大长度 100

+ 62 - 0
ktg-common/src/main/java/com/ktg/common/utils/obj/BeanUtils.java

@@ -0,0 +1,62 @@
+package com.ktg.common.utils.obj;
+
+import cn.hutool.core.bean.BeanUtil;
+import com.ktg.common.pojo.PageResult;
+import com.ktg.common.utils.bean.CollectionUtils;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * Bean 工具类
+ *
+ * 1. 默认使用 {@link BeanUtil} 作为实现类,虽然不同 bean 工具的性能有差别,但是对绝大多数同学的项目,不用在意这点性能
+ * 2. 针对复杂的对象转换,可以搜参考 AuthConvert 实现,通过 mapstruct + default 配合实现
+ *
+ * @author 芋道源码
+ */
+public class BeanUtils {
+
+    public static <T> T toBean(Object source, Class<T> targetClass) {
+        return BeanUtil.toBean(source, targetClass);
+    }
+
+    public static <T> T toBean(Object source, Class<T> targetClass, Consumer<T> peek) {
+        T target = toBean(source, targetClass);
+        if (target != null) {
+            peek.accept(target);
+        }
+        return target;
+    }
+
+    public static <S, T> List<T> toBean(List<S> source, Class<T> targetType) {
+        if (source == null) {
+            return null;
+        }
+        return CollectionUtils.convertList(source, s -> toBean(s, targetType));
+    }
+
+    public static <S, T> List<T> toBean(List<S> source, Class<T> targetType, Consumer<T> peek) {
+        List<T> list = toBean(source, targetType);
+        if (list != null) {
+            list.forEach(peek);
+        }
+        return list;
+    }
+
+    public static <S, T> PageResult<T> toBean(PageResult<S> source, Class<T> targetType) {
+        return toBean(source, targetType, null);
+    }
+
+    public static <S, T> PageResult<T> toBean(PageResult<S> source, Class<T> targetType, Consumer<T> peek) {
+        if (source == null) {
+            return null;
+        }
+        List<T> list = toBean(source.getList(), targetType);
+        if (peek != null) {
+            list.forEach(peek);
+        }
+        return new PageResult<>(list, source.getTotal());
+    }
+
+}

+ 63 - 0
ktg-common/src/main/java/com/ktg/common/utils/obj/ObjectUtils.java

@@ -0,0 +1,63 @@
+package com.ktg.common.utils.obj;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.ReflectUtil;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.function.Consumer;
+
+/**
+ * Object 工具类
+ *
+ * @author 芋道源码
+ */
+public class ObjectUtils {
+
+    /**
+     * 复制对象,并忽略 Id 编号
+     *
+     * @param object 被复制对象
+     * @param consumer 消费者,可以二次编辑被复制对象
+     * @return 复制后的对象
+     */
+    public static <T> T cloneIgnoreId(T object, Consumer<T> consumer) {
+        T result = ObjectUtil.clone(object);
+        // 忽略 id 编号
+        Field field = ReflectUtil.getField(object.getClass(), "id");
+        if (field != null) {
+            ReflectUtil.setFieldValue(result, field, null);
+        }
+        // 二次编辑
+        if (result != null) {
+            consumer.accept(result);
+        }
+        return result;
+    }
+
+    public static <T extends Comparable<T>> T max(T obj1, T obj2) {
+        if (obj1 == null) {
+            return obj2;
+        }
+        if (obj2 == null) {
+            return obj1;
+        }
+        return obj1.compareTo(obj2) > 0 ? obj1 : obj2;
+    }
+
+    @SafeVarargs
+    public static <T> T defaultIfNull(T... array) {
+        for (T item : array) {
+            if (item != null) {
+                return item;
+            }
+        }
+        return null;
+    }
+
+    @SafeVarargs
+    public static <T> boolean equalsAny(T obj, T... array) {
+        return Arrays.asList(array).contains(obj);
+    }
+
+}

+ 67 - 0
ktg-common/src/main/java/com/ktg/common/utils/obj/PageUtils.java

@@ -0,0 +1,67 @@
+package com.ktg.common.utils.obj;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.func.Func1;
+import cn.hutool.core.lang.func.LambdaUtil;
+import cn.hutool.core.util.ArrayUtil;
+import com.ktg.common.pojo.PageParam;
+import com.ktg.common.pojo.SortablePageParam;
+import com.ktg.common.pojo.SortingField;
+import org.springframework.util.Assert;
+
+import static java.util.Collections.singletonList;
+
+/**
+ * {@link cn.iocoder.yudao.framework.common.pojo.PageParam} 工具类
+ *
+ * @author 芋道源码
+ */
+public class PageUtils {
+
+    private static final Object[] ORDER_TYPES = new String[]{SortingField.ORDER_ASC, SortingField.ORDER_DESC};
+
+    public static int getStart(PageParam pageParam) {
+        return (pageParam.getPageNo() - 1) * pageParam.getPageSize();
+    }
+
+    /**
+     * 构建排序字段(默认倒序)
+     *
+     * @param func 排序字段的 Lambda 表达式
+     * @param <T>  排序字段所属的类型
+     * @return 排序字段
+     */
+    public static <T> SortingField buildSortingField(Func1<T, ?> func) {
+        return buildSortingField(func, SortingField.ORDER_DESC);
+    }
+
+    /**
+     * 构建排序字段
+     *
+     * @param func  排序字段的 Lambda 表达式
+     * @param order 排序类型 {@link SortingField#ORDER_ASC} {@link SortingField#ORDER_DESC}
+     * @param <T>   排序字段所属的类型
+     * @return 排序字段
+     */
+    public static <T> SortingField buildSortingField(Func1<T, ?> func, String order) {
+        Assert.isTrue(ArrayUtil.contains(ORDER_TYPES, order), String.format("字段的排序类型只能是 %s/%s", ORDER_TYPES));
+
+        String fieldName = LambdaUtil.getFieldName(func);
+        return new SortingField(fieldName, order);
+    }
+
+    /**
+     * 构建默认的排序字段
+     * 如果排序字段为空,则设置排序字段;否则忽略
+     *
+     * @param sortablePageParam 排序分页查询参数
+     * @param func              排序字段的 Lambda 表达式
+     * @param <T>               排序字段所属的类型
+     */
+    public static <T> void buildDefaultSortingField(SortablePageParam sortablePageParam, Func1<T, ?> func) {
+        if (sortablePageParam != null && CollUtil.isEmpty(sortablePageParam.getSortingFields())) {
+            sortablePageParam.setSortingFields(singletonList(buildSortingField(func)));
+        }
+    }
+
+}

+ 61 - 94
ktg-common/src/main/java/com/ktg/common/utils/poi/ExcelUtil.java

@@ -1,69 +1,5 @@
 package com.ktg.common.utils.poi;
 
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.math.BigDecimal;
-import java.text.DecimalFormat;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-import java.util.stream.Collectors;
-import javax.servlet.http.HttpServletResponse;
-import org.apache.commons.lang3.RegExUtils;
-import org.apache.poi.hssf.usermodel.HSSFClientAnchor;
-import org.apache.poi.hssf.usermodel.HSSFPicture;
-import org.apache.poi.hssf.usermodel.HSSFPictureData;
-import org.apache.poi.hssf.usermodel.HSSFShape;
-import org.apache.poi.hssf.usermodel.HSSFSheet;
-import org.apache.poi.hssf.usermodel.HSSFWorkbook;
-import org.apache.poi.ooxml.POIXMLDocumentPart;
-import org.apache.poi.ss.usermodel.BorderStyle;
-import org.apache.poi.ss.usermodel.Cell;
-import org.apache.poi.ss.usermodel.CellStyle;
-import org.apache.poi.ss.usermodel.CellType;
-import org.apache.poi.ss.usermodel.ClientAnchor;
-import org.apache.poi.ss.usermodel.DataValidation;
-import org.apache.poi.ss.usermodel.DataValidationConstraint;
-import org.apache.poi.ss.usermodel.DataValidationHelper;
-import org.apache.poi.ss.usermodel.DateUtil;
-import org.apache.poi.ss.usermodel.Drawing;
-import org.apache.poi.ss.usermodel.FillPatternType;
-import org.apache.poi.ss.usermodel.Font;
-import org.apache.poi.ss.usermodel.HorizontalAlignment;
-import org.apache.poi.ss.usermodel.IndexedColors;
-import org.apache.poi.ss.usermodel.PictureData;
-import org.apache.poi.ss.usermodel.Row;
-import org.apache.poi.ss.usermodel.Sheet;
-import org.apache.poi.ss.usermodel.VerticalAlignment;
-import org.apache.poi.ss.usermodel.Workbook;
-import org.apache.poi.ss.usermodel.WorkbookFactory;
-import org.apache.poi.ss.util.CellRangeAddress;
-import org.apache.poi.ss.util.CellRangeAddressList;
-import org.apache.poi.util.IOUtils;
-import org.apache.poi.xssf.streaming.SXSSFWorkbook;
-import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
-import org.apache.poi.xssf.usermodel.XSSFDataValidation;
-import org.apache.poi.xssf.usermodel.XSSFDrawing;
-import org.apache.poi.xssf.usermodel.XSSFPicture;
-import org.apache.poi.xssf.usermodel.XSSFShape;
-import org.apache.poi.xssf.usermodel.XSSFSheet;
-import org.apache.poi.xssf.usermodel.XSSFWorkbook;
-import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import com.ktg.common.annotation.Excel;
 import com.ktg.common.annotation.Excel.ColumnType;
 import com.ktg.common.annotation.Excel.Type;
@@ -79,10 +15,33 @@ import com.ktg.common.utils.file.FileTypeUtils;
 import com.ktg.common.utils.file.FileUtils;
 import com.ktg.common.utils.file.ImageUtils;
 import com.ktg.common.utils.reflect.ReflectUtils;
+import org.apache.commons.lang3.RegExUtils;
+import org.apache.poi.hssf.usermodel.*;
+import org.apache.poi.ooxml.POIXMLDocumentPart;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.ss.util.CellRangeAddressList;
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.xssf.streaming.SXSSFWorkbook;
+import org.apache.poi.xssf.usermodel.*;
+import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.math.BigDecimal;
+import java.text.DecimalFormat;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * Excel相关处理
- * 
+ *
  * @author ruoyi
  */
 public class ExcelUtil<T>
@@ -202,7 +161,7 @@ public class ExcelUtil<T>
 
     /**
      * 对excel表单默认第一个索引名转换成list
-     * 
+     *
      * @param is 输入流
      * @return 转换后集合
      */
@@ -213,7 +172,7 @@ public class ExcelUtil<T>
 
     /**
      * 对excel表单默认第一个索引名转换成list
-     * 
+     *
      * @param is 输入流
      * @param titleNum 标题占用行数
      * @return 转换后集合
@@ -225,7 +184,7 @@ public class ExcelUtil<T>
 
     /**
      * 对excel表单指定表格索引名转换成list
-     * 
+     *
      * @param sheetName 表格索引名
      * @param titleNum 标题占用行数
      * @param is 输入流
@@ -405,7 +364,7 @@ public class ExcelUtil<T>
 
     /**
      * 对list数据源将其里面的数据导入到excel表单
-     * 
+     *
      * @param list 导出数据集合
      * @param sheetName 工作表的名称
      * @return 结果
@@ -417,7 +376,7 @@ public class ExcelUtil<T>
 
     /**
      * 对list数据源将其里面的数据导入到excel表单
-     * 
+     *
      * @param list 导出数据集合
      * @param sheetName 工作表的名称
      * @param title 标题
@@ -431,7 +390,7 @@ public class ExcelUtil<T>
 
     /**
      * 对list数据源将其里面的数据导入到excel表单
-     * 
+     *
      * @param response 返回数据
      * @param list 导出数据集合
      * @param sheetName 工作表的名称
@@ -444,7 +403,7 @@ public class ExcelUtil<T>
 
     /**
      * 对list数据源将其里面的数据导入到excel表单
-     * 
+     *
      * @param response 返回数据
      * @param list 导出数据集合
      * @param sheetName 工作表的名称
@@ -461,7 +420,7 @@ public class ExcelUtil<T>
 
     /**
      * 对list数据源将其里面的数据导入到excel表单
-     * 
+     *
      * @param sheetName 工作表的名称
      * @return 结果
      */
@@ -472,7 +431,7 @@ public class ExcelUtil<T>
 
     /**
      * 对list数据源将其里面的数据导入到excel表单
-     * 
+     *
      * @param sheetName 工作表的名称
      * @param title 标题
      * @return 结果
@@ -485,7 +444,7 @@ public class ExcelUtil<T>
 
     /**
      * 对list数据源将其里面的数据导入到excel表单
-     * 
+     *
      * @param sheetName 工作表的名称
      * @return 结果
      */
@@ -496,7 +455,7 @@ public class ExcelUtil<T>
 
     /**
      * 对list数据源将其里面的数据导入到excel表单
-     * 
+     *
      * @param sheetName 工作表的名称
      * @param title 标题
      * @return 结果
@@ -509,9 +468,17 @@ public class ExcelUtil<T>
         exportExcel(response);
     }
 
+    public void importTempExcelWithDate(HttpServletResponse response, String sheetName, List<T> list)
+    {
+        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+        response.setCharacterEncoding("utf-8");
+        this.init(list, sheetName, StringUtils.EMPTY, Type.EXPORT);
+        exportExcel(response);
+    }
+
     /**
      * 对list数据源将其里面的数据导入到excel表单
-     * 
+     *
      * @return 结果
      */
     public void exportExcel(HttpServletResponse response)
@@ -533,7 +500,7 @@ public class ExcelUtil<T>
 
     /**
      * 对list数据源将其里面的数据导入到excel表单
-     * 
+     *
      * @return 结果
      */
     public AjaxResult exportExcel()
@@ -589,7 +556,7 @@ public class ExcelUtil<T>
 
     /**
      * 填充excel数据
-     * 
+     *
      * @param index 序号
      * @param row 单元格行
      */
@@ -614,7 +581,7 @@ public class ExcelUtil<T>
 
     /**
      * 创建表格样式
-     * 
+     *
      * @param wb 工作薄对象
      * @return 样式列表
      */
@@ -706,7 +673,7 @@ public class ExcelUtil<T>
 
     /**
      * 设置单元格信息
-     * 
+     *
      * @param value 单元格值
      * @param attr 注解相关
      * @param cell 单元格信息
@@ -854,7 +821,7 @@ public class ExcelUtil<T>
 
     /**
      * 设置 POI XSSFSheet 单元格提示或选择框
-     * 
+     *
      * @param sheet 表单
      * @param textlist 下拉框显示的内容
      * @param promptContent 提示内容
@@ -891,7 +858,7 @@ public class ExcelUtil<T>
 
     /**
      * 解析导出值 0=男,1=女,2=未知
-     * 
+     *
      * @param propertyValue 参数值
      * @param converterExp 翻译注解
      * @param separator 分隔符
@@ -928,7 +895,7 @@ public class ExcelUtil<T>
 
     /**
      * 反向解析值 男=0,女=1,未知=2
-     * 
+     *
      * @param propertyValue 参数值
      * @param converterExp 翻译注解
      * @param separator 分隔符
@@ -965,7 +932,7 @@ public class ExcelUtil<T>
 
     /**
      * 解析字典值
-     * 
+     *
      * @param dictValue 字典值
      * @param dictType 字典类型
      * @param separator 分隔符
@@ -978,7 +945,7 @@ public class ExcelUtil<T>
 
     /**
      * 反向解析值字典值
-     * 
+     *
      * @param dictLabel 字典标签
      * @param dictType 字典类型
      * @param separator 分隔符
@@ -991,7 +958,7 @@ public class ExcelUtil<T>
 
     /**
      * 数据处理器
-     * 
+     *
      * @param value 数据值
      * @param excel 数据注解
      * @return
@@ -1068,7 +1035,7 @@ public class ExcelUtil<T>
 
     /**
      * 获取下载路径
-     * 
+     *
      * @param filename 文件名称
      */
     public String getAbsoluteFile(String filename)
@@ -1084,7 +1051,7 @@ public class ExcelUtil<T>
 
     /**
      * 获取bean中的属性值
-     * 
+     *
      * @param vo 实体对象
      * @param field 字段
      * @param excel 注解
@@ -1115,7 +1082,7 @@ public class ExcelUtil<T>
 
     /**
      * 以类的属性的get方法方法形式获取值
-     * 
+     *
      * @param o
      * @param name
      * @return value
@@ -1210,7 +1177,7 @@ public class ExcelUtil<T>
 
     /**
      * 创建工作表
-     * 
+     *
      * @param sheetNo sheet数量
      * @param index 序号
      */
@@ -1227,7 +1194,7 @@ public class ExcelUtil<T>
 
     /**
      * 获取单元格值
-     * 
+     *
      * @param row 获取的行
      * @param column 获取单元格列号
      * @return 单元格值
@@ -1287,7 +1254,7 @@ public class ExcelUtil<T>
 
     /**
      * 判断是否是空行
-     * 
+     *
      * @param row 判断的行
      * @return
      */
@@ -1375,7 +1342,7 @@ public class ExcelUtil<T>
 
     /**
      * 格式化不同类型的日期对象
-     * 
+     *
      * @param dateFormat 日期格式
      * @param val 被格式化的日期对象
      * @return 格式化后的日期字符

+ 58 - 0
ktg-common/src/main/java/com/ktg/common/utils/rocketmq/RocketMQAutoConfiguration.java

@@ -0,0 +1,58 @@
+package com.ktg.common.utils.rocketmq;
+
+import org.apache.rocketmq.client.producer.DefaultMQProducer;
+import org.apache.rocketmq.common.message.MessageExt;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+
+/**
+ * RocketMQ自动配置类
+ * 在Spring Boot应用启动时自动配置RocketMQ相关Bean
+ */
+@Configuration // 声明为配置类
+@ConditionalOnClass(DefaultMQProducer.class) // 当类路径下有DefaultMQProducer类时生效
+@EnableConfigurationProperties(RocketMQProperties.class) // 启用配置属性
+public class RocketMQAutoConfiguration {
+
+    /**
+     * 创建生产者管理器Bean
+     * @param properties RocketMQ配置属性
+     * @return 生产者管理器实例
+     */
+    @Bean
+    @ConditionalOnMissingBean // 当容器中没有该Bean时创建
+    public RocketMQProducerManager rocketMQProducerManager(RocketMQProperties properties) {
+        return new RocketMQProducerManager(properties);
+    }
+
+    /**
+     * 创建默认的消息处理器Map
+     * 应用可以通过@Bean提供自定义的消息处理器
+     * @return 空的处理器Map
+     */
+    @Bean
+    @ConditionalOnMissingBean
+    public Map<String, Consumer<MessageExt>> rocketMQMessageHandlers() {
+        return new HashMap<>();
+    }
+
+    /**
+     * 创建消费者管理器Bean
+     * @param properties RocketMQ配置属性
+     * @param messageHandlers 消息处理器Map
+     * @return 消费者管理器实例
+     */
+    @Bean
+    @ConditionalOnMissingBean
+    public RocketMQConsumerManager rocketMQConsumerManager(RocketMQProperties properties,
+                                                           Map<String, Consumer<MessageExt>> messageHandlers) {
+        return new RocketMQConsumerManager(properties, messageHandlers);
+    }
+}

+ 140 - 0
ktg-common/src/main/java/com/ktg/common/utils/rocketmq/RocketMQConsumerManager.java

@@ -0,0 +1,140 @@
+package com.ktg.common.utils.rocketmq;
+
+import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
+import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
+import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
+import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
+import org.apache.rocketmq.client.exception.MQClientException;
+import org.apache.rocketmq.common.message.MessageExt;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+
+/**
+ * RocketMQ消费者管理器
+ * 负责管理多个RocketMQ消费者实例
+ * 实现InitializingBean在Spring初始化完成后自动初始化消费者
+ * 实现DisposableBean在Spring销毁时自动关闭消费者
+ */
+public class RocketMQConsumerManager implements InitializingBean, DisposableBean {
+    // RocketMQ配置属性
+    private final RocketMQProperties properties;
+
+    // 消息处理器Map,key为消费者名称,value为消息处理函数
+    private final Map<String, Consumer<MessageExt>> messageHandlers;
+
+    // 消费者Map,key为消费者名称,value为消费者实例
+    private final Map<String, DefaultMQPushConsumer> consumerMap = new ConcurrentHashMap<>();
+
+    /**
+     * 构造函数
+     * @param properties RocketMQ配置属性
+     * @param messageHandlers 消息处理器Map
+     */
+    public RocketMQConsumerManager(RocketMQProperties properties,
+                                   Map<String, Consumer<MessageExt>> messageHandlers) {
+        this.properties = properties;
+        this.messageHandlers = messageHandlers;
+    }
+
+    /**
+     * Spring Bean初始化完成后调用的方法
+     * 初始化所有配置的消费者实例
+     */
+    @Override
+    public void afterPropertiesSet() {
+        // 遍历所有消费者配置
+        properties.getConsumers().forEach((name, config) -> {
+            // 检查消费者是否启用
+            if (!config.isEnabled()) {
+                return;
+            }
+
+            // 获取对应的消息处理器
+            Consumer<MessageExt> messageHandler = messageHandlers.get(name);
+            if (messageHandler == null) {
+                throw new IllegalStateException("No message handler found for consumer: " + name);
+            }
+
+            // 创建消费者实例
+            DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(config.getGroup());
+
+            // 设置NameServer地址
+            consumer.setNamesrvAddr(properties.getNamesrvAddr());
+
+            // 设置消费者参数
+            consumer.setConsumeThreadMin(config.getConsumeThreadMin());
+            consumer.setConsumeThreadMax(config.getConsumeThreadMax());
+            consumer.setConsumeMessageBatchMaxSize(config.getConsumeMessageBatchMaxSize());
+
+            // 设置实例名称(如果配置了)
+            if (config.getInstanceName() != null) {
+                consumer.setInstanceName(config.getInstanceName());
+            }
+
+            try {
+                // 订阅Topic和Tags
+                consumer.subscribe(config.getTopic(), config.getTags());
+
+                // 注册消息监听器
+                consumer.registerMessageListener(new MessageListenerConcurrently() {
+                    @Override
+                    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
+                                                                    ConsumeConcurrentlyContext context) {
+                        // 处理每条消息
+                        for (MessageExt msg : msgs) {
+                            try {
+                                // 调用消息处理器处理消息
+                                messageHandler.accept(msg);
+                            } catch (Exception e) {
+                                // 处理失败,稍后重试
+                                return ConsumeConcurrentlyStatus.RECONSUME_LATER;
+                            }
+                        }
+                        // 处理成功
+                        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
+                    }
+                });
+                try {
+                    // 启动消费者
+                    consumer.start();
+                    System.out.println("=============初始化消费者成功===============");
+                } catch (Exception e) {
+                    System.out.println("初始化消费者失败!");
+                }
+
+                // 将消费者加入Map
+                consumerMap.put(name, consumer);
+            } catch (MQClientException e) {
+                throw new RuntimeException("Failed to start RocketMQ consumer: " + name, e);
+            }
+        });
+    }
+
+    /**
+     * Spring Bean销毁时调用的方法
+     * 关闭所有消费者实例
+     */
+    @Override
+    public void destroy() {
+        System.out.println("=================销毁消费者====================");
+        consumerMap.values().forEach(DefaultMQPushConsumer::shutdown);
+    }
+
+    /**
+     * 获取指定名称的消费者实例
+     * @param name 消费者名称
+     * @return 消费者实例
+     */
+    public DefaultMQPushConsumer getConsumer(String name) {
+        DefaultMQPushConsumer consumer = consumerMap.get(name);
+        if (consumer == null) {
+            throw new IllegalArgumentException("RocketMQ consumer not found: " + name);
+        }
+        return consumer;
+    }
+}

+ 52 - 0
ktg-common/src/main/java/com/ktg/common/utils/rocketmq/RocketMQHandlerConfig.java

@@ -0,0 +1,52 @@
+package com.ktg.common.utils.rocketmq;
+
+import org.apache.rocketmq.common.message.MessageExt;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+
+@Configuration
+public class RocketMQHandlerConfig {
+
+    @Bean
+    public Map<String, Consumer<MessageExt>> customMessageHandlers() {
+        Map<String, Consumer<MessageExt>> handlers = new HashMap<>();
+
+        // 订单消息处理器
+        handlers.put("order-consumer", msg -> {
+            System.out.println("处理订单消息,消息ID: " + msg.getMsgId());
+            System.out.println("消息内容: " + new String(msg.getBody()));
+            System.out.println("Topic: " + msg.getTopic());
+            System.out.println("Tags: " + msg.getTags());
+
+            // 业务逻辑处理...
+            // 可以根据不同的Tag执行不同的业务逻辑
+            if ("create".equals(msg.getTags())) {
+                processOrderCreate(msg);
+            } else if ("pay".equals(msg.getTags())) {
+                processOrderPay(msg);
+            }
+        });
+
+        // 支付消息处理器
+        handlers.put("payment-consumer", msg -> {
+            System.out.println("处理支付消息: " + new String(msg.getBody()));
+            // 支付业务逻辑...
+        });
+
+        return handlers;
+    }
+
+    private void processOrderCreate(MessageExt msg) {
+        // 订单创建处理逻辑
+        System.out.println("获取订单创建消息成功:" + msg);
+    }
+
+    private void processOrderPay(MessageExt msg) {
+        // 订单支付处理逻辑
+        System.out.println("获取订单支付消息成功:" + msg);
+    }
+}

+ 135 - 0
ktg-common/src/main/java/com/ktg/common/utils/rocketmq/RocketMQProducerManager.java

@@ -0,0 +1,135 @@
+package com.ktg.common.utils.rocketmq;
+
+import org.apache.rocketmq.client.exception.MQClientException;
+import org.apache.rocketmq.client.producer.DefaultMQProducer;
+import org.apache.rocketmq.client.producer.SendCallback;
+import org.apache.rocketmq.client.producer.SendResult;
+import org.apache.rocketmq.common.message.Message;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * RocketMQ生产者管理器
+ * 负责管理多个RocketMQ生产者实例
+ * 实现InitializingBean在Spring初始化完成后自动初始化生产者
+ * 实现DisposableBean在Spring销毁时自动关闭生产者
+ */
+public class RocketMQProducerManager implements InitializingBean, DisposableBean {
+    // RocketMQ配置属性
+    private final RocketMQProperties properties;
+
+    // 生产者Map,key为生产者名称,value为生产者实例
+    private final Map<String, DefaultMQProducer> producerMap = new ConcurrentHashMap<>();
+
+    /**
+     * 构造函数
+     * @param properties RocketMQ配置属性
+     */
+    public RocketMQProducerManager(RocketMQProperties properties) {
+        this.properties = properties;
+    }
+
+    /**
+     * Spring Bean初始化完成后调用的方法
+     * 初始化所有配置的生产者实例
+     */
+    @Override
+    public void afterPropertiesSet() {
+        // 遍历所有生产者配置
+        properties.getProducers().forEach((name, config) -> {
+            // 创建生产者实例
+            DefaultMQProducer producer = new DefaultMQProducer(config.getGroup());
+
+            // 设置NameServer地址
+            producer.setNamesrvAddr(properties.getNamesrvAddr());
+
+            // 设置生产者参数
+            producer.setRetryTimesWhenSendFailed(config.getRetryTimesWhenSendFailed());
+            producer.setSendMsgTimeout(config.getSendMsgTimeout());
+            producer.setCompressMsgBodyOverHowmuch(config.getCompressMsgBodyOverHowmuch());
+
+            // 设置实例名称(如果配置了)
+            if (config.getInstanceName() != null) {
+                producer.setInstanceName(config.getInstanceName());
+            }
+
+            try {
+                // 启动生产者
+                producer.start();
+                // 将生产者加入Map
+                producerMap.put(name, producer);
+            } catch (MQClientException e) {
+                throw new RuntimeException("Failed to start RocketMQ producer: " + name, e);
+            }
+        });
+    }
+
+    /**
+     * Spring Bean销毁时调用的方法
+     * 关闭所有生产者实例
+     */
+    @Override
+    public void destroy() {
+        producerMap.values().forEach(DefaultMQProducer::shutdown);
+    }
+
+    /**
+     * 同步发送消息
+     * @param producerName 生产者名称(配置中的key)
+     * @param topic 消息Topic
+     * @param tags 消息Tags
+     * @param keys 消息Keys
+     * @param body 消息体
+     * @return 发送结果
+     */
+    public SendResult sendSync(String producerName, String topic, String tags, String keys, byte[] body) throws Exception {
+        DefaultMQProducer producer = getProducer(producerName);
+        Message msg = new Message(topic, tags, keys, body);
+        return producer.send(msg);
+    }
+
+    /**
+     * 异步发送消息
+     * @param producerName 生产者名称
+     * @param topic 消息Topic
+     * @param tags 消息Tags
+     * @param keys 消息Keys
+     * @param body 消息体
+     * @param sendCallback 异步回调接口
+     */
+    public void sendAsync(String producerName, String topic, String tags, String keys, byte[] body, SendCallback sendCallback) throws Exception {
+        DefaultMQProducer producer = getProducer(producerName);
+        Message msg = new Message(topic, tags, keys, body);
+        producer.send(msg, sendCallback);
+    }
+
+    /**
+     * 单向发送消息(不关心发送结果)
+     * @param producerName 生产者名称
+     * @param topic 消息Topic
+     * @param tags 消息Tags
+     * @param keys 消息Keys
+     * @param body 消息体
+     */
+    public void sendOneway(String producerName, String topic, String tags, String keys, byte[] body) throws Exception {
+        DefaultMQProducer producer = getProducer(producerName);
+        Message msg = new Message(topic, tags, keys, body);
+        producer.sendOneway(msg);
+    }
+
+    /**
+     * 获取指定名称的生产者实例
+     * @param name 生产者名称
+     * @return 生产者实例
+     */
+    public DefaultMQProducer getProducer(String name) {
+        DefaultMQProducer producer = producerMap.get(name);
+        if (producer == null) {
+            throw new IllegalArgumentException("RocketMQ producer not found: " + name);
+        }
+        return producer;
+    }
+}

+ 50 - 0
ktg-common/src/main/java/com/ktg/common/utils/rocketmq/RocketMQProperties.java

@@ -0,0 +1,50 @@
+package com.ktg.common.utils.rocketmq;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * RocketMQ 配置属性类
+ * 通过@ConfigurationProperties注解绑定application.yml中的配置
+ */
+@Data // Lombok注解,自动生成getter/setter等方法
+@ConfigurationProperties(prefix = "rocketmq") // 指定配置前缀
+public class RocketMQProperties {
+    // NameServer地址,格式为 ip:port;ip:port
+    private String namesrvAddr;
+
+    // 生产者配置Map,key为生产者名称,value为生产者配置
+    private Map<String, ProducerConfig> producers = new HashMap<>();
+
+    // 消费者配置Map,key为消费者名称,value为消费者配置
+    private Map<String, ConsumerConfig> consumers = new HashMap<>();
+
+    /**
+     * 生产者配置内部类
+     */
+    @Data
+    public static class ProducerConfig {
+        private String group; // 生产者组名
+        private int retryTimesWhenSendFailed = 3; // 发送失败重试次数,默认3次
+        private int sendMsgTimeout = 3000; // 发送消息超时时间,默认3000ms
+        private int compressMsgBodyOverHowmuch = 1024 * 4; // 消息压缩阈值,默认4KB
+        private String instanceName; // 生产者实例名称
+    }
+
+    /**
+     * 消费者配置内部类
+     */
+    @Data
+    public static class ConsumerConfig {
+        private String group; // 消费者组名
+        private String topic; // 订阅的Topic
+        private String tags = "*"; // 订阅的Tag表达式,默认*表示所有
+        private int consumeThreadMin = 5; // 最小消费线程数,默认5
+        private int consumeThreadMax = 10; // 最大消费线程数,默认10
+        private int consumeMessageBatchMaxSize = 1; // 批量消费大小,默认1
+        private String instanceName; // 消费者实例名称
+        private boolean enabled = true; // 是否启用,默认true
+    }
+}

+ 45 - 0
ktg-common/src/main/java/com/ktg/common/vo/FaceCutVO.java

@@ -0,0 +1,45 @@
+package com.ktg.common.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+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;
+
+    @ApiModelProperty(value = "用户id")
+    private Long userId;
+
+    @ApiModelProperty(value = "类型(1-指纹图片 2-面部图片)")
+    private String type;
+
+    @ApiModelProperty(value = "内容")
+    private String content;
+
+    @ApiModelProperty(value = "图片地址")
+    private String imageUrl;
+
+    @ApiModelProperty(value = "图片路径")
+    private String imagePath;
+
+    @ApiModelProperty(value = "直方图rows")
+    private Integer matRows;
+
+    @ApiModelProperty(value = "直方图cols")
+    private Integer matCols;
+
+    @ApiModelProperty(value = "直方图type")
+    private Integer matType;
+
+    @ApiModelProperty(value = "直方图数据")
+    private String matData;
+
+}
+

+ 27 - 0
ktg-common/src/main/java/com/ktg/common/vo/FaceMatchVO.java

@@ -0,0 +1,27 @@
+package com.ktg.common.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+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;
+
+    @ApiModelProperty(value = "人员编号")
+    private Long userId;
+
+    @ApiModelProperty(value = "内容")
+    private String content;
+
+    @ApiModelProperty(value = "得分")
+    private Double score;
+
+}
+

+ 39 - 0
ktg-common/src/main/java/com/ktg/common/vo/VerificationVO.java

@@ -0,0 +1,39 @@
+package com.ktg.common.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+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;
+
+    @ApiModelProperty(value = "人员编号")
+    private Long userId;
+
+    @ApiModelProperty(value = "指纹图片地址")
+    private String fingerprintImg;
+
+    @ApiModelProperty(value = "指纹信息")
+    private String fingerprint;
+
+    @ApiModelProperty(value = "指纹模板信息")
+    private byte[] fingerprintTemplate;
+
+    @ApiModelProperty(value = "指纹信息")
+    private String fingerprintHex;
+
+    @ApiModelProperty(value = "排序")
+    private Integer sort;
+
+    @ApiModelProperty(value = "得分")
+    private Double score;
+
+}
+

+ 20 - 43
ktg-framework/src/main/java/com/ktg/framework/aspectj/DataScopeAspect.java

@@ -15,12 +15,11 @@ import com.ktg.common.utils.SecurityUtils;
 /**
  * 数据过滤处理
  *
- * @author ruoyi
+ * @author guoruan
  */
 @Aspect
 @Component
-public class DataScopeAspect
-{
+public class DataScopeAspect {
     /**
      * 全部数据权限
      */
@@ -52,22 +51,18 @@ public class DataScopeAspect
     public static final String DATA_SCOPE = "dataScope";
 
     @Before("@annotation(controllerDataScope)")
-    public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable
-    {
+    public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable {
         clearDataScope(point);
         handleDataScope(point, controllerDataScope);
     }
 
-    protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope)
-    {
+    protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope) {
         // 获取当前的用户
         LoginUser loginUser = SecurityUtils.getLoginUser();
-        if (StringUtils.isNotNull(loginUser))
-        {
+        if (StringUtils.isNotNull(loginUser)) {
             SysUser currentUser = loginUser.getUser();
             // 如果是超级管理员,则不过滤数据
-            if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin())
-            {
+            if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin()) {
                 dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
                         controllerDataScope.userAlias());
             }
@@ -78,56 +73,40 @@ public class DataScopeAspect
      * 数据范围过滤
      *
      * @param joinPoint 切点
-     * @param user 用户
+     * @param user      用户
      * @param userAlias 别名
      */
-    public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias)
-    {
+    public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias) {
         StringBuilder sqlString = new StringBuilder();
 
-        for (SysRole role : user.getRoles())
-        {
+        for (SysRole role : user.getRoles()) {
             String dataScope = role.getDataScope();
-            if (DATA_SCOPE_ALL.equals(dataScope))
-            {
+            if (DATA_SCOPE_ALL.equals(dataScope)) {
                 sqlString = new StringBuilder();
                 break;
-            }
-            else if (DATA_SCOPE_CUSTOM.equals(dataScope))
-            {
+            } else if (DATA_SCOPE_CUSTOM.equals(dataScope)) {
                 sqlString.append(StringUtils.format(
                         " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias,
                         role.getRoleId()));
-            }
-            else if (DATA_SCOPE_DEPT.equals(dataScope))
-            {
+            } else if (DATA_SCOPE_DEPT.equals(dataScope)) {
                 sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
-            }
-            else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
-            {
+            } else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) {
                 sqlString.append(StringUtils.format(
                         " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
                         deptAlias, user.getDeptId(), user.getDeptId()));
-            }
-            else if (DATA_SCOPE_SELF.equals(dataScope))
-            {
-                if (StringUtils.isNotBlank(userAlias))
-                {
+            } else if (DATA_SCOPE_SELF.equals(dataScope)) {
+                if (StringUtils.isNotBlank(userAlias)) {
                     sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
-                }
-                else
-                {
+                } else {
                     // 数据权限为仅本人且没有userAlias别名不查询任何数据
                     sqlString.append(" OR 1=0 ");
                 }
             }
         }
 
-        if (StringUtils.isNotBlank(sqlString.toString()))
-        {
+        if (StringUtils.isNotBlank(sqlString.toString())) {
             Object params = joinPoint.getArgs()[0];
-            if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
-            {
+            if (StringUtils.isNotNull(params) && params instanceof BaseEntity) {
                 BaseEntity baseEntity = (BaseEntity) params;
                 baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
             }
@@ -137,11 +116,9 @@ public class DataScopeAspect
     /**
      * 拼接权限sql前先清空params.dataScope参数防止注入
      */
-    private void clearDataScope(final JoinPoint joinPoint)
-    {
+    private void clearDataScope(final JoinPoint joinPoint) {
         Object params = joinPoint.getArgs()[0];
-        if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
-        {
+        if (StringUtils.isNotNull(params) && params instanceof BaseEntity) {
             BaseEntity baseEntity = (BaseEntity) params;
             baseEntity.getParams().put(DATA_SCOPE, "");
         }

+ 2 - 2
ktg-framework/src/main/java/com/ktg/framework/aspectj/DataSourceAspect.java

@@ -17,8 +17,8 @@ import com.ktg.framework.datasource.DynamicDataSourceContextHolder;
 
 /**
  * 多数据源处理
- * 
- * @author ruoyi
+ *
+ * @author guoruan
  */
 @Aspect
 @Order(1)

+ 6 - 6
ktg-framework/src/main/java/com/ktg/framework/aspectj/LogAspect.java

@@ -29,8 +29,8 @@ import com.ktg.system.domain.SysOperLog;
 
 /**
  * 操作日志记录处理
- * 
- * @author ruoyi
+ *
+ * @author guoruan
  */
 @Aspect
 @Component
@@ -51,7 +51,7 @@ public class LogAspect
 
     /**
      * 拦截异常操作
-     * 
+     *
      * @param joinPoint 切点
      * @param e 异常
      */
@@ -107,7 +107,7 @@ public class LogAspect
 
     /**
      * 获取注解中对方法的描述信息 用于Controller层注解
-     * 
+     *
      * @param log 日志
      * @param operLog 操作日志
      * @throws Exception
@@ -135,7 +135,7 @@ public class LogAspect
 
     /**
      * 获取请求的参数,放到log中
-     * 
+     *
      * @param operLog 操作日志
      * @throws Exception 异常
      */
@@ -182,7 +182,7 @@ public class LogAspect
 
     /**
      * 判断是否需要过滤的对象。
-     * 
+     *
      * @param o 对象信息。
      * @return 如果是需要过滤的对象,则返回true;否则返回false。
      */

+ 146 - 0
ktg-framework/src/main/java/com/ktg/framework/aspectj/MarsDataScopeAspect.java

@@ -0,0 +1,146 @@
+package com.ktg.framework.aspectj;
+
+import cn.hutool.core.lang.Assert;
+import com.ktg.common.annotation.MarsDataScope;
+import com.ktg.common.core.domain.BaseEntity;
+import com.ktg.common.core.domain.entity.SysRole;
+import com.ktg.common.core.domain.entity.SysUser;
+import com.ktg.common.core.domain.model.LoginUser;
+import com.ktg.common.utils.SecurityUtils;
+import com.ktg.common.utils.StringUtils;
+import com.ktg.system.service.ISysUserService;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 数据过滤处理
+ *
+ * @author guoruan
+ */
+@Aspect
+@Component
+public class MarsDataScopeAspect {
+    /**
+     * 全部数据权限
+     */
+    public static final String MARS_DATA_SCOPE_ALL = "1";
+
+    /**
+     * 自定数据权限
+     */
+    public static final String MARS_DATA_SCOPE_CUSTOM = "2";
+
+    /**
+     * 岗位数据权限
+     */
+    public static final String MARS_DATA_SCOPE_WORKSTATION = "3";
+
+    /**
+     * 岗位及以下数据权限
+     */
+    public static final String MARS_DATA_SCOPE_WORKSTATION_AND_CHILD = "4";
+
+    /**
+     * 仅本人数据权限
+     */
+    public static final String MARS_DATA_SCOPE_SELF = "5";
+
+    /**
+     * 数据权限过滤关键字
+     */
+    public static final String MARS_DATA_SCOPE = "workstationScope";
+
+
+    @Autowired
+    private ISysUserService iSysUserService;
+
+    @Before("@annotation(controllerDataScope)")
+    public void doBefore(JoinPoint point, MarsDataScope controllerDataScope) {
+        clearDataScope(point);
+        handleDataScope(point, controllerDataScope);
+    }
+
+    protected void handleDataScope(final JoinPoint joinPoint, MarsDataScope controllerDataScope) {
+        // 获取当前的用户
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        List<Long> allChildWorkstationByUserIds = iSysUserService.getAllChildWorkstationByUserId(loginUser.getUser().getUserId());
+        if (StringUtils.isNotNull(loginUser)) {
+            SysUser currentUser = loginUser.getUser();
+            // 如果是超级管理员,则不过滤数据
+            if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin()) {
+                dataScopeFilter(joinPoint, currentUser, controllerDataScope.workstationAlias(), controllerDataScope.userAlias(), allChildWorkstationByUserIds);
+            }
+        }
+    }
+
+    /**
+     * 数据范围过滤
+     *
+     * @param joinPoint 切点
+     * @param user      用户
+     * @param workstationAlias 别名
+     */
+    public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String workstationAlias, String userAlias, List<Long> allChildWorkstationByUserIds) {
+        StringBuilder sqlString = new StringBuilder();
+
+        for (SysRole role : user.getRoles()) {
+            String marsDataScope = role.getMarsDataScope();
+            if (MARS_DATA_SCOPE_ALL.equals(marsDataScope)) {
+                // 1.全部数据权限
+                sqlString = new StringBuilder();
+                break;
+            } else if (MARS_DATA_SCOPE_CUSTOM.equals(marsDataScope)) {
+                // 2.自定义数据权限
+                sqlString.append(StringUtils.format(
+                        " OR {}.workstation_id IN ( SELECT workstation_id FROM sys_role_workstation WHERE role_id = {} ) ", workstationAlias,
+                        role.getRoleId()));
+            } else if (MARS_DATA_SCOPE_WORKSTATION.equals(marsDataScope)) {
+                // 3.自身岗位权限
+                sqlString.append(StringUtils.format(" OR {}.workstation_id IN ( SELECT workstation_id FROM is_user_workstation WHERE user_id = {} )", workstationAlias, user.getUserId()));
+            } else if (MARS_DATA_SCOPE_WORKSTATION_AND_CHILD.equals(marsDataScope)) {
+               // 4.自身部门及以下数据权限
+                Assert.isFalse(allChildWorkstationByUserIds.isEmpty(), "请先给该人员配置岗位信息!");
+                String result = allChildWorkstationByUserIds.stream()
+                        .map(String::valueOf)
+                        .collect(Collectors.joining(","));
+                sqlString.append(StringUtils.format(" OR {}.workstation_id IN ({})",
+                        workstationAlias, result));
+            } else if (MARS_DATA_SCOPE_SELF.equals(marsDataScope)) {
+                // 5.自身权限
+                if (StringUtils.isNotBlank(userAlias)) {
+                    sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
+                } else {
+                    // 数据权限为仅本人且没有userAlias别名不查询任何数据
+                    sqlString.append(" OR 1=0 ");
+                }
+            }
+        }
+
+        if (StringUtils.isNotBlank(sqlString.toString())) {
+            Object params = joinPoint.getArgs()[0];
+            if (StringUtils.isNotNull(params) && params instanceof BaseEntity) {
+                BaseEntity baseBean = (BaseEntity) params;
+                baseBean.getParamMap().put(MARS_DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
+            }
+        }
+    }
+
+    /**
+     * 拼接权限sql前先清空params.dataScope参数防止注入
+     */
+    private void clearDataScope(final JoinPoint joinPoint) {
+        Object paramMap = joinPoint.getArgs()[0];
+        // if (StringUtils.isNotNull(paramMap) && paramMap instanceof BaseBean) {
+        // 为了兼容BaseEntity
+            if (StringUtils.isNotNull(paramMap) && paramMap instanceof BaseEntity) {
+                BaseEntity baseBean = (BaseEntity) paramMap;
+            baseBean.getParamMap().put(MARS_DATA_SCOPE, "");
+        }
+    }
+}

+ 1 - 1
ktg-framework/src/main/java/com/ktg/framework/aspectj/RateLimiterAspect.java

@@ -23,7 +23,7 @@ import com.ktg.common.utils.ip.IpUtils;
 /**
  * 限流处理
  *
- * @author ruoyi
+ * @author guoruan
  */
 @Aspect
 @Component

+ 1 - 1
ktg-framework/src/main/java/com/ktg/framework/config/ApplicationConfig.java

@@ -10,7 +10,7 @@ import org.springframework.context.annotation.EnableAspectJAutoProxy;
 /**
  * 程序注解配置
  *
- * @author ruoyi
+ * @author guoruan
  */
 @Configuration
 // 表示通过aop框架暴露该代理对象,AopContext能够访问

+ 2 - 2
ktg-framework/src/main/java/com/ktg/framework/config/CaptchaConfig.java

@@ -9,8 +9,8 @@ import static com.google.code.kaptcha.Constants.*;
 
 /**
  * 验证码配置
- * 
- * @author ruoyi
+ *
+ * @author guoruan
  */
 @Configuration
 public class CaptchaConfig

+ 4 - 4
ktg-framework/src/main/java/com/ktg/framework/config/DruidConfig.java

@@ -26,8 +26,8 @@ import com.ktg.framework.datasource.DynamicDataSource;
 
 /**
  * druid 配置多数据源
- * 
- * @author ruoyi
+ *
+ * @author guoruan
  */
 @Configuration
 public class DruidConfig
@@ -58,10 +58,10 @@ public class DruidConfig
         setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");
         return new DynamicDataSource(masterDataSource, targetDataSources);
     }
-    
+
     /**
      * 设置数据源
-     * 
+     *
      * @param targetDataSources 备选数据源集合
      * @param sourceName 数据源名称
      * @param beanName bean名称

+ 2 - 2
ktg-framework/src/main/java/com/ktg/framework/config/FastJson2JsonRedisSerializer.java

@@ -13,8 +13,8 @@ import java.nio.charset.Charset;
 
 /**
  * Redis使用FastJson序列化
- * 
- * @author ruoyi
+ *
+ * @author guoruan
  */
 public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
 {

+ 1 - 1
ktg-framework/src/main/java/com/ktg/framework/config/FilterConfig.java

@@ -15,7 +15,7 @@ import com.ktg.common.utils.StringUtils;
 /**
  * Filter配置
  *
- * @author ruoyi
+ * @author guoruan
  */
 @Configuration
 public class FilterConfig

+ 3 - 3
ktg-framework/src/main/java/com/ktg/framework/config/KaptchaTextCreator.java

@@ -5,8 +5,8 @@ import com.google.code.kaptcha.text.impl.DefaultTextCreator;
 
 /**
  * 验证码文本生成器
- * 
- * @author ruoyi
+ *
+ * @author guoruan
  */
 public class KaptchaTextCreator extends DefaultTextCreator
 {
@@ -72,4 +72,4 @@ public class KaptchaTextCreator extends DefaultTextCreator
         suChinese.append("=?@" + result);
         return suChinese.toString();
     }
-}
+}

+ 15 - 13
ktg-framework/src/main/java/com/ktg/framework/config/MyBatisConfig.java

@@ -1,21 +1,14 @@
 package com.ktg.framework.config;
 
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import javax.sql.DataSource;
-
 import com.baomidou.mybatisplus.annotation.DbType;
 import com.baomidou.mybatisplus.core.config.GlobalConfig;
 import com.baomidou.mybatisplus.core.toolkit.GlobalConfigUtils;
 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
 import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
 import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
+import com.ktg.common.utils.StringUtils;
 import org.apache.ibatis.io.VFS;
 import org.apache.ibatis.session.SqlSessionFactory;
-import org.mybatis.spring.SqlSessionFactoryBean;
 import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
@@ -29,12 +22,18 @@ import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
 import org.springframework.core.type.classreading.MetadataReader;
 import org.springframework.core.type.classreading.MetadataReaderFactory;
 import org.springframework.util.ClassUtils;
-import com.ktg.common.utils.StringUtils;
+
+import javax.sql.DataSource;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
 
 /**
  * Mybatis支持*匹配扫描包
  *
- * @author ruoyi
+ * @author guoruan
  */
 @Configuration
 public class MyBatisConfig
@@ -151,13 +150,16 @@ public class MyBatisConfig
         GlobalConfig globalConfig = GlobalConfigUtils.defaults();
         globalConfig.setMetaObjectHandler(new MyMetaObjectHandler());
         GlobalConfig.DbConfig dbConfig = new GlobalConfig.DbConfig();
-        dbConfig.setLogicDeleteField("delFlag");
-        dbConfig.setLogicDeleteValue("2");
+        // dbConfig.setLogicDeleteField("delFlag");
+        // dbConfig.setLogicDeleteValue("2");
+
         globalConfig.setDbConfig(dbConfig);
         sessionFactory.setGlobalConfig(globalConfig);
 
         MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
-        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
+        // interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
+        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.SQL_SERVER));
+
         sessionFactory.setPlugins(interceptor);
 
         return sessionFactory.getObject();

+ 21 - 2
ktg-framework/src/main/java/com/ktg/framework/config/MyMetaObjectHandler.java

@@ -2,21 +2,40 @@ package com.ktg.framework.config;
 
 import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
 import com.ktg.common.utils.SecurityUtils;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.ibatis.reflection.MetaObject;
 import org.springframework.stereotype.Component;
 
 /**
  * mybatis-plus自动填充
  */
+@Slf4j
 @Component
 public class MyMetaObjectHandler implements MetaObjectHandler {
+
+    // 设置默认账号,获取用户失败时
+    private final String defaultAccountId = "0";
     @Override
     public void insertFill(MetaObject metaObject) {
-        this.strictInsertFill(metaObject,"createBy",String.class, SecurityUtils.getLoginUser().getUsername());
+        String userId;
+        try {
+            userId = String.valueOf(SecurityUtils.getLoginUser().getUserId());
+        } catch (Exception e){
+            log.error("获取登录用户信息失败,给予默认用户信息");
+            userId = defaultAccountId;
+        }
+        this.strictInsertFill(metaObject,"createBy", String.class, userId);
     }
 
     @Override
     public void updateFill(MetaObject metaObject) {
-        this.strictUpdateFill(metaObject,"updateBy",String.class,SecurityUtils.getLoginUser().getUsername());
+        String userId;
+        try {
+            userId = String.valueOf(SecurityUtils.getLoginUser().getUserId());
+        } catch (Exception e){
+            log.error("获取登录用户信息失败,给予默认用户信息");
+            userId = defaultAccountId;
+        }
+        this.strictUpdateFill(metaObject,"updateBy", String.class, userId);
     }
 }

+ 2 - 2
ktg-framework/src/main/java/com/ktg/framework/config/RedisConfig.java

@@ -16,8 +16,8 @@ import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator
 
 /**
  * redis配置
- * 
- * @author ruoyi
+ *
+ * @author guoruan
  */
 @Configuration
 @EnableCaching

+ 3 - 3
ktg-framework/src/main/java/com/ktg/framework/config/ResourcesConfig.java

@@ -15,8 +15,8 @@ import com.ktg.framework.interceptor.RepeatSubmitInterceptor;
 
 /**
  * 通用配置
- * 
- * @author ruoyi
+ *
+ * @author guoruan
  */
 @Configuration
 public class ResourcesConfig implements WebMvcConfigurer
@@ -67,4 +67,4 @@ public class ResourcesConfig implements WebMvcConfigurer
         // 返回新的CorsFilter
         return new CorsFilter(source);
     }
-}
+}

+ 13 - 6
ktg-framework/src/main/java/com/ktg/framework/config/SecurityConfig.java

@@ -20,8 +20,8 @@ import com.ktg.framework.security.handle.LogoutSuccessHandlerImpl;
 
 /**
  * spring security配置
- * 
- * @author ruoyi
+ *
+ * @author guoruan
  */
 @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
 public class SecurityConfig extends WebSecurityConfigurerAdapter
@@ -31,7 +31,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
      */
     @Autowired
     private UserDetailsService userDetailsService;
-    
+
     /**
      * 认证失败处理类
      */
@@ -49,13 +49,13 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
      */
     @Autowired
     private JwtAuthenticationTokenFilter authenticationTokenFilter;
-    
+
     /**
      * 跨域过滤器
      */
     @Autowired
     private CorsFilter corsFilter;
-    
+
     /**
      * 解决 无法直接注入 AuthenticationManager
      *
@@ -97,8 +97,15 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
                 // 过滤请求
                 .authorizeRequests()
                 // 对于登录login 注册register 验证码captchaImage 允许匿名访问
-                .antMatchers("/login", "/register", "/captchaImage").anonymous()
+                .antMatchers("/login", "/register", "/captchaImage", "/cabinetLogin").anonymous()
+                .antMatchers("/loginByFingerprint", "/loginByFingerprintDat", "/loginByFace", "/loginByArcFace").permitAll()
                 .antMatchers("/mobile/login/**").permitAll()
+                .antMatchers("/iscs/card/login").permitAll()
+                .antMatchers("/iscs/card/loginByCard").permitAll()
+                .antMatchers("/iscs/key/selectIsKeyByNfcWithoutAuth").permitAll()
+                .antMatchers("/iscs/hardware/material-api/selectMaterialsByRfidList").permitAll()
+                .antMatchers("/iscs/hardware/material-api/selectCabinetMaterials").permitAll()
+                .antMatchers("/iscs/hardware-api/updateSwitchList").anonymous()
                 .antMatchers(
                         HttpMethod.GET,
                         "/",

+ 3 - 3
ktg-framework/src/main/java/com/ktg/framework/config/ServerConfig.java

@@ -6,15 +6,15 @@ import com.ktg.common.utils.ServletUtils;
 
 /**
  * 服务相关配置
- * 
- * @author ruoyi
+ *
+ * @author guoruan
  */
 @Component
 public class ServerConfig
 {
     /**
      * 获取完整的请求路径,包括:域名,端口,上下文访问路径
-     * 
+     *
      * @return 服务地址
      */
     public String getUrl()

+ 1 - 1
ktg-framework/src/main/java/com/ktg/framework/config/ThreadPoolConfig.java

@@ -12,7 +12,7 @@ import java.util.concurrent.ThreadPoolExecutor;
 /**
  * 线程池配置
  *
- * @author ruoyi
+ * @author guoruan
  **/
 @Configuration
 public class ThreadPoolConfig

+ 41 - 0
ktg-framework/src/main/java/com/ktg/framework/config/WebMvcConfig.java

@@ -0,0 +1,41 @@
+package com.ktg.framework.config;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.StringHttpMessageConverter;
+import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import java.util.List;
+
+@Configuration
+@EnableWebMvc
+public class WebMvcConfig implements WebMvcConfigurer {
+
+    @Override
+    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
+        // 配置StringHttpMessageConverter(如果需要的话)
+        converters.add(new StringHttpMessageConverter());
+
+        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
+        // 忽略序列化时null字段的显示
+        // builder.serializationInclusion(JsonInclude.Include.NON_NULL);
+        ObjectMapper objectMapper = builder.build();
+
+        SimpleModule simpleModule = new SimpleModule();
+        // 为Long类型注册ToStringSerializer,将其序列化为字符串
+        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
+        objectMapper.registerModule(simpleModule);
+
+        // 将配置好的ObjectMapper添加到MappingJackson2HttpMessageConverter中
+        converters.add(new MappingJackson2HttpMessageConverter(objectMapper));
+
+        // 如果继承了其他WebMvcConfigurer实现类,可以调用super.configureMessageConverters(converters);
+        // 但在这个例子中,我们并没有继承其他实现类,所以不需要调用
+    }
+}

+ 2 - 2
ktg-framework/src/main/java/com/ktg/framework/config/properties/DruidProperties.java

@@ -6,8 +6,8 @@ import com.alibaba.druid.pool.DruidDataSource;
 
 /**
  * druid 配置属性
- * 
- * @author ruoyi
+ *
+ * @author guoruan
  */
 @Configuration
 public class DruidProperties

+ 3 - 3
ktg-framework/src/main/java/com/ktg/framework/datasource/DynamicDataSource.java

@@ -6,8 +6,8 @@ import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
 
 /**
  * 动态数据源
- * 
- * @author ruoyi
+ *
+ * @author guoruan
  */
 public class DynamicDataSource extends AbstractRoutingDataSource
 {
@@ -23,4 +23,4 @@ public class DynamicDataSource extends AbstractRoutingDataSource
     {
         return DynamicDataSourceContextHolder.getDataSourceType();
     }
-}
+}

+ 2 - 2
ktg-framework/src/main/java/com/ktg/framework/datasource/DynamicDataSourceContextHolder.java

@@ -5,8 +5,8 @@ import org.slf4j.LoggerFactory;
 
 /**
  * 数据源切换处理
- * 
- * @author ruoyi
+ *
+ * @author guoruan
  */
 public class DynamicDataSourceContextHolder
 {

+ 2 - 2
ktg-framework/src/main/java/com/ktg/framework/interceptor/impl/SameUrlDataInterceptor.java

@@ -19,8 +19,8 @@ import com.ktg.framework.interceptor.RepeatSubmitInterceptor;
 /**
  * 判断请求url和数据是否和上一次相同,
  * 如果和上次相同,则是重复提交表单。 有效时间为10秒内。
- * 
- * @author ruoyi
+ *
+ * @author guoruan
  */
 @Component
 public class SameUrlDataInterceptor extends RepeatSubmitInterceptor

+ 3 - 3
ktg-framework/src/main/java/com/ktg/framework/manager/AsyncManager.java

@@ -8,8 +8,8 @@ import com.ktg.common.utils.spring.SpringUtils;
 
 /**
  * 异步任务管理器
- * 
- * @author ruoyi
+ *
+ * @author guoruan
  */
 public class AsyncManager
 {
@@ -37,7 +37,7 @@ public class AsyncManager
 
     /**
      * 执行任务
-     * 
+     *
      * @param task 任务
      */
     public void execute(TimerTask task)

+ 1 - 1
ktg-framework/src/main/java/com/ktg/framework/manager/ShutdownManager.java

@@ -8,7 +8,7 @@ import javax.annotation.PreDestroy;
 /**
  * 确保应用退出时能关闭后台线程
  *
- * @author ruoyi
+ * @author guoruan
  */
 @Component
 public class ShutdownManager

+ 4 - 4
ktg-framework/src/main/java/com/ktg/framework/manager/factory/AsyncFactory.java

@@ -18,8 +18,8 @@ import eu.bitwalker.useragentutils.UserAgent;
 
 /**
  * 异步工厂(产生任务用)
- * 
- * @author ruoyi
+ *
+ * @author guoruan
  */
 public class AsyncFactory
 {
@@ -27,7 +27,7 @@ public class AsyncFactory
 
     /**
      * 记录登录信息
-     * 
+     *
      * @param username 用户名
      * @param status 状态
      * @param message 消息
@@ -82,7 +82,7 @@ public class AsyncFactory
 
     /**
      * 操作日志记录
-     * 
+     *
      * @param operLog 操作日志信息
      * @return 任务task
      */

+ 2 - 2
ktg-framework/src/main/java/com/ktg/framework/security/filter/JwtAuthenticationTokenFilter.java

@@ -18,8 +18,8 @@ import com.ktg.framework.web.service.TokenService;
 
 /**
  * token过滤器 验证token有效性
- * 
- * @author ruoyi
+ *
+ * @author guoruan
  */
 @Component
 public class JwtAuthenticationTokenFilter extends OncePerRequestFilter

+ 2 - 2
ktg-framework/src/main/java/com/ktg/framework/security/handle/AuthenticationEntryPointImpl.java

@@ -15,8 +15,8 @@ import com.ktg.common.utils.StringUtils;
 
 /**
  * 认证失败处理类 返回未授权
- * 
- * @author ruoyi
+ *
+ * @author guoruan
  */
 @Component
 public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable

+ 14 - 15
ktg-framework/src/main/java/com/ktg/framework/security/handle/LogoutSuccessHandlerImpl.java

@@ -1,28 +1,26 @@
 package com.ktg.framework.security.handle;
 
-import java.io.IOException;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
 import com.alibaba.fastjson.JSON;
-import com.ktg.common.constant.Constants;
 import com.ktg.common.constant.HttpStatus;
 import com.ktg.common.core.domain.AjaxResult;
 import com.ktg.common.core.domain.model.LoginUser;
 import com.ktg.common.utils.ServletUtils;
 import com.ktg.common.utils.StringUtils;
-import com.ktg.framework.manager.AsyncManager;
-import com.ktg.framework.manager.factory.AsyncFactory;
 import com.ktg.framework.web.service.TokenService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
 
 /**
  * 自定义退出处理类 返回成功
- * 
- * @author ruoyi
+ *
+ * @author guoruan
  */
 @Configuration
 public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler
@@ -32,7 +30,7 @@ public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler
 
     /**
      * 退出处理
-     * 
+     *
      * @return
      */
     @Override
@@ -44,9 +42,10 @@ public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler
         {
             String userName = loginUser.getUsername();
             // 删除用户缓存记录
+            System.out.println("退出--------"+loginUser.getToken());
             tokenService.delLoginUser(loginUser.getToken());
             // 记录用户退出日志
-            AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, "退出成功"));
+            // AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, "退出成功"));
         }
         ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(HttpStatus.SUCCESS, "退出成功")));
     }

+ 4 - 4
ktg-framework/src/main/java/com/ktg/framework/web/domain/Server.java

@@ -23,13 +23,13 @@ import oshi.util.Util;
 
 /**
  * 服务器相关信息
- * 
- * @author ruoyi
+ *
+ * @author guoruan
  */
 public class Server
 {
     private static final int OSHI_WAIT_SECOND = 1000;
-    
+
     /**
      * CPU相关信息
      */
@@ -209,7 +209,7 @@ public class Server
 
     /**
      * 字节转换
-     * 
+     *
      * @param size 字节大小
      * @return 转换后值
      */

+ 2 - 2
ktg-framework/src/main/java/com/ktg/framework/web/domain/server/Cpu.java

@@ -4,8 +4,8 @@ import com.ktg.common.utils.Arith;
 
 /**
  * CPU相关信息
- * 
- * @author ruoyi
+ *
+ * @author guoruan
  */
 public class Cpu
 {

+ 2 - 2
ktg-framework/src/main/java/com/ktg/framework/web/domain/server/Jvm.java

@@ -6,8 +6,8 @@ import com.ktg.common.utils.DateUtils;
 
 /**
  * JVM相关信息
- * 
- * @author ruoyi
+ *
+ * @author guoruan
  */
 public class Jvm
 {

+ 2 - 2
ktg-framework/src/main/java/com/ktg/framework/web/domain/server/Mem.java

@@ -4,8 +4,8 @@ import com.ktg.common.utils.Arith;
 
 /**
  * 內存相关信息
- * 
- * @author ruoyi
+ *
+ * @author guoruan
  */
 public class Mem
 {

+ 2 - 2
ktg-framework/src/main/java/com/ktg/framework/web/domain/server/Sys.java

@@ -2,8 +2,8 @@ package com.ktg.framework.web.domain.server;
 
 /**
  * 系统相关信息
- * 
- * @author ruoyi
+ *
+ * @author guoruan
  */
 public class Sys
 {

+ 2 - 2
ktg-framework/src/main/java/com/ktg/framework/web/domain/server/SysFile.java

@@ -2,8 +2,8 @@ package com.ktg.framework.web.domain.server;
 
 /**
  * 系统文件相关信息
- * 
- * @author ruoyi
+ *
+ * @author guoruan
  */
 public class SysFile
 {

+ 2 - 2
ktg-framework/src/main/java/com/ktg/framework/web/exception/GlobalExceptionHandler.java

@@ -17,8 +17,8 @@ import com.ktg.common.utils.StringUtils;
 
 /**
  * 全局异常处理器
- * 
- * @author ruoyi
+ *
+ * @author guoruan
  */
 @RestControllerAdvice
 public class GlobalExceptionHandler

+ 36 - 0
ktg-framework/src/main/java/com/ktg/framework/web/service/IscsService.java

@@ -0,0 +1,36 @@
+package com.ktg.framework.web.service;
+
+import com.ktg.system.service.IIscsService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * 用户权限处理
+ *
+ * @author guoruan
+ */
+@Component
+public class IscsService
+{
+    @Autowired
+    private IIscsService iIscsService;
+
+    /**
+     * 获取工卡数据
+     *
+     * @param userId 用户信息
+     * @return 获取工卡数据
+     */
+    public Set<String> getUserCardList(Long userId)
+    {
+        Set<String> perms = new HashSet<>();
+        // 获取工卡数据
+        if (userId != null && userId > 0) {
+            perms.addAll(iIscsService.getUserCardList(userId));
+        }
+        return perms;
+    }
+}

+ 6 - 6
ktg-framework/src/main/java/com/ktg/framework/web/service/PermissionService.java

@@ -9,9 +9,9 @@ import com.ktg.common.utils.SecurityUtils;
 import com.ktg.common.utils.StringUtils;
 
 /**
- * RuoYi首创 自定义权限实现,ss取自SpringSecurity首字母
- * 
- * @author ruoyi
+ * guoruan首创 自定义权限实现,ss取自SpringSecurity首字母
+ *
+ * @author guoruan
  */
 @Service("ss")
 public class PermissionService
@@ -28,7 +28,7 @@ public class PermissionService
 
     /**
      * 验证用户是否具备某权限
-     * 
+     *
      * @param permission 权限字符串
      * @return 用户是否具备某权限
      */
@@ -87,7 +87,7 @@ public class PermissionService
 
     /**
      * 判断用户是否拥有某个角色
-     * 
+     *
      * @param role 角色字符串
      * @return 用户是否具备某角色
      */
@@ -153,7 +153,7 @@ public class PermissionService
 
     /**
      * 判断是否包含权限
-     * 
+     *
      * @param permissions 权限列表
      * @param permission 权限字符串
      * @return 用户是否具备某权限

+ 231 - 47
ktg-framework/src/main/java/com/ktg/framework/web/service/SysLoginService.java

@@ -1,87 +1,110 @@
 package com.ktg.framework.web.service;
 
-import javax.annotation.Resource;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.authentication.BadCredentialsException;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.core.Authentication;
-import org.springframework.stereotype.Component;
+import cn.hutool.core.lang.Assert;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.ktg.common.constant.Constants;
+import com.ktg.common.core.domain.AjaxResult;
 import com.ktg.common.core.domain.entity.SysUser;
+import com.ktg.common.core.domain.model.FingerprintLoginBody;
+import com.ktg.common.core.domain.model.LoginBody;
 import com.ktg.common.core.domain.model.LoginUser;
 import com.ktg.common.core.redis.RedisCache;
 import com.ktg.common.exception.ServiceException;
 import com.ktg.common.exception.user.CaptchaException;
 import com.ktg.common.exception.user.CaptchaExpireException;
 import com.ktg.common.exception.user.UserPasswordNotMatchException;
-import com.ktg.common.utils.DateUtils;
-import com.ktg.common.utils.MessageUtils;
-import com.ktg.common.utils.StringUtils;
-import com.ktg.common.utils.ServletUtils;
+import com.ktg.common.utils.*;
+import com.ktg.common.utils.face.ArcSoftMothodUtil;
+import com.ktg.common.utils.face.FaceMatchUtil;
 import com.ktg.common.utils.ip.IpUtils;
+import com.ktg.common.vo.FaceMatchVO;
+import com.ktg.common.vo.VerificationVO;
 import com.ktg.framework.manager.AsyncManager;
 import com.ktg.framework.manager.factory.AsyncFactory;
+import com.ktg.system.domain.SysUserCharacteristic;
 import com.ktg.system.service.ISysConfigService;
+import com.ktg.system.service.ISysUserCharacteristicService;
+import com.ktg.system.service.ISysUserOnlineService;
 import com.ktg.system.service.ISysUserService;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.Resource;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * 登录校验方法
- * 
- * @author ruoyi
+ *
+ * @author guoruan
  */
+@Slf4j
 @Component
-public class SysLoginService
-{
+public class SysLoginService {
     @Autowired
     private TokenService tokenService;
-
     @Resource
     private AuthenticationManager authenticationManager;
-
     @Autowired
     private RedisCache redisCache;
-    
     @Autowired
     private ISysUserService userService;
-
     @Autowired
     private ISysConfigService configService;
+    @Autowired
+    private ISysUserOnlineService userOnlineService;
+    @Resource
+    private SysPermissionService permissionService;
+    // 令牌秘钥
+    @Value("${token.secret}")
+    private String secret;
+    @Autowired
+    private ISysUserService iSysUserService;
+    @Autowired
+    private ISysUserCharacteristicService iSysUserCharacteristicService;
+
 
     /**
      * 登录验证
-     * 
+     *
      * @param username 用户名
      * @param password 密码
-     * @param code 验证码
-     * @param uuid 唯一标识
+     * @param code     验证码
+     * @param uuid     唯一标识
      * @return 结果
      */
-    public String login(String username, String password, String code, String uuid)
-    {
+    public String login(String username, String password, String code, String uuid) {
         boolean captchaOnOff = configService.selectCaptchaOnOff();
         // 验证码开关
-        if (captchaOnOff)
-        {
+        if (captchaOnOff) {
             validateCaptcha(username, code, uuid);
         }
         // 用户验证
         Authentication authentication = null;
-        try
-        {
+        try {
             // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
             authentication = authenticationManager
                     .authenticate(new UsernamePasswordAuthenticationToken(username, password));
-        }
-        catch (Exception e)
-        {
-            if (e instanceof BadCredentialsException)
-            {
+        } catch (Exception e) {
+            if (e instanceof BadCredentialsException) {
                 AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                 throw new UserPasswordNotMatchException();
-            }
-            else
-            {
+            } else {
                 AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
                 throw new ServiceException(e.getMessage());
             }
@@ -90,29 +113,60 @@ public class SysLoginService
         LoginUser loginUser = (LoginUser) authentication.getPrincipal();
         recordLoginInfo(loginUser.getUserId());
         // 生成token
+        String token = tokenService.createToken(loginUser);
+        // 清理多余的登录者
+        // userOnlineService.forceLogoutByName(username);
+        return token;
+    }
+    @Autowired
+    private UserDetailsService userDetailsService;
+
+    public String loginWithoutPassword(SysUser sysUser) {
+        // 用户验证
+        Authentication authentication;
+        try {
+            //直接不用springsecurity 认证、自己构造出数据
+            UserDetails userDetails = new LoginUser(sysUser.getUserId(), sysUser.getDeptId(), sysUser, permissionService.getMenuPermission(sysUser));
+            authentication = new UsernamePasswordAuthenticationToken(userDetails, null,
+                    AuthorityUtils.createAuthorityList("ROLE_USER"));
+            SecurityContextHolder.getContext().setAuthentication(authentication);
+        } catch (Exception e) {
+            if (e instanceof BadCredentialsException) {
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(sysUser.getUserName(), Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
+                throw new UserPasswordNotMatchException();
+            } else {
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(sysUser.getUserName(), Constants.LOGIN_FAIL, e.getMessage()));
+                throw new ServiceException(e.getMessage());
+            }
+        } finally {
+            SecurityContextHolder.clearContext();
+        }
+        AsyncManager.me().execute(AsyncFactory.recordLogininfor(sysUser.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
+        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
+        // 校验登陆账号
+        userDetailsService.loadUserByUsername(loginUser.getUsername());
+        recordLoginInfo(loginUser.getUserId());
+        // 生成token
         return tokenService.createToken(loginUser);
     }
 
     /**
      * 校验验证码
-     * 
+     *
      * @param username 用户名
-     * @param code 验证码
-     * @param uuid 唯一标识
+     * @param code     验证码
+     * @param uuid     唯一标识
      * @return 结果
      */
-    public void validateCaptcha(String username, String code, String uuid)
-    {
+    public void validateCaptcha(String username, String code, String uuid) {
         String verifyKey = Constants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
         String captcha = redisCache.getCacheObject(verifyKey);
         redisCache.deleteObject(verifyKey);
-        if (captcha == null)
-        {
+        if (captcha == null) {
             AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
             throw new CaptchaExpireException();
         }
-        if (!code.equalsIgnoreCase(captcha))
-        {
+        if (!code.equalsIgnoreCase(captcha)) {
             AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
             throw new CaptchaException();
         }
@@ -123,12 +177,142 @@ public class SysLoginService
      *
      * @param userId 用户ID
      */
-    public void recordLoginInfo(Long userId)
-    {
+    public void recordLoginInfo(Long userId) {
         SysUser sysUser = new SysUser();
         sysUser.setUserId(userId);
         sysUser.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
         sysUser.setLoginDate(DateUtils.getNowDate());
         userService.updateUserProfile(sysUser);
     }
+
+    public String machineLogin(LoginBody loginBody) {
+        // 生成令牌
+        String token = login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),
+                loginBody.getUuid());
+
+        // 因为只能同时在线一个,清理之前的登录
+        String oldToken = redisCache.getCacheObject(Constants.CABINET_LOGIN_TOKEN_KEY + loginBody.getUsername());
+        // 删除之前的登陆信息
+        if (StringUtils.isNotEmpty(oldToken)) {
+            try {
+                Claims claims = Jwts.parser()
+                        .setSigningKey(secret)
+                        .parseClaimsJws(oldToken)
+                        .getBody();
+                // 解析对应的权限以及用户信息
+                String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
+                redisCache.deleteObject(Constants.LOGIN_TOKEN_KEY + uuid);
+            } catch (Exception ignored) {
+            }
+        }
+        // 更换上次登录信息的记录
+        redisCache.setCacheObject(Constants.CABINET_LOGIN_TOKEN_KEY + loginBody.getUsername(), token);
+        return token;
+    }
+
+    public AjaxResult loginByFingerprint(FingerprintLoginBody loginBody) {
+        Assert.notBlank(loginBody.getInputImg(), "输入的指纹不能为空!");
+        // 获取所有的用户指纹
+        List<SysUserCharacteristic> list = iSysUserCharacteristicService.list(Wrappers.<SysUserCharacteristic>lambdaQuery()
+                .eq(SysUserCharacteristic::getType, "0"));
+        Assert.isFalse(list.isEmpty(), "指纹库中暂无您的指纹信息!");
+        List<String> collect = list.stream().map(SysUserCharacteristic::getContent).collect(Collectors.toList());
+        // 通过指纹获取最相似的用户
+        VerificationVO verificationVO = FingerprintComparisonByImg.completableFutureComparison(loginBody.getInputImg(), new HashSet<>(collect));
+        Assert.notNull(verificationVO, "无法根据指纹确定您的身份,请通过其它方式登录!");
+        String fingerprintImg = verificationVO.getFingerprintImg();
+        SysUserCharacteristic one = iSysUserCharacteristicService.getOne(Wrappers.<SysUserCharacteristic>lambdaQuery()
+                .eq(SysUserCharacteristic::getContent, fingerprintImg));
+        SysUser sysUser = iSysUserService.getById(one.getUserId());
+        // 生成令牌
+        AjaxResult ajax = AjaxResult.success();
+        String token = loginWithoutPassword(sysUser);
+        ajax.put(Constants.TOKEN, token);
+        ajax.put("nickName", sysUser.getNickName());
+        return ajax;
+    }
+
+    public AjaxResult loginByFingerprintDat(MultipartFile file) throws IOException {
+        Assert.isTrue(file.getSize() > 0, "输入的指纹不能为空!");
+        // 获取所有的用户指纹
+        List<SysUserCharacteristic> list = iSysUserCharacteristicService.list(Wrappers.<SysUserCharacteristic>lambdaQuery()
+                .eq(SysUserCharacteristic::getType, "1"));
+        Assert.isFalse(list.isEmpty(), "指纹库中暂无您的指纹信息!");
+        List<String> collect = list.stream().map(SysUserCharacteristic::getContent).collect(Collectors.toList());
+        // 通过指纹获取最相似的用户
+        VerificationVO verificationVO = FingerprintComparisonByDat.completableFutureComparison(file, new HashSet<>(collect));
+        Assert.notNull(verificationVO, "无法根据指纹确定您的身份,请通过其它方式登录!");
+        String fingerprint = verificationVO.getFingerprint();
+        SysUserCharacteristic one = iSysUserCharacteristicService.getOne(Wrappers.<SysUserCharacteristic>lambdaQuery()
+                .eq(SysUserCharacteristic::getContent, fingerprint)
+                .last("limit 1"));
+        SysUser sysUser1 = iSysUserService.getById(one.getUserId());
+        SysUser sysUser = userService.selectUserByUserName(sysUser1.getUserName());
+        // 生成令牌
+        AjaxResult ajax = AjaxResult.success();
+        String token = loginWithoutPassword(sysUser);
+        ajax.put(Constants.TOKEN, token);
+        ajax.put("nickName", sysUser.getNickName());
+        return ajax;
+    }
+
+    public AjaxResult loginByFace(MultipartFile file) throws IOException {
+        Assert.isTrue(file.getSize() > 0, "输入的人脸不能为空!");
+        // 获取所有的用户人脸
+        List<SysUserCharacteristic> list = iSysUserCharacteristicService.list(Wrappers.<SysUserCharacteristic>lambdaQuery()
+                .eq(SysUserCharacteristic::getType, "2"));
+        Assert.isFalse(list.isEmpty(), "人脸库中暂无您的人脸信息!");
+        List<String> collect = list.stream().map(SysUserCharacteristic::getContent).collect(Collectors.toList());
+        // List<FaceCutVO> bean = BeanUtils.toBean(list, FaceCutVO.class);
+        // 通过人脸获取最相似的用户
+        // 记录开始时间
+        long startTime1 = System.currentTimeMillis();
+        // FaceMatchVO faceMatchVO = FaceMatchUtil.faceRecognitionComparison(file, new HashSet<>(collect));
+        FaceMatchVO faceMatchVO = FaceMatchUtil.faceRecognitionComparison(file, collect);
+        // 记录结束时间
+        long endTime1 = System.currentTimeMillis();
+        // 计算时间差(毫秒)
+        long duration1 = endTime1 - startTime1;
+        System.out.println("Execution time in milliseconds: " + duration1);
+        Assert.notNull(faceMatchVO, "无法根据人脸确定您的身份,请通过其它方式登录!");
+        String okFace = faceMatchVO.getContent();
+        SysUserCharacteristic one = iSysUserCharacteristicService.getOne(Wrappers.<SysUserCharacteristic>lambdaQuery()
+                .eq(SysUserCharacteristic::getContent, okFace)
+                .last("limit 1"));
+        SysUser sysUser1 = iSysUserService.getById(one.getUserId());
+        SysUser sysUser = userService.selectUserByUserName(sysUser1.getUserName());
+        // 生成令牌
+        AjaxResult ajax = AjaxResult.success();
+        String token = loginWithoutPassword(sysUser);
+        ajax.put(Constants.TOKEN, token);
+        ajax.put("nickName", sysUser.getNickName());
+        return ajax;
+    }
+
+    public AjaxResult loginByArcFace(MultipartFile file) throws IOException {
+
+        Assert.isTrue(file.getSize() > 0, "输入的人脸不能为空!");
+        // 获取所有的用户人脸
+        List<SysUserCharacteristic> list = iSysUserCharacteristicService.list(Wrappers.<SysUserCharacteristic>lambdaQuery()
+                .eq(SysUserCharacteristic::getType, "2"));
+        Assert.isFalse(list.isEmpty(), "人脸库中暂无您的人脸信息!");
+        List<String> collect = list.stream().map(SysUserCharacteristic::getContent).collect(Collectors.toList());
+        // 通过人脸获取最相似的用户
+        // FaceMatchVO faceMatchVO = ArcSoftMothodUtil.completableFutureComparison(file, collect);
+        FaceMatchVO faceMatchVO = ArcSoftMothodUtil.completableFutureComparison(file, new HashSet<>(collect));
+        Assert.notNull(faceMatchVO, "无法根据人脸确定您的身份,请通过其它方式登录!");
+        String okFace = faceMatchVO.getContent();
+        SysUserCharacteristic one = iSysUserCharacteristicService.getOne(Wrappers.<SysUserCharacteristic>lambdaQuery()
+                .eq(SysUserCharacteristic::getContent, okFace)
+                .last("limit 1"));
+        SysUser sysUser = iSysUserService.getById(one.getUserId());
+        // 生成令牌
+        AjaxResult ajax = AjaxResult.success();
+        String token = loginWithoutPassword(sysUser);
+        ajax.put(Constants.TOKEN, token);
+        ajax.put("nickName", sysUser.getNickName());
+        return ajax;
+    }
+
+
 }

+ 4 - 4
ktg-framework/src/main/java/com/ktg/framework/web/service/SysPermissionService.java

@@ -10,8 +10,8 @@ import com.ktg.system.service.ISysRoleService;
 
 /**
  * 用户权限处理
- * 
- * @author ruoyi
+ *
+ * @author guoruan
  */
 @Component
 public class SysPermissionService
@@ -24,7 +24,7 @@ public class SysPermissionService
 
     /**
      * 获取角色数据权限
-     * 
+     *
      * @param user 用户信息
      * @return 角色权限信息
      */
@@ -45,7 +45,7 @@ public class SysPermissionService
 
     /**
      * 获取菜单数据权限
-     * 
+     *
      * @param user 用户信息
      * @return 菜单权限信息
      */

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно