523096025 2 mesi fa
commit
82eb4da8d8
100 ha cambiato i file con 12493 aggiunte e 0 eliminazioni
  1. 47 0
      .gitignore
  2. 20 0
      LICENSE
  3. 10 0
      README.md
  4. 151 0
      app-admin/pom.xml
  5. 29 0
      app-admin/src/main/java/com/ruoyi/RuoYiApplication.java
  6. 18 0
      app-admin/src/main/java/com/ruoyi/RuoYiServletInitializer.java
  7. 470 0
      app-admin/src/main/java/com/ruoyi/web/controller/app/DataReportController.java
  8. 181 0
      app-admin/src/main/java/com/ruoyi/web/controller/app/DataStatisticsController.java
  9. 125 0
      app-admin/src/main/java/com/ruoyi/web/controller/app/DeadDisposalController.java
  10. 213 0
      app-admin/src/main/java/com/ruoyi/web/controller/app/DistributeBatchController.java
  11. 139 0
      app-admin/src/main/java/com/ruoyi/web/controller/app/DryLossRatioController.java
  12. 149 0
      app-admin/src/main/java/com/ruoyi/web/controller/app/EntranceBatchController.java
  13. 118 0
      app-admin/src/main/java/com/ruoyi/web/controller/app/EntranceInspectionController.java
  14. 161 0
      app-admin/src/main/java/com/ruoyi/web/controller/app/HarmlessTreatmentController.java
  15. 225 0
      app-admin/src/main/java/com/ruoyi/web/controller/app/HookBindController.java
  16. 123 0
      app-admin/src/main/java/com/ruoyi/web/controller/app/HookController.java
  17. 128 0
      app-admin/src/main/java/com/ruoyi/web/controller/app/HookRegisterController.java
  18. 152 0
      app-admin/src/main/java/com/ruoyi/web/controller/app/InspectionItemController.java
  19. 212 0
      app-admin/src/main/java/com/ruoyi/web/controller/app/MonitorController.java
  20. 184 0
      app-admin/src/main/java/com/ruoyi/web/controller/app/NFIDReadRecordController.java
  21. 16 0
      app-admin/src/main/java/com/ruoyi/web/controller/app/NFIDReaderController.java
  22. 126 0
      app-admin/src/main/java/com/ruoyi/web/controller/app/PigCategoryController.java
  23. 147 0
      app-admin/src/main/java/com/ruoyi/web/controller/app/PigpenController.java
  24. 103 0
      app-admin/src/main/java/com/ruoyi/web/controller/app/PorkOtherProduceController.java
  25. 119 0
      app-admin/src/main/java/com/ruoyi/web/controller/app/PorkSideProduceController.java
  26. 99 0
      app-admin/src/main/java/com/ruoyi/web/controller/app/PorkSideProduceRecordController.java
  27. 144 0
      app-admin/src/main/java/com/ruoyi/web/controller/app/ProductCategoryController.java
  28. 58 0
      app-admin/src/main/java/com/ruoyi/web/controller/app/ProductTraceController.java
  29. 174 0
      app-admin/src/main/java/com/ruoyi/web/controller/app/PurchaserController.java
  30. 147 0
      app-admin/src/main/java/com/ruoyi/web/controller/app/RegionController.java
  31. 95 0
      app-admin/src/main/java/com/ruoyi/web/controller/app/SlaughterBatchController.java
  32. 226 0
      app-admin/src/main/java/com/ruoyi/web/controller/app/SlaughterRelationController.java
  33. 151 0
      app-admin/src/main/java/com/ruoyi/web/controller/app/StaffController.java
  34. 169 0
      app-admin/src/main/java/com/ruoyi/web/controller/app/SupplierController.java
  35. 351 0
      app-admin/src/main/java/com/ruoyi/web/controller/common/BottomRightWatermarkUtil.java
  36. 94 0
      app-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java
  37. 428 0
      app-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java
  38. 98 0
      app-admin/src/main/java/com/ruoyi/web/controller/common/FontLoader.java
  39. 72 0
      app-admin/src/main/java/com/ruoyi/web/controller/common/InMemoryMultipartFile.java
  40. 46 0
      app-admin/src/main/java/com/ruoyi/web/controller/common/MultipartConfig.java
  41. 121 0
      app-admin/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java
  42. 27 0
      app-admin/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java
  43. 82 0
      app-admin/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java
  44. 69 0
      app-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java
  45. 83 0
      app-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java
  46. 133 0
      app-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java
  47. 132 0
      app-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java
  48. 121 0
      app-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java
  49. 131 0
      app-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java
  50. 29 0
      app-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java
  51. 173 0
      app-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java
  52. 142 0
      app-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java
  53. 120 0
      app-admin/src/main/java/com/ruoyi/web/controller/system/SysPostController.java
  54. 163 0
      app-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java
  55. 38 0
      app-admin/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java
  56. 253 0
      app-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java
  57. 249 0
      app-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java
  58. 183 0
      app-admin/src/main/java/com/ruoyi/web/controller/tool/TestController.java
  59. 125 0
      app-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java
  60. 158 0
      app-admin/src/main/java/com/ruoyi/web/core/nfid/ClientHandler.java
  61. 44 0
      app-admin/src/main/java/com/ruoyi/web/core/nfid/DeviceSessionManager.java
  62. 345 0
      app-admin/src/main/java/com/ruoyi/web/core/nfid/MultiReaderNFIDProcessor.java
  63. 322 0
      app-admin/src/main/java/com/ruoyi/web/core/nfid/NFIDFrameParser.java
  64. 26 0
      app-admin/src/main/java/com/ruoyi/web/core/nfid/NFIDRedisData.java
  65. 66 0
      app-admin/src/main/java/com/ruoyi/web/core/nfid/NFIDServer.java
  66. 246 0
      app-admin/src/main/java/com/ruoyi/web/core/nfid/NFIDTestClient.java
  67. 246 0
      app-admin/src/main/java/com/ruoyi/web/core/nfid/NFIDTestClient1.java
  68. 37 0
      app-admin/src/main/java/com/ruoyi/web/core/wvp/ApiResponseParser.java
  69. 107 0
      app-admin/src/main/java/com/ruoyi/web/core/wvp/DeviceHandler.java
  70. 70 0
      app-admin/src/main/java/com/ruoyi/web/core/wvp/JsonParser.java
  71. 127 0
      app-admin/src/main/java/com/ruoyi/web/core/wvp/SessionHandler.java
  72. 38 0
      app-admin/src/main/java/com/ruoyi/web/core/wvp/domain/ApiResponse.java
  73. 612 0
      app-admin/src/main/java/com/ruoyi/web/core/wvp/domain/DeviceChannelExtend.java
  74. 15 0
      app-admin/src/main/java/com/ruoyi/web/core/wvp/domain/DeviceChannelList.java
  75. 170 0
      app-admin/src/main/java/com/ruoyi/web/core/wvp/domain/Login.java
  76. 37 0
      app-admin/src/main/java/com/ruoyi/web/primary/controller/DatabaseMetadataController.java
  77. 45 0
      app-admin/src/main/java/com/ruoyi/web/primary/entity/DatabaseMetadata.java
  78. 24 0
      app-admin/src/main/java/com/ruoyi/web/primary/service/IDatabaseMetadataService.java
  79. 36 0
      app-admin/src/main/java/com/ruoyi/web/primary/service/impl/DatabaseMetadataServiceImpl.java
  80. 65 0
      app-admin/src/main/java/com/ruoyi/web/v2/server/EnvInputServer.java
  81. 210 0
      app-admin/src/main/java/com/ruoyi/web/v2/server/EnvInputServerHandler.java
  82. 87 0
      app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/BaseDisLocationController.java
  83. 46 0
      app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/DeductionDetailsController.java
  84. 20 0
      app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/DeductionMethodController.java
  85. 43 0
      app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/DeductionRecordController.java
  86. 74 0
      app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/InspectionContentController.java
  87. 22 0
      app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/InspectionPointController.java
  88. 73 0
      app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/InspectionRecordController.java
  89. 71 0
      app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/JsCheckDeviceController.java
  90. 70 0
      app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/JsCheckInstrumentController.java
  91. 71 0
      app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/JsCheckProjectController.java
  92. 75 0
      app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/JsDeviceMaintenanceController.java
  93. 71 0
      app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/JsDisinfectController.java
  94. 70 0
      app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/JsDisinfectManageController.java
  95. 105 0
      app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/JsDistributionController.java
  96. 88 0
      app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/JsDivideCircleController.java
  97. 159 0
      app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/JsDrugCheckController.java
  98. 115 0
      app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/JsDrugController.java
  99. 95 0
      app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/JsEnvCheckController.java
  100. 0 0
      app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/JsEnvCheckProjectController.java

+ 47 - 0
.gitignore

@@ -0,0 +1,47 @@
+######################################################################
+# Build Tools
+
+.gradle
+/build/
+!gradle/wrapper/gradle-wrapper.jar
+
+target/
+!.mvn/wrapper/maven-wrapper.jar
+
+######################################################################
+# IDE
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### JRebel ###
+rebel.xml
+
+### NetBeans ###
+nbproject/private/
+build/*
+nbbuild/
+dist/
+nbdist/
+.nb-gradle/
+
+######################################################################
+# Others
+*.log
+*.xml.versionsBackup
+*.swp
+
+!*/build/*.java
+!*/build/*.html
+!*/build/*.xml

+ 20 - 0
LICENSE

@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2018 RuoYi
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 10 - 0
README.md

@@ -0,0 +1,10 @@
+<p align="center">
+	<img alt="logo" src="https://oscimg.oschina.net/oscnet/up-d3d0a9303e11d522a06cd263f3079027715.png">
+</p>
+<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi v3.8.9</h1>
+<h4 align="center">基于SpringBoot+Vue前后端分离的Java快速开发框架</h4>
+<p align="center">
+	<a href="https://gitee.com/y_project/RuoYi-Vue/stargazers"><img src="https://gitee.com/y_project/RuoYi-Vue/badge/star.svg?theme=dark"></a>
+	<a href="https://gitee.com/y_project/RuoYi-Vue"><img src="https://img.shields.io/badge/RuoYi-v3.8.9-brightgreen.svg"></a>
+	<a href="https://gitee.com/y_project/RuoYi-Vue/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a>
+</p>

+ 151 - 0
app-admin/pom.xml

@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>ruoyi</artifactId>
+        <groupId>com.ruoyi</groupId>
+        <version>3.8.9</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <packaging>jar</packaging>
+    <artifactId>app-admin-hebei</artifactId>
+    <version>0.0.2-SNAPSHOT</version>
+    <description>
+        web服务入口
+    </description>
+
+    <dependencies>
+
+        <!-- spring-boot-devtools -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-devtools</artifactId>
+            <optional>true</optional> <!-- 表示依赖不会传递 -->
+        </dependency>
+
+        <!-- swagger3-->
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-boot-starter</artifactId>
+        </dependency>
+
+        <!-- 防止进入swagger页面报类型转换错误,排除3.0.0中的引用,手动增加1.6.2版本 -->
+        <dependency>
+            <groupId>io.swagger</groupId>
+            <artifactId>swagger-models</artifactId>
+            <version>1.6.2</version>
+        </dependency>
+
+         <!-- Mysql驱动包 -->
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+
+        <!-- 核心模块-->
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>app-framework</artifactId>
+        </dependency>
+
+        <!-- 定时任务-->
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>app-quartz</artifactId>
+        </dependency>
+
+        <!--事件处理-->
+        <dependency>
+            <groupId>com.lmax</groupId>
+            <artifactId>disruptor</artifactId>
+            <version>3.4.4</version> <!-- 检查最新版本 -->
+        </dependency>
+
+        <!--内存缓存-->
+        <dependency>
+            <groupId>com.github.ben-manes.caffeine</groupId>
+            <artifactId>caffeine</artifactId>
+            <version>2.9.3</version>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>5.8.16</version>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>3.4.2</version>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-extension</artifactId>
+            <version>3.4.2</version>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-generator</artifactId>
+            <version>3.2.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>com.jcraft</groupId>
+            <artifactId>jsch</artifactId>
+            <version>0.1.54</version>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>2.5.15</version>
+                <configuration>
+                    <fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>   
+                <groupId>org.apache.maven.plugins</groupId>   
+                <artifactId>maven-war-plugin</artifactId>   
+                <version>3.1.0</version>   
+                <configuration>
+                    <failOnMissingWebXml>false</failOnMissingWebXml>
+                    <warName>${project.artifactId}</warName>
+                </configuration>   
+           </plugin>   
+        </plugins>
+        <finalName>${project.artifactId}</finalName>
+
+     <!--   <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+            </resource>
+            <resource>
+                <directory>src/main/java</directory>
+                <includes>
+                    <include>**/*.xml</include>
+                </includes>
+                <filtering>false</filtering>
+            </resource>
+        </resources>-->
+
+    </build>
+
+</project>

+ 29 - 0
app-admin/src/main/java/com/ruoyi/RuoYiApplication.java

@@ -0,0 +1,29 @@
+package com.ruoyi;
+
+import com.ruoyi.web.v2.server.EnvInputServer;
+//import com.ruoyi.web.core.nfid.NFIDServer;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.context.ApplicationContext;
+
+/**
+ * 启动程序
+ * 
+ * @author ruoyi
+ */
+@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
+@MapperScan("com.ruoyi.web.v2.v1.mapper")
+//@MapperScan("com.ruoyi.**.mapper")
+public class RuoYiApplication
+{
+    public static void main(String[] args) throws Exception
+    {
+        // System.setProperty("spring.devtools.restart.enabled", "false");
+        ApplicationContext applicationContext = SpringApplication.run(RuoYiApplication.class, args);
+//        applicationContext.getBean(NFIDServer.class).start();
+
+        applicationContext.getBean(EnvInputServer.class).run();
+    }
+}

+ 18 - 0
app-admin/src/main/java/com/ruoyi/RuoYiServletInitializer.java

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

+ 470 - 0
app-admin/src/main/java/com/ruoyi/web/controller/app/DataReportController.java

@@ -0,0 +1,470 @@
+package com.ruoyi.web.controller.app;
+
+import cn.hutool.core.date.DateUtil;
+
+
+import cn.hutool.core.lang.TypeReference;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.json.JSONUtil;
+import com.ruoyi.app.DTO.EntranceReportDTO;
+import com.ruoyi.app.DTO.PurchaserProduceTotalDTO;
+import com.ruoyi.app.DTO.SlaughterReportDTO;
+import com.ruoyi.app.model.request.*;
+
+import com.ruoyi.app.model.response.*;
+import com.ruoyi.app.service.IDataReportService;
+
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.model.ExportConfig;
+import com.ruoyi.common.model.ExportField;
+import com.ruoyi.common.service.IExcelExportService;
+import com.ruoyi.common.service.IExportFieldConfigService;
+import com.ruoyi.web.v2.v1.entity.JsStockInfoRequest;
+import org.apache.poi.ss.formula.functions.T;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.*;
+
+/**
+ * 数据报表Controller
+ *
+ * @author coede
+ * @date 2025-03-31
+ */
+@RestController
+@RequestMapping("/app/dataReport")
+public class DataReportController extends BaseController {
+    @Autowired
+    private IDataReportService dataReportService;
+
+    @Autowired
+    private IExportFieldConfigService exportFieldConfigService;
+
+    @Autowired
+    private IExcelExportService excelExportService;
+
+    /**
+     * 获取入场报表
+     */
+//    // @PreAuthorize("@ss.hasPermi('app:dataReport:getEntranceReport')")
+    @GetMapping(value = "/getEntranceReport")
+    public TableDataInfo getEntranceReport(ReqEntranceReport reqEntranceReport)
+    {
+        startPage();
+        List<EntranceReportDTO> list = dataReportService.selectEntranceReport(reqEntranceReport);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取检查检疫无害化报表
+     */
+//    // @PreAuthorize("@ss.hasPermi('app:dataReport:getInspectionReport')")
+    @GetMapping(value = "/getInspectionReport")
+    public TableDataInfo getInspectionReport(ReqEntranceReport reqEntranceReport)
+    {
+        startPage();
+        List<EntranceReportDTO> list = dataReportService.selectEntranceReport(reqEntranceReport);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取生产报表
+     */
+    // @PreAuthorize("@ss.hasPermi('app:dataReport:getSlaughterReport')")
+    @GetMapping(value = "/getSlaughterReport")
+    public TableDataInfo getSlaughterReport(ReqSlaughterReport reqSlaughterReport)
+    {
+        startPage();
+        List<SlaughterReportDTO> list = dataReportService.selectSlaughterReport(reqSlaughterReport);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取称重统计
+     */
+    // @PreAuthorize("@ss.hasPermi('app:dataReport:getWeightReport')")
+    @GetMapping(value = "/getWeightReport")
+    public TableDataInfo getWeightReport(ReqGetPorkSideProduceRecord req)
+    {
+        startPage();
+        List<PurchaserProduceTotalDTO> list = dataReportService.selectPurchaserProduceTotal(req);
+        return getDataTable(list);
+    }
+
+    /**
+     * 生猪屠宰情况日汇总表  2025-10-09
+     *
+     * @param req 查询条件
+     * @return 集合
+     */
+    @GetMapping(value = "/getDailySlaughterReport")
+    public TableDataInfo getDailySlaughterReport(GetDailySlaughterReportReq req)
+    {
+        startPage();
+        List<GetDailySlaughterReportResp> list = dataReportService.selectDailySlaughterReport(req);
+        return getDataTable(list);
+    }
+
+    /**
+     * 生猪入场查验情况表  2025-10-15
+     *
+     * @param req 查询条件
+     * @return 集合
+     */
+    @GetMapping(value = "/getDailyEntranceReport")
+    public TableDataInfo getDailyEntranceReport(GetDailyEntranceReportReq req)
+    {
+        startPage();
+        List<GetDailyEntranceReportResp> list = dataReportService.selectDailyEntranceReport(req);
+        return getDataTable(list);
+    }
+
+    /**
+     * 违禁药物检测及屠宰检验情况表  2025-10-10
+     *
+     * @param req 查询条件
+     * @return 集合
+     */
+    @GetMapping(value = "/getDailyDrugCheckAndSlaughterReport")
+    public TableDataInfo getDailyDrugCheckAndSlaughterReport(GetDailyDrugCheckAndSlaughterReportReq req)
+    {
+        startPage();
+        List<GetDailyDrugCheckAndSlaughterReportResp> list = dataReportService.selectDailyDrugCheckAndSlaughterReport(req);
+        return getDataTable(list);
+    }
+
+    /**
+     * 生猪屠宰环节无害化处理表  2025-10-11
+     *
+     * @param req 查询条件
+     * @return 集合
+     */
+    @GetMapping(value = "/getDailyHarmlessReport")
+    public TableDataInfo getDailyHarmlessReport(GetDailyHarmlessReportReq req)
+    {
+        startPage();
+        List<GetDailyHarmlessReportResp> list = dataReportService.selectDailyHarmlessReport(req);
+        return getDataTable(list);
+    }
+
+    /**
+     * 生猪产品出场情况表  2025-10-12
+     *
+     * @param req 查询条件
+     * @return 集合
+     */
+    @GetMapping(value = "/getDailyOutputReport")
+    public TableDataInfo getDailyOutputReport(GetDailyOutputReportReq req)
+    {
+        startPage();
+        List<GetDailyOutputReportResp> list = dataReportService.selectDailyOutputReport(req);
+        return getDataTable(list);
+    }
+    /**
+     * 转运
+     *
+     * @param req 查询条件
+     * @return 集合
+     */
+    @GetMapping(value = "/getZhuanYunReport")
+    public TableDataInfo getZhuanYunReport(GetDailyOutputReportReq req)
+    {
+        startPage();
+        List<GetZhuanYunReportResp> list = dataReportService.getZhuanYunReport(req);
+        return getDataTable(list);
+    }
+    /**
+     * 召回
+     *
+     * @param req 查询条件
+     * @return 生猪召回
+     */
+    @GetMapping(value = "/getZhaoHuiReport")
+    public TableDataInfo getZhaoHuiReport( GetDailyOutputReportReq req)
+    {
+        startPage();
+        List<GetZhaoHuiResp> list = dataReportService.getZhaoHuiReport(req);
+        return getDataTable(list);
+    }
+    /**
+     * 宰前
+     *
+     * @param req 查询条件
+     * @return 宰前
+     */
+    @GetMapping(value = "/getZaiQianReport")
+    public TableDataInfo getZaiQianReport( GetDailyOutputReportReq req)
+    {
+        startPage();
+        List<GetZaiQianResp> list = dataReportService.getZaiQianReport(req);
+        return getDataTable(list);
+    }
+
+    /**
+     * 环境样品
+     *
+     * @param req 查询条件
+     * @return 环境样品
+     */
+    @GetMapping(value = "/getYangPinReport")
+    public TableDataInfo getYangPinReport( GetDailyOutputReportReq req)
+    {
+        startPage();
+        List<GetYangPinResp> list = dataReportService.getYangPinReport(req);
+        return getDataTable(list);
+    }
+
+    /**
+     * 生猪入厂查验情况登记表
+     *
+     * @param req 查询条件
+     * @return 生猪入厂查验情况登记表
+     */
+    @GetMapping(value = "/getDaiZaiReport")
+    public TableDataInfo getDaiZaiReport( GetDailyOutputReportReq req)
+    {
+        startPage();
+        List<JingYangPinResp> list = dataReportService.getDaiZaiReport(req);
+        return getDataTable(list);
+    }
+    /**
+     * 生猪入厂查验情况登记表
+     *
+     * @param req 查询条件
+     * @return 生猪入厂查验情况登记表
+     */
+    @GetMapping(value = "/getRuChangReport")
+    public TableDataInfo getRuChangReport( GetDailyOutputReportReq req)
+    {
+        startPage();
+        List<RunChagnResp> list = dataReportService.getRuChangReport(req);
+        return getDataTable(list);
+    }
+    /**
+     * 生猪产品出厂记录表
+     *
+     * @param req 查询条件
+     * @return 生猪产品出厂记录表
+     */
+    @GetMapping(value = "/getChuChangReport")
+    public TableDataInfo getChuChangReport( GetDailyOutputReportReq req)
+    {
+        startPage();
+        List<ChuChagnResp> list = dataReportService.getChuChangReport(req);
+        return getDataTable(list);
+    }
+    @GetMapping(value = "/getFuJianReport")
+    public TableDataInfo getFuJianReport( GetDailyOutputReportReq req)
+    {
+        startPage();
+        List<FuJianResp> list = dataReportService.getFuJianReport(req);
+        return getDataTable(list);
+    }
+
+
+    @GetMapping(value = "/getZaiHouReport")
+    public TableDataInfo getZaiHouReport( GetDailyOutputReportReq req)
+    {
+        startPage();
+
+        List<ZaiHouResp> list = dataReportService.getZaiHouReport(req);
+        return getDataTable(list);
+    }
+    @GetMapping(value = "/getXunChaReport")
+    public TableDataInfo getXunChaReport( GetDailyOutputReportReq req)
+    {
+        startPage();
+        Date startTime = req.getStartTime();
+        if (ObjectUtil.isNotEmpty(startTime)){
+            req.setEndTime(DateUtil.endOfDay(startTime));
+            req.setStartTime(DateUtil.beginOfDay(startTime));
+        }
+        List<XunChaDel> list = dataReportService.getXunChaReport(req);
+        List list1 = new ArrayList();
+        for (XunChaDel xunChaDel : list) {
+            Map map = new HashMap();
+            map.put("patrolTime",xunChaDel.getPatrolTime());
+            String patrolList = xunChaDel.getPatrolList();
+            List<XunChaItem> xunChaItems = JSONUtil.toList(patrolList, XunChaItem.class);
+            for (XunChaItem xunChaItem : xunChaItems) {
+                map.put(xunChaItem.getName(),xunChaItem.getContent());
+            }
+            map.put("abnormalCondition",xunChaDel.getAbnormalCondition());
+            map.put("dealMethod",xunChaDel.getDealMethod());
+            map.put("dealResult",xunChaDel.getDealResult());
+            map.put("patrolUser",xunChaDel.getPatrolUser());
+            map.put("patrolList",xunChaDel.getPatrolList());
+            list1.add(map);
+        }
+        return getDataTable(list1);
+    }
+    @GetMapping(value = "/gysReport")
+    public TableDataInfo gysReport( GetDailyOutputReportReq req)
+    {
+        startPage();
+
+        List<GysResp> list = dataReportService.gysReport(req);
+        return getDataTable(list);
+    }
+    @GetMapping(value = "/tuzaiShengChanReport")
+    public TableDataInfo tuzaiShengChanReport( GetDailyOutputReportReq req)
+    {
+        startPage();
+
+        List<TuZaiShengChanResp> list = dataReportService.tuzaiShengChanReport(req);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取报表字段  2025-10-13
+     *
+     * @return 集合
+     */
+    @GetMapping(value = "/getReportFields")
+    public AjaxResult getReportFields(@RequestParam(value ="templateName",defaultValue = "") String templateName)
+    {
+        List<ExportField> list = new ArrayList<>();
+        if(Objects.equals(templateName, "DailySlaughterReport")){
+           list = exportFieldConfigService.getDailySlaughterReportFields();
+        }else if(Objects.equals(templateName, "DailyDrugCheckAndSlaughterReport")){
+            list = exportFieldConfigService.getDailyDrugCheckAndSlaughterReportFields();
+        }else if(Objects.equals(templateName, "DailyHarmlessReport")){
+            list = exportFieldConfigService.getDailyHarmlessReportFields();
+        } else if(Objects.equals(templateName, "DailyOutputReport")){
+            list = exportFieldConfigService.getDailyOutputReportFields();
+        }else if(Objects.equals(templateName, "DailyEntranceReport")){
+            list = exportFieldConfigService.getDailyEntranceReportFields();
+        }else if (Objects.equals(templateName, "stockReport")){
+            list = exportFieldConfigService.getStockReportFields();
+        }
+        else if(Objects.equals(templateName, "yangPinReport")){ //环境样品检测记录表
+            list = exportFieldConfigService.getYangPinReportFields();
+        }
+        else if(Objects.equals(templateName, "chuChangReport")){ //生猪产品出厂记录表
+             list = exportFieldConfigService.getChuChangReportFields();
+        }
+        else if(Objects.equals(templateName, "zhaoHuiReport")){ //生猪产品召回记录表
+             list = exportFieldConfigService.getZhaoHuiReportFields();
+        }
+        else if(Objects.equals(templateName, "daiZaiReport")){ //生猪待宰静养记录表
+             list = exportFieldConfigService.getDaiZaiReportFields();
+        }
+//        else if(Objects.equals(templateName, "gysReport")){ //供应商评价表
+//             list = exportFieldConfigService.getGysReportFields();
+//        }
+        else if(Objects.equals(templateName, "ruChangReport")){ //生猪入厂查验情况登记表
+             list = exportFieldConfigService.getRuChangReportFields();
+        }
+        else if(Objects.equals(templateName, "zaiHouReport")){ //宰后检验记录表
+             list = exportFieldConfigService.getZaiHouReportFields();
+        }
+        else if(Objects.equals(templateName, "zaiQianReport")){ //生猪宰前检验记录表
+             list = exportFieldConfigService.getZaiQianReportFields();
+        }
+        else if(Objects.equals(templateName, "tuzaiShengChanReport")){ //屠宰生产记录表
+             list = exportFieldConfigService.getTuzaiShengChanReportFields();
+        }
+        else if(Objects.equals(templateName, "zhuanYunReport")){ //无害化暂存转运记录表
+             list = exportFieldConfigService.getZhuanYunReportFields();
+        }
+        else if(Objects.equals(templateName, "xunChaReport")){ //现场巡查记录
+             list = exportFieldConfigService.getXunChaReportFields();
+        }
+        else if(Objects.equals(templateName, "fuJianReport")){ //样品复检
+             list = exportFieldConfigService.getFuJianReportFields();
+        }
+        else{
+           return error("不存在的报表类型");
+        }
+        return success(list);
+    }
+
+    /**
+     * 根据字段导出报表  2025-10-13
+     *
+     * @return 集合
+     */
+    @PostMapping(value = "/exportDailyReport")
+    public void  exportDailyReport(HttpServletResponse response,@RequestBody ExportReportReq exportConfig)
+    {
+        try {
+            List<?> dataList = new ArrayList<>();
+            if(Objects.equals(exportConfig.getTemplateName(), "DailySlaughterReport")){
+                GetDailySlaughterReportReq req =   new GetDailySlaughterReportReq();
+                req.setStartTime(exportConfig.getStartTime());
+                req.setEndTime(exportConfig.getEndTime());
+                dataList = dataReportService.selectDailySlaughterReport(req);
+            }else if(Objects.equals(exportConfig.getTemplateName(), "DailyDrugCheckAndSlaughterReport")){
+                GetDailyDrugCheckAndSlaughterReportReq req =   new GetDailyDrugCheckAndSlaughterReportReq();
+                req.setStartTime(exportConfig.getStartTime());
+                req.setEndTime(exportConfig.getEndTime());
+                dataList = dataReportService.selectDailyDrugCheckAndSlaughterReport(req);
+            }else if(Objects.equals(exportConfig.getTemplateName(), "DailyHarmlessReport")){
+                GetDailyHarmlessReportReq req =   new GetDailyHarmlessReportReq();
+                req.setStartTime(exportConfig.getStartTime());
+                req.setEndTime(exportConfig.getEndTime());
+                dataList = dataReportService.selectDailyHarmlessReport(req);
+            }else if(Objects.equals(exportConfig.getTemplateName(), "DailyOutputReport")){
+                GetDailyOutputReportReq req =   new GetDailyOutputReportReq();
+                req.setStartTime(exportConfig.getStartTime());
+                req.setEndTime(exportConfig.getEndTime());
+                dataList = dataReportService.selectDailyOutputReport(req);
+            }else if(Objects.equals(exportConfig.getTemplateName(), "DailyEntranceReport")){
+                GetDailyEntranceReportReq req =   new GetDailyEntranceReportReq();
+                req.setStartTime(exportConfig.getStartTime());
+                req.setEndTime(exportConfig.getEndTime());
+                dataList = dataReportService.selectDailyEntranceReport(req);
+            } else if(Objects.equals(exportConfig.getTemplateName(), "stockReport")){ //化学用品登记
+                dataList = dataReportService.selectStockReport(exportConfig);
+            }
+            else if(Objects.equals(exportConfig.getTemplateName(), "yangPinReport")){ //环境样品检测记录表
+                dataList = dataReportService.selectyangPinReport(exportConfig);
+            }
+            else if(Objects.equals(exportConfig.getTemplateName(), "chuChangReport")){ //生猪产品出厂记录表
+                dataList = dataReportService.selectchuChangReport(exportConfig);
+            }
+            else if(Objects.equals(exportConfig.getTemplateName(), "zhaoHuiReport")){ //生猪产品召回记录表
+                dataList = dataReportService.selectzhaoHuiReport(exportConfig);
+            }
+            else if(Objects.equals(exportConfig.getTemplateName(), "daiZaiReport")){ //生猪待宰静养记录表
+                dataList = dataReportService.selectdaiZaiReport(exportConfig);
+            }
+//            else if(Objects.equals(exportConfig.getTemplateName(), "gysReport")){ //供应商评价表
+//                dataList = dataReportService.selectgysReport(exportConfig);
+//            }
+            else if(Objects.equals(exportConfig.getTemplateName(), "ruChangReport")){ //生猪入厂查验情况登记表
+                dataList = dataReportService.selectruChangReport(exportConfig);
+            }
+            else if(Objects.equals(exportConfig.getTemplateName(), "zaiHouReport")){ //宰后检验记录表
+                dataList = dataReportService.selectzaiHouReport(exportConfig);
+            }
+            else if(Objects.equals(exportConfig.getTemplateName(), "zaiQianReport")){ //生猪宰前检验记录表
+                dataList = dataReportService.selectzaiQianReport(exportConfig);
+            }
+            else if(Objects.equals(exportConfig.getTemplateName(), "tuzaiShengChanReport")){ //屠宰生产记录表
+                dataList = dataReportService.selecttuzaiShengChanReport(exportConfig);
+            }
+            else if(Objects.equals(exportConfig.getTemplateName(), "zhuanYunReport")){ //无害化暂存转运记录表
+                dataList = dataReportService.selectzhuanYunReport(exportConfig);
+            }
+            else if(Objects.equals(exportConfig.getTemplateName(), "xunChaReport")){ //现场巡查记录
+                dataList = dataReportService.selectXunChaReport(exportConfig,response);
+            }
+            else if(Objects.equals(exportConfig.getTemplateName(), "fuJianReport")){ //样品复检
+                dataList = dataReportService.selectFuJianReport(exportConfig);
+            }
+            else{
+                throw new RuntimeException("不存在的报表类型");
+            }
+            excelExportService.exportExcel(dataList, exportConfig, response);
+            //return  success("导出成功");
+        } catch (Exception e) {
+            throw new RuntimeException("导出数据失败", e);
+        }
+
+    }
+}

+ 181 - 0
app-admin/src/main/java/com/ruoyi/web/controller/app/DataStatisticsController.java

@@ -0,0 +1,181 @@
+package com.ruoyi.web.controller.app;
+
+import com.ruoyi.app.model.request.SearchTimeRangeParam;
+import com.ruoyi.app.model.request.TimeRangeParam;
+import com.ruoyi.app.service.IDataStatisticsService;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+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.*;
+
+/**
+ * 数据分析Controller
+ *
+ * @author coede
+ * @date 2025-03-28
+ */
+@RestController
+@RequestMapping("/app/statistics")
+public class DataStatisticsController extends BaseController
+{
+    @Autowired
+    private IDataStatisticsService dataStatisticsService;
+
+    /**
+     * 获取生产指标信息
+     */
+//    // @PreAuthorize("@ss.hasPermi('app:statistics:entrance')")
+    @GetMapping(value = "/getGrodIndicator")
+    public AjaxResult getProdIndicator(@Validated TimeRangeParam timeRangeParam)
+    {
+
+        return success(dataStatisticsService.getProdIndicator(timeRangeParam));
+    }
+
+    /**
+     * 获取供应商供应数量前五排名
+     */
+//    // @PreAuthorize("@ss.hasPermi('app:statistics:entrance')")
+    @GetMapping(value = "/getSupplierRank")
+    public AjaxResult getSupplierRank(@Validated TimeRangeParam timeRangeParam)
+    {
+
+        return success(dataStatisticsService.selectAmountRankByTime(timeRangeParam));
+    }
+
+    /**
+     * 获取肉商接收数量前五排名
+     */
+//    // @PreAuthorize("@ss.hasPermi('app:statistics:entrance')")
+    @GetMapping(value = "/getPurchaserRank")
+    public AjaxResult getPurchaserRank(@Validated TimeRangeParam timeRangeParam)
+    {
+
+        return success(dataStatisticsService.selectSaleAmountRankByTime(timeRangeParam));
+    }
+
+    /**
+     * 根据时间范围获取不同产地供应商供应数量
+     */
+//    // @PreAuthorize("@ss.hasPermi('app:statistics:entrance')")
+    @GetMapping(value = "/getOriginAmount")
+    public AjaxResult getOriginAmount(@Validated TimeRangeParam timeRangeParam)
+    {
+
+        return success(dataStatisticsService.selectOriginAmountByTime(timeRangeParam));
+    }
+
+    /**
+     * 根据时间范围获取不同去向肉商销售数量
+     */
+//    // @PreAuthorize("@ss.hasPermi('app:statistics:entrance')")
+    @GetMapping(value = "/getSaleAmount")
+    public AjaxResult getSaleAmount(@Validated TimeRangeParam timeRangeParam)
+    {
+
+        return success(dataStatisticsService.selectSaleAmountByTime(timeRangeParam));
+    }
+
+    /**
+     * 根据时间范围获取入场检验统计数量
+     */
+    // @PreAuthorize("@ss.hasPermi('app:statistics:entrance')")
+    @GetMapping(value = "/getHarmlessAmount")
+    public AjaxResult getHarmlessAmount(@Validated TimeRangeParam timeRangeParam)
+    {
+
+        return success(dataStatisticsService.selectHarmlessSumByTime(timeRangeParam));
+    }
+
+    /**
+     * 根据获取指定时间范围内每日累计计划屠宰
+     */
+    // @PreAuthorize("@ss.hasPermi('app:statistics:entrance')")
+    @GetMapping(value = "/getEntranceAmountWithTime")
+    public AjaxResult getEntranceAmountWithTime(@Validated TimeRangeParam timeRangeParam)
+    {
+        return success(dataStatisticsService.selectEntranceAmountByTime(timeRangeParam));
+    }
+
+    /**
+     * 获取产出指标信息
+     */
+    // @PreAuthorize("@ss.hasPermi('app:statistics:output')")
+    @GetMapping(value = "/getOutputIndicator")
+    public AjaxResult getOutputIndicator(@Validated TimeRangeParam timeRangeParam)
+    {
+        return success(dataStatisticsService.getOutputIndicator(timeRangeParam));
+    }
+
+    /**
+     * 获取白条重量统计
+     */
+    // @PreAuthorize("@ss.hasPermi('app:statistics:output')")
+    @GetMapping(value = "/getSideWeight")
+    public AjaxResult getSideWeight(@Validated TimeRangeParam timeRangeParam)
+    {
+        return success(dataStatisticsService.selectSideWeightByTime(timeRangeParam));
+    }
+
+    /**
+     * 获取其他产品重量统计
+     */
+    // @PreAuthorize("@ss.hasPermi('app:statistics:output')")
+    @GetMapping(value = "/getOtherProductWeight")
+    public AjaxResult getOtherProductWeight(@Validated SearchTimeRangeParam searchTimeRangeParam)
+    {
+        return success(dataStatisticsService.selectOtherProductWeightByTime(searchTimeRangeParam));
+    }
+
+    /**
+     * 获取今日昨日统计
+     */
+    // @PreAuthorize("@ss.hasPermi('app:statistics:output')")
+    @GetMapping(value = "/getLastestIndicator")
+    public AjaxResult getLatelyIndicator()
+    {
+        return success(dataStatisticsService.getLatelyIndicator());
+    }
+
+    /**
+     * 获取近七天每日分销订单量
+     */
+    // @PreAuthorize("@ss.hasPermi('app:statistics:output')")
+    @GetMapping(value = "/getWeekEntranceCount")
+    public AjaxResult getWeekEntranceCount()
+    {
+        return success(dataStatisticsService.getWeekEntranceCount());
+    }
+
+    /**
+     * 获取近七天每日分销订单量
+     */
+    // @PreAuthorize("@ss.hasPermi('app:statistics:output')")
+    @GetMapping(value = "/getWeekDistributeCount")
+    public AjaxResult getWeekDistributeCount()
+    {
+        return success(dataStatisticsService.getWeekDistributeCount());
+    }
+
+    /**
+     * 获取各部位重量对比情况
+     */
+    // @PreAuthorize("@ss.hasPermi('app:statistics:output')")
+    @GetMapping(value = "/getPorkPartWeight")
+    public AjaxResult getPorkPartWeight()
+    {
+        return success(dataStatisticsService.getPorkPartWeight());
+    }
+
+    /**
+     * 获取产地/去向对比情况
+     */
+    // @PreAuthorize("@ss.hasPermi('app:statistics:output')")
+    @GetMapping(value = "/getPlaceAmount")
+    public AjaxResult getPlaceAmount()
+    {
+        return success(dataStatisticsService.getPlaceAmount());
+    }
+}

+ 125 - 0
app-admin/src/main/java/com/ruoyi/web/controller/app/DeadDisposalController.java

@@ -0,0 +1,125 @@
+package com.ruoyi.web.controller.app;
+
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+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.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.app.model.DeadDisposal;
+import com.ruoyi.app.service.IDeadDisposalService;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 死淘处理方式Controller
+ * 
+ * @author coede
+ * @date 2025-03-19
+ */
+@RestController
+@RequestMapping("/app/disposal")
+public class DeadDisposalController extends BaseController
+{
+    @Autowired
+    private IDeadDisposalService deadDisposalService;
+
+    /**
+     * 查询死淘处理方式列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo list(DeadDisposal deadDisposal)
+    {
+        startPage();
+        List<DeadDisposal> list = deadDisposalService.selectDeadDisposalList(deadDisposal);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出死淘处理方式列表
+     */
+    // @PreAuthorize("@ss.hasPermi('app:disposal:export')")
+    @Log(title = "死淘处理方式", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, DeadDisposal deadDisposal)
+    {
+        List<DeadDisposal> list = deadDisposalService.selectDeadDisposalList(deadDisposal);
+        ExcelUtil<DeadDisposal> util = new ExcelUtil<DeadDisposal>(DeadDisposal.class);
+        util.exportExcel(response, list, "死淘处理方式数据");
+    }
+
+    /**
+     * 获取死淘处理方式详细信息
+     */
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return success(deadDisposalService.selectDeadDisposalById(id));
+    }
+
+    /**
+     * 新增死淘处理方式
+     */
+    // @PreAuthorize("@ss.hasPermi('app:disposal:add')")
+    @Log(title = "死淘处理方式", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody DeadDisposal deadDisposal)
+    {
+        if (!deadDisposalService.checkDealReasonUnique(deadDisposal))
+        {
+            return error("新增死淘处理方式'" + deadDisposal.getDealReason() + "'失败,处理原因已存在");
+        }
+        deadDisposal.setCreateBy(getUsername());
+        return toAjax(deadDisposalService.insertDeadDisposal(deadDisposal));
+    }
+
+    /**
+     * 修改死淘处理方式
+     */
+    // @PreAuthorize("@ss.hasPermi('app:disposal:edit')")
+    @Log(title = "死淘处理方式", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody DeadDisposal deadDisposal)
+    {
+        if (!deadDisposalService.checkDealReasonUnique(deadDisposal))
+        {
+            return error("修改死淘处理方式'" + deadDisposal.getDealReason() + "'失败,处理原因已存在");
+        }
+        deadDisposal.setUpdateBy(getUsername());
+        return toAjax(deadDisposalService.updateDeadDisposal(deadDisposal));
+    }
+
+    /**
+     * 删除死淘处理方式
+     */
+    // @PreAuthorize("@ss.hasPermi('app:disposal:remove')")
+    @Log(title = "死淘处理方式", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(deadDisposalService.deleteDeadDisposalByIds(ids));
+    }
+
+    /**
+     * 获取检查项目选择框列表
+     */
+    @GetMapping("/optionselect")
+    public AjaxResult optionselect()
+    {
+        DeadDisposal deadDisposal = new DeadDisposal();
+        List<DeadDisposal> list = deadDisposalService.selectDeadDisposalList(deadDisposal);
+        return success(list);
+    }
+}

+ 213 - 0
app-admin/src/main/java/com/ruoyi/web/controller/app/DistributeBatchController.java

@@ -0,0 +1,213 @@
+package com.ruoyi.web.controller.app;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.servlet.http.HttpServletResponse;
+
+import com.ruoyi.app.DTO.ValidDistributeListDTO;
+import com.ruoyi.app.DTO.ValidSlaughterCodeDTO;
+import com.ruoyi.app.model.Purchaser;
+import com.ruoyi.app.model.request.AddDistributeBatch;
+import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.common.utils.NumberUtil;
+import com.ruoyi.common.utils.StringUtils;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+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.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.app.model.DistributeBatch;
+import com.ruoyi.app.service.IDistributeBatchService;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 分销批次Controller
+ * 
+ * @author coede
+ * @date 2025-03-19
+ */
+@Api("分销批次信息管理")
+@RestController
+@RequestMapping("/app/distributeBatch")
+public class DistributeBatchController extends BaseController
+{
+    @Autowired
+    private IDistributeBatchService distributeBatchService;
+
+    /**
+     * 查询分销批次列表
+     */
+    @ApiOperation("查询分销批次列表")
+    // @PreAuthorize("@ss.hasPermi('app:distributeBatch:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(DistributeBatch distributeBatch)
+    {
+        startPage();
+        List<DistributeBatch> list = distributeBatchService.selectDistributeBatchList(distributeBatch);
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询指定血码还未关闭的分销批次,也就是可以继续绑吊钩,
+     * 通过判断24小时内的分销批次
+     */
+    @ApiOperation("血码可用批次")
+    @GetMapping("/getValidList")
+    public AjaxResult getValidList(DistributeBatch distributeBatch)
+    {
+
+        List<ValidDistributeListDTO> list = distributeBatchService.selectValidDistribute(distributeBatch);
+        return success(list);
+    }
+
+    /**
+     * 获取存在未完成(24小时内)的分销批次的血码
+     */
+    @ApiOperation("可用血码")
+    @GetMapping("/getValidCodeList")
+    public AjaxResult getValidCodeList()
+    {
+
+        List<ValidSlaughterCodeDTO> list = distributeBatchService.selectValidCode();
+        return success(list);
+    }
+
+    /**
+     * 导出分销批次列表
+     */
+    @ApiOperation("导出分销批次列表")
+    // @PreAuthorize("@ss.hasPermi('app:distributeBatch:export')")
+    @Log(title = "分销批次", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, DistributeBatch distributeBatch)
+    {
+        List<DistributeBatch> list = distributeBatchService.selectDistributeBatchList(distributeBatch);
+        ExcelUtil<DistributeBatch> util = new ExcelUtil<DistributeBatch>(DistributeBatch.class);
+        util.exportExcel(response, list, "分销批次数据");
+    }
+
+    /**
+     * 获取分销批次详细信息
+     */
+    @ApiOperation("获取分销批次详细信息")
+    // @PreAuthorize("@ss.hasPermi('app:distributeBatch:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return success(distributeBatchService.selectDistributeBatchById(id));
+    }
+
+    /**
+     * 新增分销批次
+     */
+    @ApiOperation("新增分销批次")
+    // @PreAuthorize("@ss.hasPermi('app:distributeBatch:add')")
+    @Log(title = "分销批次", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody DistributeBatch distributeBatch)
+    {
+        if(!distributeBatchService.checkTotalLessThanAmount(distributeBatch)){
+            return error("新增分销批次失败,分销数量输入错误");
+        }
+        distributeBatch.setCreateBy(getUsername());
+        return toAjax(distributeBatchService.insertDistributeBatch(distributeBatch));
+    }
+
+    /**
+     * 批量新增分销批次
+     */
+    @ApiOperation("批量新增分销批次")
+    // @PreAuthorize("@ss.hasPermi('app:distributeBatch:add')")
+    @Log(title = "分销批次", businessType = BusinessType.INSERT)
+    @PostMapping(value = "/addBatch")
+    public AjaxResult addBatch(@Validated @RequestBody AddDistributeBatch req)
+    {
+        //生成新对象
+        List<DistributeBatch> distributeArr = req.getDistributeArr().stream()
+                .peek(item -> {
+                    item.setEntranceBatchId(req.getEntranceBatchId());
+                    item.setCreateTime(DateUtils.getNowDate());
+                    item.setCreateBy(getUsername());
+                })
+                .collect(Collectors.toList());
+        //先判断本身数组内是否有重复的
+        Set<String> uniqueIds = new HashSet<>();
+        for (DistributeBatch distribute : distributeArr) {
+            String key = String.valueOf(distribute.getPurchaserId());
+            //noinspection ConstantConditions  忽略错误的IDEA提示
+            if (!uniqueIds.add(key)) {
+                return error("新增分销批次失败,本次添加存在相同的肉商");
+            }
+        }
+        //校验数据库的
+        int add = 0;
+        for (DistributeBatch distribute : distributeArr) {
+            add += NumberUtil.getIntValue(distribute.getAmount());
+            DistributeBatch check = distributeBatchService.checkPurchaserUnique(distribute);
+            if (StringUtils.isNotNull(check))
+            {
+                Purchaser purchaser = check.getPurchaser();
+                return error("新增分销批次失败,该批次下肉商'" + purchaser.getPurchaserName() + "'已存在");
+            }
+        }
+        //判断分销数量是否超过批次实际数量
+        DistributeBatch distributeBuild = new DistributeBatch();
+        distributeBuild.setEntranceBatchId(req.getEntranceBatchId());
+        distributeBuild.setAmount(add);
+        if(!distributeBatchService.checkTotalLessThanAmount(distributeBuild)){
+            return error("新增分销批次失败,分销数量输入错误");
+        }
+        return toAjax(distributeBatchService.insertDistributeBatchByBatch(distributeArr));
+    }
+
+    /**
+     * 修改分销批次
+     */
+    @ApiOperation("修改分销批次")
+    // @PreAuthorize("@ss.hasPermi('app:distributeBatch:edit')")
+    @Log(title = "分销批次", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody DistributeBatch distributeBatch)
+    {
+        DistributeBatch check = distributeBatchService.checkPurchaserUnique(distributeBatch);
+        if (StringUtils.isNotNull(check)&& check.getId().longValue() != distributeBatch.getId().longValue())
+        {
+            Purchaser purchaser = check.getPurchaser();
+            return error("修改分销批次失败,该批次下肉商'" + purchaser.getPurchaserName() + "'已存在");
+        }
+        //判断分销数量是否超过批次实际数量
+        if(!distributeBatchService.checkTotalLessThanAmount(distributeBatch)){
+            return error("修改分销批次失败,分销数量输入错误");
+        }
+
+        distributeBatch.setUpdateBy(getUsername());
+        return toAjax(distributeBatchService.updateDistributeBatch(distributeBatch));
+    }
+
+    /**
+     * 删除分销批次
+     */
+    @ApiOperation("删除分销批次")
+    // @PreAuthorize("@ss.hasPermi('app:distributeBatch:remove')")
+    @Log(title = "分销批次", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(distributeBatchService.deleteDistributeBatchByIds(ids));
+    }
+}

+ 139 - 0
app-admin/src/main/java/com/ruoyi/web/controller/app/DryLossRatioController.java

@@ -0,0 +1,139 @@
+package com.ruoyi.web.controller.app;
+
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+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.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.app.model.DryLossRatio;
+import com.ruoyi.app.service.IDryLossRatioService;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 干损比例Controller
+ * 
+ * @author coede
+ * @date 2025-03-19
+ */
+@RestController
+@RequestMapping("/app/ratio")
+public class DryLossRatioController extends BaseController
+{
+    @Autowired
+    private IDryLossRatioService dryLossRatioService;
+
+    /**
+     * 查询干损比例列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo list(DryLossRatio dryLossRatio)
+    {
+        if(dryLossRatio.getStartTime() != null){
+            dryLossRatio.setStartTime("2025-"+dryLossRatio.getStartTime()+" 00:00:00");
+        }
+        startPage();
+        List<DryLossRatio> list = dryLossRatioService.selectDryLossRatioList(dryLossRatio);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出干损比例列表
+     */
+    // @PreAuthorize("@ss.hasPermi('app:ratio:export')")
+    @Log(title = "干损比例", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, DryLossRatio dryLossRatio)
+    {
+        List<DryLossRatio> list = dryLossRatioService.selectDryLossRatioList(dryLossRatio);
+        ExcelUtil<DryLossRatio> util = new ExcelUtil<DryLossRatio>(DryLossRatio.class);
+        util.exportExcel(response, list, "干损比例数据");
+    }
+
+    /**
+     * 获取干损比例详细信息
+     */
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return success(dryLossRatioService.selectDryLossRatioById(id));
+    }
+
+    /**
+     * 新增干损比例
+     */
+    // @PreAuthorize("@ss.hasPermi('app:ratio:add')")
+    @Log(title = "干损比例", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody DryLossRatio dryLossRatio)
+    {
+//       if(!dryLossRatio.getStartTime().before(dryLossRatio.getEndTime())){
+//           return error("开始时间必须早于结束时间");
+//       }
+        ////拼凑成完整的时间字段且年份固定,数据库采用时间字段方便比较
+        dryLossRatio.setStartTime("2025-"+dryLossRatio.getStartTime()+" 00:00:00");
+        String ids = dryLossRatioService.checkTimeOverLap(dryLossRatio);
+        if (ids != null && !ids.isEmpty())
+        {
+            return error("新增干损比例失败,已存在ID为:‘" + ids + "’列表中存在时间范围重叠的干损比例");
+        }
+        dryLossRatio.setCreateBy(getUsername());
+        return toAjax(dryLossRatioService.insertDryLossRatio(dryLossRatio));
+    }
+
+    /**
+     * 修改干损比例
+     */
+    // @PreAuthorize("@ss.hasPermi('app:ratio:edit')")
+    @Log(title = "干损比例", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody DryLossRatio dryLossRatio)
+    {
+//        if(!dryLossRatio.getStartTime().before(dryLossRatio.getEndTime())){
+//            return error("开始时间必须早于结束时间");
+//        }
+        //拼凑成完整的时间字段且年份固定,数据库采用时间字段方便比较
+        dryLossRatio.setStartTime("2025-"+dryLossRatio.getStartTime()+" 00:00:00");
+        String ids = dryLossRatioService.checkTimeOverLap(dryLossRatio);
+        if (ids != null && !ids.isEmpty())
+        {
+            return error("修改干损比例,已存在ID为:‘" + ids + "’列表中存在时间范围重叠的干损比例");
+        }
+        dryLossRatio.setUpdateBy(getUsername());
+        return toAjax(dryLossRatioService.updateDryLossRatio(dryLossRatio));
+    }
+
+    /**
+     * 删除干损比例
+     */
+    // @PreAuthorize("@ss.hasPermi('app:ratio:remove')")
+    @Log(title = "干损比例", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(dryLossRatioService.deleteDryLossRatioByIds(ids));
+    }
+
+    /**
+     * 获取满足当前时间的干损比
+     */
+    @GetMapping(value = "/getFit")
+    public AjaxResult getFit()
+    {
+        return success(dryLossRatioService.selectDryLossRatioNow());
+    }
+
+}

+ 149 - 0
app-admin/src/main/java/com/ruoyi/web/controller/app/EntranceBatchController.java

@@ -0,0 +1,149 @@
+package com.ruoyi.web.controller.app;
+
+import java.util.HashMap;
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+
+import com.ruoyi.app.model.request.ReqEntranceBatchList;
+import com.ruoyi.app.model.response.ValidEntrance;
+import com.ruoyi.web.v2.v1.service.IDeductionDetailsService;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+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.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.app.model.EntranceBatch;
+import com.ruoyi.app.service.IEntranceBatchService;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 入场批次Controller
+ * 
+ * @author coede
+ * @date 2025-03-19
+ */
+@RestController
+@RequestMapping("/app/entranceBatch")
+public class EntranceBatchController extends BaseController
+{
+    @Autowired
+    private IEntranceBatchService entranceBatchService;
+    @Autowired
+    private IDeductionDetailsService deductionDetailsService;
+
+    /**
+     * 查询入场批次列表
+     */
+//    // @PreAuthorize("@ss.hasPermi('app:entranceBatch:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(ReqEntranceBatchList req)
+    {
+        startPage();
+        List<EntranceBatch> list = entranceBatchService.selectEntranceBatchList(req);
+        return getDataTable(list);
+    }
+
+    /**
+     * 入场批次列表
+     */
+//    // @PreAuthorize("@ss.hasPermi('app:entranceBatch:page')")
+    @GetMapping("/page")
+    public TableDataInfo page(EntranceBatch entranceBatch)
+    {
+        //List<EntranceBatch> list = entranceBatchService.selectEntranceBatchList1(entranceBatch);
+        //List<EntranceBatch> list = entranceBatchService.selectEntranceBatchList2(entranceBatch);
+        List<ValidEntrance> list = entranceBatchService.getAllValidEntrance();
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出入场批次列表
+     */
+//    // @PreAuthorize("@ss.hasPermi('app:entranceBatch:export')")
+    @Log(title = "入场批次", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, ReqEntranceBatchList req)
+    {
+        List<EntranceBatch> list = entranceBatchService.selectEntranceBatchList(req);
+        ExcelUtil<EntranceBatch> util = new ExcelUtil<EntranceBatch>(EntranceBatch.class);
+        util.exportExcel(response, list, "入场批次数据");
+    }
+
+    /**
+     * 获取入场批次详细信息
+     */
+//    // @PreAuthorize("@ss.hasPermi('app:entranceBatch:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return success(entranceBatchService.selectEntranceBatchById(id));
+    }
+
+    /**
+     * 新增入场批次
+     */
+//    // @PreAuthorize("@ss.hasPermi('app:entranceBatch:add')")
+    @Log(title = "入场批次", businessType = BusinessType.INSERT)
+    @PostMapping
+    @Transactional
+    public AjaxResult add(@Validated @RequestBody EntranceBatch entranceBatch)
+    {
+        if (entranceBatch.getAnimalCertNo()!=null && !entranceBatch.getAnimalCertNo().isEmpty()){
+            //TODO 暂时关闭重复检查
+//            if (!entranceBatchService.checkAnimalCertNoUnique(entranceBatch))
+//            {
+//                return error("新增入场批次失败,列表中存在重复的检疫证号");
+//            }
+        }
+
+        entranceBatch.setCreateBy(getUsername());
+        Long id = entranceBatchService.insertEntranceBatch(entranceBatch);
+        if (id > 0){
+            return  success(new HashMap<String, Object>() {{ put("id", id);}});
+        }else{
+            return AjaxResult.error();
+        }
+    }
+
+    /**
+     * 修改入场批次
+     */
+//    // @PreAuthorize("@ss.hasPermi('app:entranceBatch:edit')")
+    @Log(title = "入场批次", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody EntranceBatch entranceBatch)
+    {
+        if (entranceBatch.getAnimalCertNo()!=null && !entranceBatch.getAnimalCertNo().isEmpty()){
+            //TODO 暂时关闭重复检查
+//            if (!entranceBatchService.checkAnimalCertNoUnique(entranceBatch))
+//            {
+//                return error("修改入场批次失败,列表中存在重复的检疫证号");
+//            }
+        }
+        entranceBatch.setUpdateBy(getUsername());
+        return toAjax(entranceBatchService.updateEntranceBatch(entranceBatch));
+    }
+
+    /**
+     * 删除入场批次
+     */
+//    // @PreAuthorize("@ss.hasPermi('app:entranceBatch:remove')")
+    @Log(title = "入场批次", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(entranceBatchService.deleteEntranceBatchByIds(ids));
+    }
+}

+ 118 - 0
app-admin/src/main/java/com/ruoyi/web/controller/app/EntranceInspectionController.java

@@ -0,0 +1,118 @@
+package com.ruoyi.web.controller.app;
+
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+
+import com.ruoyi.app.model.HarmlessTreatment;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.ObjectUtils;
+import org.springframework.validation.annotation.Validated;
+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.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.app.model.EntranceInspection;
+import com.ruoyi.app.service.IEntranceInspectionService;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 入场检验Controller
+ * 
+ * @author coede
+ * @date 2025-03-19
+ */
+@RestController
+@RequestMapping("/app/entranceInspection")
+public class EntranceInspectionController extends BaseController
+{
+    @Autowired
+    private IEntranceInspectionService entranceInspectionService;
+
+    /**
+     * 查询入场检验列表
+     */
+//    // @PreAuthorize("@ss.hasPermi('app:entranceInspection:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(EntranceInspection entranceInspection)
+    {
+        startPage();
+        List<EntranceInspection> list = entranceInspectionService.selectEntranceInspectionList(entranceInspection);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出入场检验列表
+     */
+//    // @PreAuthorize("@ss.hasPermi('app:entranceInspection:export')")
+    @Log(title = "入场检验", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, EntranceInspection entranceInspection)
+    {
+        List<EntranceInspection> list = entranceInspectionService.selectEntranceInspectionList(entranceInspection);
+        ExcelUtil<EntranceInspection> util = new ExcelUtil<EntranceInspection>(EntranceInspection.class);
+        util.exportExcel(response, list, "入场检验数据");
+    }
+
+    /**
+     * 获取入场检验详细信息
+     */
+//    // @PreAuthorize("@ss.hasPermi('app:entranceInspection:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return success(entranceInspectionService.selectEntranceInspectionById(id));
+    }
+
+    /**
+     * 新增入场检验
+     */
+//    // @PreAuthorize("@ss.hasPermi('app:entranceInspection:add')")
+    @Log(title = "入场检验", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated  @RequestBody EntranceInspection entranceInspection)
+    {
+        entranceInspection.setCreateBy(getUsername());
+        //存在性判定,已存在入场批次号相同的数据,则更新原有数据,否则新增插入
+        if(!ObjectUtils.isEmpty(entranceInspection.getEntranceBatchId())) {
+            EntranceInspection queryEntity = new EntranceInspection();
+            queryEntity.setEntranceBatchId(entranceInspection.getEntranceBatchId());
+            List<EntranceInspection> list = entranceInspectionService.selectEntranceInspectionList(queryEntity);
+            if(!ObjectUtils.isEmpty(list)) {
+                return toAjax(entranceInspectionService.updateEntranceInspection(entranceInspection));
+            }
+        }
+        return toAjax(entranceInspectionService.insertEntranceInspection(entranceInspection));
+    }
+
+    /**
+     * 修改入场检验
+     */
+//    // @PreAuthorize("@ss.hasPermi('app:entranceInspection:edit')")
+    @Log(title = "入场检验", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody EntranceInspection entranceInspection)
+    {
+        entranceInspection.setUpdateBy(getUsername());
+        return toAjax(entranceInspectionService.updateEntranceInspection(entranceInspection));
+    }
+
+    /**
+     * 删除入场检验
+     */
+//    // @PreAuthorize("@ss.hasPermi('app:entranceInspection:remove')")
+    @Log(title = "入场检验", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(entranceInspectionService.deleteEntranceInspectionByIds(ids));
+    }
+}

+ 161 - 0
app-admin/src/main/java/com/ruoyi/web/controller/app/HarmlessTreatmentController.java

@@ -0,0 +1,161 @@
+package com.ruoyi.web.controller.app;
+
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.ruoyi.app.model.EntranceBatch;
+import com.ruoyi.app.mapper.HarmlessTreatmentMapper;
+import com.ruoyi.app.service.IEntranceBatchService;
+import com.ruoyi.web.v2.v1.model.JsDivideCircle;
+import com.ruoyi.web.v2.v1.service.IJsDivideCircleService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.ObjectUtils;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.app.model.HarmlessTreatment;
+import com.ruoyi.app.service.IHarmlessTreatmentService;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 无害化处理Controller
+ * 
+ * @author coede
+ * @date 2025-03-19
+ */
+@RestController
+@RequestMapping("/app/harmlessTreatment")
+public class HarmlessTreatmentController extends BaseController
+{
+    @Autowired
+    private IHarmlessTreatmentService harmlessTreatmentService;
+    @Autowired
+    private IJsDivideCircleService divideCircleService;
+    @Autowired
+    private IEntranceBatchService entranceBatchService;
+    @Autowired
+    private HarmlessTreatmentMapper harmlessTreatmentMapper;
+
+    /**
+     * 查询无害化处理列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo list(HarmlessTreatment harmlessTreatment)
+    {
+        startPage();
+        List<HarmlessTreatment> list = harmlessTreatmentService.selectHarmlessTreatmentList(harmlessTreatment);
+        return getDataTable(list);
+    }
+
+    @GetMapping("/page")
+    public TableDataInfo page()
+    {
+        List<HarmlessTreatment> harmlessTreatments = harmlessTreatmentMapper.listAll();
+        return getDataTable(harmlessTreatments);
+    }
+    /**
+     * 导出无害化处理列表
+     */
+//    // @PreAuthorize("@ss.hasPermi('app:harmlessTreatment:export')")
+    @Log(title = "无害化处理", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, HarmlessTreatment harmlessTreatment)
+    {
+        List<HarmlessTreatment> list = harmlessTreatmentService.selectHarmlessTreatmentList(harmlessTreatment);
+        ExcelUtil<HarmlessTreatment> util = new ExcelUtil<HarmlessTreatment>(HarmlessTreatment.class);
+        util.exportExcel(response, list, "无害化处理数据");
+    }
+
+    /**
+     * 获取无害化处理详细信息
+     */
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return success(harmlessTreatmentService.selectHarmlessTreatmentById(id));
+    }
+
+    /**
+     * 获取无害化处理生猪详情详细信息
+     */
+    @GetMapping(value = "/detail")
+    public AjaxResult getEarInfo(@RequestParam("id") Long id)
+    {
+        return success(harmlessTreatmentService.selectHarmlessDetail(id));
+    }
+
+
+    /**
+     * 新增无害化处理
+     */
+//    // @PreAuthorize("@ss.hasPermi('app:harmlessTreatment:add')")
+    @Log(title = "无害化处理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated  @RequestBody HarmlessTreatment harmlessTreatment)
+    {
+        //判断全部数量是否超出了入场数量
+        int i = harmlessTreatmentService.checkTotalLessThanAmount(harmlessTreatment);
+        if(i<0){
+            return error("新增无害化处理失败,病死头数、病害头数、途亡头数或急宰头数输入错误");
+        }
+        harmlessTreatment.setCreateBy(getUsername());
+        //存在性判定,已存在入场批次号相同的数据,则更新原有数据,否则新增插入
+        if(!ObjectUtils.isEmpty(harmlessTreatment.getEntranceBatchId())) {
+            HarmlessTreatment queryEntity = new HarmlessTreatment();
+            queryEntity.setEntranceBatchId(harmlessTreatment.getEntranceBatchId());
+            List<HarmlessTreatment> list = harmlessTreatmentService.selectHarmlessTreatmentList(queryEntity);
+            if(!ObjectUtils.isEmpty(list)) {
+                return toAjax(harmlessTreatmentService.updateHarmlessTreatment(harmlessTreatment));
+            }
+        }
+        return toAjax(harmlessTreatmentService.insertHarmlessTreatment(harmlessTreatment));
+    }
+
+    /**
+     * 修改无害化处理
+     */
+//    // @PreAuthorize("@ss.hasPermi('app:harmlessTreatment:edit')")
+    @Log(title = "无害化处理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody HarmlessTreatment harmlessTreatment)
+    {
+        //判断全部数量是否超出了入场数量
+        int i = harmlessTreatmentService.checkTotalLessThanAmount(harmlessTreatment);
+        if(i<0){
+            return error("修改无害化处理失败,病死头数、病害头数、途亡头数或急宰头数输入错误");
+        }
+        //判断是否填写分圈
+        EntranceBatch entranceBatch = entranceBatchService.selectEntranceBatchById(harmlessTreatment.getEntranceBatchId());
+        int count = divideCircleService.count(new QueryWrapper<JsDivideCircle>().eq("animal_cert_no", entranceBatch.getAnimalCertNo()));
+        if (count != 0) {
+            return error("已经填写分圈,不可修改无害化处理");
+        }
+        harmlessTreatment.setUpdateBy(getUsername());
+        return toAjax(harmlessTreatmentService.updateHarmlessTreatment(harmlessTreatment));
+    }
+
+    /**
+     * 删除无害化处理
+     */
+//    // @PreAuthorize("@ss.hasPermi('app:harmlessTreatment:remove')")
+    @Log(title = "无害化处理", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        //判断是否填写分圈
+        for (Long id : ids) {
+            HarmlessTreatment harmlessTreatment = harmlessTreatmentService.selectHarmlessTreatmentById(id);
+            EntranceBatch entranceBatch = entranceBatchService.selectEntranceBatchById(harmlessTreatment.getEntranceBatchId());
+            int count = divideCircleService.count(new QueryWrapper<JsDivideCircle>().eq("animal_cert_no", entranceBatch.getAnimalCertNo()));
+            if (count != 0) {
+                return error("已经填写分圈,不可修改无害化处理");
+            }
+        }
+        return toAjax(harmlessTreatmentService.deleteHarmlessTreatmentByIds(ids));
+    }
+}

+ 225 - 0
app-admin/src/main/java/com/ruoyi/web/controller/app/HookBindController.java

@@ -0,0 +1,225 @@
+package com.ruoyi.web.controller.app;
+
+import java.util.*;
+
+import javax.servlet.http.HttpServletResponse;
+
+import com.ruoyi.app.DTO.HookBindBatchListDTO;
+import com.ruoyi.app.model.request.AddHookBindBatch;
+import com.ruoyi.app.model.request.EditHookBindBatch;
+import com.ruoyi.app.model.request.ReqHookBindDetail;
+import com.ruoyi.app.model.request.ReqHookBindList;
+import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.common.utils.StringUtils;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+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.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.app.model.HookBind;
+import com.ruoyi.app.service.IHookBindService;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+import static com.ruoyi.common.utils.uuid.IdUtils.fastSimpleUUID;
+
+/**
+ * 吊钩绑定Controller
+ * 
+ * @author coede
+ * @date 2025-03-19
+ */
+@Api("吊钩绑定信息管理")
+@RestController
+@RequestMapping("/app/hookBind")
+public class HookBindController extends BaseController
+{
+    @Autowired
+    private IHookBindService hookBindService;
+
+    /**
+     * 查询吊钩绑定列表
+     */
+    @ApiOperation("查询吊钩绑定列表")
+    @GetMapping("/list")
+    public TableDataInfo list(HookBind hookBind)
+    {
+        startPage();
+        List<HookBind> list = hookBindService.selectHookBindList(hookBind);
+        return getDataTable(list);
+    }
+
+    /**
+     * 按批次查询吊钩绑定列表
+     */
+    @ApiOperation("按批次查询吊钩绑定列表")
+    @GetMapping("/batchList")
+    public TableDataInfo batchList(ReqHookBindList req)
+    {
+        startPage();
+        List<HookBindBatchListDTO> list = hookBindService.selectHookBindListByBatch(req);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出吊钩绑定列表
+     */
+    @ApiOperation("导出吊钩绑定列表")
+    // @PreAuthorize("@ss.hasPermi('app:hookBind:export')")
+    @Log(title = "吊钩绑定", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, HookBind hookBind)
+    {
+        List<HookBind> list = hookBindService.selectHookBindList(hookBind);
+        ExcelUtil<HookBind> util = new ExcelUtil<HookBind>(HookBind.class);
+        util.exportExcel(response, list, "吊钩绑定数据");
+    }
+
+    /**
+     * 获取吊钩绑定详细信息
+     */
+    @ApiOperation("获取吊钩绑定详细信息")
+    @GetMapping(value = "/group")
+    public AjaxResult getGroupInfo(@Validated ReqHookBindDetail req)
+    {
+        return success(hookBindService.selectHookBindByBatch(req.getBatchNo()));
+    }
+
+    /**
+     * 获取吊钩绑定详细信息
+     */
+    @ApiOperation("获取吊钩绑定详细信息")
+    @GetMapping(value = "/detail/{hookNo}")
+    public AjaxResult getDetail(@PathVariable("hookNo") String hookNo)
+    {
+        return success(hookBindService.selectHookBindOnlyDetail(hookNo));
+    }
+
+    /**
+     * 新增吊钩绑定 暂时不用
+     */
+    @ApiOperation("新增吊钩绑定 暂时不用")
+    // @PreAuthorize("@ss.hasPermi('app:hookBind:add')")
+    @Log(title = "吊钩绑定", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated  @RequestBody HookBind hookBind)
+    {
+        hookBind.setCreateBy(getUsername());
+        return toAjax(hookBindService.insertHookBind(hookBind));
+    }
+
+    /**
+     * 新增吊钩批量绑定
+     */
+    @ApiOperation("新增吊钩批量绑定")
+    // @PreAuthorize("@ss.hasPermi('app:hookBind:add')")
+    @Log(title = "吊钩绑定", businessType = BusinessType.INSERT)
+    @PostMapping("/addBatch")
+    public AjaxResult addBatch(@Validated  @RequestBody AddHookBindBatch req)
+    {
+        try {
+            //生成新对象
+            List<HookBind> hookBindArr = new ArrayList<>();
+            String batchNo = fastSimpleUUID();
+            //先判断本身数组内是否有重复的
+            Set<String> uniqueIds = new HashSet<>();
+
+            for (String hook : req.getHookNoListArr()) {
+                //noinspection ConstantConditions  忽略错误的IDEA提示
+                if (!uniqueIds.add(hook)) {
+                    return error("新增吊钩批量绑定,本次添加存在相同的吊钩号");
+                }
+                HookBind hookBind = new HookBind();
+                hookBind.setBatchNo(batchNo);
+                hookBind.setHookNo(hook);
+                hookBind.setBindTime(req.getBindTime());
+                hookBind.setDistributeBatchId(req.getDistributeBatchId());
+                hookBind.setSlaughterCode(req.getSlaughterCode());
+                hookBind.setCreateTime(DateUtils.getNowDate());
+                hookBind.setCreateBy(getUsername());
+
+                //未传绑定时间默认当前时间
+                if (req.getBindTime() == null) {
+                    hookBind.setBindTime(DateUtils.getNowDate());
+                }else{
+                    hookBind.setBindTime(req.getBindTime());
+                }
+                hookBindArr.add(hookBind);
+            }
+            //校验数据库的
+            for (HookBind hookBind : hookBindArr) {
+
+                HookBind check = hookBindService.checkHookNoUnique(hookBind);
+                if (StringUtils.isNotNull(check)) {
+                    return error("新增吊钩批量绑定失败,'" + check.getHookNo() + "'同一时间已存在绑定");
+                }
+            }
+            return toAjax(hookBindService.insertHookBindBatch(hookBindArr,req.getHookNoListArr()));
+        }catch(Exception e) {
+                return error("新增吊钩批量绑定失败:"+e.getMessage());
+        }
+    }
+
+    /**
+     * 修改吊钩绑定
+     */
+    @ApiOperation("修改吊钩绑定")
+    // @PreAuthorize("@ss.hasPermi('app:hookBind:edit')")
+    @Log(title = "吊钩绑定", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody HookBind hookBind)
+    {
+
+        hookBind.setUpdateBy(getUsername());
+        return toAjax(hookBindService.updateHookBind(hookBind));
+    }
+
+    /**
+     * 批量修改吊钩绑定
+     */
+    @ApiOperation("批量修改吊钩绑定")
+    // @PreAuthorize("@ss.hasPermi('app:hookBind:edit')")
+    @Log(title = "吊钩绑定", businessType = BusinessType.UPDATE)
+    @PostMapping("/editBatch")
+    public AjaxResult editBatch(@Validated @RequestBody EditHookBindBatch req)
+    {
+        req.setUpdateBy(getUsername());
+        return toAjax(hookBindService.updateHookBindBatch(req));
+    }
+
+    /**
+     * 删除吊钩绑定
+     */
+    @ApiOperation("删除吊钩绑定")
+    // @PreAuthorize("@ss.hasPermi('app:hookBind:remove')")
+    @Log(title = "吊钩绑定", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{batchNos}")
+    public AjaxResult remove(@PathVariable String[] batchNos)
+    {
+        return toAjax(hookBindService.deleteHookBindByIds(batchNos));
+    }
+
+    /**
+     * 清除吊钩绑定关系-不删除数据只是将绑定关系设置为未绑定
+     */
+    @ApiOperation("清除吊钩绑定关系")
+    // @PreAuthorize("@ss.hasPermi('app:hookBind:clean')")
+    @Log(title = "吊钩绑定", businessType = BusinessType.UPDATE)
+    @PostMapping("/cleanBind")
+    public AjaxResult cleanBind()
+    {
+        return AjaxResult.success(hookBindService.cleanHookBind());
+    }
+}

+ 123 - 0
app-admin/src/main/java/com/ruoyi/web/controller/app/HookController.java

@@ -0,0 +1,123 @@
+package com.ruoyi.web.controller.app;
+
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+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.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.app.model.Hook;
+import com.ruoyi.app.service.IHookService;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 吊钩Controller
+ * 
+ * @author coede
+ * @date 2025-03-19
+ */
+@RestController
+@RequestMapping("/app/hook")
+public class HookController extends BaseController
+{
+    @Autowired
+    private IHookService hookService;
+
+    /**
+     * 查询吊钩列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo list(Hook hook)
+    {
+        startPage();
+        List<Hook> list = hookService.selectHookList(hook);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出吊钩列表
+     */
+    @Log(title = "吊钩", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, Hook hook)
+    {
+        List<Hook> list = hookService.selectHookList(hook);
+        ExcelUtil<Hook> util = new ExcelUtil<Hook>(Hook.class);
+        util.exportExcel(response, list, "吊钩数据");
+    }
+
+    /**
+     * 获取吊钩详细信息
+     */
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return success(hookService.selectHookById(id));
+    }
+
+    /**
+     * 新增吊钩
+     */
+    // @PreAuthorize("@ss.hasPermi('app:hook:add')")
+    @Log(title = "吊钩", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody Hook hook)
+    {
+        if (!hookService.checkHookNameUnique(hook))
+        {
+            return error("新增吊钩'" + hook.getHookName() + "'失败,列表中存在重复的吊钩类型");
+        }
+        hook.setCreateBy(getUsername());
+        return toAjax(hookService.insertHook(hook));
+    }
+
+    /**
+     * 修改吊钩
+     */
+    // @PreAuthorize("@ss.hasPermi('app:hook:edit')")
+    @Log(title = "吊钩", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody Hook hook)
+    {
+        if (!hookService.checkHookNameUnique(hook))
+        {
+            return error("修改吊钩'" + hook.getHookName() + "'失败,列表中存在重复的吊钩类型");
+        }
+        hook.setUpdateBy(getUsername());
+        return toAjax(hookService.updateHook(hook));
+    }
+
+    /**
+     * 删除吊钩
+     */
+    // @PreAuthorize("@ss.hasPermi('app:hook:remove')")
+    @Log(title = "吊钩", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(hookService.deleteHookByIds(ids));
+    }
+
+    /**
+     * 吊钩选择
+     */
+    @GetMapping("/optionselect")
+    public AjaxResult optionselect()
+    {
+        Hook hook = new Hook();
+        List<Hook> list = hookService.selectHookList(hook);
+        return success(list);
+    }
+}

+ 128 - 0
app-admin/src/main/java/com/ruoyi/web/controller/app/HookRegisterController.java

@@ -0,0 +1,128 @@
+package com.ruoyi.web.controller.app;
+
+import com.ruoyi.app.model.HookRegister;
+import com.ruoyi.app.model.request.ReqHookRegister;
+import com.ruoyi.app.service.IHookRegisterService;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+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;
+
+/**
+ * 吊钩注册Controller
+ *
+ * @author coede
+ * @date 2025-03-19
+ */
+@RestController
+@RequestMapping("/app/hookRegister")
+public class HookRegisterController extends BaseController
+{
+    @Autowired
+    private IHookRegisterService hookRegisterService;
+
+    /**
+     * 查询吊钩注册列表
+     */
+    // @PreAuthorize("@ss.hasPermi('app:hookRegister:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(ReqHookRegister req)
+    {
+        startPage();
+        List<HookRegister> list = hookRegisterService.selectHookRegisterList(req);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出吊钩注册列表
+     */
+    // @PreAuthorize("@ss.hasPermi('app:hookRegister:export')")
+    @Log(title = "吊钩注册", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, ReqHookRegister req)
+    {
+        List<HookRegister> list = hookRegisterService.selectHookRegisterList(req);
+        ExcelUtil<HookRegister> util = new ExcelUtil<HookRegister>(HookRegister.class);
+        util.exportExcel(response, list, "吊钩注册数据");
+    }
+
+    /**
+     * 获取吊钩注册详细信息
+     */
+    // @PreAuthorize("@ss.hasPermi('app:hookRegister:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return success(hookRegisterService.selectHookRegisterById(id));
+    }
+
+    /**
+     * 新增吊钩注册
+     */
+    // @PreAuthorize("@ss.hasPermi('app:hookRegister:add')")
+    @Log(title = "吊钩注册", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody HookRegister hookRegister)
+    {
+        if (!hookRegisterService.checkHookNoUnique(hookRegister))
+        {
+            return error("新增吊钩注册'" + hookRegister.getHookNo() + "'失败,列表中存在重复的吊钩编号");
+        }
+        if (!hookRegisterService.checkEpcNoUnique(hookRegister))
+        {
+            return error("新增吊钩注册'" + hookRegister.getEpcNo() + "'失败,列表中存在重复的芯片编号");
+        }
+        hookRegister.setCreateBy(getUsername());
+        return toAjax(hookRegisterService.insertHookRegister(hookRegister));
+    }
+
+    /**
+     * 修改吊钩注册
+     */
+    // @PreAuthorize("@ss.hasPermi('app:hookRegister:edit')")
+    @Log(title = "吊钩注册", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody HookRegister hookRegister)
+    {
+        if (!hookRegisterService.checkHookNoUnique(hookRegister))
+        {
+            return error("修改吊钩注册'" + hookRegister.getHookNo() + "'失败,列表中存在重复的吊钩编号");
+        }
+        if (!hookRegisterService.checkEpcNoUnique(hookRegister))
+        {
+            return error("修改吊钩注册'" + hookRegister.getEpcNo() + "'失败,列表中存在重复的芯片编号");
+        }
+        hookRegister.setUpdateBy(getUsername());
+        return toAjax(hookRegisterService.updateHookRegister(hookRegister));
+    }
+
+    /**
+     * 删除吊钩注册
+     */
+    // @PreAuthorize("@ss.hasPermi('app:hookRegister:remove')")
+    @Log(title = "吊钩注册", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(hookRegisterService.deleteHookRegisterByIds(ids));
+    }
+
+    /**
+     * 获取选择框列表
+     */
+    @GetMapping("/optionselect")
+    public AjaxResult optionselect()
+    {
+        List<HookRegister> list = hookRegisterService.selectHookRegisterList(new ReqHookRegister());
+        return success(list);
+    }
+}

+ 152 - 0
app-admin/src/main/java/com/ruoyi/web/controller/app/InspectionItemController.java

@@ -0,0 +1,152 @@
+package com.ruoyi.web.controller.app;
+
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.ruoyi.common.utils.jsonUtil;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+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.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.app.model.InspectionItem;
+import com.ruoyi.app.service.IInspectionItemService;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 检查项目Controller
+ * 
+ * @author coede
+ * @date 2025-03-19
+ */
+@RestController
+@RequestMapping("/app/inspectionItem")
+public class InspectionItemController extends BaseController
+{
+    @Autowired
+    private IInspectionItemService inspectionItemService;
+
+    /**
+     * 查询检查项目列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo list(InspectionItem inspectionItem)
+    {
+        startPage();
+        List<InspectionItem> list = inspectionItemService.selectInspectionItemList(inspectionItem);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出检查项目列表
+     */
+    // @PreAuthorize("@ss.hasPermi('app:inspectionItem:export')")
+    @Log(title = "检查项目", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, InspectionItem inspectionItem)
+    {
+        List<InspectionItem> list = inspectionItemService.selectInspectionItemList(inspectionItem);
+        ExcelUtil<InspectionItem> util = new ExcelUtil<InspectionItem>(InspectionItem.class);
+        util.exportExcel(response, list, "检查项目数据");
+    }
+
+    /**
+     * 获取检查项目详细信息
+     */
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return success(inspectionItemService.selectInspectionItemById(id));
+    }
+
+    /**
+     * 新增检查项目
+     */
+    // @PreAuthorize("@ss.hasPermi('app:inspectionItem:add')")
+    @Log(title = "检查项目", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated  @RequestBody InspectionItem inspectionItem)
+    {
+        ObjectMapper mapper = new ObjectMapper();
+        try {
+            JsonNode jsonArray = mapper.readTree(inspectionItem.getResult());
+            String fieldToCheck = "name";
+            if (jsonUtil.checkForDuplicateFields(jsonArray, fieldToCheck))
+            {
+                return error("新增检查项目'" + inspectionItem.getItemName() + "'失败,本次添加中存在重复的检查结果");
+            }
+            if (!inspectionItemService.checkItemNameUnique(inspectionItem))
+            {
+                return error("新增检查项目'" + inspectionItem.getItemName() + "'失败,列表中存在重复的项目名称");
+            }
+            inspectionItem.setCreateBy(getUsername());
+            return toAjax(inspectionItemService.insertInspectionItem(inspectionItem));
+        } catch (Exception e) {
+            return error("新增检查项目'" + inspectionItem.getItemName() + "'失败,错误的检查结果格式");
+        }
+
+
+    }
+
+    /**
+     * 修改检查项目
+     */
+    // @PreAuthorize("@ss.hasPermi('app:inspectionItem:edit')")
+    @Log(title = "检查项目", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody InspectionItem inspectionItem)
+    {
+        ObjectMapper mapper = new ObjectMapper();
+        try {
+            JsonNode jsonArray = mapper.readTree(inspectionItem.getResult());
+            String fieldToCheck = "name";
+            if (jsonUtil.checkForDuplicateFields(jsonArray, fieldToCheck))
+            {
+                return error("修改检查项目'" + inspectionItem.getItemName() + "'失败,本次添加中存在重复的检查结果");
+            }
+            if (!inspectionItemService.checkItemNameUnique(inspectionItem))
+            {
+                return error("修改检查项目'" + inspectionItem.getItemName() + "'失败,列表中存在重复的项目名称");
+            }
+            inspectionItem.setUpdateBy(getUsername());
+            return toAjax(inspectionItemService.insertInspectionItem(inspectionItem));
+        } catch (Exception e) {
+            return error("修改检查项目'" + inspectionItem.getItemName() + "'失败,错误的检查结果格式");
+        }
+    }
+
+    /**
+     * 删除检查项目
+     */
+    // @PreAuthorize("@ss.hasPermi('app:inspectionItem:remove')")
+    @Log(title = "检查项目", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(inspectionItemService.deleteInspectionItemByIds(ids));
+    }
+
+    /**
+     * 获取检查项目选择框列表
+     */
+    @GetMapping("/optionselect")
+    public AjaxResult optionselect()
+    {
+        InspectionItem inspectionItem = new InspectionItem();
+        List<InspectionItem> list = inspectionItemService.selectInspectionItemList(inspectionItem);
+        return success(list);
+    }
+}

+ 212 - 0
app-admin/src/main/java/com/ruoyi/web/controller/app/MonitorController.java

@@ -0,0 +1,212 @@
+//package com.ruoyi.web.controller.app;
+//
+//import java.util.Date;
+//import java.util.List;
+//import java.util.stream.Collectors;
+//import javax.servlet.http.HttpServletResponse;
+//
+//import com.ruoyi.app.domain.request.ReqMonitorAction;
+//import com.ruoyi.web.core.wvp.domain.DeviceChannelExtend;
+//import com.ruoyi.common.utils.DateUtils;
+//import io.swagger.annotations.Api;
+//import io.swagger.annotations.ApiOperation;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.security.access.prepost.PreAuthorize;
+//import org.springframework.beans.factory.annotation.Autowired;
+//import org.springframework.validation.annotation.Validated;
+//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.DeleteMapping;
+//import org.springframework.web.bind.annotation.PathVariable;
+//import org.springframework.web.bind.annotation.RequestBody;
+//import org.springframework.web.bind.annotation.RequestMapping;
+//import org.springframework.web.bind.annotation.RestController;
+//import com.ruoyi.common.annotation.Log;
+//import com.ruoyi.common.core.controller.BaseController;
+//import com.ruoyi.common.core.domain.AjaxResult;
+//import com.ruoyi.common.enums.BusinessType;
+//import com.ruoyi.app.domain.Monitor;
+//import com.ruoyi.app.service.IMonitorService;
+//import com.ruoyi.common.utils.poi.ExcelUtil;
+//import com.ruoyi.common.core.page.TableDataInfo;
+//import com.ruoyi.web.core.wvp.DeviceHandler;
+//
+///**
+// * 监控设备Controller
+// *
+// * @author coede
+// * @date 2025-03-19
+// */
+//@Api("监控设备信息管理")
+//@RestController
+//@RequestMapping("/app/monitor")
+//public class MonitorController extends BaseController
+//{
+//    @Autowired
+//    private IMonitorService monitorService;
+//
+//    @Autowired
+//    private DeviceHandler deviceHandler;
+//
+//    public static final Logger log = LoggerFactory.getLogger(ExcelUtil.class);
+//    /**
+//     * 查询监控设备列表
+//     */
+//    @ApiOperation("查询监控设备列表")
+//    // @PreAuthorize("@ss.hasPermi('app:monitor:list')")
+//    @GetMapping("/list")
+//    public TableDataInfo list(Monitor monitor)
+//    {
+//        startPage();
+//        List<Monitor> list = monitorService.selectMonitorList(monitor);
+//        return getDataTable(list);
+//    }
+//
+//    /**
+//     * 导出监控设备列表
+//     */
+//    @ApiOperation("导出监控设备列表")
+//    // @PreAuthorize("@ss.hasPermi('app:monitor:export')")
+//    @Log(title = "监控设备", businessType = BusinessType.EXPORT)
+//    @PostMapping("/export")
+//    public void export(HttpServletResponse response, Monitor monitor)
+//    {
+//        List<Monitor> list = monitorService.selectMonitorList(monitor);
+//        ExcelUtil<Monitor> util = new ExcelUtil<Monitor>(Monitor.class);
+//        util.exportExcel(response, list, "监控设备数据");
+//    }
+//
+//    /**
+//     * 获取监控设备详细信息
+//     */
+//    @ApiOperation("获取监控设备详细信息")
+//    // @PreAuthorize("@ss.hasPermi('app:monitor:query')")
+//    @GetMapping(value = "/{id}")
+//    public AjaxResult getInfo(@PathVariable("id") Long id)
+//    {
+//        return success(monitorService.selectMonitorById(id));
+//    }
+//
+//    /**
+//     * 新增监控设备
+//     */
+//    @ApiOperation("新增监控设备")
+//    // @PreAuthorize("@ss.hasPermi('app:monitor:add')")
+//    @Log(title = "监控设备", businessType = BusinessType.INSERT)
+//    @PostMapping
+//    public AjaxResult add(@Validated  @RequestBody Monitor monitor)
+//    {
+//        if (!monitorService.checkMonitorNameUnique(monitor))
+//        {
+//            return error("新增监控设备'" + monitor.getMonitorName() + "'失败,列表中存在重复的监控名称");
+//        }
+//        monitor.setCreateBy(getUsername());
+//        return toAjax(monitorService.insertMonitor(monitor));
+//    }
+//
+//    /**
+//     * 同步监控设备
+//     */
+//    @ApiOperation("同步监控设备")
+//    // @PreAuthorize("@ss.hasPermi('app:monitor:sync')")
+//    @Log(title = "监控设备", businessType = BusinessType.INSERT)
+//    @PostMapping("/sync")
+//    public AjaxResult sync()
+//    {
+//        try {
+//            //获取WVP所有的设备通道
+//            List<DeviceChannelExtend> channelList = deviceHandler.getDeviceList();
+//            Date now = DateUtils.getNowDate();
+//            List<Monitor> monitorArr = channelList.stream()
+//                    .map(item -> {
+//                        Monitor monitor = new Monitor();
+//                        monitor.setMonitorName(item.getName());
+//                        monitor.setIpAddr(item.getIpAddress());
+//                        monitor.setMonitorBrand(item.getManufacture());
+//                        monitor.setDeviceId(item.getDeviceId());
+//                        monitor.setChannelId(item.getChannelId());
+//                        monitor.setCreateTime(now);
+//                        monitor.setCreateBy(getUsername());
+//                        return monitor;
+//                    })
+//                    .collect(Collectors.toList());
+//            return toAjax(monitorService.syncMonitor(monitorArr));
+//        }catch (Exception e){
+//            log.error(e.getMessage());
+//            return error("同步失败");
+//        }
+//    }
+//
+//    /**
+//     * 修改监控设备
+//     */
+//    @ApiOperation("修改监控设备")
+//    // @PreAuthorize("@ss.hasPermi('app:monitor:edit')")
+//    @Log(title = "监控设备", businessType = BusinessType.UPDATE)
+//    @PutMapping
+//    public AjaxResult edit(@Validated @RequestBody Monitor monitor)
+//    {
+//        if (!monitorService.checkMonitorNameUnique(monitor))
+//        {
+//            return error("修改监控设备'" + monitor.getMonitorName() + "'失败,列表中存在重复的监控名称");
+//        }
+//        monitor.setUpdateBy(getUsername());
+//        return toAjax(monitorService.updateMonitor(monitor));
+//    }
+//
+//    /**
+//     * 删除监控设备
+//     */
+//    @ApiOperation("删除监控设备")
+//    // @PreAuthorize("@ss.hasPermi('app:monitor:remove')")
+//    @Log(title = "监控设备", businessType = BusinessType.DELETE)
+//	@DeleteMapping("/{ids}")
+//    public AjaxResult remove(@PathVariable Long[] ids)
+//    {
+//        return toAjax(monitorService.deleteMonitorByIds(ids));
+//    }
+//
+//    /**
+//     * 播放监控设备
+//     */
+//    @ApiOperation("播放监控设备")
+//    // @PreAuthorize("@ss.hasPermi('app:monitor:playStart')")
+//    @Log(title = "监控设备", businessType = BusinessType.OTHER)
+//    @PostMapping("/playStart")
+//    public AjaxResult playStart(@Validated @RequestBody ReqMonitorAction req)
+//    {
+//        try{
+//        Object res = deviceHandler.playOrStop(DeviceHandler.VIDEO_PLAY_START,req.getDeviceId(),req.getChannelId());
+//            if (res != null){
+//                return AjaxResult.success(res);
+//            }else{
+//                throw new RuntimeException("无视频信息,请重试");
+//            }
+//        }catch (Exception e){
+//            return error("播放失败:"+e.getMessage());
+//        }
+//    }
+//
+//    /**
+//     * 停止监控设备
+//     */
+//    @ApiOperation("停止监控设备")
+//    // @PreAuthorize("@ss.hasPermi('app:monitor:playStop')")
+//    @Log(title = "监控设备", businessType = BusinessType.OTHER)
+//    @PostMapping("/playStop")
+//    public AjaxResult playStop(@Validated @RequestBody ReqMonitorAction req)
+//    {
+//        try{
+//            Object res = deviceHandler.playOrStop(DeviceHandler.VIDEO_PLAY_STOP,req.getDeviceId(),req.getChannelId());
+//            if (res != null){
+//                return AjaxResult.success(res);
+//            }else{
+//                throw new RuntimeException("无视频信息,请重试");
+//            }
+//        }catch (Exception e){
+//            return error("停止失败:"+e.getMessage());
+//        }
+//    }
+//}

+ 184 - 0
app-admin/src/main/java/com/ruoyi/web/controller/app/NFIDReadRecordController.java

@@ -0,0 +1,184 @@
+package com.ruoyi.web.controller.app;
+
+
+import com.ruoyi.app.DTO.NFIDReadGroupDTO;
+import com.ruoyi.app.model.NFIDReadRecord;
+import com.ruoyi.app.model.NFIDReaderEvent;
+import com.ruoyi.app.model.request.ReqValidNFIDRecord;
+import com.ruoyi.app.service.INFIDReadRecordService;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.event.EventListener;
+import org.springframework.http.MediaType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+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.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+
+/**
+ * nfid识别记录Controller
+ *
+ */
+@Api("nfid识别记录信息管理")
+@RestController
+@RequestMapping("/app/NFIDReadRecord")
+public class NFIDReadRecordController extends BaseController
+{
+    @Autowired
+    private INFIDReadRecordService NFIDReadRecordService;
+
+    // 存储所有客户端的 SSE 连接(Key: 客户端ID,Value: SseEmitter)
+    private final Map<String, SseEmitter> emitters = new ConcurrentHashMap<>();
+
+    private final ApplicationEventPublisher eventPublisher;
+
+    // 注入事件发布器
+    public NFIDReadRecordController(ApplicationEventPublisher eventPublisher) {
+        this.eventPublisher = eventPublisher;
+    }
+
+
+    public static final Logger log = LoggerFactory.getLogger(ExcelUtil.class);
+
+    /**
+     * 查询nfid识别记录列表
+     */
+    @ApiOperation("查询nfid识别记录列表")
+    // @PreAuthorize("@ss.hasPermi('app:NFIDReadRecord:list')")
+    @GetMapping("/list")
+    public TableDataInfo list()
+    {
+        startPage();
+        List<NFIDReadRecord> list = NFIDReadRecordService.selectNFIDReadRecordList();
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询nfid识别记录分组结果
+     */
+    @ApiOperation("查询nfid识别记录分组结果")
+    // @PreAuthorize("@ss.hasPermi('app:NFIDReadRecord:list')")
+    @GetMapping("/groupList")
+    public AjaxResult groupList()
+    {
+        List<NFIDReadGroupDTO> list = NFIDReadRecordService.selectNFIDReadByGroup();
+        return success(list);
+    }
+
+    /**
+     * 查询nfid识别记录分组详情清单
+     */
+    @ApiOperation("查询nfid识别记录分组详情清单")
+    // @PreAuthorize("@ss.hasPermi('app:NFIDReadRecord:list')")
+    @GetMapping("/groupDetail")
+    public AjaxResult groupDetail(ReqValidNFIDRecord req)
+    {
+        List<NFIDReadRecord> list = NFIDReadRecordService.selectNFIDReadRecordListByGroup(req);
+        return success(list);
+    }
+
+    /**
+     * 查询可用的nfid识别记录列表
+     */
+    @ApiOperation("查询可用的nfid识别记录列表")
+    // @PreAuthorize("@ss.hasPermi('app:NFIDReadRecord:list')")
+    @GetMapping("/validList")
+    public AjaxResult validList(ReqValidNFIDRecord req)
+    {
+        List<NFIDReadRecord> list = NFIDReadRecordService.selectValidNFIDReadRecord(req);
+        return success(list);
+
+    }
+
+    /**
+     * 新增nfid识别记录
+     */
+    @ApiOperation("新增nfid识别记录")
+    // @PreAuthorize("@ss.hasPermi('app:NFIDReadRecord:add')")
+    @Log(title = "nfid识别记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated  @RequestBody NFIDReadRecord record)
+    {
+
+        return toAjax(NFIDReadRecordService.insertNFIDReadRecord(record));
+    }
+
+    /**
+     * 客户端订阅识别记录数据
+     */
+    @GetMapping(value = "/subscribe/{clientId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
+    public SseEmitter subscribe(@PathVariable String clientId) {
+            SseEmitter emitter = new SseEmitter(30_000L); // 超时时间 30 秒
+            emitters.put(clientId, emitter);
+            //定期发送心跳
+            ExecutorService executor = Executors.newSingleThreadExecutor();
+            executor.execute(() -> {
+                try {
+                    while (true) {
+                        emitter.send(SseEmitter.event()
+                                .comment("ping") // 心跳消息
+                                .reconnectTime(5000)); // 重连时间建议
+                        Thread.sleep(10_000); // 每10秒一次心跳
+                    }
+                } catch (Exception e) {
+                    emitter.completeWithError(e);
+                }
+            });
+            // 监听连接断开
+            emitter.onCompletion(() -> emitters.remove(clientId));
+            emitter.onTimeout(() -> emitters.remove(clientId));
+
+            return emitter;
+    }
+
+    /**
+     * 客户端主动取消订阅
+     */
+    @PostMapping("/unsubscribe/{clientId}")
+    public void unsubscribe(@PathVariable String clientId) {
+        SseEmitter emitter = emitters.get(clientId);
+        if (emitter != null) {
+            emitter.complete();
+            emitters.remove(clientId);
+        }
+    }
+
+    // 监听设备数据事件并广播
+    @EventListener
+    public void handleReaderData(NFIDReaderEvent event) {
+        NFIDReadRecord data = event.getData();
+        System.out.println(data);
+        emitters.forEach((clientId, emitter) -> {
+
+            try {
+                emitter.send(SseEmitter.event()
+                        .data(data)
+                        .id(String.valueOf(data.getId()))
+                        .name("reader-data"));
+            } catch (IOException e) {
+                emitter.completeWithError(e);
+                emitters.remove(clientId);
+            }
+        });
+    }
+
+
+}

+ 16 - 0
app-admin/src/main/java/com/ruoyi/web/controller/app/NFIDReaderController.java

@@ -0,0 +1,16 @@
+package com.ruoyi.web.controller.app;
+
+import com.ruoyi.app.service.INFIDReaderService;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.system.service.ISysConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/app/NFIDReader")
+public class NFIDReaderController extends BaseController {
+    @Autowired
+    private INFIDReaderService NFIDReaderService;
+
+}

+ 126 - 0
app-admin/src/main/java/com/ruoyi/web/controller/app/PigCategoryController.java

@@ -0,0 +1,126 @@
+package com.ruoyi.web.controller.app;
+
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+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.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.app.model.PigCategory;
+import com.ruoyi.app.service.IPigCategoryService;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 生猪分类Controller
+ * 
+ * @author coede
+ * @date 2025-03-19
+ */
+@RestController
+@RequestMapping("/app/pigCategory")
+public class PigCategoryController extends BaseController
+{
+    @Autowired
+    private IPigCategoryService pigCategoryService;
+
+    /**
+     * 查询生猪分类列表
+     */
+    // @PreAuthorize("@ss.hasPermi('app:pigCategory:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(PigCategory pigCategory)
+    {
+        startPage();
+        List<PigCategory> list = pigCategoryService.selectPigCategoryList(pigCategory);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出生猪分类列表
+     */
+    // @PreAuthorize("@ss.hasPermi('app:pigCategory:export')")
+    @Log(title = "生猪分类", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, PigCategory pigCategory)
+    {
+        List<PigCategory> list = pigCategoryService.selectPigCategoryList(pigCategory);
+        ExcelUtil<PigCategory> util = new ExcelUtil<PigCategory>(PigCategory.class);
+        util.exportExcel(response, list, "生猪分类数据");
+    }
+
+    /**
+     * 获取生猪分类详细信息
+     */
+    // @PreAuthorize("@ss.hasPermi('app:pigCategory:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return success(pigCategoryService.selectPigCategoryById(id));
+    }
+
+    /**
+     * 新增生猪分类
+     */
+    // @PreAuthorize("@ss.hasPermi('app:pigCategory:add')")
+    @Log(title = "生猪分类", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody PigCategory pigCategory)
+    {
+        if (!pigCategoryService.checkCategoryNameUnique(pigCategory))
+        {
+            return error("新增生猪品种'" + pigCategory.getCategoryName() + "'失败,列表中存在重复的品种名称");
+        }
+        pigCategory.setCreateBy(getUsername());
+        return toAjax(pigCategoryService.insertPigCategory(pigCategory));
+    }
+
+    /**
+     * 修改生猪分类
+     */
+    // @PreAuthorize("@ss.hasPermi('app:pigCategory:edit')")
+    @Log(title = "生猪分类", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody PigCategory pigCategory)
+    {
+        if (!pigCategoryService.checkCategoryNameUnique(pigCategory))
+        {
+            return error("修改生猪品种'" + pigCategory.getCategoryName() + "'失败,列表中存在重复的品种名称");
+        }
+        pigCategory.setUpdateBy(getUsername());
+        return toAjax(pigCategoryService.updatePigCategory(pigCategory));
+    }
+
+    /**
+     * 删除生猪分类
+     */
+    // @PreAuthorize("@ss.hasPermi('app:pigCategory:remove')")
+    @Log(title = "生猪分类", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(pigCategoryService.deletePigCategoryByIds(ids));
+    }
+
+    /**
+     * 获取分类选择框列表
+     */
+    @GetMapping("/optionselect")
+    public AjaxResult optionselect(PigCategory pigCategory)
+    {
+
+        List<PigCategory> list = pigCategoryService.selectPigCategoryList(pigCategory);
+        return success(list);
+    }
+}

+ 147 - 0
app-admin/src/main/java/com/ruoyi/web/controller/app/PigpenController.java

@@ -0,0 +1,147 @@
+package com.ruoyi.web.controller.app;
+
+import java.util.HashMap;
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+
+import com.ruoyi.app.DTO.PigpenDTO;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+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.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.app.model.Pigpen;
+import com.ruoyi.app.service.IPigpenService;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 待宰圈Controller
+ * 
+ * @author coede
+ * @date 2025-03-19
+ */
+@RestController
+@RequestMapping("/app/pigpen")
+public class PigpenController extends BaseController
+{
+    @Autowired
+    private IPigpenService pigpenService;
+
+    /**
+     * 查询待宰圈列表
+     */
+    // @PreAuthorize("@ss.hasPermi('app:pigpen:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(Pigpen pigpen)
+    {
+        startPage();
+        List<Pigpen> list = pigpenService.selectPigpenList(pigpen);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出待宰圈列表
+     */
+    // @PreAuthorize("@ss.hasPermi('app:pigpen:export')")
+    @Log(title = "待宰圈", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, Pigpen pigpen)
+    {
+        List<PigpenDTO> list = pigpenService.printPigpenList(pigpen);
+        list.forEach(item -> item.setQrcode(item.getPigpenName()));
+        ExcelUtil<PigpenDTO> util = new ExcelUtil<PigpenDTO>(PigpenDTO.class);
+        util.exportExcel(response, list, "待宰圈数据");
+    }
+
+    /**
+     * 获取待宰圈详细信息
+     */
+    // @PreAuthorize("@ss.hasPermi('app:pigpen:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return success(pigpenService.selectPigpenById(id));
+    }
+
+    /**
+     * 新增待宰圈
+     */
+    // @PreAuthorize("@ss.hasPermi('app:pigpen:add')")
+    @Log(title = "待宰圈", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody Pigpen pigpen)
+    {
+        if (!pigpenService.checkPigpenNameUnique(pigpen))
+        {
+            return error("新增待宰圈'" + pigpen.getPigpenName() + "'失败,列表中存在重复的待宰圈号");
+        }
+        pigpen.setCreateBy(getUsername());
+        return toAjax(pigpenService.insertPigpen(pigpen));
+    }
+
+    /**
+     * 修改待宰圈
+     */
+    // @PreAuthorize("@ss.hasPermi('app:pigpen:edit')")
+    @Log(title = "待宰圈", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody Pigpen pigpen)
+    {
+        if (!pigpenService.checkPigpenNameUnique(pigpen))
+        {
+            return error("修改待宰圈'" + pigpen.getPigpenName() + "'失败,列表中存在重复的待宰圈号");
+        }
+        if (pigpen.getIsUse() != 0) {
+            return error("修改待宰圈失败,待宰圈正在使用");
+        }
+        pigpen.setUpdateBy(getUsername());
+        return toAjax(pigpenService.updatePigpen(pigpen));
+    }
+
+    /**
+     * 删除待宰圈
+     */
+    // @PreAuthorize("@ss.hasPermi('app:pigpen:remove')")
+    @Log(title = "待宰圈", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        for (Long id : ids) {
+            Pigpen pigpen = pigpenService.selectPigpenById(id);
+            if (pigpen.getIsUse() != 0) {
+                return error("删除待宰圈失败,待宰圈正在使用");
+            }
+        }
+        return toAjax(pigpenService.deletePigpenByIds(ids));
+    }
+
+    /**
+     * 获取待宰圈选择框列表
+     */
+    @GetMapping("/optionselect")
+    public AjaxResult optionselect(Pigpen pigpen)
+    {
+        List<Pigpen> list = pigpenService.selectPigpenList(pigpen);
+        return success(list);
+    }
+
+    /**
+     * 获取下一个自动生成的猪圈编号
+     */
+    @GetMapping("/nextNo")
+    public AjaxResult nextNo()
+    {
+        return success(new HashMap<String, String>() {{ put("nextNo", pigpenService.getNextNo());}});
+    }
+}

+ 103 - 0
app-admin/src/main/java/com/ruoyi/web/controller/app/PorkOtherProduceController.java

@@ -0,0 +1,103 @@
+package com.ruoyi.web.controller.app;
+
+import java.util.List;
+
+import com.ruoyi.app.DTO.PorkOtherProduceDTO;
+import com.ruoyi.app.model.request.ReqGetPorkOtherProduce;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+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.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.app.model.PorkOtherProduce;
+import com.ruoyi.app.service.IPorkOtherProduceService;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 猪头生产记录Controller
+ * 
+ * @author ruoyi
+ * @date 2025-03-19
+ */
+@Api("其他生产记录信息管理")
+@RestController
+@RequestMapping("/app/porkOtherProduce")
+public class PorkOtherProduceController extends BaseController
+{
+    @Autowired
+    private IPorkOtherProduceService porkOtherProduceService;
+
+    /**
+     * 查询猪头生产记录列表
+     */
+    @ApiOperation("查询其他生产记录列表")
+    // @PreAuthorize("@ss.hasPermi('app:porkOtherProduce:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(ReqGetPorkOtherProduce porkOtherProduce)
+    {
+        startPage();
+        List<PorkOtherProduceDTO> list = porkOtherProduceService.selectPorkOtherProduceList(porkOtherProduce);
+        return getDataTable(list);
+    }
+
+
+    /**
+     * 获取猪头生产记录详细信息
+     */
+    @ApiOperation("获取猪头生产记录详细信息")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return success(porkOtherProduceService.selectPorkOtherProduceById(id));
+    }
+
+    /**
+     * 新增猪头生产记录
+     */
+    @ApiOperation("新增猪头生产记录")
+    // @PreAuthorize("@ss.hasPermi('app:porkOtherProduce:add')")
+    @Log(title = "其他生产记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody PorkOtherProduce porkOtherProduce)
+    {
+        porkOtherProduce.setCreateBy(getUsername());
+        return toAjax(porkOtherProduceService.insertPorkOtherProduce(porkOtherProduce));
+    }
+
+    /**
+     * 修改猪头生产记录
+     */
+    @ApiOperation("修改猪头生产记录")
+    // @PreAuthorize("@ss.hasPermi('app:porkOtherProduce:edit')")
+    @Log(title = "其他生产记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody PorkOtherProduce porkOtherProduce)
+    {
+        porkOtherProduce.setUpdateBy(getUsername());
+        return toAjax(porkOtherProduceService.updatePorkOtherProduce(porkOtherProduce));
+    }
+
+    /**
+     * 删除猪头生产记录
+     */
+    @ApiOperation("删除猪头生产记录")
+    // @PreAuthorize("@ss.hasPermi('app:porkOtherProduce:remove')")
+    @Log(title = "其他生产记录", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(porkOtherProduceService.deletePorkOtherProduceByIds(ids));
+    }
+}

+ 119 - 0
app-admin/src/main/java/com/ruoyi/web/controller/app/PorkSideProduceController.java

@@ -0,0 +1,119 @@
+package com.ruoyi.web.controller.app;
+
+import java.util.List;
+
+import com.ruoyi.app.DTO.HookBindDetailDTO;
+import com.ruoyi.app.DTO.PorkSideProduceDTO;
+import com.ruoyi.app.model.request.AddPorkSideProduce;
+import com.ruoyi.app.model.request.ReqGetPorkSideProduce;
+import com.ruoyi.app.service.IHookBindService;
+import com.ruoyi.common.utils.DateUtils;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+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.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.app.model.PorkSideProduce;
+import com.ruoyi.app.service.IPorkSideProduceService;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 白条生产记录Controller
+ * 
+ * @author coede
+ * @date 2025-03-19
+ */
+@Api("白条生产记录信息管理")
+@RestController
+@RequestMapping("/app/porkSideProduce")
+public class PorkSideProduceController extends BaseController
+{
+    @Autowired
+    private IPorkSideProduceService porkSideProduceService;
+
+    @Autowired
+    private IHookBindService hookBindService;
+
+    /**
+     * 查询白条生产记录列表
+     */
+    @ApiOperation("查询白条生产记录列表")
+    // @PreAuthorize("@ss.hasPermi('app:porkSideProduce:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(ReqGetPorkSideProduce req)
+    {
+        startPage();
+        List<PorkSideProduceDTO> list = porkSideProduceService.selectPorkSideProduceList(req);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取白条生产记录详细信息
+     */
+    @ApiOperation("获取白条生产记录详细信息")
+    // @PreAuthorize("@ss.hasPermi('app:porkSideProduce:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return success(porkSideProduceService.selectPorkSideProduceById(id));
+    }
+
+    /**
+     * 新增白条生产记录
+     */
+    @ApiOperation("新增白条生产记录")
+    // @PreAuthorize("@ss.hasPermi('app:porkSideProduce:add')")
+    @Log(title = "白条生产记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody AddPorkSideProduce req)
+    {
+        HookBindDetailDTO hookBind = hookBindService.selectHookBindDetail(req.getHookNo());
+
+        PorkSideProduce porkSideProduce = new PorkSideProduce();
+        porkSideProduce.setProductName(PorkSideProduce.DEFAULT_NAME);
+        porkSideProduce.setProduceTime(DateUtils.getNowDate());
+        porkSideProduce.setHookNo(req.getHookNo());
+        porkSideProduce.setSlaughterCode(hookBind.getSlaughterCode());
+        porkSideProduce.setDistributeBatchId(hookBind.getDistributeBatchId());
+        porkSideProduce.setEntranceBatchId(hookBind.getEntranceBatchId());
+        porkSideProduce.setCreateBy(getUsername());
+        return toAjax(porkSideProduceService.insertPorkSideProduce(porkSideProduce));
+    }
+
+    /**
+     * 修改白条生产记录
+     */
+    @ApiOperation("修改白条生产记录")
+    // @PreAuthorize("@ss.hasPermi('app:porkSideProduce:edit')")
+    @Log(title = "白条生产记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody PorkSideProduce porkSideProduce)
+    {
+        porkSideProduce.setUpdateBy(getUsername());
+        return toAjax(porkSideProduceService.updatePorkSideProduce(porkSideProduce));
+    }
+
+    /**
+     * 删除白条生产记录
+     */
+    @ApiOperation("删除白条生产记录")
+    // @PreAuthorize("@ss.hasPermi('app:porkSideProduce:remove')")
+    @Log(title = "白条生产记录", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(porkSideProduceService.deletePorkSideProduceByIds(ids));
+    }
+}

+ 99 - 0
app-admin/src/main/java/com/ruoyi/web/controller/app/PorkSideProduceRecordController.java

@@ -0,0 +1,99 @@
+package com.ruoyi.web.controller.app;
+
+import com.ruoyi.app.DTO.PorkSideProduceRecordDTO;
+import com.ruoyi.app.model.PorkSideProduceRecord;
+import com.ruoyi.app.model.request.ReqGetPorkSideProduceRecord;
+import com.ruoyi.app.service.IPorkSideProduceRecordService;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.DateUtils;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+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 java.util.List;
+
+/**
+ * 白条生产录入记录Controller
+ * 
+ * @author coede
+ * @date 2025-05-09
+ */
+@Api("白条生产录入记录信息管理")
+@RestController
+@RequestMapping("/app/porkSideProduceRecord")
+public class PorkSideProduceRecordController extends BaseController
+{
+    @Autowired
+    private IPorkSideProduceRecordService PorkSideProduceRecordService;
+
+    /**
+     * 查询白条生产录入记录列表
+     */
+    @ApiOperation("查询白条生产录入记录列表")
+    // @PreAuthorize("@ss.hasPermi('app:porkSideProduceRecord:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(ReqGetPorkSideProduceRecord req)
+    {
+        startPage();
+        List<PorkSideProduceRecordDTO> list = PorkSideProduceRecordService.selectPorkSideProduceRecordList(req);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取白条生产录入记录详细信息
+     */
+    @ApiOperation("获取白条生产录入记录详细信息")
+    // @PreAuthorize("@ss.hasPermi('app:porkSideProduceRecord:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return success(PorkSideProduceRecordService.selectPorkSideProduceRecordById(id));
+    }
+
+    /**
+     * 新增白条生产录入记录
+     */
+    @ApiOperation("新增白条生产录入记录")
+    // @PreAuthorize("@ss.hasPermi('app:porkSideProduceRecord:add')")
+    @Log(title = "白条生产录入记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody PorkSideProduceRecord req)
+    {
+        req.setProductName(PorkSideProduceRecord.DEFAULT_NAME);
+        req.setProduceTime(DateUtils.getNowDate());
+        req.setCreateBy(getUsername());
+        return toAjax(PorkSideProduceRecordService.insertPorkSideProduceRecord(req));
+    }
+
+    /**
+     * 修改白条生产录入记录
+     */
+    @ApiOperation("修改白条生产录入记录")
+    // @PreAuthorize("@ss.hasPermi('app:porkSideProduceRecord:edit')")
+    @Log(title = "白条生产录入记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody PorkSideProduceRecord PorkSideProduceRecord)
+    {
+        PorkSideProduceRecord.setUpdateBy(getUsername());
+        return toAjax(PorkSideProduceRecordService.updatePorkSideProduceRecord(PorkSideProduceRecord));
+    }
+
+    /**
+     * 删除白条生产录入记录
+     */
+    @ApiOperation("删除白条生产录入记录")
+    // @PreAuthorize("@ss.hasPermi('app:porkSideProduceRecord:remove')")
+    @Log(title = "白条生产录入记录", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(PorkSideProduceRecordService.deletePorkSideProduceRecordByIds(ids));
+    }
+}

+ 144 - 0
app-admin/src/main/java/com/ruoyi/web/controller/app/ProductCategoryController.java

@@ -0,0 +1,144 @@
+package com.ruoyi.web.controller.app;
+
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+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.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.app.model.ProductCategory;
+import com.ruoyi.app.service.IProductCategoryService;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 产品分类Controller
+ * 
+ * @author coede
+ * @date 2025-03-19
+ */
+@RestController
+@RequestMapping("/app/productCategory")
+public class ProductCategoryController extends BaseController
+{
+    @Autowired
+    private IProductCategoryService productCategoryService;
+
+    /**
+     * 查询产品分类列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo list(ProductCategory productCategory)
+    {
+        startPage();
+        List<ProductCategory> list = productCategoryService.selectProductCategoryList(productCategory);
+        return getDataTable(list);
+    }
+
+    @GetMapping("/page")
+    public TableDataInfo page(ProductCategory productCategory)
+    {
+        List<ProductCategory> list = productCategoryService.selectProductCategoryList(productCategory);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出产品分类列表
+     */
+    // @PreAuthorize("@ss.hasPermi('app:productCategory:export')")
+    @Log(title = "产品分类", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, ProductCategory productCategory)
+    {
+        List<ProductCategory> list = productCategoryService.selectProductCategoryList(productCategory);
+        ExcelUtil<ProductCategory> util = new ExcelUtil<ProductCategory>(ProductCategory.class);
+        util.exportExcel(response, list, "产品分类数据");
+    }
+
+    /**
+     * 获取产品分类详细信息
+     */
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return success(productCategoryService.selectProductCategoryById(id));
+    }
+
+    /**
+     * 新增产品分类
+     */
+    // @PreAuthorize("@ss.hasPermi('app:productCategory:add')")
+    @Log(title = "产品分类", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody ProductCategory productCategory)
+    {
+        if (!productCategoryService.checkProductNameUnique(productCategory))
+        {
+            return error("新增产品分类'" + productCategory.getProductName() + "'失败,列表中存在重复的产品名称");
+        }
+        productCategory.setCreateBy(getUsername());
+        return toAjax(productCategoryService.insertProductCategory(productCategory));
+    }
+
+    /**
+     * 修改产品分类
+     */
+    // @PreAuthorize("@ss.hasPermi('app:productCategory:edit')")
+    @Log(title = "产品分类", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody ProductCategory productCategory)
+    {
+        if (!productCategoryService.checkProductNameUnique(productCategory))
+        {
+            return error("修改产品分类'" + productCategory.getProductName() + "'失败,列表中存在重复的产品名称");
+        }
+        productCategory.setUpdateBy(getUsername());
+        return toAjax(productCategoryService.updateProductCategory(productCategory));
+    }
+
+    /**
+     * 删除产品分类
+     */
+    // @PreAuthorize("@ss.hasPermi('app:productCategory:remove')")
+    @Log(title = "产品分类", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(productCategoryService.deleteProductCategoryByIds(ids));
+    }
+
+    /**
+     * 获取产品分类选择框列表
+     */
+    @GetMapping("/optionselect")
+    public AjaxResult optionselect()
+    {
+        ProductCategory productCategory = new ProductCategory();
+        List<ProductCategory> list = productCategoryService.selectProductCategoryList(productCategory);
+        return success(list);
+    }
+
+    /**
+     * 获取在售产品分类选择框列表
+     */
+    @GetMapping("/allSale")
+    public AjaxResult allSale()
+    {
+        ProductCategory productCategory = new ProductCategory();
+        productCategory.setOnSaleFlag(1);
+        List<ProductCategory> list = productCategoryService.selectProductCategoryList(productCategory);
+        return success(list);
+    }
+}

+ 58 - 0
app-admin/src/main/java/com/ruoyi/web/controller/app/ProductTraceController.java

@@ -0,0 +1,58 @@
+package com.ruoyi.web.controller.app;
+
+import com.ruoyi.app.model.EntranceBatch;
+import com.ruoyi.app.model.request.ProductTraceParam;
+import com.ruoyi.app.model.request.ReqEntranceAndDistribute;
+import com.ruoyi.app.service.IEntranceBatchService;
+import com.ruoyi.app.service.IProductTraceService;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.utils.ObjectUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 产品追溯
+ *
+ * @author coede
+ * @date 2025-03-30
+ */
+@RestController
+@RequestMapping("/app/productTrace")
+public class ProductTraceController extends BaseController {
+    @Autowired
+    private IProductTraceService productTraceService;
+    @Autowired
+    private IEntranceBatchService entranceBatchService;
+
+    /**
+     * 获取追溯信息信息
+     */
+    // @PreAuthorize("@ss.hasPermi('app:productTrace:getProductTrace')")
+    @GetMapping(value = "/getProductTrace")
+    public AjaxResult getProductTrace(ProductTraceParam productTraceParam) {
+        if (ObjectUtils.allFieldsNull(productTraceParam)) {
+            //获取最近的一条
+            EntranceBatch entranceBatch = entranceBatchService.selectLatelyEntrance();
+            if (entranceBatch == null) {
+                return error("暂无入场信息");
+            }
+            productTraceParam = new ProductTraceParam();
+            productTraceParam.setAnimalCertNo(entranceBatch.getAnimalCertNo());
+        }
+        return success(productTraceService.selectProductTrace(productTraceParam));
+    }
+
+    /**
+     * 获取分割生产信息
+     */
+    // @PreAuthorize("@ss.hasPermi('app:productTrace:getPorkProduce')")
+    @GetMapping(value = "/getPorkProduce")
+    public AjaxResult getPorkProduce(ReqEntranceAndDistribute reqEntranceAndDistribute){
+        return success(productTraceService.selectPorkProduceByBatch(reqEntranceAndDistribute));
+    }
+
+}

+ 174 - 0
app-admin/src/main/java/com/ruoyi/web/controller/app/PurchaserController.java

@@ -0,0 +1,174 @@
+package com.ruoyi.web.controller.app;
+
+import java.util.HashMap;
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.ruoyi.app.model.request.ReqPurchaser;
+import com.ruoyi.web.v2.v1.utils.PhoneNumberValidator;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+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.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.app.model.Purchaser;
+import com.ruoyi.app.service.IPurchaserService;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+import static com.ruoyi.common.core.domain.AjaxResult.error;
+
+/**
+ * 肉商Controller
+ * 
+ * @author coede
+ * @date 2025-03-19
+ */
+@Api("肉商信息管理")
+@RestController
+@RequestMapping("/app/purchaser")
+public class PurchaserController extends BaseController
+{
+    @Autowired
+    private IPurchaserService purchaserService;
+
+    /**
+     * 查询肉商列表
+     */
+    @ApiOperation("查询肉商列表")
+    @GetMapping("/list")
+    public TableDataInfo list(ReqPurchaser purchaser)
+    {
+        startPage();
+        List<Purchaser> list = purchaserService.selectPurchaserList(purchaser);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取肉商选择框列表
+     */
+    @ApiOperation("获取肉商选择框列表")
+    @GetMapping("/optionselect")
+    public AjaxResult optionselect()
+    {
+        List<Purchaser> list = purchaserService.selectAllPurchaserList();
+        return success(list);
+    }
+
+    /**
+     * 导出肉商列表
+     */
+    // @PreAuthorize("@ss.hasPermi('app:purchaser:export')")
+    @Log(title = "肉商", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, ReqPurchaser purchaser)
+    {
+        List<Purchaser> list = purchaserService.selectPurchaserList(purchaser);
+        ExcelUtil<Purchaser> util = new ExcelUtil<Purchaser>(Purchaser.class);
+        util.exportExcel(response, list, "肉商数据");
+    }
+
+    /**
+     * 获取肉商详细信息
+     */
+    @ApiOperation("获取肉商详细信息")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return success(purchaserService.selectPurchaserById(id));
+    }
+
+    /**
+     * 新增肉商
+     */
+    @ApiOperation("新增肉商")
+    // @PreAuthorize("@ss.hasPermi('app:purchaser:add')")
+    @Log(title = "肉商", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody Purchaser purchaser)
+    {
+//        if (!purchaserService.checkPurchaserNoUnique(purchaser))
+//        {
+//            return error("新增肉商'" + purchaser.getPurchaserName() + "'失败,列表中存在重复的肉商编号");
+//        }
+        if (ObjectUtil.isNotEmpty(purchaser.getPhone())) {
+            if (!PhoneNumberValidator.isValidContact(purchaser.getPhone())) {
+
+                return error("联系方式格式不正确!");
+            }
+        }
+        if (!purchaserService.checkPurchaserNameUnique(purchaser))
+        {
+            return error("新增肉商'" + purchaser.getPurchaserName() + "'失败,列表中存在重复的肉商名称");
+        }
+        if (!purchaserService.checkIDNumberUnique(purchaser))
+        {
+            return error("新增肉商'" + purchaser.getPurchaserName() + "'失败,列表中存在重复的身份证号");
+        }
+        purchaser.setCreateBy(getUsername());
+        return toAjax(purchaserService.insertPurchaser(purchaser));
+    }
+
+    /**
+     * 修改肉商
+     */
+    @ApiOperation("修改肉商")
+    // @PreAuthorize("@ss.hasPermi('app:purchaser:edit')")
+    @Log(title = "肉商", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody Purchaser purchaser)
+    {
+        if (ObjectUtil.isNotEmpty(purchaser.getPhone())) {
+            if (!PhoneNumberValidator.isValidContact(purchaser.getPhone())) {
+
+                return error("联系方式格式不正确!");
+            }
+        }
+        if (!purchaserService.checkPurchaserNoUnique(purchaser))
+        {
+            return error("修改肉商'" + purchaser.getPurchaserName() + "'失败,列表中存在重复的肉商编号");
+        }
+        if (!purchaserService.checkPurchaserNameUnique(purchaser))
+        {
+            return error("修改肉商'" + purchaser.getPurchaserName() + "'失败,列表中存在重复的肉商名称");
+        }
+        if (!purchaserService.checkIDNumberUnique(purchaser))
+        {
+            return error("修改肉商'" + purchaser.getPurchaserName() + "'失败,列表中存在重复的身份证号");
+        }
+        purchaser.setUpdateBy(getUsername());
+        return toAjax(purchaserService.updatePurchaser(purchaser));
+    }
+
+    /**
+     * 删除肉商
+     */
+    @ApiOperation("删除肉商")
+    // @PreAuthorize("@ss.hasPermi('app:purchaser:remove')")
+    @Log(title = "肉商", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(purchaserService.deletePurchaserByIds(ids));
+    }
+
+    @ApiOperation("查询自动生成的肉商编号")
+    @GetMapping("/nextNo")
+    public AjaxResult nextNo()
+    {
+        return success(new HashMap<String, String>() {{ put("nextNo", purchaserService.getNextNo());}});
+    }
+}

+ 147 - 0
app-admin/src/main/java/com/ruoyi/web/controller/app/RegionController.java

@@ -0,0 +1,147 @@
+package com.ruoyi.web.controller.app;
+
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+
+import com.ruoyi.app.DTO.RegionTreeDTO;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+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.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.app.model.Region;
+import com.ruoyi.app.service.IRegionService;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 区域Controller
+ * 
+ * @author coede
+ * @date 2025-03-19
+ */
+@Api("区域信息管理")
+@RestController
+@RequestMapping("/app/region")
+public class RegionController extends BaseController
+{
+    @Autowired
+    private IRegionService regionService;
+
+    /**
+     * 查询区域列表
+     */
+    @ApiOperation("查询区域列表")
+    @GetMapping("/list")
+    public TableDataInfo list(Region region)
+    {
+        startPage();
+        List<Region> list = regionService.selectRegionList(region);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出区域列表
+     */
+    @ApiOperation("导出区域列表")
+    // @PreAuthorize("@ss.hasPermi('app:region:export')")
+    @Log(title = "区域", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, Region region)
+    {
+        List<Region> list = regionService.selectRegionList(region);
+        ExcelUtil<Region> util = new ExcelUtil<Region>(Region.class);
+        util.exportExcel(response, list, "区域数据");
+    }
+
+    /**
+     * 获取区域详细信息
+     */
+    @ApiOperation("获取区域详细信息")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return success(regionService.selectRegionById(id));
+    }
+
+    /**
+     * 新增区域
+     */
+    @ApiOperation("新增区域")
+    // @PreAuthorize("@ss.hasPermi('app:region:add')")
+    @Log(title = "区域", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated  @RequestBody Region region)
+    {
+        if (!regionService.checkRegionNameUnique(region))
+        {
+            return error("新增区域'" + region.getRegionName() + "'失败,列表中存在重复的区域名称");
+        }
+        region.setCreateBy(getUsername());
+        return toAjax(regionService.insertRegion(region));
+    }
+
+    /**
+     * 修改区域
+     */
+    @ApiOperation("修改区域")
+    // @PreAuthorize("@ss.hasPermi('app:region:edit')")
+    @Log(title = "区域", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody Region region)
+    {
+        if (!regionService.checkRegionNameUnique(region))
+        {
+            return error("修改区域'" + region.getRegionName() + "'失败,列表中存在重复的区域名称");
+        }
+        region.setUpdateBy(getUsername());
+        return toAjax(regionService.updateRegion(region));
+    }
+
+    /**
+     * 删除区域
+     */
+    @ApiOperation("删除区域")
+    // @PreAuthorize("@ss.hasPermi('app:region:remove')")
+    @Log(title = "区域", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(regionService.deleteRegionByIds(ids));
+    }
+
+    /**
+     * 查询区域选择
+     */
+    @ApiOperation("查询区域选择")
+    @GetMapping("/optionselect")
+    public AjaxResult optionselect()
+    {
+        Region region = new Region();
+        List<Region> list = regionService.selectRegionList(region);
+        return success(list);
+    }
+
+    /**
+     * 查询区域设备树
+     */
+    @ApiOperation("查询区域设备树")
+    @GetMapping("/treeList")
+    public AjaxResult treeList(Region region)
+    {
+        List<RegionTreeDTO> list = regionService.selectRegionTreeList(region);
+        return success(list);
+    }
+}

+ 95 - 0
app-admin/src/main/java/com/ruoyi/web/controller/app/SlaughterBatchController.java

@@ -0,0 +1,95 @@
+package com.ruoyi.web.controller.app;
+
+import java.util.List;
+
+import com.ruoyi.app.DTO.SlaughterBatchDTO;
+import com.ruoyi.app.model.request.ReqSlaughterBatch;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+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.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.app.model.SlaughterBatch;
+import com.ruoyi.app.service.ISlaughterBatchService;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 屠宰批次Controller
+ * 
+ * @author coede
+ * @date 2025-03-19
+ */
+@RestController
+@RequestMapping("/app/slaughterBatch")
+public class SlaughterBatchController extends BaseController
+{
+    @Autowired
+    private ISlaughterBatchService slaughterBatchService;
+
+    /**
+     * 查询屠宰批次列表
+     */
+    // @PreAuthorize("@ss.hasPermi('app:slaughterBatch:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(ReqSlaughterBatch req)
+    {
+        startPage();
+        List<SlaughterBatchDTO> list = slaughterBatchService.selectSlaughterBatchList(req);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取屠宰批次详细信息
+     */
+    // @PreAuthorize("@ss.hasPermi('app:slaughterBatch:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return success(slaughterBatchService.selectSlaughterBatchById(id));
+    }
+
+    /**
+     * 新增屠宰批次
+     */
+    // @PreAuthorize("@ss.hasPermi('app:slaughterBatch:add')")
+    @Log(title = "屠宰批次", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody SlaughterBatch slaughterBatch)
+    {
+        slaughterBatch.setCreateBy(getUsername());
+        return toAjax(slaughterBatchService.insertSlaughterBatch(slaughterBatch));
+    }
+
+    /**
+     * 修改屠宰批次
+     */
+    // @PreAuthorize("@ss.hasPermi('app:slaughterBatch:edit')")
+    @Log(title = "屠宰批次", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated  @RequestBody SlaughterBatch slaughterBatch)
+    {
+        slaughterBatch.setUpdateBy(getUsername());
+        return toAjax(slaughterBatchService.updateSlaughterBatch(slaughterBatch));
+    }
+
+    /**
+     * 删除屠宰批次
+     */
+    // @PreAuthorize("@ss.hasPermi('app:slaughterBatch:remove')")
+    @Log(title = "屠宰批次", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(slaughterBatchService.deleteSlaughterBatchByIds(ids));
+    }
+}

+ 226 - 0
app-admin/src/main/java/com/ruoyi/web/controller/app/SlaughterRelationController.java

@@ -0,0 +1,226 @@
+package com.ruoyi.web.controller.app;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.servlet.http.HttpServletResponse;
+
+import com.ruoyi.app.DTO.SlaughterRelationDTO;
+import com.ruoyi.app.model.Purchaser;
+import com.ruoyi.app.model.request.AddSlaughterRelationBatch;
+import com.ruoyi.app.model.request.ExportSlaughterRelation;
+import com.ruoyi.app.model.request.ReqGetSlaughterRelation;
+import com.ruoyi.app.model.request.ReqSlaughterRelation;
+import com.ruoyi.app.model.response.RespGetSlaughterRelation;
+import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.common.utils.StringUtils;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+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.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.app.model.SlaughterRelation;
+import com.ruoyi.app.service.ISlaughterRelationService;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 血码关系Controller
+ * 
+ * @author coede
+ * @date 2025-03-19
+ */
+@Api("血码关系信息管理")
+@RestController
+@RequestMapping("/app/slaughterRelation")
+public class SlaughterRelationController extends BaseController
+{
+    @Autowired
+    private ISlaughterRelationService slaughterRelationService;
+
+    /**
+     * 供应商血码数量列表
+     */
+    @ApiOperation("供应商血码数量列表")
+    @GetMapping("/supplierList")
+    public TableDataInfo supplierList(ReqGetSlaughterRelation req)
+    {
+        startPage();
+        List<RespGetSlaughterRelation> list = slaughterRelationService.selectSupplierList(req);
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询血码关系列表
+     */
+    @ApiOperation("查询血码关系列表")
+    @GetMapping("/list")
+    public TableDataInfo list(ReqSlaughterRelation req)
+    {
+        startPage();
+        List<SlaughterRelation> list = slaughterRelationService.selectSlaughterRelationList(req);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出血码关系列表
+     */
+    @ApiOperation("导出血码关系列表")
+    // @PreAuthorize("@ss.hasPermi('app:slaughterRelation:export')")
+    @Log(title = "血码关系", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, ExportSlaughterRelation req)
+    {
+        List<SlaughterRelationDTO> list = slaughterRelationService.exportSlaughterRelationList(req);
+        list.forEach(item -> item.setQrcode(item.getSlaughterCode()));
+        ExcelUtil<SlaughterRelationDTO> util = new ExcelUtil<>(SlaughterRelationDTO.class);
+        util.exportExcel(response, list, "血码关系数据");
+    }
+
+    /**
+     * 获取血码关系详细信息
+     */
+    @ApiOperation("获取血码关系详细信息")
+    // @PreAuthorize("@ss.hasPermi('app:slaughterRelation:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return success(slaughterRelationService.selectSlaughterRelationById(id));
+    }
+
+    /**
+     * 新增血码关系
+     */
+    @ApiOperation("新增血码关系")
+    // @PreAuthorize("@ss.hasPermi('app:slaughterRelation:add')")
+    @Log(title = "血码关系", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody SlaughterRelation slaughterRelation)
+    {
+        if (!slaughterRelationService.checkCodeUnique(slaughterRelation))
+        {
+            return error("新增血码关系失败,血码'" + slaughterRelation.getSlaughterCode() + "'已存在");
+        }
+        SlaughterRelation check = slaughterRelationService.checkPurchaserUnique(slaughterRelation);
+        if (StringUtils.isNotNull(check))
+        {
+            Purchaser purchaser = check.getPurchaser();
+            return error("新增血码关系失败,该供应商下肉商'" + purchaser.getPurchaserName() + "'已存在");
+        }
+        slaughterRelation.setCreateBy(getUsername());
+        return toAjax(slaughterRelationService.insertSlaughterRelation(slaughterRelation));
+    }
+
+    /**
+     * 批量新增血码关系
+     */
+    @ApiOperation("批量新增血码关系")
+    // @PreAuthorize("@ss.hasPermi('app:slaughterRelation:add')")
+    @Log(title = "血码关系", businessType = BusinessType.INSERT)
+    @PostMapping(value = "/addBatch")
+    public AjaxResult addBatch(@Validated @RequestBody AddSlaughterRelationBatch req)
+    {
+        //生成新对象
+        List<SlaughterRelation> relationArr = req.getRelationArr().stream()
+                .map(item -> {
+                    SlaughterRelation newrelation = new SlaughterRelation();
+                    newrelation.setPurchaserId(item.getPurchaserId());
+                    newrelation.setSlaughterCode(item.getSlaughterCode());
+                    newrelation.setSupplierId(req.getSupplierId());
+                    newrelation.setCreateTime(DateUtils.getNowDate());
+                    newrelation.setCreateBy(getUsername());
+                    return newrelation;
+                })
+                .collect(Collectors.toList());
+        //先判断本身数组内是否有重复的
+        Set<String> uniqueIds = new HashSet<>();
+        Set<String> uniqueCodes = new HashSet<>();
+        for (SlaughterRelation relation : relationArr) {
+            String key = String.valueOf(relation.getPurchaserId());
+            //noinspection ConstantConditions  忽略错误的IDEA提示
+            if (!uniqueIds.add(key)) {
+                return error("新增血码关系失败,存在同一肉商配置多个血码的情况");
+            }
+            String code = relation.getSlaughterCode();
+            //noinspection ConstantConditions  忽略错误的IDEA提示
+            if (!uniqueCodes.add(code)) {
+                return error("新增血码关系失败,存在多个肉商配置同一血码的情况");
+            }
+        }
+        //校验数据库的
+        for (SlaughterRelation relation : relationArr) {
+
+            if (!slaughterRelationService.checkCodeUnique(relation))
+            {
+                return error("新增血码关系失败,血码'" + relation.getSlaughterCode() + "'已存在");
+            }
+            SlaughterRelation check = slaughterRelationService.checkPurchaserUnique(relation);
+            if (StringUtils.isNotNull(check))
+            {
+                Purchaser purchaser = check.getPurchaser();
+                return error("新增血码关系失败,该供应商下肉商'" + purchaser.getPurchaserName() + "'已存在");
+            }
+        }
+        return toAjax(slaughterRelationService.insertSlaughterRelationBatch(relationArr));
+    }
+
+    /**
+     * 修改血码关系
+     */
+    @ApiOperation("修改血码关系")
+    // @PreAuthorize("@ss.hasPermi('app:slaughterRelation:edit')")
+    @Log(title = "血码关系", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody SlaughterRelation slaughterRelation)
+    {
+        if (!slaughterRelationService.checkCodeUnique(slaughterRelation))
+        {
+            return error("修改血码关系失败,血码'" + slaughterRelation.getSlaughterCode() + "'已存在");
+        }
+        SlaughterRelation check = slaughterRelationService.checkPurchaserUnique(slaughterRelation);
+        if (StringUtils.isNotNull(check) && check.getId().longValue() != slaughterRelation.getId().longValue())
+        {
+            Purchaser purchaser = check.getPurchaser();
+            return error("修改血码关系失败,该供应商下肉商'" + purchaser.getPurchaserName() + "'已存在");
+        }
+        slaughterRelation.setUpdateBy(getUsername());
+        return toAjax(slaughterRelationService.updateSlaughterRelation(slaughterRelation));
+    }
+
+    /**
+     * 删除血码关系
+     */
+    @ApiOperation("删除血码关系")
+    // @PreAuthorize("@ss.hasPermi('app:slaughterRelation:remove')")
+    @Log(title = "血码关系", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(slaughterRelationService.deleteSlaughterRelationByIds(ids));
+    }
+
+    /**
+     * 查询全部血码关系列表
+     */
+    @ApiOperation("查询全部血码关系列表")
+    @GetMapping("/optionselect")
+    public AjaxResult optionselect(ReqSlaughterRelation req)
+    {
+
+        List<SlaughterRelation> list = slaughterRelationService.selectSlaughterRelationList(req);
+        return success(list);
+    }
+}

+ 151 - 0
app-admin/src/main/java/com/ruoyi/web/controller/app/StaffController.java

@@ -0,0 +1,151 @@
+package com.ruoyi.web.controller.app;
+
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.system.service.ISysPostService;
+import com.ruoyi.web.v2.v1.utils.PhoneNumberValidator;
+import com.ruoyi.web.v3.model.request.JsStaffPageReq;
+import org.springframework.beans.BeanUtils;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+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.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.app.model.Staff;
+import com.ruoyi.app.service.IStaffService;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 人员Controller
+ * 
+ * @author coede
+ * @date 2025-03-20
+ */
+@RestController
+@RequestMapping("/app/staff")
+public class StaffController extends BaseController
+{
+    @Autowired
+    private IStaffService staffService;
+
+    @Autowired
+    private ISysPostService sysPostService;
+
+    /**
+     * 查询人员列表
+     */
+    // @PreAuthorize("@ss.hasPermi('app:staff:list')")
+    @GetMapping("/list")
+    public AjaxResult list(JsStaffPageReq staff)
+    {
+//        startPage();
+        Staff staffEntity = new Staff();
+        BeanUtils.copyProperties(staff, staffEntity);
+        Page<Staff> list = staffService.selectStaffList(staff.toPage(), staffEntity);
+//        return getDataTable(list);
+        return success(list);
+    }
+
+    /**
+     * 导出人员列表
+     */
+    // @PreAuthorize("@ss.hasPermi('app:staff:export')")
+    @Log(title = "人员", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, Staff staff)
+    {
+        List<Staff> list = staffService.selectStaffAll(staff);
+        ExcelUtil<Staff> util = new ExcelUtil<Staff>(Staff.class);
+        util.exportExcel(response, list, "人员数据");
+    }
+
+    /**
+     * 获取人员详细信息
+     */
+    // @PreAuthorize("@ss.hasPermi('app:staff:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        AjaxResult ajax = AjaxResult.success();
+        if (StringUtils.isNotNull(id))
+        {
+            Staff staff = staffService.selectStaffById(id);
+            ajax.put(AjaxResult.DATA_TAG, staff);
+            ajax.put("postIds", sysPostService.selectPostListByStaffId(id));
+        }
+        return ajax;
+    }
+
+    /**
+     * 新增人员
+     */
+    // @PreAuthorize("@ss.hasPermi('app:staff:add')")
+    @Log(title = "人员", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody Staff staff)
+    {
+        if (ObjectUtil.isNotEmpty(staff.getPhone())) {
+            if (!PhoneNumberValidator.isValidContact(staff.getPhone())) {
+
+                return error("联系方式格式不正确!");
+            }
+        }
+        staff.setCreateBy(getUsername());
+        return toAjax(staffService.insertStaff(staff));
+    }
+
+    /**
+     * 修改人员
+     */
+    // @PreAuthorize("@ss.hasPermi('app:staff:edit')")
+    @Log(title = "人员", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody Staff staff)
+    {
+        if (ObjectUtil.isNotEmpty(staff.getPhone())) {
+            if (!PhoneNumberValidator.isValidContact(staff.getPhone())) {
+
+                return error("联系方式格式不正确!");
+            }
+        }
+        staff.setUpdateBy(getUsername());
+        return toAjax(staffService.updateStaff(staff));
+    }
+
+    /**
+     * 删除人员
+     */
+    // @PreAuthorize("@ss.hasPermi('app:staff:remove')")
+    @Log(title = "人员", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(staffService.deleteStaffByIds(ids));
+    }
+
+    /**
+     * 查询全部人员列表
+     */
+    // @PreAuthorize("@ss.hasPermi('app:staff:optionselect')")
+    @GetMapping("/optionselect")
+    public AjaxResult optionselect(Staff staff)
+    {
+        List<Staff> list = staffService.selectStaffAll(staff);
+        return success(list);
+    }
+}

+ 169 - 0
app-admin/src/main/java/com/ruoyi/web/controller/app/SupplierController.java

@@ -0,0 +1,169 @@
+package com.ruoyi.web.controller.app;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.servlet.http.HttpServletResponse;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.ruoyi.app.model.request.ReqSupplierList;
+import com.ruoyi.web.v2.v1.utils.PhoneNumberValidator;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+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.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.app.model.Supplier;
+import com.ruoyi.app.service.ISupplierService;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 供应商Controller
+ * 
+ * @author coede
+ * @date 2025-03-19
+ */
+@Api("供应商信息管理")
+@RestController
+@RequestMapping("/app/supplier")
+public class SupplierController extends BaseController
+{
+    @Autowired
+    private ISupplierService supplierService;
+
+    /**
+     * 查询供应商列表
+     */
+    @ApiOperation("查询供应商列表")
+    @GetMapping("/list")
+    public TableDataInfo list(ReqSupplierList req)
+    {
+        startPage();
+        List<Supplier> list = supplierService.selectSupplierList(req);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取供应商选择框列表
+     */
+    @ApiOperation("获取供应商选择框列表")
+    @GetMapping("/optionselect")
+    public AjaxResult optionselect()
+    {
+        List<Supplier> list = supplierService.selectAllSupplierList();
+        //去除被标记为黑名单的供应商
+        list = list.stream().filter(item -> "0".equals(item.getInBlacklist())).collect(Collectors.toList());
+        return success(list);
+    }
+
+    /**
+     * 导出供应商列表
+     */
+    // @PreAuthorize("@ss.hasPermi('app:supplier:export')")
+    @Log(title = "供应商", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, ReqSupplierList req)
+    {
+        List<Supplier> list = supplierService.selectSupplierList(req);
+        ExcelUtil<Supplier> util = new ExcelUtil<Supplier>(Supplier.class);
+        util.exportExcel(response, list, "供应商数据");
+    }
+
+    /**
+     * 获取供应商详细信息
+     */
+    @ApiOperation("获取供应商详细信息")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return success(supplierService.selectSupplierById(id));
+    }
+
+    /**
+     * 新增供应商
+     */
+    @ApiOperation("新增供应商")
+    // @PreAuthorize("@ss.hasPermi('app:supplier:add')")
+    @Log(title = "供应商", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody Supplier supplier)
+    {
+        if (ObjectUtil.isNotEmpty(supplier.getPhone())) {
+            if (!PhoneNumberValidator.isValidContact(supplier.getPhone())) {
+
+                return error("联系方式格式不正确!");
+            }
+        }
+        if (!supplierService.checkSupplierNameUnique(supplier))
+        {
+            return error("新增供应商'" + supplier.getSupplierName() + "'失败,列表中存在重复的供应商名称");
+        }
+//        if (!supplierService.checkSupplierNoUnique(supplier))
+//        {
+//            return error("新增供应商'" + supplier.getSupplierName() + "'失败,列表中存在重复的供应商编号");
+//        }
+        supplier.setCreateBy(getUsername());
+        return toAjax(supplierService.insertSupplier(supplier));
+    }
+
+    /**
+     * 修改供应商
+     */
+    @ApiOperation("修改供应商")
+    // @PreAuthorize("@ss.hasPermi('app:supplier:edit')")
+    @Log(title = "供应商", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody Supplier supplier)
+    {
+        if (ObjectUtil.isNotEmpty(supplier.getPhone())) {
+            if (!PhoneNumberValidator.isValidContact(supplier.getPhone())) {
+
+                return error("联系方式格式不正确!");
+            }
+        }
+        if (!supplierService.checkSupplierNameUnique(supplier))
+        {
+            return error("修改供应商'" + supplier.getSupplierName() + "'失败,列表中存在重复的供应商名称");
+        }
+        if (!supplierService.checkSupplierNoUnique(supplier))
+        {
+            return error("修改供应商'" + supplier.getSupplierName() + "'失败,列表中存在重复的供应商编号");
+        }
+        supplier.setUpdateBy(getUsername());
+        return toAjax(supplierService.updateSupplier(supplier));
+    }
+
+    /**
+     * 删除供应商
+     */
+    @ApiOperation("删除供应商")
+    // @PreAuthorize("@ss.hasPermi('app:supplier:remove')")
+    @Log(title = "供应商", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(supplierService.deleteSupplierByIds(ids));
+    }
+
+    @ApiOperation("查询自动生成的供应商编号")
+    @GetMapping("/nextNo")
+    public AjaxResult nextNo()
+    {
+        return success(new HashMap<String, String>() {{ put("nextNo", supplierService.getNextNo());}});
+    }
+
+
+}

+ 351 - 0
app-admin/src/main/java/com/ruoyi/web/controller/common/BottomRightWatermarkUtil.java

@@ -0,0 +1,351 @@
+package com.ruoyi.web.controller.common;
+
+import cn.hutool.core.img.ImgUtil;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.StrUtil;
+import lombok.Builder;
+import lombok.Data;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * 右下角水印工具类
+ */
+public class BottomRightWatermarkUtil {
+    
+    /**
+     * 添加右下角文字水印(基本版)
+//     */
+//    public static MultipartFile addBottomRightWatermark(
+//            MultipartFile originalFile,
+//            String watermarkText) throws IOException {
+//
+//        return addBottomRightWatermark(
+//            originalFile,
+//            watermarkText,
+//            BottomRightWatermarkConfig.defaultConfig()
+//        );
+//    }
+    
+    /**
+     * 添加右下角文字水印(完整配置)
+     */
+//    public static MultipartFile addBottomRightWatermark(
+//            MultipartFile originalFile,
+//            String watermarkText,
+//            BottomRightWatermarkConfig config) throws IOException {
+//
+//        // 读取图片
+//        BufferedImage image = ImageIO.read(originalFile.getInputStream());
+//        if (image == null) {
+//            throw new IllegalArgumentException("无法读取图片文件");
+//        }
+//
+//        // 添加右下角水印
+//        image = addMultiLineBottomRightWatermark(image, watermarkText, config);
+//
+//        // 转换为 MultipartFile
+//        return convertToMultipartFile(image, originalFile);
+//    }
+
+
+    
+    /**
+     * 多行右下角水印
+     */
+    public static BufferedImage addMultiLineBottomRightWatermark(
+            BufferedImage image,
+            String[] watermarkLines,
+            BottomRightWatermarkConfig config) {
+        
+        Graphics2D g2d = image.createGraphics();
+
+        // 设置字体和颜色
+        Font font = new Font(config.getFontName(), config.getFontStyle(), image.getHeight() / 15 );
+        g2d.setFont(font);
+        g2d.setColor(config.getColor());
+        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, config.getOpacity()));
+        
+        // 计算每行文字尺寸
+        FontMetrics fontMetrics = g2d.getFontMetrics();
+        int lineHeight = fontMetrics.getHeight();
+        int totalHeight = lineHeight * watermarkLines.length;
+        
+        // 计算最大行宽
+        int maxWidth = 0;
+        for (String line : watermarkLines) {
+            int lineWidth = fontMetrics.stringWidth(line);
+            if (lineWidth > maxWidth) {
+                maxWidth = lineWidth;
+            }
+        }
+        
+        // 从右下角开始绘制(从下往上)
+        int startY = image.getHeight() - config.getMarginY();
+        
+        for (int i = watermarkLines.length - 1; i >= 0; i--) {
+            String line = watermarkLines[i];
+            int lineWidth = fontMetrics.stringWidth(line);
+            int x = image.getWidth() - lineWidth - config.getMarginX();
+            int y = startY - (watermarkLines.length - 1 - i) * lineHeight;
+            
+            // 添加背景(可选)
+            if (config.isWithBackground()) {
+                Color originalColor = g2d.getColor();
+                g2d.setColor(config.getBackgroundColor());
+                int padding = config.getBackgroundPadding();
+                g2d.fillRect(x - padding, y - lineHeight + padding, 
+                           lineWidth + padding * 2, lineHeight);
+                g2d.setColor(originalColor);
+            }
+            
+            g2d.drawString(line, x, y);
+        }
+        
+        g2d.dispose();
+        return image;
+    }
+    
+
+    
+    /**
+     * 带 Logo 图标的右下角水印
+     */
+    public static MultipartFile addLogoTextWatermark(
+            MultipartFile originalFile,
+            MultipartFile logoFile,
+            String watermarkText) throws IOException {
+        
+        BufferedImage image = ImageIO.read(originalFile.getInputStream());
+        BufferedImage logo = ImageIO.read(logoFile.getInputStream());
+        
+        Graphics2D g2d = image.createGraphics();
+        
+        // 配置
+        BottomRightWatermarkConfig config = BottomRightWatermarkConfig.defaultConfig();
+        Font font = new Font(config.getFontName(), config.getFontStyle(), config.getFontSize());
+        g2d.setFont(font);
+        g2d.setColor(config.getColor());
+        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, config.getOpacity()));
+        
+        // 计算文字尺寸
+        FontMetrics fontMetrics = g2d.getFontMetrics();
+        int textWidth = fontMetrics.stringWidth(watermarkText);
+        int textHeight = fontMetrics.getHeight();
+        
+        // 调整 Logo 大小
+        int logoSize = textHeight;
+        BufferedImage resizedLogo = resizeImage(logo, logoSize, logoSize);
+        
+        // 计算位置(Logo + 文字)
+        int totalWidth = logoSize + 5 + textWidth; // 5px 间距
+        int x = image.getWidth() - totalWidth - config.getMarginX();
+        int y = image.getHeight() - config.getMarginY();
+        
+        // 绘制 Logo
+        g2d.drawImage(resizedLogo, x, y - logoSize + 5, null);
+        
+        // 绘制文字
+        g2d.drawString(watermarkText, x + logoSize + 5, y);
+        
+        g2d.dispose();
+        
+        return convertToMultipartFile(image, originalFile);
+    }
+    
+    /**
+     * 旋转角度的右下角水印(倾斜效果)
+     */
+    public static MultipartFile addRotatedWatermark(
+            MultipartFile originalFile,
+            String watermarkText,
+            double angle) throws IOException {
+        
+        BufferedImage image = ImageIO.read(originalFile.getInputStream());
+        
+        Graphics2D g2d = image.createGraphics();
+        
+        // 配置
+        BottomRightWatermarkConfig config = BottomRightWatermarkConfig.defaultConfig();
+        Font font = new Font(config.getFontName(), config.getFontStyle(), config.getFontSize());
+        g2d.setFont(font);
+        g2d.setColor(config.getColor());
+        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, config.getOpacity()));
+        
+        // 计算文字尺寸
+        FontMetrics fontMetrics = g2d.getFontMetrics();
+        int textWidth = fontMetrics.stringWidth(watermarkText);
+        int textHeight = fontMetrics.getHeight();
+        
+        // 计算旋转后的位置
+        int x = image.getWidth() - textWidth - config.getMarginX();
+        int y = image.getHeight() - config.getMarginY();
+        
+        // 旋转 Graphics2D
+        g2d.rotate(Math.toRadians(angle), x + textWidth / 2, y - textHeight / 2);
+        
+        // 绘制水印
+        g2d.drawString(watermarkText, x, y);
+        
+        g2d.dispose();
+        
+        return convertToMultipartFile(image, originalFile);
+    }
+    
+
+    /**
+     * 缩放图片
+     */
+    private static BufferedImage resizeImage(BufferedImage originalImage, 
+                                            int targetWidth, int targetHeight) {
+        
+        BufferedImage resizedImage = new BufferedImage(
+            targetWidth, targetHeight, BufferedImage.TYPE_INT_ARGB);
+        
+        Graphics2D g2d = resizedImage.createGraphics();
+        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, 
+            RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+        g2d.drawImage(originalImage, 0, 0, targetWidth, targetHeight, null);
+        g2d.dispose();
+        
+        return resizedImage;
+    }
+    
+    /**
+     * 转换为 MultipartFile
+     */
+    private static MultipartFile convertToMultipartFile(
+            BufferedImage image, 
+            MultipartFile originalFile) throws IOException {
+        
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        
+        // 获取原文件扩展名
+        String originalFilename = originalFile.getOriginalFilename();
+        String extension = getFileExtension(originalFilename);
+        String formatName = getFormatName(extension);
+        
+        // 写入图片
+        ImageIO.write(image, formatName, baos);
+        
+        // 创建内存 MultipartFile
+        return new InMemoryMultipartFile(
+            originalFile.getName(),
+            originalFilename,
+            originalFile.getContentType(),
+            baos.toByteArray()
+        );
+    }
+    
+    private static String getFileExtension(String filename) {
+        if (StrUtil.isBlank(filename) || !filename.contains(".")) {
+            return "jpg";
+        }
+        return filename.substring(filename.lastIndexOf(".") + 1).toLowerCase();
+    }
+    
+    private static String getFormatName(String extension) {
+        switch (extension.toLowerCase()) {
+            case "jpg":
+            case "jpeg":
+                return "JPEG";
+            case "png":
+                return "PNG";
+            case "gif":
+                return "GIF";
+            case "bmp":
+                return "BMP";
+            default:
+                return "JPEG";
+        }
+    }
+    
+    /**
+     * 右下角水印配置类
+     */
+    @Data
+    @Builder
+    public static class BottomRightWatermarkConfig {
+        @Builder.Default
+        private Color color = Color.WHITE;
+        
+        @Builder.Default
+        private String fontName = "WenQuanYi Zen Hei";
+        
+        @Builder.Default
+        private int fontStyle = Font.BOLD;
+        
+        @Builder.Default
+        private int fontSize = 24;
+        
+        @Builder.Default
+        private float opacity = 0.7f;
+        
+        @Builder.Default
+        private int marginX = 10;  // 右边距
+        
+        @Builder.Default
+        private int marginY = 10;  // 下边距
+        
+        @Builder.Default
+        private boolean withBackground = false;
+        
+        private Color backgroundColor;
+        
+        @Builder.Default
+        private int backgroundPadding = 5;
+        
+        @Builder.Default
+        private boolean roundedBackground = false;
+        
+        @Builder.Default
+        private int backgroundRadius = 5;
+        
+        @Builder.Default
+        private boolean withShadow = false;
+        
+        @Builder.Default
+        private Color shadowColor = Color.BLACK;
+        
+        @Builder.Default
+        private float shadowOpacity = 0.5f;
+        
+        @Builder.Default
+        private int shadowOffset = 2;
+        
+        public static BottomRightWatermarkConfig defaultConfig() {
+            return BottomRightWatermarkConfig.builder().build();
+        }
+        
+        public static BottomRightWatermarkConfig withBackgroundConfig() {
+            return BottomRightWatermarkConfig.builder()
+                .withBackground(true)
+                .backgroundColor(new Color(0, 0, 0, 100))
+                .build();
+        }
+        
+        public static BottomRightWatermarkConfig withShadowConfig() {
+            return BottomRightWatermarkConfig.builder()
+                .withShadow(true)
+                .color(Color.WHITE)
+                .shadowColor(Color.BLACK)
+                .build();
+        }
+        
+        public static BottomRightWatermarkConfig subtleConfig() {
+            return BottomRightWatermarkConfig.builder()
+                .color(new Color(255, 255, 255, 180)) // 半透明白色
+                .fontSize(18)
+                .opacity(0.5f)
+                .marginX(15)
+                .marginY(15)
+                .build();
+        }
+    }
+}

+ 94 - 0
app-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java

@@ -0,0 +1,94 @@
+package com.ruoyi.web.controller.common;
+
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Resource;
+import javax.imageio.ImageIO;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.FastByteArrayOutputStream;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.google.code.kaptcha.Producer;
+import com.ruoyi.common.config.RuoYiConfig;
+import com.ruoyi.common.constant.CacheConstants;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.redis.RedisCache;
+import com.ruoyi.common.utils.sign.Base64;
+import com.ruoyi.common.utils.uuid.IdUtils;
+import com.ruoyi.system.service.ISysConfigService;
+
+/**
+ * 验证码操作处理
+ * 
+ * @author ruoyi
+ */
+@RestController
+public class CaptchaController
+{
+    @Resource(name = "captchaProducer")
+    private Producer captchaProducer;
+
+    @Resource(name = "captchaProducerMath")
+    private Producer captchaProducerMath;
+
+    @Autowired
+    private RedisCache redisCache;
+    
+    @Autowired
+    private ISysConfigService configService;
+    /**
+     * 生成验证码
+     */
+    @GetMapping("/captchaImage")
+    public AjaxResult getCode(HttpServletResponse response) throws IOException
+    {
+        AjaxResult ajax = AjaxResult.success();
+        boolean captchaEnabled = configService.selectCaptchaEnabled();
+        ajax.put("captchaEnabled", captchaEnabled);
+        if (!captchaEnabled)
+        {
+            return ajax;
+        }
+
+        // 保存验证码信息
+        String uuid = IdUtils.simpleUUID();
+        String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid;
+
+        String capStr = null, code = null;
+        BufferedImage image = null;
+
+        // 生成验证码
+        String captchaType = RuoYiConfig.getCaptchaType();
+        if ("math".equals(captchaType))
+        {
+            String capText = captchaProducerMath.createText();
+            capStr = capText.substring(0, capText.lastIndexOf("@"));
+            code = capText.substring(capText.lastIndexOf("@") + 1);
+            image = captchaProducerMath.createImage(capStr);
+        }
+        else if ("char".equals(captchaType))
+        {
+            capStr = code = captchaProducer.createText();
+            image = captchaProducer.createImage(capStr);
+        }
+
+        redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
+        // 转换流信息写出
+        FastByteArrayOutputStream os = new FastByteArrayOutputStream();
+        try
+        {
+            ImageIO.write(image, "jpg", os);
+        }
+        catch (IOException e)
+        {
+            return AjaxResult.error(e.getMessage());
+        }
+
+        ajax.put("uuid", uuid);
+        ajax.put("img", Base64.encode(os.toByteArray()));
+        return ajax;
+    }
+}

+ 428 - 0
app-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java

@@ -0,0 +1,428 @@
+package com.ruoyi.web.controller.common;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.nio.file.Files;
+import java.util.*;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.imageio.ImageIO;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.img.Img;
+import cn.hutool.core.img.ImgUtil;
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson2.JSON;
+import com.ruoyi.app.service.IPicParseService;
+import com.ruoyi.common.PdfParse2Util;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.utils.PdfParseUtil;
+import com.ruoyi.common.utils.SecurityUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import com.ruoyi.common.config.RuoYiConfig;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.file.FileUploadUtils;
+import com.ruoyi.common.utils.file.FileUtils;
+import com.ruoyi.framework.config.ServerConfig;
+
+import technology.tabula.*;
+import technology.tabula.extractors.SpreadsheetExtractionAlgorithm;
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+/**
+ * 通用请求处理
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/common")
+public class CommonController
+{
+    private static final Logger log = LoggerFactory.getLogger(CommonController.class);
+
+    @Autowired
+    private ServerConfig serverConfig;
+
+    @Autowired
+    private IPicParseService picParseService;
+
+    private static final String FILE_DELIMETER = ",";
+
+    /**
+     * 通用下载请求
+     * 
+     * @param fileName 文件名称
+     * @param delete 是否删除
+     */
+    @GetMapping("/download")
+    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);
+            String filePath = RuoYiConfig.getDownloadPath() + fileName;
+
+            response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
+            FileUtils.setAttachmentResponseHeader(response, realFileName);
+            FileUtils.writeBytes(filePath, response.getOutputStream());
+            if (delete)
+            {
+                FileUtils.deleteFile(filePath);
+            }
+        }
+        catch (Exception e)
+        {
+            log.error("下载文件失败", e);
+        }
+    }
+    @PostMapping("/uploadA")
+    public AjaxResult uploadA(MultipartFile file,
+                                 @RequestParam(value = "waterText", required = false)int waterText ) throws Exception
+    {
+//        Map<String, Object>  detail = new HashMap<>();
+//        detail = PdfParseUtil.extractPdfContent(file);
+
+        Map<String, Object>  detail = picParseService.picParseA(file);
+
+        return AjaxResult.success(detail);
+    }
+
+    /**
+     * 通用上传请求(单个)
+     */
+    @PostMapping("/upload")
+    public AjaxResult uploadFile(MultipartFile file,
+                                 @RequestParam(value = "waterText", required = false)int waterText ) throws Exception
+    {
+        try
+        {
+
+            // 上传文件路径
+            String filePath = RuoYiConfig.getUploadPath();
+
+            // 上传并返回新文件名称
+            String fileName = null;
+            if (ObjectUtil.isNotEmpty(waterText) && waterText == 1){
+
+                String loginUser = SecurityUtils.getLoginUser().getUser().getNickName();
+                System.out.println(loginUser);
+                String format = DateUtil.format(new Date(), "yyyy/MM/dd/HH/mm");
+                List<String> list = Arrays.asList(loginUser, format);
+                BufferedImage image = javax.imageio.ImageIO.read(file.getInputStream());
+                BottomRightWatermarkUtil.BottomRightWatermarkConfig config =
+                        BottomRightWatermarkUtil.BottomRightWatermarkConfig.builder()
+                                .fontSize(12)
+                                .opacity(0.5f)
+                                .withBackground(true)
+                                .backgroundColor(new Color(0, 0, 0, 120))
+                                .build();
+                BufferedImage bufferedImage = BottomRightWatermarkUtil.addMultiLineBottomRightWatermark(image, list.toArray(new String[0]), config);
+                MultipartFile multipartFile = convertToMultipartFile(bufferedImage, file);
+
+                fileName =  FileUploadUtils.upload(filePath, multipartFile);
+                multipartFile.getInputStream().close();
+
+            }else {
+                fileName = FileUploadUtils.upload(filePath,  file);
+            }
+
+            String url = serverConfig.getUrl() + fileName;
+            AjaxResult ajax = AjaxResult.success();
+            ajax.put("url", url);
+            ajax.put("fileName", fileName);
+            ajax.put("newFileName", FileUtils.getName(fileName));
+            ajax.put("originalFilename", file.getOriginalFilename());
+
+            return ajax;
+        }
+        catch (Exception e)
+        {
+            return AjaxResult.error(e.getMessage());
+        }
+    }
+    private static MultipartFile convertToMultipartFile(
+            BufferedImage image,
+            MultipartFile originalFile) throws IOException {
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+        // 获取原文件扩展名
+        String originalFilename = originalFile.getOriginalFilename();
+        String extension = getFileExtension(originalFilename);
+        String formatName = getFormatName(extension);
+
+        // 写入图片
+        ImageIO.write(image, formatName, baos);
+
+        // 创建内存 MultipartFile
+        return new InMemoryMultipartFile(
+                originalFile.getName(),
+                originalFilename,
+                originalFile.getContentType(),
+                baos.toByteArray()
+        );
+    }
+    private static String getFormatName(String extension) {
+        switch (extension.toLowerCase()) {
+            case "jpg":
+            case "jpeg":
+                return "JPEG";
+            case "png":
+                return "PNG";
+            case "gif":
+                return "GIF";
+            case "bmp":
+                return "BMP";
+            default:
+                return "JPEG";
+        }
+    }
+
+    private static String getFileExtension(String filename) {
+        if (StrUtil.isBlank(filename) || !filename.contains(".")) {
+            return "jpg";
+        }
+        return filename.substring(filename.lastIndexOf(".") + 1).toLowerCase();
+    }
+
+
+    /**
+     * 上传PDF文件并解析
+     */
+    @PostMapping("/uploadAndParse")
+    public AjaxResult uploadAndParse(MultipartFile file ,
+                                     @RequestParam(value = "type", required = false) String type) throws Exception
+    {
+        //验证文件
+        if (file.isEmpty()) {
+            return AjaxResult.error("请上传文件");
+        }
+
+        try
+        {
+
+            if (type ==null || "0".equals(type)){
+                try {
+                    // 上传文件路径
+                    String filePath = RuoYiConfig.getUploadPath();
+//                 上传并返回新文件名称
+                    String fileName = FileUploadUtils.upload(filePath, file);
+                    String url = serverConfig.getUrl() + fileName;
+                    //解析
+                    Map<String, Object>  detail = new HashMap<>();
+                    if("application/pdf".equals(file.getContentType())){
+                        detail = PdfParseUtil.extractPdfContent(file);
+                    }else if(file.getContentType() != null && file.getContentType().startsWith("image/")){
+                        detail = picParseService.picParse(file);
+                    }else{
+                        return AjaxResult.error("不支持的文件格式");
+                    }
+                    AjaxResult ajax = AjaxResult.success();
+                    ajax.put("detail", detail);
+                    ajax.put("url", url);
+                    ajax.put("fileName", fileName);
+                    ajax.put("newFileName", FileUtils.getName(fileName));
+                    ajax.put("originalFilename", file.getOriginalFilename());
+                    return ajax;
+                }catch (Exception e){
+                    throw new Exception("检疫证识别失败,请手工填写");
+                }
+
+            }else {
+                // 上传文件路径
+                String filePath = RuoYiConfig.getUploadPath();
+                // 上传并返回新文件名称
+                String fileName = FileUploadUtils.upload(filePath, file);
+                String url = serverConfig.getUrl() + fileName;
+                //解析
+                Map<String, Object>  detail = new HashMap<>();
+
+//                 detail = picParseService.picParseA(file);
+                detail = PdfParse2Util.extractPdfContent(file);
+
+                AjaxResult ajax = AjaxResult.success();
+                ajax.put("detail", detail);
+                ajax.put("url", url);
+                ajax.put("fileName", fileName);
+                ajax.put("newFileName", FileUtils.getName(fileName));
+                ajax.put("originalFilename", file.getOriginalFilename());
+                return ajax;
+            }
+
+        }
+        catch (Exception e)
+        {
+            return AjaxResult.error(e.getMessage());
+        }
+    }
+
+
+
+
+        public static List<String> parseEarTags(String input) {
+            List<String> allTags = new ArrayList<>();
+            // 正则匹配三种模式:1.完整范围 2.单个耳标 3.多段范围
+            Pattern pattern = Pattern.compile("(\\d{15})(?:-(\\d{3}))?|(\\d{15})-(\\d{6})");
+            Matcher matcher = pattern.matcher(input);
+
+            while (matcher.find()) {
+                // 处理完整范围(如123012615932429-465)
+                if (matcher.group(1) != null && matcher.group(2) != null) {
+                    String base = matcher.group(1).substring(0, 12);
+                    int start = Integer.parseInt(matcher.group(1).substring(12));
+                    int end = Integer.parseInt(matcher.group(2));
+                    generateRange(base, start, end, allTags);
+                }
+                // 处理单个耳标(如233011223610519)
+                else if (matcher.group(3) != null) {
+                    allTags.add(matcher.group(3));
+                }
+                // 处理多段范围(如236215559302-557595-517)
+                else if (matcher.group(4) != null && matcher.group(5) != null) {
+                    String base = matcher.group(4).substring(0, 12);
+                    int start = Integer.parseInt(matcher.group(4).substring(12));
+                    int end = Integer.parseInt(matcher.group(5));
+                    generateRange(base, start, end, allTags);
+                }
+            }
+            return allTags;
+        }
+
+        private static void generateRange(String base, int start, int end, List<String> list) {
+            for (int i = start; i <= end; i++) {
+                list.add(base + String.format("%03d", i));
+            }
+        }
+
+    /**
+     * 上传图片并解析
+     */
+    @PostMapping("/uploadImgAndParse")
+    public AjaxResult uploadImgAndParse(MultipartFile file) throws Exception
+    {
+        //验证文件
+        if (file.isEmpty()) {
+            return AjaxResult.error("请上传图片");
+        }
+
+        // 方法三:判断是否是image类型(所有图片格式)
+        if (file.getContentType() == null || !file.getContentType().startsWith("image/")) {
+            return AjaxResult.error("仅支持图片文件");
+        }
+        try
+        {
+            // 上传文件路径
+            String filePath = RuoYiConfig.getUploadPath();
+
+            String fileName = FileUploadUtils.upload(filePath, file);
+            String url = serverConfig.getUrl() + fileName;
+            Map<String, Object>  detail = picParseService.picParse(file);
+            AjaxResult ajax = AjaxResult.success();
+            ajax.put("detail", detail);
+
+            ajax.put("url", url);
+            ajax.put("fileName", fileName);
+            ajax.put("newFileName", FileUtils.getName(fileName));
+            ajax.put("originalFilename", file.getOriginalFilename());
+            return ajax;
+        }
+        catch (Exception e)
+        {
+            return AjaxResult.error(e.getMessage());
+        }
+    }
+
+    /**
+     * 通用上传请求(多个)
+     */
+    @PostMapping("/uploads")
+    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)
+            {
+                // 上传并返回新文件名称
+                String fileName = FileUploadUtils.upload(filePath, file);
+                String url = serverConfig.getUrl() + fileName;
+                urls.add(url);
+                fileNames.add(fileName);
+                newFileNames.add(FileUtils.getName(fileName));
+                originalFilenames.add(file.getOriginalFilename());
+            }
+            AjaxResult ajax = AjaxResult.success();
+            ajax.put("urls", StringUtils.join(urls, FILE_DELIMETER));
+            ajax.put("fileNames", StringUtils.join(fileNames, FILE_DELIMETER));
+            ajax.put("newFileNames", StringUtils.join(newFileNames, FILE_DELIMETER));
+            ajax.put("originalFilenames", StringUtils.join(originalFilenames, FILE_DELIMETER));
+            return ajax;
+        }
+        catch (Exception e)
+        {
+            return AjaxResult.error(e.getMessage());
+        }
+    }
+
+    /**
+     * 本地资源通用下载
+     */
+    @GetMapping("/download/resource")
+    public void resourceDownload(String resource, HttpServletRequest request, HttpServletResponse response)
+            throws Exception
+    {
+        try
+        {
+            if (!FileUtils.checkAllowDownload(resource))
+            {
+                throw new Exception(StringUtils.format("资源文件({})非法,不允许下载。 ", resource));
+            }
+            // 本地资源路径
+            String localPath = RuoYiConfig.getProfile();
+            // 数据库资源地址
+            String downloadPath = localPath + StringUtils.substringAfter(resource, Constants.RESOURCE_PREFIX);
+            // 下载名称
+            String downloadName = StringUtils.substringAfterLast(downloadPath, "/");
+            response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
+            FileUtils.setAttachmentResponseHeader(response, downloadName);
+            FileUtils.writeBytes(downloadPath, response.getOutputStream());
+        }
+        catch (Exception e)
+        {
+            log.error("下载文件失败", e);
+        }
+    }
+
+    @PostMapping("/addBatch")
+    public ResponseEntity<Map<String, Object>> addBatch(@RequestBody Map<String, Object> params) {
+        System.out.println(JSON.toJSONString(params));
+        Map<String, Object> response = new HashMap<>();
+        response.put("resultCode", 0);
+        response.put("resultMsg", "success");
+        return ResponseEntity.ok(response);
+    }
+}

+ 98 - 0
app-admin/src/main/java/com/ruoyi/web/controller/common/FontLoader.java

@@ -0,0 +1,98 @@
+package com.ruoyi.web.controller.common;
+
+import java.awt.*;
+import java.io.File;
+import java.io.InputStream;
+
+public class FontLoader {
+    
+    /**
+     * 从资源文件加载字体
+     */
+    public static Font loadFontFromResource(String fontPath, int style, float size) {
+        try {
+            // 从 classpath 加载字体文件
+            InputStream is = FontLoader.class.getClassLoader().getResourceAsStream(fontPath);
+            if (is == null) {
+                // 如果资源中找不到,尝试从文件系统加载
+                return loadFontFromFile(fontPath, style, size);
+            }
+            
+            Font font = Font.createFont(Font.TRUETYPE_FONT, is);
+            return font.deriveFont(style, size);
+        } catch (Exception e) {
+            System.err.println("加载字体失败: " + e.getMessage());
+            // 返回默认字体
+            return new Font("SansSerif", style, (int)size);
+        }
+    }
+    
+    /**
+     * 从文件系统加载字体
+     */
+    public static Font loadFontFromFile(String fontPath, int style, float size) {
+        try {
+            File fontFile = new File(fontPath);
+            Font font = Font.createFont(Font.TRUETYPE_FONT, fontFile);
+            // 注册到 GraphicsEnvironment
+            GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
+            ge.registerFont(font);
+            return font.deriveFont(style, size);
+        } catch (Exception e) {
+            System.err.println("加载字体文件失败: " + e.getMessage());
+            return new Font("SansSerif", style, (int)size);
+        }
+    }
+    
+    /**
+     * 获取可用的中文字体
+     */
+    public static String[] getAvailableChineseFonts() {
+        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
+        String[] fontNames = ge.getAvailableFontFamilyNames();
+        
+        // 中文字体常见名称
+        String[] chineseFontKeywords = {
+            "宋体", "SimSun", "黑体", "SimHei", "微软雅黑", "Microsoft YaHei",
+            "楷体", "KaiTi", "仿宋", "FangSong", "苹方", "PingFang",
+            "文泉驿", "WenQuanYi", "Noto Sans CJK", "Source Han Sans"
+        };
+        
+        java.util.List<String> chineseFonts = new java.util.ArrayList<>();
+        for (String fontName : fontNames) {
+            for (String keyword : chineseFontKeywords) {
+                if (fontName.contains(keyword)) {
+                    chineseFonts.add(fontName);
+                    break;
+                }
+            }
+        }
+        
+        return chineseFonts.toArray(new String[0]);
+    }
+    
+    /**
+     * 获取最佳可用字体
+     */
+    public static String getBestAvailableFont() {
+        String[] chineseFonts = getAvailableChineseFonts();
+        if (chineseFonts.length > 0) {
+            return chineseFonts[0];
+        }
+        
+        // 检查系统字体
+        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
+        String[] allFonts = ge.getAvailableFontFamilyNames();
+        
+        // 尝试查找支持中文的字体
+        for (String font : allFonts) {
+            if (font.toLowerCase().contains("sans") || 
+                font.toLowerCase().contains("serif") ||
+                font.toLowerCase().contains("mono")) {
+                return font;
+            }
+        }
+        
+        return "SansSerif"; // 默认字体
+    }
+}

+ 72 - 0
app-admin/src/main/java/com/ruoyi/web/controller/common/InMemoryMultipartFile.java

@@ -0,0 +1,72 @@
+package com.ruoyi.web.controller.common;
+
+import org.springframework.web.multipart.MultipartFile;
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+/**
+ * 内存中的 MultipartFile 实现
+ */
+public class InMemoryMultipartFile implements MultipartFile {
+    
+    private final String name;
+    private final String originalFilename;
+    private final String contentType;
+    private final byte[] bytes;
+    
+    public InMemoryMultipartFile(String name, String originalFilename, 
+                                String contentType, byte[] bytes) {
+        this.name = name;
+        this.originalFilename = originalFilename;
+        this.contentType = contentType;
+        this.bytes = bytes != null ? bytes : new byte[0];
+    }
+    
+    @Override
+    public String getName() {
+        return name;
+    }
+    
+    @Override
+    public String getOriginalFilename() {
+        return originalFilename;
+    }
+    
+    @Override
+    public String getContentType() {
+        return contentType;
+    }
+    
+    @Override
+    public boolean isEmpty() {
+        return bytes.length == 0;
+    }
+    
+    @Override
+    public long getSize() {
+        return bytes.length;
+    }
+    
+    @Override
+    public byte[] getBytes() throws IOException {
+        return bytes;
+    }
+    
+    @Override
+    public InputStream getInputStream() throws IOException {
+        return new ByteArrayInputStream(bytes);
+    }
+    
+    @Override
+    public void transferTo(File dest) throws IOException, IllegalStateException {
+        try (FileOutputStream fos = new FileOutputStream(dest)) {
+            fos.write(bytes);
+        }
+    }
+    
+    @Override
+    public void transferTo(Path dest) throws IOException, IllegalStateException {
+        Files.write(dest, bytes);
+    }
+}

+ 46 - 0
app-admin/src/main/java/com/ruoyi/web/controller/common/MultipartConfig.java

@@ -0,0 +1,46 @@
+package com.ruoyi.web.controller.common;
+
+import org.springframework.boot.web.servlet.MultipartConfigFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.util.unit.DataSize;
+import org.springframework.web.multipart.MultipartResolver;
+import org.springframework.web.multipart.support.StandardServletMultipartResolver;
+
+import javax.servlet.MultipartConfigElement;
+
+@Configuration
+public class MultipartConfig {
+    
+    /**
+     * 配置 MultipartResolver
+     * 设置为不使用临时文件,直接在内存中处理
+     */
+    @Bean
+    public MultipartResolver multipartResolver() {
+        StandardServletMultipartResolver multipartResolver = 
+            new StandardServletMultipartResolver();
+        multipartResolver.setResolveLazily(true); // 延迟解析
+        return multipartResolver;
+    }
+    
+    /**
+     * 配置 Multipart 配置
+     */
+    @Bean
+    public MultipartConfigElement multipartConfigElement() {
+        MultipartConfigFactory factory = new MultipartConfigFactory();
+        
+        // 设置文件大小限制
+        factory.setMaxFileSize(DataSize.ofMegabytes(10));
+        factory.setMaxRequestSize(DataSize.ofMegabytes(50));
+        
+        // 关键配置:设置文件大小阈值,小于此值直接保存在内存
+        factory.setFileSizeThreshold(DataSize.ofMegabytes(2)); // 2MB以下的文件直接内存处理
+        
+        // 设置临时文件目录(可选,不设置则使用系统默认)
+        // factory.setLocation("自定义临时目录路径");
+        
+        return factory.createMultipartConfig();
+    }
+}

+ 121 - 0
app-admin/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java

@@ -0,0 +1,121 @@
+package com.ruoyi.web.controller.monitor;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisCallback;
+import org.springframework.data.redis.core.RedisTemplate;
+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.ruoyi.common.constant.CacheConstants;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.system.domain.SysCache;
+
+/**
+ * 缓存监控
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/monitor/cache")
+public class CacheController
+{
+    @Autowired
+    private RedisTemplate<String, String> redisTemplate;
+
+    private final static List<SysCache> caches = new ArrayList<SysCache>();
+    {
+        caches.add(new SysCache(CacheConstants.LOGIN_TOKEN_KEY, "用户信息"));
+        caches.add(new SysCache(CacheConstants.SYS_CONFIG_KEY, "配置信息"));
+        caches.add(new SysCache(CacheConstants.SYS_DICT_KEY, "数据字典"));
+        caches.add(new SysCache(CacheConstants.CAPTCHA_CODE_KEY, "验证码"));
+        caches.add(new SysCache(CacheConstants.REPEAT_SUBMIT_KEY, "防重提交"));
+        caches.add(new SysCache(CacheConstants.RATE_LIMIT_KEY, "限流处理"));
+        caches.add(new SysCache(CacheConstants.PWD_ERR_CNT_KEY, "密码错误次数"));
+    }
+
+    // @PreAuthorize("@ss.hasPermi('monitor:cache:list')")
+    @GetMapping()
+    public AjaxResult getInfo() throws Exception
+    {
+        Properties info = (Properties) redisTemplate.execute((RedisCallback<Object>) connection -> connection.info());
+        Properties commandStats = (Properties) redisTemplate.execute((RedisCallback<Object>) connection -> connection.info("commandstats"));
+        Object dbSize = redisTemplate.execute((RedisCallback<Object>) connection -> connection.dbSize());
+
+        Map<String, Object> result = new HashMap<>(3);
+        result.put("info", info);
+        result.put("dbSize", dbSize);
+
+        List<Map<String, String>> pieList = new ArrayList<>();
+        commandStats.stringPropertyNames().forEach(key -> {
+            Map<String, String> data = new HashMap<>(2);
+            String property = commandStats.getProperty(key);
+            data.put("name", StringUtils.removeStart(key, "cmdstat_"));
+            data.put("value", StringUtils.substringBetween(property, "calls=", ",usec"));
+            pieList.add(data);
+        });
+        result.put("commandStats", pieList);
+        return AjaxResult.success(result);
+    }
+
+    // @PreAuthorize("@ss.hasPermi('monitor:cache:list')")
+    @GetMapping("/getNames")
+    public AjaxResult cache()
+    {
+        return AjaxResult.success(caches);
+    }
+
+    // @PreAuthorize("@ss.hasPermi('monitor:cache:list')")
+    @GetMapping("/getKeys/{cacheName}")
+    public AjaxResult getCacheKeys(@PathVariable String cacheName)
+    {
+        Set<String> cacheKeys = redisTemplate.keys(cacheName + "*");
+        return AjaxResult.success(new TreeSet<>(cacheKeys));
+    }
+
+    // @PreAuthorize("@ss.hasPermi('monitor:cache:list')")
+    @GetMapping("/getValue/{cacheName}/{cacheKey}")
+    public AjaxResult getCacheValue(@PathVariable String cacheName, @PathVariable String cacheKey)
+    {
+        String cacheValue = redisTemplate.opsForValue().get(cacheKey);
+        SysCache sysCache = new SysCache(cacheName, cacheKey, cacheValue);
+        return AjaxResult.success(sysCache);
+    }
+
+    // @PreAuthorize("@ss.hasPermi('monitor:cache:list')")
+    @DeleteMapping("/clearCacheName/{cacheName}")
+    public AjaxResult clearCacheName(@PathVariable String cacheName)
+    {
+        Collection<String> cacheKeys = redisTemplate.keys(cacheName + "*");
+        redisTemplate.delete(cacheKeys);
+        return AjaxResult.success();
+    }
+
+    // @PreAuthorize("@ss.hasPermi('monitor:cache:list')")
+    @DeleteMapping("/clearCacheKey/{cacheKey}")
+    public AjaxResult clearCacheKey(@PathVariable String cacheKey)
+    {
+        redisTemplate.delete(cacheKey);
+        return AjaxResult.success();
+    }
+
+    // @PreAuthorize("@ss.hasPermi('monitor:cache:list')")
+    @DeleteMapping("/clearCacheAll")
+    public AjaxResult clearCacheAll()
+    {
+        Collection<String> cacheKeys = redisTemplate.keys("*");
+        redisTemplate.delete(cacheKeys);
+        return AjaxResult.success();
+    }
+}

+ 27 - 0
app-admin/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java

@@ -0,0 +1,27 @@
+package com.ruoyi.web.controller.monitor;
+
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.framework.web.domain.Server;
+
+/**
+ * 服务器监控
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/monitor/server")
+public class ServerController
+{
+    // @PreAuthorize("@ss.hasPermi('monitor:server:list')")
+    @GetMapping()
+    public AjaxResult getInfo() throws Exception
+    {
+        Server server = new Server();
+        server.copyTo();
+        return AjaxResult.success(server);
+    }
+}

+ 82 - 0
app-admin/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java

@@ -0,0 +1,82 @@
+package com.ruoyi.web.controller.monitor;
+
+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.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.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.framework.web.service.SysPasswordService;
+import com.ruoyi.system.domain.SysLogininfor;
+import com.ruoyi.system.service.ISysLogininforService;
+
+/**
+ * 系统访问记录
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/monitor/logininfor")
+public class SysLogininforController extends BaseController
+{
+    @Autowired
+    private ISysLogininforService logininforService;
+
+    @Autowired
+    private SysPasswordService passwordService;
+
+    // @PreAuthorize("@ss.hasPermi('monitor:logininfor:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(SysLogininfor logininfor)
+    {
+        startPage();
+        List<SysLogininfor> list = logininforService.selectLogininforList(logininfor);
+        return getDataTable(list);
+    }
+
+    @Log(title = "登录日志", businessType = BusinessType.EXPORT)
+    // @PreAuthorize("@ss.hasPermi('monitor:logininfor:export')")
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, SysLogininfor logininfor)
+    {
+        List<SysLogininfor> list = logininforService.selectLogininforList(logininfor);
+        ExcelUtil<SysLogininfor> util = new ExcelUtil<SysLogininfor>(SysLogininfor.class);
+        util.exportExcel(response, list, "登录日志");
+    }
+
+    // @PreAuthorize("@ss.hasPermi('monitor:logininfor:remove')")
+    @Log(title = "登录日志", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{infoIds}")
+    public AjaxResult remove(@PathVariable Long[] infoIds)
+    {
+        return toAjax(logininforService.deleteLogininforByIds(infoIds));
+    }
+
+    // @PreAuthorize("@ss.hasPermi('monitor:logininfor:remove')")
+    @Log(title = "登录日志", businessType = BusinessType.CLEAN)
+    @DeleteMapping("/clean")
+    public AjaxResult clean()
+    {
+        logininforService.cleanLogininfor();
+        return success();
+    }
+
+    // @PreAuthorize("@ss.hasPermi('monitor:logininfor:unlock')")
+    @Log(title = "账户解锁", businessType = BusinessType.OTHER)
+    @GetMapping("/unlock/{userName}")
+    public AjaxResult unlock(@PathVariable("userName") String userName)
+    {
+        passwordService.clearLoginRecordCache(userName);
+        return success();
+    }
+}

+ 69 - 0
app-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java

@@ -0,0 +1,69 @@
+package com.ruoyi.web.controller.monitor;
+
+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.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.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.domain.SysOperLog;
+import com.ruoyi.system.service.ISysOperLogService;
+
+/**
+ * 操作日志记录
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/monitor/operlog")
+public class SysOperlogController extends BaseController
+{
+    @Autowired
+    private ISysOperLogService operLogService;
+
+    // @PreAuthorize("@ss.hasPermi('monitor:operlog:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(SysOperLog operLog)
+    {
+        startPage();
+        List<SysOperLog> list = operLogService.selectOperLogList(operLog);
+        return getDataTable(list);
+    }
+
+    @Log(title = "操作日志", businessType = BusinessType.EXPORT)
+    // @PreAuthorize("@ss.hasPermi('monitor:operlog:export')")
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, SysOperLog operLog)
+    {
+        List<SysOperLog> list = operLogService.selectOperLogList(operLog);
+        ExcelUtil<SysOperLog> util = new ExcelUtil<SysOperLog>(SysOperLog.class);
+        util.exportExcel(response, list, "操作日志");
+    }
+
+    @Log(title = "操作日志", businessType = BusinessType.DELETE)
+    // @PreAuthorize("@ss.hasPermi('monitor:operlog:remove')")
+    @DeleteMapping("/{operIds}")
+    public AjaxResult remove(@PathVariable Long[] operIds)
+    {
+        return toAjax(operLogService.deleteOperLogByIds(operIds));
+    }
+
+    @Log(title = "操作日志", businessType = BusinessType.CLEAN)
+    // @PreAuthorize("@ss.hasPermi('monitor:operlog:remove')")
+    @DeleteMapping("/clean")
+    public AjaxResult clean()
+    {
+        operLogService.cleanOperLog();
+        return success();
+    }
+}

+ 83 - 0
app-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java

@@ -0,0 +1,83 @@
+package com.ruoyi.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.ruoyi.common.annotation.Log;
+import com.ruoyi.common.constant.CacheConstants;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.core.redis.RedisCache;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.system.domain.SysUserOnline;
+import com.ruoyi.system.service.ISysUserOnlineService;
+
+/**
+ * 在线用户监控
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/monitor/online")
+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(CacheConstants.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))
+            {
+                userOnlineList.add(userOnlineService.selectOnlineByInfo(ipaddr, userName, user));
+            }
+            else if (StringUtils.isNotEmpty(ipaddr))
+            {
+                userOnlineList.add(userOnlineService.selectOnlineByIpaddr(ipaddr, user));
+            }
+            else if (StringUtils.isNotEmpty(userName) && StringUtils.isNotNull(user.getUser()))
+            {
+                userOnlineList.add(userOnlineService.selectOnlineByUserName(userName, user));
+            }
+            else
+            {
+                userOnlineList.add(userOnlineService.loginUserToUserOnline(user));
+            }
+        }
+        Collections.reverse(userOnlineList);
+        userOnlineList.removeAll(Collections.singleton(null));
+        return getDataTable(userOnlineList);
+    }
+
+    /**
+     * 强退用户
+     */
+    // @PreAuthorize("@ss.hasPermi('monitor:online:forceLogout')")
+    @Log(title = "在线用户", businessType = BusinessType.FORCE)
+    @DeleteMapping("/{tokenId}")
+    public AjaxResult forceLogout(@PathVariable String tokenId)
+    {
+        redisCache.deleteObject(CacheConstants.LOGIN_TOKEN_KEY + tokenId);
+        return success();
+    }
+}

+ 133 - 0
app-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java

@@ -0,0 +1,133 @@
+package com.ruoyi.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 com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.domain.SysConfig;
+import com.ruoyi.system.service.ISysConfigService;
+
+/**
+ * 参数配置 信息操作处理
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/system/config")
+public class SysConfigController extends BaseController
+{
+    @Autowired
+    private ISysConfigService configService;
+
+    /**
+     * 获取参数配置列表
+     */
+    // @PreAuthorize("@ss.hasPermi('system:config:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(SysConfig config)
+    {
+        startPage();
+        List<SysConfig> list = configService.selectConfigList(config);
+        return getDataTable(list);
+    }
+
+    @Log(title = "参数管理", businessType = BusinessType.EXPORT)
+    // @PreAuthorize("@ss.hasPermi('system:config:export')")
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, SysConfig config)
+    {
+        List<SysConfig> list = configService.selectConfigList(config);
+        ExcelUtil<SysConfig> util = new ExcelUtil<SysConfig>(SysConfig.class);
+        util.exportExcel(response, list, "参数数据");
+    }
+
+    /**
+     * 根据参数编号获取详细信息
+     */
+    // @PreAuthorize("@ss.hasPermi('system:config:query')")
+    @GetMapping(value = "/{configId}")
+    public AjaxResult getInfo(@PathVariable Long configId)
+    {
+        return success(configService.selectConfigById(configId));
+    }
+
+    /**
+     * 根据参数键名查询参数值
+     */
+    @GetMapping(value = "/configKey/{configKey}")
+    public AjaxResult getConfigKey(@PathVariable String configKey)
+    {
+        return success(configService.selectConfigByKey(configKey));
+    }
+
+    /**
+     * 新增参数配置
+     */
+    // @PreAuthorize("@ss.hasPermi('system:config:add')")
+    @Log(title = "参数管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody SysConfig config)
+    {
+        if (!configService.checkConfigKeyUnique(config))
+        {
+            return error("新增参数'" + config.getConfigName() + "'失败,参数键名已存在");
+        }
+        config.setCreateBy(getUsername());
+        return toAjax(configService.insertConfig(config));
+    }
+
+    /**
+     * 修改参数配置
+     */
+    // @PreAuthorize("@ss.hasPermi('system:config:edit')")
+    @Log(title = "参数管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody SysConfig config)
+    {
+        if (!configService.checkConfigKeyUnique(config))
+        {
+            return error("修改参数'" + config.getConfigName() + "'失败,参数键名已存在");
+        }
+        config.setUpdateBy(getUsername());
+        return toAjax(configService.updateConfig(config));
+    }
+
+    /**
+     * 删除参数配置
+     */
+    // @PreAuthorize("@ss.hasPermi('system:config:remove')")
+    @Log(title = "参数管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{configIds}")
+    public AjaxResult remove(@PathVariable Long[] configIds)
+    {
+        configService.deleteConfigByIds(configIds);
+        return success();
+    }
+
+    /**
+     * 刷新参数缓存
+     */
+    // @PreAuthorize("@ss.hasPermi('system:config:remove')")
+    @Log(title = "参数管理", businessType = BusinessType.CLEAN)
+    @DeleteMapping("/refreshCache")
+    public AjaxResult refreshCache()
+    {
+        configService.resetConfigCache();
+        return success();
+    }
+}

+ 132 - 0
app-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java

@@ -0,0 +1,132 @@
+package com.ruoyi.web.controller.system;
+
+import java.util.List;
+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 com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.domain.entity.SysDept;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.system.service.ISysDeptService;
+
+/**
+ * 部门信息
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/system/dept")
+public class SysDeptController extends BaseController
+{
+    @Autowired
+    private ISysDeptService deptService;
+
+    /**
+     * 获取部门列表
+     */
+    // @PreAuthorize("@ss.hasPermi('system:dept:list')")
+    @GetMapping("/list")
+    public AjaxResult list(SysDept dept)
+    {
+        List<SysDept> depts = deptService.selectDeptList(dept);
+        return success(depts);
+    }
+
+    /**
+     * 查询部门列表(排除节点)
+     */
+    // @PreAuthorize("@ss.hasPermi('system:dept:list')")
+    @GetMapping("/list/exclude/{deptId}")
+    public AjaxResult excludeChild(@PathVariable(value = "deptId", required = false) Long deptId)
+    {
+        List<SysDept> depts = deptService.selectDeptList(new SysDept());
+        depts.removeIf(d -> d.getDeptId().intValue() == deptId || ArrayUtils.contains(StringUtils.split(d.getAncestors(), ","), deptId + ""));
+        return success(depts);
+    }
+
+    /**
+     * 根据部门编号获取详细信息
+     */
+    // @PreAuthorize("@ss.hasPermi('system:dept:query')")
+    @GetMapping(value = "/{deptId}")
+    public AjaxResult getInfo(@PathVariable Long deptId)
+    {
+        deptService.checkDeptDataScope(deptId);
+        return success(deptService.selectDeptById(deptId));
+    }
+
+    /**
+     * 新增部门
+     */
+    // @PreAuthorize("@ss.hasPermi('system:dept:add')")
+    @Log(title = "部门管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody SysDept dept)
+    {
+        if (!deptService.checkDeptNameUnique(dept))
+        {
+            return error("新增部门'" + dept.getDeptName() + "'失败,部门名称已存在");
+        }
+        dept.setCreateBy(getUsername());
+        return toAjax(deptService.insertDept(dept));
+    }
+
+    /**
+     * 修改部门
+     */
+    // @PreAuthorize("@ss.hasPermi('system:dept:edit')")
+    @Log(title = "部门管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody SysDept dept)
+    {
+        Long deptId = dept.getDeptId();
+        deptService.checkDeptDataScope(deptId);
+        if (!deptService.checkDeptNameUnique(dept))
+        {
+            return error("修改部门'" + dept.getDeptName() + "'失败,部门名称已存在");
+        }
+        else if (dept.getParentId().equals(deptId))
+        {
+            return error("修改部门'" + dept.getDeptName() + "'失败,上级部门不能是自己");
+        }
+        else if (StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus()) && deptService.selectNormalChildrenDeptById(deptId) > 0)
+        {
+            return error("该部门包含未停用的子部门!");
+        }
+        dept.setUpdateBy(getUsername());
+        return toAjax(deptService.updateDept(dept));
+    }
+
+    /**
+     * 删除部门
+     */
+    // @PreAuthorize("@ss.hasPermi('system:dept:remove')")
+    @Log(title = "部门管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{deptId}")
+    public AjaxResult remove(@PathVariable Long deptId)
+    {
+        if (deptService.hasChildByDeptId(deptId))
+        {
+            return warn("存在下级部门,不允许删除");
+        }
+        if (deptService.checkDeptExistUser(deptId))
+        {
+            return warn("部门存在用户,不允许删除");
+        }
+        deptService.checkDeptDataScope(deptId);
+        return toAjax(deptService.deleteDeptById(deptId));
+    }
+}

+ 121 - 0
app-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java

@@ -0,0 +1,121 @@
+package com.ruoyi.web.controller.system;
+
+import java.util.ArrayList;
+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 com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.domain.entity.SysDictData;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.service.ISysDictDataService;
+import com.ruoyi.system.service.ISysDictTypeService;
+
+/**
+ * 数据字典信息
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/system/dict/data")
+public class SysDictDataController extends BaseController
+{
+    @Autowired
+    private ISysDictDataService dictDataService;
+
+    @Autowired
+    private ISysDictTypeService dictTypeService;
+
+    // @PreAuthorize("@ss.hasPermi('system:dict:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(SysDictData dictData)
+    {
+        startPage();
+        List<SysDictData> list = dictDataService.selectDictDataList(dictData);
+        return getDataTable(list);
+    }
+
+    @Log(title = "字典数据", businessType = BusinessType.EXPORT)
+    // @PreAuthorize("@ss.hasPermi('system:dict:export')")
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, SysDictData dictData)
+    {
+        List<SysDictData> list = dictDataService.selectDictDataList(dictData);
+        ExcelUtil<SysDictData> util = new ExcelUtil<SysDictData>(SysDictData.class);
+        util.exportExcel(response, list, "字典数据");
+    }
+
+    /**
+     * 查询字典数据详细
+     */
+    // @PreAuthorize("@ss.hasPermi('system:dict:query')")
+    @GetMapping(value = "/{dictCode}")
+    public AjaxResult getInfo(@PathVariable Long dictCode)
+    {
+        return success(dictDataService.selectDictDataById(dictCode));
+    }
+
+    /**
+     * 根据字典类型查询字典数据信息
+     */
+    @GetMapping(value = "/type/{dictType}")
+    public AjaxResult dictType(@PathVariable String dictType)
+    {
+        List<SysDictData> data = dictTypeService.selectDictDataByType(dictType);
+        if (StringUtils.isNull(data))
+        {
+            data = new ArrayList<SysDictData>();
+        }
+        return success(data);
+    }
+
+    /**
+     * 新增字典类型
+     */
+    // @PreAuthorize("@ss.hasPermi('system:dict:add')")
+    @Log(title = "字典数据", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody SysDictData dict)
+    {
+        dict.setCreateBy(getUsername());
+        return toAjax(dictDataService.insertDictData(dict));
+    }
+
+    /**
+     * 修改保存字典类型
+     */
+    // @PreAuthorize("@ss.hasPermi('system:dict:edit')")
+    @Log(title = "字典数据", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody SysDictData dict)
+    {
+        dict.setUpdateBy(getUsername());
+        return toAjax(dictDataService.updateDictData(dict));
+    }
+
+    /**
+     * 删除字典类型
+     */
+    // @PreAuthorize("@ss.hasPermi('system:dict:remove')")
+    @Log(title = "字典类型", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{dictCodes}")
+    public AjaxResult remove(@PathVariable Long[] dictCodes)
+    {
+        dictDataService.deleteDictDataByIds(dictCodes);
+        return success();
+    }
+}

+ 131 - 0
app-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java

@@ -0,0 +1,131 @@
+package com.ruoyi.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 com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.domain.entity.SysDictType;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.service.ISysDictTypeService;
+
+/**
+ * 数据字典信息
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/system/dict/type")
+public class SysDictTypeController extends BaseController
+{
+    @Autowired
+    private ISysDictTypeService dictTypeService;
+
+    // @PreAuthorize("@ss.hasPermi('system:dict:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(SysDictType dictType)
+    {
+        startPage();
+        List<SysDictType> list = dictTypeService.selectDictTypeList(dictType);
+        return getDataTable(list);
+    }
+
+    @Log(title = "字典类型", businessType = BusinessType.EXPORT)
+    // @PreAuthorize("@ss.hasPermi('system:dict:export')")
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, SysDictType dictType)
+    {
+        List<SysDictType> list = dictTypeService.selectDictTypeList(dictType);
+        ExcelUtil<SysDictType> util = new ExcelUtil<SysDictType>(SysDictType.class);
+        util.exportExcel(response, list, "字典类型");
+    }
+
+    /**
+     * 查询字典类型详细
+     */
+    // @PreAuthorize("@ss.hasPermi('system:dict:query')")
+    @GetMapping(value = "/{dictId}")
+    public AjaxResult getInfo(@PathVariable Long dictId)
+    {
+        return success(dictTypeService.selectDictTypeById(dictId));
+    }
+
+    /**
+     * 新增字典类型
+     */
+    // @PreAuthorize("@ss.hasPermi('system:dict:add')")
+    @Log(title = "字典类型", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody SysDictType dict)
+    {
+        if (!dictTypeService.checkDictTypeUnique(dict))
+        {
+            return error("新增字典'" + dict.getDictName() + "'失败,字典类型已存在");
+        }
+        dict.setCreateBy(getUsername());
+        return toAjax(dictTypeService.insertDictType(dict));
+    }
+
+    /**
+     * 修改字典类型
+     */
+    // @PreAuthorize("@ss.hasPermi('system:dict:edit')")
+    @Log(title = "字典类型", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody SysDictType dict)
+    {
+        if (!dictTypeService.checkDictTypeUnique(dict))
+        {
+            return error("修改字典'" + dict.getDictName() + "'失败,字典类型已存在");
+        }
+        dict.setUpdateBy(getUsername());
+        return toAjax(dictTypeService.updateDictType(dict));
+    }
+
+    /**
+     * 删除字典类型
+     */
+    // @PreAuthorize("@ss.hasPermi('system:dict:remove')")
+    @Log(title = "字典类型", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{dictIds}")
+    public AjaxResult remove(@PathVariable Long[] dictIds)
+    {
+        dictTypeService.deleteDictTypeByIds(dictIds);
+        return success();
+    }
+
+    /**
+     * 刷新字典缓存
+     */
+    // @PreAuthorize("@ss.hasPermi('system:dict:remove')")
+    @Log(title = "字典类型", businessType = BusinessType.CLEAN)
+    @DeleteMapping("/refreshCache")
+    public AjaxResult refreshCache()
+    {
+        dictTypeService.resetDictCache();
+        return success();
+    }
+
+    /**
+     * 获取字典选择框列表
+     */
+    @GetMapping("/optionselect")
+    public AjaxResult optionselect()
+    {
+        List<SysDictType> dictTypes = dictTypeService.selectDictTypeAll();
+        return success(dictTypes);
+    }
+}

+ 29 - 0
app-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java

@@ -0,0 +1,29 @@
+package com.ruoyi.web.controller.system;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.config.RuoYiConfig;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * 首页
+ *
+ * @author ruoyi
+ */
+@RestController
+public class SysIndexController
+{
+    /** 系统基础配置 */
+    @Autowired
+    private RuoYiConfig ruoyiConfig;
+
+    /**
+     * 访问首页,提示语
+     */
+    @RequestMapping("/")
+    public String index()
+    {
+        return StringUtils.format("欢迎使用{}后台管理框架,当前版本:v{},请通过前端地址访问。", ruoyiConfig.getName(), ruoyiConfig.getVersion());
+    }
+}

+ 173 - 0
app-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java

@@ -0,0 +1,173 @@
+package com.ruoyi.web.controller.system;
+
+import java.sql.SQLException;
+import java.util.List;
+import java.util.Set;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.ruoyi.framework.datasource.DynamicDataSource;
+import com.ruoyi.framework.datasource.DynamicDataSourceContextHolder;
+import com.ruoyi.framework.datasource.DynamicDataSourceService;
+import com.ruoyi.web.primary.entity.DatabaseMetadata;
+import com.ruoyi.web.primary.service.IDatabaseMetadataService;
+import com.ruoyi.web.v2.v1.entity.UserAccount;
+import com.ruoyi.web.v2.v1.service.IUserAccountService;
+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.ruoyi.common.constant.Constants;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.domain.entity.SysMenu;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.core.domain.model.LoginBody;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.framework.web.service.SysLoginService;
+import com.ruoyi.framework.web.service.SysPermissionService;
+import com.ruoyi.framework.web.service.TokenService;
+import com.ruoyi.system.service.ISysMenuService;
+
+import javax.sql.DataSource;
+
+/**
+ * 登录验证
+ * 
+ * @author ruoyi
+ */
+@RestController
+public class SysLoginController
+{
+    @Autowired
+    private SysLoginService loginService;
+
+    @Autowired
+    private ISysMenuService menuService;
+
+    @Autowired
+    private SysPermissionService permissionService;
+
+    @Autowired
+    private TokenService tokenService;
+    @Autowired
+    private IDatabaseMetadataService metadataService;
+    /**
+     * 登录方法
+     * 
+     * @param loginBody 登录信息
+     * @return 结果
+     */
+    @PostMapping("/login")
+    public AjaxResult login(@RequestBody LoginBody loginBody) throws Exception {
+        AjaxResult ajax = AjaxResult.success();
+        // 生成令牌
+//        String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),
+//                loginBody.getUuid());
+
+//        DatabaseMetadata databaseMetadata = databaseMetadataService.selectByName(loginBody.getDbName());
+        DatabaseMetadata databaseMetadata = databaseMetadataService.selectByAccountId(loginBody.getUserId());
+        if (ObjectUtil.isEmpty(databaseMetadata)){
+            throw new Exception("账号不存在");
+        }
+        UserAccount userAccount = userAccountService.getOne(new QueryWrapper<UserAccount>().eq("user_id", databaseMetadata.getUserId()));
+        //根据现场需求,去除验证码
+        swichDb(databaseMetadata.getDbName());
+        String token = loginService.loginNoCaptcha(loginBody.getUsername(), loginBody.getPassword(), databaseMetadata.getDbName()
+                ,userAccount.getUserId(),userAccount.getAccountBalance(),userAccount.getExperienceBalance(),
+                userAccount.getAnimalType() ,databaseMetadata.getVersionName(),databaseMetadata.getVersion());
+
+        ajax.put(Constants.TOKEN, token);
+
+        return ajax;
+    }
+    @Autowired
+    private DynamicDataSourceService dynamicDataSourceService;
+    @Autowired
+    private IDatabaseMetadataService databaseMetadataService;
+    @Autowired
+    private IUserAccountService userAccountService;
+    /**
+     * 无验证码登录方法
+     *
+     * @param loginBody 登录信息
+     * @return 结果
+     */
+    @PostMapping("/loginNoCaptcha")
+    public AjaxResult loginNoCaptcha(@RequestBody LoginBody loginBody ) throws Exception {
+
+        DatabaseMetadata databaseMetadata = databaseMetadataService.selectByAccountId(loginBody.getUserId());
+        UserAccount userAccount = userAccountService.getOne(new QueryWrapper<UserAccount>().eq("user_id", databaseMetadata.getUserId()));
+
+        AjaxResult ajax = AjaxResult.success();
+        swichDb(databaseMetadata.getDbName());
+        // 生成令牌
+        String token = loginService.loginNoCaptcha(loginBody.getUsername(), loginBody.getPassword(),databaseMetadata.getDbName()
+                ,userAccount.getUserId(),userAccount.getAccountBalance(),userAccount.getExperienceBalance(),
+                userAccount.getAnimalType(),databaseMetadata.getVersionName(),databaseMetadata.getVersion() );
+
+
+        ajax.put(Constants.TOKEN, token);
+        return ajax;
+    }
+
+    private void swichDb(String  dbName) throws Exception {
+        DatabaseMetadata databaseMetadata = metadataService.selectByName(dbName);
+
+        if (databaseMetadata == null || databaseMetadata.getDbStatus() ==0 ) {
+            throw new Exception("数据库不存在或已禁用");
+        }
+        if (!dynamicDataSourceService.switchDataSource(dbName)) {
+
+            throw new Exception("无法连接到用户数据库");
+        }
+    }
+
+    /**
+     * 获取用户信息
+     * 
+     * @return 用户信息
+     */
+    @GetMapping("getInfo")
+    public AjaxResult getInfo()
+    {
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        SysUser user = loginUser.getUser();
+        // 角色集合
+        Set<String> roles = permissionService.getRolePermission(user);
+        // 权限集合
+        Set<String> permissions = permissionService.getMenuPermission(user);
+        if (!loginUser.getPermissions().equals(permissions))
+        {
+            loginUser.setPermissions(permissions);
+            tokenService.refreshToken(loginUser);
+        }
+
+        user.setAccountId(loginUser.getAccountId());
+        user.setAccountBalance(loginUser.getAccountBalance());
+        user.setExperienceBalance(loginUser.getExperienceBalance());
+        user.setAnimalType(loginUser.getAnimalType());
+
+        AjaxResult ajax = AjaxResult.success();
+        ajax.put("user", user);
+        ajax.put("version", loginUser.getVersion());
+        ajax.put("versionName", loginUser.getVersionName());
+        ajax.put("roles", roles);
+        ajax.put("permissions", permissions);
+        return ajax;
+    }
+
+    /**
+     * 获取路由信息
+     * 
+     * @return 路由信息
+     */
+    @GetMapping("getRouters")
+    public AjaxResult getRouters()
+    {
+        Long userId = SecurityUtils.getUserId();
+        List<SysMenu> menus = menuService.selectMenuTreeByUserId(userId);
+        return AjaxResult.success(menuService.buildMenus(menus));
+    }
+}

+ 142 - 0
app-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java

@@ -0,0 +1,142 @@
+package com.ruoyi.web.controller.system;
+
+import java.util.List;
+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 com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.domain.entity.SysMenu;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.system.service.ISysMenuService;
+
+/**
+ * 菜单信息
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/system/menu")
+public class SysMenuController extends BaseController
+{
+    @Autowired
+    private ISysMenuService menuService;
+
+    /**
+     * 获取菜单列表
+     */
+    // @PreAuthorize("@ss.hasPermi('system:menu:list')")
+    @GetMapping("/list")
+    public AjaxResult list(SysMenu menu)
+    {
+        List<SysMenu> menus = menuService.selectMenuList(menu, getUserId());
+        return success(menus);
+    }
+
+    /**
+     * 根据菜单编号获取详细信息
+     */
+    // @PreAuthorize("@ss.hasPermi('system:menu:query')")
+    @GetMapping(value = "/{menuId}")
+    public AjaxResult getInfo(@PathVariable Long menuId)
+    {
+        return success(menuService.selectMenuById(menuId));
+    }
+
+    /**
+     * 获取菜单下拉树列表
+     */
+    @GetMapping("/treeselect")
+    public AjaxResult treeselect(SysMenu menu)
+    {
+        List<SysMenu> menus = menuService.selectMenuList(menu, getUserId());
+        return success(menuService.buildMenuTreeSelect(menus));
+    }
+
+    /**
+     * 加载对应角色菜单列表树
+     */
+    @GetMapping(value = "/roleMenuTreeselect/{roleId}")
+    public AjaxResult roleMenuTreeselect(@PathVariable("roleId") Long roleId)
+    {
+        List<SysMenu> menus = menuService.selectMenuList(getUserId());
+        AjaxResult ajax = AjaxResult.success();
+        ajax.put("checkedKeys", menuService.selectMenuListByRoleId(roleId));
+        ajax.put("menus", menuService.buildMenuTreeSelect(menus));
+        return ajax;
+    }
+
+    /**
+     * 新增菜单
+     */
+    // @PreAuthorize("@ss.hasPermi('system:menu:add')")
+    @Log(title = "菜单管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody SysMenu menu)
+    {
+        if (!menuService.checkMenuNameUnique(menu))
+        {
+            return error("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
+        }
+        else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath()))
+        {
+            return error("新增菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头");
+        }
+        menu.setCreateBy(getUsername());
+        return toAjax(menuService.insertMenu(menu));
+    }
+
+    /**
+     * 修改菜单
+     */
+    // @PreAuthorize("@ss.hasPermi('system:menu:edit')")
+    @Log(title = "菜单管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody SysMenu menu)
+    {
+        if (!menuService.checkMenuNameUnique(menu))
+        {
+            return error("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
+        }
+        else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath()))
+        {
+            return error("修改菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头");
+        }
+        else if (menu.getMenuId().equals(menu.getParentId()))
+        {
+            return error("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己");
+        }
+        menu.setUpdateBy(getUsername());
+        return toAjax(menuService.updateMenu(menu));
+    }
+
+    /**
+     * 删除菜单
+     */
+    // @PreAuthorize("@ss.hasPermi('system:menu:remove')")
+    @Log(title = "菜单管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{menuId}")
+    public AjaxResult remove(@PathVariable("menuId") Long menuId)
+    {
+        if (menuService.hasChildByMenuId(menuId))
+        {
+            return warn("存在子菜单,不允许删除");
+        }
+        if (menuService.checkMenuExistRole(menuId))
+        {
+            return warn("菜单已分配,不允许删除");
+        }
+        return toAjax(menuService.deleteMenuById(menuId));
+    }
+}

+ 120 - 0
app-admin/src/main/java/com/ruoyi/web/controller/system/SysPostController.java

@@ -0,0 +1,120 @@
+package com.ruoyi.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 com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.domain.SysPost;
+import com.ruoyi.system.service.ISysPostService;
+
+/**
+ * 岗位信息操作处理
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/system/post")
+public class SysPostController extends BaseController
+{
+    @Autowired
+    private ISysPostService postService;
+
+    /**
+     * 获取岗位列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo list(SysPost post)
+    {
+        startPage();
+        List<SysPost> list = postService.selectPostList(post);
+        return getDataTable(list);
+    }
+    
+    @Log(title = "岗位管理", businessType = BusinessType.EXPORT)
+    // @PreAuthorize("@ss.hasPermi('system:post:export')")
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, SysPost post)
+    {
+        List<SysPost> list = postService.selectPostList(post);
+        ExcelUtil<SysPost> util = new ExcelUtil<SysPost>(SysPost.class);
+        util.exportExcel(response, list, "岗位数据");
+    }
+
+    /**
+     * 根据岗位编号获取详细信息
+     */
+    // @PreAuthorize("@ss.hasPermi('system:post:query')")
+    @GetMapping(value = "/{postId}")
+    public AjaxResult getInfo(@PathVariable Long postId)
+    {
+        return success(postService.selectPostById(postId));
+    }
+
+    /**
+     * 新增岗位
+     */
+    // @PreAuthorize("@ss.hasPermi('system:post:add')")
+    @Log(title = "岗位管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody SysPost post)
+    {
+        if (!postService.checkPostNameUnique(post))
+        {
+            return error("新增岗位'" + post.getPostName() + "'失败,列表中存在重复的岗位名称");
+        }
+        post.setCreateBy(getUsername());
+        return toAjax(postService.insertPost(post));
+    }
+
+    /**
+     * 修改岗位
+     */
+    // @PreAuthorize("@ss.hasPermi('system:post:edit')")
+    @Log(title = "岗位管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody SysPost post)
+    {
+        if (!postService.checkPostNameUnique(post))
+        {
+            return error("修改岗位'" + post.getPostName() + "'失败,列表中存在重复的岗位名称");
+        }
+        post.setUpdateBy(getUsername());
+        return toAjax(postService.updatePost(post));
+    }
+
+    /**
+     * 删除岗位
+     */
+    // @PreAuthorize("@ss.hasPermi('system:post:remove')")
+    @Log(title = "岗位管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{postIds}")
+    public AjaxResult remove(@PathVariable Long[] postIds)
+    {
+        return toAjax(postService.deletePostByIds(postIds));
+    }
+
+    /**
+     * 获取岗位选择框列表
+     */
+    @GetMapping("/optionselect")
+    public AjaxResult optionselect()
+    {
+        List<SysPost> posts = postService.selectPostAll();
+        return success(posts);
+    }
+}

+ 163 - 0
app-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java

@@ -0,0 +1,163 @@
+package com.ruoyi.web.controller.system;
+
+import java.util.Map;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.ruoyi.framework.datasource.DynamicDataSourceService;
+import com.ruoyi.web.v2.v1.entity.UserAccount;
+import com.ruoyi.web.v2.v1.service.IUserAccountService;
+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.ruoyi.common.annotation.Log;
+import com.ruoyi.common.config.RuoYiConfig;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.file.FileUploadUtils;
+import com.ruoyi.common.utils.file.MimeTypeUtils;
+import com.ruoyi.framework.web.service.TokenService;
+import com.ruoyi.system.service.ISysUserService;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * 个人信息 业务处理
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/system/user/profile")
+public class SysProfileController extends BaseController
+{
+    @Autowired
+    private ISysUserService userService;
+
+    @Autowired
+    private TokenService tokenService;
+    @Autowired
+    private IUserAccountService userAccountService;
+
+    @Autowired
+    private DynamicDataSourceService dynamicDataSourceService;
+    /**
+     * 个人信息
+     */
+    @GetMapping
+    public AjaxResult profile()
+    {
+        LoginUser loginUser = getLoginUser();
+        SysUser user = loginUser.getUser();
+        AjaxResult ajax = AjaxResult.success(user);
+        dynamicDataSourceService.switchToMaster();
+        UserAccount byUserId = userAccountService.getByUserId(loginUser.getAccountId());
+        if (ObjectUtil.isNotEmpty(byUserId)){
+            user.setAccountId(byUserId.getUserId());
+            user.setAccountBalance(byUserId.getAccountBalance());
+            user.setExperienceBalance(byUserId.getExperienceBalance());
+            user.setAnimalType(byUserId.getAnimalType());
+        }
+
+
+        ajax.put("roleGroup", userService.selectUserRoleGroup(loginUser.getUsername()));
+//        String dbName = loginUser.getDbName();
+       // ajax.put("postGroup", userService.selectUserPostGroup(loginUser.getUsername()));
+        return ajax;
+    }
+
+    /**
+     * 修改用户
+     */
+    @Log(title = "个人信息", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult updateProfile(@RequestBody SysUser user)
+    {
+        LoginUser loginUser = getLoginUser();
+        SysUser currentUser = loginUser.getUser();
+        currentUser.setNickName(user.getNickName());
+        currentUser.setEmail(user.getEmail());
+        currentUser.setPhonenumber(user.getPhonenumber());
+        currentUser.setSex(user.getSex());
+        if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(currentUser))
+        {
+            return error("修改用户'" + loginUser.getUsername() + "'失败,手机号码已存在");
+        }
+        if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(currentUser))
+        {
+            return error("修改用户'" + loginUser.getUsername() + "'失败,邮箱账号已存在");
+        }
+        if (userService.updateUserProfile(currentUser) > 0)
+        {
+            // 更新缓存用户信息
+            tokenService.setLoginUser(loginUser);
+            return success();
+        }
+        return error("修改个人信息异常,请联系管理员");
+    }
+
+    /**
+     * 重置密码
+     */
+    @Log(title = "个人信息", businessType = BusinessType.UPDATE)
+    @PutMapping("/updatePwd")
+    public AjaxResult updatePwd(@RequestBody Map<String, String> params)
+    {
+        String oldPassword = params.get("oldPassword");
+        String newPassword = params.get("newPassword");
+        LoginUser loginUser = getLoginUser();
+        String userName = loginUser.getUsername();
+        String password = loginUser.getPassword();
+        if (!SecurityUtils.matchesPassword(oldPassword, password))
+        {
+            return error("修改密码失败,旧密码错误");
+        }
+        if (SecurityUtils.matchesPassword(newPassword, password))
+        {
+            return error("新密码不能与旧密码相同");
+        }
+        newPassword = SecurityUtils.encryptPassword(newPassword);
+        if (userService.resetUserPwd(userName, newPassword) > 0)
+        {
+            // 更新缓存用户密码
+            loginUser.getUser().setPassword(newPassword);
+            tokenService.setLoginUser(loginUser);
+            return success();
+        }
+        return error("修改密码异常,请联系管理员");
+    }
+
+    /**
+     * 头像上传
+     */
+    @Log(title = "用户头像", businessType = BusinessType.UPDATE)
+    @PostMapping("/avatar")
+    public AjaxResult avatar(@RequestParam("avatarfile") MultipartFile file) throws Exception
+    {
+        if (!file.isEmpty())
+        {
+            LoginUser loginUser = getLoginUser();
+            String avatar = FileUploadUtils.upload(RuoYiConfig.getAvatarPath(), file, MimeTypeUtils.IMAGE_EXTENSION);
+            if (userService.updateUserAvatar(loginUser.getUsername(), avatar))
+            {
+                AjaxResult ajax = AjaxResult.success();
+                ajax.put("imgUrl", avatar);
+                // 更新缓存用户头像
+                loginUser.getUser().setAvatar(avatar);
+                tokenService.setLoginUser(loginUser);
+                return ajax;
+            }
+        }
+        return error("上传图片异常,请联系管理员");
+    }
+}

+ 38 - 0
app-admin/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java

@@ -0,0 +1,38 @@
+package com.ruoyi.web.controller.system;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.domain.model.RegisterBody;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.framework.web.service.SysRegisterService;
+import com.ruoyi.system.service.ISysConfigService;
+
+/**
+ * 注册验证
+ * 
+ * @author ruoyi
+ */
+@RestController
+public class SysRegisterController extends BaseController
+{
+    @Autowired
+    private SysRegisterService registerService;
+
+    @Autowired
+    private ISysConfigService configService;
+
+    @PostMapping("/register")
+    public AjaxResult register(@RequestBody RegisterBody user)
+    {
+        if (!("true".equals(configService.selectConfigByKey("sys.account.registerUser"))))
+        {
+            return error("当前系统没有开启注册功能!");
+        }
+        String msg = registerService.register(user);
+        return StringUtils.isEmpty(msg) ? success() : error(msg);
+    }
+}

+ 253 - 0
app-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java

@@ -0,0 +1,253 @@
+package com.ruoyi.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 com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.domain.entity.SysDept;
+import com.ruoyi.common.core.domain.entity.SysRole;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.framework.web.service.SysPermissionService;
+import com.ruoyi.framework.web.service.TokenService;
+import com.ruoyi.system.domain.SysUserRole;
+import com.ruoyi.system.service.ISysDeptService;
+import com.ruoyi.system.service.ISysRoleService;
+import com.ruoyi.system.service.ISysUserService;
+
+/**
+ * 角色信息
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/system/role")
+public class SysRoleController extends BaseController
+{
+    @Autowired
+    private ISysRoleService roleService;
+
+    @Autowired
+    private TokenService tokenService;
+
+    @Autowired
+    private SysPermissionService permissionService;
+
+    @Autowired
+    private ISysUserService userService;
+
+    @Autowired
+    private ISysDeptService deptService;
+
+    @GetMapping("/list")
+    public TableDataInfo list(SysRole role)
+    {
+        startPage();
+        List<SysRole> list = roleService.selectRoleList(role);
+        return getDataTable(list);
+    }
+
+    @Log(title = "角色管理", businessType = BusinessType.EXPORT)
+    // @PreAuthorize("@ss.hasPermi('system:role:export')")
+    @PostMapping("/export")
+    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, "角色数据");
+    }
+
+    /**
+     * 根据角色编号获取详细信息
+     */
+    // @PreAuthorize("@ss.hasPermi('system:role:query')")
+    @GetMapping(value = "/{roleId}")
+    public AjaxResult getInfo(@PathVariable Long roleId)
+    {
+        roleService.checkRoleDataScope(roleId);
+        return success(roleService.selectRoleById(roleId));
+    }
+
+    /**
+     * 新增角色
+     */
+    // @PreAuthorize("@ss.hasPermi('system:role:add')")
+    @Log(title = "角色管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody SysRole role)
+    {
+        if (!roleService.checkRoleNameUnique(role))
+        {
+            return error("新增角色'" + role.getRoleName() + "'失败,列表中存在重复的角色名称");
+        }
+        role.setCreateBy(getUsername());
+        return toAjax(roleService.insertRole(role));
+
+    }
+
+    /**
+     * 修改保存角色
+     */
+    // @PreAuthorize("@ss.hasPermi('system:role:edit')")
+    @Log(title = "角色管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody SysRole role)
+    {
+        roleService.checkRoleAllowed(role);
+        roleService.checkRoleDataScope(role.getRoleId());
+        if (!roleService.checkRoleNameUnique(role))
+        {
+            return error("修改角色'" + role.getRoleName() + "'失败,列表中存在重复的角色名称");
+        }
+        role.setUpdateBy(getUsername());
+        
+        if (roleService.updateRole(role) > 0)
+        {
+            // 更新缓存用户权限
+            LoginUser loginUser = getLoginUser();
+            if (StringUtils.isNotNull(loginUser.getUser()) && !loginUser.getUser().isAdmin())
+            {
+                loginUser.setUser(userService.selectUserByUserName(loginUser.getUser().getUserName()));
+                loginUser.setPermissions(permissionService.getMenuPermission(loginUser.getUser()));
+                tokenService.setLoginUser(loginUser);
+            }
+            return success();
+        }
+        return error("修改角色'" + role.getRoleName() + "'失败,请联系管理员");
+    }
+
+    /**
+     * 修改保存数据权限
+     */
+    // @PreAuthorize("@ss.hasPermi('system:role:edit')")
+    @Log(title = "角色管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/dataScope")
+    public AjaxResult dataScope(@RequestBody SysRole role)
+    {
+        roleService.checkRoleAllowed(role);
+        roleService.checkRoleDataScope(role.getRoleId());
+        return toAjax(roleService.authDataScope(role));
+    }
+
+    /**
+     * 状态修改
+     */
+    // @PreAuthorize("@ss.hasPermi('system:role:edit')")
+    @Log(title = "角色管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/changeStatus")
+    public AjaxResult changeStatus(@RequestBody SysRole role)
+    {
+        roleService.checkRoleAllowed(role);
+        roleService.checkRoleDataScope(role.getRoleId());
+        role.setUpdateBy(getUsername());
+        return toAjax(roleService.updateRoleStatus(role));
+    }
+
+    /**
+     * 删除角色
+     */
+    // @PreAuthorize("@ss.hasPermi('system:role:remove')")
+    @Log(title = "角色管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{roleIds}")
+    public AjaxResult remove(@PathVariable Long[] roleIds)
+    {
+        return toAjax(roleService.deleteRoleByIds(roleIds));
+    }
+
+    /**
+     * 获取角色选择框列表
+     */
+    // @PreAuthorize("@ss.hasPermi('system:role:query')")
+    @GetMapping("/optionselect")
+    public AjaxResult optionselect()
+    {
+        return success(roleService.selectRoleAll());
+    }
+
+    /**
+     * 查询已分配用户角色列表
+     */
+    // @PreAuthorize("@ss.hasPermi('system:role:list')")
+    @GetMapping("/authUser/allocatedList")
+    public TableDataInfo allocatedList(SysUser user)
+    {
+        startPage();
+        List<SysUser> list = userService.selectAllocatedList(user);
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询未分配用户角色列表
+     */
+    // @PreAuthorize("@ss.hasPermi('system:role:list')")
+    @GetMapping("/authUser/unallocatedList")
+    public TableDataInfo unallocatedList(SysUser user)
+    {
+        startPage();
+        List<SysUser> list = userService.selectUnallocatedList(user);
+        return getDataTable(list);
+    }
+
+    /**
+     * 取消授权用户
+     */
+    // @PreAuthorize("@ss.hasPermi('system:role:edit')")
+    @Log(title = "角色管理", businessType = BusinessType.GRANT)
+    @PutMapping("/authUser/cancel")
+    public AjaxResult cancelAuthUser(@RequestBody SysUserRole userRole)
+    {
+        return toAjax(roleService.deleteAuthUser(userRole));
+    }
+
+    /**
+     * 批量取消授权用户
+     */
+    // @PreAuthorize("@ss.hasPermi('system:role:edit')")
+    @Log(title = "角色管理", businessType = BusinessType.GRANT)
+    @PutMapping("/authUser/cancelAll")
+    public AjaxResult cancelAuthUserAll(Long roleId, Long[] userIds)
+    {
+        return toAjax(roleService.deleteAuthUsers(roleId, userIds));
+    }
+
+    /**
+     * 批量选择用户授权
+     */
+    // @PreAuthorize("@ss.hasPermi('system:role:edit')")
+    @Log(title = "角色管理", businessType = BusinessType.GRANT)
+    @PutMapping("/authUser/selectAll")
+    public AjaxResult selectAuthUserAll(Long roleId, Long[] userIds)
+    {
+        roleService.checkRoleDataScope(roleId);
+        return toAjax(roleService.insertAuthUsers(roleId, userIds));
+    }
+
+    /**
+     * 获取对应角色部门树列表
+     */
+    // @PreAuthorize("@ss.hasPermi('system:role:query')")
+    @GetMapping(value = "/deptTree/{roleId}")
+    public AjaxResult deptTree(@PathVariable("roleId") Long roleId)
+    {
+        AjaxResult ajax = AjaxResult.success();
+        ajax.put("checkedKeys", deptService.selectDeptListByRoleId(roleId));
+        ajax.put("depts", deptService.selectDeptTreeList(new SysDept()));
+        return ajax;
+    }
+}

+ 249 - 0
app-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java

@@ -0,0 +1,249 @@
+package com.ruoyi.web.controller.system;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.servlet.http.HttpServletResponse;
+
+import com.ruoyi.app.model.request.EditSysUser;
+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 com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.domain.entity.SysDept;
+import com.ruoyi.common.core.domain.entity.SysRole;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.service.ISysDeptService;
+import com.ruoyi.system.service.ISysPostService;
+import com.ruoyi.system.service.ISysRoleService;
+import com.ruoyi.system.service.ISysUserService;
+
+/**
+ * 用户信息
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/system/user")
+public class SysUserController extends BaseController
+{
+    @Autowired
+    private ISysUserService userService;
+
+    @Autowired
+    private ISysRoleService roleService;
+
+    @Autowired
+    private ISysDeptService deptService;
+
+    @Autowired
+    private ISysPostService postService;
+
+    /**
+     * 获取用户列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo list(SysUser user)
+    {
+        startPage();
+        List<SysUser> list = userService.selectUserList(user);
+        return getDataTable(list);
+    }
+
+    @Log(title = "用户管理", businessType = BusinessType.EXPORT)
+    // @PreAuthorize("@ss.hasPermi('system:user:export')")
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, SysUser user)
+    {
+        List<SysUser> list = userService.selectUserList(user);
+        ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class);
+        util.exportExcel(response, list, "用户数据");
+    }
+
+    @Log(title = "用户管理", businessType = BusinessType.IMPORT)
+    // @PreAuthorize("@ss.hasPermi('system:user:import')")
+    @PostMapping("/importData")
+    public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception
+    {
+        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 success(message);
+    }
+
+    @PostMapping("/importTemplate")
+    public void importTemplate(HttpServletResponse response)
+    {
+        ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class);
+        util.importTemplateExcel(response, "用户数据");
+    }
+
+    /**
+     * 根据用户编号获取详细信息
+     */
+    // @PreAuthorize("@ss.hasPermi('system:user:query')")
+    @GetMapping(value = { "/", "/{userId}" })
+    public AjaxResult getInfo(@PathVariable(value = "userId", required = false) Long userId)
+    {
+        AjaxResult ajax = AjaxResult.success();
+        if (StringUtils.isNotNull(userId))
+        {
+            userService.checkUserDataScope(userId);
+            SysUser sysUser = userService.selectUserById(userId);
+            if(StringUtils.isNotEmpty(sysUser.getRoles()) && !sysUser.getRoles().isEmpty()){
+                sysUser.setRoleId(sysUser.getRoles().get(0).getRoleId());
+            }
+            ajax.put(AjaxResult.DATA_TAG, sysUser);
+        }
+        List<SysRole> roles = roleService.selectRoleAll();
+        ajax.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));
+        return ajax;
+    }
+
+    /**
+     * 新增用户
+     */
+    // @PreAuthorize("@ss.hasPermi('system:user:add')")
+    @Log(title = "用户管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody SysUser user)
+    {
+        roleService.checkRoleDataScope(user.getRoleIds());
+        if (!userService.checkUserNameUnique(user))
+        {
+            return error("新增用户'" + user.getUserName() + "'失败,登录账号已存在");
+        }
+        user.setCreateBy(getUsername());
+        user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));
+        return toAjax(userService.insertUser(user));
+    }
+
+    /**
+     * 修改用户
+     */
+    // @PreAuthorize("@ss.hasPermi('system:user:edit')")
+    @Log(title = "用户管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody EditSysUser req)
+    {
+        SysUser user = new SysUser();
+        user.setUserId(req.getUserId());
+        user.setUserName(req.getUserName());
+        user.setNickName(req.getNickName());
+        user.setEmail(req.getEmail());
+        user.setPhonenumber(req.getPhonenumber());
+        user.setSex(req.getSex());
+        user.setAvatar(req.getAvatar());
+        user.setStatus(req.getStatus());
+        user.setRoleId(req.getRoleId());
+        userService.checkUserAllowed(user);
+        userService.checkUserDataScope(user.getUserId());
+        roleService.checkRoleDataScope(user.getRoleId());
+        if (!userService.checkUserNameUnique(user))
+        {
+            return error("修改用户'" + user.getUserName() + "'失败,登录账号已存在");
+        }
+        user.setUpdateBy(getUsername());
+        return toAjax(userService.updateUser(user));
+    }
+
+    /**
+     * 删除用户
+     */
+    // @PreAuthorize("@ss.hasPermi('system:user:remove')")
+    @Log(title = "用户管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{userIds}")
+    public AjaxResult remove(@PathVariable Long[] userIds)
+    {
+        if (ArrayUtils.contains(userIds, getUserId()))
+        {
+            return error("当前用户不能删除");
+        }
+        return toAjax(userService.deleteUserByIds(userIds));
+    }
+
+    /**
+     * 重置密码
+     */
+    // @PreAuthorize("@ss.hasPermi('system:user:resetPwd')")
+    @Log(title = "用户管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/resetPwd")
+    public AjaxResult resetPwd(@RequestBody SysUser user)
+    {
+        userService.checkUserAllowed(user);
+        userService.checkUserDataScope(user.getUserId());
+        user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));
+        user.setUpdateBy(getUsername());
+        return toAjax(userService.resetPwd(user));
+    }
+
+    /**
+     * 状态修改
+     */
+    // @PreAuthorize("@ss.hasPermi('system:user:edit')")
+    @Log(title = "用户管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/changeStatus")
+    public AjaxResult changeStatus(@RequestBody SysUser user)
+    {
+        userService.checkUserAllowed(user);
+        userService.checkUserDataScope(user.getUserId());
+        user.setUpdateBy(getUsername());
+        return toAjax(userService.updateUserStatus(user));
+    }
+
+    /**
+     * 根据用户编号获取授权角色
+     */
+    // @PreAuthorize("@ss.hasPermi('system:user:query')")
+    @GetMapping("/authRole/{userId}")
+    public AjaxResult authRole(@PathVariable("userId") Long userId)
+    {
+        AjaxResult ajax = AjaxResult.success();
+        SysUser user = userService.selectUserById(userId);
+        List<SysRole> roles = roleService.selectRolesByUserId(userId);
+        ajax.put("user", user);
+        ajax.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));
+        return ajax;
+    }
+
+    /**
+     * 用户授权角色
+     */
+    // @PreAuthorize("@ss.hasPermi('system:user:edit')")
+    @Log(title = "用户管理", businessType = BusinessType.GRANT)
+    @PutMapping("/authRole")
+    public AjaxResult insertAuthRole(Long userId, Long[] roleIds)
+    {
+        userService.checkUserDataScope(userId);
+        roleService.checkRoleDataScope(roleIds);
+        userService.insertUserAuth(userId, roleIds);
+        return success();
+    }
+
+    /**
+     * 获取部门树列表
+     */
+    // @PreAuthorize("@ss.hasPermi('system:user:list')")
+    @GetMapping("/deptTree")
+    public AjaxResult deptTree(SysDept dept)
+    {
+        return success(deptService.selectDeptTreeList(dept));
+    }
+}

+ 183 - 0
app-admin/src/main/java/com/ruoyi/web/controller/tool/TestController.java

@@ -0,0 +1,183 @@
+package com.ruoyi.web.controller.tool;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+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 com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.utils.StringUtils;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import io.swagger.annotations.ApiOperation;
+
+/**
+ * swagger 用户测试方法
+ * 
+ * @author ruoyi
+ */
+@Api("用户信息管理")
+@RestController
+@RequestMapping("/test/user")
+public class TestController extends BaseController
+{
+    private final static Map<Integer, UserEntity> users = new LinkedHashMap<Integer, UserEntity>();
+    {
+        users.put(1, new UserEntity(1, "admin", "admin123", "15888888888"));
+        users.put(2, new UserEntity(2, "ry", "admin123", "15666666666"));
+    }
+
+    @ApiOperation("获取用户列表")
+    @GetMapping("/list")
+    public R<List<UserEntity>> userList()
+    {
+        List<UserEntity> userList = new ArrayList<UserEntity>(users.values());
+        return R.ok(userList);
+    }
+
+    @ApiOperation("获取用户详细")
+    @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class)
+    @GetMapping("/{userId}")
+    public R<UserEntity> getUser(@PathVariable Integer userId)
+    {
+        if (!users.isEmpty() && users.containsKey(userId))
+        {
+            return R.ok(users.get(userId));
+        }
+        else
+        {
+            return R.fail("用户不存在");
+        }
+    }
+
+    @ApiOperation("新增用户")
+    @ApiImplicitParams({
+        @ApiImplicitParam(name = "userId", value = "用户id", dataType = "Integer", dataTypeClass = Integer.class),
+        @ApiImplicitParam(name = "username", value = "用户名称", dataType = "String", dataTypeClass = String.class),
+        @ApiImplicitParam(name = "password", value = "用户密码", dataType = "String", dataTypeClass = String.class),
+        @ApiImplicitParam(name = "mobile", value = "用户手机", dataType = "String", dataTypeClass = String.class)
+    })
+    @PostMapping("/save")
+    public R<String> save(UserEntity user)
+    {
+        if (StringUtils.isNull(user) || StringUtils.isNull(user.getUserId()))
+        {
+            return R.fail("用户ID不能为空");
+        }
+        users.put(user.getUserId(), user);
+        return R.ok();
+    }
+
+    @ApiOperation("更新用户")
+    @PutMapping("/update")
+    public R<String> update(@RequestBody UserEntity user)
+    {
+        if (StringUtils.isNull(user) || StringUtils.isNull(user.getUserId()))
+        {
+            return R.fail("用户ID不能为空");
+        }
+        if (users.isEmpty() || !users.containsKey(user.getUserId()))
+        {
+            return R.fail("用户不存在");
+        }
+        users.remove(user.getUserId());
+        users.put(user.getUserId(), user);
+        return R.ok();
+    }
+
+    @ApiOperation("删除用户信息")
+    @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class)
+    @DeleteMapping("/{userId}")
+    public R<String> delete(@PathVariable Integer userId)
+    {
+        if (!users.isEmpty() && users.containsKey(userId))
+        {
+            users.remove(userId);
+            return R.ok();
+        }
+        else
+        {
+            return R.fail("用户不存在");
+        }
+    }
+}
+
+@ApiModel(value = "UserEntity", description = "用户实体")
+class UserEntity
+{
+    @ApiModelProperty("用户ID")
+    private Integer userId;
+
+    @ApiModelProperty("用户名称")
+    private String username;
+
+    @ApiModelProperty("用户密码")
+    private String password;
+
+    @ApiModelProperty("用户手机")
+    private String mobile;
+
+    public UserEntity()
+    {
+
+    }
+
+    public UserEntity(Integer userId, String username, String password, String mobile)
+    {
+        this.userId = userId;
+        this.username = username;
+        this.password = password;
+        this.mobile = mobile;
+    }
+
+    public Integer getUserId()
+    {
+        return userId;
+    }
+
+    public void setUserId(Integer userId)
+    {
+        this.userId = userId;
+    }
+
+    public String getUsername()
+    {
+        return username;
+    }
+
+    public void setUsername(String username)
+    {
+        this.username = username;
+    }
+
+    public String getPassword()
+    {
+        return password;
+    }
+
+    public void setPassword(String password)
+    {
+        this.password = password;
+    }
+
+    public String getMobile()
+    {
+        return mobile;
+    }
+
+    public void setMobile(String mobile)
+    {
+        this.mobile = mobile;
+    }
+}

+ 125 - 0
app-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java

@@ -0,0 +1,125 @@
+package com.ruoyi.web.core.config;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import com.ruoyi.common.config.RuoYiConfig;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.models.auth.In;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.service.ApiKey;
+import springfox.documentation.service.AuthorizationScope;
+import springfox.documentation.service.Contact;
+import springfox.documentation.service.SecurityReference;
+import springfox.documentation.service.SecurityScheme;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spi.service.contexts.SecurityContext;
+import springfox.documentation.spring.web.plugins.Docket;
+
+/**
+ * Swagger2的接口配置
+ * 
+ * @author ruoyi
+ */
+@Configuration
+public class SwaggerConfig
+{
+    /** 系统基础配置 */
+    @Autowired
+    private RuoYiConfig ruoyiConfig;
+
+    /** 是否开启swagger */
+    @Value("${swagger.enabled}")
+    private boolean enabled;
+
+    /** 设置请求的统一前缀 */
+    @Value("${swagger.pathMapping}")
+    private String pathMapping;
+
+    /**
+     * 创建API
+     */
+    @Bean
+    public Docket createRestApi()
+    {
+        return new Docket(DocumentationType.OAS_30)
+                // 是否启用Swagger
+                .enable(enabled)
+                // 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息)
+                .apiInfo(apiInfo())
+                // 设置哪些接口暴露给Swagger展示
+                .select()
+                // 扫描所有有注解的api,用这种方式更灵活
+                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
+                // 扫描指定包中的swagger注解
+                // .apis(RequestHandlerSelectors.basePackage("com.ruoyi.project.tool.swagger"))
+                // 扫描所有 .apis(RequestHandlerSelectors.any())
+                .paths(PathSelectors.any())
+                .build()
+                /* 设置安全模式,swagger可以设置访问token */
+                .securitySchemes(securitySchemes())
+                .securityContexts(securityContexts())
+                .pathMapping(pathMapping);
+    }
+
+    /**
+     * 安全模式,这里指定token通过Authorization头请求头传递
+     */
+    private List<SecurityScheme> securitySchemes()
+    {
+        List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>();
+        apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue()));
+        return apiKeyList;
+    }
+
+    /**
+     * 安全上下文
+     */
+    private List<SecurityContext> securityContexts()
+    {
+        List<SecurityContext> securityContexts = new ArrayList<>();
+        securityContexts.add(
+                SecurityContext.builder()
+                        .securityReferences(defaultAuth())
+                        .operationSelector(o -> o.requestMappingPattern().matches("/.*"))
+                        .build());
+        return securityContexts;
+    }
+
+    /**
+     * 默认的安全上引用
+     */
+    private List<SecurityReference> defaultAuth()
+    {
+        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
+        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
+        authorizationScopes[0] = authorizationScope;
+        List<SecurityReference> securityReferences = new ArrayList<>();
+        securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
+        return securityReferences;
+    }
+
+    /**
+     * 添加摘要信息
+     */
+    private ApiInfo apiInfo()
+    {
+        // 用ApiInfoBuilder进行定制
+        return new ApiInfoBuilder()
+                // 设置标题
+                .title("标题:若依管理系统_接口文档")
+                // 描述
+                .description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...")
+                // 作者信息
+                .contact(new Contact(ruoyiConfig.getName(), null, null))
+                // 版本
+                .version("版本号:" + ruoyiConfig.getVersion())
+                .build();
+    }
+}

+ 158 - 0
app-admin/src/main/java/com/ruoyi/web/core/nfid/ClientHandler.java

@@ -0,0 +1,158 @@
+package com.ruoyi.web.core.nfid;
+
+import com.ruoyi.app.model.NFIDReader;
+import com.ruoyi.app.service.INFIDReaderService;
+import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.common.utils.HexUtil;
+import com.ruoyi.common.utils.spring.SpringUtils;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.util.ReferenceCountUtil;
+import org.springframework.stereotype.Component;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@Component
+public class ClientHandler extends ChannelInboundHandlerAdapter {
+
+
+    private INFIDReaderService NFIDReaderService = SpringUtils.getBean(INFIDReaderService.class);
+
+    private final AtomicInteger responseIndex = new AtomicInteger(1);
+
+    private MultiReaderNFIDProcessor processor = SpringUtils.getBean(MultiReaderNFIDProcessor.class);
+
+    @Override
+    public void channelActive(ChannelHandlerContext ctx) {
+        System.out.println("Client connected: " + ctx.channel().remoteAddress());
+
+        // 识别器连接后发送读取识别器设备信息指令
+        ByteBuf handshake = Unpooled.wrappedBuffer(HexUtil.hexToBytes("5A000101000000DCE5"));
+        //识别器连接后发送读取标签指令
+        sendReadResponseFrame(ctx);
+        ctx.writeAndFlush(handshake);
+    }
+
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+        try {
+            ByteBuf byteBuf = (ByteBuf) msg;
+            byte[] bytes = new byte[byteBuf.readableBytes()];
+            byteBuf.readBytes(bytes);
+
+            // 解析帧
+            List<NFIDFrameParser.NFIDFrame> frames = NFIDFrameParser.parseFrames(bytes);
+
+            if (!frames.isEmpty()) {
+                // 处理帧
+                processFrames(ctx, frames,bytes);
+            }
+        } finally {
+            ReferenceCountUtil.release(msg); // 释放ByteBuf
+        }
+    }
+
+    private void processFrames(ChannelHandlerContext ctx, List<NFIDFrameParser.NFIDFrame> frames,byte[] bytes) {
+        for (NFIDFrameParser.NFIDFrame frame : frames) {
+            // 记录接收到的帧
+           //System.out.println("Received frame: " + frame);
+
+//            // 检查是否可以发送读取
+//            if (NFIDFrameParser.isReadResponseRequiredFrame(frame)) {
+//                //发送读取指令
+//                sendReadResponseFrame(ctx);
+//            }
+            // 检查是否需要发送连接状态确认的响应
+            if (NFIDFrameParser.isStatusResponseRequiredFrame(frame)) {
+                //发送响应指令
+                sendStatusResponseFrame(ctx,bytes);
+            }
+
+            // 检查是否读写器信息帧
+            if (NFIDFrameParser.isDeviceInfoFrame(frame)) {
+                // 首次连接时注册设备
+                //if(!DeviceSessionManager.contains(ctx.channel())) {
+                    //读取设备序列号
+                    List<NFIDFrameParser.Parameter> params = frame.getParameters();
+                    if (params != null && params.size() > 0) {
+                        //字节转换成ascii
+                        String DeviceId = new String(params.get(0).getValue(), StandardCharsets.US_ASCII);
+                        DeviceSessionManager.registerDevice(ctx.channel(), DeviceId);
+                        //更新识别器状态
+                        updateNFIDReader(ctx,NFIDReader.STATUS_INLINE);
+                    }
+                //}
+            }
+            //检查是否为标签数据
+            if (NFIDFrameParser.isTagInfoFrame(frame)) {
+                List<NFIDFrameParser.Parameter> params = frame.getParameters();
+                String tag = HexUtil.bytesToHex(params.get(0).getValue());
+                //System.out.println("Tag: " + tag);
+                if (params != null && params.size() > 0) {
+                    String reader =  DeviceSessionManager.getDeviceId(ctx.channel());
+                    if (reader !=null && !reader.equals("")) {
+//                        System.out.println("天线:"+ctx.channel().id().asLongText());
+                        processor.receiveTag(tag, reader);
+                    }
+                }
+            }
+        }
+
+        // 通知帧处理器
+       // frameHandler.handleFrames(frames, ctx);
+    }
+
+    //向读写器发送读取指令
+    private void sendReadResponseFrame(ChannelHandlerContext ctx) {
+        int index = responseIndex.getAndUpdate(i -> i < 4 ? i + 1 : 1);
+        byte[] response = NFIDFrameParser.generateReadResponseFrame(index);
+        ctx.writeAndFlush(Unpooled.wrappedBuffer(response));
+        System.out.println("Sent read response frame with index: " + index);
+    }
+
+    //向读写器发送状态确认指令
+    private void sendStatusResponseFrame(ChannelHandlerContext ctx,byte[] bytes) {
+        ctx.writeAndFlush(Unpooled.wrappedBuffer(bytes));
+       // System.out.println("Sent status confirm response frame" );
+    }
+
+    @Override
+    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+        System.out.println("Client disconnected: " + ctx.channel().remoteAddress());
+
+        //更新识别器状态
+        updateNFIDReader(ctx,NFIDReader.STATUS_OFFLINE);
+
+        //断开则取消注册
+        DeviceSessionManager.unregister(ctx.channel());
+        ctx.fireChannelInactive();
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+        System.err.println("Error in client handler: " + cause.getMessage());
+
+        //更新识别器状态
+        updateNFIDReader(ctx,NFIDReader.STATUS_ERROR);
+
+        ctx.close();
+    }
+
+    //更新识别器
+    private int updateNFIDReader(ChannelHandlerContext ctx, String status){
+        NFIDReader NFIDReader = new NFIDReader();
+        String deviceId = DeviceSessionManager.getDeviceId(ctx.channel());
+        if(deviceId == null || deviceId.isEmpty()){
+            return 0;
+        }
+        NFIDReader.setDeviceSerial(deviceId);
+        NFIDReader.setLastActiveTime(DateUtils.getNowDate());
+        NFIDReader.setStatus(status);
+        return NFIDReaderService.updateNFIDReaderBySerial(NFIDReader);
+    }
+
+}

+ 44 - 0
app-admin/src/main/java/com/ruoyi/web/core/nfid/DeviceSessionManager.java

@@ -0,0 +1,44 @@
+package com.ruoyi.web.core.nfid;
+
+import io.netty.channel.Channel;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+public class DeviceSessionManager {
+    private static final ConcurrentHashMap<String, String> channelToDeviceMap = new ConcurrentHashMap<>();
+    private static final ConcurrentHashMap<String, Channel> deviceToChannelMap = new ConcurrentHashMap<>();
+
+    /**
+     * 检查Channel是否已注册设备
+     * @param channel Netty Channel对象
+     * @return 如果已注册返回true,否则返回false
+     */
+    public static boolean contains(Channel channel) {
+        return channelToDeviceMap.containsKey(channel.id().asLongText());
+    }
+
+    // 注册设备连接
+    public static void registerDevice(Channel channel, String deviceId) {
+        String channelId = channel.id().asLongText();
+        channelToDeviceMap.put(channelId, deviceId);
+        deviceToChannelMap.put(deviceId, channel);
+    }
+
+    // 获取设备ID
+    public static String getDeviceId(Channel channel) {
+        return channelToDeviceMap.get(channel.id().asLongText());
+    }
+
+    // 获取设备Channel
+    public static Channel getDeviceChannel(String deviceId) {
+        return deviceToChannelMap.get(deviceId);
+    }
+
+    /**
+     * 取消Channel的注册
+     * @param channel Netty Channel对象
+     */
+    public static void unregister(Channel channel) {
+        channelToDeviceMap.remove(channel.id().asLongText());
+    }
+}

+ 345 - 0
app-admin/src/main/java/com/ruoyi/web/core/nfid/MultiReaderNFIDProcessor.java

@@ -0,0 +1,345 @@
+package com.ruoyi.web.core.nfid;
+
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.lmax.disruptor.*;
+import com.lmax.disruptor.dsl.Disruptor;
+import com.lmax.disruptor.dsl.ProducerType;
+import com.ruoyi.app.DTO.HookBindDetailDTO;
+import com.ruoyi.app.model.NFIDReadRecord;
+import com.ruoyi.app.model.PorkSideProduce;
+import com.ruoyi.app.service.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.data.redis.core.RedisOperations;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.SessionCallback;
+import org.springframework.stereotype.Component;
+import java.util.*;
+import java.util.concurrent.*;
+import java.sql.*;
+import java.util.stream.Collectors;
+
+import com.ruoyi.app.model.NFIDReader;
+
+import javax.annotation.PreDestroy;
+
+@Component
+public class MultiReaderNFIDProcessor {
+    // 配置参数
+    private static final int BUFFER_FLUSH_SIZE = 200;
+    private static final long BUFFER_FLUSH_INTERVAL_MS = 1000;
+    private static final long TAG_EXPIRY_MS = 6*60*60*1000;
+    private static final int DISRUPTOR_BUFFER_SIZE = 1024 * 8;
+
+    @Autowired
+    private RedisTemplate redisTemplate;
+
+    @Autowired
+    private IHookRegisterService hookRegisterService;
+
+    @Autowired
+    private INFIDReaderService NFIDReaderService;
+
+    @Autowired
+    private IHookBindService hookBindService;
+
+    @Autowired
+    private INFIDReadRecordService NFIDReadRecordService;
+
+    @Autowired
+    private IPorkSideProduceService porkSideProduceService;
+
+    @PreDestroy
+    public void destroy()
+    {
+        disruptor.shutdown();
+        flushAllBuffers();
+    }
+
+    // 数据结构
+    //private final ConcurrentHashMap<String, Long> localCache;
+    private final Cache<String, Long> localCache = Caffeine.newBuilder()
+            .maximumSize(100_000)
+            .expireAfterWrite(TAG_EXPIRY_MS, TimeUnit.MILLISECONDS)
+            .build();
+    private final String redisCachePrefix = "nfid:reader:"; // 按识别器分组的缓存
+
+    // Disruptor相关
+    private final Disruptor<TagEvent> disruptor;
+    private final RingBuffer<TagEvent> ringBuffer;
+
+    // 批量处理
+    private final List<TagRecord> batchBuffer = Collections.synchronizedList(new ArrayList<>());
+    // 按阶段分组缓冲区
+    private final ConcurrentMap<String, List<TagRecord>> batchBuffers = new ConcurrentHashMap<>();
+
+    private static class TagEvent {
+        String tagId;
+        String readerId;
+        long timestamp;
+
+        void set(String tagId, String readerId, long timestamp) {
+            this.tagId = tagId;
+            this.readerId = readerId;
+            this.timestamp = timestamp;
+        }
+    }
+
+    private static class TagRecord {
+        String tagId;
+        String readerId;
+        Timestamp detectionTime;
+    }
+
+    public MultiReaderNFIDProcessor() {
+
+        // 初始化Disruptor
+        ExecutorService disruptorExecutor = Executors.newSingleThreadExecutor();
+        this.disruptor = new Disruptor<>(
+                TagEvent::new,
+                DISRUPTOR_BUFFER_SIZE,
+                disruptorExecutor,
+                ProducerType.MULTI,
+                new SleepingWaitStrategy()
+        );
+
+        // 设置事件处理器
+        disruptor.handleEventsWith(this::handleEvent);
+        this.ringBuffer = disruptor.start();
+
+        // 启动定时任务
+        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
+        scheduler.scheduleAtFixedRate(
+                this::flushAllBuffers,
+                BUFFER_FLUSH_INTERVAL_MS,
+                BUFFER_FLUSH_INTERVAL_MS,
+                TimeUnit.MILLISECONDS
+        );
+        scheduler.scheduleAtFixedRate(
+                this::cleanExpiredCache,
+                1, 1, TimeUnit.MINUTES
+        );
+    }
+
+    /**
+     * 接收标签数据(带识别器ID)
+     */
+    public void receiveTag(String tagId, String readerId) {
+        long sequence = ringBuffer.next();
+        try {
+            TagEvent event = ringBuffer.get(sequence);
+            event.set(tagId, readerId, System.currentTimeMillis());
+        } finally {
+            ringBuffer.publish(sequence);
+        }
+    }
+
+    /**
+     * 处理标签事件
+     */
+    private void handleEvent(TagEvent event, long sequence, boolean endOfBatch) {
+        //System.out.println("处理标签事件");
+        String compositeKey = event.readerId + ":" + event.tagId;
+        long now = System.currentTimeMillis();
+
+        // 1. 检查本地缓存
+        Long lastSeen = localCache.getIfPresent(compositeKey);
+        if (lastSeen != null && (now - lastSeen) < TAG_EXPIRY_MS) {
+
+           // System.out.println("检查本地缓存");
+            return;
+        }
+
+        // 2. 检查Redis缓存
+        String redisKey = redisCachePrefix + event.readerId;
+        try {
+            Boolean exists = redisTemplate.opsForHash().hasKey(redisKey, event.tagId);
+           // System.out.println(redisKey+"-"+event.tagId);
+            if (exists != null && exists) {
+               // System.out.println("保存"+compositeKey);
+                localCache.put(compositeKey, now);
+                return;
+            }
+        } catch (Exception e) {
+            System.out.println(e.getMessage());
+        }
+
+        // 新标签,加入处理流程
+        localCache.put(compositeKey, now);
+
+        // 准备批量记录
+       // System.out.println("准备批量记录");
+
+
+        // 更新Redis缓存
+        updateRedisCache(event.readerId, event.tagId, now);
+
+        String batchName = getReaderType(event.readerId);
+
+        TagRecord record = new TagRecord();
+        record.tagId = event.tagId;
+        record.readerId = event.readerId;
+        record.detectionTime = new Timestamp(event.timestamp);
+        batchBuffer.add(record);
+
+        // 添加到对应表的缓冲区
+        batchBuffers.computeIfAbsent(batchName, k -> new ArrayList<>())
+                .add(record);
+        // 检查批量大小
+        if (batchBuffers.get(batchName).size() >= BUFFER_FLUSH_SIZE) {
+            flushBuffer(batchName);
+        }
+    }
+
+    private String getReaderType(String deviceSerial){
+       return NFIDReaderService.selectNFIDReaderSpotByKey(deviceSerial);
+    }
+
+    private void updateRedisCache(String readerId, String tagId, long timestamp) {
+       // System.out.println("更新Redis缓存");
+        String redisKey = redisCachePrefix + readerId;
+        try  {
+            redisTemplate.opsForHash().put(redisKey, tagId, String.valueOf(timestamp));
+
+            redisTemplate.expire(redisKey, TAG_EXPIRY_MS / 1000, TimeUnit.SECONDS);
+        } catch (Exception e) {
+            System.out.println(e.getMessage());
+        }
+    }
+
+    //刷新全部
+    private void flushAllBuffers() {
+        Set<String> batchNames;
+        synchronized (batchBuffers) {
+            batchNames = new HashSet<>(batchBuffers.keySet());
+        }
+
+        for (String batchName : batchNames) {
+            flushBuffer(batchName);
+        }
+    }
+
+    /**
+     * 批量写入数据库
+     */
+    private void flushBuffer(String batchName) {
+        List<TagRecord> records;
+        synchronized (batchBuffers) {
+            records = new ArrayList<>(batchBuffers.getOrDefault(batchName, Collections.emptyList()));
+            batchBuffers.remove(batchName);
+        }
+
+        if (records.isEmpty()) return;
+
+        try {
+            if(Objects.equals(batchName, NFIDReader.BIND_SPOT)){
+                //吊钩绑定
+                saveBind(records);
+            }else if(Objects.equals(batchName, NFIDReader.WEIGHT_SPOT)){
+                //白条称重
+                saveWeight(records);
+            }else{
+                throw new Exception("未知点位类型:"+batchName);
+            }
+
+        } catch (Exception e) {
+            System.out.println(e.getMessage());
+
+        }
+    }
+    //吊钩绑定
+    private void saveBind(List<TagRecord> records) {
+        //2025-05-008修改逻辑,产品需求吊钩绑定识别不直接添加绑定记录,改为先添加识别记录,在吊钩绑定页面收到选择识别记录
+        //生成新对象
+        List<NFIDReadRecord> recordList = records.stream()
+                .map(item -> {
+                    NFIDReadRecord record = new NFIDReadRecord();
+                    record.setEpcNo(item.tagId);
+                    record.setHookNo(hookRegisterService.selectHookNoByEpcNo(item.tagId));
+                    record.setDeviceSerial(item.readerId);
+                    record.setCreateTime(item.detectionTime);
+                    return record;
+                })
+                .collect(Collectors.toList());
+        NFIDReadRecordService.insertNFIDReadRecordBatch(recordList);
+    }
+
+    //白条称重
+    private void saveWeight(List<TagRecord> records) {
+        //生成新对象
+        List<PorkSideProduce> porkSideProduces = records.stream()
+                .map(item -> {
+                    PorkSideProduce porkSideProduce = new PorkSideProduce();
+                    porkSideProduce.setEpcNo(item.tagId);
+                    porkSideProduce.setHookNo(hookRegisterService.selectHookNoByEpcNo(item.tagId));
+                    porkSideProduce.setDeviceSerial(item.readerId);
+                    porkSideProduce.setProduceTime(item.detectionTime);
+                    porkSideProduce.setCreateTime(item.detectionTime);
+                    porkSideProduce.setProductName(PorkSideProduce.DEFAULT_NAME);
+
+                    if(!Objects.equals(porkSideProduce.getHookNo(), "")){
+                        //获取吊钩绑定关联信息
+                        HookBindDetailDTO hookBindDetail = hookBindService.selectHookBindDetail(porkSideProduce.getHookNo());
+                        porkSideProduce.setEntranceBatchId(hookBindDetail.getEntranceBatchId());
+                        porkSideProduce.setDistributeBatchId(hookBindDetail.getDistributeBatchId());
+                        porkSideProduce.setSlaughterCode(hookBindDetail.getSlaughterCode());
+                    }
+                    return porkSideProduce;
+                })
+                .collect(Collectors.toList());
+        porkSideProduceService.insertPorkSideProduceBatch(porkSideProduces);
+    }
+    /**
+     * 清理过期缓存
+     */
+    private void cleanExpiredCache() {
+       // System.out.println("清理过期缓存");
+        long now = System.currentTimeMillis();
+
+        // 清理本地缓存
+        localCache.asMap().entrySet().removeIf(entry ->
+                (now - entry.getValue()) >= TAG_EXPIRY_MS
+        );
+
+        // 清理Redis缓存
+        try{
+            // 获取所有识别器缓存键
+            // 获取所有匹配的keys
+            Set<String> readerKeys = redisTemplate.keys(redisCachePrefix + "*");
+
+            if (readerKeys != null) {  // keys可能返回null
+                for (String key : readerKeys) {
+                    // 获取整个hash
+                    Map<Object, Object> tags = redisTemplate.opsForHash().entries(key);
+
+                    // 过滤过期的entry
+                    tags.entrySet().removeIf(entry -> {
+                        long timestamp = Long.parseLong(entry.getValue().toString());
+                        return (now - timestamp) >= TAG_EXPIRY_MS;
+                    });
+
+                    // 执行事务操作
+                    redisTemplate.execute(new SessionCallback<Object>() {
+                       @Override
+                        public Object execute(RedisOperations operations) throws DataAccessException {
+                            operations.watch(key);
+                            operations.multi();
+
+                            if (tags.isEmpty()) {
+                                operations.delete(key);
+                            } else {
+                                operations.opsForHash().putAll(key, tags);
+                                operations.expire(key, TAG_EXPIRY_MS / 1000, TimeUnit.SECONDS);
+                            }
+
+                            return operations.exec();
+                        }
+                    });
+                }
+            }
+        } catch (Exception e) {
+            System.out.println(e.getMessage());
+        }
+    }
+}

+ 322 - 0
app-admin/src/main/java/com/ruoyi/web/core/nfid/NFIDFrameParser.java

@@ -0,0 +1,322 @@
+package com.ruoyi.web.core.nfid;
+
+import com.ruoyi.common.utils.HexUtil;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+
+public class NFIDFrameParser {
+
+    // 帧头标识
+    public static final byte FRAME_HEADER = (byte) 0x5A;
+
+    // 最大数据长度
+    private static final int MAX_DATA_LENGTH = 1024;
+
+    // 最小帧长度(帧头1 + 控制字4 + 长度2 + 校验2 = 9字节)
+    private static final int MIN_FRAME_LENGTH = 9;
+
+    // CRC16-CCITT多项式(X16 + X15 + X2 + 1)
+    private static final int CRC16_POLY = 0x1021;
+    private static final int CRC16_INIT = 0x0000;
+
+    // 添加帧类型判断方法
+    //判断是否待读取
+    public static boolean isReadResponseRequiredFrame(NFIDFrame frame) {
+        // 判断条件:数据长度在9-20字节之间
+        return frame.getDataLength() >= 9 && frame.getDataLength() < 20;
+    }
+
+    // 添加响应帧生成方法
+    public static byte[] generateReadResponseFrame(int index) {
+        // 5A0001021000080000000X01020006ED08
+        // 其中X是1-4的循环索引
+        String hexStr = String.format("5A0001021000080000000%d01020006ED08", index);
+        return HexUtil.hexToBytes(hexStr);
+    }
+
+    //判断是否为链接状态确认信息
+    public static boolean isStatusResponseRequiredFrame(NFIDFrame frame) {
+        // 判断条件:数据长度在4字节
+        return frame.getDataLength() == 4;
+    }
+
+    // 判断是否为读写器信息
+    public static boolean isDeviceInfoFrame(NFIDFrame frame) {
+        // 判断条件:数据长度在70-90字节之间
+        return frame.getDataLength() >= 70;
+    }
+
+    // 判断是否为标签信息
+    public static boolean isTagInfoFrame(NFIDFrame frame) {
+        // 判断条件:数据长度
+        List<Parameter> parameters = frame.getParameters();
+        return parameters.size() == 1 && parameters.get(0).getLength() == 12;
+    }
+
+    /**
+     * 解析接收到的数据
+     * @param data 接收到的原始字节数组
+     * @return 解析出的完整帧列表
+     */
+    public static List<NFIDFrame> parseFrames(byte[] data) {
+        List<NFIDFrame> frames = new ArrayList<>();
+        ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN);
+
+        while (buffer.remaining() >= MIN_FRAME_LENGTH) {
+            // 查找帧头
+            int startPos = buffer.position();
+            if (buffer.get() != FRAME_HEADER) {
+                continue; // 不是帧头,继续查找
+            }
+
+            // 标记帧开始位置
+            int frameStart = startPos;
+
+            try {
+                // 读取控制字(4字节)
+                int controlWord = buffer.getInt();
+
+                // 检查RS485标志位(假设第31位是RS485标志位)
+                boolean hasRS485Address = (controlWord & 0x80000000) != 0;
+
+                // 读取设备地址(如果有)
+                byte deviceAddress = 0;
+                if (hasRS485Address) {
+                    if (buffer.remaining() < 1) {
+                        break; // 数据不足
+                    }
+                    deviceAddress = buffer.get();
+                }
+
+                // 读取数据长度(2字节)
+                if (buffer.remaining() < 2) {
+                    break; // 数据不足
+                }
+                int dataLength = buffer.getShort() & 0xFFFF;
+
+                // 检查数据长度是否合法
+                if (dataLength > MAX_DATA_LENGTH) {
+                    // 非法长度,跳过这个帧头
+                    buffer.position(frameStart + 1);
+                    continue;
+                }
+
+                // 检查是否有足够的数据(数据 + 校验码)
+                if (buffer.remaining() < dataLength + 2) {
+                    break; // 数据不足
+                }
+
+                // 读取数据内容
+                byte[] frameData = new byte[dataLength];
+                buffer.get(frameData);
+
+                // 读取校验码
+                int receivedChecksum = buffer.getShort() & 0xFFFF;
+
+                // 计算校验范围(从控制字到数据结束)
+                int checksumStart = frameStart + 1;
+                int checksumEnd = frameStart + 1 + (hasRS485Address ? 5 : 4) + 2 + dataLength;
+                byte[] checksumData = new byte[checksumEnd - checksumStart];
+                System.arraycopy(data, checksumStart, checksumData, 0, checksumData.length);
+
+                // 计算CRC校验
+                int calculatedChecksum = calculateCRC16(checksumData);
+
+                // 校验通过则添加到结果列表
+                if (receivedChecksum == calculatedChecksum) {
+                    NFIDFrame frame = new NFIDFrame();
+                    frame.setControlWord(controlWord);
+                    frame.setHasRS485Address(hasRS485Address);
+                    frame.setDeviceAddress(deviceAddress);
+                    frame.setDataLength(dataLength);
+                    frame.setData(frameData);
+                    frame.setChecksum(receivedChecksum);
+                    frames.add(frame);
+                } else {
+                    // 校验失败,跳过这个帧头继续查找
+                    buffer.position(frameStart + 1);
+                }
+
+            } catch (Exception e) {
+                // 发生异常,跳过这个帧头继续查找
+                buffer.position(frameStart + 1);
+            }
+        }
+
+        return frames;
+    }
+
+    /**
+     * 计算CRC16-CCITT校验值
+     * @param data 要计算的数据
+     * @return CRC16校验值
+     */
+    private static int calculateCRC16(byte[] data) {
+        int crc = CRC16_INIT;
+
+        for (byte b : data) {
+            crc ^= (b & 0xFF) << 8;
+            for (int i = 0; i < 8; i++) {
+                if ((crc & 0x8000) != 0) {
+                    crc = (crc << 1) ^ CRC16_POLY;
+                } else {
+                    crc <<= 1;
+                }
+                crc &= 0xFFFF; // 确保保持16位
+            }
+        }
+
+        return crc;
+    }
+    public static List<Parameter> parseParameters(byte[] data) {
+
+        List<Parameter> parameters = new ArrayList<>();
+        ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN);
+
+        while (buffer.remaining() > 0) {
+            // 读取参数长度(2字节)
+            if (buffer.remaining() < 2) break;
+            int paramLength = buffer.getShort() & 0xFFFF;
+            //System.out.println(paramLength);
+            // 检查是否有足够的数据
+            if (buffer.remaining() < paramLength) break;
+
+            // 读取参数内容
+            byte[] paramValue = new byte[paramLength];
+            buffer.get(paramValue);
+           // System.out.println(bytesToHex(paramValue));
+            parameters.add(new Parameter( paramLength, paramValue));
+        }
+
+        return parameters;
+    }
+
+    /**
+     * 参数数据结构
+     */
+    public static class Parameter {
+        private final int length;
+        private final byte[] value;
+
+        public Parameter(int length, byte[] value) {
+
+            this.length = length;
+            this.value = value;
+        }
+
+
+        public int getLength() {
+            return length;
+        }
+
+        public byte[] getValue() {
+            return value;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("Parameter{ Length=%d, Value=%s}",
+                    length, HexUtil.bytesToHex(value));
+        }
+
+    }
+    /**
+     * NFID帧数据结构
+     */
+    public static class NFIDFrame {
+        private int controlWord;         // 协议控制字(4字节)
+        private boolean hasRS485Address;  // 是否有RS485地址
+        private byte deviceAddress;       // 串行设备地址(1字节)
+        private int dataLength;           // 数据长度(2字节)
+        private byte[] data;              // 数据参数(N字节)
+        private int checksum;            // 校验码(2字节)
+
+        private List<Parameter> parameters; // 新增参数列表
+        // getters and setters
+
+        public List<Parameter> getParameters() {
+            if (parameters == null && data != null) {
+                parameters = parseParameters(data);
+            }
+            return parameters;
+        }
+
+        public int getControlWord() {
+            return controlWord;
+        }
+
+        public void setControlWord(int controlWord) {
+            this.controlWord = controlWord;
+        }
+
+        public boolean isHasRS485Address() {
+            return hasRS485Address;
+        }
+
+        public void setHasRS485Address(boolean hasRS485Address) {
+            this.hasRS485Address = hasRS485Address;
+        }
+
+        public byte getDeviceAddress() {
+            return deviceAddress;
+        }
+
+        public void setDeviceAddress(byte deviceAddress) {
+            this.deviceAddress = deviceAddress;
+        }
+
+        public int getDataLength() {
+            return dataLength;
+        }
+
+        public void setDataLength(int dataLength) {
+            this.dataLength = dataLength;
+        }
+
+        public byte[] getData() {
+            return data;
+        }
+
+        public void setData(byte[] data) {
+            this.data = data;
+        }
+
+        public int getChecksum() {
+            return checksum;
+        }
+
+        public void setChecksum(int checksum) {
+            this.checksum = checksum;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("NFIDFrame{\n");
+            sb.append("  controlWord=0x").append(Integer.toHexString(controlWord)).append("\n");
+            sb.append("  hasRS485Address=").append(hasRS485Address).append("\n");
+            if (hasRS485Address) {
+                sb.append("  deviceAddress=0x").append(String.format("%02X", deviceAddress & 0xFF)).append("\n");
+            }
+            sb.append("  dataLength=").append(dataLength).append("\n");
+            sb.append("  checksum=0x").append(String.format("%04X", checksum)).append("\n");
+
+            // 添加参数信息
+            List<Parameter> params = getParameters();
+            if (!params.isEmpty()) {
+                sb.append("  parameters=[\n");
+                for (Parameter param : params) {
+                    sb.append("    ").append(param).append("\n");
+                }
+                sb.append("  ]\n");
+            }
+
+            sb.append("}");
+            return sb.toString();
+        }
+
+    }
+}

+ 26 - 0
app-admin/src/main/java/com/ruoyi/web/core/nfid/NFIDRedisData.java

@@ -0,0 +1,26 @@
+package com.ruoyi.web.core.nfid;
+
+import java.time.Instant;
+
+public class NFIDRedisData {
+    private String paramValue;
+    private Instant readTime;
+
+    public NFIDRedisData(String paramValue, Instant readTime) {
+        this.paramValue = paramValue;
+        this.readTime = readTime;
+    }
+
+    public String getParamValue() {
+        return paramValue;
+    }
+    public void setParamValue(String paramValue) {
+        this.paramValue = paramValue;
+    }
+    public Instant getReadTime() {
+        return readTime;
+    }
+    public void setReadTime(Instant readTime) {
+        this.readTime = readTime;
+    }
+}

+ 66 - 0
app-admin/src/main/java/com/ruoyi/web/core/nfid/NFIDServer.java

@@ -0,0 +1,66 @@
+//package com.ruoyi.web.core.nfid;
+//
+//import com.ruoyi.common.utils.spring.SpringUtils;
+//import io.netty.bootstrap.ServerBootstrap;
+//import io.netty.channel.*;
+//import io.netty.channel.nio.NioEventLoopGroup;
+//import io.netty.channel.socket.SocketChannel;
+//import io.netty.channel.socket.nio.NioServerSocketChannel;
+//import org.springframework.beans.factory.annotation.Autowired;
+//import org.springframework.stereotype.Component;
+//
+//@Component
+//public class NFIDServer {
+//    private final int port = 3728;
+//   // private final NFIDFrameHandler frameHandler;
+//    private EventLoopGroup bossGroup;
+//    private EventLoopGroup workerGroup;
+//
+////    @Autowired
+////    private ClientHandler clientHandler;
+//
+////    public NFIDServer(int port) {
+////        this.port = port;
+////       // this.frameHandler = frameHandler;
+////    }
+//
+//    public void start() throws Exception {
+//        bossGroup = new NioEventLoopGroup(1);
+//        workerGroup = new NioEventLoopGroup();
+//
+//        try {
+//            ServerBootstrap b = new ServerBootstrap();
+//            b.group(bossGroup, workerGroup)
+//                    .channel(NioServerSocketChannel.class)
+//                    .childHandler(new ChannelInitializer<SocketChannel>() {
+//                        @Override
+//                        protected void initChannel(SocketChannel ch) {
+//                            ch.pipeline().addLast(new ClientHandler());
+//                        }
+//                    })
+//                    .option(ChannelOption.SO_BACKLOG, 128)
+//                    .childOption(ChannelOption.SO_KEEPALIVE, true);
+//
+//            ChannelFuture f = b.bind(port).sync();
+//            System.out.println("NFID Server started on port " + port);
+//            f.channel().closeFuture().sync();
+//        } finally {
+//            stop();
+//        }
+//    }
+//
+//    public void stop() {
+//        if (workerGroup != null) {
+//            workerGroup.shutdownGracefully();
+//        }
+//        if (bossGroup != null) {
+//            bossGroup.shutdownGracefully();
+//        }
+//    }
+//
+//    public static void main(String[] args) throws Exception {
+//        // 启动服务器
+//        NFIDServer server = new NFIDServer();
+//        server.start();
+//    }
+//}

+ 246 - 0
app-admin/src/main/java/com/ruoyi/web/core/nfid/NFIDTestClient.java

@@ -0,0 +1,246 @@
+//package com.ruoyi.web.core.nfid;
+//
+//import io.netty.bootstrap.Bootstrap;
+//import io.netty.buffer.ByteBuf;
+//import io.netty.buffer.Unpooled;
+//import io.netty.channel.*;
+//import io.netty.channel.nio.NioEventLoopGroup;
+//import io.netty.channel.socket.SocketChannel;
+//import io.netty.channel.socket.nio.NioSocketChannel;
+//import io.netty.handler.timeout.IdleState;
+//import io.netty.handler.timeout.IdleStateEvent;
+//import io.netty.handler.timeout.IdleStateHandler;
+//
+//import java.util.Arrays;
+//import java.util.List;
+//import java.util.Random;
+//import java.util.concurrent.TimeUnit;
+//
+//public class NFIDTestClient {
+//    private static final String HOST = "localhost";
+//    private static final int PORT = 3728;
+//    private static final int HEARTBEAT_INTERVAL = 30; // 秒
+//    private static final int RECONNECT_DELAY = 5; // 重试间隔(秒)
+//
+//    // 测试帧数据
+//    private static final List<byte[]> TEST_FRAMES = Arrays.asList(
+//            hexStringToByteArray("5A000112000018000C00010000F04EE845D972D375340002013D08000DE3AA3482"),
+//            hexStringToByteArray("5A000112000018000CE28068940000502F57B4B649300002010D08000DE3AAF63F"),
+//            hexStringToByteArray("5A000112000018000C2C14F77EF04EE845D974502A340002012C08000DE3AA7930"),
+//            hexStringToByteArray("5A00010100004B00145238303038303030303230323030303030303038000001A1000C4A756E203130203230323000010011000002000E56312E30305F323031393036303903000A323032302D30362D31308FC5")
+//    );
+//
+//    // 心跳帧
+//    private static final byte[] HEARTBEAT_FRAME = hexStringToByteArray("5A00000001000000000000");
+//
+//    // 测试模式枚举
+//    private enum TestMode {
+//        NORMAL,      // 正常发送
+//        STICKY,      // 粘包模式
+//        SPLIT,       // 拆包模式
+//        MIXED        // 混合模式
+//    }
+//
+//    private static TestMode currentMode = TestMode.MIXED; // 默认使用混合模式
+//
+//    public static void main(String[] args) throws Exception {
+//        if (args.length > 0) {
+//            currentMode = TestMode.valueOf(args[0].toUpperCase());
+//        }
+//        System.out.println("Starting NFID test client in " + currentMode + " mode");
+//
+//        EventLoopGroup group = new NioEventLoopGroup();
+//        Bootstrap bootstrap = createBootstrap(group);
+//        connect(bootstrap, 0);
+//    }
+//
+//    private static Bootstrap createBootstrap(EventLoopGroup group) {
+//        Bootstrap b = new Bootstrap();
+//        b.group(group)
+//                .channel(NioSocketChannel.class)
+//                .handler(new ChannelInitializer<SocketChannel>() {
+//                    @Override
+//                    protected void initChannel(SocketChannel ch) {
+//                        ch.pipeline()
+//                                .addLast(new IdleStateHandler(0, HEARTBEAT_INTERVAL, 0, TimeUnit.SECONDS))
+//                                .addLast(new HeartbeatHandler())
+//                                .addLast(new FrameSenderHandler())
+//                                .addLast(new ResponseHandler());
+//                    }
+//                });
+//        return b;
+//    }
+//
+//    private static void connect(Bootstrap bootstrap, int attempt) {
+//        bootstrap.connect(HOST, PORT).addListener(new ChannelFutureListener() {
+//            @Override
+//            public void operationComplete(ChannelFuture future) throws Exception {
+//                if (future.isSuccess()) {
+//                    System.out.println("Connected to NFID server at " + HOST + ":" + PORT);
+//                    Channel channel = future.channel();
+//
+//                    channel.closeFuture().addListener(closeFuture -> {
+//                        System.out.println("Connection lost, attempting to reconnect...");
+//                        scheduleReconnect(bootstrap);
+//                    });
+//
+//                } else {
+//                    System.err.println("Connection attempt " + (attempt + 1) + " failed: " + future.cause().getMessage());
+//                    scheduleReconnect(bootstrap);
+//                }
+//            }
+//        });
+//    }
+//
+//    private static void scheduleReconnect(Bootstrap bootstrap) {
+//        bootstrap.config().group().schedule(() -> {
+//            System.out.println("Reconnecting...");
+//            connect(bootstrap, 0);
+//        }, RECONNECT_DELAY, TimeUnit.SECONDS);
+//    }
+//
+//    /**
+//     * 心跳处理器
+//     */
+//    private static class HeartbeatHandler extends ChannelInboundHandlerAdapter {
+//        @Override
+//        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
+//            if (evt instanceof IdleStateEvent) {
+//                IdleStateEvent e = (IdleStateEvent) evt;
+//                if (e.state() == IdleState.WRITER_IDLE) {
+//                    ctx.writeAndFlush(Unpooled.wrappedBuffer(HEARTBEAT_FRAME));
+//                    System.out.println("Sent heartbeat at " + System.currentTimeMillis());
+//                }
+//            }
+//        }
+//    }
+//
+//    /**
+//     * 数据帧发送处理器
+//     */
+//    private static class FrameSenderHandler extends ChannelInboundHandlerAdapter {
+//        private final Random random = new Random();
+//        private final int MIN_INTERVAL = 500; // 1秒
+//        private final int MAX_INTERVAL = 1500; // 3秒
+//
+//        @Override
+//        public void channelActive(ChannelHandlerContext ctx) {
+//            System.out.println("Channel active, starting frame sender in " + currentMode + " mode");
+//            scheduleSendFrame(ctx);
+//        }
+//
+//        private void scheduleSendFrame(ChannelHandlerContext ctx) {
+//            if (!ctx.channel().isActive()) return;
+//
+//            int delay = MIN_INTERVAL + random.nextInt(MAX_INTERVAL - MIN_INTERVAL);
+//            ctx.executor().schedule(() -> {
+//                if (ctx.channel().isActive()) {
+//                    sendFrameAccordingToMode(ctx);
+//                    scheduleSendFrame(ctx);
+//                }
+//            }, delay, TimeUnit.MILLISECONDS);
+//        }
+//
+//        private void sendFrameAccordingToMode(ChannelHandlerContext ctx) {
+//            switch (currentMode) {
+//                case NORMAL:
+//                    sendNormalFrame(ctx);
+//                    break;
+//                case STICKY:
+//                    sendStickyPackets(ctx);
+//                    break;
+//                case SPLIT:
+//                    sendSplitFrame(ctx);
+//                    break;
+//                case MIXED:
+//                    if (random.nextBoolean()) {
+//                        sendNormalFrame(ctx);
+//                    } else if (random.nextBoolean()) {
+//                        sendStickyPackets(ctx);
+//                    } else {
+//                        sendSplitFrame(ctx);
+//                    }
+//                    break;
+//            }
+//        }
+//
+//        private void sendNormalFrame(ChannelHandlerContext ctx) {
+//            byte[] frame = TEST_FRAMES.get(random.nextInt(TEST_FRAMES.size()));
+//            ctx.writeAndFlush(Unpooled.wrappedBuffer(frame));
+//            System.out.println("[NORMAL] Sent single frame: " + bytesToHex(frame));
+//        }
+//
+//        private void sendStickyPackets(ChannelHandlerContext ctx) {
+//            // 随机合并2-4个帧一起发送
+//            int frameCount = 2 + random.nextInt(3);
+//            ByteBuf buffer = ctx.alloc().buffer();
+//
+//            for (int i = 0; i < frameCount; i++) {
+//                byte[] frame = TEST_FRAMES.get(random.nextInt(TEST_FRAMES.size()));
+//                buffer.writeBytes(frame);
+//                System.out.println("[STICKY] Adding frame " + (i+1) + ": " + bytesToHex(frame));
+//            }
+//
+//            ctx.writeAndFlush(buffer);
+//            System.out.println("[STICKY] Sent " + frameCount + " frames as sticky packet");
+//        }
+//
+//        private void sendSplitFrame(ChannelHandlerContext ctx) {
+//            byte[] frame = TEST_FRAMES.get(random.nextInt(TEST_FRAMES.size()));
+//            int splitPoint = 5 + random.nextInt(frame.length - 10); // 确保分割点在中间
+//
+//            byte[] part1 = Arrays.copyOfRange(frame, 0, splitPoint);
+//            byte[] part2 = Arrays.copyOfRange(frame, splitPoint, frame.length);
+//
+//            System.out.println("[SPLIT] Splitting frame at position " + splitPoint +
+//                    " (total length: " + frame.length + ")");
+//
+//            // 发送第一部分
+//            ctx.writeAndFlush(Unpooled.wrappedBuffer(part1));
+//            System.out.println("[SPLIT] Sent first part: " + bytesToHex(part1));
+//
+//            // 延迟发送第二部分
+//            ctx.executor().schedule(() -> {
+//                if (ctx.channel().isActive()) {
+//                    ctx.writeAndFlush(Unpooled.wrappedBuffer(part2));
+//                    System.out.println("[SPLIT] Sent second part: " + bytesToHex(part2));
+//                }
+//            }, 100 + random.nextInt(400), TimeUnit.MILLISECONDS); // 100-500ms延迟
+//        }
+//    }
+//
+//    /**
+//     * 响应处理器
+//     */
+//    private static class ResponseHandler extends SimpleChannelInboundHandler<Object> {
+//        @Override
+//        protected void channelRead0(ChannelHandlerContext ctx, Object msg) {
+//            System.out.println("Received server response: " + msg);
+//        }
+//
+//        @Override
+//        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+//            System.err.println("Connection error: " + cause.getMessage());
+//            ctx.close();
+//        }
+//    }
+//
+//    // 辅助方法
+//    private static byte[] hexStringToByteArray(String s) {
+//        int len = s.length();
+//        byte[] data = new byte[len / 2];
+//        for (int i = 0; i < len; i += 2) {
+//            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+//                    + Character.digit(s.charAt(i+1), 16));
+//        }
+//        return data;
+//    }
+//
+//    private static String bytesToHex(byte[] bytes) {
+//        StringBuilder sb = new StringBuilder();
+//        for (byte b : bytes) {
+//            sb.append(String.format("%02X ", b));
+//        }
+//        return sb.toString().trim();
+//    }
+//}

+ 246 - 0
app-admin/src/main/java/com/ruoyi/web/core/nfid/NFIDTestClient1.java

@@ -0,0 +1,246 @@
+//package com.ruoyi.web.core.nfid;
+//
+//import io.netty.bootstrap.Bootstrap;
+//import io.netty.buffer.ByteBuf;
+//import io.netty.buffer.Unpooled;
+//import io.netty.channel.*;
+//import io.netty.channel.nio.NioEventLoopGroup;
+//import io.netty.channel.socket.SocketChannel;
+//import io.netty.channel.socket.nio.NioSocketChannel;
+//import io.netty.handler.timeout.IdleState;
+//import io.netty.handler.timeout.IdleStateEvent;
+//import io.netty.handler.timeout.IdleStateHandler;
+//
+//import java.util.Arrays;
+//import java.util.List;
+//import java.util.Random;
+//import java.util.concurrent.TimeUnit;
+//
+//public class NFIDTestClient1 {
+//    private static final String HOST = "localhost";
+//    private static final int PORT = 3728;
+//    private static final int HEARTBEAT_INTERVAL = 30; // 秒
+//    private static final int RECONNECT_DELAY = 5; // 重试间隔(秒)
+//
+//    // 测试帧数据
+//    private static final List<byte[]> TEST_FRAMES = Arrays.asList(
+//            hexStringToByteArray("5A000112000018000C00010000F04EE845D972D375340002013D08000DE3AA3482"),
+//            hexStringToByteArray("5A000112000018000CE28068940000502F57B4B649300002010D08000DE3AAF63F"),
+//            hexStringToByteArray("5A000112000018000C2C14F77EF04EE845D974502A340002012C08000DE3AA7930"),
+//            hexStringToByteArray("5A00010100004B00145238303038303030303230323030303030303038000001A1000C4A756E203130203230323000010011000002000E56312E30305F323031393036303903000A323032302D30362D31308FC5")
+//    );
+//
+//    // 心跳帧
+//    private static final byte[] HEARTBEAT_FRAME = hexStringToByteArray("5A00000001000000000000");
+//
+//    // 测试模式枚举
+//    private enum TestMode {
+//        NORMAL,      // 正常发送
+//        STICKY,      // 粘包模式
+//        SPLIT,       // 拆包模式
+//        MIXED        // 混合模式
+//    }
+//
+//    private static TestMode currentMode = TestMode.MIXED; // 默认使用混合模式
+//
+//    public static void main(String[] args) throws Exception {
+//        if (args.length > 0) {
+//            currentMode = TestMode.valueOf(args[0].toUpperCase());
+//        }
+//        System.out.println("Starting NFID test client in " + currentMode + " mode");
+//
+//        EventLoopGroup group = new NioEventLoopGroup();
+//        Bootstrap bootstrap = createBootstrap(group);
+//        connect(bootstrap, 0);
+//    }
+//
+//    private static Bootstrap createBootstrap(EventLoopGroup group) {
+//        Bootstrap b = new Bootstrap();
+//        b.group(group)
+//                .channel(NioSocketChannel.class)
+//                .handler(new ChannelInitializer<SocketChannel>() {
+//                    @Override
+//                    protected void initChannel(SocketChannel ch) {
+//                        ch.pipeline()
+//                                .addLast(new IdleStateHandler(0, HEARTBEAT_INTERVAL, 0, TimeUnit.SECONDS))
+//                                .addLast(new HeartbeatHandler())
+//                                .addLast(new FrameSenderHandler())
+//                                .addLast(new ResponseHandler());
+//                    }
+//                });
+//        return b;
+//    }
+//
+//    private static void connect(Bootstrap bootstrap, int attempt) {
+//        bootstrap.connect(HOST, PORT).addListener(new ChannelFutureListener() {
+//            @Override
+//            public void operationComplete(ChannelFuture future) throws Exception {
+//                if (future.isSuccess()) {
+//                    System.out.println("Connected to NFID server at " + HOST + ":" + PORT);
+//                    Channel channel = future.channel();
+//
+//                    channel.closeFuture().addListener(closeFuture -> {
+//                        System.out.println("Connection lost, attempting to reconnect...");
+//                        scheduleReconnect(bootstrap);
+//                    });
+//
+//                } else {
+//                    System.err.println("Connection attempt " + (attempt + 1) + " failed: " + future.cause().getMessage());
+//                    scheduleReconnect(bootstrap);
+//                }
+//            }
+//        });
+//    }
+//
+//    private static void scheduleReconnect(Bootstrap bootstrap) {
+//        bootstrap.config().group().schedule(() -> {
+//            System.out.println("Reconnecting...");
+//            connect(bootstrap, 0);
+//        }, RECONNECT_DELAY, TimeUnit.SECONDS);
+//    }
+//
+//    /**
+//     * 心跳处理器
+//     */
+//    private static class HeartbeatHandler extends ChannelInboundHandlerAdapter {
+//        @Override
+//        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
+//            if (evt instanceof IdleStateEvent) {
+//                IdleStateEvent e = (IdleStateEvent) evt;
+//                if (e.state() == IdleState.WRITER_IDLE) {
+//                    ctx.writeAndFlush(Unpooled.wrappedBuffer(HEARTBEAT_FRAME));
+//                    System.out.println("Sent heartbeat at " + System.currentTimeMillis());
+//                }
+//            }
+//        }
+//    }
+//
+//    /**
+//     * 数据帧发送处理器
+//     */
+//    private static class FrameSenderHandler extends ChannelInboundHandlerAdapter {
+//        private final Random random = new Random();
+//        private final int MIN_INTERVAL = 500; // 1秒
+//        private final int MAX_INTERVAL = 1500; // 3秒
+//
+//        @Override
+//        public void channelActive(ChannelHandlerContext ctx) {
+//            System.out.println("Channel active, starting frame sender in " + currentMode + " mode");
+//            scheduleSendFrame(ctx);
+//        }
+//
+//        private void scheduleSendFrame(ChannelHandlerContext ctx) {
+//            if (!ctx.channel().isActive()) return;
+//
+//            int delay = MIN_INTERVAL + random.nextInt(MAX_INTERVAL - MIN_INTERVAL);
+//            ctx.executor().schedule(() -> {
+//                if (ctx.channel().isActive()) {
+//                    sendFrameAccordingToMode(ctx);
+//                    scheduleSendFrame(ctx);
+//                }
+//            }, delay, TimeUnit.MILLISECONDS);
+//        }
+//
+//        private void sendFrameAccordingToMode(ChannelHandlerContext ctx) {
+//            switch (currentMode) {
+//                case NORMAL:
+//                    sendNormalFrame(ctx);
+//                    break;
+//                case STICKY:
+//                    sendStickyPackets(ctx);
+//                    break;
+//                case SPLIT:
+//                    sendSplitFrame(ctx);
+//                    break;
+//                case MIXED:
+//                    if (random.nextBoolean()) {
+//                        sendNormalFrame(ctx);
+//                    } else if (random.nextBoolean()) {
+//                        sendStickyPackets(ctx);
+//                    } else {
+//                        sendSplitFrame(ctx);
+//                    }
+//                    break;
+//            }
+//        }
+//
+//        private void sendNormalFrame(ChannelHandlerContext ctx) {
+//            byte[] frame = TEST_FRAMES.get(random.nextInt(TEST_FRAMES.size()));
+//            ctx.writeAndFlush(Unpooled.wrappedBuffer(frame));
+//            System.out.println("[NORMAL] Sent single frame: " + bytesToHex(frame));
+//        }
+//
+//        private void sendStickyPackets(ChannelHandlerContext ctx) {
+//            // 随机合并2-4个帧一起发送
+//            int frameCount = 2 + random.nextInt(3);
+//            ByteBuf buffer = ctx.alloc().buffer();
+//
+//            for (int i = 0; i < frameCount; i++) {
+//                byte[] frame = TEST_FRAMES.get(random.nextInt(TEST_FRAMES.size()));
+//                buffer.writeBytes(frame);
+//                System.out.println("[STICKY] Adding frame " + (i+1) + ": " + bytesToHex(frame));
+//            }
+//
+//            ctx.writeAndFlush(buffer);
+//            System.out.println("[STICKY] Sent " + frameCount + " frames as sticky packet");
+//        }
+//
+//        private void sendSplitFrame(ChannelHandlerContext ctx) {
+//            byte[] frame = TEST_FRAMES.get(random.nextInt(TEST_FRAMES.size()));
+//            int splitPoint = 5 + random.nextInt(frame.length - 10); // 确保分割点在中间
+//
+//            byte[] part1 = Arrays.copyOfRange(frame, 0, splitPoint);
+//            byte[] part2 = Arrays.copyOfRange(frame, splitPoint, frame.length);
+//
+//            System.out.println("[SPLIT] Splitting frame at position " + splitPoint +
+//                    " (total length: " + frame.length + ")");
+//
+//            // 发送第一部分
+//            ctx.writeAndFlush(Unpooled.wrappedBuffer(part1));
+//            System.out.println("[SPLIT] Sent first part: " + bytesToHex(part1));
+//
+//            // 延迟发送第二部分
+//            ctx.executor().schedule(() -> {
+//                if (ctx.channel().isActive()) {
+//                    ctx.writeAndFlush(Unpooled.wrappedBuffer(part2));
+//                    System.out.println("[SPLIT] Sent second part: " + bytesToHex(part2));
+//                }
+//            }, 100 + random.nextInt(400), TimeUnit.MILLISECONDS); // 100-500ms延迟
+//        }
+//    }
+//
+//    /**
+//     * 响应处理器
+//     */
+//    private static class ResponseHandler extends SimpleChannelInboundHandler<Object> {
+//        @Override
+//        protected void channelRead0(ChannelHandlerContext ctx, Object msg) {
+//            System.out.println("Received server response: " + msg);
+//        }
+//
+//        @Override
+//        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+//            System.err.println("Connection error: " + cause.getMessage());
+//            ctx.close();
+//        }
+//    }
+//
+//    // 辅助方法
+//    private static byte[] hexStringToByteArray(String s) {
+//        int len = s.length();
+//        byte[] data = new byte[len / 2];
+//        for (int i = 0; i < len; i += 2) {
+//            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+//                    + Character.digit(s.charAt(i+1), 16));
+//        }
+//        return data;
+//    }
+//
+//    private static String bytesToHex(byte[] bytes) {
+//        StringBuilder sb = new StringBuilder();
+//        for (byte b : bytes) {
+//            sb.append(String.format("%02X ", b));
+//        }
+//        return sb.toString().trim();
+//    }
+//}

+ 37 - 0
app-admin/src/main/java/com/ruoyi/web/core/wvp/ApiResponseParser.java

@@ -0,0 +1,37 @@
+//package com.ruoyi.web.core.wvp;
+//
+//import com.fasterxml.jackson.core.type.TypeReference;
+//import com.fasterxml.jackson.databind.JavaType;
+//import com.fasterxml.jackson.databind.ObjectMapper;
+//import com.ruoyi.web.core.wvp.domain.ApiResponse;
+//
+//public class ApiResponseParser {
+//    private static final ObjectMapper objectMapper = new ObjectMapper();
+//
+//    /**
+//     * 解析完整ApiResponse,包含状态码和消息
+//     * @param json JSON字符串
+//     * @param dataType data字段的类型
+//     * @return 完整的ApiResponse对象
+//     */
+//    public static <T> ApiResponse<T> parseFullResponse(String json, Class<T> dataType) throws Exception {
+//        // 构建包含泛型信息的JavaType
+//        JavaType javaType = objectMapper.getTypeFactory()
+//                .constructParametricType(ApiResponse.class, dataType);
+//
+//        return objectMapper.readValue(json, javaType);
+//    }
+//
+//    /**
+//     * 使用TypeReference处理复杂嵌套类型
+//     * @param json JSON字符串
+//     * @param typeReference 类型引用
+//     * @return 完整的ApiResponse对象
+//     */
+//    public static <T> ApiResponse<T> parseFullResponse(
+//            String json,
+//            TypeReference<ApiResponse<T>> typeReference) throws Exception {
+//
+//        return objectMapper.readValue(json, typeReference);
+//    }
+//}

+ 107 - 0
app-admin/src/main/java/com/ruoyi/web/core/wvp/DeviceHandler.java

@@ -0,0 +1,107 @@
+//package com.ruoyi.web.core.wvp;
+//
+//import com.fasterxml.jackson.core.type.TypeReference;
+//import com.ruoyi.common.utils.spring.SpringUtils;
+//import com.ruoyi.web.core.nfid.MultiReaderNFIDProcessor;
+//import com.ruoyi.web.core.wvp.domain.ApiResponse;
+//import com.ruoyi.common.constant.Constants;
+//import com.ruoyi.common.utils.StringUtils;
+//import com.ruoyi.common.utils.http.HttpResponse;
+//import com.ruoyi.common.utils.poi.ExcelUtil;
+//import com.ruoyi.web.core.wvp.domain.DeviceChannelExtend;
+//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.stereotype.Component;
+//
+//import java.net.HttpURLConnection;
+//import java.util.ArrayList;
+//import java.util.HashMap;
+//import java.util.List;
+//import java.util.Map;
+//
+//import static com.ruoyi.common.utils.http.HttpUtils.*;
+//
+//@Component
+//public class DeviceHandler {
+//    @Value(value = "${wvp.addr}")
+//    private String addr;
+//
+//    private final SessionHandler sessionHandler = SpringUtils.getBean(SessionHandler.class);
+//
+//    public static final Logger log = LoggerFactory.getLogger(ExcelUtil.class);
+//
+//    public static final String VIDEO_PLAY_START = "start";
+//
+//    public static final String VIDEO_PLAY_STOP = "stop";
+//
+//    //播放
+//    public Object playOrStop(String type,String deviceId,String channelId) throws Exception{
+//            String url= addr+ "/api/play/"+type+"/" + deviceId + "/" + channelId;
+//            Map<String, String> headers = new HashMap<>();
+//            String token = sessionHandler.getToken();
+//            if (StringUtils.isNotEmpty(token)) {
+//                headers.put("access-token", sessionHandler.getToken());
+//            }else{
+//                throw new RuntimeException("登录WVP失败");
+//            }
+//            HttpResponse response = sendHttpGet(url, StringUtils.EMPTY, Constants.UTF8,headers);
+//            if(response.getStatusCode() == HttpURLConnection.HTTP_OK){
+//                // 处理响应
+//                ApiResponse<Object> apiResponse = JsonParser.parseWithJavaType(response.getContent(), Object.class);
+//
+//                if (apiResponse.getCode()!=null && apiResponse.getCode() ==0) {
+//                    return apiResponse.getData();
+//                } else {
+//                    throw new RuntimeException(apiResponse.getMsg());
+//                }
+//            }else if (response.getStatusCode() == HttpURLConnection.HTTP_UNAUTHORIZED){
+//                //是否重新登录
+//                if (sessionHandler.needRelogin()){
+//                    //重新登录
+//                    sessionHandler.refreshToken();
+//                }
+//            }
+//            return null;
+//    }
+//
+//    //设备清单
+//    public List<DeviceChannelExtend> getDeviceList() throws Exception {
+//        try {
+//            String url= addr+ "/api/device/query/allChannels";
+//            Map<String, String> headers = new HashMap<>();
+//            String token = sessionHandler.getToken();
+//            if (StringUtils.isNotEmpty(token)) {
+//                headers.put("access-token", sessionHandler.getToken());
+//            }else{
+//                throw new RuntimeException("登录WVP失败");
+//            }
+//            HttpResponse response = sendHttpGet(url, StringUtils.EMPTY, Constants.UTF8,headers);
+//            if(response.getStatusCode() == HttpURLConnection.HTTP_OK){
+//                TypeReference<ApiResponse<List<DeviceChannelExtend>>> typeRef =
+//                        new TypeReference<ApiResponse<List<DeviceChannelExtend>>>() {};
+//                // 处理响应
+//                ApiResponse<List<DeviceChannelExtend>> apiResponse = ApiResponseParser.parseFullResponse(response.getContent(), typeRef);
+//
+//                if (apiResponse.getCode()!=null && apiResponse.getCode() ==0) {
+//                    return apiResponse.getData();
+//                } else {
+//                    throw new RuntimeException(apiResponse.getMsg());
+//                }
+//            }else if (response.getStatusCode() == HttpURLConnection.HTTP_UNAUTHORIZED){
+//                //是否重新登录
+//                if (sessionHandler.needRelogin()){
+//                    //重新登录
+//                    sessionHandler.refreshToken();
+//                }
+//                throw new Exception("重新登录");
+//            }
+//
+//        }catch (Exception e){
+//            log.error("请求失败: " + e.getMessage());
+//            throw e;
+//        }
+//        return new ArrayList<>();
+//    }
+//}

+ 70 - 0
app-admin/src/main/java/com/ruoyi/web/core/wvp/JsonParser.java

@@ -0,0 +1,70 @@
+//package com.ruoyi.web.core.wvp;
+//
+//import com.fasterxml.jackson.core.type.TypeReference;
+//import com.fasterxml.jackson.databind.JavaType;
+//import com.fasterxml.jackson.databind.ObjectMapper;
+//import com.ruoyi.web.core.wvp.domain.ApiResponse;
+//
+//import java.util.List;
+//import java.util.Map;
+//
+//public class JsonParser {
+//    private static final ObjectMapper objectMapper = new ObjectMapper();
+//
+//    // 方法1: 使用TypeReference
+//    public static <T> ApiResponse<T> parseWithTypeReference(String json, Class<T> dataType) {
+//        try {
+//            JavaType type = objectMapper.getTypeFactory()
+//                    .constructParametricType(ApiResponse.class, dataType);
+//            return objectMapper.readValue(json, type);
+//        } catch (Exception e) {
+//            throw new RuntimeException("JSON解析失败: " + json, e);
+//        }
+//    }
+//
+//    // 通用解析方法
+//    public static <T> ApiResponse<T> parseResponse(String json, JavaType dataType) {
+//        try {
+//            JavaType responseType = objectMapper.getTypeFactory()
+//                    .constructParametricType(ApiResponse.class, dataType);
+//            return objectMapper.readValue(json, responseType);
+//        } catch (Exception e) {
+//            throw new RuntimeException("JSON解析失败", e);
+//        }
+//    }
+//    // 解析为指定类型的便捷方法
+//    public static <T> ApiResponse<T> parseToTypedResponse(String json, Class<T> dataType) {
+//        return parseResponse(json, objectMapper.getTypeFactory().constructType(dataType));
+//    }
+//
+//    // 方法2: 使用JavaType
+//    public static <T> ApiResponse<T> parseWithJavaType(String json, Class<T> dataType) {
+//        try {
+//            JavaType javaType = objectMapper.getTypeFactory()
+//                    .constructParametricType(ApiResponse.class, dataType);
+//            return objectMapper.readValue(json, javaType);
+//        } catch (Exception e) {
+//            throw new RuntimeException("JSON解析失败", e);
+//        }
+//    }
+//
+//    // 方法3: 处理嵌套泛型(如List<T>)
+//    public static <T> ApiResponse<List<T>> parseListResponse(String json, Class<T> elementType) {
+//        try {
+//            JavaType elementJavaType = objectMapper.getTypeFactory()
+//                    .constructType(elementType);
+//            JavaType listType = objectMapper.getTypeFactory()
+//                    .constructCollectionType(List.class, elementType);
+//            JavaType responseType = objectMapper.getTypeFactory()
+//                    .constructParametricType(ApiResponse.class, listType);
+//            return objectMapper.readValue(json, responseType);
+//        } catch (Exception e) {
+//            throw new RuntimeException("JSON解析失败", e);
+//        }
+//    }
+//
+//    // 转换为Map
+//    public static Map<String, Object> parseJsonToMap(String json) throws Exception {
+//        return objectMapper.readValue(json, new TypeReference<Map<String, Object>>() {});
+//    }
+//}

+ 127 - 0
app-admin/src/main/java/com/ruoyi/web/core/wvp/SessionHandler.java

@@ -0,0 +1,127 @@
+//package com.ruoyi.web.core.wvp;
+//import com.ruoyi.common.constant.Constants;
+//import com.ruoyi.common.utils.StringUtils;
+//import com.ruoyi.common.utils.http.HttpResponse;
+//import com.ruoyi.web.core.wvp.domain.ApiResponse;
+//import com.ruoyi.web.core.wvp.domain.Login;
+//import com.ruoyi.common.core.redis.RedisCache;
+//import com.ruoyi.common.utils.poi.ExcelUtil;
+//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.stereotype.Component;
+//
+//import java.net.HttpURLConnection;
+//import java.nio.charset.StandardCharsets;
+//import java.security.MessageDigest;
+//import java.security.NoSuchAlgorithmException;
+//import java.util.HashMap;
+//import java.util.concurrent.Executors;
+//import java.util.concurrent.ScheduledExecutorService;
+//import java.util.concurrent.TimeUnit;
+//
+//import static com.ruoyi.common.utils.http.HttpUtils.sendHttpGet;
+//
+//@Component
+//public class SessionHandler {
+//    @Autowired
+//    private RedisCache redisCache;
+//
+//    @Value(value = "${wvp.addr}")
+//    private String addr;
+//
+//    @Value(value = "${wvp.username}")
+//    private String username;
+//
+//    @Value(value = "${wvp.password}")
+//    private String password;
+//
+//    private final String WVP_TOKEN = "WVP_TOKEN";
+//
+//    //token有效期 10分钟
+//    private final Integer WVP_TOKEN_EXPIRATION = 10;
+//
+//    public final Logger log = LoggerFactory.getLogger(ExcelUtil.class);
+//
+//    private long lastLoginTime;
+//
+//    public SessionHandler(){
+//        // 启动定时任务
+//        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
+//        scheduler.scheduleAtFixedRate(
+//                this::loginWVP,
+//                1,
+//                20*60,
+//                TimeUnit.SECONDS
+//        );
+//    }
+//
+//    //登录WVP
+//    private String loginWVP() {
+//        try {
+//            String url= addr+ "/api/user/login?username="+username+"&password="+hashPassword(password);
+//            HttpResponse response = sendHttpGet(url, StringUtils.EMPTY, Constants.UTF8,new HashMap<>());
+//
+//            if(response.getStatusCode() == HttpURLConnection.HTTP_OK) {
+//                // 处理响应
+//                ApiResponse<Login> apiResponse = ApiResponseParser.parseFullResponse(response.getContent(), Login.class);
+//                if (apiResponse.getCode() != null && apiResponse.getCode() == 0) {
+//                    Login loginInfo = apiResponse.getData();
+//                    redisCache.setCacheObject(WVP_TOKEN, loginInfo.getAccessToken(), WVP_TOKEN_EXPIRATION, TimeUnit.MINUTES);
+//                    lastLoginTime = System.currentTimeMillis();
+//                    return loginInfo.getAccessToken();
+//                } else {
+//                    throw new RuntimeException(apiResponse.getMsg());
+//                }
+//            }else{
+//                throw new RuntimeException("登录失败");
+//            }
+//        }catch (Exception e){
+//           log.error("请求失败: " + e.getMessage());
+//        }
+//        return null;
+//    }
+//
+//    public long getLastLoginTime(){
+//        return lastLoginTime;
+//    }
+//
+//    public String getToken(){
+//        String token =  redisCache.getCacheObject(WVP_TOKEN);
+//        if (token == null) {
+//           return loginWVP();
+//        }
+//        return token;
+//    }
+//
+//    public boolean needRelogin(){
+//        return  (System.currentTimeMillis() - lastLoginTime > WVP_TOKEN_EXPIRATION*60*1000);
+//    }
+//
+//    public String refreshToken(){
+//        redisCache.deleteObject(WVP_TOKEN);
+//        return loginWVP();
+//    }
+//
+//    private static String hashPassword(String password) {
+//        try {
+//            // MD5
+//            MessageDigest digest = MessageDigest.getInstance("MD5");
+//            byte[] hashBytes = digest.digest(password.getBytes(StandardCharsets.UTF_8));
+//
+//            // byte数组转成16进制的字符串
+//            StringBuilder hexString = new StringBuilder();
+//            for (byte b : hashBytes) {
+//                String hex = Integer.toHexString(0xff & b);
+//                if (hex.length() == 1) {
+//                    hexString.append('0');
+//                }
+//                hexString.append(hex);
+//            }
+//            return hexString.toString();
+//        } catch (NoSuchAlgorithmException e) {
+//            throw new RuntimeException("MD5 algorithm not found", e);
+//        }
+//    }
+//}

+ 38 - 0
app-admin/src/main/java/com/ruoyi/web/core/wvp/domain/ApiResponse.java

@@ -0,0 +1,38 @@
+//package com.ruoyi.web.core.wvp.domain;
+//
+//import org.apache.poi.ss.formula.functions.T;
+//
+//public class ApiResponse<T> {
+//    private Integer code;
+//    private T data;
+//    private String msg;
+//    public void setCode(Integer code)
+//    {
+//        this.code = code;
+//    }
+//
+//    public Integer getCode()
+//    {
+//        return code;
+//    }
+//
+//    public void setData(T data)
+//    {
+//        this.data = data;
+//    }
+//
+//    public T getData()
+//    {
+//        return data;
+//    }
+//
+//    public void setMsg(String msg)
+//    {
+//        this.msg = msg;
+//    }
+//
+//    public String getMsg()
+//    {
+//        return msg;
+//    }
+//}

+ 612 - 0
app-admin/src/main/java/com/ruoyi/web/core/wvp/domain/DeviceChannelExtend.java

@@ -0,0 +1,612 @@
+//package com.ruoyi.web.core.wvp.domain;
+//
+//public class DeviceChannelExtend {
+//
+//    /**
+//     * 数据库自增ID
+//     */
+//    private int id;
+//
+//    /**
+//     * 通道id
+//     */
+//    private String channelId;
+//
+//    /**
+//     * 设备id
+//     */
+//    private String deviceId;
+//
+//    /**
+//     * 通道名
+//     */
+//    private String name;
+//
+//    private String deviceName;
+//
+//    private boolean deviceOnline;
+//
+//    /**
+//     * 生产厂商
+//     */
+//    private String manufacture;
+//
+//    /**
+//     * 型号
+//     */
+//    private String model;
+//
+//    /**
+//     * 设备归属
+//     */
+//    private String owner;
+//
+//    /**
+//     * 行政区域
+//     */
+//    private String civilCode;
+//
+//    /**
+//     * 警区
+//     */
+//    private String block;
+//
+//    /**
+//     * 安装地址
+//     */
+//    private String address;
+//
+//    /**
+//     * 是否有子设备 1有, 0没有
+//     */
+//    private int parental;
+//
+//    /**
+//     * 父级id
+//     */
+//    private String parentId;
+//
+//    /**
+//     * 信令安全模式  缺省为0; 0:不采用; 2: S/MIME签名方式; 3: S/ MIME加密签名同时采用方式; 4:数字摘要方式
+//     */
+//    private int safetyWay;
+//
+//    /**
+//     * 注册方式 缺省为1;1:符合IETFRFC3261标准的认证注册模 式; 2:基于口令的双向认证注册模式; 3:基于数字证书的双向认证注册模式
+//     */
+//    private int registerWay;
+//
+//    /**
+//     * 证书序列号
+//     */
+//    private String certNum;
+//
+//    /**
+//     * 证书有效标识 缺省为0;证书有效标识:0:无效1: 有效
+//     */
+//    private int certifiable;
+//
+//    /**
+//     * 证书无效原因码
+//     */
+//    private int errCode;
+//
+//    /**
+//     * 证书终止有效期
+//     */
+//    private String endTime;
+//
+//    /**
+//     * 保密属性 缺省为0; 0:不涉密, 1:涉密
+//     */
+//    private String secrecy;
+//
+//    /**
+//     * IP地址
+//     */
+//    private String ipAddress;
+//
+//    /**
+//     * 端口号
+//     */
+//    private int port;
+//
+//    /**
+//     * 密码
+//     */
+//    private String password;
+//
+//    /**
+//     * 云台类型
+//     */
+//    private int PTZType;
+//
+//    /**
+//     * 云台类型描述字符串
+//     */
+//    private String PTZTypeText;
+//
+//    /**
+//     * 创建时间
+//     */
+//    private String createTime;
+//
+//    /**
+//     * 更新时间
+//     */
+//    private String updateTime;
+//
+//    /**
+//     * 在线/离线
+//     * 1在线,0离线
+//     * 默认在线
+//     * 信令:
+//     * <Status>ON</Status>
+//     * <Status>OFF</Status>
+//     * 遇到过NVR下的IPC下发信令可以推流, 但是 Status 响应 OFF
+//     */
+//    private String status;
+//
+//    /**
+//     * 经度
+//     */
+//    private double longitude;
+//
+//    /**
+//     * 纬度
+//     */
+//    private double latitude;
+//
+//    /**
+//     * 经度 GCJ02
+//     */
+//    private double longitudeGcj02;
+//
+//    /**
+//     * 纬度 GCJ02
+//     */
+//    private double latitudeGcj02;
+//
+//    /**
+//     * 经度 WGS84
+//     */
+//    private double longitudeWgs84;
+//
+//    /**
+//     * 纬度 WGS84
+//     */
+//    private double latitudeWgs84;
+//
+//    /**
+//     * 子设备数
+//     */
+//    private int subCount;
+//
+//    /**
+//     * 流唯一编号,存在表示正在直播
+//     */
+//    private String streamId;
+//
+//    /**
+//     *  是否含有音频
+//     */
+//    private boolean hasAudio;
+//
+//    /**
+//     * 标记通道的类型,0->国标通道 1->直播流通道 2->业务分组/虚拟组织/行政区划
+//     */
+//    private int channelType;
+//
+//    /**
+//     * 业务分组
+//     */
+//    private String businessGroupId;
+//
+//    /**
+//     * GPS的更新时间
+//     */
+//    private String gpsTime;
+//
+//    // Constructors
+//    public DeviceChannelExtend() {
+//    }
+//
+//    public DeviceChannelExtend(String channelId, String deviceId, String name) {
+//        this.channelId = channelId;
+//        this.deviceId = deviceId;
+//        this.name = name;
+//    }
+//
+//    // Getters and Setters
+//    public int getId() {
+//        return id;
+//    }
+//
+//    public void setId(int id) {
+//        this.id = id;
+//    }
+//
+//    public String getChannelId() {
+//        return channelId;
+//    }
+//
+//    public void setChannelId(String channelId) {
+//        this.channelId = channelId;
+//    }
+//
+//    public String getDeviceId() {
+//        return deviceId;
+//    }
+//
+//    public void setDeviceId(String deviceId) {
+//        this.deviceId = deviceId;
+//    }
+//
+//    public String getName() {
+//        return name;
+//    }
+//
+//    public void setName(String name) {
+//        this.name = name;
+//    }
+//
+//    public String getDeviceName() {
+//        return deviceName;
+//    }
+//
+//    public void setDeviceName(String deviceName) {
+//        this.deviceName = deviceName;
+//    }
+//
+//    public boolean isDeviceOnline() {
+//        return deviceOnline;
+//    }
+//
+//    public void setDeviceOnline(boolean deviceOnline) {
+//        this.deviceOnline = deviceOnline;
+//    }
+//
+//    public String getManufacture() {
+//        return manufacture;
+//    }
+//
+//    public void setManufacture(String manufacture) {
+//        this.manufacture = manufacture;
+//    }
+//
+//    public String getModel() {
+//        return model;
+//    }
+//
+//    public void setModel(String model) {
+//        this.model = model;
+//    }
+//
+//    public String getOwner() {
+//        return owner;
+//    }
+//
+//    public void setOwner(String owner) {
+//        this.owner = owner;
+//    }
+//
+//    public String getCivilCode() {
+//        return civilCode;
+//    }
+//
+//    public void setCivilCode(String civilCode) {
+//        this.civilCode = civilCode;
+//    }
+//
+//    public String getBlock() {
+//        return block;
+//    }
+//
+//    public void setBlock(String block) {
+//        this.block = block;
+//    }
+//
+//    public String getAddress() {
+//        return address;
+//    }
+//
+//    public void setAddress(String address) {
+//        this.address = address;
+//    }
+//
+//    public int getParental() {
+//        return parental;
+//    }
+//
+//    public void setParental(int parental) {
+//        this.parental = parental;
+//    }
+//
+//    public String getParentId() {
+//        return parentId;
+//    }
+//
+//    public void setParentId(String parentId) {
+//        this.parentId = parentId;
+//    }
+//
+//    public int getSafetyWay() {
+//        return safetyWay;
+//    }
+//
+//    public void setSafetyWay(int safetyWay) {
+//        this.safetyWay = safetyWay;
+//    }
+//
+//    public int getRegisterWay() {
+//        return registerWay;
+//    }
+//
+//    public void setRegisterWay(int registerWay) {
+//        this.registerWay = registerWay;
+//    }
+//
+//    public String getCertNum() {
+//        return certNum;
+//    }
+//
+//    public void setCertNum(String certNum) {
+//        this.certNum = certNum;
+//    }
+//
+//    public int getCertifiable() {
+//        return certifiable;
+//    }
+//
+//    public void setCertifiable(int certifiable) {
+//        this.certifiable = certifiable;
+//    }
+//
+//    public int getErrCode() {
+//        return errCode;
+//    }
+//
+//    public void setErrCode(int errCode) {
+//        this.errCode = errCode;
+//    }
+//
+//    public String getEndTime() {
+//        return endTime;
+//    }
+//
+//    public void setEndTime(String endTime) {
+//        this.endTime = endTime;
+//    }
+//
+//    public String getSecrecy() {
+//        return secrecy;
+//    }
+//
+//    public void setSecrecy(String secrecy) {
+//        this.secrecy = secrecy;
+//    }
+//
+//    public String getIpAddress() {
+//        return ipAddress;
+//    }
+//
+//    public void setIpAddress(String ipAddress) {
+//        this.ipAddress = ipAddress;
+//    }
+//
+//    public int getPort() {
+//        return port;
+//    }
+//
+//    public void setPort(int port) {
+//        this.port = port;
+//    }
+//
+//    public String getPassword() {
+//        return password;
+//    }
+//
+//    public void setPassword(String password) {
+//        this.password = password;
+//    }
+//
+//    public int getPTZType() {
+//        return PTZType;
+//    }
+//
+//    public void setPTZType(int PTZType) {
+//        this.PTZType = PTZType;
+//        switch (PTZType) {
+//            case 0:
+//                this.PTZTypeText = "未知";
+//                break;
+//            case 1:
+//                this.PTZTypeText = "球机";
+//                break;
+//            case 2:
+//                this.PTZTypeText = "半球";
+//                break;
+//            case 3:
+//                this.PTZTypeText = "固定枪机";
+//                break;
+//            case 4:
+//                this.PTZTypeText = "遥控枪机";
+//                break;
+//        }
+//    }
+//
+//    public String getPTZTypeText() {
+//        return PTZTypeText;
+//    }
+//
+//    public void setPTZTypeText(String PTZTypeText) {
+//        this.PTZTypeText = PTZTypeText;
+//    }
+//
+//    public String getCreateTime() {
+//        return createTime;
+//    }
+//
+//    public void setCreateTime(String createTime) {
+//        this.createTime = createTime;
+//    }
+//
+//    public String getUpdateTime() {
+//        return updateTime;
+//    }
+//
+//    public void setUpdateTime(String updateTime) {
+//        this.updateTime = updateTime;
+//    }
+//
+//    public String getStatus() {
+//        return status;
+//    }
+//
+//    public void setStatus(String status) {
+//        this.status = status;
+//    }
+//
+//    public double getLongitude() {
+//        return longitude;
+//    }
+//
+//    public void setLongitude(double longitude) {
+//        this.longitude = longitude;
+//    }
+//
+//    public double getLatitude() {
+//        return latitude;
+//    }
+//
+//    public void setLatitude(double latitude) {
+//        this.latitude = latitude;
+//    }
+//
+//    public double getLongitudeGcj02() {
+//        return longitudeGcj02;
+//    }
+//
+//    public void setLongitudeGcj02(double longitudeGcj02) {
+//        this.longitudeGcj02 = longitudeGcj02;
+//    }
+//
+//    public double getLatitudeGcj02() {
+//        return latitudeGcj02;
+//    }
+//
+//    public void setLatitudeGcj02(double latitudeGcj02) {
+//        this.latitudeGcj02 = latitudeGcj02;
+//    }
+//
+//    public double getLongitudeWgs84() {
+//        return longitudeWgs84;
+//    }
+//
+//    public void setLongitudeWgs84(double longitudeWgs84) {
+//        this.longitudeWgs84 = longitudeWgs84;
+//    }
+//
+//    public double getLatitudeWgs84() {
+//        return latitudeWgs84;
+//    }
+//
+//    public void setLatitudeWgs84(double latitudeWgs84) {
+//        this.latitudeWgs84 = latitudeWgs84;
+//    }
+//
+//    public int getSubCount() {
+//        return subCount;
+//    }
+//
+//    public void setSubCount(int subCount) {
+//        this.subCount = subCount;
+//    }
+//
+//    public String getStreamId() {
+//        return streamId;
+//    }
+//
+//    public void setStreamId(String streamId) {
+//        this.streamId = streamId;
+//    }
+//
+//    public boolean isHasAudio() {
+//        return hasAudio;
+//    }
+//
+//    public void setHasAudio(boolean hasAudio) {
+//        this.hasAudio = hasAudio;
+//    }
+//
+//    public int getChannelType() {
+//        return channelType;
+//    }
+//
+//    public void setChannelType(int channelType) {
+//        this.channelType = channelType;
+//    }
+//
+//    public String getBusinessGroupId() {
+//        return businessGroupId;
+//    }
+//
+//    public void setBusinessGroupId(String businessGroupId) {
+//        this.businessGroupId = businessGroupId;
+//    }
+//
+//    public String getGpsTime() {
+//        return gpsTime;
+//    }
+//
+//    public void setGpsTime(String gpsTime) {
+//        this.gpsTime = gpsTime;
+//    }
+//
+//    @Override
+//    public String toString() {
+//        return "DeviceChannelExtend{" +
+//                "id=" + id +
+//                ", channelId='" + channelId + '\'' +
+//                ", deviceId='" + deviceId + '\'' +
+//                ", name='" + name + '\'' +
+//                ", deviceName='" + deviceName + '\'' +
+//                ", deviceOnline=" + deviceOnline +
+//                ", manufacture='" + manufacture + '\'' +
+//                ", model='" + model + '\'' +
+//                ", owner='" + owner + '\'' +
+//                ", civilCode='" + civilCode + '\'' +
+//                ", block='" + block + '\'' +
+//                ", address='" + address + '\'' +
+//                ", parental=" + parental +
+//                ", parentId='" + parentId + '\'' +
+//                ", safetyWay=" + safetyWay +
+//                ", registerWay=" + registerWay +
+//                ", certNum='" + certNum + '\'' +
+//                ", certifiable=" + certifiable +
+//                ", errCode=" + errCode +
+//                ", endTime='" + endTime + '\'' +
+//                ", secrecy='" + secrecy + '\'' +
+//                ", ipAddress='" + ipAddress + '\'' +
+//                ", port=" + port +
+//                ", password='" + password + '\'' +
+//                ", PTZType=" + PTZType +
+//                ", PTZTypeText='" + PTZTypeText + '\'' +
+//                ", createTime='" + createTime + '\'' +
+//                ", updateTime='" + updateTime + '\'' +
+//                ", status='" + status + '\'' +
+//                ", longitude=" + longitude +
+//                ", latitude=" + latitude +
+//                ", longitudeGcj02=" + longitudeGcj02 +
+//                ", latitudeGcj02=" + latitudeGcj02 +
+//                ", longitudeWgs84=" + longitudeWgs84 +
+//                ", latitudeWgs84=" + latitudeWgs84 +
+//                ", subCount=" + subCount +
+//                ", streamId='" + streamId + '\'' +
+//                ", hasAudio=" + hasAudio +
+//                ", channelType=" + channelType +
+//                ", businessGroupId='" + businessGroupId + '\'' +
+//                ", gpsTime='" + gpsTime + '\'' +
+//                '}';
+//    }
+//}

+ 15 - 0
app-admin/src/main/java/com/ruoyi/web/core/wvp/domain/DeviceChannelList.java

@@ -0,0 +1,15 @@
+//package com.ruoyi.web.core.wvp.domain;
+//
+//import java.util.List;
+//
+//public class DeviceChannelList {
+//    private List<DeviceChannelExtend> channelList;
+//
+//    public List<DeviceChannelExtend> getChannelList() {
+//        return channelList;
+//    }
+//
+//    public void setChannelList(List<DeviceChannelExtend> channelList) {
+//        this.channelList = channelList;
+//    }
+//}

+ 170 - 0
app-admin/src/main/java/com/ruoyi/web/core/wvp/domain/Login.java

@@ -0,0 +1,170 @@
+//package com.ruoyi.web.core.wvp.domain;
+//
+//import com.fasterxml.jackson.annotation.JsonFormat;
+//import java.util.Date;
+//
+//public class Login {
+//    private String accessToken;
+//    private String serverId;
+//    private Long id;
+//    private Boolean enabled;
+//    private String pushKey;
+//    private Role role;
+//    private String username;
+//    private Boolean accountNonExpired;
+//    private Boolean accountNonLocked;
+//    private Boolean credentialsNonExpired;
+//    private String password;
+//    private Object authorities;
+//
+//    // getters and setters
+//    public String getAccessToken() {
+//        return accessToken;
+//    }
+//
+//    public void setAccessToken(String accessToken) {
+//        this.accessToken = accessToken;
+//    }
+//
+//    public String getServerId() {
+//        return serverId;
+//    }
+//
+//    public void setServerId(String serverId) {
+//        this.serverId = serverId;
+//    }
+//
+//    public Long getId() {
+//        return id;
+//    }
+//
+//    public void setId(Long id) {
+//        this.id = id;
+//    }
+//
+//    public Boolean getEnabled() {
+//        return enabled;
+//    }
+//
+//    public void setEnabled(Boolean enabled) {
+//        this.enabled = enabled;
+//    }
+//
+//    public String getPushKey() {
+//        return pushKey;
+//    }
+//
+//    public void setPushKey(String pushKey) {
+//        this.pushKey = pushKey;
+//    }
+//
+//    public Role getRole() {
+//        return role;
+//    }
+//
+//    public void setRole(Role role) {
+//        this.role = role;
+//    }
+//
+//    public String getUsername() {
+//        return username;
+//    }
+//
+//    public void setUsername(String username) {
+//        this.username = username;
+//    }
+//
+//    public Boolean getAccountNonExpired() {
+//        return accountNonExpired;
+//    }
+//
+//    public void setAccountNonExpired(Boolean accountNonExpired) {
+//        this.accountNonExpired = accountNonExpired;
+//    }
+//
+//    public Boolean getAccountNonLocked() {
+//        return accountNonLocked;
+//    }
+//
+//    public void setAccountNonLocked(Boolean accountNonLocked) {
+//        this.accountNonLocked = accountNonLocked;
+//    }
+//
+//    public Boolean getCredentialsNonExpired() {
+//        return credentialsNonExpired;
+//    }
+//
+//    public void setCredentialsNonExpired(Boolean credentialsNonExpired) {
+//        this.credentialsNonExpired = credentialsNonExpired;
+//    }
+//
+//    public String getPassword() {
+//        return password;
+//    }
+//
+//    public void setPassword(String password) {
+//        this.password = password;
+//    }
+//
+//    public Object getAuthorities() {
+//        return authorities;
+//    }
+//
+//    public void setAuthorities(Object authorities) {
+//        this.authorities = authorities;
+//    }
+//
+//    // Role内部类
+//    public static class Role {
+//        private Long id;
+//        private String name;
+//        private String authority;
+//
+//        @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+//        private Date createTime;
+//
+//        @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+//        private Date updateTime;
+//
+//        // getters and setters
+//        public Long getId() {
+//            return id;
+//        }
+//
+//        public void setId(Long id) {
+//            this.id = id;
+//        }
+//
+//        public String getName() {
+//            return name;
+//        }
+//
+//        public void setName(String name) {
+//            this.name = name;
+//        }
+//
+//        public String getAuthority() {
+//            return authority;
+//        }
+//
+//        public void setAuthority(String authority) {
+//            this.authority = authority;
+//        }
+//
+//        public Date getCreateTime() {
+//            return createTime;
+//        }
+//
+//        public void setCreateTime(Date createTime) {
+//            this.createTime = createTime;
+//        }
+//
+//        public Date getUpdateTime() {
+//            return updateTime;
+//        }
+//
+//        public void setUpdateTime(Date updateTime) {
+//            this.updateTime = updateTime;
+//        }
+//    }
+//}

+ 37 - 0
app-admin/src/main/java/com/ruoyi/web/primary/controller/DatabaseMetadataController.java

@@ -0,0 +1,37 @@
+package com.ruoyi.web.primary.controller;
+
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.web.primary.entity.DatabaseMetadata;
+import com.ruoyi.web.primary.service.IDatabaseMetadataService;
+import com.ruoyi.web.v2.v1.entity.JsStockInfoRequest;
+import com.ruoyi.web.v2.v1.service.IJsStockInfoService;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+
+import static com.ruoyi.common.core.domain.AjaxResult.error;
+import static com.ruoyi.common.core.domain.AjaxResult.success;
+
+/**
+ * <p>
+ *  前端控制器
+ * </p>
+ *
+ * @author author
+ * @since 2025-11-25
+ */
+@RestController
+@RequestMapping("/database-metadata")
+public class DatabaseMetadataController {
+    @Autowired
+    private IDatabaseMetadataService metadataService;
+    @GetMapping("/list/list")
+    public AjaxResult listVillageSpecialByPage() {
+        return success(metadataService.list(new QueryWrapper<DatabaseMetadata>()
+                .eq("db_status",1).select("db_name , username,version,version_name")));
+    }
+}

+ 45 - 0
app-admin/src/main/java/com/ruoyi/web/primary/entity/DatabaseMetadata.java

@@ -0,0 +1,45 @@
+package com.ruoyi.web.primary.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * <p>
+ * 
+ * </p>
+ *
+ * @author author
+ * @since 2025-11-25
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+@TableName("database_metadata")
+public class DatabaseMetadata implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 名字
+     */
+    @TableId(value = "db_name", type = IdType.AUTO)
+    private String dbName;
+
+    private String jdbcUrl;
+
+    private String username;
+
+    private String password;
+    private String dbUsername;
+    private String version;
+    private String versionName;
+    private Integer dbStatus;
+    private String userId;
+
+
+}

+ 24 - 0
app-admin/src/main/java/com/ruoyi/web/primary/service/IDatabaseMetadataService.java

@@ -0,0 +1,24 @@
+package com.ruoyi.web.primary.service;
+
+import com.ruoyi.web.primary.entity.DatabaseMetadata;
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.springframework.beans.PropertyValues;
+
+import java.util.List;
+
+/**
+ * <p>
+ *  服务类
+ * </p>
+ *
+ * @author author
+ * @since 2025-11-25
+ */
+public interface IDatabaseMetadataService extends IService<DatabaseMetadata> {
+
+    List<DatabaseMetadata> getAllConfigs();
+
+    DatabaseMetadata selectByName(String dbName);
+
+    DatabaseMetadata selectByAccountId(String userId);
+}

+ 36 - 0
app-admin/src/main/java/com/ruoyi/web/primary/service/impl/DatabaseMetadataServiceImpl.java

@@ -0,0 +1,36 @@
+package com.ruoyi.web.primary.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.ruoyi.web.primary.entity.DatabaseMetadata;
+import com.ruoyi.web.v2.v1.mapper.DatabaseMetadataMapper;
+import com.ruoyi.web.primary.service.IDatabaseMetadataService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * <p>
+ *  服务实现类
+ * </p>
+ *
+ * @author author
+ * @since 2025-11-25
+ */
+@Service
+public class DatabaseMetadataServiceImpl extends ServiceImpl<DatabaseMetadataMapper, DatabaseMetadata> implements IDatabaseMetadataService {
+
+    @Override
+    public List<DatabaseMetadata> getAllConfigs() {
+        return this.list();
+    }
+
+    @Override
+    public DatabaseMetadata selectByName(String dbName) {
+        return this.getOne(new QueryWrapper<DatabaseMetadata>().eq("db_name",dbName));
+    }
+    @Override
+    public DatabaseMetadata selectByAccountId(String userId) {
+        return this.getOne(new QueryWrapper<DatabaseMetadata>().eq("user_id",userId));
+    }
+}

+ 65 - 0
app-admin/src/main/java/com/ruoyi/web/v2/server/EnvInputServer.java

@@ -0,0 +1,65 @@
+package com.ruoyi.web.v2.server;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.*;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class EnvInputServer {
+    @Autowired
+    private EnvInputServerHandler serverHandler;
+    //监听端口
+//    private int port = 3729;
+//    private int port = 3949;
+    private int port = 3929;
+    //创建构造方法
+    public EnvInputServer(){
+    }
+
+
+/**
+ *
+ * 功能描述: 启动方法前台多个服务  处理多个线程
+ *
+ * @param:
+ * @return:
+ * @auther: LiGang
+ * @date: 2019/3/26 11:31
+ */
+    /**
+     * 启动流程
+     */
+    public   void run() throws InterruptedException {
+        //配置服务端线程组
+        EventLoopGroup bossGroup=new NioEventLoopGroup();
+        EventLoopGroup workGroup=new NioEventLoopGroup();
+
+        try{
+            //引导整个server的启动
+            ServerBootstrap serverBootstrap = new ServerBootstrap();
+            serverBootstrap.group(bossGroup,workGroup)
+                    .channel(NioServerSocketChannel.class)    //指定处理的连接类型
+                    .childHandler(new ChannelInitializer<SocketChannel>() {
+                        @Override
+                        protected void initChannel(SocketChannel socketChannel) throws Exception {
+                            System.out.println("连接建立");
+                            socketChannel.pipeline().addLast(serverHandler);
+                        }
+                    });
+            System.out.println("# 耳标及采集器设备数据接收服务器已经启动,监听端口(Port):"+port);
+            System.out.println("# 准备接收数据:");
+            //绑定端口,同步等待成功
+            ChannelFuture cf = serverBootstrap.bind(port).sync();
+            // 等待服务端监听端口关闭
+            cf.channel().closeFuture().sync();
+        }finally {
+            //优雅的退出
+            bossGroup.shutdownGracefully();
+            workGroup.shutdownGracefully();
+        }
+    }
+}

+ 210 - 0
app-admin/src/main/java/com/ruoyi/web/v2/server/EnvInputServerHandler.java

@@ -0,0 +1,210 @@
+package com.ruoyi.web.v2.server;
+
+
+import com.ruoyi.web.v2.v1.model.ProtSj;
+import com.ruoyi.web.v2.v1.service.IProtSjService;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.util.Date;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static ch.qos.logback.core.encoder.ByteArrayUtil.hexStringToByteArray;
+
+
+/**
+ * @Project : huimv.shiwan
+ * @Package : com.huimv.biosafety.uface.controller
+ * @Description : TODO
+ * @Version : 1.0
+ * @Author : ZhuoNing
+ * @Create : 2020-12-25
+ **/
+@ChannelHandler.Sharable
+@Component
+public class EnvInputServerHandler extends ChannelInboundHandlerAdapter {
+    private static final Logger log = LoggerFactory.getLogger(EnvInputServerHandler.class);
+    private StringBuilder askTextSb = null;
+
+    @Autowired
+    private IProtSjService protSjService;
+
+    private Map<String, String> channelMap = new ConcurrentHashMap<>();
+
+    @Override
+    public void channelActive(ChannelHandlerContext ctx) throws Exception {
+        // 当客户端连接时,打印出客户端的 IP 地址
+        System.out.println("连接成功");
+        ByteBuf bufff = Unpooled.buffer();
+        bufff.writeBytes(hexString2Bytes("5A000101000000DCE5"));
+        ctx.writeAndFlush(bufff);
+        super.channelActive(ctx);
+    }
+
+    // 设备数据的前缀
+    private static final String DEVICE_PREFIX = "device:";
+    private  static Integer INDEX = 1;
+    //
+    public void appendClientAsk(String text) {
+        if (this.askTextSb == null) {
+            askTextSb = new StringBuilder();
+        }
+        askTextSb.append(text);
+    }
+
+@Override
+public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+    System.out.println("收到的数据:" + msg);
+
+    if (msg instanceof ByteBuf) {
+        ByteBuf byteBuf = (ByteBuf) msg;
+
+        try {
+            // 1. 打印原始十六进制数据(用于调试)
+            String hexData = ByteBufUtil.hexDump(byteBuf);
+            System.out.println("十六进制数据: " + hexData);
+
+            // 2. 检查数据长度是否足够(假设每个参数占4字节,状态码占2字节)
+            if (byteBuf.readableBytes() < 22) { // 5个浮点数(4*5=20) + 1个状态码(2) = 22字节
+                System.err.println("数据长度不足!");
+                return;
+            }
+
+            // 3. 读取数据到字节数组(方便解析)
+
+            byte[] bytes = hexStringToByteArray(hexData.substring(14));
+
+            // 4. 解析浮点数参数(IEEE 754,大端序)
+            float cod = bytesToFloat(bytes, 0);       // 寄存器 0-1
+            float ph = bytesToFloat(bytes, 4);        // 寄存器 2-3
+            float flow = bytesToFloat(bytes, 8);      // 寄存器 4-5
+            float ammonia = bytesToFloat(bytes, 12);   // 寄存器 6-7
+            float phosphorus = bytesToFloat(bytes, 16);// 寄存器 8-9
+
+            // 5. 解析状态码(16位有符号整数)
+            int status = bytesToInt16(bytes, 20);     // 寄存器 10
+
+            // 6. 打印解析结果
+            System.out.println("解析结果:");
+            System.out.println("COD: " + cod + " mg/L");
+            System.out.println("pH: " + ph);
+            System.out.println("流量: " + flow + " m³/h");
+            System.out.println("氨氮: " + ammonia + " mg/L");
+            System.out.println("总磷: " + phosphorus + " mg/L");
+
+            protSjService.save(new ProtSj(cod+"",ph+"",flow+"",ammonia+"",phosphorus+"",new Date()));
+
+        } finally {
+            // 7. 释放 ByteBuf
+            byteBuf.release();
+        }
+    }
+}
+
+
+
+    // 解析 IEEE 754 浮点数(4字节)
+    private  float bytesToFloat(byte[] bytes, int offset) {
+        return ByteBuffer.wrap(bytes, offset, 4)
+                .order(ByteOrder.BIG_ENDIAN)
+                .getFloat();
+    }
+
+    // 解析 16位有符号整数(2字节)
+    private  int bytesToInt16(byte[] bytes, int offset) {
+        return ByteBuffer.wrap(bytes, offset, 2)
+                .order(ByteOrder.BIG_ENDIAN)
+                .getShort();
+    }
+
+
+
+    public    boolean isStartOfToday(Date date) {
+        // 创建一个示例的LocalDateTime对象
+        LocalDateTime exampleTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
+        // 获取今天的开始时间
+        LocalDateTime todayStart = LocalDateTime.of(LocalDateTime.now().toLocalDate(), LocalTime.MIDNIGHT);
+
+        // 比较给定的时间和今天的开始时间
+        if (exampleTime.isAfter(todayStart)) {
+            System.out.println("给定的时间大于今天的开始时间");
+            return true;
+        } else {
+            System.out.println("给定的时间不大于今天的开始时间");
+            return false;
+        }
+    }
+
+    @Override
+    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+        Channel channel = ctx.channel();
+        String key = channel.id().asLongText(); // 或者使用其他唯一标识符
+        System.out.println("退出-->"+key);
+        channelMap.remove(key);
+        super.channelInactive(ctx);
+    }
+    public static byte[] hexString2Bytes(String src) {
+
+        int l = src.length() / 2;
+
+        byte[] ret = new byte[l];
+
+        for (int i = 0; i < l; i++) {
+
+            ret[i] = (byte) Integer.valueOf(src.substring(i * 2, i * 2 + 2), 16).byteValue();
+
+        }
+
+        return ret;
+
+    }
+
+
+
+
+
+    @Override
+    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
+
+//        if (true){
+//            ctx.writeAndFlush(Unpooled.copiedBuffer(new byte[]{1, 2, 3}));
+//        }
+
+//        if (askTextSb.toString().indexOf("end") != -1) {
+//            // {处理客户端消息}
+
+
+//            handleClientAskCmd(askTextSb.toString(), ctx);
+//            //清空重置;
+//            askTextSb.delete(0, askTextSb.length());
+//        }
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+        System.err.println("发生异常: " + cause.getMessage());
+        if (cause.getMessage().indexOf("Connection reset") != -1) {
+            log.info("相关采集器设备正在重启:" + cause.toString());
+        }
+        ctx.close();
+    }
+
+    //应答
+    public void answerCmd(String answerText, ChannelHandlerContext ctx) {
+        ctx.writeAndFlush(Unpooled.copiedBuffer(answerText.getBytes()));
+    }
+
+
+}

+ 87 - 0
app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/BaseDisLocationController.java

@@ -0,0 +1,87 @@
+package com.ruoyi.web.v2.v1.controller;
+
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.web.v2.v1.entity.BaseDisLocation;
+import com.ruoyi.web.v2.v1.service.IBaseDisLocationService;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+import static com.ruoyi.common.core.domain.AjaxResult.error;
+import static com.ruoyi.common.core.domain.AjaxResult.success;
+
+/**
+ * <p>
+ *  前端控制器
+ * </p>
+ *
+ * @author author
+ * @since 2025-12-01
+ */
+@RestController
+@RequestMapping("/base-dis-location")
+public class BaseDisLocationController {
+    @Autowired
+    private IBaseDisLocationService baseDisLocationService;
+
+    @ApiOperation("检验项目管理添加")
+    @PostMapping("/add")
+    public AjaxResult add(@RequestBody BaseDisLocation checkProject) throws JsonProcessingException {
+        if (baseDisLocationService.count(new QueryWrapper<BaseDisLocation>().eq(
+                "location_name",checkProject.getLocationName()))>0){
+            error("该名字已存在");
+        }
+        return success(baseDisLocationService.save(checkProject));
+    }
+
+    @ApiOperation("检验项目管理修改")
+    @PostMapping("/edit")
+    public AjaxResult edit(@RequestBody BaseDisLocation checkProject) throws JsonProcessingException {
+        if (baseDisLocationService.count(new QueryWrapper<BaseDisLocation>().eq(
+                "location_name",checkProject.getLocationName()).ne("id",checkProject.getId()))>0){
+            error("该名字已存在");
+        }
+        return success(baseDisLocationService.updateById(checkProject));
+    }
+
+    @ApiOperation("检验项目管理删除")
+    @PostMapping("/delete")
+    public AjaxResult delete(@RequestBody Map<String, String> paramsMap) {
+        String ids = paramsMap.get("ids");
+        for (String s : ids.split(",")) {
+            BaseDisLocation byId = baseDisLocationService.getById(s);
+            if (byId.getLocationDel() == 1){
+                error("系统地点,不能删除");
+            }
+            baseDisLocationService.removeById(s);
+        }
+      return success();
+    }
+
+    @ApiOperation("检验项目管理列表")
+    @PostMapping("/list")
+    public AjaxResult listAll(){
+        return success(baseDisLocationService.list());
+    }
+
+    @ApiOperation("检验项目管理分页")
+    @GetMapping("/page")
+    public AjaxResult page(@RequestParam("pageNum") Integer pageNum, @RequestParam("pageSize") Integer pageSize ) {
+        return success(baseDisLocationService.page(new Page<BaseDisLocation>(pageNum, pageSize)));
+    }
+
+    @ApiOperation("检验项目管理详情")
+    @PostMapping("/listById")
+    public AjaxResult listById(@RequestBody Map<String, String> paramsMap){
+        String id = paramsMap.get("id");
+        return success(baseDisLocationService.getById(id));
+    }
+
+}

+ 46 - 0
app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/DeductionDetailsController.java

@@ -0,0 +1,46 @@
+package com.ruoyi.web.v2.v1.controller;
+
+
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.web.v2.v1.entity.request.DeductionRecordRequest;
+import com.ruoyi.web.v2.v1.service.IDeductionDetailsService;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+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;
+
+/**
+ * <p>
+ *  前端控制器
+ * </p>
+ *
+ * @author author
+ * @since 2025-12-25
+ */
+@RestController
+@RequestMapping("/pay/deduction-details")
+public class DeductionDetailsController {
+    @Autowired
+    private IDeductionDetailsService deductionDetailsService;
+
+
+
+    @ApiOperation("分页查询")
+    @PostMapping("/page")
+    public AjaxResult page(@RequestBody DeductionRecordRequest deductionRecordRequest)   {
+
+        return deductionDetailsService.getListPayRecordPage(deductionRecordRequest);
+
+    }
+    @ApiOperation("查询全部")
+    @PostMapping("/list")
+    public AjaxResult list(@RequestBody DeductionRecordRequest deductionRecordRequest) {
+
+        return deductionDetailsService.getList(deductionRecordRequest);
+    }
+
+
+}

+ 20 - 0
app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/DeductionMethodController.java

@@ -0,0 +1,20 @@
+package com.ruoyi.web.v2.v1.controller;
+
+
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ *  前端控制器
+ * </p>
+ *
+ * @author author
+ * @since 2025-12-25
+ */
+@RestController
+@RequestMapping("/deduction-method")
+public class DeductionMethodController {
+
+}

+ 43 - 0
app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/DeductionRecordController.java

@@ -0,0 +1,43 @@
+package com.ruoyi.web.v2.v1.controller;
+
+
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.web.v2.v1.entity.request.DeductionRecordRequest;
+import com.ruoyi.web.v2.v1.service.IDeductionRecordService;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+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;
+
+/**
+ * <p>
+ *  前端控制器
+ * </p>
+ *
+ * @author author
+ * @since 2025-12-25
+ */
+@RestController
+@RequestMapping("/pay/deduction-record")
+public class DeductionRecordController {
+    @Autowired
+    private IDeductionRecordService deductionRecordService;
+
+    @ApiOperation("分页查询")
+    @PostMapping("/page")
+    public AjaxResult page(@RequestBody DeductionRecordRequest deductionRecordRequest)   {
+        return deductionRecordService.getListPayRecordPage(deductionRecordRequest);
+
+    }
+
+    @ApiOperation("查询全部")
+    @PostMapping("/list")
+    public AjaxResult list(@RequestBody DeductionRecordRequest deductionRecordRequest) {
+
+        return deductionRecordService.getList(deductionRecordRequest);
+    }
+
+}

+ 74 - 0
app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/InspectionContentController.java

@@ -0,0 +1,74 @@
+package com.ruoyi.web.v2.v1.controller;
+
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.web.v2.v1.entity.InspectionContent;
+import com.ruoyi.web.v2.v1.service.IInspectionContentService;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+import static com.ruoyi.common.core.domain.AjaxResult.success;
+
+/**
+ * <p>
+ *  前端控制器
+ * </p>
+ *
+ * @author author
+ * @since 2025-11-20
+ */
+@RestController
+@RequestMapping("/inspection-content")
+public class InspectionContentController {
+
+    @Autowired
+    private IInspectionContentService inspectionContentService;
+
+    @ApiOperation("检验项目管理添加")
+    @PostMapping("/add")
+    public AjaxResult add(@RequestBody InspectionContent checkProject) throws JsonProcessingException {
+        return success(inspectionContentService.save(checkProject));
+    }
+
+    @ApiOperation("检验项目管理修改")
+    @PostMapping("/edit")
+    public AjaxResult edit(@RequestBody InspectionContent checkProject) throws JsonProcessingException {
+        return success(inspectionContentService.updateById(checkProject));
+    }
+
+    @ApiOperation("检验项目管理删除")
+    @PostMapping("/delete")
+    public AjaxResult delete(@RequestBody Map<String, String> paramsMap) {
+        String ids = paramsMap.get("ids");
+        for (String s : ids.split(",")) {
+             inspectionContentService.removeById(s);
+        }
+        return success();
+    }
+
+    @ApiOperation("检验项目管理列表")
+    @PostMapping("/list")
+    public AjaxResult listAll(){
+        return success(inspectionContentService.list());
+    }
+
+    @ApiOperation("检验项目管理分页")
+    @GetMapping("/page")
+    public AjaxResult page(@RequestParam("pageNum") Integer pageNum, @RequestParam("pageSize") Integer pageSize
+            , @RequestParam(value = "projectName", required = false) String projectName) {
+        return success(inspectionContentService.page(new Page<InspectionContent>(pageNum,pageSize)));
+    }
+
+    @ApiOperation("检验项目管理详情")
+    @PostMapping("/listById")
+    public AjaxResult listById(@RequestBody Map<String, String> paramsMap){
+        String id = paramsMap.get("id");
+        return success(inspectionContentService.getById(id));
+    }
+
+}

+ 22 - 0
app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/InspectionPointController.java

@@ -0,0 +1,22 @@
+package com.ruoyi.web.v2.v1.controller;
+
+
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ *  前端控制器
+ * </p>
+ *
+ * @author author
+ * @since 2025-11-20
+ */
+@RestController
+@RequestMapping("/inspection-point")
+public class InspectionPointController {
+
+
+
+}

+ 73 - 0
app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/InspectionRecordController.java

@@ -0,0 +1,73 @@
+package com.ruoyi.web.v2.v1.controller;
+
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.web.v2.v1.entity.InspectionRecord;
+import com.ruoyi.web.v2.v1.service.IInspectionRecordService;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+import static com.ruoyi.common.core.domain.AjaxResult.success;
+
+/**
+ * <p>
+ *  前端控制器
+ * </p>
+ *
+ * @author author
+ * @since 2025-11-20
+ */
+@RestController
+@RequestMapping("/inspection-record")
+public class InspectionRecordController {
+
+    @Autowired
+    private IInspectionRecordService inspectionRecordService;
+
+    @ApiOperation("检验项目管理添加")
+    @PostMapping("/add")
+    public AjaxResult add(@RequestBody InspectionRecord checkProject) throws JsonProcessingException {
+        return success(inspectionRecordService.save(checkProject));
+    }
+
+    @ApiOperation("检验项目管理修改")
+    @PostMapping("/edit")
+    public AjaxResult edit(@RequestBody InspectionRecord checkProject) throws JsonProcessingException {
+        return success(inspectionRecordService.updateById(checkProject));
+    }
+
+    @ApiOperation("检验项目管理删除")
+    @PostMapping("/delete")
+    public AjaxResult delete(@RequestBody Map<String, String> paramsMap) {
+        String ids = paramsMap.get("ids");
+        for (String s : ids.split(",")) {
+            inspectionRecordService.removeById(s);
+        }
+        return success();
+    }
+
+    @ApiOperation("检验项目管理列表")
+    @PostMapping("/list")
+    public AjaxResult listAll(){
+        return success(inspectionRecordService.list());
+    }
+
+    @ApiOperation("检验项目管理分页")
+    @GetMapping("/page")
+    public AjaxResult page(@RequestParam("pageNum") Integer pageNum, @RequestParam("pageSize") Integer pageSize
+            , @RequestParam(value = "projectName", required = false) String projectName) {
+        return success(inspectionRecordService.page(new Page<InspectionRecord>(pageNum,pageSize)));
+    }
+
+    @ApiOperation("检验项目管理详情")
+    @PostMapping("/listById")
+    public AjaxResult listById(@RequestBody Map<String, String> paramsMap){
+        String id = paramsMap.get("id");
+        return success(inspectionRecordService.getById(id));
+    }
+}

+ 71 - 0
app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/JsCheckDeviceController.java

@@ -0,0 +1,71 @@
+package com.ruoyi.web.v2.v1.controller;
+
+
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.web.v2.v1.model.JsCheckDevice;
+import com.ruoyi.web.v2.v1.service.IJsCheckDeviceService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+import static com.ruoyi.common.core.domain.AjaxResult.success;
+
+/**
+ * <p>
+ * v2.0设备档案管理 前端控制器
+ * </p>
+ *
+ * @author author
+ * @since 2025-05-21
+ */
+@RestController
+@RequestMapping("/js-check-device")
+@Api("2.0设备档案管理")
+public class JsCheckDeviceController {
+
+    @Autowired
+    private IJsCheckDeviceService deviceService;
+
+    @ApiOperation("设备档案管理添加")
+    @PostMapping("/add")
+    public AjaxResult add(@RequestBody JsCheckDevice checkDevice){
+        return deviceService.add(checkDevice);
+    }
+
+    @ApiOperation("设备档案管理修改")
+    @PostMapping("/edit")
+    public AjaxResult edit(@RequestBody JsCheckDevice checkDevice){
+        return deviceService.edit(checkDevice);
+    }
+
+    @ApiOperation("设备档案管理删除")
+    @PostMapping("/delete")
+    public AjaxResult delete(@RequestBody Map<String, String> paramsMap) {
+        String ids = paramsMap.get("ids");
+        return deviceService.delete(ids);
+    }
+
+    @ApiOperation("设备档案管理列表")
+    @PostMapping("/list")
+    public AjaxResult listAll(){
+        return deviceService.listAll();
+    }
+
+    @ApiOperation("设备档案管理分页")
+    @GetMapping("/page")
+    public AjaxResult page(@RequestParam("pageNum") Integer pageNum, @RequestParam("pageSize") Integer pageSize
+            , @RequestParam(value = "deviceNum", required = false) String deviceNum,
+                           @RequestParam(value = "deviceName", required = false) String deviceName) {
+        return deviceService.page(pageNum, pageSize, deviceNum,deviceName);
+    }
+
+    @ApiOperation("设备档案管理详情")
+    @PostMapping("/listById")
+    public AjaxResult listById(@RequestBody Map<String, String> paramsMap){
+        String id = paramsMap.get("id");
+        return success(deviceService.getById(id));
+    }
+}

+ 70 - 0
app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/JsCheckInstrumentController.java

@@ -0,0 +1,70 @@
+package com.ruoyi.web.v2.v1.controller;
+
+
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.web.v2.v1.model.JsCheckInstrument;
+import com.ruoyi.web.v2.v1.service.IJsCheckInstrumentService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+import static com.ruoyi.common.core.domain.AjaxResult.success;
+
+/**
+ * <p>
+ * v2.0检验仪器管理 前端控制器
+ * </p>
+ *
+ * @author author
+ * @since 2025-05-21
+ */
+@RestController
+@RequestMapping("/js-check-instrument")
+@Api("2.0检验仪器管理")
+public class JsCheckInstrumentController {
+
+    @Autowired
+    private IJsCheckInstrumentService instrumentService;
+    
+    @ApiOperation("检验仪器管理添加")
+    @PostMapping("/add")
+    public AjaxResult add(@RequestBody JsCheckInstrument checkDevice){
+        return instrumentService.add(checkDevice);
+    }
+
+    @ApiOperation("检验仪器管理修改")
+    @PostMapping("/edit")
+    public AjaxResult edit(@RequestBody JsCheckInstrument checkDevice){
+        return instrumentService.edit(checkDevice);
+    }
+
+    @ApiOperation("检验仪器管理删除")
+    @PostMapping("/delete")
+    public AjaxResult delete(@RequestBody Map<String, String> paramsMap) {
+        String ids = paramsMap.get("ids");
+        return instrumentService.delete(ids);
+    }
+
+    @ApiOperation("检验仪器管理列表")
+    @PostMapping("/list")
+    public AjaxResult listAll(){
+        return instrumentService.listAll();
+    }
+
+    @ApiOperation("检验仪器管理分页")
+    @GetMapping("/page")
+    public AjaxResult page(@RequestParam("pageNum") Integer pageNum, @RequestParam("pageSize") Integer pageSize
+            , @RequestParam(value = "deviceNum", required = false) String deviceNum,
+                           @RequestParam(value = "deviceName", required = false) String deviceName) {
+        return instrumentService.page(pageNum, pageSize, deviceNum,deviceName);
+    }
+    @ApiOperation("检验仪器管理详情")
+    @PostMapping("/listById")
+    public AjaxResult listById(@RequestBody Map<String, String> paramsMap){
+        String id = paramsMap.get("id");
+        return success(instrumentService.getById(id));
+    }
+}

+ 71 - 0
app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/JsCheckProjectController.java

@@ -0,0 +1,71 @@
+package com.ruoyi.web.v2.v1.controller;
+
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.web.v2.v1.model.JsCheckProject;
+import com.ruoyi.web.v2.v1.service.IJsCheckProjectService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+import static com.ruoyi.common.core.domain.AjaxResult.success;
+
+/**
+ * <p>
+ * 2.0检验项目管理 前端控制器
+ * </p>
+ *
+ * @author author
+ * @since 2025-05-20
+ */
+@RestController
+@RequestMapping("/js-check-project")
+@Api("2.0检验项目管理")
+public class JsCheckProjectController {
+
+    @Autowired
+    private IJsCheckProjectService projectService;
+
+    @ApiOperation("检验项目管理添加")
+    @PostMapping("/add")
+    public AjaxResult add(@RequestBody JsCheckProject checkProject) throws JsonProcessingException {
+        return projectService.add(checkProject);
+    }
+
+    @ApiOperation("检验项目管理修改")
+    @PostMapping("/edit")
+    public AjaxResult edit(@RequestBody JsCheckProject checkProject) throws JsonProcessingException {
+        return projectService.edit(checkProject);
+    }
+
+    @ApiOperation("检验项目管理删除")
+    @PostMapping("/delete")
+    public AjaxResult delete(@RequestBody Map<String, String> paramsMap) {
+        String ids = paramsMap.get("ids");
+        return projectService.delete(ids);
+    }
+
+    @ApiOperation("检验项目管理列表")
+    @PostMapping("/list")
+    public AjaxResult listAll(){
+        return projectService.listAll();
+    }
+
+    @ApiOperation("检验项目管理分页")
+    @GetMapping("/page")
+    public AjaxResult page(@RequestParam("pageNum") Integer pageNum, @RequestParam("pageSize") Integer pageSize
+            , @RequestParam(value = "projectName", required = false) String projectName) {
+        return projectService.page(pageNum, pageSize, projectName);
+    }
+
+    @ApiOperation("检验项目管理详情")
+    @PostMapping("/listById")
+    public AjaxResult listById(@RequestBody Map<String, String> paramsMap){
+        String id = paramsMap.get("id");
+        return success(projectService.getById(id));
+    }
+}

+ 75 - 0
app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/JsDeviceMaintenanceController.java

@@ -0,0 +1,75 @@
+package com.ruoyi.web.v2.v1.controller;
+
+
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.web.v2.v1.model.JsDeviceMaintenance;
+import com.ruoyi.web.v2.v1.service.IJsDeviceMaintenanceService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+import static com.ruoyi.common.core.domain.AjaxResult.success;
+
+/**
+ * <p>
+ * v2.0设备维护登记 前端控制器
+ * </p>
+ *
+ * @author author
+ * @since 2025-05-23
+ */
+@RestController
+@RequestMapping("/js-device-maintenance")
+@Api("2.0设备维护登记")
+public class JsDeviceMaintenanceController {
+
+    @Autowired
+    private IJsDeviceMaintenanceService deviceMaintenanceService;
+
+
+    @ApiOperation("设备维护登记添加")
+    @PostMapping("/add")
+    public AjaxResult add(@RequestBody JsDeviceMaintenance deviceMaintenance) {
+        return deviceMaintenanceService.add(deviceMaintenance);
+    }
+
+    @ApiOperation("设备维护登记修改")
+    @PostMapping("/edit")
+    public AjaxResult edit(@RequestBody JsDeviceMaintenance deviceMaintenance) {
+        return deviceMaintenanceService.edit(deviceMaintenance);
+    }
+
+    @ApiOperation("设备维护登记删除")
+    @PostMapping("/delete")
+    public AjaxResult delete(@RequestBody Map<String, String> paramsMap) {
+        String ids = paramsMap.get("ids");
+        return deviceMaintenanceService.delete(ids);
+    }
+
+    @ApiOperation("设备维护登记列表")
+    @PostMapping("/list")
+    public AjaxResult listAll() {
+        return deviceMaintenanceService.listAll();
+    }
+
+    @ApiOperation("设备维护登记分页")
+    @GetMapping("/page")
+    public AjaxResult page(@RequestParam("pageNum") Integer pageNum, @RequestParam("pageSize") Integer pageSize
+            , @RequestParam(value = "startTime", required = false) String startTime
+            , @RequestParam(value = "endTime", required = false) String endTime
+            , @RequestParam(value = "deviceNum", required = false) String deviceNum
+            , @RequestParam(value = "deviceName", required = false) String deviceName) {
+
+        return deviceMaintenanceService.page(pageNum, pageSize, startTime, endTime, deviceNum,deviceName);
+    }
+
+    @ApiOperation("设备维护登记详情")
+    @PostMapping("/listById")
+    public AjaxResult listById(@RequestBody Map<String, String> paramsMap) {
+        String id = paramsMap.get("id");
+        return success(deviceMaintenanceService.getById(id));
+    }
+}

+ 71 - 0
app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/JsDisinfectController.java

@@ -0,0 +1,71 @@
+package com.ruoyi.web.v2.v1.controller;
+
+
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.web.v2.v1.model.JsDisinfect;
+import com.ruoyi.web.v2.v1.service.IJsDisinfectService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+import static com.ruoyi.common.core.domain.AjaxResult.success;
+
+/**
+ * <p>
+ * v2.0消毒登记 前端控制器
+ * </p>
+ *
+ * @author author
+ * @since 2025-05-22
+ */
+@RestController
+@RequestMapping("/js-disinfect")
+@Api("2.0消毒登记")
+public class JsDisinfectController {
+
+    @Autowired
+    private IJsDisinfectService disinfectService;
+
+    @ApiOperation("消毒登记管理添加")
+    @PostMapping("/add")
+    public AjaxResult add(@RequestBody JsDisinfect disinfect){
+        return disinfectService.add(disinfect);
+    }
+
+    @ApiOperation("消毒登记管理修改")
+    @PostMapping("/edit")
+    public AjaxResult edit(@RequestBody JsDisinfect disinfect){
+        return disinfectService.edit(disinfect);
+    }
+
+    @ApiOperation("消毒登记管理删除")
+    @PostMapping("/delete")
+    public AjaxResult delete(@RequestBody Map<String, String> paramsMap) {
+        String ids = paramsMap.get("ids");
+        return disinfectService.delete(ids);
+    }
+
+    @ApiOperation("消毒登记管理列表")
+    @PostMapping("/list")
+    public AjaxResult listAll(){
+        return disinfectService.listAll();
+    }
+
+    @ApiOperation("消毒登记管理分页")
+    @GetMapping("/page")
+    public AjaxResult page(@RequestParam("pageNum") Integer pageNum, @RequestParam("pageSize") Integer pageSize
+            , @RequestParam(value = "startTime", required = false) String startTime
+            , @RequestParam(value = "endTime", required = false) String endTime,
+                           @RequestParam(value = "disinfectLocation", required = false) String disinfectLocation) {
+        return disinfectService.page(pageNum, pageSize, startTime,endTime,disinfectLocation);
+    }
+    @ApiOperation("消毒登记管理详情")
+    @PostMapping("/listById")
+    public AjaxResult listById(@RequestBody Map<String, String> paramsMap){
+        String id = paramsMap.get("id");
+        return success(disinfectService.getById(id));
+    }
+}

+ 70 - 0
app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/JsDisinfectManageController.java

@@ -0,0 +1,70 @@
+package com.ruoyi.web.v2.v1.controller;
+
+
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.web.v2.v1.model.JsDisinfectManage;
+import com.ruoyi.web.v2.v1.service.IJsDisinfectManageService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+import static com.ruoyi.common.core.domain.AjaxResult.success;
+
+/**
+ * <p>
+ * 2.0消毒方式管理 前端控制器
+ * </p>
+ *
+ * @author author
+ * @since 2025-05-20
+ */
+@RestController
+@RequestMapping("/js-disinfect-manage")
+@Api("2.0消毒方式管理")
+public class JsDisinfectManageController extends BaseController {
+
+    @Autowired
+    private IJsDisinfectManageService disinfectManageService;
+
+    @ApiOperation("消毒方式管理添加")
+    @PostMapping("/add")
+    public AjaxResult add(@RequestBody JsDisinfectManage jsDisinfectManage){
+        return disinfectManageService.add(jsDisinfectManage);
+    }
+
+    @ApiOperation("消毒方式管理修改")
+    @PostMapping("/edit")
+    public AjaxResult edit(@RequestBody JsDisinfectManage jsDisinfectManage){
+        return disinfectManageService.edit(jsDisinfectManage);
+    }
+
+    @ApiOperation("消毒方式管理删除")
+    @PostMapping("/delete")
+    public AjaxResult delete(@RequestBody Map<String, String> paramsMap) {
+        String ids = paramsMap.get("ids");
+        return disinfectManageService.delete(ids);
+    }
+
+    @ApiOperation("消毒方式管理列表")
+    @PostMapping("/list")
+    public AjaxResult listAll(){
+        return disinfectManageService.listAll();
+    }
+
+    @ApiOperation("消毒方式管理分页")
+    @GetMapping("/page")
+    public AjaxResult page(@RequestParam("pageNum") Integer pageNum, @RequestParam("pageSize") Integer pageSize
+            , @RequestParam(value = "disinfectMethod", required = false) String disinfectMethod) {
+        return disinfectManageService.page(pageNum, pageSize, disinfectMethod);
+    }
+    @ApiOperation("消毒方式管理详情")
+    @PostMapping("/listById")
+    public AjaxResult listById(@RequestBody Map<String, String> paramsMap){
+        String id = paramsMap.get("id");
+        return success(disinfectManageService.getById(id));
+    }
+}

+ 105 - 0
app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/JsDistributionController.java

@@ -0,0 +1,105 @@
+package com.ruoyi.web.v2.v1.controller;
+
+
+import com.ruoyi.common.annotation.Anonymous;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.web.v2.v1.model.*;
+import com.ruoyi.web.v2.v1.service.IJsDistributionService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+
+import static com.ruoyi.common.core.domain.AjaxResult.success;
+import static com.ruoyi.common.utils.PageUtils.startPage;
+
+/**
+ * <p>
+ * v2.0分销登记 前端控制器
+ * </p>
+ *
+ * @author author
+ * @since 2025-05-26
+ */
+@RestController
+@RequestMapping("/js-distribution")
+@Api("2.0分销登记")
+public class JsDistributionController extends BaseController {
+
+    @Autowired
+    private IJsDistributionService distributionService;
+
+    @ApiOperation("分销登记添加")
+    @PostMapping("/add")
+    public AjaxResult add(@RequestBody JsDistribution distribution) {
+        return distributionService.add(distribution);
+    }
+
+    @ApiOperation("分销登记修改")
+    @PostMapping("/edit")
+    public AjaxResult edit(@RequestBody JsDistribution distribution) {
+        return distributionService.edit(distribution);
+    }
+
+    @ApiOperation("分销登记删除")
+    @PostMapping("/delete")
+    public AjaxResult delete(@RequestBody Map<String, String> paramsMap) {
+        String ids = paramsMap.get("ids");
+        return distributionService.delete(ids);
+    }
+
+    @ApiOperation("分销登记列表")
+    @PostMapping("/list")
+    public AjaxResult listAll() {
+        return distributionService.listAll();
+    }
+
+    @ApiOperation("分销登记分页")
+    @GetMapping("/page")
+    public AjaxResult page(@RequestParam("pageNum") Integer pageNum, @RequestParam("pageSize") Integer pageSize
+            , @RequestParam(value = "startTime", required = false) String startTime
+            , @RequestParam(value = "endTime", required = false) String endTime
+            , @RequestParam(value = "supplierName", required = false) String supplierName
+            , @RequestParam(value = "purchaserName", required = false) String purchaserName
+            , @RequestParam(value = "animalCertNo", required = false) String animalCertNo
+            , @RequestParam(value = "animalType", required = false) String animalType) {
+
+        return distributionService.page(animalType ,pageNum, pageSize, startTime, endTime, supplierName, purchaserName, animalCertNo);
+    }
+
+    @ApiOperation("分销登记详情")
+    @PostMapping("/listById")
+    public AjaxResult listById(@RequestBody Map<String, String> paramsMap) {
+        String id = paramsMap.get("id");
+        return success(distributionService.getById(id));
+    }
+
+    @ApiOperation("最近分销记录")
+    @GetMapping("/lastRecord")
+    public AjaxResult lastRecord() {
+        return success(distributionService.selectLastRecord());
+    }
+
+    @ApiOperation("屠宰生产清单")
+    @Anonymous
+    @GetMapping("/productionPage")
+    public TableDataInfo productionPage(GetDistributionListReq req) {
+        startPage();
+        List<JsProduction> list = distributionService.productionPage(req);
+        return getDataTable(list);
+    }
+
+    @ApiOperation("合格证修改")
+    @PostMapping("/editProduce")
+    public AjaxResult editProduce(@RequestBody @Validated EditProduceReq req) {
+        return distributionService.editProduce(req);
+    }
+
+
+}

+ 88 - 0
app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/JsDivideCircleController.java

@@ -0,0 +1,88 @@
+package com.ruoyi.web.v2.v1.controller;
+
+
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.web.v2.v1.model.JsDivideCircle;
+import com.ruoyi.web.v2.v1.service.IJsDivideCircleService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+import static com.ruoyi.common.core.domain.AjaxResult.success;
+
+/**
+ * <p>
+ * v2.0分圈登记 前端控制器
+ * </p>
+ *
+ * @author author
+ * @since 2025-05-22
+ */
+@RestController
+@RequestMapping("/js-divide-circle")
+@Api("2.0分圈登记")
+public class JsDivideCircleController {
+
+    @Autowired
+    private IJsDivideCircleService divideCircleService;
+
+    @ApiOperation("分圈登记添加")
+    @PostMapping("/add")
+    public AjaxResult add(@RequestBody JsDivideCircle divideCircle) {
+        return divideCircleService.add(divideCircle);
+    }
+
+    @ApiOperation("分圈登记修改")
+    @PostMapping("/edit")
+    public AjaxResult edit(@RequestBody JsDivideCircle divideCircle) {
+        return divideCircleService.edit(divideCircle);
+    }
+
+    @ApiOperation("分圈登记删除")
+    @PostMapping("/delete")
+    public AjaxResult delete(@RequestBody Map<String, String> paramsMap) {
+        String ids = paramsMap.get("ids");
+
+        return divideCircleService.delete(ids);
+    }
+
+    @ApiOperation("分圈登记列表")
+    @PostMapping("/list")
+    public AjaxResult listAll(@RequestBody Map<String, String> paramsMap) {
+        String startTime = paramsMap.get("startTime");
+        return divideCircleService.listAll(startTime);
+    }
+
+    @ApiOperation("分圈登记分页")
+    @GetMapping("/page")
+    public AjaxResult page(@RequestParam("pageNum") Integer pageNum, @RequestParam("pageSize") Integer pageSize
+            , @RequestParam(value = "startTime", required = false) String startTime
+            , @RequestParam(value = "endTime", required = false) String endTime
+            , @RequestParam(value = "pigpenName", required = false) String pigpenName) {
+
+        return divideCircleService.page(pageNum, pageSize, startTime, endTime, pigpenName);
+    }
+
+    @ApiOperation("分圈登记详情")
+    @PostMapping("/listById")
+    public AjaxResult listById(@RequestBody Map<String, String> paramsMap) {
+        String id = paramsMap.get("id");
+        return success(divideCircleService.getById(id));
+    }
+
+    @ApiOperation("存栏数据核查")
+    @GetMapping("/review")
+    public AjaxResult review() {
+        return success(divideCircleService.review());
+    }
+
+    @ApiOperation("查询分圈后有效批次号列表")
+    @GetMapping("/listSeries")
+    public AjaxResult series() {
+        return success(divideCircleService.listSeriesNo());
+    }
+
+}

+ 159 - 0
app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/JsDrugCheckController.java

@@ -0,0 +1,159 @@
+package com.ruoyi.web.v2.v1.controller;
+
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.ruoyi.common.annotation.Anonymous;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.web.v2.v1.model.JsDrugCheck;
+import com.ruoyi.web.v2.v1.service.IJsDrugCheckService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.ObjectUtils;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+import static com.ruoyi.common.core.domain.AjaxResult.success;
+import static com.ruoyi.common.utils.PageUtils.startPage;
+
+/**
+ * <p>
+ * v2.0违禁药物登记 前端控制器
+ * </p>
+ *
+ * @author author
+ * @since 2025-05-22
+ */
+@RestController
+@RequestMapping("/js-drug-check")
+@Api("2.0违禁药物登记")
+public class JsDrugCheckController {
+
+    @Autowired
+    private IJsDrugCheckService drugCheckService;
+
+
+    @ApiOperation("违禁药物登记添加")
+    @PostMapping("/add")
+    public AjaxResult add(@RequestBody JsDrugCheck drugCheck) {
+        String allResult = "合格";
+        for (HashMap<String, String> result:drugCheck.getExamineList()){
+            for (Map.Entry<String, String> entry : result.entrySet()){
+                if (Objects.equals(entry.getKey(), "value") && (!Objects.equals(entry.getValue(), "阴性") && !Objects.equals(entry.getValue(), "合格"))) {
+                    allResult = "不合格";
+                    break;
+                }
+            }
+        }
+        drugCheck.setResult(allResult);
+        return drugCheckService.add(drugCheck);
+    }
+
+    @ApiOperation("违禁药物登记修改")
+    @PostMapping("/edit")
+    public AjaxResult edit(@RequestBody JsDrugCheck drugCheck) {
+        String allResult = "合格";
+        for (HashMap<String, String> result:drugCheck.getExamineList()){
+            for (Map.Entry<String, String> entry : result.entrySet()){
+                if (Objects.equals(entry.getKey(), "value") && (!Objects.equals(entry.getValue(), "阴性") && !Objects.equals(entry.getValue(), "合格"))) {
+                    allResult = "不合格";
+                    break;
+                }
+            }
+        }
+        drugCheck.setResult(allResult);
+        return drugCheckService.edit(drugCheck);
+    }
+
+    @ApiOperation("违禁药物登记删除")
+    @PostMapping("/delete")
+    public AjaxResult delete(@RequestBody Map<String, String> paramsMap) {
+        String ids = paramsMap.get("ids");
+        return drugCheckService.delete(ids);
+    }
+
+    @ApiOperation("违禁药物登记列表")
+    @PostMapping("/list")
+    public AjaxResult listAll(@RequestBody Map<String, String> param) {
+        if(!ObjectUtils.isEmpty(param) && !ObjectUtils.isEmpty(param.getOrDefault("checkType", null))) {
+            QueryWrapper wrapper = new QueryWrapper();
+            wrapper.eq("check_type", param.get("checkType"));
+            return success(drugCheckService.list(wrapper));
+        }
+        return drugCheckService.listAll();
+    }
+
+    @ApiOperation("违禁药物登记分页")
+    @GetMapping("/page")
+    public AjaxResult page(@RequestParam("pageNum") Integer pageNum, @RequestParam("pageSize") Integer pageSize
+            , @RequestParam(value = "startTime", required = false) String startTime
+            , @RequestParam(value = "endTime", required = false) String endTime
+            , @RequestParam(value = "animalCertNo", required = false) String animalCertNo
+            , @RequestParam(value = "checkType", required = false) String checkType) {
+
+        return drugCheckService.page(pageNum, pageSize, startTime, endTime, animalCertNo, checkType);
+    }
+
+    @ApiOperation("违禁药物登记详情")
+    @GetMapping("/listById")
+    public AjaxResult listById(JsDrugCheck drugCheck) {
+        return success(drugCheckService.getDetail(drugCheck));
+    }
+
+    @ApiOperation("违禁药物检测及屠宰检验情况登记表")
+    @Anonymous
+    @GetMapping("/drugAndProduce")
+    public AjaxResult drugAndProduce(@RequestParam("pageNum") Integer pageNum, @RequestParam("pageSize") Integer pageSize
+            , @RequestParam(value = "startTime", required = false) String startTime
+            , @RequestParam(value = "endTime", required = false) String endTime
+            , @RequestParam(value = "cargoOwner", required = false) String cargoOwner) {
+        return success(drugCheckService.drugAndProduce(pageNum, pageSize,startTime,endTime,cargoOwner));
+    }
+
+    @ApiOperation("无害化处理登记表")
+    @Anonymous
+    @GetMapping("/harmlessTreatment")
+    public AjaxResult harmlessTreatment(@RequestParam("pageNum") Integer pageNum, @RequestParam("pageSize") Integer pageSize
+            , @RequestParam(value = "startTime", required = false) String startTime
+            , @RequestParam(value = "endTime", required = false) String endTime
+            , @RequestParam(value = "cargoOwner", required = false) String cargoOwner) {
+        return success(drugCheckService.harmlessTreatment(pageNum, pageSize,startTime,endTime,cargoOwner));
+    }
+
+        @ApiOperation("生猪入场查验情况登记表")
+    @Anonymous
+    @GetMapping("/Admission")
+    public AjaxResult Admission(@RequestParam("pageNum") Integer pageNum,
+                                @RequestParam("pageSize") Integer pageSize
+                , @RequestParam(value = "startTime", required = false) String startTime
+                , @RequestParam(value = "endTime", required = false) String endTime
+                , @RequestParam(value = "animalCertNo", required = false) String animalCertNo
+                , @RequestParam(value = "cargoOwner", required = false) String cargoOwner) {
+        return success(drugCheckService.Admission(pageNum, pageSize,startTime,endTime,animalCertNo,cargoOwner));
+    }
+
+
+    @ApiOperation("生猪产品出场情况登记表")
+    @Anonymous
+    @GetMapping("/outPlace")
+    public AjaxResult outPlace(@RequestParam("pageNum") Integer pageNum, @RequestParam("pageSize") Integer pageSize
+            , @RequestParam(value = "startTime", required = false) String startTime
+            , @RequestParam(value = "endTime", required = false) String endTime
+            , @RequestParam(value = "cargoOwner", required = false) String cargoOwner) {
+        return success(drugCheckService.outPlace(pageNum, pageSize,startTime,endTime,cargoOwner));
+    }
+
+    @ApiOperation("生猪屠宰情况日汇总等级表")
+    @Anonymous
+    @GetMapping("/SummaryVo")
+    public AjaxResult SummaryVo(@RequestParam("pageNum") Integer pageNum, @RequestParam("pageSize") Integer pageSize
+            , @RequestParam(value = "startTime", required = false) String startTime
+            , @RequestParam(value = "endTime", required = false) String endTime) {
+        return success(drugCheckService.SummaryVo(pageNum, pageSize, startTime, endTime));
+    }
+
+}

+ 115 - 0
app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/JsDrugController.java

@@ -0,0 +1,115 @@
+package com.ruoyi.web.v2.v1.controller;
+
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.jsonUtil;
+import com.ruoyi.web.v2.v1.model.JsDrug;
+import com.ruoyi.web.v2.v1.service.IJsDrugService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.ObjectUtils;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+import static com.ruoyi.common.core.domain.AjaxResult.error;
+import static com.ruoyi.common.core.domain.AjaxResult.success;
+
+/**
+ * <p>
+ * v2.0违禁药物名称 前端控制器
+ * </p>
+ *
+ * @author author
+ * @since 2025-05-21
+ */
+@RestController
+@RequestMapping("/js-drug")
+@Api("2.0违禁药物名称")
+public class JsDrugController {
+
+    @Autowired
+    private IJsDrugService drugService;
+
+    @ApiOperation("违禁药物管理添加")
+    @PostMapping("/add")
+    public AjaxResult add(@RequestBody JsDrug jsDrug) throws JsonProcessingException {
+        ObjectMapper mapper = new ObjectMapper();
+        JsonNode jsonArray = mapper.readTree(jsDrug.getResult());
+        String fieldToCheck = "name";
+        if (jsonUtil.checkForDuplicateFields(jsonArray, fieldToCheck)) {
+            return error("新增药物'" + jsDrug.getDrugName() + "'失败,本次添加中存在重复的检查结果");
+        }
+        int i = drugService.count(new QueryWrapper<JsDrug>().eq("drug_name", jsDrug.getDrugName()));
+        if (i != 0) {
+            return error("该药物已存在");
+        }
+        drugService.save(jsDrug);
+        return success();
+    }
+
+    @ApiOperation("违禁药物管理修改")
+    @PostMapping("/edit")
+    public AjaxResult edit(@RequestBody JsDrug jsDrug) throws JsonProcessingException {
+        ObjectMapper mapper = new ObjectMapper();
+        JsonNode jsonArray = mapper.readTree(jsDrug.getResult());
+        String fieldToCheck = "name";
+        if (jsonUtil.checkForDuplicateFields(jsonArray, fieldToCheck)) {
+            return error("修改药物'" + jsDrug.getDrugName() + "'失败,本次添加中存在重复的检查结果");
+        }
+        int i = drugService.count(new QueryWrapper<JsDrug>().eq("drug_name", jsDrug.getDrugName()).ne("id",jsDrug.getId()));
+        if (i != 0) {
+            return error("该药物已存在");
+        }
+        drugService.updateById(jsDrug);
+        return success();
+    }
+
+    @ApiOperation("违禁药物管理删除")
+    @PostMapping("/delete")
+    public AjaxResult delete(@RequestBody Map<String, String> paramsMap) {
+        String ids = paramsMap.get("ids");
+        String[] split = ids.split(",");
+        for (String s : split) {
+            drugService.removeById(s);
+        }
+        return success();
+    }
+
+    @ApiOperation("违禁药物管理列表")
+    @PostMapping("/list")
+    public AjaxResult listAll(@RequestBody Map<String, String> param){
+        if(!ObjectUtils.isEmpty(param) && !ObjectUtils.isEmpty(param.getOrDefault("type", null))) {
+            QueryWrapper wrapper = new QueryWrapper();
+            wrapper.eq("type", param.get("type"));
+            return success(drugService.list(wrapper));
+        }
+        return success(drugService.list());
+    }
+
+    @ApiOperation("违禁药物管理分页")
+    @GetMapping("/page")
+    public AjaxResult page(@RequestParam("pageNum") Integer pageNum, @RequestParam("pageSize") Integer pageSize
+            , @RequestParam(value = "drugName", required = false) String drugName,
+                           @RequestParam(value = "type", required = false) String type) {
+        Page<JsDrug> page = new Page<>(pageNum, pageSize);
+        QueryWrapper<JsDrug> queryWrapper = new QueryWrapper<>();
+        queryWrapper.like(StringUtils.isNotEmpty(drugName), "drug_name", drugName);
+        queryWrapper.eq("type", type);
+        return success(drugService.page(page, queryWrapper));
+    }
+
+    @ApiOperation("违禁药物管理详情")
+    @PostMapping("/listById")
+    public AjaxResult listById(@RequestBody Map<String, String> paramsMap){
+        String id = paramsMap.get("id");
+        return success(drugService.getById(id));
+    }
+}

+ 95 - 0
app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/JsEnvCheckController.java

@@ -0,0 +1,95 @@
+package com.ruoyi.web.v2.v1.controller;
+
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.jsonUtil;
+import com.ruoyi.web.v2.v1.model.JsEnvCheck;
+import com.ruoyi.web.v2.v1.model.JsEnvCheck;
+import com.ruoyi.web.v2.v1.service.IJsDrugService;
+import com.ruoyi.web.v2.v1.service.IJsEnvCheckService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.ObjectUtils;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+import static com.ruoyi.common.core.domain.AjaxResult.error;
+import static com.ruoyi.common.core.domain.AjaxResult.success;
+
+/**
+ * <p>
+ * v2.0环境拭子检测 前端控制器
+ * </p>
+ *
+ * @author coede
+ * @since 2025-09-28
+ */
+@RestController
+@RequestMapping("/js-envCheck")
+@Api("2.0环境拭子检测")
+public class JsEnvCheckController {
+
+    @Autowired
+    private IJsEnvCheckService envCheckService;
+
+    @ApiOperation("环境拭子检测管理添加")
+    @PostMapping("/add")
+    public AjaxResult add(@RequestBody JsEnvCheck jsEnvCheck) throws JsonProcessingException {
+       if(envCheckService.save(jsEnvCheck)){
+           return success();
+       }else {
+           return error();
+       }
+    }
+
+    @ApiOperation("环境拭子检测管理修改")
+    @PostMapping("/edit")
+    public AjaxResult edit(@RequestBody JsEnvCheck jsEnvCheck) throws JsonProcessingException {
+        if(envCheckService.updateById(jsEnvCheck)){
+            return success();
+        }else {
+            return error();
+        }
+    }
+
+    @ApiOperation("环境拭子检测管理删除")
+    @PostMapping("/delete")
+    public AjaxResult delete(@RequestBody Map<String, String> paramsMap) {
+        String ids = paramsMap.get("ids");
+        String[] split = ids.split(",");
+        for (String s : split) {
+            envCheckService.removeById(s);
+        }
+        return success();
+    }
+
+    @ApiOperation("环境拭子检测管理列表")
+    @PostMapping("/list")
+    public AjaxResult listAll(@RequestBody Map<String, String> param){
+        return success(envCheckService.list());
+    }
+
+    @ApiOperation("环境拭子检测管理分页")
+    @GetMapping("/page")
+    public AjaxResult page(@RequestParam("pageNum") Integer pageNum, @RequestParam("pageSize") Integer pageSize
+            , @RequestParam(value = "startTime", required = false) String startTime
+            , @RequestParam(value = "endTime", required = false) String endTime
+            , @RequestParam(value = "checkProject", required = false) String checkProject) {
+        return envCheckService.page(pageNum, pageSize, startTime, endTime, checkProject);
+    }
+
+    @ApiOperation("环境拭子检测管理详情")
+    @PostMapping("/listById")
+    public AjaxResult listById(@RequestBody Map<String, String> paramsMap){
+        String id = paramsMap.get("id");
+        return success(envCheckService.getById(id));
+    }
+}

+ 0 - 0
app-admin/src/main/java/com/ruoyi/web/v2/v1/controller/JsEnvCheckProjectController.java


Some files were not shown because too many files changed in this diff