Bladeren bron

修复了一些已知bug

zmj 1 maand geleden
bovenliggende
commit
43e59583a3
100 gewijzigde bestanden met toevoegingen van 6231 en 100 verwijderingen
  1. 1 0
      ruoyi-api/pom.xml
  2. 21 8
      ruoyi-api/ruoyi-api-base/src/main/java/com/ruoyi/base/api/RemoteBaseService.java
  3. 126 0
      ruoyi-api/ruoyi-api-base/src/main/java/com/ruoyi/base/api/domain/AgriculturalMachines.java
  4. 120 0
      ruoyi-api/ruoyi-api-base/src/main/java/com/ruoyi/base/api/domain/AiGrapeDiseaseReport.java
  5. 11 4
      ruoyi-api/ruoyi-api-base/src/main/java/com/ruoyi/base/api/factory/RemoteBaerFallbackFactory.java
  6. 36 0
      ruoyi-api/ruoyi-api-mqtt/pom.xml
  7. 59 0
      ruoyi-api/ruoyi-api-mqtt/src/main/java/com/ruoyi/mqtt/api/MqttRemoteService.java
  8. 14 0
      ruoyi-api/ruoyi-api-mqtt/src/main/java/com/ruoyi/mqtt/api/domain/AreaDTO.java
  9. 13 0
      ruoyi-api/ruoyi-api-mqtt/src/main/java/com/ruoyi/mqtt/api/domain/CustomRouteDTO.java
  10. 30 0
      ruoyi-api/ruoyi-api-mqtt/src/main/java/com/ruoyi/mqtt/api/domain/GongShapeRouteDTO.java
  11. 25 0
      ruoyi-api/ruoyi-api-mqtt/src/main/java/com/ruoyi/mqtt/api/domain/HomePointDTO.java
  12. 24 0
      ruoyi-api/ruoyi-api-mqtt/src/main/java/com/ruoyi/mqtt/api/domain/HuiShapeRouteDTO.java
  13. 76 0
      ruoyi-api/ruoyi-api-mqtt/src/main/java/com/ruoyi/mqtt/api/domain/JobCreateRequestDTO.java
  14. 42 0
      ruoyi-api/ruoyi-api-mqtt/src/main/java/com/ruoyi/mqtt/api/domain/JobCreateResultDTO.java
  15. 29 0
      ruoyi-api/ruoyi-api-mqtt/src/main/java/com/ruoyi/mqtt/api/domain/JobExecuteRequestDTO.java
  16. 22 0
      ruoyi-api/ruoyi-api-mqtt/src/main/java/com/ruoyi/mqtt/api/domain/MetaDTO.java
  17. 24 0
      ruoyi-api/ruoyi-api-mqtt/src/main/java/com/ruoyi/mqtt/api/domain/PointDTO.java
  18. 18 0
      ruoyi-api/ruoyi-api-mqtt/src/main/java/com/ruoyi/mqtt/api/domain/RidgeRouteDTO.java
  19. 21 0
      ruoyi-api/ruoyi-api-mqtt/src/main/java/com/ruoyi/mqtt/api/domain/RouteDTO.java
  20. 46 0
      ruoyi-api/ruoyi-api-mqtt/src/main/java/com/ruoyi/mqtt/api/factory/MqttRemoteFallbackFactory.java
  21. 0 0
      ruoyi-api/ruoyi-api-mqtt/src/main/resources/META-INF/spring.factories
  22. 20 60
      ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysLogininfor.java
  23. 17 1
      ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysUser.java
  24. 2 2
      ruoyi-common/ruoyi-common-core/pom.xml
  25. 10 2
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java
  26. 7 1
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/ServiceNameConstants.java
  27. 57 1
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/UserConstants.java
  28. 25 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/BusinessBaseException.java
  29. 13 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/VcodeException.java
  30. 188 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/json/JSON.java
  31. 51 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/AddressUtils.java
  32. 23 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/DateUtils.java
  33. 72 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/HtmlImageExtractor.java
  34. 30 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/web/controller/BaseController.java
  35. 20 11
      ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/service/TokenService.java
  36. 6 0
      ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/ValidateCodeFilter.java
  37. 1 0
      ruoyi-modules/pom.xml
  38. 1 1
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/controller/AgriculturalMachinesController.java
  39. 121 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/controller/AiGrapeDiseaseReportController.java
  40. 105 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/controller/BatchController.java
  41. 105 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/controller/CertificateController.java
  42. 11 1
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/controller/DeviceController.java
  43. 1 1
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/controller/MallController.java
  44. 155 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/controller/ReportController.java
  45. 118 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/controller/SpeciesInfoController.java
  46. 105 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/controller/SpeciesTempRecordController.java
  47. 131 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/controller/VideoController.java
  48. 119 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/domain/AiGrapeDiseaseReport.java
  49. 101 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/domain/Batch.java
  50. 131 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/domain/Certificate.java
  51. 54 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/domain/KnowledgeImages.java
  52. 56 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/domain/Report.java
  53. 91 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/domain/SpeciesInfo.java
  54. 147 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/domain/SpeciesTempRecord.java
  55. 58 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/domain/Video.java
  56. 43 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/domain/dto/ReportDTO.java
  57. 45 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/domain/dto/VideoUploadDTO.java
  58. 63 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/mapper/AiGrapeDiseaseReportMapper.java
  59. 69 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/mapper/BatchMapper.java
  60. 69 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/mapper/CertificateMapper.java
  61. 10 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/mapper/KnowledgeContentMapper.java
  62. 78 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/mapper/ReportMapper.java
  63. 70 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/mapper/SpeciesInfoMapper.java
  64. 61 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/mapper/SpeciesTempRecordMapper.java
  65. 61 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/mapper/VideoMapper.java
  66. 62 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/IAiGrapeDiseaseReportService.java
  67. 61 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/IBatchService.java
  68. 61 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/ICertificateService.java
  69. 69 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/IReportService.java
  70. 74 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/ISpeciesInfoService.java
  71. 61 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/ISpeciesTempRecordService.java
  72. 75 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/IVideoService.java
  73. 98 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/impl/AiGrapeDiseaseReportServiceImpl.java
  74. 104 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/impl/BatchServiceImpl.java
  75. 93 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/impl/CertificateServiceImpl.java
  76. 3 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/impl/DeviceServiceImpl.java
  77. 37 1
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/impl/KnowledgeContentServiceImpl.java
  78. 118 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/impl/ReportServiceImpl.java
  79. 110 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/impl/SpeciesInfoServiceImpl.java
  80. 95 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/impl/SpeciesTempRecordServiceImpl.java
  81. 211 0
      ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/impl/VideoServiceImpl.java
  82. 1 1
      ruoyi-modules/ruoyi-base/src/main/resources/bootstrap.yml
  83. 2 2
      ruoyi-modules/ruoyi-base/src/main/resources/mapper/base/AgriculturalMachinesMapper.xml
  84. 163 0
      ruoyi-modules/ruoyi-base/src/main/resources/mapper/base/AiGrapeDiseaseReportMapper.xml
  85. 133 0
      ruoyi-modules/ruoyi-base/src/main/resources/mapper/base/BatchMapper.xml
  86. 86 0
      ruoyi-modules/ruoyi-base/src/main/resources/mapper/base/CertificateMapper.xml
  87. 21 0
      ruoyi-modules/ruoyi-base/src/main/resources/mapper/base/KnowledgeContentMapper.xml
  88. 93 0
      ruoyi-modules/ruoyi-base/src/main/resources/mapper/base/ReportMapper.xml
  89. 160 0
      ruoyi-modules/ruoyi-base/src/main/resources/mapper/base/SpeciesInfoMapper.xml
  90. 92 0
      ruoyi-modules/ruoyi-base/src/main/resources/mapper/base/SpeciesTempRecordMapper.xml
  91. 90 0
      ruoyi-modules/ruoyi-base/src/main/resources/mapper/base/VideoMapper.xml
  92. 3 3
      ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/utils/FileUploadUtils.java
  93. 148 0
      ruoyi-modules/ruoyi-mqtt/pom.xml
  94. 33 0
      ruoyi-modules/ruoyi-mqtt/src/main/java/com/ruoyi/mqtt/RuoYiMqttApplication.java
  95. 152 0
      ruoyi-modules/ruoyi-mqtt/src/main/java/com/ruoyi/mqtt/config/MqttConfig.java
  96. 78 0
      ruoyi-modules/ruoyi-mqtt/src/main/java/com/ruoyi/mqtt/controller/MqttController.java
  97. 13 0
      ruoyi-modules/ruoyi-mqtt/src/main/java/com/ruoyi/mqtt/dto/AddJobResponse.java
  98. 52 0
      ruoyi-modules/ruoyi-mqtt/src/main/java/com/ruoyi/mqtt/dto/DiseaseRecognitionReportDTO.java
  99. 52 0
      ruoyi-modules/ruoyi-mqtt/src/main/java/com/ruoyi/mqtt/dto/JobStatusReportDTO.java
  100. 48 0
      ruoyi-modules/ruoyi-mqtt/src/main/java/com/ruoyi/mqtt/dto/LogEventDTO.java

+ 1 - 0
ruoyi-api/pom.xml

@@ -11,6 +11,7 @@
     <modules>
         <module>ruoyi-api-system</module>
         <module>ruoyi-api-base</module>
+        <module>ruoyi-api-mqtt</module>
     </modules>
 
     <artifactId>ruoyi-api</artifactId>

+ 21 - 8
ruoyi-api/ruoyi-api-base/src/main/java/com/ruoyi/base/api/RemoteBaseService.java

@@ -1,19 +1,13 @@
 package com.ruoyi.base.api;
 
 
-import com.ruoyi.base.api.domain.SensorDataStatistics;
-import com.ruoyi.base.api.domain.SensorRealtimeData;
-import com.ruoyi.base.api.domain.WeatherDataStatistics;
-import com.ruoyi.base.api.domain.WeatherRealtimeData;
+import com.ruoyi.base.api.domain.*;
 import com.ruoyi.base.api.factory.RemoteBaerFallbackFactory;
 import com.ruoyi.common.core.constant.SecurityConstants;
 import com.ruoyi.common.core.constant.ServiceNameConstants;
 import com.ruoyi.common.core.web.domain.AjaxResult;
 import org.springframework.cloud.openfeign.FeignClient;
-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.RequestHeader;
+import org.springframework.web.bind.annotation.*;
 
 /**
  * 基础服务
@@ -66,4 +60,23 @@ public interface RemoteBaseService {
     public AjaxResult addWeatherStatistics(@RequestBody WeatherDataStatistics weatherRealtimeData, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
 
 
+    /**
+     * 获取农机管理详细信息
+     */
+    @GetMapping("/machines/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
+
+
+    /**
+     * 新增AI农作物病害诊断报告
+     *
+     * @param aiGrapeDiseaseReport AI农作物病害诊断报告对象
+     * @param source 请求来源
+     * @return
+     */
+    @PostMapping("/GrapeDiseaseReport")
+    AjaxResult addGrapeDiseaseReport(@RequestBody AiGrapeDiseaseReport aiGrapeDiseaseReport,
+                                     @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
+
+
 }

+ 126 - 0
ruoyi-api/ruoyi-api-base/src/main/java/com/ruoyi/base/api/domain/AgriculturalMachines.java

@@ -0,0 +1,126 @@
+package com.ruoyi.base.api.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.common.core.annotation.Excel;
+import com.ruoyi.common.core.web.domain.BaseEntity;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 农机管理列表对象 agricultural_machines
+ *
+ * @author zmj
+ * @date 2025-07-01
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class AgriculturalMachines extends BaseEntity
+{
+
+    /** 农机ID */
+    private Long id;
+
+    /** 农机编号 */
+    @Excel(name = "农机编号")
+    private String machineCode;
+
+    /** 农机名称 */
+    @Excel(name = "农机名称")
+    private String machineName;
+
+    /** 农机类型 */
+    @Excel(name = "农机类型", readConverterExp = "1=拖拉机,2=收割机,3=播种机,4=喷雾机,5=其他")
+    private String machineType;
+
+    /** 部门名称 */
+    @Excel(name = "所属农场")
+    private String deptName;
+
+
+    @Excel(name = "负责人")
+    private String managerName;
+
+    /** 厂家 */
+    @Excel(name = "厂家")
+    private String manufacturer;
+
+    /** 型号 */
+    @Excel(name = "型号")
+    private String model;
+
+    /** 所属农场ID */
+    private Long farmId;
+
+    /** 所属地块区域 */
+    private String fieldArea;
+
+    /** 负责人 */
+    private Long managerId;
+
+
+    /** 生产日期 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "生产日期", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date manufactureDate;
+
+
+    /** 启用时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "启用时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date purchaseDate;
+
+
+
+    /** 当前状态 */
+    @Excel(name = "当前状态", readConverterExp = "0=离线,1=在线,2=待命,3=作业中,4=维护中,5=故障")
+    private Integer onlineStatus;
+
+    /** 当前任务 */
+    @Excel(name = "当前任务")
+    private String currentTask;
+
+    /** 当前地块 */
+    private String currentField;
+
+    /** 保养状态 */
+    @Excel(name = "保养状态", readConverterExp = "1=正常,2=需保养,3=待修复")
+    private String maintenanceStatus;
+
+    /** 定位状态 */
+    @Excel(name = "定位状态", readConverterExp = "1=良好,2=异常,3=无信号")
+    private String locationStatus;
+
+    /** 报警次数 */
+    @Excel(name = "报警次数")
+    private Long alarmCount;
+
+    /** 农机描述 */
+    private String description;
+
+    /** 农机图片 */
+    private String imageUrl;
+
+    /** 状态 */
+    private Integer status;
+
+
+    /** 所属机构id集合-查询条件 */
+    private List<String> deptIdList;
+
+    /** 所属机构id集合-查询条件 */
+    private List<String> fieldIdList;
+
+    /** 所属地块 */
+    private String fieldName;
+
+
+
+
+
+
+}

+ 120 - 0
ruoyi-api/ruoyi-api-base/src/main/java/com/ruoyi/base/api/domain/AiGrapeDiseaseReport.java

@@ -0,0 +1,120 @@
+package com.ruoyi.base.api.domain;
+
+
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.common.core.annotation.Excel;
+import com.ruoyi.common.core.web.domain.BaseEntity;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * AI葡萄病害诊断报告对象 ai_grape_disease_report
+ *
+ * @author zmj
+ * @date 2026-01-12
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class AiGrapeDiseaseReport extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 主键(自增) */
+    private Long id;
+
+    /** 作物类型 */
+    @Excel(name = "作物类型")
+    private String cropType;
+
+    /** 设备编号 */
+    @Excel(name = "设备编号")
+    private String deviceCode;
+
+    /** 经度 */
+    @Excel(name = "经度")
+    private Double lng;
+
+    /** 纬度 */
+    @Excel(name = "纬度")
+    private Double lat;
+
+    /** 病害名称 */
+    @Excel(name = "病害名称")
+    private String diseaseName;
+
+    /** AI识别置信度 */
+    @Excel(name = "AI识别置信度")
+    private BigDecimal confidence;
+
+    /** 严重程度 */
+    @Excel(name = "严重程度")
+    private Long severityLevel;
+
+    /** 采集/识别时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "采集/识别时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date collectTime;
+
+    /** 所在地块/农场 */
+    @Excel(name = "所在地块/农场")
+    private String farmName;
+    private Long farmId;
+
+    /** 病害图片访问URL */
+    @Excel(name = "病害图片访问URL")
+    private String imgUrl;
+
+    /** 处理状态 */
+    @Excel(name = "处理状态")
+    private Long handleStatus;
+
+    /** 紧急处理措施 */
+    @Excel(name = "紧急处理措施")
+    private String emergencyMeasure;
+
+    /** 田间管理建议 */
+    @Excel(name = "田间管理建议")
+    private String manageAdvice;
+
+    /** 预防方案 */
+    @Excel(name = "预防方案")
+    private String preventPlan;
+
+    /** 处理备注 */
+    @Excel(name = "处理备注")
+    private String handleNote;
+
+    /** 处理人 */
+    @Excel(name = "处理人")
+    private String handleUser;
+
+    /** 处理时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "处理时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date handleTime;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "创建时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date createdTime;
+
+    /** 更新时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "更新时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date updatedTime;
+
+    /** 所属机构id集合-查询条件 */
+    private List<String> deptIdList;
+
+
+}

+ 11 - 4
ruoyi-api/ruoyi-api-base/src/main/java/com/ruoyi/base/api/factory/RemoteBaerFallbackFactory.java

@@ -3,10 +3,7 @@ package com.ruoyi.base.api.factory;
 
 
 import com.ruoyi.base.api.RemoteBaseService;
-import com.ruoyi.base.api.domain.SensorDataStatistics;
-import com.ruoyi.base.api.domain.SensorRealtimeData;
-import com.ruoyi.base.api.domain.WeatherDataStatistics;
-import com.ruoyi.base.api.domain.WeatherRealtimeData;
+import com.ruoyi.base.api.domain.*;
 import com.ruoyi.common.core.domain.R;
 import com.ruoyi.common.core.web.domain.AjaxResult;
 import org.slf4j.Logger;
@@ -48,6 +45,16 @@ public class RemoteBaerFallbackFactory implements FallbackFactory<RemoteBaseServ
                 return AjaxResult.error("新增气象设备统计数据失败:" + throwable.getMessage());
             }
 
+            @Override
+            public AjaxResult getInfo(Long id, String source) {
+                return AjaxResult.error("获取农机管理详细信息失败:" + throwable.getMessage());
+            }
+
+            @Override
+            public AjaxResult addGrapeDiseaseReport(AiGrapeDiseaseReport aiGrapeDiseaseReport, String source) {
+                return AjaxResult.error("新增AI农作物病害诊断报告失败:" + throwable.getMessage());
+            }
+
 
         };
     }

+ 36 - 0
ruoyi-api/ruoyi-api-mqtt/pom.xml

@@ -0,0 +1,36 @@
+<?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-api</artifactId>
+        <groupId>com.ruoyi</groupId>
+        <version>3.6.5</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-api-mqtt</artifactId>
+
+
+    <description>
+        ruoyi-api-mqtt接口模块
+    </description>
+
+    <dependencies>
+
+
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-api-system</artifactId>
+        </dependency>
+
+
+    </dependencies>
+
+</project>

+ 59 - 0
ruoyi-api/ruoyi-api-mqtt/src/main/java/com/ruoyi/mqtt/api/MqttRemoteService.java

