feat (farm): 设备管理

- 本服务接口
- 对接第三方设备商
This commit is contained in:
达富斌
2025-09-19 17:32:24 +08:00
parent 7e29eafb48
commit 46f9bd6cb9
54 changed files with 2256 additions and 41 deletions

View File

@@ -109,11 +109,24 @@ public class BaseInfoController {
BeanUtils.toBean(list, BaseInfoRespVO.class)); BeanUtils.toBean(list, BaseInfoRespVO.class));
} }
@GetMapping("/base/plot/list")
@Operation(summary = "基地-地块列表")
public CommonResult<List<BaseInfoForWxRespVO>> getBasePlotList() {
List<BaseInfoForWxRespVO> res = baseInfoService.getBaseListForWx();
return success(res);
}
@GetMapping("/base/list")
@Operation(summary = "基地列表")
public CommonResult<List<BaseNamesRespVO>> getBaseList() {
List<BaseInfoDO> baseInfos = baseInfoService.getBaseInfos();
List<BaseNamesRespVO> res = baseInfos.stream().map(e -> new BaseNamesRespVO(e.getId(),e.getName())).toList();
return success(res);
}
/** /**
* === === 微信端 === === * === === 微信端 === ===
*/ */
// @GetMapping("/wx/base/list")
// @Operation(summary = "微信端-常用地块集合")
@GetMapping("/wx/base/list") @GetMapping("/wx/base/list")
@Operation(summary = "基地列表(微信端)") @Operation(summary = "基地列表(微信端)")
public CommonResult<List<BaseInfoForWxRespVO>> getBaseListForWx() { public CommonResult<List<BaseInfoForWxRespVO>> getBaseListForWx() {

View File

@@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.farm.controller.admin.baseinfo.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @description:
* @author: binda
* @date: 2025/9/19 16:08
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class BaseNamesRespVO {
private String baseId;
private String baseName;
}

View File

@@ -6,9 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.farm.controller.admin.deviceclassify.vo.DeviceClassifyPageReqVO; import cn.iocoder.yudao.module.farm.controller.admin.deviceclassify.vo.*;
import cn.iocoder.yudao.module.farm.controller.admin.deviceclassify.vo.DeviceClassifyRespVO;
import cn.iocoder.yudao.module.farm.controller.admin.deviceclassify.vo.DeviceClassifySaveReqVO;
import cn.iocoder.yudao.module.farm.dal.dataobject.deviceclassify.DeviceClassifyDO; import cn.iocoder.yudao.module.farm.dal.dataobject.deviceclassify.DeviceClassifyDO;
import cn.iocoder.yudao.module.farm.service.deviceclassify.DeviceClassifyService; import cn.iocoder.yudao.module.farm.service.deviceclassify.DeviceClassifyService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -106,4 +104,18 @@ public class DeviceClassifyController {
BeanUtils.toBean(list, DeviceClassifyRespVO.class)); BeanUtils.toBean(list, DeviceClassifyRespVO.class));
} }
@GetMapping("/device-type/list")
@Operation(summary = "设备类型列表")
public CommonResult<List<DeviceTypeRespVO>> getDeviceTypeList() {
List<DeviceTypeRespVO> res = deviceClassifyService.getDeviceTypeList();
return success(res);
}
@GetMapping("/device-model/list")
@Operation(summary = "设备型号列表")
public CommonResult<List<DeviceModelRespVO>> getDeviceModelList(String deviceTypeId) {
List<DeviceModelRespVO> res = deviceClassifyService.getDeviceModelList(deviceTypeId);
return success(res);
}
} }

View File

@@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.farm.controller.admin.deviceclassify.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* @description: 设备型号
* @author: binda
* @date: 2025/9/19 16:12
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DeviceModelRespVO {
@Schema(description = "设备型号ID")
private String deviceModelId;
@Schema(description = "设备型号名称")
private String deviceModelName;
}

View File

@@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.farm.controller.admin.deviceclassify.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* @description: 设备类型
* @author: binda
* @date: 2025/9/19 16:12
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DeviceTypeRespVO {
@Schema(description = "设备类型ID")
private String deviceTypeId;
@Schema(description = "设备类型名称")
private String deviceTypeName;
@Schema(description = "设备类型图片")
private String image;
@Schema(description = "设备类型图片集合")
private List<String> imageList;
}

View File

@@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.farm.controller.admin.deviceinfo.vo.DeviceInfoPageReqVO; import cn.iocoder.yudao.module.farm.controller.admin.deviceinfo.vo.DeviceInfoPageReqVO;
import cn.iocoder.yudao.module.farm.controller.admin.deviceinfo.vo.DeviceInfoRespVO; import cn.iocoder.yudao.module.farm.controller.admin.deviceinfo.vo.DeviceInfoRespVO;
import cn.iocoder.yudao.module.farm.controller.admin.deviceinfo.vo.DeviceInfoSaveReqVO; import cn.iocoder.yudao.module.farm.controller.admin.deviceinfo.vo.DeviceInfoSaveReqVO;
import cn.iocoder.yudao.module.farm.controller.admin.deviceinfo.vo.v2.*;
import cn.iocoder.yudao.module.farm.dal.dataobject.deviceinfo.DeviceInfoDO; import cn.iocoder.yudao.module.farm.dal.dataobject.deviceinfo.DeviceInfoDO;
import cn.iocoder.yudao.module.farm.dal.dataobject.mqtt.MqttConnectionDO; import cn.iocoder.yudao.module.farm.dal.dataobject.mqtt.MqttConnectionDO;
import cn.iocoder.yudao.module.farm.service.deviceinfo.DeviceInfoService; import cn.iocoder.yudao.module.farm.service.deviceinfo.DeviceInfoService;
@@ -43,14 +44,16 @@ public class DeviceInfoController {
@PostMapping("/create") @PostMapping("/create")
@Operation(summary = "创建设备信息") @Operation(summary = "创建设备信息")
public CommonResult<String> createDeviceInfo(@Valid @RequestBody DeviceInfoSaveReqVO createReqVO) { public CommonResult<String> createDeviceInfo(@Valid @RequestBody DeviceInfoSaveV2ReqVO createReqVO) {
return success(deviceInfoService.createDeviceInfo(createReqVO)); DeviceInfoSaveReqVO targetObj = BeanUtils.toBean(createReqVO, DeviceInfoSaveReqVO.class);
return success(deviceInfoService.createDeviceInfo(targetObj));
} }
@PutMapping("/update") @PutMapping("/update")
@Operation(summary = "更新设备信息") @Operation(summary = "更新设备信息")
public CommonResult<Boolean> updateDeviceInfo(@Valid @RequestBody DeviceInfoSaveReqVO updateReqVO) { public CommonResult<Boolean> updateDeviceInfo(@Valid @RequestBody DeviceInfoSaveV2ReqVO updateReqVO) {
deviceInfoService.updateDeviceInfo(updateReqVO); DeviceInfoSaveReqVO targetObj = BeanUtils.toBean(updateReqVO, DeviceInfoSaveReqVO.class);
deviceInfoService.updateDeviceInfo(targetObj);
return success(true); return success(true);
} }
@@ -73,16 +76,24 @@ public class DeviceInfoController {
@GetMapping("/get") @GetMapping("/get")
@Operation(summary = "获得设备信息") @Operation(summary = "获得设备信息")
@Parameter(name = "id", description = "编号", required = true, example = "1024") @Parameter(name = "id", description = "编号", required = true, example = "1024")
public CommonResult<DeviceInfoRespVO> getDeviceInfo(@RequestParam("id") String id) { public CommonResult<DeviceInfoDetailV2RespVO> getDeviceInfo(@RequestParam("id") String id) {
DeviceInfoDO deviceInfo = deviceInfoService.getDeviceInfo(id); DeviceInfoDO deviceInfo = deviceInfoService.getDeviceInfo(id);
return success(BeanUtils.toBean(deviceInfo, DeviceInfoRespVO.class)); return success(BeanUtils.toBean(deviceInfo, DeviceInfoDetailV2RespVO.class));
} }
@GetMapping("/page") @GetMapping("/page")
@Operation(summary = "获得设备信息分页") @Operation(summary = "获得设备信息分页")
public CommonResult<PageResult<DeviceInfoRespVO>> getDeviceInfoPage(@Valid DeviceInfoPageReqVO pageReqVO) { public CommonResult<PageResult<DeviceInfoPageV2RespVO>> getDeviceInfoPage(@Valid DeviceInfoPageV2ReqVO pageReqVO) {
PageResult<DeviceInfoDO> pageResult = deviceInfoService.getDeviceInfoPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, DeviceInfoRespVO.class)); DeviceInfoPageReqVO targetObj = new DeviceInfoPageReqVO();
targetObj.setBaseId(pageReqVO.getBaseId());
targetObj.setDeviceType(pageReqVO.getDeviceTypeId());
targetObj.setDeviceName(pageReqVO.getDeviceName());
PageResult<DeviceInfoDO> pageResult = deviceInfoService.getDeviceInfoPage(targetObj);
PageResult<DeviceInfoPageV2RespVO> deviceInfoRespVOPageResult = BeanUtils.toBean(pageResult, DeviceInfoPageV2RespVO.class);
return success(deviceInfoRespVOPageResult);
} }
@GetMapping("/export-excel") @GetMapping("/export-excel")
@@ -106,6 +117,14 @@ public class DeviceInfoController {
} catch (MqttException e) { } catch (MqttException e) {
return error(500, "设备连接失败"); return error(500, "设备连接失败");
} }
} }
@GetMapping("/statistics")
@Operation(summary = "订阅设备上报并持久化")
@Parameter(name = "baseId", description = "基地编号")
public CommonResult<DeviceStatisticsRespVO> getStatisticsInfo(@RequestParam("baseId") String baseId) {
DeviceStatisticsRespVO res = deviceInfoService.getStatisticsInfo(baseId);
return success(res);
}
} }