@@ -0,0 +1,59 @@
+package com.ruoyi.mqtt.api;
+
+
+import com.ruoyi.common.core.constant.SecurityConstants;
+import com.ruoyi.common.core.constant.ServiceNameConstants;
+import com.ruoyi.common.core.web.domain.AjaxResult;
+import com.ruoyi.mqtt.api.domain.JobCreateRequestDTO;
+import com.ruoyi.mqtt.api.domain.JobExecuteRequestDTO;
+import com.ruoyi.mqtt.api.factory.MqttRemoteFallbackFactory;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestParam;
+
+/**
+ * MQTT服务Feign远程调用接口(契约层)
+ * value:ruoyi-mqtt在Nacos的服务名
+ * fallbackFactory:可选,降级处理(避免服务不可用时报错)
+ */
+@FeignClient(value = ServiceNameConstants.MQTT_SERVICE, contextId = "mqttRemoteService", fallbackFactory = MqttRemoteFallbackFactory.class)
+public interface MqttRemoteService {
+
+    /**
+     * 基础发送MQTT消息接口(通用)
+     * @param topic 消息主题
+     * @param payload 消息体(JSON字符串)
+     * @return 发送结果
+     */
+    @PostMapping("/mqtt/sendMessage")
+    AjaxResult sendMqttMessage(
+            @RequestParam("topic") String topic,
+            @RequestParam("payload") String payload, @RequestHeader(SecurityConstants.FROM_SOURCE) String source
+    );
+
+    /**
+     * 业务封装:下发农机作业指令(推荐,参数结构化)
+     * @return 发送结果
+     */
+    @PostMapping("/mqtt/sendTaskCommand")
+    AjaxResult sendTaskCommand(@RequestParam("machineCode") String machineCode,
+                               @RequestParam("jobId") String jobId,
+                               @RequestParam("action") String action,
+                               @RequestParam("requestId") String requestId, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
+
+
+    /**
+     * 新增作业(创建作业指令下发)
+     * 对应Topic: device/{machineCode}/cmd/job/create
+     * @param machineCode 设备编码
+     * @param request 作业创建请求参数
+     * @return 作业创建结果
+     */
+    @PostMapping("/mqtt/createJob")
+    AjaxResult createJob(
+            @RequestParam("machineCode") String machineCode,
+            @RequestBody JobCreateRequestDTO request, @RequestHeader(SecurityConstants.FROM_SOURCE) String source
+    );
+}

+ 14 - 0
ruoyi-api/ruoyi-api-mqtt/src/main/java/com/ruoyi/mqtt/api/domain/AreaDTO.java

@@ -0,0 +1,14 @@
+package com.ruoyi.mqtt.api.domain;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class AreaDTO {
+    /**
+     * 区域顶点数组
+     * 必传,至少包含3个顶点坐标
+     */
+    private List<PointDTO> points;
+}

+ 13 - 0
ruoyi-api/ruoyi-api-mqtt/src/main/java/com/ruoyi/mqtt/api/domain/CustomRouteDTO.java

@@ -0,0 +1,13 @@
+package com.ruoyi.mqtt.api.domain;
+
+import lombok.Data;
+
+/**
+ * 自定义路线DTO
+ * 对应jobType=CUSTOM的路线参数
+ * 无额外参数,仅包含mode字段
+ */
+@Data
+public class CustomRouteDTO extends RouteDTO {
+    // 无额外参数,使用父类的mode字段(固定为CUSTOM)
+}

+ 30 - 0
ruoyi-api/ruoyi-api-mqtt/src/main/java/com/ruoyi/mqtt/api/domain/GongShapeRouteDTO.java

@@ -0,0 +1,30 @@
+package com.ruoyi.mqtt.api.domain;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 弓子形路线DTO
+ * 对应jobType=GONGSHAPE的路线参数
+ */
+@Data
+public class GongShapeRouteDTO extends RouteDTO {
+    /**
+     * 路径宽度
+     * 必传,弓子形路线的路径宽度
+     */
+    private double pathWidth;
+
+    /**
+     * 航线角度(°)
+     * 必传,弓子形路线的角度参数
+     */
+    private double lineAngle;
+
+    /**
+     * 作业起点坐标
+     * 必传,弓子形作业的起始位置
+     */
+    private List<PointDTO> startPoint;
+}

+ 25 - 0
ruoyi-api/ruoyi-api-mqtt/src/main/java/com/ruoyi/mqtt/api/domain/HomePointDTO.java

@@ -0,0 +1,25 @@
+package com.ruoyi.mqtt.api.domain;
+
+
+import lombok.Data;
+
+/**
+ * 返航点DTO
+ * 描述设备完成作业后的返航位置及朝向
+ */
+@Data
+public class HomePointDTO {
+    /**
+     * 返航点X坐标
+     * 必传
+     */
+    private double x;
+
+    /**
+     * 返航点Y坐标
+     * 必传
+     */
+    private double y;
+
+
+}

+ 24 - 0
ruoyi-api/ruoyi-api-mqtt/src/main/java/com/ruoyi/mqtt/api/domain/HuiShapeRouteDTO.java

@@ -0,0 +1,24 @@
+package com.ruoyi.mqtt.api.domain;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 回字形路线DTO
+ * 对应jobType=HUISHAPE的路线参数
+ */
+@Data
+public class HuiShapeRouteDTO extends RouteDTO {
+    /**
+     * 路径宽度(米)
+     * 必传,回字形路线的路径宽度
+     */
+    private double pathWidth;
+
+    /**
+     * 障碍物点
+     * 必传,回字形作业的起始位置
+     */
+    private List<PointDTO> startPoint;
+}

+ 76 - 0
ruoyi-api/ruoyi-api-mqtt/src/main/java/com/ruoyi/mqtt/api/domain/JobCreateRequestDTO.java

@@ -0,0 +1,76 @@
+package com.ruoyi.mqtt.api.domain;
+
+import lombok.Data;
+
+/**
+ * 作业创建请求DTO
+ * 对应MQTT Topic: device/{deviceId}/cmd/job/create
+ */
+@Data
+public class JobCreateRequestDTO {
+    /**
+     * 请求唯一ID(幂等)
+     * 必传,用于标识单次请求的唯一性
+     */
+    private String requestId;
+
+    /**
+     * 路线类型
+     * 必传,枚举值:HUISHAPE(回字形)、GONGSHAPE(弓子形)、CUSTOM(自定义)、RIDGE(垄沟)
+     */
+    private Long jobType;
+
+    /**
+     * 作业ID
+     * 必传,用于标识作业的唯一ID
+     */
+    private String jobId;
+    /**
+     * 操作类型
+     * 必传,枚举值:START(开始)、PAUSE(暂停)、RESUME(继续)、CANCEL(取消)、RECALL(召回)
+     */
+    private String action;
+
+    /**
+     * 作业名称
+     * 必传,用于标识作业的名称信息
+     */
+    private String jobName;
+
+    /**
+     * 作业区域信息
+     * 必传,包含区域顶点坐标数组
+     */
+    private AreaDTO area;
+
+
+    /**
+     * 障碍物区域
+     * 可选,包含区域顶点坐标数组
+     */
+    private AreaDTO obstacle;
+
+    /**
+     * 路径宽度
+     * 可选,用于标识作业的宽度信息
+     */
+    private Double pathWidth;
+
+    /**
+     * 路线参数
+     * 必传,根据jobType不同包含不同的参数结构
+     */
+    //private RouteDTO route;
+
+    /**
+     * 返航点信息
+     * 必传,包含返航点坐标及朝向
+     */
+    private HomePointDTO homePoint;
+
+    /**
+     * 元数据信息
+     * 可选,包含创建人及创建时间戳
+     */
+    private MetaDTO meta;
+}

+ 42 - 0
ruoyi-api/ruoyi-api-mqtt/src/main/java/com/ruoyi/mqtt/api/domain/JobCreateResultDTO.java

@@ -0,0 +1,42 @@
+package com.ruoyi.mqtt.api.domain;
+
+import lombok.Data;
+
+/**
+ * 作业创建结果DTO
+ * 对应MQTT Topic: device/{deviceId}/report/job/create_result
+ * 用于返回作业创建的成功或失败信息
+ */
+@Data
+public class JobCreateResultDTO {
+    /**
+     * 请求唯一ID
+     * 与创建请求中的requestId对应
+     */
+    private String requestId;
+
+    /**
+     * 处理结果
+     * 枚举值:SUCCESS(成功)、FAIL(失败)
+     */
+    private String result;
+
+    /**
+     * 作业ID
+     * 成功时返回,用于标识创建的作业
+     */
+    private String jobId;
+
+    /**
+     * 错误码
+     * 失败时返回,参考协议中的错误码定义(如4001=作业类型不支持)
+     */
+    private Integer errorCode;
+
+    /**
+     * 错误信息
+     * 失败时返回,描述错误原因
+     */
+    private String message;
+}
+

+ 29 - 0
ruoyi-api/ruoyi-api-mqtt/src/main/java/com/ruoyi/mqtt/api/domain/JobExecuteRequestDTO.java

@@ -0,0 +1,29 @@
+package com.ruoyi.mqtt.api.domain;
+
+import lombok.Data;
+
+/**
+ * 作业执行请求DTO
+ * 对应MQTT Topic: device/{deviceId}/cmd/job/execute
+ * 用于下发作业执行相关指令(开始、暂停、继续等)
+ */
+@Data
+public class JobExecuteRequestDTO {
+    /**
+     * 执行请求唯一ID(幂等)
+     * 用于标识单次执行请求的唯一性
+     */
+    private String requestId;
+
+    /**
+     * 作业ID
+     * 必传,指定要操作的作业
+     */
+    private String jobId;
+
+    /**
+     * 操作类型
+     * 枚举值:START(开始)、PAUSE(暂停)、RESUME(继续)、CANCEL(取消)、RECALL(召回)
+     */
+    private String action;
+}

+ 22 - 0
ruoyi-api/ruoyi-api-mqtt/src/main/java/com/ruoyi/mqtt/api/domain/MetaDTO.java

@@ -0,0 +1,22 @@
+package com.ruoyi.mqtt.api.domain;
+
+import lombok.Data;
+
+/**
+ * 元数据DTO
+ * 包含作业的创建信息
+ */
+@Data
+public class MetaDTO {
+    /**
+     * 创建人标识
+     * 如用户ID或系统标识
+     */
+    private String createdBy;
+
+    /**
+     * 创建时间戳
+     * 毫秒级时间戳,标识作业创建时间
+     */
+    private Long createdAt;
+}

+ 24 - 0
ruoyi-api/ruoyi-api-mqtt/src/main/java/com/ruoyi/mqtt/api/domain/PointDTO.java

@@ -0,0 +1,24 @@
+package com.ruoyi.mqtt.api.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 坐标点DTO
+ * 用于描述二维平面中的坐标位置
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class PointDTO {
+    /**
+     * X坐标值
+     */
+    private double x;
+
+    /**
+     * Y坐标值
+     */
+    private double y;
+}

+ 18 - 0
ruoyi-api/ruoyi-api-mqtt/src/main/java/com/ruoyi/mqtt/api/domain/RidgeRouteDTO.java

@@ -0,0 +1,18 @@
+package com.ruoyi.mqtt.api.domain;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 垄沟区域路线DTO
+ * 对应jobType=RIDGE的路线参数
+ */
+@Data
+public class RidgeRouteDTO extends RouteDTO {
+    /**
+     * 连线形成的航迹点数组
+     * 必传,垄沟路线的航迹点集合
+     */
+    private List<PointDTO> lines;
+}

+ 21 - 0
ruoyi-api/ruoyi-api-mqtt/src/main/java/com/ruoyi/mqtt/api/domain/RouteDTO.java

@@ -0,0 +1,21 @@
+package com.ruoyi.mqtt.api.domain;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 路线DTO(父类)
+ * 定义所有路线类型的公共字段
+ */
+@Data
+@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
+public abstract class RouteDTO {
+    /**
+     * 路线模式
+     * 必传,与jobType保持一致,用于标识具体路线类型
+     */
+    private String mode;
+}

+ 46 - 0
ruoyi-api/ruoyi-api-mqtt/src/main/java/com/ruoyi/mqtt/api/factory/MqttRemoteFallbackFactory.java

@@ -0,0 +1,46 @@
+package com.ruoyi.mqtt.api.factory;
+
+
+import com.ruoyi.common.core.web.domain.AjaxResult;
+import com.ruoyi.mqtt.api.MqttRemoteService;
+import com.ruoyi.mqtt.api.domain.JobCreateRequestDTO;
+import com.ruoyi.mqtt.api.domain.JobExecuteRequestDTO;
+import com.ruoyi.system.api.factory.RemoteLogFallbackFactory;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.cloud.openfeign.FallbackFactory;
+import org.springframework.stereotype.Component;
+
+
+/**
+ * MQTT Feign调用降级处理(ruoyi-mqtt服务不可用时触发)
+ */
+@Component
+public class MqttRemoteFallbackFactory implements FallbackFactory<MqttRemoteService> {
+
+    private static final Logger log = LoggerFactory.getLogger(MqttRemoteFallbackFactory.class);
+
+    @Override
+    public MqttRemoteService create(Throwable throwable) {
+        log.error("MQTT服务调用失败:{}", throwable.getMessage());
+        return new MqttRemoteService() {
+            @Override
+            public AjaxResult sendMqttMessage(String topic, String payload, String source) {
+                return AjaxResult.error("MQTT服务暂不可用,消息发送失败");
+            }
+
+            @Override
+            public AjaxResult sendTaskCommand(String machineCode, String jobId, String action, String requestId, String source) {
+                return AjaxResult.error("MQTT服务暂不可用,作业指令下发失败");
+            }
+
+            @Override
+            public AjaxResult createJob(String machineCode, JobCreateRequestDTO request, String source) {
+                return AjaxResult.error("MQTT服务暂不可用,作业指令下发失败");
+            }
+
+
+        };
+    }
+}

+ 0 - 0
ruoyi-api/ruoyi-api-mqtt/src/main/resources/META-INF/spring.factories


+ 20 - 60
ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysLogininfor.java

@@ -5,12 +5,18 @@ import com.fasterxml.jackson.annotation.JsonFormat;
 import com.ruoyi.common.core.annotation.Excel;
 import com.ruoyi.common.core.annotation.Excel.ColumnType;
 import com.ruoyi.common.core.web.domain.BaseEntity;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
 
 /**
  * 系统访问记录表 sys_logininfor
- * 
+ *
  * @author ruoyi
  */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
 public class SysLogininfor extends BaseEntity
 {
     private static final long serialVersionUID = 1L;
@@ -31,6 +37,18 @@ public class SysLogininfor extends BaseEntity
     @Excel(name = "地址")
     private String ipaddr;
 
+    /** 登录地点 */
+    @Excel(name = "登录地点")
+    private String loginLocation;
+
+    /** 浏览器类型 */
+    @Excel(name = "浏览器")
+    private String browser;
+
+    /** 操作系统 */
+    @Excel(name = "操作系统 ")
+    private String os;
+
     /** 描述 */
     @Excel(name = "描述")
     private String msg;
@@ -40,63 +58,5 @@ public class SysLogininfor extends BaseEntity
     @Excel(name = "访问时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
     private Date accessTime;
 
-    public Long getInfoId()
-    {
-        return infoId;
-    }
-
-    public void setInfoId(Long infoId)
-    {
-        this.infoId = infoId;
-    }
-
-    public String getUserName()
-    {
-        return userName;
-    }
-
-    public void setUserName(String userName)
-    {
-        this.userName = userName;
-    }
-
-    public String getStatus()
-    {
-        return status;
-    }
-
-    public void setStatus(String status)
-    {
-        this.status = status;
-    }
-
-    public String getIpaddr()
-    {
-        return ipaddr;
-    }
-
-    public void setIpaddr(String ipaddr)
-    {
-        this.ipaddr = ipaddr;
-    }
-
-    public String getMsg()
-    {
-        return msg;
-    }
-
-    public void setMsg(String msg)
-    {
-        this.msg = msg;
-    }
-
-    public Date getAccessTime()
-    {
-        return accessTime;
-    }
 
-    public void setAccessTime(Date accessTime)
-    {
-        this.accessTime = accessTime;
-    }
-}
+}

+ 17 - 1
ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysUser.java

@@ -3,6 +3,10 @@ package com.ruoyi.system.api.domain;
 import java.util.Date;
 import java.util.List;
 import javax.validation.constraints.*;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
 import org.apache.commons.lang3.builder.ToStringBuilder;
 import org.apache.commons.lang3.builder.ToStringStyle;
 import com.ruoyi.common.core.annotation.Excel;
@@ -15,9 +19,10 @@ import com.ruoyi.common.core.xss.Xss;
 
 /**
  * 用户对象 sys_user
- * 
+ *
  * @author ruoyi
  */
+@Data
 public class SysUser extends BaseEntity
 {
     private static final long serialVersionUID = 1L;
@@ -85,6 +90,9 @@ public class SysUser extends BaseEntity
     })
     private SysDept dept;
 
+    /** 盐加密 */
+    private String salt;
+
     /** 角色对象 */
     private List<SysRole> roles;
 
@@ -97,6 +105,14 @@ public class SysUser extends BaseEntity
     /** 角色ID */
     private Long roleId;
 
+    @Excel(name = "岗位")
+    private String postName;
+
+    //服是否绑定手机  0=未绑定 1绑定)
+    private Integer bindingPhone;
+
+
+
     public SysUser()
     {
 

+ 2 - 2
ruoyi-common/ruoyi-common-core/pom.xml

@@ -10,7 +10,7 @@
     <modelVersion>4.0.0</modelVersion>
 
     <artifactId>ruoyi-common-core</artifactId>
-    
+
     <description>
         ruoyi-common-core核心模块
     </description>
@@ -22,7 +22,7 @@
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-starter-openfeign</artifactId>
         </dependency>
-        
+
         <!-- SpringCloud Loadbalancer -->
         <dependency>
             <groupId>org.springframework.cloud</groupId>

+ 10 - 2
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java

@@ -2,7 +2,7 @@ package com.ruoyi.common.core.constant;
 
 /**
  * 缓存常量信息
- * 
+ *
  * @author ruoyi
  */
 public class CacheConstants
@@ -11,11 +11,19 @@ public class CacheConstants
      * 缓存有效期,默认720(分钟)
      */
     public final static long EXPIRATION = 720;
+    /**
+     * 缓存有效期,默认30(天)
+     */
+    public final static long UNIAPPEXPIRATION = 30;
 
+    /**
+     * 缓存刷新时间,默认1440(分钟)
+     */
+    public final static long REFRESH_TIME = 1440;
     /**
      * 缓存刷新时间,默认120(分钟)
      */
-    public final static long REFRESH_TIME = 120;
+//    public final static long REFRESH_TIME = 120;
 
     /**
      * 密码最大错误次数

+ 7 - 1
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/ServiceNameConstants.java

@@ -2,7 +2,7 @@ package com.ruoyi.common.core.constant;
 
 /**
  * 服务名称
- * 
+ *
  * @author ruoyi
  */
 public class ServiceNameConstants
@@ -29,4 +29,10 @@ public class ServiceNameConstants
      */
     public static final String BASE_SERVICE = "ruoyi-base";
 
+
+    /**
+     * mqtt的serviceid
+     */
+    public static final String MQTT_SERVICE = "ruoyi-mqtt";
+
 }

+ 57 - 1
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/UserConstants.java

@@ -2,7 +2,7 @@ package com.ruoyi.common.core.constant;
 
 /**
  * 用户常量信息
- * 
+ *
  * @author ruoyi
  */
 public class UserConstants
@@ -74,6 +74,51 @@ public class UserConstants
 
     public static final int USERNAME_MAX_LENGTH = 20;
 
+
+    /** 登录名称是否唯一的返回结果码 */
+    public final static String USER_NAME_UNIQUE = "0";
+    public final static String USER_NAME_NOT_UNIQUE = "1";
+
+    /** 手机号码是否唯一的返回结果 */
+    public final static String USER_PHONE_UNIQUE = "0";
+    public final static String USER_PHONE_NOT_UNIQUE = "1";
+
+    /** e-mail 是否唯一的返回结果 */
+    public final static String USER_EMAIL_UNIQUE = "0";
+    public final static String USER_EMAIL_NOT_UNIQUE = "1";
+
+    /** 部门名称是否唯一的返回结果码 */
+    public final static String DEPT_NAME_UNIQUE = "0";
+    public final static String DEPT_NAME_NOT_UNIQUE = "1";
+
+    /** 角色名称是否唯一的返回结果码 */
+    public final static String ROLE_NAME_UNIQUE = "0";
+    public final static String ROLE_NAME_NOT_UNIQUE = "1";
+
+    /** 岗位名称是否唯一的返回结果码 */
+    public final static String POST_NAME_UNIQUE = "0";
+    public final static String POST_NAME_NOT_UNIQUE = "1";
+
+    /** 角色权限是否唯一的返回结果码 */
+    public final static String ROLE_KEY_UNIQUE = "0";
+    public final static String ROLE_KEY_NOT_UNIQUE = "1";
+
+    /** 岗位编码是否唯一的返回结果码 */
+    public final static String POST_CODE_UNIQUE = "0";
+    public final static String POST_CODE_NOT_UNIQUE = "1";
+
+    /** 菜单名称是否唯一的返回结果码 */
+    public final static String MENU_NAME_UNIQUE = "0";
+    public final static String MENU_NAME_NOT_UNIQUE = "1";
+
+    /** 字典类型是否唯一的返回结果码 */
+    public final static String DICT_TYPE_UNIQUE = "0";
+    public final static String DICT_TYPE_NOT_UNIQUE = "1";
+
+    /** 参数键名是否唯一的返回结果码 */
+    public final static String CONFIG_KEY_UNIQUE = "0";
+    public final static String CONFIG_KEY_NOT_UNIQUE = "1";
+
     /**
      * 密码长度限制
      */
@@ -81,6 +126,17 @@ public class UserConstants
 
     public static final int PASSWORD_MAX_LENGTH = 20;
 
+    /**
+     * 手机号码格式限制
+     */
+    public static final String MOBILE_PHONE_NUMBER_PATTERN = "^0{0,1}(13[0-9]|15[0-9]|14[0-9]|18[0-9])[0-9]{8}$";
+
+    /**
+     * 邮箱格式限制
+     */
+    public static final String EMAIL_PATTERN = "^((([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+(\\.([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(\\\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.?";
+
+
     public static boolean isAdmin(Long userId)
     {
         return userId != null && 1L == userId;

+ 25 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/BusinessBaseException.java

@@ -0,0 +1,25 @@
+package com.ruoyi.common.core.exception;
+
+/**
+ * 基础异常
+ */
+public class BusinessBaseException extends RuntimeException {
+
+    private static final long serialVersionUID = -8429604276572803453L;
+
+    private String msg;
+
+    public String getMsg() {
+        return msg;
+    }
+
+    public void setMsg(String msg) {
+        this.msg = msg;
+    }
+
+    @Override
+    public String getMessage() {
+        return msg;
+    }
+}
+

+ 13 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/VcodeException.java

@@ -0,0 +1,13 @@
+package com.ruoyi.common.core.exception;
+
+/**
+ * 验证码.
+ */
+public class VcodeException extends BusinessBaseException {
+
+    private static final long serialVersionUID = -6616761792443893950L;
+
+    public VcodeException(String msg) {
+        setMsg(msg);
+    }
+}

+ 188 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/json/JSON.java

@@ -0,0 +1,188 @@
+package com.ruoyi.common.core.json;
+
+import com.fasterxml.jackson.core.JsonGenerationException;
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * JSON解析处理
+ *
+ *
+ */
+public class JSON
+{
+    public static final String DEFAULT_FAIL = "\"Parse failed\"";
+    private static final ObjectMapper objectMapper = new ObjectMapper();
+    private static final ObjectWriter objectWriter = objectMapper.writerWithDefaultPrettyPrinter();
+
+    public static void marshal(File file, Object value) throws Exception
+    {
+        try
+        {
+            objectWriter.writeValue(file, value);
+        }
+        catch (JsonGenerationException e)
+        {
+            throw new Exception(e);
+        }
+        catch (JsonMappingException e)
+        {
+            throw new Exception(e);
+        }
+        catch (IOException e)
+        {
+            throw new Exception(e);
+        }
+    }
+
+    public static void marshal(OutputStream os, Object value) throws Exception
+    {
+        try
+        {
+            objectWriter.writeValue(os, value);
+        }
+        catch (JsonGenerationException e)
+        {
+            throw new Exception(e);
+        }
+        catch (JsonMappingException e)
+        {
+            throw new Exception(e);
+        }
+        catch (IOException e)
+        {
+            throw new Exception(e);
+        }
+    }
+
+    public static String marshal(Object value) throws Exception
+    {
+        try
+        {
+            return objectWriter.writeValueAsString(value);
+        }
+        catch (JsonGenerationException e)
+        {
+            throw new Exception(e);
+        }
+        catch (JsonMappingException e)
+        {
+            throw new Exception(e);
+        }
+        catch (IOException e)
+        {
+            throw new Exception(e);
+        }
+    }
+
+    public static byte[] marshalBytes(Object value) throws Exception
+    {
+        try
+        {
+            return objectWriter.writeValueAsBytes(value);
+        }
+        catch (JsonGenerationException e)
+        {
+            throw new Exception(e);
+        }
+        catch (JsonMappingException e)
+        {
+            throw new Exception(e);
+        }
+        catch (IOException e)
+        {
+            throw new Exception(e);
+        }
+    }
+
+    public static <T> T unmarshal(File file, Class<T> valueType) throws Exception
+    {
+        try
+        {
+            return objectMapper.readValue(file, valueType);
+        }
+        catch (JsonParseException e)
+        {
+            throw new Exception(e);
+        }
+        catch (JsonMappingException e)
+        {
+            throw new Exception(e);
+        }
+        catch (IOException e)
+        {
+            throw new Exception(e);
+        }
+    }
+
+    public static <T> T unmarshal(InputStream is, Class<T> valueType) throws Exception
+    {
+        try
+        {
+            return objectMapper.readValue(is, valueType);
+        }
+        catch (JsonParseException e)
+        {
+            throw new Exception(e);
+        }
+        catch (JsonMappingException e)
+        {
+            throw new Exception(e);
+        }
+        catch (IOException e)
+        {
+            throw new Exception(e);
+        }
+    }
+
+    public static <T> T unmarshal(String str, Class<T> valueType) throws Exception
+    {
+        try
+        {
+            return objectMapper.readValue(str, valueType);
+        }
+        catch (JsonParseException e)
+        {
+            throw new Exception(e);
+        }
+        catch (JsonMappingException e)
+        {
+            throw new Exception(e);
+        }
+        catch (IOException e)
+        {
+            throw new Exception(e);
+        }
+    }
+
+    public static <T> T unmarshal(byte[] bytes, Class<T> valueType) throws Exception
+    {
+        try
+        {
+            if (bytes == null)
+            {
+                bytes = new byte[0];
+            }
+            return objectMapper.readValue(bytes, 0, bytes.length, valueType);
+        }
+        catch (JsonParseException e)
+        {
+            throw new Exception(e);
+        }
+        catch (JsonMappingException e)
+        {
+            throw new Exception(e);
+        }
+        catch (IOException e)
+        {
+            throw new Exception(e);
+        }
+    }
+}

+ 51 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/AddressUtils.java

@@ -0,0 +1,51 @@
+package com.ruoyi.common.core.utils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 获取地址类
+ *
+ *
+ */
+public class AddressUtils
+{
+    private static final Logger log = LoggerFactory.getLogger(AddressUtils.class);
+
+    public static final String IP_URL = "http://ip.taobao.com/service/getIpInfo.php";
+
+    public static String getRealAddressByIP(String ip)
+    {
+        String address = "XX XX";
+
+       /* // 内网不查询
+        if (IpUtils.internalIp(ip))
+        {
+            return "内网IP";
+        }
+        if (true)
+        // if (Global.isAddressEnabled())
+        {
+            String rspStr = HttpUtils.sendPost(IP_URL, "ip=" + ip);
+            if (StringUtils.isEmpty(rspStr))
+            {
+                //log.error("获取地理位置异常 {}", ip);
+                return address;
+            }
+            JSONObject obj;
+            try
+            {
+                obj = JSON.unmarshal(rspStr, JSONObject.class);
+                JSONObject data = obj.getObj("data");
+                String region = data.getStr("region");
+                String city = data.getStr("city");
+                address = region + " " + city;
+            }
+            catch (Exception e)
+            {
+                //log.error("获取地理位置异常 {}", ip);
+            }
+        }*/
+        return address;
+    }
+}

+ 23 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/DateUtils.java

@@ -8,6 +8,7 @@ import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
+import java.util.Calendar;
 import java.util.Date;
 import org.apache.commons.lang3.time.DateFormatUtils;
 
@@ -28,6 +29,11 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
 
     public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
 
+    private final static SimpleDateFormat shortSdf = new SimpleDateFormat("yyyy-MM-dd");
+    private final static SimpleDateFormat longHourSdf = new SimpleDateFormat("yyyy-MM-dd HH");;
+    private final static SimpleDateFormat longSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");;
+    private final static SimpleDateFormat hourSdf = new SimpleDateFormat("HH:mm");
+
     private static String[] parsePatterns = {
             "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
             "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
@@ -180,4 +186,21 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
         ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
         return Date.from(zdt.toInstant());
     }
+
+
+    /**
+     * 当前天的开始时间
+     *
+     * @return
+     */
+    public static Date getCurrentDayStartTime() {
+        Calendar c = Calendar.getInstance();
+        Date now = null;
+        try {
+            now = longSdf.parse(shortSdf.format(c.getTime()) + " 00:00:00");
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return now;
+    }
 }

+ 72 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/HtmlImageExtractor.java

@@ -0,0 +1,72 @@
+package com.ruoyi.common.core.utils;
+
+
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * HTML 图片提取工具类
+ *
+ * @author ruoyi
+ */
+public class HtmlImageExtractor
+{
+    /**
+     * 从 HTML 内容中提取所有图片的 src 属性
+     *
+     * @param htmlContent HTML 内容
+     * @return 图片路径列表
+     */
+    public static List<String> extractImageSrcs(String htmlContent)
+    {
+        List<String> imageSrcs = new ArrayList<>();
+
+        if (htmlContent == null || htmlContent.trim().isEmpty())
+        {
+            return imageSrcs;
+        }
+
+        // 匹配 img 标签的 src 属性
+        String regex = "<img[^>]+src\\s*=\\s*['\"]([^'\"]+)['\"][^>]*>";
+        Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
+        Matcher matcher = pattern.matcher(htmlContent);
+
+        while (matcher.find())
+        {
+            String src = matcher.group(1);
+            if (src != null && !src.trim().isEmpty())
+            {
+                imageSrcs.add(src);
+            }
+        }
+
+        return imageSrcs;
+    }
+
+    /**
+     * 从 HTML 内容中提取第一张图片的 src 属性
+     *
+     * @param htmlContent HTML 内容
+     * @return 第一个图片路径,如果没有则返回 null
+     */
+    public static String extractFirstImageSrc(String htmlContent)
+    {
+        List<String> imageSrcs = extractImageSrcs(htmlContent);
+        return imageSrcs.isEmpty() ? null : imageSrcs.get(0);
+    }
+
+    /**
+     * 统计 HTML 内容中的图片数量
+     *
+     * @param htmlContent HTML 内容
+     * @return 图片数量
+     */
+    public static int countImages(String htmlContent)
+    {
+        return extractImageSrcs(htmlContent).size();
+    }
+}
+

+ 30 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/web/controller/BaseController.java

@@ -97,6 +97,21 @@ public class BaseController
         return AjaxResult.success();
     }
 
+    /**
+     * 返回成功消息
+     *
+     * @param msg 内容
+     * @return 成功消息
+     */
+    public static AjaxResult success(Object obj,String msg)
+    {
+        AjaxResult json = new AjaxResult();
+        json.put("msg", msg);
+        json.put("code", 200);
+        json.put("result", obj);
+        return json;
+    }
+
     /**
      * 返回成功消息
      */
@@ -121,6 +136,21 @@ public class BaseController
         return AjaxResult.error();
     }
 
+    /**
+     * 返回错误消息
+     *
+     * @param code 错误码
+     * @param msg 内容
+     * @return 错误消息
+     */
+    public static AjaxResult error(int code, String msg)
+    {
+        AjaxResult json = new AjaxResult();
+        json.put("code", code);
+        json.put("msg", msg);
+        return json;
+    }
+
     /**
      * 返回失败消息
      */

+ 20 - 11
ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/service/TokenService.java

@@ -21,7 +21,7 @@ import com.ruoyi.system.api.model.LoginUser;
 
 /**
  * token验证处理
- * 
+ *
  * @author ruoyi
  */
 @Component
@@ -35,13 +35,14 @@ public class TokenService
     protected static final long MILLIS_SECOND = 1000;
 
     protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
-
-    private final static long TOKEN_EXPIRE_TIME = CacheConstants.EXPIRATION;
+    protected static final long TOKEN_EXPIRE_SECONDS = 30L * 24 * 60 * 60 * 1000;// 30天 单位:毫秒
+    //    private final static long TOKEN_EXPIRE_TIME = CacheConstants.EXPIRATION;
+    private final static long TOKEN_EXPIRE_TIME = CacheConstants.UNIAPPEXPIRATION;
 
     private final static String ACCESS_TOKEN = CacheConstants.LOGIN_TOKEN_KEY;
     private final static String UNIAPP_ACCESS_TOKEN = CacheConstants.UNIAPP_LOGIN_TOKEN_KEY;
 
-    private final static Long TOKEN_REFRESH_THRESHOLD_MINUTES = CacheConstants.REFRESH_TIME * MILLIS_MINUTE;
+    private final static Long TOKEN_REFRESH_THRESHOLD_MINUTES = CacheConstants.REFRESH_TIME * MILLIS_MINUTE;// 不足24小时刷新
 
     /**
      * 创建令牌
@@ -55,7 +56,12 @@ public class TokenService
         loginUser.setUserid(userId);
         loginUser.setUsername(userName);
         loginUser.setIpaddr(IpUtils.getIpAddr());
-        refreshToken(loginUser);
+        // 处理农小禹非手机号登录;
+        if (loginUser.getSysUser().getUserType().equals("11")){
+            refreshUniappToken(loginUser);
+        }else {
+            refreshToken(loginUser);
+        }
 
         // Jwt存储信息
         Map<String, Object> claimsMap = new HashMap<String, Object>();
@@ -94,6 +100,7 @@ public class TokenService
         Map<String, Object> rspMap = new HashMap<String, Object>();
         rspMap.put("access_token", JwtUtils.createToken(claimsMap));
         rspMap.put("expires_in", TOKEN_EXPIRE_TIME);
+        rspMap.put("userInfo", loginUser);// 返回用户信息 H5端使用
         return rspMap;
     }
 
@@ -201,9 +208,11 @@ public class TokenService
         long currentTime = System.currentTimeMillis();
         if (expireTime - currentTime <= TOKEN_REFRESH_THRESHOLD_MINUTES)
         {
-            refreshToken(loginUser);
+
             if (loginUser.getSysUser().getUserType().equals("11")){
                 refreshUniappToken(loginUser);
+            }else {
+                refreshToken(loginUser);
             }
         }
     }
@@ -216,10 +225,10 @@ public class TokenService
     public void refreshToken(LoginUser loginUser)
     {
         loginUser.setLoginTime(System.currentTimeMillis());
-        loginUser.setExpireTime(loginUser.getLoginTime() + TOKEN_EXPIRE_TIME * MILLIS_MINUTE);
+        loginUser.setExpireTime(loginUser.getLoginTime() + TOKEN_EXPIRE_SECONDS);
         // 根据uuid将loginUser缓存
         String userKey = getTokenKey(loginUser.getToken());
-        redisService.setCacheObject(userKey, loginUser, TOKEN_EXPIRE_TIME, TimeUnit.MINUTES);
+        redisService.setCacheObject(userKey, loginUser, TOKEN_EXPIRE_TIME, TimeUnit.DAYS);
     }
     /**
      * 刷新令牌有效期
@@ -229,10 +238,10 @@ public class TokenService
     public void refreshUniappToken(LoginUser loginUser)
     {
         loginUser.setLoginTime(System.currentTimeMillis());
-        loginUser.setExpireTime(loginUser.getLoginTime() + TOKEN_EXPIRE_TIME * MILLIS_MINUTE);
+        loginUser.setExpireTime(loginUser.getLoginTime() + TOKEN_EXPIRE_SECONDS);
         // 根据uuid将loginUser缓存
         String userKey = getUniappTokenKey(loginUser.getToken());
-        redisService.setCacheObject(userKey, loginUser, TOKEN_EXPIRE_TIME, TimeUnit.MINUTES);
+        redisService.setCacheObject(userKey, loginUser, TOKEN_EXPIRE_TIME, TimeUnit.DAYS);
     }
 
     private String getTokenKey(String token)
@@ -243,4 +252,4 @@ public class TokenService
     {
         return UNIAPP_ACCESS_TOKEN + token;
     }
-}
+}

+ 6 - 0
ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/ValidateCodeFilter.java

@@ -55,6 +55,12 @@ public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object>
                 return chain.filter(exchange); // H5 请求跳过验证码
             }
 
+            // ==== 判断是否为硬件设备请求 ====
+            String deviceType = request.getHeaders().getFirst("Device-Type");
+            if ("DEVICE".equalsIgnoreCase(deviceType)) {
+                return chain.filter(exchange); // 硬件设备请求跳过验证码
+            }
+
             try
             {
                 String rspStr = resolveBodyFromRequest(request);

+ 1 - 0
ruoyi-modules/pom.xml

@@ -16,6 +16,7 @@
         <module>ruoyi-uniapp</module>
         <module>ruoyi-qxsb</module>
         <module>ruoyi-base</module>
+        <module>ruoyi-mqtt</module>
     </modules>
 
     <artifactId>ruoyi-modules</artifactId>

+ 1 - 1
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/controller/AgriculturalMachinesController.java

@@ -65,7 +65,7 @@ public class AgriculturalMachinesController extends BaseController
     /**
      * 获取农机管理列表详细信息
      */