View File

@@ -40,8 +40,10 @@ public class DeviceInfoPageReqVO extends PageParam {
@Schema(description = "LP产品唯一ID", example = "10175") @Schema(description = "LP产品唯一ID", example = "10175")
private String lpProductUniqueId; private String lpProductUniqueId;
@Schema(description = "设备状态", example = "2") @Schema(description = "设备状态:0-未激活,1-在线,2-离线,3-维护中,4-故障")
private Integer status; private Integer deviceState;
@Schema(description = "绑定状态:1: 绑定; 0: 未绑定")
private Integer bindingState;
@Schema(description = "设备类型", example = "2") @Schema(description = "设备类型", example = "2")
private String deviceType; private String deviceType;

View File

@@ -52,9 +52,10 @@ public class DeviceInfoRespVO {
@ExcelProperty("LP产品唯一ID") @ExcelProperty("LP产品唯一ID")
private String lpProductUniqueId; private String lpProductUniqueId;
@Schema(description = "设备状态", example = "2") @Schema(description = "设备状态:0-未激活,1-在线,2-离线,3-维护中,4-故障")
@ExcelProperty("设备状态") private Integer deviceState;
private Integer status; @Schema(description = "绑定状态:1: 绑定; 0: 未绑定")
private Integer bindingState;
@Schema(description = "设备类型", example = "2") @Schema(description = "设备类型", example = "2")
@ExcelProperty("设备类型") @ExcelProperty("设备类型")

View File

@@ -42,8 +42,10 @@ public class DeviceInfoSaveReqVO {
@Schema(description = "LP产品唯一ID", example = "10175") @Schema(description = "LP产品唯一ID", example = "10175")
private String lpProductUniqueId; private String lpProductUniqueId;
@Schema(description = "设备状态", example = "2") @Schema(description = "设备状态:0-未激活,1-在线,2-离线,3-维护中,4-故障")
private Integer status; private Integer deviceState;
@Schema(description = "绑定状态:1: 绑定; 0: 未绑定")
private Integer bindingState;
@Schema(description = "设备类型", example = "2") @Schema(description = "设备类型", example = "2")
private String deviceType; private String deviceType;

View File

@@ -0,0 +1,73 @@
package cn.iocoder.yudao.module.farm.controller.admin.deviceinfo.vo.v2;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @description: 设备详情 请求实体
* @author: binda
* @date: 2025/9/19 09:47
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DeviceInfoDetailV2RespVO {
private String id;
@Schema(description = "设备全局唯一标识(设备厂商提供)", requiredMode = Schema.RequiredMode.REQUIRED)
private String deviceUniqueId;
@Schema(description = "设备类型 (设备类型ID)")
private String deviceType;
@Schema(description = "设备分类名称 (设备型号,字符拼接)")
private String deviceCategoryName;
@Schema(description = "设备名称(本服务自定义名称)")
private String deviceName;
@Schema(description = "负责人")
private String managerId;
@Schema(description = "是否存在物联网卡 (1:有;2:无;)")
private Boolean isIotCardExisted;
@Schema(description = "设备状态:0-未激活,1-在线,2-离线,3-维护中,4-故障")
private Integer deviceState;
@Schema(description = "绑定状态:1: 绑定; 0: 未绑定")
private Integer bindingState;
@Schema(description = "设备坐标")
private String point;
@Schema(description = "基地iD")
private String baseId;
@Schema(description = "基地坐标")
private String basePoint;
@Schema(description = "地块ID")
private String plotId;
@Schema(description = "地块坐标")
private String plotPoint;
@Schema(description = "土壤墒情")
private SoilInfo soilInfo;
@Schema(description = "水肥机")
private WfInfo wfInfo;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class SoilInfo{
private String deviceUniqueId;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class WfInfo{
private String deviceUniqueId;
}
}

View File

@@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.farm.controller.admin.deviceinfo.vo.v2;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @description:
* @author: binda
* @date: 2025/9/19 15:35
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DeviceInfoPageV2ReqVO {
@Schema(description = "基地ID")
private String baseId;
@Schema(description = "设备类型ID")
private String deviceTypeId;
@Schema(description = "设备名称")
private String deviceName;
@Schema(description = "设备状态:0-未激活,1-在线,2-离线,3-维护中,4-故障")
private Integer deviceState;
}

View File

@@ -0,0 +1,49 @@
package cn.iocoder.yudao.module.farm.controller.admin.deviceinfo.vo.v2;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* @description: 分页查询
* @author: binda
* @date: 2025/9/19 14:22
*/
@Data
public class DeviceInfoPageV2RespVO extends PageParam {
private String id;
@Schema(description = "设备全局唯一标识(设备厂商提供)", requiredMode = Schema.RequiredMode.REQUIRED)
private String deviceUniqueId;
@Schema(description = "设备类型 (设备类型ID)")
private String deviceType;
@Schema(description = "设备分类名称 (设备型号,字符拼接)")
private String deviceCategoryName;
@Schema(description = "设备名称(本服务自定义名称)")
private String deviceName;
@Schema(description = "负责人")
private String managerId;
@Schema(description = "是否存在物联网卡 (1:有;2:无;)")
private Boolean isIotCardExisted;
@Schema(description = "设备状态:0-未激活,1-在线,2-离线,3-维护中,4-故障")
private Integer deviceState;
@Schema(description = "绑定状态:1: 绑定; 0: 未绑定")
private Integer bindingState;
@Schema(description = "设备坐标")
private String point;
@Schema(description = "基地iD")
private String baseId;
@Schema(description = "基地坐标")
private String basePoint;
@Schema(description = "地块ID")
private String plotId;
@Schema(description = "地块坐标")
private String plotPoint;
}

View File

@@ -0,0 +1,45 @@
package cn.iocoder.yudao.module.farm.controller.admin.deviceinfo.vo.v2;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @description: 新增设备 请求实体
* @author: binda
* @date: 2025/9/19 09:47
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DeviceInfoSaveV2ReqVO {
private String id;
@Schema(description = "设备全局唯一标识(设备厂商提供)", requiredMode = Schema.RequiredMode.REQUIRED)
private String deviceUniqueId;
@Schema(description = "设备类型 (设备类型ID)")
private String deviceType;
@Schema(description = "设备分类名称 (设备型号,字符拼接)")
private String deviceCategoryName;
@Schema(description = "设备名称(本服务自定义名称)")
private String deviceName;
@Schema(description = "负责人")
private String managerId;
@Schema(description = "是否存在物联网卡 (1:有;0:无;)")
private Boolean isIotCardExisted;
@Schema(description = "设备状态:0-未激活,1-在线,2-离线,3-维护中,4-故障")
private Integer deviceState;
@Schema(description = "绑定状态:1: 绑定; 0: 未绑定")
private Integer bindingState;
private String baseId;
private String plotId;
@Schema(description = "设备坐标")
private String point;
}

View File

@@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.farm.controller.admin.deviceinfo.vo.v2;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @description: 设备统计
* @author: binda
* @date: 2025/9/19 15:48
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DeviceStatisticsRespVO {
@Schema(description = "设备总数")
private Integer deviceTotal;
@Schema(description = "在线总数")
private Integer deviceOnline;
@Schema(description = "离线总数")
private Integer deviceOffline;
@Schema(description = "异常总数")
private Integer deviceWarning;
}

View File

@@ -54,11 +54,11 @@ public class DeviceStatusController {
} }
DeviceStatusRespVO respVO = BeanUtils.toBean(device, DeviceStatusRespVO.class); DeviceStatusRespVO respVO = BeanUtils.toBean(device, DeviceStatusRespVO.class);
if (device.getState() != null) { if (device.getDeviceState() != null) {
DeviceStateEnum stateEnum = DeviceStateEnum.valueOf(device.getState()); DeviceStateEnum stateEnum = DeviceStateEnum.valueOf(device.getDeviceState());
if (stateEnum != null) { if (stateEnum != null) {
respVO.setStateName(stateEnum.getName()); respVO.setStateName(stateEnum.getName());
respVO.setOnline(DeviceStateEnum.isOnline(device.getState())); respVO.setOnline(DeviceStateEnum.isOnline(device.getDeviceState()));
} }
} }
return success(respVO); return success(respVO);

View File

@@ -23,12 +23,11 @@ import cn.iocoder.yudao.module.farm.framework.util.ImageCollectionUtil;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class DeviceInfoDO extends BaseDO { public class DeviceInfoDO extends BaseDO {
@TableId(type = IdType.INPUT)
private String id; private String id;
/** /**
* 设备全局唯一标识 * 设备全局唯一标识 (设备商提供)
*/ */
@TableId(type = IdType.INPUT)
private String deviceUniqueId; private String deviceUniqueId;
/** /**
* 设备名称 * 设备名称
@@ -166,7 +165,11 @@ public class DeviceInfoDO extends BaseDO {
/** /**
* 设备状态:0-未激活,1-在线,2-离线,3-维护中,4-故障 * 设备状态:0-未激活,1-在线,2-离线,3-维护中,4-故障
*/ */
private Integer state; private Integer deviceState;
/**
* 绑定状态:1: 绑定; 0: 未绑定
*/
private Integer bindingState;
/** /**
* 设备激活时间 * 设备激活时间
@@ -212,6 +215,20 @@ public class DeviceInfoDO extends BaseDO {
private String tenantId; private String tenantId;
private String point; private String point;
/**
* 负责人
*/
private String managerId;
/** /**
* 获取设备图片集合 * 获取设备图片集合
*/ */

View File

@@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.farm.controller.admin.deviceinfo.vo.DeviceInfoPageReqVO; import cn.iocoder.yudao.module.farm.controller.admin.deviceinfo.vo.DeviceInfoPageReqVO;
import cn.iocoder.yudao.module.farm.controller.admin.deviceinfo.vo.v2.DeviceStatisticsRespVO;
import cn.iocoder.yudao.module.farm.dal.dataobject.deviceinfo.DeviceInfoDO; import cn.iocoder.yudao.module.farm.dal.dataobject.deviceinfo.DeviceInfoDO;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
@@ -30,7 +31,7 @@ public interface DeviceInfoMapper extends BaseMapperX<DeviceInfoDO> {
.eqIfPresent(DeviceInfoDO::getLpProductKey, reqVO.getLpProductKey()) .eqIfPresent(DeviceInfoDO::getLpProductKey, reqVO.getLpProductKey())
.likeIfPresent(DeviceInfoDO::getLpProductName, reqVO.getLpProductName()) .likeIfPresent(DeviceInfoDO::getLpProductName, reqVO.getLpProductName())
.eqIfPresent(DeviceInfoDO::getLpProductUniqueId, reqVO.getLpProductUniqueId()) .eqIfPresent(DeviceInfoDO::getLpProductUniqueId, reqVO.getLpProductUniqueId())
.eqIfPresent(DeviceInfoDO::getStatus, reqVO.getStatus()) .eqIfPresent(DeviceInfoDO::getStatus, reqVO.getDeviceState())
.eqIfPresent(DeviceInfoDO::getDeviceType, reqVO.getDeviceType()) .eqIfPresent(DeviceInfoDO::getDeviceType, reqVO.getDeviceType())
.eqIfPresent(DeviceInfoDO::getType, reqVO.getType()) .eqIfPresent(DeviceInfoDO::getType, reqVO.getType())
.eqIfPresent(DeviceInfoDO::getVendor, reqVO.getVendor()) .eqIfPresent(DeviceInfoDO::getVendor, reqVO.getVendor())
@@ -62,7 +63,7 @@ public interface DeviceInfoMapper extends BaseMapperX<DeviceInfoDO> {
*/ */
default List<DeviceInfoDO> selectListByState(Integer state) { default List<DeviceInfoDO> selectListByState(Integer state) {
return selectList(new LambdaQueryWrapperX<DeviceInfoDO>() return selectList(new LambdaQueryWrapperX<DeviceInfoDO>()
.eq(DeviceInfoDO::getState, state)); .eq(DeviceInfoDO::getDeviceState, state));
} }
/** /**
@@ -70,7 +71,7 @@ public interface DeviceInfoMapper extends BaseMapperX<DeviceInfoDO> {
*/ */
default List<DeviceInfoDO> selectTimeoutDevices(@Param("timeoutTime") LocalDateTime timeoutTime) { default List<DeviceInfoDO> selectTimeoutDevices(@Param("timeoutTime") LocalDateTime timeoutTime) {
return selectList(new LambdaQueryWrapperX<DeviceInfoDO>() return selectList(new LambdaQueryWrapperX<DeviceInfoDO>()
.eq(DeviceInfoDO::getState, 1) // 只查询在线状态的设备 .eq(DeviceInfoDO::getDeviceState, 1) // 只查询在线状态的设备
.and(wrapper -> wrapper .and(wrapper -> wrapper
.isNull(DeviceInfoDO::getLastMessageTime) // 从未上报过消息 .isNull(DeviceInfoDO::getLastMessageTime) // 从未上报过消息
.or() .or()
@@ -83,7 +84,14 @@ public interface DeviceInfoMapper extends BaseMapperX<DeviceInfoDO> {
*/ */
default Long selectCountByState(Integer state) { default Long selectCountByState(Integer state) {
return selectCount(new LambdaQueryWrapperX<DeviceInfoDO>() return selectCount(new LambdaQueryWrapperX<DeviceInfoDO>()
.eq(DeviceInfoDO::getState, state)); .eq(DeviceInfoDO::getDeviceState, state));
} }
/**
* 设备统计信息
* @param baseId
* @return
*/
DeviceStatisticsRespVO getStatisticsInfo(@Param("baseId") String baseId,
@Param("tenantId") String tenantId);
} }

View File

@@ -26,6 +26,12 @@ public interface ErrorCodeConstants {
// ========== 设备信息 1-050-004-000 ========== // ========== 设备信息 1-050-004-000 ==========
ErrorCode DEVICE_INFO_NOT_EXISTS = new ErrorCode(1_050_004_000, "设备信息不存在"); ErrorCode DEVICE_INFO_NOT_EXISTS = new ErrorCode(1_050_004_000, "设备信息不存在");
ErrorCode DEVICE_INFO_NOT_EXISTS_FOR_USER_LOGIN = new ErrorCode(1_050_004_001, "物连网农业四情采集,登录失败");
ErrorCode DEVICE_INFO_NOT_EXISTS_FOR_USER_DEVICE = new ErrorCode(1_050_004_002, "获取用户设备失败");
ErrorCode DEVICE_INFO_NOT_EXISTS_FOR_REAL_TIME_DATA= new ErrorCode(1_050_004_003, "获取设备实时数据失败");
ErrorCode DEVICE_INFO_NOT_EXISTS_FOR_ALARM_NOTICE_RECORD= new ErrorCode(1_050_004_004, "获取设备报警通知记录失败");
ErrorCode DEVICE_INFO_NOT_EXISTS_FOR_WF_LOGIN= new ErrorCode(1_050_004_005, "水肥机登录失败");
ErrorCode DEVICE_TYPE_ERROR= new ErrorCode(1_050_004_006, "未找到对应的设备类型");
// ========== 设备地块关系 1-050-005-000 ========== // ========== 设备地块关系 1-050-005-000 ==========
ErrorCode DEVICE_PLOT_RELATION_NOT_EXISTS = new ErrorCode(1_050_005_000, "设备地块关系不存在"); ErrorCode DEVICE_PLOT_RELATION_NOT_EXISTS = new ErrorCode(1_050_005_000, "设备地块关系不存在");

View File

@@ -0,0 +1,13 @@
package cn.iocoder.yudao.module.farm.manager.devices;
import org.springframework.stereotype.Service;
/**
* @description: 设备管理
* 参考文档: 物连网农业四情采集api协议.pdf 版本:v2.0.3 日期:2022.08.16
* @author: binda
* @date: 2025/9/17 10:50
*/
@Service
public class DeviceManager {
}

View File

@@ -0,0 +1,79 @@
package cn.iocoder.yudao.module.farm.manager.devices.commons;
/**
* @description: 设备常量
* @author: binda
* @date: 2025/9/17 15:24
*/
public class DeviceConst {
/**
* 四情 设备类型
*/
public final String DEVICE_TYPE_IRRIGATION = "irrigation"; // 灌溉
public final String DEVICE_TYPE_WORM = "worm"; // 虫害
public final String DEVICE_TYPE_WORM_FLAG_SHIP = "wormFlagship";
public final String DEVICE_TYPE_SOIL = "soil"; // 土壤
public final String DEVICE_TYPE_MET = "met"; // 重金属
public final String DEVICE_TYPE_SPORE = "spore"; // 孢子
public final String DEVICE_TYPE_CAMERA = "camera"; // 监控
/**
* 四情 设备状态
*/
public final String DEVICE_STATUS_ONLINE = "online";
public final String DEVICE_STATUS_OFFLINE = "offline";
public final String DEVICE_STATUS_ALARM = "alarm";
/**
* 四情 http 状态
*/
public final String HTTP_STATUS_1000 = "1000"; // 成功
public final String HTTP_STATUS_1001 = "1001"; // 失败 (执行错误)
public final String HTTP_STATUS_1002 = "1002"; // 失败 (验证失败)
public final String HTTP_STATUS_1003 = "1003"; // 服务端异常
public final String HTTP_STATUS_1004 = "1004"; // 登录过期
public final String HTTP_STATUS_2000 = "2000"; // 登录超时
public final String HTTP_STATUS_4000 = "4000"; // 未登录或登录失败
public final String HTTP_STATUS_5000 = "5000"; // 操作失败
/**
* 四情-墒情 设备实时数据 - 节点类型
*/
public final int NODE_TYPE_1 = 1; // 模拟量
public final int NODE_TYPE_1_1 = 1; // 模拟量 使能模拟量
public final int NODE_TYPE_1_2 = 2; // 模拟量 使能
public final int NODE_TYPE_2 = 2; // 模拟量
public final int NODE_TYPE_2_1 = 1; // 模拟量 使能模拟量
public final int NODE_TYPE_2_2 = 2; // 模拟量 禁用
public final int NODE_TYPE_3 = 3; // 模拟量
public final int NODE_TYPE_3_1 = 1; // 模拟量 禁用模拟量
public final int NODE_TYPE_3_2 = 2; // 模拟量 使能
public final int NODE_TYPE_4 = 4; // 浮点型设备
public final int NODE_TYPE_5 = 5; // 开关量型设备
public final int NODE_TYPE_6 = 6; // 32位有符号整型
public final int NODE_TYPE_7 = 7; // 32位无符号整型
public final int NODE_TYPE_8 = 8; // 遥调设备
/**
* 四情-墒情 设备实时数据 - 节点报警等级
*/
public final int ALARM_STATUS_0 = 0; // 正常
public final int ALARM_STATUS_1 = 1; // 超上限报警
public final int ALARM_STATUS_2 = 2; // 超下限报警
public final int ALARM_STATUS_3 = 3; // 开关闭合报警
public final int ALARM_STATUS_4 = 4; // 开关断开报警
public final int ALARM_STATUS_5 = 5; // 遥调报警
/**
* 四情-设备报警通知记录 - 通知类型
*/
public final int NOTICE_TYPE_0 = 0; // 邮箱
public final int NOTICE_TYPE_1 = 1; // 短信
// TODO: 2025/9/17 UpdateNodeInfoReqVO
}

View File

@@ -0,0 +1,62 @@
package cn.iocoder.yudao.module.farm.manager.devices.conf;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* @description: 设备配置
* @author: binda
* @date: 2025/9/17 10:55
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "device-manager")
public class DeviceConf {
private FourSituations fourSituations;
private WaterAndFertilizer waterAndFertilizer;
@Data
public static class FourSituations {
private String hosts;
private String username;
private String password;
private Global global;
private Soil soil;
}
@Data
public static class Global {
private String url21; // 用户登录
private String url22; // 获取登录用户信息
private String url23; // 获取子用户信息
private String url24; // 设备报警通知记录
private String url25; // 获取用户区域
private String url26; // 获取用户设备
private String url27; // 批量获取摄像头设备信息
private String url28; // 获取设备实时数据
private String url29; // 区域绑定设备
}
@Data
public static class Soil {
private String url41; // 墒情设备, 获取设备全部信息
private String url42; // 墒情设备, 更新设备信息
private String url43; // 墒情设备, 更新节点信息
private String url44; // 墒情设备, 获取历史数据
}
@Data
public static class WaterAndFertilizer {
private String hosts;
private String username;
private String password;
private String url1;
private String url2;
private String url3;
private String url4;
}
}

View File

@@ -0,0 +1,15 @@
package cn.iocoder.yudao.module.farm.manager.devices.conf;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* @description:
* @author: binda
* @date: 2025/9/17 14:42
*/
@Configuration
@EnableScheduling
public class SchedulingConfig {
}

View File

@@ -0,0 +1,204 @@
package cn.iocoder.yudao.module.farm.manager.devices.soil;
import cn.iocoder.yudao.module.farm.manager.devices.soil.dto.UserLoginRespVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @description: 缓存token
* @author: binda
* @date: 2025/9/17 14:33
*/
@Slf4j
public abstract class BaseTokenCacheService {
// 使用ConcurrentHashMap存储token信息,key: tenantId, value: token
private final Map<String, String> tokenCache = new ConcurrentHashMap<>();
// 存储token详细信息,key: token, value: UserLoginRespVO
private final Map<String, UserLoginRespVO> tokenDetailCache = new ConcurrentHashMap<>();
/**
* 每30分钟清理一次过期token
*/
@Scheduled(fixedRate = 30 * 60 * 1000) // 30分钟,单位毫秒
public void cleanExpiredTokens() {
long currentTime = System.currentTimeMillis() / 1000;
int initialTokenCount = tokenDetailCache.size();
int initialTenantCount = tokenCache.size();
// 清理过期的token详情
tokenDetailCache.entrySet().removeIf(entry ->
entry.getValue() != null && entry.getValue().expDate <= currentTime
);
// 同步清理租户ID映射
tokenCache.entrySet().removeIf(entry -> {
String token = entry.getValue();
UserLoginRespVO tokenInfo = tokenDetailCache.get(token);
return tokenInfo == null || tokenInfo.expDate <= currentTime;
});
int finalTokenCount = tokenDetailCache.size();
int finalTenantCount = tokenCache.size();
int removedTokenCount = initialTokenCount - finalTokenCount;
int removedTenantCount = initialTenantCount - finalTenantCount;
log.info("Token缓存清理完成: 清理前 {} 个token({} 租户), 清理后 {} 个token({} 租户), 移除 {} 个过期token({} 租户)",
initialTokenCount, initialTenantCount,
finalTokenCount, finalTenantCount,
removedTokenCount, removedTenantCount);
}
/**
* 缓存token(基于租户ID)
*/
protected void cacheToken(String tenantId, UserLoginRespVO loginRespVO) {
if (tenantId != null && loginRespVO != null && loginRespVO.token != null) {
// 存储租户ID到token的映射
tokenCache.put(tenantId, loginRespVO.token);
// 存储token详细信息
tokenDetailCache.put(loginRespVO.token, loginRespVO);
log.debug("租户 {} 的Token已缓存: {}", tenantId, loginRespVO.token);
}
}
protected void cacheToken(String tenantId, String token) {
tokenCache.put(tenantId, token);
}
/**
* 根据租户ID获取token
*/
protected String getTokenByTenantId(String tenantId) {
String token = tokenCache.get(tenantId);
if (token != null && isTokenValid(token)) {
log.debug("获取租户 {} 的Token: {}", tenantId, token);
return token;
}
// 如果token过期,移除映射
if (token != null) {
tokenCache.remove(tenantId);
tokenDetailCache.remove(token);
log.debug("租户 {} 的Token已过期,已移除", tenantId);
}
return null;
}
/**
* 根据租户ID获取token详细信息
*/
protected UserLoginRespVO getTokenInfoByTenantId(String tenantId) {
String token = getTokenByTenantId(tenantId);
if (token != null) {
return tokenDetailCache.get(token);
}
return null;
}
/**
* 根据token获取详细信息
*/
protected UserLoginRespVO getTokenInfo(String token) {
UserLoginRespVO tokenInfo = tokenDetailCache.get(token);
if (tokenInfo != null && isTokenValid(tokenInfo)) {
return tokenInfo;
}
// 如果token过期,移除
if (tokenInfo != null) {
removeTokenByValue(token);
}
return null;
}
/**
* 检查租户的token是否有效
*/
protected boolean isTokenValidByTenantId(String tenantId) {
String token = tokenCache.get(tenantId);
boolean isValid = token != null && isTokenValid(token);
log.debug("租户 {} 的Token验证结果: {}", tenantId, isValid ? "有效" : "无效");
return isValid;
}
/**
* 检查token是否有效
*/
protected boolean isTokenValid(String token) {
UserLoginRespVO tokenInfo = tokenDetailCache.get(token);
boolean isValid = tokenInfo != null && isTokenValid(tokenInfo);
log.debug("Token验证结果: {} -> {}", token, isValid ? "有效" : "无效");
return isValid;
}
/**
* 检查token是否有效(内部方法)
*/
private boolean isTokenValid(UserLoginRespVO tokenInfo) {
long currentTime = System.currentTimeMillis() / 1000;
return tokenInfo.expDate > currentTime;
}
/**
* 根据租户ID移除token
*/
protected void removeTokenByTenantId(String tenantId) {
String token = tokenCache.remove(tenantId);
if (token != null) {
tokenDetailCache.remove(token);
log.debug("租户 {} 的Token已移除: {}", tenantId, token);
}
}
/**
* 根据token值移除
*/
protected void removeTokenByValue(String token) {
// 需要找到对应的租户ID
tokenDetailCache.remove(token);
tokenCache.entrySet().removeIf(entry -> entry.getValue().equals(token));
log.debug("Token已移除: {}", token);
}
/**
* 获取所有租户的token数量
*/
protected int getTenantTokenCount() {
return tokenCache.size();
}
/**
* 获取所有token的数量
*/
protected int getTotalTokenCount() {
return tokenDetailCache.size();
}
/**
* 清理所有token
*/
protected void clearAllTokens() {
int tenantCount = tokenCache.size();
int tokenCount = tokenDetailCache.size();
tokenCache.clear();
tokenDetailCache.clear();
log.info("所有Token已清除,共清除 {} 个租户的 {} 个token", tenantCount, tokenCount);
}
/**
* 获取租户ID列表
*/
protected java.util.Set<String> getAllTenantIds() {
return new java.util.HashSet<>(tokenCache.keySet());
}
/**
* 获取所有token信息(调试用)
*/
protected Map<String, String> getTokenCache() {
return new ConcurrentHashMap<>(tokenCache);
}
}

View File

@@ -0,0 +1,194 @@
package cn.iocoder.yudao.module.farm.manager.devices.soil;
import cn.iocoder.yudao.module.farm.manager.devices.conf.DeviceConf;
import cn.iocoder.yudao.module.farm.manager.devices.soil.dto.*;
import cn.iocoder.yudao.module.farm.utils.HttpClientUtils;
import cn.iocoder.yudao.module.farm.utils.HttpClientUtils.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUser;
import static cn.iocoder.yudao.module.farm.enums.ErrorCodeConstants.*;
/**
* @description: 土壤墒情
* @author: binda
* @date: 2025/9/17 10:57
*/
@Slf4j
@Service
public class SoilService extends BaseTokenCacheService {
private final DeviceConf deviceConf;
private final HttpClientUtils httpClientUtils;
public SoilService(DeviceConf deviceConf, HttpClientUtils httpClientUtils) {
this.deviceConf = deviceConf;
this.httpClientUtils = httpClientUtils;
}
private final String SOIL = "soil_";
/**
* 登录获取token
*
* @param req
* @return
*/
public UserLoginRespVO userLogin(UserLoginReqVO req) {
String tenantId = getLoginUser().getTenantId().toString();
String hosts = deviceConf.getFourSituations().getHosts();
String userLoginURL = deviceConf.getFourSituations().getGlobal().getUrl21();
try {
ApiResponse<UserLoginRespVO> response = httpClientUtils.postRequest(hosts, userLoginURL, req, null, UserLoginRespVO.class);
if (response.isSuccess()) {
UserLoginRespVO data = response.getData();
// TODO: 2025/9/17 持久化
cacheToken(SOIL+tenantId, data);
}
return response.getData();
} catch (Exception e) {
log.error("租户 {} 登录失败: {}", tenantId, e.getMessage(), e);
throw exception(DEVICE_INFO_NOT_EXISTS_FOR_USER_LOGIN);
}
}
/**
* 验证租户token有效性
*/
public boolean validateUserToken(String tenantId) {
log.debug("验证租户 {} 的Token", tenantId);
return isTokenValidByTenantId(SOIL+tenantId);
}
/**
* 获取用户设备
*/
public List<SysUserDeviceRespVO> getSysUserDevice(SysUserDeviceReqVO req) {
String tenantId = getLoginUser().getTenantId().toString();
String token = getTokenByTenantId(tenantId);
String hosts = deviceConf.getFourSituations().getHosts();
String getSysUserDeviceURL = deviceConf.getFourSituations().getGlobal().getUrl26();
Map<String, String> headers = new HashMap<>();
headers.put("token", token);
Map<String, String> params = new HashMap<>();
params.put("groupId", StringUtils.defaultString(req.getGroupId()));
params.put("deviceType", StringUtils.defaultString(req.getDeviceType()));
try {
ApiResponse<List<SysUserDeviceRespVO>> response = httpClientUtils.getRequestAsList(hosts, getSysUserDeviceURL, params, headers, SysUserDeviceRespVO.class);
if (!response.isSuccess()) {
throw exception(DEVICE_INFO_NOT_EXISTS_FOR_USER_DEVICE);
}
return response.getData();
} catch (Exception e) {
e.printStackTrace();
throw exception(DEVICE_INFO_NOT_EXISTS_FOR_USER_DEVICE);
}
}
/**
* 获取设备实时数据
*/
public List<RealTimeDataRespVO> getRealTimeData(RealTimeDataReqVO req) {
String tenantId = getLoginUser().getTenantId().toString();
String token = getTokenByTenantId(tenantId);
String hosts = deviceConf.getFourSituations().getHosts();
String getSysUserDeviceURL = deviceConf.getFourSituations().getGlobal().getUrl27();
Map<String, String> headers = new HashMap<>();
headers.put("token", token);
Map<String, String> params = new HashMap<>();
params.put("deviceAddrs", req.getDeviceAddrs());
try {
ApiResponse<List<RealTimeDataRespVO>> response = httpClientUtils.getRequestAsList(hosts, getSysUserDeviceURL, params, headers, RealTimeDataRespVO.class);
if (!response.isSuccess()) {
throw exception(DEVICE_INFO_NOT_EXISTS_FOR_REAL_TIME_DATA);
}
return response.getData();
} catch (Exception e) {
e.printStackTrace();
throw exception(DEVICE_INFO_NOT_EXISTS_FOR_REAL_TIME_DATA);
}
}
/**
* 设备报请通知记录
*/
public List<AlarmNoticeRecordRespVO> getAlarmNoticeRecord(AlarmNoticeRecordReqVO req) {
String tenantId = getLoginUser().getTenantId().toString();
String token = getTokenByTenantId(tenantId);
String hosts = deviceConf.getFourSituations().getHosts();
String getAlarmNoticeRecord = deviceConf.getFourSituations().getGlobal().getUrl24();
Map<String, String> headers = new HashMap<>();
headers.put("token", token);
Map<String, String> params = req.toFieldValueMap();
try {
ApiResponse<List<AlarmNoticeRecordRespVO>> response = httpClientUtils.getRequestAsList(hosts, getAlarmNoticeRecord, params, headers, AlarmNoticeRecordRespVO.class);
if (!response.isSuccess()) {
throw exception(DEVICE_INFO_NOT_EXISTS_FOR_ALARM_NOTICE_RECORD);
}
return response.getData();
} catch (Exception e) {
e.printStackTrace();
throw exception(DEVICE_INFO_NOT_EXISTS_FOR_ALARM_NOTICE_RECORD);
}
}
/**
* 墒情设备, 获取设备全部信息
*/
public List<DeviceAllInfoRespVO> getDeviceAllInfo(DeviceAllInfoReqVO req) {
return null;
}
/**
* 墒情设备, 更新设备信息
*/
public Boolean updateDevice(UpdateDeviceReqVO req) {
return false;
}
/**
* 墒情设备, 更新节点信息
*/
public Boolean updateNodeInfo(UpdateNodeInfoReqVO req) {
return false;
}
/**
* 墒情设备, 获取历史数据
*/
public HistoryDataListPageRespVO getHistoryDataList(HistoryDataListPageReqVO req) {
return null;
}
}

View File

@@ -0,0 +1,53 @@
package cn.iocoder.yudao.module.farm.manager.devices.soil.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.HashMap;
import java.util.Map;
/**
* @description: 设备报请通知记录 请求实体
* @author: binda
* @date: 2025/9/17 19:51
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AlarmNoticeRecordReqVO {
/**
* 开始时间(yyyy-MM-dd HH:mm:ss)
* tips: 必选
*/
private String beginTime;
/**
* 结束时间(yyyy-MM-dd HH:mm:ss)
* tips: 必选
*/
private String endTime;
/**
* 设备地址码
* tips: 必选
*/
private String deviceAddr;
/**
* 节点ID (多个节点使用","分隔)
* tips: 非必选
*/
private String factorIds;
/**
* 通知类型 0 邮件 1 短信
*/
private String noticeType;
public Map<String, String> toFieldValueMap() {
Map<String, String> map = new HashMap<>();
map.put("beginTime", this.getBeginTime());
map.put("endTime", this.getEndTime());
map.put("deviceAddr", this.getDeviceAddr());
map.put("factorIds", this.getFactorIds());
map.put("noticeType", this.getNoticeType());
return map;
}
}

View File

@@ -0,0 +1,54 @@
package cn.iocoder.yudao.module.farm.manager.devices.soil.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @description: 设备报请通知记录 响应实体
* @author: binda
* @date: 2025/9/17 19:51
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AlarmNoticeRecordRespVO {
/**
* 通知记录表 id
*/
private Integer noticeID;
/**
* 类型 0 邮件 1 短信
*/
private Integer noticeIType;
/**
* 设备地址
*/
private String deviceAddr;
/**
* 节点ID
*/
private String factorId;
/**
* 节点名称
*/
private String factorName;
/**
* 联系人
*/
private String contact;
/**
* 通知内容
*/
private String noticeIContent;
/**
* 备注
*/
private String remark;
/**
* 创建时间
*/
private String createTime;
}

View File

@@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.farm.manager.devices.soil.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @description: 墒情设备, 设备全部信息 请求实体
* @author: binda
* @date: 2025/9/17 20:13
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DeviceAllInfoReqVO {
/**
* 必选
* 设备地址(多个用英文逗号分隔,最多同时获取 5 个设备信息)
*/
private String deviceStr;
}

View File

@@ -0,0 +1,154 @@
package cn.iocoder.yudao.module.farm.manager.devices.soil.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* @description: 墒情设备, 设备全部信息 响应实体
* @author: binda
* @date: 2025/9/17 20:13
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DeviceAllInfoRespVO {
/** 设备地址 */
private String deviceAddr;
/** 设备名称 */
private String deviceName;
/** 经度 */
private double lng;
/** 纬度 */
private double lat;
/** 数据保存间隔(分钟) */
private Integer saveDataInterval;
/** 离线判断间隔(分钟) */
private Integer offlineInterval;
/** 节点信息列表 */
private List<NodeInfo> nodeInfoList;
/** 遥调信息列表 */
private List<RegulatingInfo> regulatingInfoList;
/**
* 节点信息内部类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class NodeInfo {
/** 节点编号 */
private Integer nodeId;
/** 设备地址 */
private String deviceAddr;
/** 节点名称 */
private String nodeName;
/** 节点是否开启 0:关闭 1:开启 */
private Integer enable;
/** 0 常规 ;1 雨量 ;2 蒸发量 */
private Integer nodeMold;
/**
* 节点类型:
* 1: 模拟量 1 使能模拟量 2 使能
* 2: 模拟量 1 使能模拟量 2 禁用
* 3: 模拟量 1 禁用模拟量 2 使能
* 4: 浮点型设备
* 5: 开关量型设备
* 6: 32 位有符号整形
* 7: 32 位无符号整形
* 8: 遥调设备
*/
private Integer nodeType;
/** 优先级 */
private Integer priority;
/** 小数位 */
private Integer digits;
/** 模拟量 1 名称 */
private String temName;
/** 模拟量 1 单位 */
private String temUnit;
/** 模拟量 1 系数 */
private double temRatio;
/** 模拟量 1 偏差 */
private double temOffset;
/** 模拟量 1 上限 */
private double temUpperLimit;
/** 模拟量 1 下限 */
private double temLowerLimit;
/** 模拟量 2 名称 */
private String humName;
/** 模拟量 2 单位 */
private String humUnit;
/** 模拟量 2 系数 */
private double humRatio;
/** 模拟量 2 偏差 */
private double humOffset;
/** 模拟量 2 上限 */
private double humUpperLimit;
/** 模拟量 2 下限 */
private double humLowerLimit;
/** 开关量闭合显示内容 */
private String switchOnContent;
/** 开关量断开显示内容 */
private String switchOffContent;
/** 开关量报警类型(0 不报警 1 闭合报警 2 断开报警) */
private Integer switchAlarmType;
}
/**
* 遥调信息内部类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class RegulatingInfo {
/** 遥调编号(8 位或 15 位) */
private String regularId;
/** 节点编号 */
private Integer nodeId;
/** 设备地址 */
private String deviceAddr;
/** 遥调档位值 */
private double regularValue;
/** 遥调档位内容 */
private String regularText;
/** 是否开启报警 0:不开启 1:开启 */
private Integer alarmEnable;
}
}

View File

@@ -0,0 +1,46 @@
package cn.iocoder.yudao.module.farm.manager.devices.soil.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @description: 墒情设备, 获取历史数据 请求实体
* @author: binda
* @date: 2025/9/17 20:53
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class HistoryDataListPageReqVO {
/**
* 必选
* 开始时间 (时间戳)
*/
private String beginTime;
/**
* 必选
* 结束时间 (时间戳)
*/
private String endTime;
/**
* 必选
* 设备地址
*/
private String deviceAddr;
/**
* 非必选
* 节点ID
*/
private String nodeId;
/**
* 必选
* 页码
*/
private Integer pages;
/**
* 必选
* 每页数量
*/
private Integer limit;
}

View File

@@ -0,0 +1,62 @@
package cn.iocoder.yudao.module.farm.manager.devices.soil.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* @description: 墒情设备, 获取历史数据 响应实体
* @author: binda
* @date: 2025/9/17 20:53
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class HistoryDataListPageRespVO {
/**
* 页码,每页数量,总页数,总条数
*/
private Integer pages;
private Integer limit;
private Integer totalPages;
private Integer total;
private List<Row> rows;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Row{
/**
* 记录表 id
*/
private Integer historyId;
/**
* 节点编号
*/
private Integer nodeId;
/**
* 设备地址
*/
private String deviceAddress;
/**
* 模拟量 1
*/
private String temStr;
private Double temValue;
/**
* 模拟狼 2
*/
private String humStr;
private Double humValue;
/**
* 是否是报警数据 0:正常 1:报警
*/
private Integer alarmStatus;
/**
* 保存时间(时间戳)
*/
private Long recordTime;
}
}

View File

@@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.farm.manager.devices.soil.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* @description: 设备实时数据 请求实体
* @author: binda
* @date: 2025/9/17 19:02
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RealTimeDataReqVO {
/**
* 设备地址 非必定
* tips: 多个用英文逗号分隔,空为获取当前用户下的所有设备
*/
private String deviceAddrs;
}

View File

@@ -0,0 +1,68 @@
package cn.iocoder.yudao.module.farm.manager.devices.soil.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* @description: 设备实时数据 相应试题
* @author: binda
* @date: 2025/9/17 19:01
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RealTimeDataRespVO {
/**
* 设备地址
*/
private String deviceAddr;
/**
* 设备名称
*/
private String deviceName;
/**
* 经度
*/
private Double lat;
/**
* 维度
*/
private Double lng;
/**
* 设备状态
*/
private String status;
/**
* 设备类型
*/
private String deviceType;
private List<RealTimeData> data;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class RealTimeData {
// 节点编号
private Integer nodeId;
// 节点名称
private String nodeName;
// 节点类型
private Integer nodeType;
// 模拟量1 名称,单位,原值,显示值,报警等级
private String temName;
private String temUnit;
private Double temValue;
private String temValueStr;
private Integer temAlarmStatus;
// 模拟量2 名称,单位,原值,显示值,报警等级
private String humName;
private String humUnit;
private String humValue;
private String humValueStr;
private String humAlarmStatus;
}
}

View File

@@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.farm.manager.devices.soil.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @description: 获取用户设备 请求实体
* @author: binda
* @date: 2025/9/17 15:28
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SysUserDeviceReqVO {
/**
* 区域ID 非必填
*/
private String groupId;
/**
* 设备类型 非必填
*/
private String deviceType;
}

View File

@@ -0,0 +1,45 @@
package cn.iocoder.yudao.module.farm.manager.devices.soil.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @description: 获取用户设备 响应实体
* @author: binda
* @date: 2025/9/17 15:31
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SysUserDeviceRespVO {
/**
* 设备地址
*/
public String deviceAddr;
/**
* 区域ID
*/
public String groupId;
/**
* 设备类型
*/
public String deviceType;
/**
* 设备名称
*/
public String deviceName;
/**
* 设备的经度
*/
public String deviceLng;
/**
* 设备的维度
*/
public String deviceLat;
/**
* 设备iccid
*/
public String deviceIccId;
}

View File

@@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.farm.manager.devices.soil.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @description: 墒情设备, 更新设备信息 请求实体
* @author: binda
* @date: 2025/9/17 20:29
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UpdateDeviceReqVO {
/**
* 设备地址
*/
private String deviceAddr;
private String deviceName;
private Double lat;
private Double lng;
/**
* 离线判断间隔
*/
private Integer offlineInterval;
/**
* 数据保存间隔
*/
private Integer saveDataInterval;
}

View File

@@ -0,0 +1,81 @@
package cn.iocoder.yudao.module.farm.manager.devices.soil.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @description: 墒情设备, 更新节点信息 请求实体
* @author: binda
* @date: 2025/9/17 20:34
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UpdateNodeInfoReqVO {
/**
* 必选
* 设备地址
*/
private String deviceAddr;
/**
* 必选
* 节点编号
*/
private String nodeId;
private String nodeName;
/**
* 参考 cn.iocoder.yudao.module.farm.manager.devices.commons.DeviceConst: 四情-墒情 设备实时数据 - 节点类型
*/
private Integer nodeType;
/**
* 0 常规 ;1 雨量 ;2 蒸发量
*/
private Integer nodeMold;
/**
* 小数位数
*/
private Integer digits;
/**
* 节点是否开启 0:关闭 1:开启
*/
private Integer enable;
/**
* 优先级
*/
private Integer priority;
/**
* 开关量报警类型 (0 不报警 1 闭合报警 2 断开报警)
*/
private Integer switchAlarmType;
/**
* 开关量闭合显示内容
*/
private String switchOnContent;
/**
* 开关量断开显示内容
*/
private String switchOffContent;
/**
* 模拟量2 : 名称,单位,上限,下限,偏差,系数
*/
private String humName;
private String humUnit;
private Double humUpperLimit;
private Double humLowerLimit;
private Double humOffset;
private Double humRatio;
/**
* 模拟量2 : 名称,单位,上限,下限,偏差,系数
*/
private String temName;
private String temUnit;
private Double temUpperLimit;
private Double temLowerLimit;
private Double temOffset;
private Double temRatio;
}

View File

@@ -0,0 +1,18 @@
package cn.iocoder.yudao.module.farm.manager.devices.soil.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @description: 登录请求实体
* @author: binda
* @date: 2025/9/17 13:46
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserLoginReqVO {
public String loginName;
public String loginPwd;
}

View File

@@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.farm.manager.devices.soil.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @description: 登录响应实体
* @author: binda
* @date: 2025/9/17 13:46
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserLoginRespVO {
/**
* 登录标识
*/
public String loginSign;
/**
* token 生成时间
*/
public long currDate;
/**
* 过期时间
*/
public long expDate;
/**
* 令牌
*/
public String token;
}

View File

@@ -0,0 +1,90 @@
package cn.iocoder.yudao.module.farm.manager.devices.wf;
import cn.iocoder.yudao.module.farm.manager.devices.conf.DeviceConf;
import cn.iocoder.yudao.module.farm.manager.devices.soil.BaseTokenCacheService;
import cn.iocoder.yudao.module.farm.manager.devices.wf.dto.*;
import cn.iocoder.yudao.module.farm.utils.HttpClientUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUser;
import static cn.iocoder.yudao.module.farm.enums.ErrorCodeConstants.DEVICE_INFO_NOT_EXISTS_FOR_WF_LOGIN;
/**
* @description: 水肥机
* @author: binda
* @date: 2025/9/17 10:59
*/
@Slf4j
@Service
public class WaterFertilizerService extends BaseTokenCacheService {
private final DeviceConf deviceConf;
private final HttpClientUtils httpClientUtils;
public WaterFertilizerService(DeviceConf deviceConf, HttpClientUtils httpClientUtils) {
this.deviceConf = deviceConf;
this.httpClientUtils = httpClientUtils;
}
private final String Authorization = "Authorization";
private final String Bearer = "Bearer ";
private final String WF = "wf_";
/**
* 获取公钥
*/
public LoginRespVO login() {
String tenantId = getLoginUser().getTenantId().toString();
String hosts = deviceConf.getWaterAndFertilizer().getHosts();
String loginURL = deviceConf.getWaterAndFertilizer().getUrl1();
LoginReqVO req = new LoginReqVO(deviceConf.getWaterAndFertilizer().getUsername(),
deviceConf.getWaterAndFertilizer().getPassword());
try {
HttpClientUtils.ApiResponse<LoginRespVO> response = httpClientUtils.postRequest(hosts, loginURL, req, null, LoginRespVO.class);
if (!response.isSuccess()) {
throw exception(DEVICE_INFO_NOT_EXISTS_FOR_WF_LOGIN);
}
LoginRespVO data = response.getData();
cacheToken(WF+tenantId, data.getToken());
return response.getData();
} catch (Exception e) {
e.printStackTrace();
throw exception(DEVICE_INFO_NOT_EXISTS_FOR_WF_LOGIN);
}
}
/**
* 获取设备列表
* @param userCode 用户名
* @return
*/
public DevicePageRespVO getDeviceList(String userCode) {
return null;
}
/**
* 获取设备实时状态
* @param deviceId 设备ID
* @return
*/
public RealTimeDeviceDataRespVO getRealTimeDeviceData(String deviceId) {
return null;
}
/**
* 更改设备属性
* @param deviceId 设备ID
* @param paramStr 更改值: true: 打开,false: 关闭
* @return
*/
public UpdateDeviceStatusRespVO updateDeviceStatus(String deviceId, String paramStr) {
return null;
}
}

View File

@@ -0,0 +1,51 @@
package cn.iocoder.yudao.module.farm.manager.devices.wf.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
import java.util.List;
/**
* @description: 获取设备列表 分页响应实体
* @author: binda
* @date: 2025/9/18 14:30
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DevicePageRespVO {
private Integer code;
private String msg;
private Integer total;
private List<Row> rows;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Row{
private Long deviceId;
private Long deptId;
private String productCode;
private String productName;
private String modelCode;
private String deviceCode;
private String code; // 设备编号
private String name;
private String farmland;
private String modelName;
private String userCode;
private Double lng;
private Double lat;
private Long farmlandId;
private String delFlag;
private String loginIp;
private Date loginDate;
private String createBy;
private Date createTime;
private String updateBy;
private Date updateTime;
private String remark;
}
}

View File

@@ -0,0 +1,18 @@
package cn.iocoder.yudao.module.farm.manager.devices.wf.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @description: 获取公钥 请求实体
* @author: binda
* @date: 2025/9/18 14:19
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginReqVO {
private String username;
private String password;
}

View File

@@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.farm.manager.devices.wf.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @description: 获取公钥 响应实体
* @author: binda
* @date: 2025/9/18 14:26
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginRespVO {
private Integer code;
private String msg;
private String token;
}

View File

@@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.farm.manager.devices.wf.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* @description: 设备实时数据
* @author: binda
* @date: 2025/9/18 14:38
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RealTimeDeviceDataRespVO {
private String deviceId;
private String name;
private String stateText; // 是否在线 中文
private String stateValue; // 是否在线 编码 online
private List<ControlProperty> controlProperties;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class ControlProperty {
private String id; // 开关标识
private String name;
private String operateType; // 设备类型
private String operateValue;
private String value;
private String formatValue;
}
}

View File

@@ -0,0 +1,18 @@
package cn.iocoder.yudao.module.farm.manager.devices.wf.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @description: 更改设备属性 响应实体
* @author: binda
* @date: 2025/9/18 14:45
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UpdateDeviceStatusRespVO {
private Integer code;
private String msg;
}

View File

@@ -352,6 +352,9 @@ public class BaseInfoServiceImpl implements BaseInfoService {
@Override @Override
public Double getBaseTotalArea(String tenantId) { public Double getBaseTotalArea(String tenantId) {
BigDecimal d = baseInfoMapper.getTotalBaseArea(null, Long.valueOf(tenantId)); BigDecimal d = baseInfoMapper.getTotalBaseArea(null, Long.valueOf(tenantId));
if (d == null) {
return 0.00;
}
return d.doubleValue(); return d.doubleValue();
} }

View File

@@ -61,4 +61,7 @@ public interface DeviceClassifyService {
List<DeviceClassifyDO> getDeviceClassifyList(); List<DeviceClassifyDO> getDeviceClassifyList();
List<DeviceTypeRespVO> getDeviceTypeList();
List<DeviceModelRespVO> getDeviceModelList(String deviceTypeId);
} }

View File

@@ -6,6 +6,8 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.farm.controller.admin.deviceclassify.vo.DeviceClassifyPageReqVO; import cn.iocoder.yudao.module.farm.controller.admin.deviceclassify.vo.DeviceClassifyPageReqVO;
import cn.iocoder.yudao.module.farm.controller.admin.deviceclassify.vo.DeviceClassifySaveReqVO; import cn.iocoder.yudao.module.farm.controller.admin.deviceclassify.vo.DeviceClassifySaveReqVO;
import cn.iocoder.yudao.module.farm.controller.admin.deviceclassify.vo.DeviceModelRespVO;
import cn.iocoder.yudao.module.farm.controller.admin.deviceclassify.vo.DeviceTypeRespVO;
import cn.iocoder.yudao.module.farm.dal.dataobject.deviceclassify.DeviceClassifyDO; import cn.iocoder.yudao.module.farm.dal.dataobject.deviceclassify.DeviceClassifyDO;
import cn.iocoder.yudao.module.farm.dal.mysql.deviceclassify.DeviceClassifyMapper; import cn.iocoder.yudao.module.farm.dal.mysql.deviceclassify.DeviceClassifyMapper;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
@@ -15,6 +17,7 @@ import org.springframework.validation.annotation.Validated;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUser; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUser;
@@ -108,6 +111,41 @@ public class DeviceClassifyServiceImpl implements DeviceClassifyService {
return deviceClassifyDOS; return deviceClassifyDOS;
} }
@Override
public List<DeviceTypeRespVO> getDeviceTypeList() {
List<DeviceClassifyDO> list = this.getDeviceClassifyList();
return list.stream()
.filter(device -> device.getParentId() != null && device.getParentId() == 0L)
.map(device -> new DeviceTypeRespVO(
String.valueOf(device.getId()),
device.getName(),
device.getImage(),
device.getImageList()
))
.collect(Collectors.toList());
}
@Override
public List<DeviceModelRespVO> getDeviceModelList(String deviceTypeId) {
List<DeviceClassifyDO> list = this.getDeviceClassifyList();
Long parentId = Long.valueOf(deviceTypeId);
DeviceClassifyDO parentDevice = list.stream()
.filter(device -> device.getId().equals(parentId))
.findFirst()
.orElseThrow(() -> {throw exception(DEVICE_TYPE_ERROR);});
return list.stream()
.filter(device -> device.getParentId() != null && device.getParentId().equals(parentId))
.map(childDevice -> new DeviceModelRespVO(
String.valueOf(childDevice.getId()),
parentDevice.getName() + "/" + childDevice.getName()
))
.collect(Collectors.toList());
}
/** /**
* 处理图片集合转换 * 处理图片集合转换
* 支持从 imageList 或 image 字段获取图片,优先使用 imageList * 支持从 imageList 或 image 字段获取图片,优先使用 imageList

View File

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.farm.service.deviceinfo;
import cn.iocoder.yudao.module.farm.controller.admin.deviceinfo.vo.DeviceInfoPageReqVO; import cn.iocoder.yudao.module.farm.controller.admin.deviceinfo.vo.DeviceInfoPageReqVO;
import cn.iocoder.yudao.module.farm.controller.admin.deviceinfo.vo.DeviceInfoSaveReqVO; import cn.iocoder.yudao.module.farm.controller.admin.deviceinfo.vo.DeviceInfoSaveReqVO;
import cn.iocoder.yudao.module.farm.controller.admin.deviceinfo.vo.v2.DeviceStatisticsRespVO;
import cn.iocoder.yudao.module.farm.dal.dataobject.deviceinfo.DeviceInfoDO; import cn.iocoder.yudao.module.farm.dal.dataobject.deviceinfo.DeviceInfoDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageParam;
@@ -61,4 +62,10 @@ public interface DeviceInfoService {
*/ */
PageResult<DeviceInfoDO> getDeviceInfoPage(DeviceInfoPageReqVO pageReqVO); PageResult<DeviceInfoDO> getDeviceInfoPage(DeviceInfoPageReqVO pageReqVO);
/**
* 设备统计
* @param baseId
* @return
*/
DeviceStatisticsRespVO getStatisticsInfo(String baseId);
} }

View File

@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.IdUtil;
import cn.iocoder.yudao.module.farm.controller.admin.deviceinfo.vo.DeviceInfoPageReqVO; import cn.iocoder.yudao.module.farm.controller.admin.deviceinfo.vo.DeviceInfoPageReqVO;
import cn.iocoder.yudao.module.farm.controller.admin.deviceinfo.vo.DeviceInfoSaveReqVO; import cn.iocoder.yudao.module.farm.controller.admin.deviceinfo.vo.DeviceInfoSaveReqVO;
import cn.iocoder.yudao.module.farm.controller.admin.deviceinfo.vo.v2.DeviceStatisticsRespVO;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
@@ -107,4 +108,9 @@ private void validateDeviceInfoExists(String id) {
return pageResult; return pageResult;
} }
@Override
public DeviceStatisticsRespVO getStatisticsInfo(String baseId) {
return deviceInfoMapper.getStatisticsInfo(baseId,String.valueOf(getLoginUser().getTenantId()));
}
} }

View File

@@ -41,7 +41,7 @@ public class DeviceStatusServiceImpl implements DeviceStatusService {
// 构建更新对象 // 构建更新对象
DeviceInfoDO updateObj = new DeviceInfoDO(); DeviceInfoDO updateObj = new DeviceInfoDO();
updateObj.setId(device.getId()); updateObj.setId(device.getId());
updateObj.setState(state); updateObj.setDeviceState(state);
updateObj.setLastMessageTime(messageTime); updateObj.setLastMessageTime(messageTime);
// 根据状态设置相应的时间字段 // 根据状态设置相应的时间字段
@@ -84,8 +84,8 @@ public class DeviceStatusServiceImpl implements DeviceStatusService {
updateObj.setLastMessageTime(messageTime); updateObj.setLastMessageTime(messageTime);
// 如果设备当前是离线状态,收到消息后自动上线 // 如果设备当前是离线状态,收到消息后自动上线
if (DeviceStateEnum.isOffline(device.getState()) || device.getState() == null) { if (DeviceStateEnum.isOffline(device.getDeviceState()) || device.getDeviceState() == null) {
updateObj.setState(DeviceStateEnum.ONLINE.getState()); updateObj.setDeviceState(DeviceStateEnum.ONLINE.getState());
updateObj.setOnlineTime(messageTime); updateObj.setOnlineTime(messageTime);
// 如果是首次激活,设置激活时间 // 如果是首次激活,设置激活时间
if (device.getActiveTime() == null) { if (device.getActiveTime() == null) {
@@ -99,16 +99,16 @@ public class DeviceStatusServiceImpl implements DeviceStatusService {
@Override @Override
public boolean isDeviceOnline(String deviceUniqueId) { public boolean isDeviceOnline(String deviceUniqueId) {
DeviceInfoDO device = deviceInfoMapper.selectOne(DeviceInfoDO::getDeviceUniqueId, deviceUniqueId); DeviceInfoDO device = deviceInfoMapper.selectOne(DeviceInfoDO::getDeviceUniqueId, deviceUniqueId);
return device != null && DeviceStateEnum.isOnline(device.getState()); return device != null && DeviceStateEnum.isOnline(device.getDeviceState());
} }
@Override @Override
public DeviceStateEnum getDeviceState(String deviceUniqueId) { public DeviceStateEnum getDeviceState(String deviceUniqueId) {
DeviceInfoDO device = deviceInfoMapper.selectOne(DeviceInfoDO::getDeviceUniqueId, deviceUniqueId); DeviceInfoDO device = deviceInfoMapper.selectOne(DeviceInfoDO::getDeviceUniqueId, deviceUniqueId);
if (device == null || device.getState() == null) { if (device == null || device.getDeviceState() == null) {
return DeviceStateEnum.INACTIVE; return DeviceStateEnum.INACTIVE;
} }
return DeviceStateEnum.valueOf(device.getState()); return DeviceStateEnum.valueOf(device.getDeviceState());
} }
@Override @Override
@@ -140,7 +140,7 @@ public class DeviceStatusServiceImpl implements DeviceStatusService {
for (DeviceInfoDO device : timeoutDevices) { for (DeviceInfoDO device : timeoutDevices) {
// 只处理当前在线的设备 // 只处理当前在线的设备
if (DeviceStateEnum.isOnline(device.getState())) { if (DeviceStateEnum.isOnline(device.getDeviceState())) {
deviceOffline(device.getDeviceUniqueId()); deviceOffline(device.getDeviceUniqueId());
count++; count++;
log.info("设备超时离线:{}", device.getDeviceUniqueId()); log.info("设备超时离线:{}", device.getDeviceUniqueId());

View File

@@ -244,6 +244,9 @@ public class HomeServiceImpl implements HomeService {
@Override @Override
public List<BaseNameVO> getBaseNameList() { public List<BaseNameVO> getBaseNameList() {
List<BaseInfoDO> baseNameList = baseInfoService.getBaseInfos(); List<BaseInfoDO> baseNameList = baseInfoService.getBaseInfos();
if (ObjectUtils.isEmpty(baseNameList)) {
return new ArrayList<>();
}
return baseNameList.stream().map(e -> new BaseNameVO(e.getId(), e.getName())).collect(Collectors.toList()); return baseNameList.stream().map(e -> new BaseNameVO(e.getId(), e.getName())).collect(Collectors.toList());
} }

View File

@@ -0,0 +1,215 @@
package cn.iocoder.yudao.module.farm.utils;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.List;
import java.util.Map;
/**
* @description: http client
* @author: binda
* @date: 2025/9/16 16:46
*/
@Component
public class HttpClientUtils {
private final HttpClient httpClient;
private final ObjectMapper objectMapper;
public HttpClientUtils(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
this.httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
}
private String getBaseUrl(String hosts) {
if (hosts.startsWith("http")) {
return hosts;
}
return "https://" + hosts;
}
// 通用GET请求 - 返回原始JsonNode,让调用方自己解析
public JsonNode getRequestRaw(String hosts,
String path,
Map<String, String> params,
Map<String, String> headers) throws Exception {
String queryString = buildQueryString(params);
HttpRequest request = buildRequest(hosts, path + queryString, "GET", null, headers);
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
return objectMapper.readTree(response.body());
} else {
throw new RuntimeException("HTTP request failed with status: " + response.statusCode());
}
}
// 通用GET请求 - 自动解析为指定类型
public <T> ApiResponse<T> getRequest(String hosts,
String path,
Map<String, String> params,
Map<String, String> headers,
Class<T> dataType) throws Exception {
JsonNode responseJson = getRequestRaw(hosts, path, params, headers);
return parseResponse(responseJson, dataType);
}
// 通用GET请求 - 自动解析为List类型
public <T> ApiResponse<List<T>> getRequestAsList(String hosts,
String path,
Map<String, String> params,
Map<String, String> headers,
Class<T> itemType) throws Exception {
JsonNode responseJson = getRequestRaw(hosts, path, params, headers);
return parseResponseAsList(responseJson, itemType);
}
// 通用POST请求 - 返回原始JsonNode
public JsonNode postRequestRaw(String hosts,
String path,
Object requestBody,
Map<String, String> headers) throws Exception {
String requestBodyJson = objectMapper.writeValueAsString(requestBody);
HttpRequest request = buildRequest(hosts, path, "POST", requestBodyJson, headers);
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
return objectMapper.readTree(response.body());
} else {
throw new RuntimeException("HTTP request failed with status: " + response.statusCode());
}
}
// 通用POST请求 - 自动解析为指定类型
public <T> ApiResponse<T> postRequest(String hosts,
String path,
Object requestBody,
Map<String, String> headers,
Class<T> dataType) throws Exception {
JsonNode responseJson = postRequestRaw(hosts, path, requestBody, headers);
return parseResponse(responseJson, dataType);
}
// 通用POST请求 - 自动解析为List类型
public <T> ApiResponse<List<T>> postRequestAsList(String hosts,
String path,
Object requestBody,
Map<String, String> headers,
Class<T> itemType) throws Exception {
JsonNode responseJson = postRequestRaw(hosts, path, requestBody, headers);
return parseResponseAsList(responseJson, itemType);
}
// 构建查询字符串
private String buildQueryString(Map<String, String> params) throws UnsupportedEncodingException {
if (params == null || params.isEmpty()) {
return "";
}
StringBuilder queryBuilder = new StringBuilder("?");
for (Map.Entry<String, String> entry : params.entrySet()) {
if (queryBuilder.length() > 1) {
queryBuilder.append("&");
}
queryBuilder.append(entry.getKey())
.append("=")
.append(java.net.URLEncoder.encode(entry.getValue(), "UTF-8"));
}
return queryBuilder.toString();
}
// 构建HTTP请求
private HttpRequest buildRequest(String hosts,
String path,
String method,
String requestBody,
Map<String, String> headers) throws Exception {
HttpRequest.Builder builder = HttpRequest.newBuilder()
.uri(URI.create(this.getBaseUrl(hosts) + "/" + path))
.header("Content-Type", "application/json")
.timeout(Duration.ofSeconds(10));
if ("GET".equals(method)) {
builder.GET();
} else if ("POST".equals(method)) {
builder.POST(HttpRequest.BodyPublishers.ofString(requestBody));
}
if (headers != null) {
for (Map.Entry<String, String> entry : headers.entrySet()) {
builder.header(entry.getKey(), entry.getValue());
}
}
return builder.build();
}
// 解析响应为单个对象
private <T> ApiResponse<T> parseResponse(JsonNode responseJson, Class<T> dataType) {
String code = responseJson.get("code").asText();
String message = responseJson.get("message").asText();
JsonNode dataNode = responseJson.get("data");
T data = null;
if (dataNode != null && !dataNode.isNull()) {
data = objectMapper.convertValue(dataNode, dataType);
}
return new ApiResponse<>(code, message, data);
}
// 解析响应为List
private <T> ApiResponse<List<T>> parseResponseAsList(JsonNode responseJson, Class<T> itemType) {
String code = responseJson.get("code").asText();
String message = responseJson.get("message").asText();
JsonNode dataNode = responseJson.get("data");
List<T> data = null;
if (dataNode != null && !dataNode.isNull() && dataNode.isArray()) {
JavaType listType = objectMapper.getTypeFactory().constructCollectionType(List.class, itemType);
data = objectMapper.convertValue(dataNode, listType);
}
return new ApiResponse<>(code, message, data);
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class ApiResponse<T> {
private String code;
private String message;
private T data;
public boolean isSuccess() {
return "1000".equals(code);
}
}
}

View File

@@ -9,4 +9,24 @@
文档可见:https://www.iocoder.cn/MyBatis/x-plugins/ 文档可见:https://www.iocoder.cn/MyBatis/x-plugins/
--> -->
<select id="getStatisticsInfo"
resultType="cn.iocoder.yudao.module.farm.controller.admin.deviceinfo.vo.v2.DeviceStatisticsRespVO">
SELECT
COALESCE(COUNT(*), 0) AS deviceTotal,
COALESCE(SUM(CASE WHEN device_state = 1 THEN 1 ELSE 0 END), 0) AS deviceOnline,
COALESCE(SUM(CASE WHEN device_state = 2 THEN 1 ELSE 0 END), 0) AS deviceOffline,
COALESCE(SUM(CASE WHEN device_state = 4 THEN 1 ELSE 0 END), 0) AS deviceWarning
FROM farm_device_info
<where>
deleted = b'0'
<if test="baseId != null and baseId != ''">
AND base_id = #{baseId}
</if>
<if test="tenantId != null and tenantId != ''">
AND tenant_id = #{tenantId}
</if>
</where>
</select>
</mapper> </mapper>

View File

@@ -344,3 +344,36 @@ spring:
wms: wms:
hosts: 47.111.15.9:8074/prod-api hosts: 47.111.15.9:8074/prod-api
url_1: getTenantByMobile url_1: getTenantByMobile
# 万懋设备管理
device-manager:
# 四情 (文档来源: 物连网农业四情采集api协议.pdf 版本:v2.0.3 日期:2022.08.16)
four-situations:
hosts: http://farm.0531yun.cn/#/ # http://farm.0531yun.cn/#/
username: cq250904jxtr
password: cq250904jxtr
global:
url-2-1: /api/v2.0/entrance/user/userLogin #用户登录 POST
url-2-2: /api/v2.0/entrance/user/getUser #获取登录用户信息
url-2-3: /api/v2.0/entrance/user/getChildUser #获取子用户信息
url-2-4: /api/v2.0/entrance/device/getAlarmNoticeRecord #设备报请通知记录 GET
url-2-5: /api/v2.0/entrance/group/getsysUserGroup #获取用户区域
url-2-6: /api/v2.0/entrance/device/getsysUserDevice #获取用户设备 GET
url-2-7: /api/v2.0/entrance/device/getDeviceCamera #批量获取摄像头设备信息
url-2-8: /api/v2.0/entrance/device/getRealTimeData #获取设备实时数据 GET
url-2-9: /api/v2.0/entrance/device/groupBoundDevice #区域绑定设备
# 土壤墒情
soil:
url-4-1: /api/v2.0/soil/device/getDeviceAllInfo # 墒情设备, 获取设备全部信息 GET
url-4-2: /api/v2.0/soil/device/updateDevice # 墒情设备, 更新设备信息 POST
url-4-3: /api/v2.0/soil/device/updateNodeInfo # 墒情设备, 更新节点信息 POST
url-4-4: /api/v2.0/soil/history/getHistoryDataList # 墒情设备, 获取历史数据 GET
# 水费机 (文档参考: 智慧物联 HTTPS通讯协议说明书 版本:1.0.1 日期:2025.06.08)
water-and-fertilizer:
hosts: https://ryy.mythiot.com
username: jishui
password: jishui@888@
url-1: /prod-api/login # 获取公钥 POST
url-2: /prod-api/agriculture/deviceMain/list # 获取设备列表 GET
url-3: /prod-api/agriculture/deviceMain/getDeviceAllPropertyAndDataSimple/{0} # 获取设备实时状态 GET {0}: deviceId
url-4: /prod-api/agriculture/deviceMain/updateDeviceStatus # 更改设备属性 POST