-    @RequiresPermissions("base:machines:query")
+    /*@RequiresPermissions("base:machines:query")*/
     @GetMapping(value = "/{id}")
     public AjaxResult getInfo(@PathVariable("id") Long id)
     {

+ 121 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/controller/AiGrapeDiseaseReportController.java

@@ -0,0 +1,121 @@
+package com.ruoyi.base.controller;
+
+import java.util.List;
+import java.io.IOException;
+import javax.servlet.http.HttpServletResponse;
+
+import com.ruoyi.base.domain.dto.VideoUploadDTO;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.system.api.RemoteFileService;
+import com.ruoyi.system.api.domain.SysFile;
+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.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.log.annotation.Log;
+import com.ruoyi.common.log.enums.BusinessType;
+import com.ruoyi.common.security.annotation.RequiresPermissions;
+import com.ruoyi.base.domain.AiGrapeDiseaseReport;
+import com.ruoyi.base.service.IAiGrapeDiseaseReportService;
+import com.ruoyi.common.core.web.controller.BaseController;
+import com.ruoyi.common.core.web.domain.AjaxResult;
+import com.ruoyi.common.core.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.web.page.TableDataInfo;
+
+/**
+ * AI农作物病害诊断报告Controller
+ *
+ * @author zmj
+ * @date 2026-01-12
+ */
+@RestController
+@RequestMapping("/GrapeDiseaseReport")
+public class AiGrapeDiseaseReportController extends BaseController
+{
+    @Autowired
+    private IAiGrapeDiseaseReportService aiGrapeDiseaseReportService;
+
+    @Autowired
+    private RemoteFileService remoteFileService;
+
+
+
+
+    /**
+     * 查询AI农作物病害诊断报告列表
+     */
+    @RequiresPermissions("base:GrapeDiseaseReport:list")
+    @GetMapping("/list")
+    public TableDataInfo list(AiGrapeDiseaseReport aiGrapeDiseaseReport)
+    {
+        startPage();
+        List<AiGrapeDiseaseReport> list = aiGrapeDiseaseReportService.selectAiGrapeDiseaseReportList(aiGrapeDiseaseReport);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出AI农作物病害诊断报告列表
+     */
+    @RequiresPermissions("base:GrapeDiseaseReport:export")
+    @Log(title = "AI农作物病害诊断报告", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, AiGrapeDiseaseReport aiGrapeDiseaseReport)
+    {
+        List<AiGrapeDiseaseReport> list = aiGrapeDiseaseReportService.selectAiGrapeDiseaseReportList(aiGrapeDiseaseReport);
+        ExcelUtil<AiGrapeDiseaseReport> util = new ExcelUtil<AiGrapeDiseaseReport>(AiGrapeDiseaseReport.class);
+        util.exportExcel(response, list, "AI农作物病害诊断报告数据");
+    }
+
+    /**
+     * 获取AI农作物病害诊断报告详细信息
+     */
+    @RequiresPermissions("base:GrapeDiseaseReport:query")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return success(aiGrapeDiseaseReportService.selectAiGrapeDiseaseReportById(id));
+    }
+
+    @GetMapping(value = "/getCount")
+    public AjaxResult getCount()
+    {
+        return success(aiGrapeDiseaseReportService.selectAiGrapeDiseaseReportByCount());
+    }
+
+    /**
+     * 新增AI农作物病害诊断报告
+     */
+    @Log(title = "AI农作物病害诊断报告", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody AiGrapeDiseaseReport aiGrapeDiseaseReport)
+    {
+        return toAjax(aiGrapeDiseaseReportService.insertAiGrapeDiseaseReport(aiGrapeDiseaseReport));
+    }
+
+    /**
+     * 修改AI农作物病害诊断报告
+     */
+    @RequiresPermissions("base:GrapeDiseaseReport:edit")
+    @Log(title = "AI农作物病害诊断报告", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody AiGrapeDiseaseReport aiGrapeDiseaseReport)
+    {
+        return toAjax(aiGrapeDiseaseReportService.updateAiGrapeDiseaseReport(aiGrapeDiseaseReport));
+    }
+
+    /**
+     * 删除AI农作物病害诊断报告
+     */
+    @RequiresPermissions("base:GrapeDiseaseReport:remove")
+    @Log(title = "AI农作物病害诊断报告", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(aiGrapeDiseaseReportService.deleteAiGrapeDiseaseReportByIds(ids));
+    }
+}

+ 105 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/controller/BatchController.java

@@ -0,0 +1,105 @@
+package com.ruoyi.base.controller;
+
+import java.util.List;
+import java.io.IOException;
+import javax.servlet.http.HttpServletResponse;
+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.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.log.annotation.Log;
+import com.ruoyi.common.log.enums.BusinessType;
+import com.ruoyi.common.security.annotation.RequiresPermissions;
+import com.ruoyi.base.domain.Batch;
+import com.ruoyi.base.service.IBatchService;
+import com.ruoyi.common.core.web.controller.BaseController;
+import com.ruoyi.common.core.web.domain.AjaxResult;
+import com.ruoyi.common.core.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.web.page.TableDataInfo;
+
+/**
+ * 批次管理Controller
+ *
+ * @author ruoyi
+ * @date 2026-03-31
+ */
+@RestController
+@RequestMapping("/batch")
+public class BatchController extends BaseController
+{
+    @Autowired
+    private IBatchService batchService;
+
+    /**
+     * 查询批次管理列表
+     */
+    @RequiresPermissions("base:batch:list")
+    @GetMapping("/list")
+    public TableDataInfo list(Batch batch)
+    {
+        startPage();
+        List<Batch> list = batchService.selectBatchList(batch);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出批次管理列表
+     */
+    @RequiresPermissions("base:batch:export")
+    @Log(title = "批次管理", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, Batch batch)
+    {
+        List<Batch> list = batchService.selectBatchList(batch);
+        ExcelUtil<Batch> util = new ExcelUtil<Batch>(Batch.class);
+        util.exportExcel(response, list, "批次管理数据");
+    }
+
+    /**
+     * 获取批次管理详细信息
+     */
+   // @RequiresPermissions("base:batch:query")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return success(batchService.selectBatchById(id));
+    }
+
+    /**
+     * 新增批次管理
+     */
+    @RequiresPermissions("base:batch:add")
+    @Log(title = "批次管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody Batch batch)
+    {
+        return toAjax(batchService.insertBatch(batch));
+    }
+
+    /**
+     * 修改批次管理
+     */
+    @RequiresPermissions("base:batch:edit")
+    @Log(title = "批次管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody Batch batch)
+    {
+        return toAjax(batchService.updateBatch(batch));
+    }
+
+    /**
+     * 删除批次管理
+     */
+    @RequiresPermissions("base:batch:remove")
+    @Log(title = "批次管理", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(batchService.deleteBatchByIds(ids));
+    }
+}

+ 105 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/controller/CertificateController.java

@@ -0,0 +1,105 @@
+package com.ruoyi.base.controller;
+
+import java.util.List;
+import java.io.IOException;
+import javax.servlet.http.HttpServletResponse;
+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.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.log.annotation.Log;
+import com.ruoyi.common.log.enums.BusinessType;
+import com.ruoyi.common.security.annotation.RequiresPermissions;
+import com.ruoyi.base.domain.Certificate;
+import com.ruoyi.base.service.ICertificateService;
+import com.ruoyi.common.core.web.controller.BaseController;
+import com.ruoyi.common.core.web.domain.AjaxResult;
+import com.ruoyi.common.core.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.web.page.TableDataInfo;
+
+/**
+ * 合格证Controller
+ * 
+ * @author ruoyi
+ * @date 2026-03-31
+ */
+@RestController
+@RequestMapping("/certificate")
+public class CertificateController extends BaseController
+{
+    @Autowired
+    private ICertificateService certificateService;
+
+    /**
+     * 查询合格证列表
+     */
+    @RequiresPermissions("base:certificate:list")
+    @GetMapping("/list")
+    public TableDataInfo list(Certificate certificate)
+    {
+        startPage();
+        List<Certificate> list = certificateService.selectCertificateList(certificate);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出合格证列表
+     */
+    @RequiresPermissions("base:certificate:export")
+    @Log(title = "合格证", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, Certificate certificate)
+    {
+        List<Certificate> list = certificateService.selectCertificateList(certificate);
+        ExcelUtil<Certificate> util = new ExcelUtil<Certificate>(Certificate.class);
+        util.exportExcel(response, list, "合格证数据");
+    }
+
+    /**
+     * 获取合格证详细信息
+     */
+    @RequiresPermissions("base:certificate:query")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return success(certificateService.selectCertificateById(id));
+    }
+
+    /**
+     * 新增合格证
+     */
+    @RequiresPermissions("base:certificate:add")
+    @Log(title = "合格证", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody Certificate certificate)
+    {
+        return toAjax(certificateService.insertCertificate(certificate));
+    }
+
+    /**
+     * 修改合格证
+     */
+    @RequiresPermissions("base:certificate:edit")
+    @Log(title = "合格证", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody Certificate certificate)
+    {
+        return toAjax(certificateService.updateCertificate(certificate));
+    }
+
+    /**
+     * 删除合格证
+     */
+    @RequiresPermissions("base:certificate:remove")
+    @Log(title = "合格证", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(certificateService.deleteCertificateByIds(ids));
+    }
+}

+ 11 - 1
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/controller/DeviceController.java

@@ -81,7 +81,17 @@ public class DeviceController extends BaseController
     @GetMapping(value = "/playback")
     public AjaxResult playback(Device device)
     {
-        return success(deviceService.videoPlayback(device));
+        try {
+            String result = deviceService.videoPlayback(device);
+            return success(result);
+        } catch (RuntimeException e) {
+            if ("VIDEO_CONNECTION_TIMEOUT".equals(e.getMessage())) {
+                return error("视频连接超时,请检查网络连接或服务器状态");
+            }
+            return error("播放失败:" + e.getMessage());
+        } catch (Exception e) {
+            return error("播放失败:" + e.getMessage());
+        }
     }
 
     /**

+ 1 - 1
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/controller/MallController.java

@@ -31,7 +31,7 @@ public class MallController extends BaseController
     /**
      * 查询农资商城列表
      */
-    @RequiresPermissions("base:mall:list")
+//    @RequiresPermissions("base:mall:list")
     @GetMapping("/list")
     public TableDataInfo list(Mall mall)
     {

+ 155 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/controller/ReportController.java

@@ -0,0 +1,155 @@
+package com.ruoyi.base.controller;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.io.IOException;
+import javax.servlet.http.HttpServletResponse;
+
+import com.ruoyi.base.domain.dto.ReportDTO;
+import com.ruoyi.common.core.utils.DateUtils;
+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.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.log.annotation.Log;
+import com.ruoyi.common.log.enums.BusinessType;
+import com.ruoyi.common.security.annotation.RequiresPermissions;
+import com.ruoyi.base.domain.Report;
+import com.ruoyi.base.service.IReportService;
+import com.ruoyi.common.core.web.controller.BaseController;
+import com.ruoyi.common.core.web.domain.AjaxResult;
+import com.ruoyi.common.core.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.web.page.TableDataInfo;
+
+/**
+ * 检测报告Controller
+ *
+ * @author ruoyi
+ * @date 2026-03-31
+ */
+@RestController
+@RequestMapping("/report")
+public class ReportController extends BaseController
+{
+    @Autowired
+    private IReportService reportService;
+
+    /**
+     * 查询检测报告列表
+     */
+    @RequiresPermissions("base:report:list")
+    @GetMapping("/list")
+    public TableDataInfo list(Report report)
+    {
+        startPage();
+        List<Report> list = reportService.selectReportList(report);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出检测报告列表
+     */
+    @RequiresPermissions("base:report:export")
+    @Log(title = "检测报告", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, Report report)
+    {
+        List<Report> list = reportService.selectReportList(report);
+        ExcelUtil<Report> util = new ExcelUtil<Report>(Report.class);
+        util.exportExcel(response, list, "检测报告数据");
+    }
+
+    /**
+     * 获取检测报告详细信息
+     */
+    @RequiresPermissions("base:report:query")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return success(reportService.selectReportById(id));
+    }
+
+    /**
+     * 新增检测报告
+     */
+    @RequiresPermissions("base:report:add")
+    @Log(title = "检测报告", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody Report report)
+    {
+        return toAjax(reportService.insertReport(report));
+    }
+
+    /**
+     * 修改检测报告
+     */
+    @RequiresPermissions("base:report:edit")
+    @Log(title = "检测报告", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody Report report)
+    {
+        return toAjax(reportService.updateReport(report));
+    }
+
+    /**
+     * 删除检测报告
+     */
+    @RequiresPermissions("base:report:remove")
+    @Log(title = "检测报告", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(reportService.deleteReportByIds(ids));
+    }
+
+
+    /**
+     * 批量新增检测报告
+     */
+    @Log(title = "批量新增检测报告", businessType = BusinessType.INSERT)
+    @PostMapping("/batchAdd")
+    public AjaxResult batchAdd(@RequestBody ReportDTO reportDTO)
+    {
+        if (reportDTO == null || reportDTO.getBatchId() == null) {
+            return error("批次 ID 不能为空");
+        }
+
+        if (reportDTO.getReportItems() == null || reportDTO.getReportItems().isEmpty()) {
+            return error("检测报告不能为空");
+        }
+
+        try {
+            List<Report> reports = new ArrayList<>();
+            for (ReportDTO.ReportItem item : reportDTO.getReportItems()) {
+                Report report = new Report();
+                report.setBatchId(reportDTO.getBatchId());
+                report.setReportNo(item.getReportNo());
+
+                if (item.getReportDate() != null && !item.getReportDate().isEmpty()) {
+                    report.setReportDate(DateUtils.parseDate(item.getReportDate()));
+                }
+
+                report.setReportFiles(item.getReportFiles());
+                report.setReportStatus("uploaded");
+                report.setCreatedAt(new Date());
+
+                reports.add(report);
+            }
+
+            int result = reportService.batchInsertReport(reports);
+            if (result > 0) {
+                return success("新增成功,共添加 " + reports.size() + " 条检测报告");
+            } else {
+                return error("新增失败");
+            }
+        } catch (Exception e) {
+            return error("新增失败:" + e.getMessage());
+        }
+    }
+}

+ 118 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/controller/SpeciesInfoController.java

@@ -0,0 +1,118 @@
+package com.ruoyi.base.controller;
+
+import java.util.List;
+import java.io.IOException;
+import javax.servlet.http.HttpServletResponse;
+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.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.log.annotation.Log;
+import com.ruoyi.common.log.enums.BusinessType;
+import com.ruoyi.common.security.annotation.RequiresPermissions;
+import com.ruoyi.base.domain.SpeciesInfo;
+import com.ruoyi.base.service.ISpeciesInfoService;
+import com.ruoyi.common.core.web.controller.BaseController;
+import com.ruoyi.common.core.web.domain.AjaxResult;
+import com.ruoyi.common.core.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.web.page.TableDataInfo;
+
+/**
+ * 物种基础信息Controller
+ *
+ * @author zmj
+ * @date 2026-03-23
+ */
+@RestController
+@RequestMapping("/speciesInfo")
+public class SpeciesInfoController extends BaseController
+{
+    @Autowired
+    private ISpeciesInfoService speciesInfoService;
+
+    /**
+     * 查询物种基础信息列表
+     */
+    @RequiresPermissions("base:speciesInfo:list")
+    @GetMapping("/list")
+    public TableDataInfo list(SpeciesInfo speciesInfo)
+    {
+        startPage();
+        List<SpeciesInfo> list = speciesInfoService.selectSpeciesInfoList(speciesInfo);
+        return getDataTable(list);
+    }
+
+
+
+    /**
+     * 统计物种基础信息数量
+     */
+    @Log(title = "物种基础信息统计", businessType = BusinessType.OTHER)
+    @PostMapping("/count")
+    public AjaxResult count(@RequestBody SpeciesInfo speciesInfo)
+    {
+        Long count = speciesInfoService.countSpeciesInfo(speciesInfo);
+        return success(count);
+    }
+
+    /**
+     * 导出物种基础信息列表
+     */
+    @RequiresPermissions("base:speciesInfo:export")
+    @Log(title = "物种基础信息", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, SpeciesInfo speciesInfo)
+    {
+        List<SpeciesInfo> list = speciesInfoService.selectSpeciesInfoList(speciesInfo);
+        ExcelUtil<SpeciesInfo> util = new ExcelUtil<SpeciesInfo>(SpeciesInfo.class);
+        util.exportExcel(response, list, "物种基础信息数据");
+    }
+
+    /**
+     * 获取物种基础信息详细信息
+     */
+    @RequiresPermissions("base:speciesInfo:query")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return success(speciesInfoService.selectSpeciesInfoById(id));
+    }
+
+    /**
+     * 新增物种基础信息
+     */
+    @RequiresPermissions("base:speciesInfo:add")
+    @Log(title = "物种基础信息", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody SpeciesInfo speciesInfo)
+    {
+        return toAjax(speciesInfoService.insertSpeciesInfo(speciesInfo));
+    }
+
+    /**
+     * 修改物种基础信息
+     */
+    @RequiresPermissions("base:speciesInfo:edit")
+    @Log(title = "物种基础信息", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody SpeciesInfo speciesInfo)
+    {
+        return toAjax(speciesInfoService.updateSpeciesInfo(speciesInfo));
+    }
+
+    /**
+     * 删除物种基础信息
+     */
+    @RequiresPermissions("base:speciesInfo:remove")
+    @Log(title = "物种基础信息", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(speciesInfoService.deleteSpeciesInfoByIds(ids));
+    }
+}

+ 105 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/controller/SpeciesTempRecordController.java

@@ -0,0 +1,105 @@
+package com.ruoyi.base.controller;
+
+import java.util.List;
+import java.io.IOException;
+import javax.servlet.http.HttpServletResponse;
+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.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.log.annotation.Log;
+import com.ruoyi.common.log.enums.BusinessType;
+import com.ruoyi.common.security.annotation.RequiresPermissions;
+import com.ruoyi.base.domain.SpeciesTempRecord;
+import com.ruoyi.base.service.ISpeciesTempRecordService;
+import com.ruoyi.common.core.web.controller.BaseController;
+import com.ruoyi.common.core.web.domain.AjaxResult;
+import com.ruoyi.common.core.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.web.page.TableDataInfo;
+
+/**
+ * 物种体温监测记录Controller
+ * 
+ * @author zmj
+ * @date 2026-03-23
+ */
+@RestController
+@RequestMapping("/recordTempRecord")
+public class SpeciesTempRecordController extends BaseController
+{
+    @Autowired
+    private ISpeciesTempRecordService speciesTempRecordService;
+
+    /**
+     * 查询物种体温监测记录列表
+     */
+    @RequiresPermissions("base:recordTempRecord:list")
+    @GetMapping("/list")
+    public TableDataInfo list(SpeciesTempRecord speciesTempRecord)
+    {
+        startPage();
+        List<SpeciesTempRecord> list = speciesTempRecordService.selectSpeciesTempRecordList(speciesTempRecord);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出物种体温监测记录列表
+     */
+    @RequiresPermissions("base:recordTempRecord:export")
+    @Log(title = "物种体温监测记录", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, SpeciesTempRecord speciesTempRecord)
+    {
+        List<SpeciesTempRecord> list = speciesTempRecordService.selectSpeciesTempRecordList(speciesTempRecord);
+        ExcelUtil<SpeciesTempRecord> util = new ExcelUtil<SpeciesTempRecord>(SpeciesTempRecord.class);
+        util.exportExcel(response, list, "物种体温监测记录数据");
+    }
+
+    /**
+     * 获取物种体温监测记录详细信息
+     */
+    @RequiresPermissions("base:recordTempRecord:query")
+    @GetMapping(value = "/{recordId}")
+    public AjaxResult getInfo(@PathVariable("recordId") String recordId)
+    {
+        return success(speciesTempRecordService.selectSpeciesTempRecordByRecordId(recordId));
+    }
+
+    /**
+     * 新增物种体温监测记录
+     */
+    @RequiresPermissions("base:recordTempRecord:add")
+    @Log(title = "物种体温监测记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody SpeciesTempRecord speciesTempRecord)
+    {
+        return toAjax(speciesTempRecordService.insertSpeciesTempRecord(speciesTempRecord));
+    }
+
+    /**
+     * 修改物种体温监测记录
+     */
+    @RequiresPermissions("base:recordTempRecord:edit")
+    @Log(title = "物种体温监测记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody SpeciesTempRecord speciesTempRecord)
+    {
+        return toAjax(speciesTempRecordService.updateSpeciesTempRecord(speciesTempRecord));
+    }
+
+    /**
+     * 删除物种体温监测记录
+     */
+    @RequiresPermissions("base:recordTempRecord:remove")
+    @Log(title = "物种体温监测记录", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{recordIds}")
+    public AjaxResult remove(@PathVariable String[] recordIds)
+    {
+        return toAjax(speciesTempRecordService.deleteSpeciesTempRecordByRecordIds(recordIds));
+    }
+}

+ 131 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/controller/VideoController.java

@@ -0,0 +1,131 @@
+package com.ruoyi.base.controller;
+
+import java.util.List;
+import java.io.IOException;
+import javax.servlet.http.HttpServletResponse;
+
+import com.ruoyi.base.domain.dto.VideoUploadDTO;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.system.api.RemoteFileService;
+import com.ruoyi.system.api.domain.SysFile;
+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.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.log.annotation.Log;
+import com.ruoyi.common.log.enums.BusinessType;
+import com.ruoyi.common.security.annotation.RequiresPermissions;
+import com.ruoyi.base.domain.Video;
+import com.ruoyi.base.service.IVideoService;
+import com.ruoyi.common.core.web.controller.BaseController;
+import com.ruoyi.common.core.web.domain.AjaxResult;
+import com.ruoyi.common.core.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.web.page.TableDataInfo;
+
+/**
+ * 物种监测视频Controller
+ *
+ * @author zmj
+ * @date 2026-03-23
+ */
+@RestController
+@RequestMapping("/video")
+public class VideoController extends BaseController
+{
+    @Autowired
+    private IVideoService videoService;
+
+    @Autowired
+    private RemoteFileService remoteFileService;
+
+    /**
+     * 上传视频文件及参数
+     */
+    @Log(title = "上传视频文件及参数", businessType = BusinessType.INSERT)
+    @PostMapping("/uploadVideo")
+    public AjaxResult uploadVideo(@RequestBody VideoUploadDTO dto)
+    {
+        try
+        {
+            return success(videoService.uploadVideo(dto));
+        }
+        catch (Exception e)
+        {
+            return error("上传失败:" + e.getMessage());
+        }
+    }
+
+
+    /**
+     * 查询物种监测视频列表
+     */
+    @RequiresPermissions("base:video:list")
+    @GetMapping("/list")
+    public TableDataInfo list(Video video)
+    {
+        startPage();
+        List<Video> list = videoService.selectVideoList(video);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出物种监测视频列表
+     */
+    @RequiresPermissions("base:video:export")
+    @Log(title = "物种监测视频", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, Video video)
+    {
+        List<Video> list = videoService.selectVideoList(video);
+        ExcelUtil<Video> util = new ExcelUtil<Video>(Video.class);
+        util.exportExcel(response, list, "物种监测视频数据");
+    }
+
+    /**
+     * 获取物种监测视频详细信息
+     */
+    @RequiresPermissions("base:video:query")
+    @GetMapping(value = "/{videoId}")
+    public AjaxResult getInfo(@PathVariable("videoId") String videoId)
+    {
+        return success(videoService.selectVideoByVideoId(videoId));
+    }
+
+    /**
+     * 新增物种监测视频
+     */
+    @RequiresPermissions("base:video:add")
+    @Log(title = "物种监测视频", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody Video video)
+    {
+        return toAjax(videoService.insertVideo(video));
+    }
+
+    /**
+     * 修改物种监测视频
+     */
+    @RequiresPermissions("base:video:edit")
+    @Log(title = "物种监测视频", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody Video video)
+    {
+        return toAjax(videoService.updateVideo(video));
+    }
+
+    /**
+     * 删除物种监测视频
+     */
+    @RequiresPermissions("base:video:remove")
+    @Log(title = "物种监测视频", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{videoIds}")
+    public AjaxResult remove(@PathVariable String[] videoIds)
+    {
+        return toAjax(videoService.deleteVideoByVideoIds(videoIds));
+    }
+}

+ 119 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/domain/AiGrapeDiseaseReport.java

@@ -0,0 +1,119 @@
+package com.ruoyi.base.domain;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.core.annotation.Excel;
+import com.ruoyi.common.core.web.domain.BaseEntity;
+
+/**
+ * AI农作物病害诊断报告对象 ai_grape_disease_report
+ *
+ * @author zmj
+ * @date 2026-01-12
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class AiGrapeDiseaseReport extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 主键(自增) */
+    private Long id;
+
+    /** 作物类型 */
+    @Excel(name = "作物类型")
+    private String cropType;
+
+    /** 设备编号 */
+    @Excel(name = "设备编号")
+    private String deviceCode;
+
+    /** 经度 */
+    @Excel(name = "经度")
+    private Double lng;
+
+    /** 纬度 */
+    @Excel(name = "纬度")
+    private Double lat;
+
+    /** 病害名称 */
+    @Excel(name = "病害名称")
+    private String diseaseName;
+
+    /** AI识别置信度 */
+    @Excel(name = "AI识别置信度")
+    private BigDecimal confidence;
+
+    /** 严重程度 */
+    @Excel(name = "严重程度")
+    private Long severityLevel;
+
+    /** 采集/识别时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "采集/识别时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date collectTime;
+
+    /** 所在地块/农场 */
+    @Excel(name = "所在地块/农场")
+    private String farmName;
+    private Long farmId;
+    private Long todayCount;
+    private Long pendingCount;
+
+    /** 病害图片访问URL */
+    @Excel(name = "病害图片访问URL")
+    private String imgUrl;
+
+    /** 处理状态 */
+    @Excel(name = "处理状态")
+    private Long handleStatus;
+
+    /** 紧急处理措施 */
+    @Excel(name = "紧急处理措施")
+    private String emergencyMeasure;
+
+    /** 田间管理建议 */
+    @Excel(name = "田间管理建议")
+    private String manageAdvice;
+
+    /** 预防方案 */
+    @Excel(name = "预防方案")
+    private String preventPlan;
+
+    /** 处理备注 */
+    @Excel(name = "处理备注")
+    private String handleNote;
+
+    /** 处理人 */
+    @Excel(name = "处理人")
+    private String handleUser;
+
+    /** 处理时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "处理时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date handleTime;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "创建时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date createdTime;
+
+    /** 更新时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "更新时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date updatedTime;
+
+    /** 所属机构id集合-查询条件 */
+    private List<String> deptIdList;
+
+
+}

+ 101 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/domain/Batch.java

@@ -0,0 +1,101 @@
+package com.ruoyi.base.domain;
+
+import java.util.Date;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.core.annotation.Excel;
+import com.ruoyi.common.core.web.domain.BaseEntity;
+
+/**
+ * 批次管理对象 batch
+ *
+ * @author ruoyi
+ * @date 2026-03-31
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class Batch extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 主键ID */
+    private Long id;
+
+    /** 批次号(唯一) */
+    @Excel(name = "批次号", readConverterExp = "唯=一")
+    private String batchNo;
+
+    /** 商品名称 */
+    @Excel(name = "商品名称")
+    private String productName;
+
+    /** 商品规格 */
+    @Excel(name = "商品规格")
+    private String productSpec;
+
+    /** 商品图片URL */
+    @Excel(name = "商品图片URL")
+    private String productImage;
+
+    /** 商品简介 */
+    @Excel(name = "商品简介")
+    private String productDesc;
+
+    /** 农场名称 */
+    @Excel(name = "农场名称")
+    private String farmName;
+
+    /** 农场所在地 */
+    @Excel(name = "农场所在地")
+    private String farmRegion;
+
+    /** 农场图片URL */
+    @Excel(name = "农场图片URL")
+    private String farmImage;
+
+    /** 农场简介 */
+    @Excel(name = "农场简介")
+    private String farmIntro;
+
+    /** 生产/采收日期 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "生产/采收日期", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date produceDate;
+
+    /** 包装日期 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "包装日期", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date packageDate;
+
+    /** 状态:draft/published/offline */
+    @Excel(name = "状态:draft/published/offline")
+    private String status;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "创建时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date createdAt;
+
+    /** 更新时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "更新时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date updatedAt;
+
+   private String produceDateStart;
+   private String produceDateEnd;
+   private String packageDateStart;
+   private String packageDateEnd;
+
+    /** 合格证信息(一个批次对应一个合格证) */
+    private Certificate certificate;
+
+    /** 检测报告列表(一个批次对应多个检测报告) */
+    private List<Report> reports;
+}

+ 131 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/domain/Certificate.java

@@ -0,0 +1,131 @@
+package com.ruoyi.base.domain;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.core.annotation.Excel;
+import com.ruoyi.common.core.web.domain.BaseEntity;
+
+/**
+ * 合格证对象 certificate
+ * 
+ * @author ruoyi
+ * @date 2026-03-31
+ */
+public class Certificate extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 主键ID */
+    private Long id;
+
+    /** 关联批次ID */
+    @Excel(name = "关联批次ID")
+    private Long batchId;
+
+    /** 状态:uploaded/pending */
+    @Excel(name = "状态:uploaded/pending")
+    private String certStatus;
+
+    /** 文件URL列表(JSON数组) */
+    @Excel(name = "文件URL列表", readConverterExp = "J=SON数组")
+    private String certFiles;
+
+    /** 合格证编号 */
+    @Excel(name = "合格证编号")
+    private String certNo;
+
+    /** 开具日期 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "开具日期", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date certIssueDate;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "创建时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date createdAt;
+
+    public void setId(Long id) 
+    {
+        this.id = id;
+    }
+
+    public Long getId() 
+    {
+        return id;
+    }
+
+    public void setBatchId(Long batchId) 
+    {
+        this.batchId = batchId;
+    }
+
+    public Long getBatchId() 
+    {
+        return batchId;
+    }
+
+    public void setCertStatus(String certStatus) 
+    {
+        this.certStatus = certStatus;
+    }
+
+    public String getCertStatus() 
+    {
+        return certStatus;
+    }
+
+    public void setCertFiles(String certFiles) 
+    {
+        this.certFiles = certFiles;
+    }
+
+    public String getCertFiles() 
+    {
+        return certFiles;
+    }
+
+    public void setCertNo(String certNo) 
+    {
+        this.certNo = certNo;
+    }
+
+    public String getCertNo() 
+    {
+        return certNo;
+    }
+
+    public void setCertIssueDate(Date certIssueDate) 
+    {
+        this.certIssueDate = certIssueDate;
+    }
+
+    public Date getCertIssueDate() 
+    {
+        return certIssueDate;
+    }
+
+    public void setCreatedAt(Date createdAt) 
+    {
+        this.createdAt = createdAt;
+    }
+
+    public Date getCreatedAt() 
+    {
+        return createdAt;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("id", getId())
+            .append("batchId", getBatchId())
+            .append("certStatus", getCertStatus())
+            .append("certFiles", getCertFiles())
+            .append("certNo", getCertNo())
+            .append("certIssueDate", getCertIssueDate())
+            .append("createdAt", getCreatedAt())
+            .toString();
+    }
+}

+ 54 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/domain/KnowledgeImages.java

@@ -0,0 +1,54 @@
+package com.ruoyi.base.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.ruoyi.common.core.annotation.Excel;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class KnowledgeImages implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /** ID */
+    private Integer id;
+
+    /** 文章ID */
+    @Excel(name = "文章ID")
+    @JsonProperty("articleId")
+    private Integer articleId;
+
+    /** 图片URL */
+    private String url;
+
+    /** 图片标题 */
+    private String title;
+
+    /** 图片副标题 */
+    private String subtitle;
+
+    /** 背景颜色代码,如"#4CAF50" */
+    private String color;
+
+    /** 排序 */
+    @JsonProperty("sortOrder")
+    private Integer sortOrder;
+
+    /** 图片类型: carousel-轮播图, content-内容图片 */
+    @Excel(name = "图片类型", readConverterExp = "carousel=轮播图,content=内容图片")
+    @JsonProperty("imageType")
+    private String imageType;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonProperty("createTime")
+    private Date createTime;
+
+
+}

+ 56 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/domain/Report.java

@@ -0,0 +1,56 @@
+package com.ruoyi.base.domain;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.core.annotation.Excel;
+import com.ruoyi.common.core.web.domain.BaseEntity;
+
+/**
+ * 检测报告对象 report
+ *
+ * @author ruoyi
+ * @date 2026-03-31
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class Report extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 主键ID */
+    private Long id;
+
+    /** 关联批次ID */
+    @Excel(name = "关联批次ID")
+    private Long batchId;
+
+    /** 状态:uploaded/pending */
+    @Excel(name = "状态:uploaded/pending")
+    private String reportStatus;
+
+    /** 文件URL列表(JSON数组) */
+    @Excel(name = "文件URL列表", readConverterExp = "J=SON数组")
+    private String reportFiles;
+
+    /** 报告编号 */
+    @Excel(name = "报告编号")
+    private String reportNo;
+
+    /** 检测日期 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "检测日期", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date reportDate;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "创建时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date createdAt;
+
+
+}

+ 91 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/domain/SpeciesInfo.java

@@ -0,0 +1,91 @@
+package com.ruoyi.base.domain;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.core.annotation.Excel;
+import com.ruoyi.common.core.web.domain.BaseEntity;
+
+/**
+ * 物种基础信息对象 species_info
+ *
+ * @author zmj
+ * @date 2026-03-23
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+
+public class SpeciesInfo extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 主键ID */
+    private Long id;
+
+    /** 物种编号,如NY-001 */
+    @Excel(name = "物种编号,如NY-001")
+    private String speciesId;
+
+    /** 品种,如荷斯坦奶牛 */
+    @Excel(name = "品种,如荷斯坦奶牛")
+    private String breed;
+
+    /** 物种类型 */
+    @Excel(name = "物种类型")
+    private Long speciesType;
+
+    /** 性别 */
+    @Excel(name = "性别")
+    private String sex;
+
+    /** 年龄(岁) */
+    @Excel(name = "年龄", readConverterExp = "岁=")
+    private Long age;
+
+    /** 体重(kg) */
+    @Excel(name = "体重", readConverterExp = "k=g")
+    private BigDecimal weight;
+
+    /** 所属农场 */
+    private Long farmId;
+    @Excel(name = "所属农场")
+    private String farmName;
+
+    /** 所属地块 */
+    private Long fieldId;
+    @Excel(name = "所属地块")
+    private String fieldName;
+
+    /** 最近监测温度(℃) */
+    @Excel(name = "最近监测温度", readConverterExp = "℃=")
+    private BigDecimal latestTemp;
+
+    /** 最近监测时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "最近监测时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date latestTempTime;
+
+    /** 健康状态 */
+    @Excel(name = "健康状态")
+    private Long healthStatus;
+
+    /** 所属机构id集合-查询条件 */
+    private List<String> deptIdList;
+
+    /** 所属机构id集合-查询条件 */
+    private List<String> fieldIdList;
+    /**
+     * 异常数量
+     */
+    private Long exceptionCount;
+
+
+}

+ 147 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/domain/SpeciesTempRecord.java

@@ -0,0 +1,147 @@
+package com.ruoyi.base.domain;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.core.annotation.Excel;
+import com.ruoyi.common.core.web.domain.BaseEntity;
+
+/**
+ * 物种体温监测记录对象 species_temp_record
+ * 
+ * @author zmj
+ * @date 2026-03-23
+ */
+public class SpeciesTempRecord extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 监测记录ID(主键),如TC-0001 */
+    private String recordId;
+
+    /** 物种主键id,关联species_info表 */
+    @Excel(name = "物种主键id,关联species_info表")
+    private Long speciesInfoId;
+
+    /** 物种 */
+    @Excel(name = "物种")
+    private Long speciesType;
+
+    /** 监测温度(℃) */
+    @Excel(name = "监测温度", readConverterExp = "℃=")
+    private BigDecimal tempValue;
+
+    /** 监测时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "监测时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date tempTime;
+
+    /** 监测摄像头编号 */
+    @Excel(name = "监测摄像头编号")
+    private String deviceId;
+
+    /** 本次监测健康状态 */
+    @Excel(name = "本次监测健康状态")
+    private Long healthStatus;
+
+    /** 关联视频ID,关联cattle_video表 */
+    @Excel(name = "关联视频ID,关联cattle_video表")
+    private String videoId;
+
+    public void setRecordId(String recordId) 
+    {
+        this.recordId = recordId;
+    }
+
+    public String getRecordId() 
+    {
+        return recordId;
+    }
+
+    public void setSpeciesInfoId(Long speciesInfoId) 
+    {
+        this.speciesInfoId = speciesInfoId;
+    }
+
+    public Long getSpeciesInfoId() 
+    {
+        return speciesInfoId;
+    }
+
+    public void setSpeciesType(Long speciesType) 
+    {
+        this.speciesType = speciesType;
+    }
+
+    public Long getSpeciesType() 
+    {
+        return speciesType;
+    }
+
+    public void setTempValue(BigDecimal tempValue) 
+    {
+        this.tempValue = tempValue;
+    }
+
+    public BigDecimal getTempValue() 
+    {
+        return tempValue;
+    }
+
+    public void setTempTime(Date tempTime) 
+    {
+        this.tempTime = tempTime;
+    }
+
+    public Date getTempTime() 
+    {
+        return tempTime;
+    }
+
+    public void setDeviceId(String deviceId) 
+    {
+        this.deviceId = deviceId;
+    }
+
+    public String getDeviceId() 
+    {
+        return deviceId;
+    }
+
+    public void setHealthStatus(Long healthStatus) 
+    {
+        this.healthStatus = healthStatus;
+    }
+
+    public Long getHealthStatus() 
+    {
+        return healthStatus;
+    }
+
+    public void setVideoId(String videoId) 
+    {
+        this.videoId = videoId;
+    }
+
+    public String getVideoId() 
+    {
+        return videoId;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("recordId", getRecordId())
+            .append("speciesInfoId", getSpeciesInfoId())
+            .append("speciesType", getSpeciesType())
+            .append("tempValue", getTempValue())
+            .append("tempTime", getTempTime())
+            .append("deviceId", getDeviceId())
+            .append("healthStatus", getHealthStatus())
+            .append("videoId", getVideoId())
+            .append("createTime", getCreateTime())
+            .toString();
+    }
+}

+ 58 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/domain/Video.java

@@ -0,0 +1,58 @@
+package com.ruoyi.base.domain;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.core.annotation.Excel;
+import com.ruoyi.common.core.web.domain.BaseEntity;
+
+/**
+ * 物种监测视频对象 video
+ *
+ * @author zmj
+ * @date 2026-03-23
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class Video extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 视频ID(主键),如VIDEO-001 */
+    private String videoId;
+
+    /** 拍摄摄像头编号 */
+    @Excel(name = "拍摄摄像头编号")
+    private String deviceId;
+
+    /** 监测物种:0牛,1羊,2猪 */
+    @Excel(name = "监测物种:0牛,1羊,2猪")
+    private Long speciesType;
+
+    /** 视频开始时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "视频开始时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date videoStartTime;
+
+    /** 视频结束时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "视频结束时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date videoEndTime;
+
+    /** 视频时长(秒) */
+    @Excel(name = "视频时长", readConverterExp = "秒=")
+    private Long videoDuration;
+
+    /** 视频存储路径(服务器路径/OSS地址) */
+    @Excel(name = "视频存储路径", readConverterExp = "服=务器路径/OSS地址")
+    private String videoPath;
+    /** 红外视频存储路径(服务器路径/OSS地址) */
+    private String irVideoPath;
+
+
+}

+ 43 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/domain/dto/ReportDTO.java

@@ -0,0 +1,43 @@
+package com.ruoyi.base.domain.dto;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 检测报告数据传输对象
+ */
+@Data
+public class ReportDTO {
+
+    /** 批次 ID */
+    private Long batchId;
+
+    /** 批次号 */
+    private String batchNo;
+
+    /** 商品名称 */
+    private String productName;
+
+    /** 农场名称 */
+    private String farmName;
+
+    /** 检测报告项列表 */
+    private List<ReportItem> reportItems;
+
+    /**
+     * 检测报告项
+     */
+    @Data
+    public static class ReportItem {
+
+        /** 报告编号 */
+        private String reportNo;
+
+        /** 检测日期 */
+        private String reportDate;
+
+        /** 报告文件 URL */
+        private String reportFiles;
+    }
+}

+ 45 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/domain/dto/VideoUploadDTO.java

@@ -0,0 +1,45 @@
+package com.ruoyi.base.domain.dto;
+
+
+import com.ruoyi.common.core.web.domain.BaseEntity;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.web.multipart.MultipartFile;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class VideoUploadDTO extends BaseEntity {
+
+
+
+    /** 物种编号(如 NY-001) */
+    private String speciesId;
+
+    /** 监测体温值 */
+    private Double tempValue;
+
+    /** 体温监测时间 */
+    private String tempTime;
+
+    /** 摄像头设备编号 */
+    private String deviceId;
+
+
+    /** 物种类型(0-牛,1-羊,2-猪) */
+    private Integer speciesType;
+
+    /** 视频开始录制时间 */
+    private String videoStartTime;
+
+    /** 视频结束录制时间 */
+    private String videoEndTime;
+
+    /** 视频时长(秒) */
+    private Integer videoDuration;
+    /** 视频存储路径 */
+    private String videoUrl;
+    /** 红外视频存储路径 */
+    private String irVideoURL;
+}

+ 63 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/mapper/AiGrapeDiseaseReportMapper.java

@@ -0,0 +1,63 @@
+package com.ruoyi.base.mapper;
+
+import java.util.List;
+import com.ruoyi.base.domain.AiGrapeDiseaseReport;
+
+/**
+ * AI农作物病害诊断报告Mapper接口
+ * 
+ * @author zmj
+ * @date 2026-01-12
+ */
+public interface AiGrapeDiseaseReportMapper 
+{
+    /**
+     * 查询AI农作物病害诊断报告
+     * 
+     * @param id AI农作物病害诊断报告主键
+     * @return AI农作物病害诊断报告
+     */
+    public AiGrapeDiseaseReport selectAiGrapeDiseaseReportById(Long id);
+
+    /**
+     * 查询AI农作物病害诊断报告列表
+     * 
+     * @param aiGrapeDiseaseReport AI农作物病害诊断报告
+     * @return AI农作物病害诊断报告集合
+     */
+    public List<AiGrapeDiseaseReport> selectAiGrapeDiseaseReportList(AiGrapeDiseaseReport aiGrapeDiseaseReport);
+
+    /**
+     * 新增AI农作物病害诊断报告
+     * 
+     * @param aiGrapeDiseaseReport AI农作物病害诊断报告
+     * @return 结果
+     */
+    public int insertAiGrapeDiseaseReport(AiGrapeDiseaseReport aiGrapeDiseaseReport);
+
+    /**
+     * 修改AI农作物病害诊断报告
+     * 
+     * @param aiGrapeDiseaseReport AI农作物病害诊断报告
+     * @return 结果
+     */
+    public int updateAiGrapeDiseaseReport(AiGrapeDiseaseReport aiGrapeDiseaseReport);
+
+    /**
+     * 删除AI农作物病害诊断报告
+     * 
+     * @param id AI农作物病害诊断报告主键
+     * @return 结果
+     */
+    public int deleteAiGrapeDiseaseReportById(Long id);
+
+    /**
+     * 批量删除AI农作物病害诊断报告
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteAiGrapeDiseaseReportByIds(Long[] ids);
+
+    AiGrapeDiseaseReport selectAiGrapeDiseaseReportByCount();
+}

+ 69 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/mapper/BatchMapper.java

@@ -0,0 +1,69 @@
+package com.ruoyi.base.mapper;
+
+import java.util.List;
+import com.ruoyi.base.domain.Batch;
+
+/**
+ * 批次管理Mapper接口
+ *
+ * @author ruoyi
+ * @date 2026-03-31
+ */
+public interface BatchMapper
+{
+    /**
+     * 查询批次管理
+     *
+     * @param id 批次管理主键
+     * @return 批次管理
+     */
+    public Batch selectBatchById(Long id);
+
+    /**
+     * 查询批次管理列表
+     *
+     * @param batch 批次管理
+     * @return 批次管理集合
+     */
+    public List<Batch> selectBatchList(Batch batch);
+
+    /**
+     * 通过批次号查询批次
+     *
+     * @param batchNo 批次号
+     * @return 批次管理
+     */
+    public Batch selectBatchByBatchNo(String batchNo);
+
+    /**
+     * 新增批次管理
+     *
+     * @param batch 批次管理
+     * @return 结果
+     */
+    public int insertBatch(Batch batch);
+
+    /**
+     * 修改批次管理
+     *
+     * @param batch 批次管理
+     * @return 结果
+     */
+    public int updateBatch(Batch batch);
+
+    /**
+     * 删除批次管理
+     *
+     * @param id 批次管理主键
+     * @return 结果
+     */
+    public int deleteBatchById(Long id);
+
+    /**
+     * 批量删除批次管理
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteBatchByIds(Long[] ids);
+}

+ 69 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/mapper/CertificateMapper.java

@@ -0,0 +1,69 @@
+package com.ruoyi.base.mapper;
+
+import java.util.List;
+import com.ruoyi.base.domain.Certificate;
+
+/**
+ * 合格证Mapper接口
+ *
+ * @author ruoyi
+ * @date 2026-03-31
+ */
+public interface CertificateMapper
+{
+    /**
+     * 查询合格证
+     *
+     * @param id 合格证主键
+     * @return 合格证
+     */
+    public Certificate selectCertificateById(Long id);
+
+    /**
+     * 查询合格证列表
+     *
+     * @param certificate 合格证
+     * @return 合格证集合
+     */
+    public List<Certificate> selectCertificateList(Certificate certificate);
+
+    /**
+     * 新增合格证
+     *
+     * @param certificate 合格证
+     * @return 结果
+     */
+    public int insertCertificate(Certificate certificate);
+
+    /**
+     * 修改合格证
+     *
+     * @param certificate 合格证
+     * @return 结果
+     */
+    public int updateCertificate(Certificate certificate);
+
+    /**
+     * 删除合格证
+     *
+     * @param id 合格证主键
+     * @return 结果
+     */
+    public int deleteCertificateById(Long id);
+
+    /**
+     * 批量删除合格证
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteCertificateByIds(Long[] ids);
+
+    /**
+     * 根据批次 ID 查询合格证
+     *
+     * @param batchId 批次 ID
+     * @return 合格证
+     */
+    public Certificate selectCertificateByBatchId(Long batchId);
+}

+ 10 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/mapper/KnowledgeContentMapper.java

@@ -2,6 +2,7 @@ package com.ruoyi.base.mapper;
 
 import java.util.List;
 import com.ruoyi.base.domain.KnowledgeContent;
+import com.ruoyi.base.domain.KnowledgeImages;
 
 /**
  * 农技知识管理Mapper接口
@@ -58,4 +59,13 @@ public interface KnowledgeContentMapper
      * @return 结果
      */
     public int deleteKnowledgeContentByIds(Long[] ids);
+
+
+    /**
+     * 新增知识图片
+     *
+     * @param knowledgeImage 知识图片
+     * @return 结果
+     */
+    public int insertKnowledgeImages(KnowledgeImages knowledgeImage);
 }

+ 78 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/mapper/ReportMapper.java

@@ -0,0 +1,78 @@
+package com.ruoyi.base.mapper;
+
+import java.util.List;
+import com.ruoyi.base.domain.Report;
+
+/**
+ * 检测报告Mapper接口
+ *
+ * @author ruoyi
+ * @date 2026-03-31
+ */
+public interface ReportMapper
+{
+    /**
+     * 查询检测报告
+     *
+     * @param id 检测报告主键
+     * @return 检测报告
+     */
+    public Report selectReportById(Long id);
+
+    /**
+     * 查询检测报告列表
+     *
+     * @param report 检测报告
+     * @return 检测报告集合
+     */
+    public List<Report> selectReportList(Report report);
+
+    /**
+     * 新增检测报告
+     *
+     * @param report 检测报告
+     * @return 结果
+     */
+    public int insertReport(Report report);
+
+    /**
+     * 修改检测报告
+     *
+     * @param report 检测报告
+     * @return 结果
+     */
+    public int updateReport(Report report);
+
+    /**
+     * 删除检测报告
+     *
+     * @param id 检测报告主键
+     * @return 结果
+     */
+    public int deleteReportById(Long id);
+
+    /**
+     * 批量删除检测报告
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteReportByIds(Long[] ids);
+
+
+    /**
+     * 根据批次 ID 查询检测报告列表
+     *
+     * @param batchId 批次 ID
+     * @return 检测报告集合
+     */
+    public List<Report> selectReportsByBatchId(Long batchId);
+
+    /**
+     * 批量新增检测报告
+     *
+     * @param reports 检测报告列表
+     * @return 结果
+     */
+    public int batchInsertReport(List<Report> reports);
+}

+ 70 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/mapper/SpeciesInfoMapper.java

@@ -0,0 +1,70 @@
+package com.ruoyi.base.mapper;
+
+import java.util.List;
+import com.ruoyi.base.domain.SpeciesInfo;
+
+/**
+ * 物种基础信息Mapper接口
+ *
+ * @author zmj
+ * @date 2026-03-23
+ */
+public interface SpeciesInfoMapper
+{
+    /**
+     * 查询物种基础信息
+     *
+     * @param id 物种基础信息主键
+     * @return 物种基础信息
+     */
+    public SpeciesInfo selectSpeciesInfoById(Long id);
+
+    /**
+     * 查询物种基础信息列表
+     *
+     * @param speciesInfo 物种基础信息
+     * @return 物种基础信息集合
+     */
+    public List<SpeciesInfo> selectSpeciesInfoList(SpeciesInfo speciesInfo);
+
+
+    /**
+     * 统计物种基础信息数量
+     *
+     * @param speciesInfo 物种基础信息
+     * @return 数量
+     */
+    public Long countSpeciesInfo(SpeciesInfo speciesInfo);
+
+    /**
+     * 新增物种基础信息
+     *
+     * @param speciesInfo 物种基础信息
+     * @return 结果
+     */
+    public int insertSpeciesInfo(SpeciesInfo speciesInfo);
+
+    /**
+     * 修改物种基础信息
+     *
+     * @param speciesInfo 物种基础信息
+     * @return 结果
+     */
+    public int updateSpeciesInfo(SpeciesInfo speciesInfo);
+
+    /**
+     * 删除物种基础信息
+     *
+     * @param id 物种基础信息主键
+     * @return 结果
+     */
+    public int deleteSpeciesInfoById(Long id);
+
+    /**
+     * 批量删除物种基础信息
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteSpeciesInfoByIds(Long[] ids);
+}

+ 61 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/mapper/SpeciesTempRecordMapper.java

@@ -0,0 +1,61 @@
+package com.ruoyi.base.mapper;
+
+import java.util.List;
+import com.ruoyi.base.domain.SpeciesTempRecord;
+
+/**
+ * 物种体温监测记录Mapper接口
+ * 
+ * @author zmj
+ * @date 2026-03-23
+ */
+public interface SpeciesTempRecordMapper 
+{
+    /**
+     * 查询物种体温监测记录
+     * 
+     * @param recordId 物种体温监测记录主键
+     * @return 物种体温监测记录
+     */
+    public SpeciesTempRecord selectSpeciesTempRecordByRecordId(String recordId);
+
+    /**
+     * 查询物种体温监测记录列表
+     * 
+     * @param speciesTempRecord 物种体温监测记录
+     * @return 物种体温监测记录集合
+     */
+    public List<SpeciesTempRecord> selectSpeciesTempRecordList(SpeciesTempRecord speciesTempRecord);
+
+    /**
+     * 新增物种体温监测记录
+     * 
+     * @param speciesTempRecord 物种体温监测记录
+     * @return 结果
+     */
+    public int insertSpeciesTempRecord(SpeciesTempRecord speciesTempRecord);
+
+    /**
+     * 修改物种体温监测记录
+     * 
+     * @param speciesTempRecord 物种体温监测记录
+     * @return 结果
+     */
+    public int updateSpeciesTempRecord(SpeciesTempRecord speciesTempRecord);
+
+    /**
+     * 删除物种体温监测记录
+     * 
+     * @param recordId 物种体温监测记录主键
+     * @return 结果
+     */
+    public int deleteSpeciesTempRecordByRecordId(String recordId);
+
+    /**
+     * 批量删除物种体温监测记录
+     * 
+     * @param recordIds 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteSpeciesTempRecordByRecordIds(String[] recordIds);
+}

+ 61 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/mapper/VideoMapper.java

@@ -0,0 +1,61 @@
+package com.ruoyi.base.mapper;
+
+import java.util.List;
+import com.ruoyi.base.domain.Video;
+
+/**
+ * 物种监测视频Mapper接口
+ * 
+ * @author zmj
+ * @date 2026-03-23
+ */
+public interface VideoMapper 
+{
+    /**
+     * 查询物种监测视频
+     * 
+     * @param videoId 物种监测视频主键
+     * @return 物种监测视频
+     */
+    public Video selectVideoByVideoId(String videoId);
+
+    /**
+     * 查询物种监测视频列表
+     * 
+     * @param video 物种监测视频
+     * @return 物种监测视频集合
+     */
+    public List<Video> selectVideoList(Video video);
+
+    /**
+     * 新增物种监测视频
+     * 
+     * @param video 物种监测视频
+     * @return 结果
+     */
+    public int insertVideo(Video video);
+
+    /**
+     * 修改物种监测视频
+     * 
+     * @param video 物种监测视频
+     * @return 结果
+     */
+    public int updateVideo(Video video);
+
+    /**
+     * 删除物种监测视频
+     * 
+     * @param videoId 物种监测视频主键
+     * @return 结果
+     */
+    public int deleteVideoByVideoId(String videoId);
+
+    /**
+     * 批量删除物种监测视频
+     * 
+     * @param videoIds 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteVideoByVideoIds(String[] videoIds);
+}

+ 62 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/IAiGrapeDiseaseReportService.java

@@ -0,0 +1,62 @@
+package com.ruoyi.base.service;
+
+import java.util.List;
+import com.ruoyi.base.domain.AiGrapeDiseaseReport;
+
+/**
+ * AI农作物病害诊断报告Service接口
+ *
+ * @author zmj
+ * @date 2026-01-12
+ */
+public interface IAiGrapeDiseaseReportService
+{
+    /**
+     * 查询AI农作物病害诊断报告
+     *
+     * @param id AI农作物病害诊断报告主键
+     * @return AI农作物病害诊断报告
+     */
+    public AiGrapeDiseaseReport selectAiGrapeDiseaseReportById(Long id);
+    public AiGrapeDiseaseReport selectAiGrapeDiseaseReportByCount();
+
+    /**
+     * 查询AI农作物病害诊断报告列表
+     *
+     * @param aiGrapeDiseaseReport AI农作物病害诊断报告
+     * @return AI农作物病害诊断报告集合
+     */
+    public List<AiGrapeDiseaseReport> selectAiGrapeDiseaseReportList(AiGrapeDiseaseReport aiGrapeDiseaseReport);
+
+    /**
+     * 新增AI农作物病害诊断报告
+     *
+     * @param aiGrapeDiseaseReport AI农作物病害诊断报告
+     * @return 结果
+     */
+    public int insertAiGrapeDiseaseReport(AiGrapeDiseaseReport aiGrapeDiseaseReport);
+
+    /**
+     * 修改AI农作物病害诊断报告
+     *
+     * @param aiGrapeDiseaseReport AI农作物病害诊断报告
+     * @return 结果
+     */
+    public int updateAiGrapeDiseaseReport(AiGrapeDiseaseReport aiGrapeDiseaseReport);
+
+    /**
+     * 批量删除AI农作物病害诊断报告
+     *
+     * @param ids 需要删除的AI农作物病害诊断报告主键集合
+     * @return 结果
+     */
+    public int deleteAiGrapeDiseaseReportByIds(Long[] ids);
+
+    /**
+     * 删除AI农作物病害诊断报告信息
+     *
+     * @param id AI农作物病害诊断报告主键
+     * @return 结果
+     */
+    public int deleteAiGrapeDiseaseReportById(Long id);
+}

+ 61 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/IBatchService.java

@@ -0,0 +1,61 @@
+package com.ruoyi.base.service;
+
+import java.util.List;
+import com.ruoyi.base.domain.Batch;
+
+/**
+ * 批次管理Service接口
+ * 
+ * @author ruoyi
+ * @date 2026-03-31
+ */
+public interface IBatchService 
+{
+    /**
+     * 查询批次管理
+     * 
+     * @param id 批次管理主键
+     * @return 批次管理
+     */
+    public Batch selectBatchById(Long id);
+
+    /**
+     * 查询批次管理列表
+     * 
+     * @param batch 批次管理
+     * @return 批次管理集合
+     */
+    public List<Batch> selectBatchList(Batch batch);
+
+    /**
+     * 新增批次管理
+     * 
+     * @param batch 批次管理
+     * @return 结果
+     */
+    public int insertBatch(Batch batch);
+
+    /**
+     * 修改批次管理
+     * 
+     * @param batch 批次管理
+     * @return 结果
+     */
+    public int updateBatch(Batch batch);
+
+    /**
+     * 批量删除批次管理
+     * 
+     * @param ids 需要删除的批次管理主键集合
+     * @return 结果
+     */
+    public int deleteBatchByIds(Long[] ids);
+
+    /**
+     * 删除批次管理信息
+     * 
+     * @param id 批次管理主键
+     * @return 结果
+     */
+    public int deleteBatchById(Long id);
+}

+ 61 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/ICertificateService.java

@@ -0,0 +1,61 @@
+package com.ruoyi.base.service;
+
+import java.util.List;
+import com.ruoyi.base.domain.Certificate;
+
+/**
+ * 合格证Service接口
+ * 
+ * @author ruoyi
+ * @date 2026-03-31
+ */
+public interface ICertificateService 
+{
+    /**
+     * 查询合格证
+     * 
+     * @param id 合格证主键
+     * @return 合格证
+     */
+    public Certificate selectCertificateById(Long id);
+
+    /**
+     * 查询合格证列表
+     * 
+     * @param certificate 合格证
+     * @return 合格证集合
+     */
+    public List<Certificate> selectCertificateList(Certificate certificate);
+
+    /**
+     * 新增合格证
+     * 
+     * @param certificate 合格证
+     * @return 结果
+     */
+    public int insertCertificate(Certificate certificate);
+
+    /**
+     * 修改合格证
+     * 
+     * @param certificate 合格证
+     * @return 结果
+     */
+    public int updateCertificate(Certificate certificate);
+
+    /**
+     * 批量删除合格证
+     * 
+     * @param ids 需要删除的合格证主键集合
+     * @return 结果
+     */
+    public int deleteCertificateByIds(Long[] ids);
+
+    /**
+     * 删除合格证信息
+     * 
+     * @param id 合格证主键
+     * @return 结果
+     */
+    public int deleteCertificateById(Long id);
+}

+ 69 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/IReportService.java

@@ -0,0 +1,69 @@
+package com.ruoyi.base.service;
+
+import java.util.List;
+import com.ruoyi.base.domain.Report;
+
+/**
+ * 检测报告Service接口
+ *
+ * @author ruoyi
+ * @date 2026-03-31
+ */
+public interface IReportService
+{
+    /**
+     * 查询检测报告
+     *
+     * @param id 检测报告主键
+     * @return 检测报告
+     */
+    public Report selectReportById(Long id);
+
+    /**
+     * 查询检测报告列表
+     *
+     * @param report 检测报告
+     * @return 检测报告集合
+     */
+    public List<Report> selectReportList(Report report);
+
+    /**
+     * 新增检测报告
+     *
+     * @param report 检测报告
+     * @return 结果
+     */
+    public int insertReport(Report report);
+
+    /**
+     * 修改检测报告
+     *
+     * @param report 检测报告
+     * @return 结果
+     */
+    public int updateReport(Report report);
+
+    /**
+     * 批量删除检测报告
+     *
+     * @param ids 需要删除的检测报告主键集合
+     * @return 结果
+     */
+    public int deleteReportByIds(Long[] ids);
+
+    /**
+     * 删除检测报告信息
+     *
+     * @param id 检测报告主键
+     * @return 结果
+     */
+    public int deleteReportById(Long id);
+
+    /**
+     * 批量新增检测报告
+     *
+     * @param reports 检测报告列表
+     * @return 结果
+     */
+    public int batchInsertReport(List<Report> reports);
+}

+ 74 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/ISpeciesInfoService.java

@@ -0,0 +1,74 @@
+package com.ruoyi.base.service;
+
+import java.util.List;
+
+import com.ruoyi.base.domain.AiGrapeDiseaseReport;
+import com.ruoyi.base.domain.SpeciesInfo;
+
+/**
+ * 物种基础信息Service接口
+ *
+ * @author zmj
+ * @date 2026-03-23
+ */
+public interface ISpeciesInfoService
+{
+    /**
+     * 查询物种基础信息
+     *
+     * @param id 物种基础信息主键
+     * @return 物种基础信息
+     */
+    public SpeciesInfo selectSpeciesInfoById(Long id);
+
+    /**
+     * 查询物种基础信息列表
+     *
+     * @param speciesInfo 物种基础信息
+     * @return 物种基础信息集合
+     */
+    public List<SpeciesInfo> selectSpeciesInfoList(SpeciesInfo speciesInfo);
+
+
+
+    /**
+     * 统计物种基础信息数量
+     *
+     * @param speciesInfo 物种基础信息
+     * @return 数量
+     */
+    public Long countSpeciesInfo(SpeciesInfo speciesInfo);
+
+
+    /**
+     * 新增物种基础信息
+     *
+     * @param speciesInfo 物种基础信息
+     * @return 结果
+     */
+    public int insertSpeciesInfo(SpeciesInfo speciesInfo);
+
+    /**
+     * 修改物种基础信息
+     *
+     * @param speciesInfo 物种基础信息
+     * @return 结果
+     */
+    public int updateSpeciesInfo(SpeciesInfo speciesInfo);
+
+    /**
+     * 批量删除物种基础信息
+     *
+     * @param ids 需要删除的物种基础信息主键集合
+     * @return 结果
+     */
+    public int deleteSpeciesInfoByIds(Long[] ids);
+
+    /**
+     * 删除物种基础信息信息
+     *
+     * @param id 物种基础信息主键
+     * @return 结果
+     */
+    public int deleteSpeciesInfoById(Long id);
+}

+ 61 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/ISpeciesTempRecordService.java

@@ -0,0 +1,61 @@
+package com.ruoyi.base.service;
+
+import java.util.List;
+import com.ruoyi.base.domain.SpeciesTempRecord;
+
+/**
+ * 物种体温监测记录Service接口
+ * 
+ * @author zmj
+ * @date 2026-03-23
+ */
+public interface ISpeciesTempRecordService 
+{
+    /**
+     * 查询物种体温监测记录
+     * 
+     * @param recordId 物种体温监测记录主键
+     * @return 物种体温监测记录
+     */
+    public SpeciesTempRecord selectSpeciesTempRecordByRecordId(String recordId);
+
+    /**
+     * 查询物种体温监测记录列表
+     * 
+     * @param speciesTempRecord 物种体温监测记录
+     * @return 物种体温监测记录集合
+     */
+    public List<SpeciesTempRecord> selectSpeciesTempRecordList(SpeciesTempRecord speciesTempRecord);
+
+    /**
+     * 新增物种体温监测记录
+     * 
+     * @param speciesTempRecord 物种体温监测记录
+     * @return 结果
+     */
+    public int insertSpeciesTempRecord(SpeciesTempRecord speciesTempRecord);
+
+    /**
+     * 修改物种体温监测记录
+     * 
+     * @param speciesTempRecord 物种体温监测记录
+     * @return 结果
+     */
+    public int updateSpeciesTempRecord(SpeciesTempRecord speciesTempRecord);
+
+    /**
+     * 批量删除物种体温监测记录
+     * 
+     * @param recordIds 需要删除的物种体温监测记录主键集合
+     * @return 结果
+     */
+    public int deleteSpeciesTempRecordByRecordIds(String[] recordIds);
+
+    /**
+     * 删除物种体温监测记录信息
+     * 
+     * @param recordId 物种体温监测记录主键
+     * @return 结果
+     */
+    public int deleteSpeciesTempRecordByRecordId(String recordId);
+}

+ 75 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/IVideoService.java

@@ -0,0 +1,75 @@
+package com.ruoyi.base.service;
+
+import java.util.List;
+import com.ruoyi.base.domain.Video;
+import com.ruoyi.base.domain.dto.VideoUploadDTO;
+import com.ruoyi.system.api.domain.SysFile;
+
+/**
+ * 物种监测视频Service接口
+ *
+ * @author zmj
+ * @date 2026-03-23
+ */
+public interface IVideoService
+{
+    /**
+     * 查询物种监测视频
+     *
+     * @param videoId 物种监测视频主键
+     * @return 物种监测视频
+     */
+    public Video selectVideoByVideoId(String videoId);
+
+
+
+    /**
+     * 上传视频文件及参数(包含事务处理)
+     *
+     * @param dto 视频上传 DTO
+     * @return 上传后的 SysFile 对象
+     * @throws Exception 上传过程中可能出现的异常
+     */
+    public int uploadVideo(VideoUploadDTO dto) throws Exception;
+
+
+    /**
+     * 查询物种监测视频列表
+     *
+     * @param video 物种监测视频
+     * @return 物种监测视频集合
+     */
+    public List<Video> selectVideoList(Video video);
+
+    /**
+     * 新增物种监测视频
+     *
+     * @param video 物种监测视频
+     * @return 结果
+     */
+    public int insertVideo(Video video);
+
+    /**
+     * 修改物种监测视频
+     *
+     * @param video 物种监测视频
+     * @return 结果
+     */
+    public int updateVideo(Video video);
+
+    /**
+     * 批量删除物种监测视频
+     *
+     * @param videoIds 需要删除的物种监测视频主键集合
+     * @return 结果
+     */
+    public int deleteVideoByVideoIds(String[] videoIds);
+
+    /**
+     * 删除物种监测视频信息
+     *
+     * @param videoId 物种监测视频主键
+     * @return 结果
+     */
+    public int deleteVideoByVideoId(String videoId);
+}

+ 98 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/impl/AiGrapeDiseaseReportServiceImpl.java

@@ -0,0 +1,98 @@
+package com.ruoyi.base.service.impl;
+
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.base.mapper.AiGrapeDiseaseReportMapper;
+import com.ruoyi.base.domain.AiGrapeDiseaseReport;
+import com.ruoyi.base.service.IAiGrapeDiseaseReportService;
+
+/**
+ * AI农作物病害诊断报告Service业务层处理
+ *
+ * @author zmj
+ * @date 2026-01-12
+ */
+@Service
+public class AiGrapeDiseaseReportServiceImpl implements IAiGrapeDiseaseReportService
+{
+    @Autowired
+    private AiGrapeDiseaseReportMapper aiGrapeDiseaseReportMapper;
+
+    /**
+     * 查询AI农作物病害诊断报告
+     *
+     * @param id AI农作物病害诊断报告主键
+     * @return AI农作物病害诊断报告
+     */
+    @Override
+    public AiGrapeDiseaseReport selectAiGrapeDiseaseReportById(Long id)
+    {
+        return aiGrapeDiseaseReportMapper.selectAiGrapeDiseaseReportById(id);
+    }
+
+    @Override
+    public AiGrapeDiseaseReport selectAiGrapeDiseaseReportByCount() {
+        return aiGrapeDiseaseReportMapper.selectAiGrapeDiseaseReportByCount();
+    }
+
+    /**
+     * 查询AI农作物病害诊断报告列表
+     *
+     * @param aiGrapeDiseaseReport AI农作物病害诊断报告
+     * @return AI农作物病害诊断报告
+     */
+    @Override
+    public List<AiGrapeDiseaseReport> selectAiGrapeDiseaseReportList(AiGrapeDiseaseReport aiGrapeDiseaseReport)
+    {
+        return aiGrapeDiseaseReportMapper.selectAiGrapeDiseaseReportList(aiGrapeDiseaseReport);
+    }
+
+    /**
+     * 新增AI农作物病害诊断报告
+     *
+     * @param aiGrapeDiseaseReport AI农作物病害诊断报告
+     * @return 结果
+     */
+    @Override
+    public int insertAiGrapeDiseaseReport(AiGrapeDiseaseReport aiGrapeDiseaseReport)
+    {
+        return aiGrapeDiseaseReportMapper.insertAiGrapeDiseaseReport(aiGrapeDiseaseReport);
+    }
+
+    /**
+     * 修改AI农作物病害诊断报告
+     *
+     * @param aiGrapeDiseaseReport AI农作物病害诊断报告
+     * @return 结果
+     */
+    @Override
+    public int updateAiGrapeDiseaseReport(AiGrapeDiseaseReport aiGrapeDiseaseReport)
+    {
+        return aiGrapeDiseaseReportMapper.updateAiGrapeDiseaseReport(aiGrapeDiseaseReport);
+    }
+
+    /**
+     * 批量删除AI农作物病害诊断报告
+     *
+     * @param ids 需要删除的AI农作物病害诊断报告主键
+     * @return 结果
+     */
+    @Override
+    public int deleteAiGrapeDiseaseReportByIds(Long[] ids)
+    {
+        return aiGrapeDiseaseReportMapper.deleteAiGrapeDiseaseReportByIds(ids);
+    }
+
+    /**
+     * 删除AI农作物病害诊断报告信息
+     *
+     * @param id AI农作物病害诊断报告主键
+     * @return 结果
+     */
+    @Override
+    public int deleteAiGrapeDiseaseReportById(Long id)
+    {
+        return aiGrapeDiseaseReportMapper.deleteAiGrapeDiseaseReportById(id);
+    }
+}

+ 104 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/impl/BatchServiceImpl.java

@@ -0,0 +1,104 @@
+package com.ruoyi.base.service.impl;
+
+import java.util.List;
+
+import com.ruoyi.common.core.exception.ServiceException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.base.mapper.BatchMapper;
+import com.ruoyi.base.domain.Batch;
+import com.ruoyi.base.service.IBatchService;
+
+/**
+ * 批次管理Service业务层处理
+ *
+ * @author ruoyi
+ * @date 2026-03-31
+ */
+@Service
+public class BatchServiceImpl implements IBatchService
+{
+    @Autowired
+    private BatchMapper batchMapper;
+
+    /**
+     * 查询批次管理
+     *
+     * @param id 批次管理主键
+     * @return 批次管理
+     */
+    @Override
+    public Batch selectBatchById(Long id)
+    {
+        return batchMapper.selectBatchById(id);
+    }
+
+    /**
+     * 查询批次管理列表
+     *
+     * @param batch 批次管理
+     * @return 批次管理
+     */
+    @Override
+    public List<Batch> selectBatchList(Batch batch)
+    {
+        return batchMapper.selectBatchList(batch);
+    }
+
+
+
+    /**
+     * 新增批次管理
+     *
+     * @param batch 批次管理
+     * @return 结果
+     */
+    @Override
+    public int insertBatch(Batch batch)
+    {
+        // 校验批次号是否已存在
+        if (batch.getBatchNo() != null && !batch.getBatchNo().isEmpty()) {
+            Batch existBatch = batchMapper.selectBatchByBatchNo(batch.getBatchNo());
+            if (existBatch != null) {
+                throw new ServiceException("批次号 " + batch.getBatchNo() + " 已存在,不能重复添加");
+            }
+        }
+        return batchMapper.insertBatch(batch);
+    }
+
+    /**
+     * 修改批次管理
+     *
+     * @param batch 批次管理
+     * @return 结果
+     */
+    @Override
+    public int updateBatch(Batch batch)
+    {
+        return batchMapper.updateBatch(batch);
+    }
+
+    /**
+     * 批量删除批次管理
+     *
+     * @param ids 需要删除的批次管理主键
+     * @return 结果
+     */
+    @Override
+    public int deleteBatchByIds(Long[] ids)
+    {
+        return batchMapper.deleteBatchByIds(ids);
+    }
+
+    /**
+     * 删除批次管理信息
+     *
+     * @param id 批次管理主键
+     * @return 结果
+     */
+    @Override
+    public int deleteBatchById(Long id)
+    {
+        return batchMapper.deleteBatchById(id);
+    }
+}

+ 93 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/impl/CertificateServiceImpl.java

@@ -0,0 +1,93 @@
+package com.ruoyi.base.service.impl;
+
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.base.mapper.CertificateMapper;
+import com.ruoyi.base.domain.Certificate;
+import com.ruoyi.base.service.ICertificateService;
+
+/**
+ * 合格证Service业务层处理
+ * 
+ * @author ruoyi
+ * @date 2026-03-31
+ */
+@Service
+public class CertificateServiceImpl implements ICertificateService 
+{
+    @Autowired
+    private CertificateMapper certificateMapper;
+
+    /**
+     * 查询合格证
+     * 
+     * @param id 合格证主键
+     * @return 合格证
+     */
+    @Override
+    public Certificate selectCertificateById(Long id)
+    {
+        return certificateMapper.selectCertificateById(id);
+    }
+
+    /**
+     * 查询合格证列表
+     * 
+     * @param certificate 合格证
+     * @return 合格证
+     */
+    @Override
+    public List<Certificate> selectCertificateList(Certificate certificate)
+    {
+        return certificateMapper.selectCertificateList(certificate);
+    }
+
+    /**
+     * 新增合格证
+     * 
+     * @param certificate 合格证
+     * @return 结果
+     */
+    @Override
+    public int insertCertificate(Certificate certificate)
+    {
+        return certificateMapper.insertCertificate(certificate);
+    }
+
+    /**
+     * 修改合格证
+     * 
+     * @param certificate 合格证
+     * @return 结果
+     */
+    @Override
+    public int updateCertificate(Certificate certificate)
+    {
+        return certificateMapper.updateCertificate(certificate);
+    }
+
+    /**
+     * 批量删除合格证
+     * 
+     * @param ids 需要删除的合格证主键
+     * @return 结果
+     */
+    @Override
+    public int deleteCertificateByIds(Long[] ids)
+    {
+        return certificateMapper.deleteCertificateByIds(ids);
+    }
+
+    /**
+     * 删除合格证信息
+     * 
+     * @param id 合格证主键
+     * @return 结果
+     */
+    @Override
+    public int deleteCertificateById(Long id)
+    {
+        return certificateMapper.deleteCertificateById(id);
+    }
+}

+ 3 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/impl/DeviceServiceImpl.java

@@ -180,6 +180,9 @@ public class DeviceServiceImpl implements IDeviceService
                     return wsFlv.toString();
                 }
             }
+        } catch (java.net.SocketTimeoutException e) {
+            log.error("视频连接超时,请检查网络连接或服务器状态", e);
+            throw new RuntimeException("VIDEO_CONNECTION_TIMEOUT", e);
         } catch (Exception e) {
             log.error("日志打印异常! 错误信息:" + e.getMessage(), e);
             return "";

+ 37 - 1
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/impl/KnowledgeContentServiceImpl.java

@@ -1,12 +1,18 @@
 package com.ruoyi.base.service.impl;
 
 import java.util.List;
+
+import com.ruoyi.base.domain.KnowledgeImages;
 import com.ruoyi.common.core.utils.DateUtils;
+import com.ruoyi.common.core.utils.HtmlImageExtractor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.ruoyi.base.mapper.KnowledgeContentMapper;
 import com.ruoyi.base.domain.KnowledgeContent;
 import com.ruoyi.base.service.IKnowledgeContentService;
+import org.springframework.transaction.annotation.Transactional;
 
 /**
  * 农技知识管理Service业务层处理
@@ -20,6 +26,10 @@ public class KnowledgeContentServiceImpl implements IKnowledgeContentService
     @Autowired
     private KnowledgeContentMapper knowledgeContentMapper;
 
+    private static final Logger log = LoggerFactory.getLogger(KnowledgeContentServiceImpl.class);
+
+
+
     /**
      * 查询农技知识管理
      *
@@ -51,10 +61,36 @@ public class KnowledgeContentServiceImpl implements IKnowledgeContentService
      * @return 结果
      */
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public int insertKnowledgeContent(KnowledgeContent knowledgeContent)
     {
         knowledgeContent.setCreateTime(DateUtils.getNowDate());
-        return knowledgeContentMapper.insertKnowledgeContent(knowledgeContent);
+        int insertCount = knowledgeContentMapper.insertKnowledgeContent(knowledgeContent);
+
+        if (insertCount > 0 && knowledgeContent.getId() != null)
+        {
+            KnowledgeImages knowledgeImage = new KnowledgeImages();
+            knowledgeImage.setArticleId(Math.toIntExact(knowledgeContent.getId()));
+            knowledgeImage.setUrl(knowledgeContent.getImageUrl());
+            knowledgeImage.setTitle(knowledgeContent.getTitle());
+            knowledgeImage.setSortOrder(1);
+            knowledgeImage.setImageType("carousel");
+
+            try
+            {
+                knowledgeContentMapper.insertKnowledgeImages(knowledgeImage);
+            }
+            catch (Exception e)
+            {
+                log.error("插入知识图片失败,articleId: {}", knowledgeContent.getId(), e);
+                throw new RuntimeException("插入知识图片失败", e);
+            }
+        }
+
+        return insertCount;
+
+
+
     }
 
     /**

+ 118 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/impl/ReportServiceImpl.java

@@ -0,0 +1,118 @@
+package com.ruoyi.base.service.impl;
+
+import java.util.Date;
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.base.mapper.ReportMapper;
+import com.ruoyi.base.domain.Report;
+import com.ruoyi.base.service.IReportService;
+
+/**
+ * 检测报告Service业务层处理
+ *
+ * @author ruoyi
+ * @date 2026-03-31
+ */
+@Service
+public class ReportServiceImpl implements IReportService
+{
+    @Autowired
+    private ReportMapper reportMapper;
+
+    /**
+     * 查询检测报告
+     *
+     * @param id 检测报告主键
+     * @return 检测报告
+     */
+    @Override
+    public Report selectReportById(Long id)
+    {
+        return reportMapper.selectReportById(id);
+    }
+
+    /**
+     * 查询检测报告列表
+     *
+     * @param report 检测报告
+     * @return 检测报告
+     */
+    @Override
+    public List<Report> selectReportList(Report report)
+    {
+        return reportMapper.selectReportList(report);
+    }
+
+    /**
+     * 新增检测报告
+     *
+     * @param report 检测报告
+     * @return 结果
+     */
+    @Override
+    public int insertReport(Report report)
+    {
+        return reportMapper.insertReport(report);
+    }
+
+    /**
+     * 修改检测报告
+     *
+     * @param report 检测报告
+     * @return 结果
+     */
+    @Override
+    public int updateReport(Report report)
+    {
+        return reportMapper.updateReport(report);
+    }
+
+    /**
+     * 批量删除检测报告
+     *
+     * @param ids 需要删除的检测报告主键
+     * @return 结果
+     */
+    @Override
+    public int deleteReportByIds(Long[] ids)
+    {
+        return reportMapper.deleteReportByIds(ids);
+    }
+
+    /**
+     * 删除检测报告信息
+     *
+     * @param id 检测报告主键
+     * @return 结果
+     */
+    @Override
+    public int deleteReportById(Long id)
+    {
+        return reportMapper.deleteReportById(id);
+    }
+
+    /**
+     * 批量新增检测报告
+     *
+     * @param reports 检测报告列表
+     * @return 结果
+     */
+    @Override
+    public int batchInsertReport(List<Report> reports)
+    {
+        if (reports != null && !reports.isEmpty()) {
+            Date now = new Date();
+            for (Report report : reports) {
+                if (report.getCreatedAt() == null) {
+                    report.setCreatedAt(now);
+                }
+                if (report.getReportStatus() == null || report.getReportStatus().isEmpty()) {
+                    report.setReportStatus("uploaded");
+                }
+            }
+            return reportMapper.batchInsertReport(reports);
+        }
+        return 0;
+    }
+}

+ 110 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/impl/SpeciesInfoServiceImpl.java

@@ -0,0 +1,110 @@
+package com.ruoyi.base.service.impl;
+
+import java.util.List;
+import com.ruoyi.common.core.utils.DateUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.base.mapper.SpeciesInfoMapper;
+import com.ruoyi.base.domain.SpeciesInfo;
+import com.ruoyi.base.service.ISpeciesInfoService;
+
+/**
+ * 物种基础信息Service业务层处理
+ *
+ * @author zmj
+ * @date 2026-03-23
+ */
+@Service
+public class SpeciesInfoServiceImpl implements ISpeciesInfoService
+{
+    @Autowired
+    private SpeciesInfoMapper speciesInfoMapper;
+
+    /**
+     * 查询物种基础信息
+     *
+     * @param id 物种基础信息主键
+     * @return 物种基础信息
+     */
+    @Override
+    public SpeciesInfo selectSpeciesInfoById(Long id)
+    {
+        return speciesInfoMapper.selectSpeciesInfoById(id);
+    }
+
+    /**
+     * 查询物种基础信息列表
+     *
+     * @param speciesInfo 物种基础信息
+     * @return 物种基础信息
+     */
+    @Override
+    public List<SpeciesInfo> selectSpeciesInfoList(SpeciesInfo speciesInfo)
+    {
+        return speciesInfoMapper.selectSpeciesInfoList(speciesInfo);
+    }
+
+    /**
+     * 统计物种基础信息数量
+     *
+     * @param speciesInfo 物种基础信息
+     * @return 数量
+     */
+    @Override
+    public Long countSpeciesInfo(SpeciesInfo speciesInfo)
+    {
+        return speciesInfoMapper.countSpeciesInfo(speciesInfo);
+    }
+
+
+
+    /**
+     * 新增物种基础信息
+     *
+     * @param speciesInfo 物种基础信息
+     * @return 结果
+     */
+    @Override
+    public int insertSpeciesInfo(SpeciesInfo speciesInfo)
+    {
+        speciesInfo.setCreateTime(DateUtils.getNowDate());
+        return speciesInfoMapper.insertSpeciesInfo(speciesInfo);
+    }
+
+    /**
+     * 修改物种基础信息
+     *
+     * @param speciesInfo 物种基础信息
+     * @return 结果
+     */
+    @Override
+    public int updateSpeciesInfo(SpeciesInfo speciesInfo)
+    {
+        speciesInfo.setUpdateTime(DateUtils.getNowDate());
+        return speciesInfoMapper.updateSpeciesInfo(speciesInfo);
+    }
+
+    /**
+     * 批量删除物种基础信息
+     *
+     * @param ids 需要删除的物种基础信息主键
+     * @return 结果
+     */
+    @Override
+    public int deleteSpeciesInfoByIds(Long[] ids)
+    {
+        return speciesInfoMapper.deleteSpeciesInfoByIds(ids);
+    }
+
+    /**
+     * 删除物种基础信息信息
+     *
+     * @param id 物种基础信息主键
+     * @return 结果
+     */
+    @Override
+    public int deleteSpeciesInfoById(Long id)
+    {
+        return speciesInfoMapper.deleteSpeciesInfoById(id);
+    }
+}

+ 95 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/impl/SpeciesTempRecordServiceImpl.java

@@ -0,0 +1,95 @@
+package com.ruoyi.base.service.impl;
+
+import java.util.List;
+import com.ruoyi.common.core.utils.DateUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.base.mapper.SpeciesTempRecordMapper;
+import com.ruoyi.base.domain.SpeciesTempRecord;
+import com.ruoyi.base.service.ISpeciesTempRecordService;
+
+/**
+ * 物种体温监测记录Service业务层处理
+ * 
+ * @author zmj
+ * @date 2026-03-23
+ */
+@Service
+public class SpeciesTempRecordServiceImpl implements ISpeciesTempRecordService 
+{
+    @Autowired
+    private SpeciesTempRecordMapper speciesTempRecordMapper;
+
+    /**
+     * 查询物种体温监测记录
+     * 
+     * @param recordId 物种体温监测记录主键
+     * @return 物种体温监测记录
+     */
+    @Override
+    public SpeciesTempRecord selectSpeciesTempRecordByRecordId(String recordId)
+    {
+        return speciesTempRecordMapper.selectSpeciesTempRecordByRecordId(recordId);
+    }
+
+    /**
+     * 查询物种体温监测记录列表
+     * 
+     * @param speciesTempRecord 物种体温监测记录
+     * @return 物种体温监测记录
+     */
+    @Override
+    public List<SpeciesTempRecord> selectSpeciesTempRecordList(SpeciesTempRecord speciesTempRecord)
+    {
+        return speciesTempRecordMapper.selectSpeciesTempRecordList(speciesTempRecord);
+    }
+
+    /**
+     * 新增物种体温监测记录
+     * 
+     * @param speciesTempRecord 物种体温监测记录
+     * @return 结果
+     */
+    @Override
+    public int insertSpeciesTempRecord(SpeciesTempRecord speciesTempRecord)
+    {
+        speciesTempRecord.setCreateTime(DateUtils.getNowDate());
+        return speciesTempRecordMapper.insertSpeciesTempRecord(speciesTempRecord);
+    }
+
+    /**
+     * 修改物种体温监测记录
+     * 
+     * @param speciesTempRecord 物种体温监测记录
+     * @return 结果
+     */
+    @Override
+    public int updateSpeciesTempRecord(SpeciesTempRecord speciesTempRecord)
+    {
+        return speciesTempRecordMapper.updateSpeciesTempRecord(speciesTempRecord);
+    }
+
+    /**
+     * 批量删除物种体温监测记录
+     * 
+     * @param recordIds 需要删除的物种体温监测记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteSpeciesTempRecordByRecordIds(String[] recordIds)
+    {
+        return speciesTempRecordMapper.deleteSpeciesTempRecordByRecordIds(recordIds);
+    }
+
+    /**
+     * 删除物种体温监测记录信息
+     * 
+     * @param recordId 物种体温监测记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteSpeciesTempRecordByRecordId(String recordId)
+    {
+        return speciesTempRecordMapper.deleteSpeciesTempRecordByRecordId(recordId);
+    }
+}

+ 211 - 0
ruoyi-modules/ruoyi-base/src/main/java/com/ruoyi/base/service/impl/VideoServiceImpl.java

@@ -0,0 +1,211 @@
+package com.ruoyi.base.service.impl;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import com.ruoyi.base.domain.SpeciesInfo;
+import com.ruoyi.base.domain.SpeciesTempRecord;
+import com.ruoyi.base.domain.dto.VideoUploadDTO;
+import com.ruoyi.base.service.ISpeciesInfoService;
+import com.ruoyi.base.service.ISpeciesTempRecordService;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.utils.DateUtils;
+import com.ruoyi.system.api.RemoteFileService;
+import com.ruoyi.system.api.domain.SysFile;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.base.mapper.VideoMapper;
+import com.ruoyi.base.domain.Video;
+import com.ruoyi.base.service.IVideoService;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * 物种监测视频Service业务层处理
+ *
+ * @author zmj
+ * @date 2026-03-23
+ */
+@Service
+public class VideoServiceImpl implements IVideoService
+{
+    @Autowired
+    private VideoMapper videoMapper;
+
+    @Autowired
+    private ISpeciesTempRecordService speciesTempRecordService;
+
+    @Autowired
+    private RemoteFileService remoteFileService;
+
+    @Autowired
+    private ISpeciesInfoService speciesInfoService;
+
+    /**
+     * 查询物种监测视频
+     *
+     * @param videoId 物种监测视频主键
+     * @return 物种监测视频
+     */
+    @Override
+    public Video selectVideoByVideoId(String videoId)
+    {
+        return videoMapper.selectVideoByVideoId(videoId);
+    }
+
+    /**
+     * 上传视频文件及参数(包含事务处理)
+     *
+     * @param dto 视频上传 DTO
+     * @return 上传后的 SysFile 对象
+     * @throws Exception 上传过程中可能出现的异常
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int uploadVideo(VideoUploadDTO dto) throws Exception
+    {
+        System.out.println("上传文件:" + dto);
+        if (dto.getVideoUrl() == null || dto.getVideoUrl().isEmpty()) {
+            throw new RuntimeException("视频路径不能为空");
+        }
+
+        // 0. 根据 speciesId 查询物种信息(如果提供了 speciesId)
+        SpeciesInfo speciesInfo = null;
+        Long speciesInfoId = null;
+        if (dto.getSpeciesId() != null && !dto.getSpeciesId().isEmpty()) {
+            SpeciesInfo queryParam = new SpeciesInfo();
+            queryParam.setSpeciesId(dto.getSpeciesId());
+            List<SpeciesInfo> speciesList = speciesInfoService.selectSpeciesInfoList(queryParam);
+            if (speciesList == null || speciesList.isEmpty()) {
+                throw new RuntimeException("未找到物种编号为 " + dto.getSpeciesId() + " 的物种信息");
+            }
+            speciesInfo = speciesList.get(0);
+            speciesInfoId = speciesInfo.getId();
+        }
+
+
+        // 2. 构建 Video 对象并保存到 video 表
+        Video video = new Video();
+        video.setDeviceId(dto.getDeviceId());
+        video.setSpeciesType(dto.getSpeciesType() != null ? Long.valueOf(dto.getSpeciesType()) : null);
+        video.setVideoStartTime(dto.getVideoStartTime() != null ?
+                DateUtils.parseDate(dto.getVideoStartTime()) : null);
+        video.setVideoEndTime(dto.getVideoEndTime() != null ?
+                DateUtils.parseDate(dto.getVideoEndTime()) : null);
+        video.setVideoDuration(dto.getVideoDuration() != null ? Long.valueOf(dto.getVideoDuration()) : null);
+        video.setVideoPath(dto.getVideoUrl());
+        video.setIrVideoPath(dto.getIrVideoURL());
+
+        int videoResult = insertVideo(video);
+        if (videoResult <= 0) {
+            throw new RuntimeException("保存视频信息失败");
+        }
+
+        // 3. 获取生成的视频 ID
+        String videoId = video.getVideoId();
+        if (videoId == null || videoId.isEmpty()) {
+            throw new RuntimeException("视频 ID 生成失败");
+        }
+
+        // 4. 计算健康状态
+        Long healthStatus = 1L; // 默认正常
+        if (dto.getTempValue() != null && dto.getTempValue() > 39) {
+            healthStatus = 2L; // 异常
+        }
+
+        // 5. 构建 SpeciesTempRecord 对象并保存到 species_temp_record 表
+        SpeciesTempRecord tempRecord = new SpeciesTempRecord();
+        tempRecord.setSpeciesInfoId(speciesInfoId);
+        tempRecord.setSpeciesType(dto.getSpeciesType() != null ? Long.valueOf(dto.getSpeciesType()) : null);
+        tempRecord.setTempValue(dto.getTempValue() != null ?
+                new BigDecimal(dto.getTempValue().toString()) : null);
+        tempRecord.setTempTime(dto.getTempTime() != null ?
+                DateUtils.parseDate(dto.getTempTime()) : null);
+        tempRecord.setDeviceId(dto.getDeviceId());
+        tempRecord.setHealthStatus(healthStatus);
+        tempRecord.setVideoId(videoId);
+
+        int tempResult = speciesTempRecordService.insertSpeciesTempRecord(tempRecord);
+        if (tempResult <= 0) {
+            throw new RuntimeException("保存体温监测记录失败");
+        }
+
+        int updateResult = 0;
+
+        // 6. 更新 species_info 表中的最新体温和时间(如果查询到了物种信息)
+        if (speciesInfo != null && dto.getTempValue() != null) {
+            speciesInfo.setLatestTemp(new BigDecimal(dto.getTempValue().toString()));
+            speciesInfo.setLatestTempTime(dto.getTempTime() != null ?
+                    DateUtils.parseDate(dto.getTempTime()) : DateUtils.getNowDate());
+            speciesInfo.setHealthStatus(healthStatus);
+
+             updateResult = speciesInfoService.updateSpeciesInfo(speciesInfo);
+            if (updateResult <= 0) {
+                throw new RuntimeException("更新物种信息失败");
+            }
+        }
+
+        // 7. 返回成功结果
+        return updateResult;
+    }
+
+    /**
+     * 查询物种监测视频列表
+     *
+     * @param video 物种监测视频
+     * @return 物种监测视频
+     */
+    @Override
+    public List<Video> selectVideoList(Video video)
+    {
+        return videoMapper.selectVideoList(video);
+    }
+
+    /**
+     * 新增物种监测视频
+     *
+     * @param video 物种监测视频
+     * @return 结果
+     */
+    @Override
+    public int insertVideo(Video video)
+    {
+        video.setCreateTime(DateUtils.getNowDate());
+        return videoMapper.insertVideo(video);
+    }
+
+    /**
+     * 修改物种监测视频
+     *
+     * @param video 物种监测视频
+     * @return 结果
+     */
+    @Override
+    public int updateVideo(Video video)
+    {
+        return videoMapper.updateVideo(video);
+    }
+
+    /**
+     * 批量删除物种监测视频
+     *
+     * @param videoIds 需要删除的物种监测视频主键
+     * @return 结果
+     */
+    @Override
+    public int deleteVideoByVideoIds(String[] videoIds)
+    {
+        return videoMapper.deleteVideoByVideoIds(videoIds);
+    }
+
+    /**
+     * 删除物种监测视频信息
+     *
+     * @param videoId 物种监测视频主键
+     * @return 结果
+     */
+    @Override
+    public int deleteVideoByVideoId(String videoId)
+    {
+        return videoMapper.deleteVideoByVideoId(videoId);
+    }
+}

+ 1 - 1
ruoyi-modules/ruoyi-base/src/main/resources/bootstrap.yml

@@ -20,7 +20,6 @@ spring:
         server-addr: 121.4.16.100:8848
         username: nacos
         password: nacos369
-        namespace: dev
       config:
         username: nacos
         password: nacos369
@@ -31,3 +30,4 @@ spring:
         # 共享配置
         shared-configs:
           - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
+

+ 2 - 2
ruoyi-modules/ruoyi-base/src/main/resources/mapper/base/AgriculturalMachinesMapper.xml

@@ -95,7 +95,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="onlineStatus != null">online_status,</if>
             <if test="currentTask != null">current_task,</if>
             <if test="currentField != null">current_field,</if>
-            <if test="maintenanceStatus != null">maintenance_status,</if>
+            <if test="maintenanceStatus != null and maintenanceStatus != ''">maintenance_status,</if>
             <if test="locationStatus != null">location_status,</if>
             <if test="alarmCount != null">alarm_count,</if>
             <if test="description != null">description,</if>
@@ -122,7 +122,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="onlineStatus != null">#{onlineStatus},</if>
             <if test="currentTask != null">#{currentTask},</if>
             <if test="currentField != null">#{currentField},</if>
-            <if test="maintenanceStatus != null">#{maintenanceStatus},</if>
+            <if test="maintenanceStatus != null and maintenanceStatus != ''">#{maintenanceStatus},</if>
             <if test="locationStatus != null">#{locationStatus},</if>
             <if test="alarmCount != null">#{alarmCount},</if>
             <if test="description != null">#{description},</if>

+ 163 - 0
ruoyi-modules/ruoyi-base/src/main/resources/mapper/base/AiGrapeDiseaseReportMapper.xml

@@ -0,0 +1,163 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.base.mapper.AiGrapeDiseaseReportMapper">
+
+    <resultMap type="AiGrapeDiseaseReport" id="AiGrapeDiseaseReportResult">
+        <result property="id"    column="id"    />
+        <result property="cropType"    column="crop_type"    />
+        <result property="deviceCode"    column="device_code"    />
+        <result property="lng"    column="lng"    />
+        <result property="lat"    column="lat"    />
+        <result property="diseaseName"    column="disease_name"    />
+        <result property="confidence"    column="confidence"    />
+        <result property="severityLevel"    column="severity_level"    />
+        <result property="collectTime"    column="collect_time"    />
+        <result property="farmName"    column="farm_name"    />
+        <result property="farmId"    column="farm_id"    />
+        <result property="imgUrl"    column="img_url"    />
+        <result property="handleStatus"    column="handle_status"    />
+        <result property="emergencyMeasure"    column="emergency_measure"    />
+        <result property="manageAdvice"    column="manage_advice"    />
+        <result property="preventPlan"    column="prevent_plan"    />
+        <result property="handleNote"    column="handle_note"    />
+        <result property="handleUser"    column="handle_user"    />
+        <result property="handleTime"    column="handle_time"    />
+        <result property="createdTime"    column="CREATED_TIME"    />
+        <result property="updatedTime"    column="UPDATED_TIME"    />
+        <result property="todayCount"    column="today_count"    />
+        <result property="pendingCount"    column="pending_count"    />
+    </resultMap>
+
+    <sql id="selectAiGrapeDiseaseReportVo">
+        select id, crop_type, device_code, lng, lat, disease_name, confidence, severity_level, collect_time, farm_name, img_url, handle_status, emergency_measure, manage_advice, prevent_plan, handle_note, handle_user, handle_time, CREATED_TIME, UPDATED_TIME from ai_grape_disease_report
+    </sql>
+
+    <select id="selectAiGrapeDiseaseReportList" parameterType="AiGrapeDiseaseReport" resultMap="AiGrapeDiseaseReportResult">
+        select agdr.*, sd.dept_name as farm_name
+        from ai_grape_disease_report as agdr left join sys_dept sd on agdr.farm_id = sd.dept_id
+        <where>
+            <if test="cropType != null  and cropType != ''"> and crop_type like concat('%', #{cropType}, '%')</if>
+            <if test="deviceCode != null  and deviceCode != ''"> and device_code = #{deviceCode}</if>
+            <if test="lng != null "> and lng = #{lng}</if>
+            <if test="lat != null "> and lat = #{lat}</if>
+            <if test="diseaseName != null  and diseaseName != ''"> and disease_name like concat('%', #{diseaseName}, '%')</if>
+            <if test="confidence != null "> and confidence = #{confidence}</if>
+            <if test="severityLevel != null "> and severity_level = #{severityLevel}</if>
+            <if test="collectTime != null "> and collect_time = #{collectTime}</if>
+            <if test="farmName != null  and farmName != ''"> and farm_name like concat('%', #{farmName}, '%')</if>
+            <if test="imgUrl != null  and imgUrl != ''"> and img_url = #{imgUrl}</if>
+            <if test="handleStatus != null "> and handle_status = #{handleStatus}</if>
+            <if test="emergencyMeasure != null  and emergencyMeasure != ''"> and emergency_measure = #{emergencyMeasure}</if>
+            <if test="manageAdvice != null  and manageAdvice != ''"> and manage_advice = #{manageAdvice}</if>
+            <if test="preventPlan != null  and preventPlan != ''"> and prevent_plan = #{preventPlan}</if>
+            <if test="handleNote != null  and handleNote != ''"> and handle_note = #{handleNote}</if>
+            <if test="handleUser != null  and handleUser != ''"> and handle_user = #{handleUser}</if>
+            <if test="handleTime != null "> and handle_time = #{handleTime}</if>
+            <if test="createdTime != null "> and CREATED_TIME = #{createdTime}</if>
+            <if test="updatedTime != null "> and UPDATED_TIME = #{updatedTime}</if>
+            <if test="deptIdList != null  and deptIdList.size() != 0"> and agdr.farm_id in (<foreach collection="deptIdList" item="deptId" separator=",">#{deptId}</foreach>)</if>
+        </where>
+    </select>
+
+    <select id="selectAiGrapeDiseaseReportById" parameterType="Long" resultMap="AiGrapeDiseaseReportResult">
+        select agdr.*, sd.dept_name as farm_name
+        from ai_grape_disease_report as agdr left join sys_dept sd on agdr.farm_id = sd.dept_id
+        where id = #{id}
+    </select>
+    <select id="selectAiGrapeDiseaseReportByCount" resultMap="AiGrapeDiseaseReportResult">
+        SELECT
+            (SELECT COUNT(*)
+             FROM ai_grape_disease_report
+             WHERE DATE(collect_time) = CURDATE()) AS today_count,
+            (SELECT COUNT(*)
+        FROM ai_grape_disease_report
+        WHERE handle_status = 0) AS pending_count;
+
+
+    </select>
+
+    <insert id="insertAiGrapeDiseaseReport" parameterType="AiGrapeDiseaseReport" useGeneratedKeys="true" keyProperty="id">
+        insert into ai_grape_disease_report
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="cropType != null and cropType != ''">crop_type,</if>
+            <if test="deviceCode != null and deviceCode != ''">device_code,</if>
+            <if test="lng != null">lng,</if>
+            <if test="lat != null">lat,</if>
+            <if test="diseaseName != null and diseaseName != ''">disease_name,</if>
+            <if test="confidence != null">confidence,</if>
+            <if test="severityLevel != null">severity_level,</if>
+            <if test="collectTime != null">collect_time,</if>
+            <if test="farmName != null">farm_name,</if>
+            <if test="imgUrl != null and imgUrl != ''">img_url,</if>
+            <if test="handleStatus != null">handle_status,</if>
+            <if test="emergencyMeasure != null and emergencyMeasure != ''">emergency_measure,</if>
+            <if test="manageAdvice != null and manageAdvice != ''">manage_advice,</if>
+            <if test="preventPlan != null and preventPlan != ''">prevent_plan,</if>
+            <if test="handleNote != null">handle_note,</if>
+            <if test="handleUser != null">handle_user,</if>
+            <if test="handleTime != null">handle_time,</if>
+            <if test="createdTime != null">CREATED_TIME,</if>
+            <if test="updatedTime != null">UPDATED_TIME,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="cropType != null and cropType != ''">#{cropType},</if>
+            <if test="deviceCode != null and deviceCode != ''">#{deviceCode},</if>
+            <if test="lng != null">#{lng},</if>
+            <if test="lat != null">#{lat},</if>
+            <if test="diseaseName != null and diseaseName != ''">#{diseaseName},</if>
+            <if test="confidence != null">#{confidence},</if>
+            <if test="severityLevel != null">#{severityLevel},</if>
+            <if test="collectTime != null">#{collectTime},</if>
+            <if test="farmName != null">#{farmName},</if>
+            <if test="imgUrl != null and imgUrl != ''">#{imgUrl},</if>
+            <if test="handleStatus != null">#{handleStatus},</if>
+            <if test="emergencyMeasure != null and emergencyMeasure != ''">#{emergencyMeasure},</if>
+            <if test="manageAdvice != null and manageAdvice != ''">#{manageAdvice},</if>
+            <if test="preventPlan != null and preventPlan != ''">#{preventPlan},</if>
+            <if test="handleNote != null">#{handleNote},</if>
+            <if test="handleUser != null">#{handleUser},</if>
+            <if test="handleTime != null">#{handleTime},</if>
+            <if test="createdTime != null">#{createdTime},</if>
+            <if test="updatedTime != null">#{updatedTime},</if>
+         </trim>
+    </insert>
+
+    <update id="updateAiGrapeDiseaseReport" parameterType="AiGrapeDiseaseReport">
+        update ai_grape_disease_report
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="cropType != null and cropType != ''">crop_type = #{cropType},</if>
+            <if test="deviceCode != null and deviceCode != ''">device_code = #{deviceCode},</if>
+            <if test="lng != null">lng = #{lng},</if>
+            <if test="lat != null">lat = #{lat},</if>
+            <if test="diseaseName != null and diseaseName != ''">disease_name = #{diseaseName},</if>
+            <if test="confidence != null">confidence = #{confidence},</if>
+            <if test="severityLevel != null">severity_level = #{severityLevel},</if>
+            <if test="collectTime != null">collect_time = #{collectTime},</if>
+            <if test="farmName != null">farm_name = #{farmName},</if>
+            <if test="imgUrl != null and imgUrl != ''">img_url = #{imgUrl},</if>
+            <if test="handleStatus != null">handle_status = #{handleStatus},</if>
+            <if test="emergencyMeasure != null and emergencyMeasure != ''">emergency_measure = #{emergencyMeasure},</if>
+            <if test="manageAdvice != null and manageAdvice != ''">manage_advice = #{manageAdvice},</if>
+            <if test="preventPlan != null and preventPlan != ''">prevent_plan = #{preventPlan},</if>
+            <if test="handleNote != null">handle_note = #{handleNote},</if>
+            <if test="handleUser != null">handle_user = #{handleUser},</if>
+            <if test="handleTime != null">handle_time = #{handleTime},</if>
+            <if test="createdTime != null">CREATED_TIME = #{createdTime},</if>
+            <if test="updatedTime != null">UPDATED_TIME = #{updatedTime},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteAiGrapeDiseaseReportById" parameterType="Long">
+        delete from ai_grape_disease_report where id = #{id}
+    </delete>
+
+    <delete id="deleteAiGrapeDiseaseReportByIds" parameterType="String">
+        delete from ai_grape_disease_report where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 133 - 0
ruoyi-modules/ruoyi-base/src/main/resources/mapper/base/BatchMapper.xml

@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.base.mapper.BatchMapper">
+
+    <resultMap type="Batch" id="BatchResult">
+        <result property="id"    column="id"    />
+        <result property="batchNo"    column="batch_no"    />
+        <result property="productName"    column="product_name"    />
+        <result property="productSpec"    column="product_spec"    />
+        <result property="productImage"    column="product_image"    />
+        <result property="productDesc"    column="product_desc"    />
+        <result property="farmName"    column="farm_name"    />
+        <result property="farmRegion"    column="farm_region"    />
+        <result property="farmImage"    column="farm_image"    />
+        <result property="farmIntro"    column="farm_intro"    />
+        <result property="produceDate"    column="produce_date"    />
+        <result property="packageDate"    column="package_date"    />
+        <result property="status"    column="status"    />
+        <result property="createdAt"    column="created_at"    />
+        <result property="updatedAt"    column="updated_at"    />
+        <association property="certificate" javaType="Certificate" select="com.ruoyi.base.mapper.CertificateMapper.selectCertificateByBatchId" column="id"/>
+        <collection property="reports" javaType="List" ofType="Report" select="com.ruoyi.base.mapper.ReportMapper.selectReportsByBatchId" column="id"/>
+
+    </resultMap>
+
+    <sql id="selectBatchVo">
+        select id, batch_no, product_name, product_spec, product_image, product_desc, farm_name, farm_region, farm_image, farm_intro, produce_date, package_date, status, created_at, updated_at from batch
+    </sql>
+
+    <select id="selectBatchList" parameterType="Batch" resultMap="BatchResult">
+        <include refid="selectBatchVo"/>
+        <where>
+            <if test="batchNo != null  and batchNo != ''"> and batch_no = #{batchNo}</if>
+            <if test="productName != null  and productName != ''"> and product_name like concat('%', #{productName}, '%')</if>
+            <if test="productSpec != null  and productSpec != ''"> and product_spec = #{productSpec}</if>
+            <if test="productImage != null  and productImage != ''"> and product_image = #{productImage}</if>
+            <if test="productDesc != null  and productDesc != ''"> and product_desc = #{productDesc}</if>
+            <if test="farmName != null  and farmName != ''"> and farm_name like concat('%', #{farmName}, '%')</if>
+            <if test="farmRegion != null  and farmRegion != ''"> and farm_region = #{farmRegion}</if>
+            <if test="farmImage != null  and farmImage != ''"> and farm_image = #{farmImage}</if>
+            <if test="farmIntro != null  and farmIntro != ''"> and farm_intro = #{farmIntro}</if>
+            <if test="produceDate != null "> and produce_date = #{produceDate}</if>
+            <if test="packageDate != null "> and package_date = #{packageDate}</if>
+            <if test="produceDateStart != null and produceDateStart != '' and produceDateEnd != null and produceDateEnd != ''">
+             and produce_date between #{produceDateStart} and #{produceDateEnd}</if>
+            <if test="packageDateStart != null and packageDateStart != '' and packageDateEnd != null and packageDateEnd != ''">
+             and package_date between #{packageDateStart} and #{packageDateEnd}</if>
+
+            <if test="status != null  and status != ''"> and status = #{status}</if>
+            <if test="createdAt != null "> and created_at = #{createdAt}</if>
+            <if test="updatedAt != null "> and updated_at = #{updatedAt}</if>
+        </where>
+    </select>
+
+    <select id="selectBatchById" parameterType="Long" resultMap="BatchResult">
+        <include refid="selectBatchVo"/>
+        where id = #{id}
+    </select>
+    <select id="selectBatchByBatchNo" parameterType="String" resultMap="BatchResult">
+        <include refid="selectBatchVo"/>
+        where batch_no = #{batchNo}
+    </select>
+
+    <insert id="insertBatch" parameterType="Batch" useGeneratedKeys="true" keyProperty="id">
+        insert into batch
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="batchNo != null and batchNo != ''">batch_no,</if>
+            <if test="productName != null and productName != ''">product_name,</if>
+            <if test="productSpec != null">product_spec,</if>
+            <if test="productImage != null">product_image,</if>
+            <if test="productDesc != null">product_desc,</if>
+            <if test="farmName != null and farmName != ''">farm_name,</if>
+            <if test="farmRegion != null and farmRegion != ''">farm_region,</if>
+            <if test="farmImage != null">farm_image,</if>
+            <if test="farmIntro != null">farm_intro,</if>
+            <if test="produceDate != null">produce_date,</if>
+            <if test="packageDate != null">package_date,</if>
+            <if test="status != null and status != ''">status,</if>
+            <if test="createdAt != null">created_at,</if>
+            <if test="updatedAt != null">updated_at,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="batchNo != null and batchNo != ''">#{batchNo},</if>
+            <if test="productName != null and productName != ''">#{productName},</if>
+            <if test="productSpec != null">#{productSpec},</if>
+            <if test="productImage != null">#{productImage},</if>
+            <if test="productDesc != null">#{productDesc},</if>
+            <if test="farmName != null and farmName != ''">#{farmName},</if>
+            <if test="farmRegion != null and farmRegion != ''">#{farmRegion},</if>
+            <if test="farmImage != null">#{farmImage},</if>
+            <if test="farmIntro != null">#{farmIntro},</if>
+            <if test="produceDate != null">#{produceDate},</if>
+            <if test="packageDate != null">#{packageDate},</if>
+            <if test="status != null and status != ''">#{status},</if>
+            <if test="createdAt != null">#{createdAt},</if>
+            <if test="updatedAt != null">#{updatedAt},</if>
+         </trim>
+    </insert>
+
+    <update id="updateBatch" parameterType="Batch">
+        update batch
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="batchNo != null and batchNo != ''">batch_no = #{batchNo},</if>
+            <if test="productName != null and productName != ''">product_name = #{productName},</if>
+            <if test="productSpec != null">product_spec = #{productSpec},</if>
+            <if test="productImage != null">product_image = #{productImage},</if>
+            <if test="productDesc != null">product_desc = #{productDesc},</if>
+            <if test="farmName != null and farmName != ''">farm_name = #{farmName},</if>
+            <if test="farmRegion != null and farmRegion != ''">farm_region = #{farmRegion},</if>
+            <if test="farmImage != null">farm_image = #{farmImage},</if>
+            <if test="farmIntro != null">farm_intro = #{farmIntro},</if>
+            <if test="produceDate != null">produce_date = #{produceDate},</if>
+            <if test="packageDate != null">package_date = #{packageDate},</if>
+            <if test="status != null and status != ''">status = #{status},</if>
+            <if test="createdAt != null">created_at = #{createdAt},</if>
+            <if test="updatedAt != null">updated_at = #{updatedAt},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteBatchById" parameterType="Long">
+        delete from batch where id = #{id}
+    </delete>
+
+    <delete id="deleteBatchByIds" parameterType="String">
+        delete from batch where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 86 - 0
ruoyi-modules/ruoyi-base/src/main/resources/mapper/base/CertificateMapper.xml

@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.base.mapper.CertificateMapper">
+
+    <resultMap type="Certificate" id="CertificateResult">
+        <result property="id"    column="id"    />
+        <result property="batchId"    column="batch_id"    />
+        <result property="certStatus"    column="cert_status"    />
+        <result property="certFiles"    column="cert_files"    />
+        <result property="certNo"    column="cert_no"    />
+        <result property="certIssueDate"    column="cert_issue_date"    />
+        <result property="createdAt"    column="created_at"    />
+    </resultMap>
+
+    <sql id="selectCertificateVo">
+        select id, batch_id, cert_status, cert_files, cert_no, cert_issue_date, created_at from certificate
+    </sql>
+
+    <select id="selectCertificateList" parameterType="Certificate" resultMap="CertificateResult">
+        <include refid="selectCertificateVo"/>
+        <where>
+            <if test="batchId != null "> and batch_id = #{batchId}</if>
+            <if test="certStatus != null  and certStatus != ''"> and cert_status = #{certStatus}</if>
+            <if test="certFiles != null  and certFiles != ''"> and cert_files = #{certFiles}</if>
+            <if test="certNo != null  and certNo != ''"> and cert_no = #{certNo}</if>
+            <if test="certIssueDate != null "> and cert_issue_date = #{certIssueDate}</if>
+            <if test="createdAt != null "> and created_at = #{createdAt}</if>
+        </where>
+    </select>
+
+    <select id="selectCertificateById" parameterType="Long" resultMap="CertificateResult">
+        <include refid="selectCertificateVo"/>
+        where id = #{id}
+    </select>
+    <select id="selectCertificateByBatchId" parameterType="Long" resultMap="CertificateResult">
+        <include refid="selectCertificateVo"/>
+        where batch_id = #{batchId}
+        limit 1
+    </select>
+
+    <insert id="insertCertificate" parameterType="Certificate" useGeneratedKeys="true" keyProperty="id">
+        insert into certificate
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="batchId != null">batch_id,</if>
+            <if test="certStatus != null and certStatus != ''">cert_status,</if>
+            <if test="certFiles != null">cert_files,</if>
+            <if test="certNo != null and certNo != ''">cert_no,</if>
+            <if test="certIssueDate != null">cert_issue_date,</if>
+            <if test="createdAt != null">created_at,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="batchId != null">#{batchId},</if>
+            <if test="certStatus != null and certStatus != ''">#{certStatus},</if>
+            <if test="certFiles != null">#{certFiles},</if>
+            <if test="certNo != null and certNo != ''">#{certNo},</if>
+            <if test="certIssueDate != null">#{certIssueDate},</if>
+            <if test="createdAt != null">#{createdAt},</if>
+         </trim>
+    </insert>
+
+    <update id="updateCertificate" parameterType="Certificate">
+        update certificate
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="batchId != null">batch_id = #{batchId},</if>
+            <if test="certStatus != null and certStatus != ''">cert_status = #{certStatus},</if>
+            <if test="certFiles != null">cert_files = #{certFiles},</if>
+            <if test="certNo != null and certNo != ''">cert_no = #{certNo},</if>
+            <if test="certIssueDate != null">cert_issue_date = #{certIssueDate},</if>
+            <if test="createdAt != null">created_at = #{createdAt},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteCertificateById" parameterType="Long">
+        delete from certificate where id = #{id}
+    </delete>
+
+    <delete id="deleteCertificateByIds" parameterType="String">
+        delete from certificate where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 21 - 0
ruoyi-modules/ruoyi-base/src/main/resources/mapper/base/KnowledgeContentMapper.xml

@@ -92,6 +92,27 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="status != null">#{status},</if>
          </trim>
     </insert>
+    <insert id="insertKnowledgeImages" parameterType="KnowledgeImages" useGeneratedKeys="true" keyProperty="id">
+        insert into knowledge_image (
+        <if test="articleId != null">article_id,</if>
+        <if test="url != null">url,</if>
+        <if test="title != null">title,</if>
+        <if test="subtitle != null">subtitle,</if>
+        <if test="color != null">color,</if>
+        <if test="sortOrder != null">sort_order,</if>
+        <if test="imageType != null">image_type,</if>
+        create_time
+        ) values (
+        <if test="articleId != null">#{articleId},</if>
+        <if test="url != null">#{url},</if>
+        <if test="title != null">#{title},</if>
+        <if test="subtitle != null">#{subtitle},</if>
+        <if test="color != null">#{color},</if>
+        <if test="sortOrder != null">#{sortOrder},</if>
+        <if test="imageType != null">#{imageType},</if>
+        sysdate()
+        )
+    </insert>
 
     <update id="updateKnowledgeContent" parameterType="KnowledgeContent">
         update knowledge_content

+ 93 - 0
ruoyi-modules/ruoyi-base/src/main/resources/mapper/base/ReportMapper.xml

@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.base.mapper.ReportMapper">
+
+    <resultMap type="Report" id="ReportResult">
+        <result property="id"    column="id"    />
+        <result property="batchId"    column="batch_id"    />
+        <result property="reportStatus"    column="report_status"    />
+        <result property="reportFiles"    column="report_files"    />
+        <result property="reportNo"    column="report_no"    />
+        <result property="reportDate"    column="report_date"    />
+        <result property="createdAt"    column="created_at"    />
+    </resultMap>
+
+    <sql id="selectReportVo">
+        select id, batch_id, report_status, report_files, report_no, report_date, created_at from report
+    </sql>
+
+    <select id="selectReportList" parameterType="Report" resultMap="ReportResult">
+        <include refid="selectReportVo"/>
+        <where>
+            <if test="batchId != null "> and batch_id = #{batchId}</if>
+            <if test="reportStatus != null  and reportStatus != ''"> and report_status = #{reportStatus}</if>
+            <if test="reportFiles != null  and reportFiles != ''"> and report_files = #{reportFiles}</if>
+            <if test="reportNo != null  and reportNo != ''"> and report_no = #{reportNo}</if>
+            <if test="reportDate != null "> and report_date = #{reportDate}</if>
+            <if test="createdAt != null "> and created_at = #{createdAt}</if>
+        </where>
+    </select>
+
+    <select id="selectReportById" parameterType="Long" resultMap="ReportResult">
+        <include refid="selectReportVo"/>
+        where id = #{id}
+    </select>
+    <select id="selectReportsByBatchId" parameterType="Long" resultMap="ReportResult">
+        <include refid="selectReportVo"/>
+        where batch_id = #{batchId}
+        order by created_at DESC
+    </select>
+
+    <insert id="insertReport" parameterType="Report" useGeneratedKeys="true" keyProperty="id">
+        insert into report
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="batchId != null">batch_id,</if>
+            <if test="reportStatus != null and reportStatus != ''">report_status,</if>
+            <if test="reportFiles != null">report_files,</if>
+            <if test="reportNo != null and reportNo != ''">report_no,</if>
+            <if test="reportDate != null">report_date,</if>
+            <if test="createdAt != null">created_at,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="batchId != null">#{batchId},</if>
+            <if test="reportStatus != null and reportStatus != ''">#{reportStatus},</if>
+            <if test="reportFiles != null">#{reportFiles},</if>
+            <if test="reportNo != null and reportNo != ''">#{reportNo},</if>
+            <if test="reportDate != null">#{reportDate},</if>
+            <if test="createdAt != null">#{createdAt},</if>
+         </trim>
+    </insert>
+    <insert id="batchInsertReport" parameterType="java.util.List">
+        insert into report (batch_id, report_status, report_files, report_no, report_date, created_at)
+        values
+        <foreach collection="list" item="item" separator=",">
+            (#{item.batchId}, #{item.reportStatus}, #{item.reportFiles}, #{item.reportNo}, #{item.reportDate}, #{item.createdAt})
+        </foreach>
+    </insert>
+
+    <update id="updateReport" parameterType="Report">
+        update report
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="batchId != null">batch_id = #{batchId},</if>
+            <if test="reportStatus != null and reportStatus != ''">report_status = #{reportStatus},</if>
+            <if test="reportFiles != null">report_files = #{reportFiles},</if>
+            <if test="reportNo != null and reportNo != ''">report_no = #{reportNo},</if>
+            <if test="reportDate != null">report_date = #{reportDate},</if>
+            <if test="createdAt != null">created_at = #{createdAt},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteReportById" parameterType="Long">
+        delete from report where id = #{id}
+    </delete>
+
+    <delete id="deleteReportByIds" parameterType="String">
+        delete from report where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 160 - 0
ruoyi-modules/ruoyi-base/src/main/resources/mapper/base/SpeciesInfoMapper.xml

@@ -0,0 +1,160 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.base.mapper.SpeciesInfoMapper">
+
+    <resultMap type="SpeciesInfo" id="SpeciesInfoResult">
+        <result property="id"    column="id"    />
+        <result property="speciesId"    column="species_id"    />
+        <result property="breed"    column="breed"    />
+        <result property="speciesType"    column="species_type"    />
+        <result property="sex"    column="sex"    />
+        <result property="age"    column="age"    />
+        <result property="weight"    column="weight"    />
+        <result property="farmId"    column="farm_id"    />
+        <result property="fieldId"    column="field_id"    />
+        <result property="latestTemp"    column="latest_temp"    />
+        <result property="latestTempTime"    column="latest_temp_time"    />
+        <result property="healthStatus"    column="health_status"    />
+        <result property="createBy"    column="create_by"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateBy"    column="update_by"    />
+        <result property="updateTime"    column="update_time"    />
+        <result property="fieldName"    column="field_name"    />
+        <result property="farmName"    column="farm_name"    />
+        <result property="remark"    column="remark"    />
+    </resultMap>
+
+    <sql id="selectSpeciesInfoVo">
+        select id, species_id, breed, species_type, sex, age, weight, farm_id, field_id, latest_temp, latest_temp_time, health_status, create_by, create_time, update_by, update_time, remark from species_info
+    </sql>
+    <select id="countSpeciesInfo" parameterType="SpeciesInfo" resultType="java.lang.Long">
+        select count(1)
+        from species_info si
+        <where>
+            <if test="speciesId != null  and speciesId != ''"> and species_id = #{speciesId}</if>
+            <if test="breed != null  and breed != ''"> and breed = #{breed}</if>
+            <if test="speciesType != null "> and species_type = #{speciesType}</if>
+            <if test="sex != null  and sex != ''"> and sex = #{sex}</if>
+            <if test="age != null "> and age = #{age}</if>
+            <if test="weight != null "> and weight = #{weight}</if>
+            <if test="healthStatus != null "> and health_status = #{healthStatus}</if>
+            <if test="deptIdList != null  and deptIdList.size() != 0"> and si.farm_id in (<foreach collection="deptIdList" item="deptId" separator=",">#{deptId}</foreach>)</if>
+            <if test="fieldIdList != null  and fieldIdList.size() != 0"> and si.field_id in (<foreach collection="fieldIdList" item="deptId" separator=",">#{deptId}</foreach>)</if>
+            <if test="params.latestTempMax != null and params.latestTempMax != '' and params.latestTempMin != null and params.latestTempMin != ''">
+                and latest_temp between #{params.latestTempMin} and #{params.latestTempMax}
+            </if>
+            <if test="params.latestTempTimeStart != null and params.latestTempTimeStart != '' and params.latestTempTimeEnd != null and params.latestTempTimeEnd != ''">
+                and latest_temp_time between #{params.latestTempTimeStart} and #{params.latestTempTimeEnd}
+            </if>
+        </where>
+    </select>
+
+    <select id="selectSpeciesInfoList" parameterType="SpeciesInfo" resultMap="SpeciesInfoResult">
+        select si.*, d.dept_name as farm_name,f.field_name
+        from species_info si
+        left join sys_dept d on si.farm_id = d.dept_id
+        left join field f on si.field_id = f.id
+        <where>
+            <if test="speciesId != null  and speciesId != ''"> and species_id = #{speciesId}</if>
+            <if test="breed != null  and breed != ''"> and breed = #{breed}</if>
+            <if test="speciesType != null "> and species_type = #{speciesType}</if>
+            <if test="sex != null  and sex != ''"> and sex = #{sex}</if>
+            <if test="age != null "> and age = #{age}</if>
+            <if test="weight != null "> and weight = #{weight}</if>
+            <if test="healthStatus != null "> and health_status = #{healthStatus}</if>
+            <if test="deptIdList != null  and deptIdList.size() != 0"> and si.farm_id in (<foreach collection="deptIdList" item="deptId" separator=",">#{deptId}</foreach>)</if>
+            <if test="fieldIdList != null  and fieldIdList.size() != 0"> and si.field_id in (<foreach collection="fieldIdList" item="deptId" separator=",">#{deptId}</foreach>)</if>
+            <if test="params.latestTempMax != null and params.latestTempMax != '' and params.latestTempMin != null and params.latestTempMin != ''">
+                and latest_temp between #{params.latestTempMin} and #{params.latestTempMax}
+            </if>
+            <if test="params.latestTempTimeStart != null and params.latestTempTimeStart != '' and params.latestTempTimeEnd != null and params.latestTempTimeEnd != ''">
+                and latest_temp_time between #{params.latestTempTimeStart} and #{params.latestTempTimeEnd}
+            </if>
+        </where>
+    </select>
+
+    <select id="selectSpeciesInfoById" parameterType="Long" resultMap="SpeciesInfoResult">
+        select id, species_id, breed, species_type, sex, age, weight, farm_id, field_id,
+               latest_temp, latest_temp_time, health_status, create_by, create_time, update_by, update_time, remark
+        from species_info
+
+        where id = #{id}
+    </select>
+
+
+    <insert id="insertSpeciesInfo" parameterType="SpeciesInfo" useGeneratedKeys="true" keyProperty="id">
+        insert into species_info
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="speciesId != null and speciesId != ''">species_id,</if>
+            <if test="breed != null and breed != ''">breed,</if>
+            <if test="speciesType != null">species_type,</if>
+            <if test="sex != null">sex,</if>
+            <if test="age != null">age,</if>
+            <if test="weight != null">weight,</if>
+            <if test="farmId != null and farmId != ''">farm_id,</if>
+            <if test="fieldId != null and fieldId != ''">field_id,</if>
+            <if test="latestTemp != null">latest_temp,</if>
+            <if test="latestTempTime != null">latest_temp_time,</if>
+            <if test="healthStatus != null">health_status,</if>
+            <if test="createBy != null">create_by,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateBy != null">update_by,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="remark != null">remark,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="speciesId != null and speciesId != ''">#{speciesId},</if>
+            <if test="breed != null and breed != ''">#{breed},</if>
+            <if test="speciesType != null">#{speciesType},</if>
+            <if test="sex != null">#{sex},</if>
+            <if test="age != null">#{age},</if>
+            <if test="weight != null">#{weight},</if>
+            <if test="farmId != null and farmId != ''">#{farmId},</if>
+            <if test="fieldId != null and fieldId != ''">#{fieldId},</if>
+            <if test="latestTemp != null">#{latestTemp},</if>
+            <if test="latestTempTime != null">#{latestTempTime},</if>
+            <if test="healthStatus != null">#{healthStatus},</if>
+            <if test="createBy != null">#{createBy},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateBy != null">#{updateBy},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="remark != null">#{remark},</if>
+         </trim>
+    </insert>
+
+    <update id="updateSpeciesInfo" parameterType="SpeciesInfo">
+        update species_info
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="speciesId != null and speciesId != ''">species_id = #{speciesId},</if>
+            <if test="breed != null and breed != ''">breed = #{breed},</if>
+            <if test="speciesType != null">species_type = #{speciesType},</if>
+            <if test="sex != null">sex = #{sex},</if>
+            <if test="age != null">age = #{age},</if>
+            <if test="weight != null">weight = #{weight},</if>
+            <if test="farmId != null and farmId != ''">farm_id = #{farmId},</if>
+            <if test="fieldId != null and fieldId != ''">field_id = #{fieldId},</if>
+            <if test="latestTemp != null">latest_temp = #{latestTemp},</if>
+            <if test="latestTempTime != null">latest_temp_time = #{latestTempTime},</if>
+            <if test="healthStatus != null">health_status = #{healthStatus},</if>
+            <if test="createBy != null">create_by = #{createBy},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateBy != null">update_by = #{updateBy},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="remark != null">remark = #{remark},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteSpeciesInfoById" parameterType="Long">
+        delete from species_info where id = #{id}
+    </delete>
+
+    <delete id="deleteSpeciesInfoByIds" parameterType="String">
+        delete from species_info where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 92 - 0
ruoyi-modules/ruoyi-base/src/main/resources/mapper/base/SpeciesTempRecordMapper.xml

@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.base.mapper.SpeciesTempRecordMapper">
+    
+    <resultMap type="SpeciesTempRecord" id="SpeciesTempRecordResult">
+        <result property="recordId"    column="record_id"    />
+        <result property="speciesInfoId"    column="species_info_id"    />
+        <result property="speciesType"    column="species_type"    />
+        <result property="tempValue"    column="temp_value"    />
+        <result property="tempTime"    column="temp_time"    />
+        <result property="deviceId"    column="device_id"    />
+        <result property="healthStatus"    column="health_status"    />
+        <result property="videoId"    column="video_id"    />
+        <result property="createTime"    column="create_time"    />
+    </resultMap>
+
+    <sql id="selectSpeciesTempRecordVo">
+        select record_id, species_info_id, species_type, temp_value, temp_time, device_id, health_status, video_id, create_time from species_temp_record
+    </sql>
+
+    <select id="selectSpeciesTempRecordList" parameterType="SpeciesTempRecord" resultMap="SpeciesTempRecordResult">
+        <include refid="selectSpeciesTempRecordVo"/>
+        <where>  
+            <if test="speciesInfoId != null "> and species_info_id = #{speciesInfoId}</if>
+            <if test="speciesType != null "> and species_type = #{speciesType}</if>
+            <if test="tempValue != null "> and temp_value = #{tempValue}</if>
+            <if test="tempTime != null "> and temp_time = #{tempTime}</if>
+            <if test="deviceId != null  and deviceId != ''"> and device_id = #{deviceId}</if>
+            <if test="healthStatus != null "> and health_status = #{healthStatus}</if>
+            <if test="videoId != null  and videoId != ''"> and video_id = #{videoId}</if>
+        </where>
+    </select>
+    
+    <select id="selectSpeciesTempRecordByRecordId" parameterType="String" resultMap="SpeciesTempRecordResult">
+        <include refid="selectSpeciesTempRecordVo"/>
+        where record_id = #{recordId}
+    </select>
+
+    <insert id="insertSpeciesTempRecord" parameterType="SpeciesTempRecord">
+        insert into species_temp_record
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="recordId != null">record_id,</if>
+            <if test="speciesInfoId != null">species_info_id,</if>
+            <if test="speciesType != null">species_type,</if>
+            <if test="tempValue != null">temp_value,</if>
+            <if test="tempTime != null">temp_time,</if>
+            <if test="deviceId != null">device_id,</if>
+            <if test="healthStatus != null">health_status,</if>
+            <if test="videoId != null">video_id,</if>
+            <if test="createTime != null">create_time,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="recordId != null">#{recordId},</if>
+            <if test="speciesInfoId != null">#{speciesInfoId},</if>
+            <if test="speciesType != null">#{speciesType},</if>
+            <if test="tempValue != null">#{tempValue},</if>
+            <if test="tempTime != null">#{tempTime},</if>
+            <if test="deviceId != null">#{deviceId},</if>
+            <if test="healthStatus != null">#{healthStatus},</if>
+            <if test="videoId != null">#{videoId},</if>
+            <if test="createTime != null">#{createTime},</if>
+         </trim>
+    </insert>
+
+    <update id="updateSpeciesTempRecord" parameterType="SpeciesTempRecord">
+        update species_temp_record
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="speciesInfoId != null">species_info_id = #{speciesInfoId},</if>
+            <if test="speciesType != null">species_type = #{speciesType},</if>
+            <if test="tempValue != null">temp_value = #{tempValue},</if>
+            <if test="tempTime != null">temp_time = #{tempTime},</if>
+            <if test="deviceId != null">device_id = #{deviceId},</if>
+            <if test="healthStatus != null">health_status = #{healthStatus},</if>
+            <if test="videoId != null">video_id = #{videoId},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+        </trim>
+        where record_id = #{recordId}
+    </update>
+
+    <delete id="deleteSpeciesTempRecordByRecordId" parameterType="String">
+        delete from species_temp_record where record_id = #{recordId}
+    </delete>
+
+    <delete id="deleteSpeciesTempRecordByRecordIds" parameterType="String">
+        delete from species_temp_record where record_id in 
+        <foreach item="recordId" collection="array" open="(" separator="," close=")">
+            #{recordId}
+        </foreach>
+    </delete>
+</mapper>

+ 90 - 0
ruoyi-modules/ruoyi-base/src/main/resources/mapper/base/VideoMapper.xml

@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.base.mapper.VideoMapper">
+
+    <resultMap type="Video" id="VideoResult">
+        <result property="videoId"    column="video_id"    />
+        <result property="deviceId"    column="device_id"    />
+        <result property="speciesType"    column="species_type"    />
+        <result property="videoStartTime"    column="video_start_time"    />
+        <result property="videoEndTime"    column="video_end_time"    />
+        <result property="videoDuration"    column="video_duration"    />
+        <result property="videoPath"    column="video_path"    />
+        <result property="createTime"    column="create_time"    />
+    </resultMap>
+
+    <sql id="selectVideoVo">
+        select video_id, device_id, species_type, video_start_time, video_end_time, video_duration, video_path, create_time from video
+    </sql>
+
+    <select id="selectVideoList" parameterType="Video" resultMap="VideoResult">
+        <include refid="selectVideoVo"/>
+        <where>
+            <if test="deviceId != null  and deviceId != ''"> and device_id = #{deviceId}</if>
+            <if test="speciesType != null "> and species_type = #{speciesType}</if>
+            <if test="videoStartTime != null "> and video_start_time = #{videoStartTime}</if>
+            <if test="videoEndTime != null "> and video_end_time = #{videoEndTime}</if>
+            <if test="videoDuration != null "> and video_duration = #{videoDuration}</if>
+            <if test="videoPath != null  and videoPath != ''"> and video_path = #{videoPath}</if>
+        </where>
+    </select>
+
+    <select id="selectVideoByVideoId" parameterType="String" resultMap="VideoResult">
+        <include refid="selectVideoVo"/>
+        where video_id = #{videoId}
+    </select>
+
+    <insert id="insertVideo" parameterType="Video"
+            useGeneratedKeys="true" keyProperty="videoId" keyColumn="video_id">
+        insert into video
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="videoId != null">video_id,</if>
+            <if test="deviceId != null">device_id,</if>
+            <if test="speciesType != null">species_type,</if>
+            <if test="videoStartTime != null">video_start_time,</if>
+            <if test="videoEndTime != null">video_end_time,</if>
+            <if test="videoDuration != null">video_duration,</if>
+            <if test="videoPath != null and videoPath != ''">video_path,</if>
+            <if test="irVideoPath != null and irVideoPath != ''">ir_video_path,</if>
+            <if test="createTime != null">create_time,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="videoId != null">#{videoId},</if>
+            <if test="deviceId != null">#{deviceId},</if>
+            <if test="speciesType != null">#{speciesType},</if>
+            <if test="videoStartTime != null">#{videoStartTime},</if>
+            <if test="videoEndTime != null">#{videoEndTime},</if>
+            <if test="videoDuration != null">#{videoDuration},</if>
+            <if test="videoPath != null and videoPath != ''">#{videoPath},</if>
+            <if test="irVideoPath != null and irVideoPath != ''">#{irVideoPath},</if>
+            <if test="createTime != null">#{createTime},</if>
+         </trim>
+    </insert>
+
+    <update id="updateVideo" parameterType="Video">
+        update video
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="deviceId != null">device_id = #{deviceId},</if>
+            <if test="speciesType != null">species_type = #{speciesType},</if>
+            <if test="videoStartTime != null">video_start_time = #{videoStartTime},</if>
+            <if test="videoEndTime != null">video_end_time = #{videoEndTime},</if>
+            <if test="videoDuration != null">video_duration = #{videoDuration},</if>
+            <if test="videoPath != null and videoPath != ''">video_path = #{videoPath},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+        </trim>
+        where video_id = #{videoId}
+    </update>
+
+    <delete id="deleteVideoByVideoId" parameterType="String">
+        delete from video where video_id = #{videoId}
+    </delete>
+
+    <delete id="deleteVideoByVideoIds" parameterType="String">
+        delete from video where video_id in
+        <foreach item="videoId" collection="array" open="(" separator="," close=")">
+            #{videoId}
+        </foreach>
+    </delete>
+</mapper>

+ 3 - 3
ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/utils/FileUploadUtils.java

@@ -18,7 +18,7 @@ import com.ruoyi.common.core.utils.uuid.Seq;
 
 /**
  * 文件上传工具类
- * 
+ *
  * @author ruoyi
  */
 public class FileUploadUtils
@@ -26,7 +26,7 @@ public class FileUploadUtils
     /**
      * 默认大小 50M
      */
-    public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024L;
+    public static final long DEFAULT_MAX_SIZE = 200 * 1024 * 1024L;
 
     /**
      * 默认的文件名最大长度 100
@@ -182,4 +182,4 @@ public class FileUploadUtils
         }
         return false;
     }
-}
+}

+ 148 - 0
ruoyi-modules/ruoyi-mqtt/pom.xml

@@ -0,0 +1,148 @@
+<?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-modules</artifactId>
+        <groupId>com.ruoyi</groupId>
+        <version>3.6.5</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-mqtt</artifactId>
+
+    <description>
+        ruoyi-modules-mqtt服务模块
+    </description>
+
+    <dependencies>
+
+        <!-- SpringCloud Alibaba Nacos -->
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
+        </dependency>
+
+        <!-- SpringCloud Alibaba Nacos Config -->
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
+        </dependency>
+
+        <!-- SpringCloud Alibaba Sentinel -->
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
+        </dependency>
+
+        <!-- SpringBoot Actuator -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+
+        <!-- Mysql Connector -->
+        <dependency>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
+        </dependency>
+
+        <!-- RuoYi Common DataSource -->
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-common-datasource</artifactId>
+        </dependency>
+
+        <!-- RuoYi Common DataScope -->
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-common-datascope</artifactId>
+        </dependency>
+
+        <!-- RuoYi Common Log -->
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-common-log</artifactId>
+        </dependency>
+
+        <!-- RuoYi Common Swagger -->
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-common-swagger</artifactId>
+        </dependency>
+
+        <!-- JWT -->
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+            <version>0.9.1</version>
+        </dependency>
+
+        <!--Lombok-->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+        <!-- Hutool工具包 -->
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>${hutool.version}</version>
+        </dependency>
+
+        <!-- MQTT 客户端核心依赖 -->
+        <dependency>
+            <groupId>org.eclipse.paho</groupId>
+            <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
+            <version>1.2.5</version>
+        </dependency>
+        <!-- Spring 整合 MQTT(简化配置) -->
+        <dependency>
+            <groupId>org.springframework.integration</groupId>
+            <artifactId>spring-integration-mqtt</artifactId>
+            <version>5.5.15</version>
+        </dependency>
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-api-mqtt</artifactId>
+            <version>3.6.5</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-test</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-api-base</artifactId>
+        </dependency>
+
+
+    </dependencies>
+
+    <build>
+        <finalName>${project.artifactId}</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 33 - 0
ruoyi-modules/ruoyi-mqtt/src/main/java/com/ruoyi/mqtt/RuoYiMqttApplication.java

@@ -0,0 +1,33 @@
+package com.ruoyi.mqtt;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import com.ruoyi.common.security.annotation.EnableCustomConfig;
+import com.ruoyi.common.security.annotation.EnableRyFeignClients;
+import org.springframework.context.annotation.ComponentScan;
+
+/**
+ * UniApp服务启动类
+ */
+@EnableCustomConfig
+@EnableRyFeignClients
+@SpringBootApplication
+@ComponentScan(basePackages = {"com.ruoyi.mqtt", "com.ruoyi.common", "com.ruoyi.mqtt.api"})
+@MapperScan("com.ruoyi.**.mapper")
+public class RuoYiMqttApplication {
+    public static void main(String[] args)
+    {
+        SpringApplication.run(RuoYiMqttApplication.class, args);
+        System.out.println("(♥◠‿◠)ノ゙  mqtt模块启动成功   ლ(´ڡ`ლ)゙  \n" +
+                " .-------.       ____     __        \n" +
+                " |  _ _   \\      \\   \\   /  /    \n" +
+                " | ( ' )  |       \\  _. /  '       \n" +
+                " |(_ o _) /        _( )_ .'         \n" +
+                " | (_,_).' __  ___(_ o _)'          \n" +
+                " |  |\\ \\  |  ||   |(_,_)'         \n" +
+                " |  | \\ `'   /|   `-'  /           \n" +
+                " |  |  \\    /  \\      /           \n" +
+                " ''-'   `'-'    `-..-'              ");
+    }
+}

+ 152 - 0
ruoyi-modules/ruoyi-mqtt/src/main/java/com/ruoyi/mqtt/config/MqttConfig.java

@@ -0,0 +1,152 @@
+package com.ruoyi.mqtt.config;
+
+import com.ruoyi.mqtt.listener.MqttMessageListener;
+import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.integration.annotation.ServiceActivator;
+import org.springframework.integration.channel.DirectChannel;
+import org.springframework.integration.core.MessageProducer;
+import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
+import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
+import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;
+import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
+import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.messaging.MessageHandler;
+
+/**
+ * EMQX MQTT 配置类
+ *
+ * @author zmj
+ */
+@Configuration
+public class MqttConfig {
+
+    /** EMQX MQTT 连接地址 */
+    private static final String BROKER = "tcp://121.4.16.100:1883";
+
+    /** EMQX 用户名 */
+    private static final String USERNAME = "emqx_u";
+
+    /** EMQX 密码 */
+    private static final String PASSWORD = "public";
+
+    /** 客户端ID */
+    private static final String CLIENT_ID = "mqttx_1e1f7a7e";
+
+    /** 订阅的主题(多个用逗号分隔) */
+    private static final String SUBSCRIBE_TOPICS = "device/+/report/job/create_result,device/+/report/job/status,log/+/event,device/+/cmd/jc/execute";
+
+    /** QoS 级别 */
+    private static final int QOS = 1;
+
+    /** 断线重连 */
+    private static final boolean RECONNECT = true;
+
+    /** 心跳间隔 */
+    private static final int KEEP_ALIVE_INTERVAL = 60;
+
+    /** 连接超时时间 */
+    private static final int CONNECTION_TIMEOUT = 30;
+
+    /**
+     * MQTT 连接参数配置(修复:移除错误的 setDefaultQos 方法)
+     */
+    @Bean
+    public MqttConnectOptions mqttConnectOptions() {
+        MqttConnectOptions options = new MqttConnectOptions();
+        // 设置连接地址
+        options.setServerURIs(new String[]{BROKER});
+        // 设置认证信息
+        options.setUserName(USERNAME);
+        options.setPassword(PASSWORD.toCharArray());
+        // 断线重连(正确配置)
+        options.setAutomaticReconnect(RECONNECT);
+        // 心跳间隔
+        options.setKeepAliveInterval(KEEP_ALIVE_INTERVAL);
+        // 连接超时时间
+        options.setConnectionTimeout(CONNECTION_TIMEOUT);
+        // 清除会话(false=保留会话,断线重连后接收离线消息)
+        options.setCleanSession(false);
+
+        return options;
+    }
+
+    /**
+     * MQTT 客户端工厂
+     */
+    @Bean
+    public MqttPahoClientFactory mqttClientFactory() {
+        DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
+        factory.setConnectionOptions(mqttConnectOptions());
+        return factory;
+    }
+
+    /**
+     * 消息接收通道
+     */
+    @Bean
+    public MessageChannel mqttInputChannel() {
+        return new DirectChannel();
+    }
+
+    /**
+     * MQTT 消息生产者(接收 EMQX 消息)
+     * 关键修复:QoS 配置在适配器中,而非连接参数
+     */
+    @Bean
+    public MessageProducer inbound() {
+        // 拆分订阅主题
+        String[] topics = SUBSCRIBE_TOPICS.split(",");
+        // 创建消息驱动适配器
+        MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter(
+                CLIENT_ID + "-inbound", mqttClientFactory(), topics);
+        // 设置消息转换器(UTF-8 编码)
+        adapter.setConverter(new DefaultPahoMessageConverter());
+
+        // ========== 正确配置 QoS:在适配器中设置 ==========
+        adapter.setQos(QOS);
+
+        // 绑定接收通道
+        adapter.setOutputChannel(mqttInputChannel());
+        return adapter;
+    }
+
+    /**
+     * 消息监听器(处理接收到的 MQTT 消息)
+     */
+    @Bean
+    @ServiceActivator(inputChannel = "mqttInputChannel")
+    public MessageHandler mqttMessageHandler() {
+        return new MqttMessageListener();
+    }
+
+    /**
+     * 消息发送通道
+     */
+    @Bean
+    public MessageChannel mqttOutputChannel() {
+        return new DirectChannel();
+    }
+
+    /**
+     * MQTT 消息处理器(发送消息到 EMQX)
+     * 关键修复:默认 QoS 在处理器中配置
+     */
+    @Bean
+    @ServiceActivator(inputChannel = "mqttOutputChannel")
+    public MessageHandler mqttOutbound() {
+        MqttPahoMessageHandler handler = new MqttPahoMessageHandler(
+                CLIENT_ID + "-outbound", mqttClientFactory());
+
+        // ========== 正确配置默认 QoS:在消息处理器中 ==========
+        handler.setDefaultQos(QOS);
+
+        // 异步发送
+        handler.setAsync(true);
+        // 保留消息(可选)
+        handler.setDefaultRetained(false);
+        return handler;
+    }
+}

+ 78 - 0
ruoyi-modules/ruoyi-mqtt/src/main/java/com/ruoyi/mqtt/controller/MqttController.java

@@ -0,0 +1,78 @@
+package com.ruoyi.mqtt.controller;
+import com.ruoyi.common.core.web.domain.AjaxResult;
+import com.ruoyi.mqtt.api.domain.JobCreateRequestDTO;
+import com.ruoyi.mqtt.service.MqttSendService;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import com.alibaba.fastjson.JSON;
+import javax.annotation.Resource;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * MQTT服务的对外接口Controller
+ * 提供Feign调用的入口
+ */
+@RestController
+public class MqttController {
+
+    @Resource
+    private MqttSendService mqttSendService;
+
+    /**
+     * 发送MQTT消息(对应Feign接口)
+     */
+    @PostMapping("/mqtt/sendMessage")
+    public AjaxResult sendMqttMessage(
+            @RequestParam("topic") String topic,
+            @RequestParam("payload") String payload
+    ) {
+        boolean result = mqttSendService.sendMessage(topic, payload);
+        return result ? AjaxResult.success("消息发送成功") : AjaxResult.error("消息发送失败");
+    }
+
+    @PostMapping("/mqtt/sendTaskCommand")
+    public AjaxResult sendTaskCommand(
+            @RequestParam("machineCode") String machineCode,
+            @RequestParam("jobId") String jobId,
+            @RequestParam("action") String action,
+            @RequestParam("requestId") String requestId
+    ) {
+        // 构造payload
+        Map<String, Object> payload = new HashMap<>();
+        payload.put("requestId", requestId);
+        payload.put("jobId", jobId);
+        payload.put("action", action);
+
+        // 发送消息到指定topic
+        String topic = String.format("device/%s/cmd/job/execute", machineCode);
+        boolean result = mqttSendService.sendMessage(topic, JSON.toJSONString(payload));
+
+        return result ? AjaxResult.success("作业指令下发成功") : AjaxResult.error("作业指令下发失败");
+    }
+
+
+    /**
+     * 下发作业(创建作业指令下发)
+     * 对应Topic: device/{deviceId}/cmd/job/execute
+     */
+    @PostMapping("/mqtt/createJob")
+    public AjaxResult createJob(
+            @RequestParam("machineCode") String machineCode,
+            @RequestBody JobCreateRequestDTO request
+    ) {
+        System.out.println(request);
+        // 构建MQTT Topic(遵循协议文档定义)
+        String topic = String.format("device/%s/cmd/job/execute", machineCode);
+
+        // 发送MQTT消息(QoS2,非保留)
+        boolean result = mqttSendService.sendMessage(topic, JSON.toJSONString(request));
+
+        return result ?
+                AjaxResult.success("作业指令下发成功") :
+                AjaxResult.error("作业指令下发失败");
+    }
+
+}

+ 13 - 0
ruoyi-modules/ruoyi-mqtt/src/main/java/com/ruoyi/mqtt/dto/AddJobResponse.java

@@ -0,0 +1,13 @@
+package com.ruoyi.mqtt.dto;
+
+import lombok.Data;
+
+@Data
+public class AddJobResponse {
+
+    private String requestId;
+    private String result;
+    private String jobId;
+    private Integer errorCode;//4001=作业类型不支持,4002=路线参数非法,4003=区域点数不足,4004=起点非法,4005=航迹非法,5000=设备内部错误
+    private String message;
+}

+ 52 - 0
ruoyi-modules/ruoyi-mqtt/src/main/java/com/ruoyi/mqtt/dto/DiseaseRecognitionReportDTO.java

@@ -0,0 +1,52 @@
+package com.ruoyi.mqtt.dto;
+
+import lombok.Data;
+
+/**
+ * 病害识别数据上报DTO
+ *
+ * @author zmj
+ */
+@Data
+public class DiseaseRecognitionReportDTO {
+
+    /**
+     * 设备编码
+     */
+    private String device_code;
+
+    /**
+     * 经度
+     */
+    private Double lng;
+
+    /**
+     * 纬度
+     */
+    private Double lat;
+
+    /**
+     * 病害名称
+     */
+    private String disease_name;
+
+    /**
+     * AI识别置信度
+     */
+    private Double confidence;
+
+    /**
+     * 图片base64编码
+     */
+    private String img_base64;
+
+    /**
+     * 采集/识别时间
+     */
+    private String collect_time;
+
+    /**
+     * 作物类型
+     */
+    private String crop_type;
+}

+ 52 - 0
ruoyi-modules/ruoyi-mqtt/src/main/java/com/ruoyi/mqtt/dto/JobStatusReportDTO.java

@@ -0,0 +1,52 @@
+package com.ruoyi.mqtt.dto;
+
+import com.ruoyi.mqtt.api.domain.PointDTO;
+import com.ruoyi.mqtt.listener.MqttMessageListener;
+import lombok.Data;
+
+/**
+ * 作业状态报告DTO
+ * 对应MQTT Topic: device/{deviceId}/report/job/status
+ * 用于设备上报作业状态信息(每3秒一次)
+ */
+@Data
+public class JobStatusReportDTO {
+    /**
+     * 执行请求唯一ID
+     * 与执行请求中的requestId对应
+     */
+    private String requestId;
+
+    private String deviceId;
+    /**
+     * 当前作业ID
+     * 标识正在执行的作业
+     */
+    private String jobId;
+    private String state; // 对应JobStateEnum
+
+    /**
+     * 当前作业位置
+     * 设备当前所在的坐标位置
+     */
+    private PointDTO currentPoint;
+    /**
+     * 作业完成百分比
+     * 范围0-100,标识作业进度
+     */
+    private Integer progress;
+    /**
+     * 设备当前时间
+     * 格式如:2025-03-30 14:25:30
+     */
+    private String reportTime;
+    private String speed;
+    private String direction;
+    private String battery;
+    private String fault_code;
+    /**
+     * 设备消息
+     * 描述当前作业状态的附加信息
+     */
+    private String message;
+}

+ 48 - 0
ruoyi-modules/ruoyi-mqtt/src/main/java/com/ruoyi/mqtt/dto/LogEventDTO.java

@@ -0,0 +1,48 @@
+package com.ruoyi.mqtt.dto;
+
+import com.ruoyi.mqtt.listener.MqttMessageListener;
+import lombok.Data;
+
+/**
+ * 日志事件DTO
+ * 对应MQTT Topic: log/{deviceId}/event
+ * 用于记录设备相关的事件日志
+ */
+@Data
+public class LogEventDTO {
+    /**
+     * 事件唯一ID(全局唯一)
+     * 必传,标识单次事件的唯一性
+     */
+    private String eventId;
+    /**
+     * 车辆唯一标识
+     * 必传,关联的设备ID
+     */
+    private String deviceId;
+    private String eventType; // 对应EventTypeEnum
+    private String source;    // 对应SourceEnum
+    private String action;    // 对应ActionEnum
+    private String status;    // 对应StatusEnum
+
+    /**
+     * 操作人信息
+     * 仅在eventType=OPERATION时必填
+     */
+    private OperatorDTO operator;
+    /**
+     * 事件链路追踪信息
+     * 用于跟踪事件的处理链路
+     */
+    private TraceDTO trace;
+    /**
+     * 业务扩展数据
+     * 根据eventType+action动态变化,包含具体业务信息
+     */
+    private PayloadDTO payload;
+    /**
+     * 事件时间(Unix秒)
+     * 必传,事件发生的时间戳
+     */
+    private Long timestamp;
+}

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