feat (farm): 设备对接,修复部分bug

- 重构设备接口
- 修复部分bug
This commit is contained in:
达富斌
2025-09-26 21:40:55 +08:00
parent 3c17055bb3
commit a8ac9d91db
54 changed files with 2802 additions and 514 deletions

View File

@@ -42,4 +42,15 @@ cn.iocoder.yudao.module.farm.controller.admin
├── xxx // 角色管理
├── xxx // 菜单管理
└── xxx // 字典管理
````
````
# 设备商提供能力
| 功能类型 | 土壤设备 | 水肥机 |
|--------------|----------|----------|
| 登录 | ✅ 支持 | ✅ 支持 |
| 获取设备列表 | ✅ 支持 | ✅ 支持 |
| 获取实时数据 | ✅ 支持 | ✅ 支持 |
| 获取历史数据 | ✅ 支持 | ❌ 不支持 |
| 更新设备属性 | ❌ 不支持 | ✅ 支持 |

View File

@@ -92,7 +92,7 @@ public class ApiController {
@PostMapping("/digital-cockpit/baseAndDevice/list")
@Operation(summary = "数字驾驶舱-基地与设备信息列表")
public CommonResult<List<FarmBaseAndDeviceInfoRespVO>> getBaseAndDeviceInfoList(@Valid @RequestBody FarmBaseAndDeviceInfoReqVO req) {
CommonResult<List<FarmBaseInfoRespVO>> bResult = baseInfoService.getBaseInfoList(new FarmBaseInfoReqVO(req.getTenantId()));
CommonResult<List<FarmBaseInfoRespVO>> bResult = baseInfoService.getBaseInfoList(new FarmBaseInfoReqVO(req.getBaseId(),req.getTenantId()));
List<FarmBaseAndDeviceInfoRespVO> dList = BeanUtils.toBean(bResult.getData(), FarmBaseAndDeviceInfoRespVO.class);
Map<String, String> dtNameMap = deviceInfoService.getDeviceTypeNameMap(req.getTenantId());

View File

@@ -15,6 +15,9 @@ import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
public class FarmBaseAndDeviceInfoReqVO {
@Schema(description = "基地D")
@NotNull(message = "基地D: 不能为空")
private String baseId;
@Schema(description = "租户ID")
@NotNull(message = "租户ID: 不能为空")
private String tenantId;

View File

@@ -16,6 +16,9 @@ import lombok.NoArgsConstructor;
@NoArgsConstructor
public class FarmBaseInfoReqVO {
@Schema(description = "基地D")
// @NotNull(message = "基地D: 不能为空")
private String baseId;
@Schema(description = "租户ID")
@NotNull(message = "租户ID: 不能为空")
private String tenantId;

View File

@@ -2,11 +2,18 @@ package cn.iocoder.yudao.module.farm.controller.admin.wx;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.farm.controller.admin.api.vo.DeviceStateRespVO;
import cn.iocoder.yudao.module.farm.controller.admin.wx.vo.*;
import cn.iocoder.yudao.module.farm.dal.dataobject.baseinfo.BaseInfoDO;
import cn.iocoder.yudao.module.farm.dal.dataobject.deviceinfo.DeviceInfoDO;
import cn.iocoder.yudao.module.farm.dal.dataobject.plotinfo.PlotInfoDO;
import cn.iocoder.yudao.module.farm.service.baseinfo.BaseInfoService;
import cn.iocoder.yudao.module.farm.service.deviceinfo.DeviceInfoService;
import cn.iocoder.yudao.module.farm.service.farminformation.FarmInformationService;
import cn.iocoder.yudao.module.farm.service.farmingrecord.FarmingRecordService;
import cn.iocoder.yudao.module.farm.service.messagecenter.MessageCenterService;
import cn.iocoder.yudao.module.farm.service.plotinfo.PlotInfoService;
import cn.iocoder.yudao.module.farm.service.productionplan.ProductionPlanService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -20,6 +27,11 @@ import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUser;
import static cn.iocoder.yudao.module.farm.enums.CommonConstants.BINDING_STATE_MAP;
import static cn.iocoder.yudao.module.farm.enums.CommonConstants.DEVICE_STATE_MAP;
/**
* @description: 微信端
@@ -36,12 +48,18 @@ public class WxController {
private final DeviceInfoService deviceInfoService;
private final FarmingRecordService farmingRecordService;
private final MessageCenterService messageCenterService;
private final BaseInfoService baseInfoService;
private final PlotInfoService plotInfoService;
private final ProductionPlanService productionPlanService;
public WxController(FarmInformationService farmInformationService, DeviceInfoService deviceInfoService, FarmingRecordService farmingRecordService, MessageCenterService messageCenterService) {
public WxController(FarmInformationService farmInformationService, DeviceInfoService deviceInfoService, FarmingRecordService farmingRecordService, MessageCenterService messageCenterService, BaseInfoService baseInfoService, PlotInfoService plotInfoService, ProductionPlanService productionPlanService) {
this.farmInformationService = farmInformationService;
this.deviceInfoService = deviceInfoService;
this.farmingRecordService = farmingRecordService;
this.messageCenterService = messageCenterService;
this.baseInfoService = baseInfoService;
this.plotInfoService = plotInfoService;
this.productionPlanService = productionPlanService;
}
@@ -74,29 +92,75 @@ public class WxController {
@Operation(summary = "获取设备信息")
@Parameter(name = "baseId", description = "基地编号")
public CommonResult<DeviceInfoRespVO> getDeviceInfoList(@RequestParam("baseId") String baseId) {
DeviceInfoRespVO.DeviceRealData realData = new DeviceInfoRespVO.DeviceRealData("剩余电量", 100, "大气湿度", 0, "大气温度", 0);
DeviceInfoRespVO.DeviceDetail deviceDetail = new DeviceInfoRespVO.DeviceDetail();
deviceDetail.setId("DEV001");
deviceDetail.setDeviceUniqueId("SN202405270001");
deviceDetail.setDeviceType("DT001");
deviceDetail.setDeviceTypeName("农业环境气象监测-新");
deviceDetail.setImage("http://223.83.152.93:9000/farm/20250909/trsqjc_1757398720478.png");
deviceDetail.setImageList(Arrays.asList("http://223.83.152.93:9000/farm/20250909/trsqjc_1757398720478.png"));
deviceDetail.setDeviceState(1);
deviceDetail.setDeviceStateName("在线");
deviceDetail.setBindingState(1);
deviceDetail.setBindingStateName("已绑定");
deviceDetail.setPlotName("门村村小香薯种植基地");
deviceDetail.setRealData(realData);
List<DeviceInfoRespVO.PlotInfo> plotInfos = this.getPlotInfos(baseId);
ProductionPlanService.CultivatingInfo cultivatingInfo = productionPlanService.getCultivatingInfo(baseId, "");
int cultivatingSpecies = cultivatingInfo.getCultivatingSpeciesCount(); // 在种物种
int cultivatingPlotCount = cultivatingInfo.getCultivatingPlotCount(); // 在种地块
int plotCount = plotInfos.size();
BaseInfoDO b = baseInfoService.getBaseInfo(baseId);
DeviceInfoRespVO d = new DeviceInfoRespVO();
d.setTotal(1);
d.setOnlineTotal(1);
d.setOfflineTotal(0);
d.setDeviceDetail(deviceDetail);
DeviceInfoRespVO res = new DeviceInfoRespVO();
res.setBaseId(baseId);
res.setBaseName(b.getName());
res.setBaseArea(b.getArea().intValue());
res.setBasePoint(b.getPoint());
res.setCultivatingPlotCount(cultivatingPlotCount);
res.setPlotCount(plotCount);
res.setCultivatingSpecies(cultivatingSpecies);
res.setPlotInfos(plotInfos);
return CommonResult.success(res);
}
return CommonResult.success(d);
private List<DeviceInfoRespVO.PlotInfo> getPlotInfos(String baseId) {
List<DeviceInfoRespVO.PlotInfo> res = new ArrayList<>();
List<PlotInfoDO> pList = plotInfoService.getPlotInfos(baseId);
pList.forEach(p -> {
List<DeviceInfoRespVO.DeviceDetail> deviceInfos = this.getDeviceInfos(baseId, p.getId());
DeviceInfoRespVO.PlotInfo target = new DeviceInfoRespVO.PlotInfo();
target.setPlotId(p.getId());
target.setPlotName(p.getPlotName());
target.setPlotArea(p.getPlotArea().intValue());
target.setPlotColor(p.getPlotColor());
target.setPlotPoint(p.getPoint());
target.setDeviceDetails(deviceInfos);
res.add(target);
});
return res;
}
private List<DeviceInfoRespVO.DeviceDetail> getDeviceInfos(String baseId, String plotId) {
Map<String, String> dtMap = deviceInfoService.getDeviceTypeNameMap(String.valueOf(getLoginUser().getTenantId()));
List<DeviceInfoDO> bdList = deviceInfoService.getBaseAndDeviceInfoList(baseId, plotId);
DeviceStateRespVO ds = deviceInfoService.countDeviceState(baseId, String.valueOf(getLoginUser().getTenantId()));
return bdList.stream().map(e -> {
List<String> imageList = deviceInfoService.getImage(e.getDeviceType());
DeviceInfoRespVO.DeviceDetail target = new DeviceInfoRespVO.DeviceDetail();
target.setTotal(ds.getTotalDevices());
target.setOnlineTotal(ds.getTotalOnlineDevices());
target.setOfflineTotal(ds.getTotalOfflineDevices());
target.setId(e.getId());
target.setDeviceUniqueId(e.getDeviceUniqueId());
target.setDeviceType(e.getDeviceType());
target.setDeviceTypeName(dtMap.get(e.getDeviceType()));
target.setImage(imageList.get(0));
target.setImageList(imageList);
target.setDeviceState(e.getDeviceState());
target.setDeviceStateName(DEVICE_STATE_MAP.get(e.getDeviceState()));
target.setBindingState(e.getBindingState());
target.setBindingStateName(BINDING_STATE_MAP.get(e.getBindingState()));
target.setDevicePoint(e.getPoint());
// TODO: 2025/9/26 接入真实数据
target.setRealData(Arrays.asList(new DeviceInfoRespVO.DeviceRealData("剩余电量", 100),
new DeviceInfoRespVO.DeviceRealData("大气湿度", 0),
new DeviceInfoRespVO.DeviceRealData("大气温度", 0)
));
return target;
}).toList();
}
}

View File

@@ -16,16 +16,52 @@ import java.util.List;
@AllArgsConstructor
@NoArgsConstructor
public class DeviceInfoRespVO {
@Schema(description = "基地ID")
private String baseId;
@Schema(description = "基地名称")
private String baseName;
@Schema(description = "基地面积")
private Integer baseArea;
@Schema(description = "基地坐标")
private String basePoint;
@Schema(description = "在种地块数量")
private Integer cultivatingPlotCount;
@Schema(description = "地块总数量")
private Integer plotCount;
@Schema(description = "在种物种数量")
private Integer cultivatingSpecies;
@Schema(description = "地块列表")
private List<PlotInfo> plotInfos;
private Integer total;
private Integer onlineTotal;
private Integer offlineTotal;
private DeviceDetail deviceDetail;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class PlotInfo{
@Schema(description = "地块ID")
private String plotId;
@Schema(description = "地块名称")
private String plotName;
@Schema(description = "地块面积")
private Integer plotArea;
@Schema(description = "地块坐标")
private String plotPoint;
@Schema(description = "地块颜色")
private String plotColor;
@Schema(description = "设备列表")
private List<DeviceDetail> deviceDetails;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class DeviceDetail{
@Schema(description = "设备总数")
private Integer total;
@Schema(description = "在线设备数量")
private Integer onlineTotal;
@Schema(description = "离线设备数量")
private Integer offlineTotal;
private String id;
@Schema(description = "设备全局唯一标识(设备厂商提供)", requiredMode = Schema.RequiredMode.REQUIRED)
private String deviceUniqueId;
@@ -43,10 +79,10 @@ public class DeviceInfoRespVO {
@Schema(description = "绑定状态:1: 绑定; 0: 未绑定")
private Integer bindingState;
private String bindingStateName;
@Schema(description = "地块名称")
private String plotName;
@Schema(description = "设备坐标")
private String devicePoint;
@Schema(description = "设备实时数据")
private DeviceRealData realData;
private List<DeviceRealData> realData;
}
@Data
@@ -55,10 +91,6 @@ public class DeviceInfoRespVO {
public static class DeviceRealData{
private String k1;
private Integer v1;
private String k2;
private Integer v2;
private String k3;
private Integer v3;
}
}

View File

@@ -102,5 +102,5 @@ public interface DeviceInfoMapper extends BaseMapperX<DeviceInfoDO> {
* @param tenantId
* @return
*/
DeviceStateRespVO countDeviceState(String tenantId);
DeviceStateRespVO countDeviceState(@Param("baseId") String baseId,@Param("tenantId") String tenantId);
}

View File

@@ -15,7 +15,6 @@ import org.springframework.stereotype.Service;
import java.util.List;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUser;
import static cn.iocoder.yudao.module.farm.manager.devices.commons.DeviceConst.ALARM_STATUS_NAME_MAP;
/**
* @description: 设备管理
@@ -23,79 +22,79 @@ import static cn.iocoder.yudao.module.farm.manager.devices.commons.DeviceConst.A
* @author: binda
* @date: 2025/9/17 10:50
*/
@Service
//@Service
public class DeviceManager {
private final SoilService soilService;
private final WaterFertilizerService waterFertilizerService;
public DeviceManager(SoilService soilService, WaterFertilizerService waterFertilizerService) {
this.soilService = soilService;
this.waterFertilizerService = waterFertilizerService;
}
public DeviceInfoDetailV2RespVO getRealData(String deviceUniqueId) {
DeviceInfoDetailV2RespVO.SoilInfo soilInfo = this.getSoilData(deviceUniqueId);
DeviceInfoDetailV2RespVO.WfInfo wfInfo = this.getWfData(deviceUniqueId);
return new DeviceInfoDetailV2RespVO(deviceUniqueId, null, null);
}
public DeviceInfoDetailV2RespVO.SoilInfo getSoilData(String deviceUniqueId) {
// 判断该deviceUniqueId是否来源于土壤墒情
boolean flag = false;
if (flag) {
return null;
}
String tenantId = String.valueOf(getLoginUser().getTenantId());
if (!soilService.validateUserToken(tenantId)) {
soilService.userLogin();
}
List<RealTimeDataRespVO> realTimeData = soilService.getRealTimeData(new RealTimeDataReqVO(deviceUniqueId));
HistoryDataListPageRespVO historyDataList = soilService.getHistoryDataList(new HistoryDataListPageReqVO());
return new DeviceInfoDetailV2RespVO.SoilInfo(deviceUniqueId, coverToSoil(realTimeData), coverToSoil(historyDataList));
}
public DeviceInfoDetailV2RespVO.RealTimeData coverToSoil(List<RealTimeDataRespVO> realTimeData) {
if (ObjectUtils.isEmpty(realTimeData)) {
return null;
}
RealTimeDataRespVO sourceObj = realTimeData.get(0);
return BeanUtils.toBean(sourceObj, DeviceInfoDetailV2RespVO.RealTimeData.class);
}
public List<DeviceInfoDetailV2RespVO.HistoryDataList> coverToSoil(HistoryDataListPageRespVO historyDataList ) {
if (ObjectUtils.isEmpty(historyDataList)) {
return null;
}
List<HistoryDataListPageRespVO.Row> source = historyDataList.getRows();
return source.stream().map(e -> {
DeviceInfoDetailV2RespVO.HistoryDataList target = new DeviceInfoDetailV2RespVO.HistoryDataList();
target.setHistoryId(e.getHistoryId());
target.setRecordTime(DateUtils.timestampToLocalDateTime(e.getRecordTime()));
target.setDeviceAddress(e.getDeviceAddress());
target.setAlarmStatus(e.getAlarmStatus());
target.setAlarmStatusName(ALARM_STATUS_NAME_MAP.getOrDefault(e.getAlarmStatus(),""));
target.setTemStr(e.getTemStr());
target.setTemValue(e.getTemValue());
target.setHumStr(e.getHumStr());
target.setHumValue(e.getHumValue());
return target;
}).toList();
}
public DeviceInfoDetailV2RespVO.WfInfo getWfData(String deviceUniqueId) {
String tenantId = String.valueOf(getLoginUser().getTenantId());
return null;
}
// private final SoilService soilService;
// private final WaterFertilizerService waterFertilizerService;
//
// public DeviceManager(SoilService soilService, WaterFertilizerService waterFertilizerService) {
// this.soilService = soilService;
// this.waterFertilizerService = waterFertilizerService;
// }
//
// public DeviceInfoDetailV2RespVO getRealData(String deviceUniqueId) {
// DeviceInfoDetailV2RespVO.SoilInfo soilInfo = this.getSoilData(deviceUniqueId);
// DeviceInfoDetailV2RespVO.WfInfo wfInfo = this.getWfData(deviceUniqueId);
// return new DeviceInfoDetailV2RespVO(deviceUniqueId, null, null);
// }
//
// public DeviceInfoDetailV2RespVO.SoilInfo getSoilData(String deviceUniqueId) {
// // 判断该deviceUniqueId是否来源于土壤墒情
// boolean flag = false;
// if (flag) {
// return null;
// }
//
// String tenantId = String.valueOf(getLoginUser().getTenantId());
// if (!soilService.validateUserToken(tenantId)) {
// soilService.userLogin();
// }
//
// List<RealTimeDataRespVO> realTimeData = soilService.getRealTimeData(new RealTimeDataReqVO(deviceUniqueId));
// HistoryDataListPageRespVO historyDataList = soilService.getHistoryDataList(new HistoryDataListPageReqVO());
// return new DeviceInfoDetailV2RespVO.SoilInfo(deviceUniqueId, coverToSoil(realTimeData), coverToSoil(historyDataList));
// }
//
// public DeviceInfoDetailV2RespVO.RealTimeData coverToSoil(List<RealTimeDataRespVO> realTimeData) {
// if (ObjectUtils.isEmpty(realTimeData)) {
// return null;
// }
// RealTimeDataRespVO sourceObj = realTimeData.get(0);
// return BeanUtils.toBean(sourceObj, DeviceInfoDetailV2RespVO.RealTimeData.class);
// }
//
// public List<DeviceInfoDetailV2RespVO.HistoryDataList> coverToSoil(HistoryDataListPageRespVO historyDataList ) {
// if (ObjectUtils.isEmpty(historyDataList)) {
// return null;
// }
//
// List<HistoryDataListPageRespVO.Row> source = historyDataList.getRows();
// return source.stream().map(e -> {
// DeviceInfoDetailV2RespVO.HistoryDataList target = new DeviceInfoDetailV2RespVO.HistoryDataList();
// target.setHistoryId(e.getHistoryId());
// target.setRecordTime(DateUtils.timestampToLocalDateTime(e.getRecordTime()));
// target.setDeviceAddress(e.getDeviceAddress());
// target.setAlarmStatus(e.getAlarmStatus());
// target.setAlarmStatusName(ALARM_STATUS_NAME_MAP.getOrDefault(e.getAlarmStatus(),""));
// target.setTemStr(e.getTemStr());
// target.setTemValue(e.getTemValue());
// target.setHumStr(e.getHumStr());
// target.setHumValue(e.getHumValue());
// return target;
// }).toList();
//
// }
//
//
//
// public DeviceInfoDetailV2RespVO.WfInfo getWfData(String deviceUniqueId) {
// String tenantId = String.valueOf(getLoginUser().getTenantId());
//
//
//
// return null;
// }
}

View File

@@ -9,87 +9,97 @@ import java.util.Map;
* @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; // 短信
public final static int ALARM_STATUS_NAME_0 = 0; // 正常
public final static int ALARM_STATUS_NAME_1 = 1; // 报警
// TODO: 2025/9/17 UpdateNodeInfoReqVO
public static Map<Integer, String> ALARM_STATUS_NAME_MAP = new HashMap<>();
static {
ALARM_STATUS_NAME_MAP.put(ALARM_STATUS_NAME_0, "正常");
ALARM_STATUS_NAME_MAP.put(ALARM_STATUS_NAME_1, "报警");
}
//
// /**
// * 四情 设备类型
// *
// * 虫情设备普通版: worm;
// * 虫情设备旗舰版: wormFlagship;
// * 孢子设备: spore;
// * 气象设备:met;
// * 墒情设备:soil;
// * 智慧环控 3.0 设备:irrigation;
// * 智慧环控 2.0 设备:irrigation2;
// * 摄像头:camera;
// *
// */
// 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; // 短信
//
//
//
//
// public final static int ALARM_STATUS_NAME_0 = 0; // 正常
// public final static int ALARM_STATUS_NAME_1 = 1; // 报警
//
// // TODO: 2025/9/17 UpdateNodeInfoReqVO
//
//
// public static Map<Integer, String> ALARM_STATUS_NAME_MAP = new HashMap<>();
// static {
// ALARM_STATUS_NAME_MAP.put(ALARM_STATUS_NAME_0, "正常");
// ALARM_STATUS_NAME_MAP.put(ALARM_STATUS_NAME_1, "报警");
// }
}

View File

@@ -9,54 +9,54 @@ import org.springframework.context.annotation.Configuration;
* @author: binda
* @date: 2025/9/17 10:55
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "device-manager")
//@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;
}
// 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

@@ -8,8 +8,8 @@ import org.springframework.scheduling.annotation.EnableScheduling;
* @author: binda
* @date: 2025/9/17 14:42
*/
@Configuration
@EnableScheduling
//@Configuration
//@EnableScheduling
public class SchedulingConfig {
}

View File

@@ -22,182 +22,182 @@ import static cn.iocoder.yudao.module.farm.enums.ErrorCodeConstants.*;
* @date: 2025/9/17 10:57
*/
@Slf4j
@Service
//@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_";
public void userLogin() {
String username = deviceConf.getFourSituations().getUsername();
String password = deviceConf.getFourSituations().getPassword();
this.userLogin(new UserLoginReqVO(username,password));
}
/**
* 登录获取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;
}
//
// 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_";
//
// public void userLogin() {
// String username = deviceConf.getFourSituations().getUsername();
// String password = deviceConf.getFourSituations().getPassword();
//
// this.userLogin(new UserLoginReqVO(username,password));
// }
//
// /**
// * 登录获取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

@@ -1,90 +0,0 @@
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,24 @@
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/25 23:07
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DevicePageReqVO {
/**
* 令牌 必填
*/
private String token;
/**
* 用户名
*/
private String userCode;
}

View File

@@ -0,0 +1,78 @@
package cn.iocoder.yudao.module.farm.manager.equipmentproducer;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.*;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.service.DeviceService;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* @description:
* @author: binda
* @date: 2025/9/25 20:44
*/
@Service
public class EquipmentProducerManager {
private final Map<String, DeviceService> deviceServiceMap;
public EquipmentProducerManager(List<DeviceService> deviceServices) {
this.deviceServiceMap = deviceServices.stream()
.collect(Collectors.toMap(DeviceService::getDeviceType, Function.identity()));
}
/**
* 统一登录方法
*/
public UnifiedLoginResponse login(String deviceType, Long tenantId, String deviceId, Object loginRequest) {
DeviceService service = this.getDeviceService(deviceType);
return service.login(tenantId, deviceId, loginRequest);
}
/**
* 统一获取设备列表方法
*/
public DeviceListResponse getDeviceList(String deviceType, Long tenantId, String token, Object listRequest) {
DeviceService service = this.getDeviceService(deviceType);
return service.getDeviceList(tenantId, token, listRequest);
}
/**
* 统一获取实时数据方法
*/
public RealTimeDataResponse getRealTimeData(String deviceType, Long tenantId, String token, Object realTimeRequest) {
DeviceService service = this.getDeviceService(deviceType);
return service.getRealTimeData(tenantId, token, realTimeRequest);
}
/**
* 统一获取历史数据方法
*/
public HistoryDataResponse getHistoryData(String deviceType, Long tenantId, String token, Object historyRequest) {
DeviceService service = getDeviceService(deviceType);
return service.getHistoryData(tenantId, token, historyRequest);
}
/**
* 统一更新设备属性方法
*/
public UpdateDeviceResponse updateDeviceProperties(String deviceType, Long tenantId, String token, Object updateRequest) {
DeviceService service = getDeviceService(deviceType);
return service.updateDeviceProperties(tenantId, token, updateRequest);
}
/**
* 根据设备类型获取对应的设备服务
*/
private DeviceService getDeviceService(String deviceType) {
DeviceService service = deviceServiceMap.get(deviceType);
if (service == null) {
throw new IllegalArgumentException("不支持的设备类型: " + deviceType);
}
return service;
}
}

View File

@@ -0,0 +1,105 @@
package cn.iocoder.yudao.module.farm.manager.equipmentproducer.commons;
import java.util.HashMap;
import java.util.Map;
/**
* @description: 设备常量
* @author: binda
* @date: 2025/9/17 15:24
*/
public class DeviceConst {
/**
* 四情 设备类型
*
* 虫情设备普通版: worm;
* 虫情设备旗舰版: wormFlagship;
* 孢子设备: spore;
* 气象设备:met;
* 墒情设备:soil;
* 智慧环控 3.0 设备:irrigation;
* 智慧环控 2.0 设备:irrigation2;
* 摄像头:camera;
*
*/
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; // 短信
public final static int ALARM_STATUS_NAME_0 = 0; // 正常
public final static int ALARM_STATUS_NAME_1 = 1; // 报警
// TODO: 2025/9/17 UpdateNodeInfoReqVO
public static Map<Integer, String> ALARM_STATUS_NAME_MAP = new HashMap<>();
static {
ALARM_STATUS_NAME_MAP.put(ALARM_STATUS_NAME_0, "正常");
ALARM_STATUS_NAME_MAP.put(ALARM_STATUS_NAME_1, "报警");
}
}

View File

@@ -0,0 +1,62 @@
package cn.iocoder.yudao.module.farm.manager.equipmentproducer.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.equipmentproducer.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,30 @@
package cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.service.TokenCacheService;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* @description: 统一设备列表响应
* @author: binda
* @date: 2025/9/25 23:29
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DeviceListResponse {
private String code;
private String message;
private List<TokenCacheService.DeviceInfo> data;
public static DeviceListResponse success(List<TokenCacheService.DeviceInfo> data) {
return new DeviceListResponse("200", "成功", data);
}
public static DeviceListResponse error(String message) {
return new DeviceListResponse("500", message, null);
}
}

View File

@@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @description: TODO
* @author: binda
* @date: 2025/9/26 20:32
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class HistoryDataResponse {
private String code;
private String message;
private Object data;
public static HistoryDataResponse success(Object data) {
return new HistoryDataResponse("200", "成功", data);
}
public static HistoryDataResponse error(String message) {
return new HistoryDataResponse("500", message, null);
}
public static HistoryDataResponse notSupported() {
return new HistoryDataResponse("501", "该设备类型不支持历史数据功能", null);
}
}

View File

@@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @description: 实时数据
* @author: binda
* @date: 2025/9/26 18:32
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RealTimeDataResponse {
private String code;
private String message;
private Object data; // 由于不同设备实时数据结构差异大,使用Object类型
public static RealTimeDataResponse success(Object data) {
return new RealTimeDataResponse("200", "成功", data);
}
public static RealTimeDataResponse error(String message) {
return new RealTimeDataResponse("500", message, null);
}
}

View File

@@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @description: 统一登录响应
* @author: binda
* @date: 2025/9/25 21:26
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UnifiedLoginResponse {
private String code;
private String message;
private LoginData data;
public static UnifiedLoginResponse success(LoginData data) {
return new UnifiedLoginResponse("200", "成功", data);
}
public static UnifiedLoginResponse error(String message) {
return new UnifiedLoginResponse("500", message, null);
}
// 登录数据
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class LoginData {
private String loginSign;
private Long currDate;
private Long expDate;
private String token;
}
}

View File

@@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @description:
* @author: binda
* @date: 2025/9/26 21:09
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UpdateDeviceResponse {
private String code;
private String message;
private Object data;
public static UpdateDeviceResponse success(Object data) {
return new UpdateDeviceResponse("200", "成功", data);
}
public static UpdateDeviceResponse error(String message) {
return new UpdateDeviceResponse("500", message, null);
}
public static UpdateDeviceResponse notSupported() {
return new UpdateDeviceResponse("501", "该设备类型不支持更新设备属性功能", null);
}
}

View File

@@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.soil.devicelist;
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,55 @@
package cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.soil.devicelist;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* @description: 获取用户设备 响应实体
* @author: binda
* @date: 2025/9/17 15:31
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SysUserDeviceRespVO {
private String code;
private String message;
private List<DeviceInfo> data;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class DeviceInfo{
/**
* 设备地址
*/
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,108 @@
package cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.soil.history;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.time.ZoneId;
/**
* @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;
public HistoryDataListPageReqVO(String beginTime, String endTime) {
this.beginTime = beginTime;
this.endTime = endTime;
}
public static class HistoryDataTimeRangeUtil {
/**
* 获取近12小时的时间范围
*/
public static HistoryDataListPageReqVO getLast12Hours() {
LocalDateTime endTime = LocalDateTime.now();
LocalDateTime startTime = endTime.minusHours(12);
return buildRequestVO(startTime, endTime);
}
/**
* 获取近一天的时间范围
*/
public static HistoryDataListPageReqVO getLast24Hours() {
LocalDateTime endTime = LocalDateTime.now();
LocalDateTime startTime = endTime.minusDays(1);
return buildRequestVO(startTime, endTime);
}
/**
* 获取近三天的时间范围
*/
public static HistoryDataListPageReqVO getLast3Days() {
LocalDateTime endTime = LocalDateTime.now();
LocalDateTime startTime = endTime.minusDays(3);
return buildRequestVO(startTime, endTime);
}
/**
* 获取近七天的时间范围
*/
public static HistoryDataListPageReqVO getLast7Days() {
LocalDateTime endTime = LocalDateTime.now();
LocalDateTime startTime = endTime.minusDays(7);
return buildRequestVO(startTime, endTime);
}
/**
* 构建请求VO对象
*/
private static HistoryDataListPageReqVO buildRequestVO(LocalDateTime startTime, LocalDateTime endTime) {
String beginTimeStamp = String.valueOf(startTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
String endTimeStamp = String.valueOf(endTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
return new HistoryDataListPageReqVO(beginTimeStamp, endTimeStamp);
}
}
}

View File

@@ -0,0 +1,62 @@
package cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.soil.history;
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,18 @@
package cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.soil.login;
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,43 @@
package cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.soil.login;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @description: 登录响应实体
* @author: binda
* @date: 2025/9/17 13:46
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserLoginRespVO {
private String code;
private String message;
private VendorData data;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class VendorData {
/**
* 登录标识
*/
public String loginSign;
/**
* token 生成时间
*/
public long currDate;
/**
* 过期时间
*/
public long expDate;
/**
* 令牌
*/
public String token;
}
}

View File

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

View File

@@ -0,0 +1,79 @@
package cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.soil.realtime;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.soil.devicelist.SysUserDeviceRespVO;
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 code;
private String message;
private List<DeviceInfo> data;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class DeviceInfo{
/**
* 设备地址
*/
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.equipmentproducer.dto.wf.devicelist;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @description:
* @author: binda
* @date: 2025/9/25 23:07
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DevicePageReqVO {
/**
* 令牌 必填
*/
private String token;
/**
* 用户名
*/
private String userCode;
}

View File

@@ -0,0 +1,51 @@
package cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.wf.devicelist;
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.equipmentproducer.dto.wf.login;
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.equipmentproducer.dto.wf.login;
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 String code;
private String msg;
private String token;
}

View File

@@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.wf.realtime;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @description: TODO
* @author: binda
* @date: 2025/9/26 18:09
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RealTimeDeviceDataReqVO {
/**
* 设备编号 必填
*/
private String deviceId;
}

View File

@@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.wf.realtime;
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; // 是否在线 编码
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,28 @@
package cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.wf.updateparams;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @description:
* @author: binda
* @date: 2025/9/26 21:01
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UpdateDeviceStatusReqVO {
private String deviceId;
private ParamStr paramStr;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class ParamStr{
// 设备开关名称
private String Value1;
// 要修改的值
private String Value11;
}
}

View File

@@ -0,0 +1,18 @@
package cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.wf.updateparams;
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

@@ -0,0 +1,161 @@
package cn.iocoder.yudao.module.farm.manager.equipmentproducer.service;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.*;
import java.util.List;
import java.util.Map;
/**
* @description:
* @author: binda
* @date: 2025/9/25 20:38
*/
public abstract class AbstractDeviceService extends TokenCacheService implements DeviceService{
@Override
public UnifiedLoginResponse login(Long tenantId, String deviceId, Object loginRequest) {
String cacheKey = generateCacheKey(getDeviceType(), tenantId, deviceId);
// 检查缓存中是否有有效token
TokenCacheService.TokenInfo cachedToken = getToken(cacheKey);
if (cachedToken != null && isTokenValid(cacheKey)) {
return UnifiedLoginResponse.success(convertToLoginData(cachedToken));
}
// 调用具体设备登录
UnifiedLoginResponse loginResponse = doLogin(loginRequest);
if ("200".equals(loginResponse.getCode()) && loginResponse.getData() != null) {
// 缓存token信息
TokenCacheService.TokenInfo tokenInfo = new TokenCacheService.TokenInfo(
loginResponse.getData().getToken(),
loginResponse.getData().getCurrDate(),
loginResponse.getData().getExpDate(),
loginResponse.getData().getLoginSign()
);
cacheToken(cacheKey, tokenInfo);
}
return loginResponse;
}
@Override
public DeviceListResponse getDeviceList(Long tenantId, String token, Object listRequest) {
// 验证token有效性
String deviceCacheKey = generateDeviceCacheKey(getDeviceType(), tenantId, token);
// 先检查设备缓存
List<DeviceInfo> cachedDevices = getDeviceList(deviceCacheKey);
if (cachedDevices != null && !cachedDevices.isEmpty()) {
return DeviceListResponse.success(cachedDevices);
}
// 验证token是否有效
if (!validateToken(tenantId, token)) {
return DeviceListResponse.error("Token无效或已过期");
}
// TODO: 2025/9/25 如果token过期,直接登录重新获取token
// 调用具体设备获取列表
DeviceListResponse listResponse = doGetDeviceList(token, listRequest);
if ("200".equals(listResponse.getCode()) && listResponse.getData() != null) {
// 缓存设备列表
cacheDeviceList(deviceCacheKey, listResponse.getData());
}
return listResponse;
}
@Override
public RealTimeDataResponse getRealTimeData(Long tenantId, String token, Object realTimeRequest) {
// 验证token有效性
if (!validateToken(tenantId, token)) {
return RealTimeDataResponse.error("Token无效或已过期");
}
// 调用具体设备获取实时数据
return doGetRealTimeData(token, realTimeRequest);
}
@Override
public HistoryDataResponse getHistoryData(Long tenantId, String token, Object historyRequest) {
// 验证token有效性
if (!validateToken(tenantId, token)) {
return HistoryDataResponse.error("Token无效或已过期");
}
// 调用具体设备获取历史数据
return doGetHistoryData(token, historyRequest);
}
@Override
public UpdateDeviceResponse updateDeviceProperties(Long tenantId, String token, Object updateRequest) {
// 验证token有效性
if (!validateToken(tenantId, token)) {
return UpdateDeviceResponse.error("Token无效或已过期");
}
// 调用具体设备更新属性
return doUpdateDeviceProperties(token, updateRequest);
}
/**
* 具体设备登录逻辑(由子类实现)
*/
protected abstract UnifiedLoginResponse doLogin(Object loginRequest);
/**
* 具体设备获取列表逻辑(由子类实现)
*/
protected abstract DeviceListResponse doGetDeviceList(String token, Object listRequest);
/**
* 具体设备获取实时数据逻辑(由子类实现)
*/
protected abstract RealTimeDataResponse doGetRealTimeData(String token, Object realTimeRequest);
/**
* 默认实现:不支持设备属性更新功能
* 子类可以重写此方法来实现具体的属性更新逻辑
*/
protected UpdateDeviceResponse doUpdateDeviceProperties(String token, Object updateRequest) {
return UpdateDeviceResponse.notSupported();
}
/**
* 默认实现:不支持历史数据功能
* 子类可以重写此方法来实现具体的历史数据获取逻辑
*/
protected HistoryDataResponse doGetHistoryData(String token, Object historyRequest) {
return HistoryDataResponse.notSupported();
}
/**
* 转换TokenInfo为LoginData
*/
protected UnifiedLoginResponse.LoginData convertToLoginData(TokenCacheService.TokenInfo tokenInfo) {
return new UnifiedLoginResponse.LoginData(
tokenInfo.getLoginSign(),
tokenInfo.getCurrDate(),
tokenInfo.getExpDate(),
tokenInfo.getToken()
);
}
/**
* 验证token有效性
*/
private boolean validateToken(Long tenantId, String token) {
// 遍历token缓存,查找匹配的token
for (Map.Entry<String, TokenCacheService.TokenInfo> entry : tokenCacheMap.entrySet()) {
if (entry.getKey().startsWith(getDeviceType() + ":" + tenantId + ":") &&
token.equals(entry.getValue().getToken()) &&
isTokenValid(entry.getKey())) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.farm.manager.equipmentproducer.service;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.*;
/**
* @description:
* @author: binda
* @data: 2025/9/25 20:35
*/
public interface DeviceService {
String getDeviceType();
/**
* 登录获取token
*/
UnifiedLoginResponse login(Long tenantId, String deviceId, Object loginRequest);
/**
* 获取设备列表
*/
DeviceListResponse getDeviceList(Long tenantId, String token, Object listRequest);
/**
* 获取实时数据
*/
RealTimeDataResponse getRealTimeData(Long tenantId, String token, Object realTimeRequest);
/**
* 获取历史数据
*/
HistoryDataResponse getHistoryData(Long tenantId, String token, Object historyRequest);
/**
* 更新设备属性
*/
UpdateDeviceResponse updateDeviceProperties(Long tenantId, String token, Object updateRequest);
}

View File

@@ -0,0 +1,75 @@
package cn.iocoder.yudao.module.farm.manager.equipmentproducer.service;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @description: 设备历史数据持久化
* @author: binda
* @date: 2025/9/26 20:37
*/
@Service
public class HistoryDataStorageService {
// 模拟MySQL表存储 - 实际项目中这里会是MyBatis Mapper或JPA Repository
private final Map<String, List<HistoryDataEntity>> historyDataStorage = new ConcurrentHashMap<>();
public void saveHistoryData(String deviceAddr, List<HistoryDataEntity> historyData) {
}
public List<HistoryDataEntity> queryHistoryData(String deviceAddr,
Long beginTime,
Long endTime,
Integer page,
Integer limit) {
return null;
}
public int getTotalCount(String deviceAddr, Long beginTime, Long endTime) {
return 0;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class HistoryDataEntity {
/**
* 记录表 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,140 @@
package cn.iocoder.yudao.module.farm.manager.equipmentproducer.service;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @description:
* @author: binda
* @date: 2025/9/25 20:38
*/
@Slf4j
public abstract class TokenCacheService {
public final Map<String, TokenInfo> tokenCacheMap = new ConcurrentHashMap<>();
private final Map<String, List<DeviceInfo>> deviceCacheMap = new ConcurrentHashMap<>();
/**
* 生成缓存key
* @param deviceType 设备类型
* @param tenantId 租户ID
* @param identifier 设备表主键ID
* @return
*/
public String generateCacheKey(String deviceType, Long tenantId, String identifier) {
return deviceType + ":" + tenantId + ":" + identifier;
}
/**
* 生成设别列表key
* @param deviceType 设备类型
* @param tenantId 租户ID
* @param token 令牌
* @return
*/
public String generateDeviceCacheKey(String deviceType, Long tenantId, String token) {
return deviceType + ":" + tenantId + ":" + token;
}
/**
* 缓存token信息
*/
public void cacheToken(String key, TokenInfo tokenInfo) {
tokenCacheMap.put(key, tokenInfo);
}
/**
* 通过key获取token信息
*/
public TokenInfo getToken(String key) {
return tokenCacheMap.get(key);
}
/**
* 验证token是否有效
*/
public boolean isTokenValid(String key) {
TokenInfo tokenInfo = tokenCacheMap.get(key);
if (tokenInfo == null) {
return false;
}
return tokenInfo.getExpDate() > System.currentTimeMillis();
}
/**
* 移除token
*/
public void removeToken(String key) {
tokenCacheMap.remove(key);
}
// 设备列表缓存相关方法
public void cacheDeviceList(String key, List<DeviceInfo> deviceList) {
deviceCacheMap.put(key, deviceList);
}
public List<DeviceInfo> getDeviceList(String key) {
return deviceCacheMap.get(key);
}
public void removeDeviceList(String key) {
deviceCacheMap.remove(key);
}
/**
* 定期清除过期token
*/
@Scheduled(fixedRate = 300000) // 每5分钟执行一次
public void cleanupExpiredTokens() {
long currentTime = System.currentTimeMillis();
// 清理过期token
Iterator<Map.Entry<String, TokenInfo>> tokenIterator = tokenCacheMap.entrySet().iterator();
while (tokenIterator.hasNext()) {
Map.Entry<String, TokenInfo> entry = tokenIterator.next();
if (entry.getValue().getExpDate() <= currentTime) {
tokenIterator.remove();
// 同时清理对应的设备缓存
removeDeviceList(entry.getKey());
}
}
log.info("清理过期token缓存");
// 清理长时间未使用的设备缓存(可选)
}
/**
* Token信息封装
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class TokenInfo {
private String token;
private Long currDate;
private Long expDate;
private String loginSign;
}
/**
* Token信息封装
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class DeviceInfo {
// 设备编号
private String deviceCode;
// 设备名称
private String deviceName;
// 扩展字段,用于存储原始设备信息
private Map<String, Object> extraInfo;
}
}

View File

@@ -0,0 +1,368 @@
package cn.iocoder.yudao.module.farm.manager.equipmentproducer.service.impl;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.DeviceListResponse;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.HistoryDataResponse;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.RealTimeDataResponse;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.UnifiedLoginResponse;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.soil.devicelist.SysUserDeviceReqVO;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.soil.devicelist.SysUserDeviceRespVO;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.soil.history.HistoryDataListPageReqVO;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.soil.history.HistoryDataListPageRespVO;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.soil.login.UserLoginReqVO;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.soil.login.UserLoginRespVO;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.soil.realtime.RealTimeDataReqVO;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.soil.realtime.RealTimeDataRespVO;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.service.AbstractDeviceService;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.service.HistoryDataStorageService;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.service.TokenCacheService;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
/**
* @description: 土壤墒情
* @author: binda
* @date: 2025/9/25 20:40
*/
@Service
public class SoilService extends AbstractDeviceService {
private final HistoryDataStorageService historyDataStorageService;
public SoilService(HistoryDataStorageService historyDataStorageService) {
this.historyDataStorageService = historyDataStorageService;
}
@Override
public String getDeviceType() {
return "soil";
}
@Override
protected UnifiedLoginResponse doLogin(Object loginRequest) {
try {
// 转换请求参数
UserLoginReqVO soilRequest = (UserLoginReqVO) loginRequest;
// TODO: 2025/9/25 调用土壤设备厂商API POST 请求方式
UserLoginRespVO vendorResponse = callSoilVendorApi(soilRequest);
// 转换为统一响应格式
return convertToUnifiedResponse(vendorResponse);
} catch (Exception e) {
return UnifiedLoginResponse.error("土壤设备登录失败: " + e.getMessage());
}
}
@Override
protected DeviceListResponse doGetDeviceList(String token, Object listRequest) {
try {
// 转换请求参数
SysUserDeviceReqVO soilRequest = (SysUserDeviceReqVO) listRequest;
// TODO: 2025/9/25 调用土壤设备厂商API获取设备列表 GET 请求方式
SysUserDeviceRespVO vendorResponse = callSoilDeviceListApi(token, soilRequest);
// 转换为统一响应格式
return convertToUnifiedDeviceResponse(vendorResponse);
} catch (Exception e) {
return DeviceListResponse.error("土壤设备列表获取失败: " + e.getMessage());
}
}
@Override
protected RealTimeDataResponse doGetRealTimeData(String token, Object realTimeRequest) {
try {
// 转换请求参数
RealTimeDataReqVO soilRequest = (RealTimeDataReqVO) realTimeRequest;
// TODO: 2025/9/26 调用土壤设备厂商API获取实时数据
RealTimeDataRespVO vendorResponse = callSoilRealTimeDataApi(token, soilRequest);
// 直接返回原始响应数据,不进行统一转换
return RealTimeDataResponse.success(vendorResponse);
} catch (Exception e) {
return RealTimeDataResponse.error("土壤设备实时数据获取失败: " + e.getMessage());
}
}
@Override
protected HistoryDataResponse doGetHistoryData(String token, Object historyRequest) {
try {
// 转换请求参数
HistoryDataListPageReqVO soilRequest = (HistoryDataListPageReqVO) historyRequest;
// TODO: 2025/9/26 调用土壤设备厂商API获取历史数据
HistoryDataListPageRespVO vendorResponse = callSoilHistoryDataApi(token, soilRequest);
// TODO: 2025/9/26 模拟保存到MySQL
saveHistoryDataToDatabase(vendorResponse, soilRequest);
return HistoryDataResponse.success(vendorResponse);
} catch (Exception e) {
return HistoryDataResponse.error("土壤设备历史数据获取失败: " + e.getMessage());
}
}
private UserLoginRespVO callSoilVendorApi(UserLoginReqVO request) {
// 模拟调用土壤设备厂商API
// 实际实现中这里会是HTTP请求
UserLoginRespVO response = new UserLoginRespVO();
response.setCode("200");
response.setMessage("成功");
response.setData(new UserLoginRespVO.VendorData());
response.getData().setLoginSign("soil-login-sign-" + System.currentTimeMillis());
response.getData().setCurrDate(System.currentTimeMillis());
response.getData().setExpDate(System.currentTimeMillis() + 3600000); // 1小时后过期
response.getData().setToken("soil-token-" + System.currentTimeMillis());
return response;
}
private UnifiedLoginResponse convertToUnifiedResponse(UserLoginRespVO vendorResponse) {
if (!"200".equals(vendorResponse.getCode())) {
return UnifiedLoginResponse.error(vendorResponse.getMessage());
}
UnifiedLoginResponse.LoginData loginData = new UnifiedLoginResponse.LoginData(
vendorResponse.getData().getLoginSign(),
vendorResponse.getData().getCurrDate(),
vendorResponse.getData().getExpDate(),
vendorResponse.getData().getToken()
);
return UnifiedLoginResponse.success(loginData);
}
private SysUserDeviceRespVO callSoilDeviceListApi(String token, SysUserDeviceReqVO request) {
// 模拟调用土壤设备厂商API
SysUserDeviceRespVO response = new SysUserDeviceRespVO();
response.setCode("200");
response.setMessage("成功");
List<SysUserDeviceRespVO.DeviceInfo> deviceList = new ArrayList<>();
// 模拟设备数据
SysUserDeviceRespVO.DeviceInfo device1 = new SysUserDeviceRespVO.DeviceInfo();
device1.setDeviceAddr("SOIL_001");
device1.setDeviceName("土壤监测设备1");
device1.setGroupId("G001");
device1.setDeviceType("SOIL_MONITOR");
deviceList.add(device1);
SysUserDeviceRespVO.DeviceInfo device2 = new SysUserDeviceRespVO.DeviceInfo();
device2.setDeviceAddr("SOIL_002");
device2.setDeviceName("土壤监测设备2");
device2.setGroupId("G001");
device2.setDeviceType("SOIL_MONITOR");
deviceList.add(device2);
response.setData(deviceList);
return response;
}
private DeviceListResponse convertToUnifiedDeviceResponse(SysUserDeviceRespVO vendorResponse) {
if (!"200".equals(vendorResponse.getCode())) {
return DeviceListResponse.error(vendorResponse.getMessage());
}
List<TokenCacheService.DeviceInfo> unifiedDevices = vendorResponse.getData().stream()
.map(vendorDevice -> {
TokenCacheService.DeviceInfo device = new TokenCacheService.DeviceInfo();
device.setDeviceCode(vendorDevice.getDeviceAddr());
device.setDeviceName(vendorDevice.getDeviceName());
// 保存原始设备信息到extraInfo
Map<String, Object> extraInfo = new HashMap<>();
extraInfo.put("groupId", vendorDevice.getGroupId());
extraInfo.put("deviceType", vendorDevice.getDeviceType());
extraInfo.put("deviceLng", vendorDevice.getDeviceLng());
extraInfo.put("deviceLat", vendorDevice.getDeviceLat());
extraInfo.put("deviceIccId", vendorDevice.getDeviceIccId());
device.setExtraInfo(extraInfo);
return device;
})
.collect(Collectors.toList());
return DeviceListResponse.success(unifiedDevices);
}
private RealTimeDataRespVO callSoilRealTimeDataApi(String token, RealTimeDataReqVO request) {
// 模拟调用土壤设备厂商API获取实时数据
RealTimeDataRespVO response = new RealTimeDataRespVO();
response.setCode("200");
response.setMessage("成功");
List<RealTimeDataRespVO.DeviceInfo> deviceList = new ArrayList<>();
// 模拟设备1的实时数据
RealTimeDataRespVO.DeviceInfo device1 = new RealTimeDataRespVO.DeviceInfo();
device1.setDeviceAddr("SOIL_001");
device1.setDeviceName("土壤监测设备1");
device1.setLat(39.9093);
device1.setLng(116.3974);
device1.setStatus("在线");
device1.setDeviceType("SOIL_MONITOR");
List<RealTimeDataRespVO.RealTimeData> realTimeDataList1 = new ArrayList<>();
// 节点1数据
RealTimeDataRespVO.RealTimeData node1 = new RealTimeDataRespVO.RealTimeData();
node1.setNodeId(1);
node1.setNodeName("土壤温度监测点");
node1.setNodeType(1);
node1.setTemName("土壤温度");
node1.setTemUnit("°C");
node1.setTemValue(25.6);
node1.setTemValueStr("25.6°C");
node1.setTemAlarmStatus(0);
realTimeDataList1.add(node1);
// 节点2数据
RealTimeDataRespVO.RealTimeData node2 = new RealTimeDataRespVO.RealTimeData();
node2.setNodeId(2);
node2.setNodeName("土壤湿度监测点");
node2.setNodeType(2);
node2.setHumName("土壤湿度");
node2.setHumUnit("%");
node2.setHumValue("65.2");
node2.setHumValueStr("65.2%");
node2.setHumAlarmStatus("0");
realTimeDataList1.add(node2);
device1.setData(realTimeDataList1);
deviceList.add(device1);
// 模拟设备2的实时数据(如果请求了多个设备)
if (request.getDeviceAddrs() != null && request.getDeviceAddrs().contains("SOIL_002")) {
RealTimeDataRespVO.DeviceInfo device2 = new RealTimeDataRespVO.DeviceInfo();
device2.setDeviceAddr("SOIL_002");
device2.setDeviceName("土壤监测设备2");
device2.setLat(39.9193);
device2.setLng(116.4074);
device2.setStatus("离线");
device2.setDeviceType("SOIL_MONITOR");
List<RealTimeDataRespVO.RealTimeData> realTimeDataList2 = new ArrayList<>();
RealTimeDataRespVO.RealTimeData node3 = new RealTimeDataRespVO.RealTimeData();
node3.setNodeId(1);
node3.setNodeName("土壤温度监测点");
node3.setNodeType(1);
node3.setTemName("土壤温度");
node3.setTemUnit("°C");
node3.setTemValue(23.1);
node3.setTemValueStr("23.1°C");
node3.setTemAlarmStatus(0);
realTimeDataList2.add(node3);
device2.setData(realTimeDataList2);
deviceList.add(device2);
}
response.setData(deviceList);
return response;
}
private HistoryDataListPageRespVO callSoilHistoryDataApi(String token, HistoryDataListPageReqVO request) {
// 模拟调用土壤设备厂商API获取历史数据
HistoryDataListPageRespVO response = new HistoryDataListPageRespVO();
// 设置分页信息
response.setPages(request.getPages());
response.setLimit(request.getLimit());
// 模拟从厂商API获取的数据
List<HistoryDataListPageRespVO.Row> rows = generateMockHistoryData(
request.getDeviceAddr(),
Long.parseLong(request.getBeginTime()),
Long.parseLong(request.getEndTime()),
request.getNodeId()
);
int total = rows.size();
int totalPages = (int) Math.ceil((double) total / request.getLimit());
// 分页处理
int fromIndex = (request.getPages() - 1) * request.getLimit();
int toIndex = Math.min(fromIndex + request.getLimit(), total);
if (fromIndex >= total) {
response.setRows(new ArrayList<>());
} else {
response.setRows(rows.subList(fromIndex, toIndex));
}
response.setTotalPages(totalPages);
response.setTotal(total);
return response;
}
private List<HistoryDataListPageRespVO.Row> generateMockHistoryData(String deviceAddr, Long beginTime, Long endTime, String nodeId) {
List<HistoryDataListPageRespVO.Row> rows = new ArrayList<>();
Random random = new Random();
// 生成模拟数据 - 假设每小时间隔一条数据
long currentTime = beginTime;
int recordId = 1;
while (currentTime <= endTime) {
HistoryDataListPageRespVO.Row row = new HistoryDataListPageRespVO.Row();
row.setHistoryId(recordId++);
row.setNodeId(nodeId != null ? Integer.parseInt(nodeId) : 1);
row.setDeviceAddress(deviceAddr);
// 模拟温度数据
double tempValue = 20 + random.nextDouble() * 15; // 20-35°C
row.setTemValue(tempValue);
row.setTemStr(String.format("%.1f°C", tempValue));
// 模拟湿度数据
double humidityValue = 30 + random.nextDouble() * 40; // 30-70%
row.setHumValue(humidityValue);
row.setHumStr(String.format("%.1f%%", humidityValue));
// 模拟报警状态(5%的概率报警)
row.setAlarmStatus(random.nextDouble() < 0.05 ? 1 : 0);
row.setRecordTime(currentTime);
rows.add(row);
currentTime += 3600000; // 增加1小时
}
return rows;
}
private void saveHistoryDataToDatabase(HistoryDataListPageRespVO response, HistoryDataListPageReqVO request) {
if (historyDataStorageService != null && response.getRows() != null) {
// 转换为实体类
List<HistoryDataStorageService.HistoryDataEntity> entities = response.getRows().stream()
.map(row -> {
HistoryDataStorageService.HistoryDataEntity entity = new HistoryDataStorageService.HistoryDataEntity();
entity.setHistoryId(row.getHistoryId());
entity.setNodeId(row.getNodeId());
entity.setDeviceAddress(row.getDeviceAddress());
entity.setTemStr(row.getTemStr());
entity.setTemValue(row.getTemValue());
entity.setHumStr(row.getHumStr());
entity.setHumValue(row.getHumValue());
entity.setAlarmStatus(row.getAlarmStatus());
entity.setRecordTime(row.getRecordTime());
return entity;
})
.collect(Collectors.toList());
// 保存到模拟数据库
historyDataStorageService.saveHistoryData(request.getDeviceAddr(), entities);
}
}
}

View File

@@ -0,0 +1,332 @@
package cn.iocoder.yudao.module.farm.manager.equipmentproducer.service.impl;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.DeviceListResponse;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.RealTimeDataResponse;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.UnifiedLoginResponse;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.UpdateDeviceResponse;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.wf.devicelist.DevicePageReqVO;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.wf.devicelist.DevicePageRespVO;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.wf.login.LoginReqVO;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.wf.login.LoginRespVO;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.wf.realtime.RealTimeDeviceDataReqVO;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.wf.realtime.RealTimeDeviceDataRespVO;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.wf.updateparams.UpdateDeviceStatusReqVO;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.dto.wf.updateparams.UpdateDeviceStatusRespVO;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.service.AbstractDeviceService;
import cn.iocoder.yudao.module.farm.manager.equipmentproducer.service.TokenCacheService;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @description: 水肥机
* @author: binda
* @date: 2025/9/25 20:42
*/
@Service
public class WaterFertilizerService extends AbstractDeviceService {
@Override
public String getDeviceType() {
return "water_fertilizer";
}
@Override
protected UnifiedLoginResponse doLogin(Object loginRequest) {
try {
// 转换请求参数
LoginReqVO wfRequest = (LoginReqVO) loginRequest;
// TODO: 2025/9/25 调用水肥机设备厂商API POST 请求方式
LoginRespVO vendorResponse = callWFVendorApi(wfRequest);
// 转换为统一响应格式
return convertToUnifiedResponse(vendorResponse);
} catch (Exception e) {
return UnifiedLoginResponse.error("水肥机设备登录失败: " + e.getMessage());
}
}
@Override
protected DeviceListResponse doGetDeviceList(String token, Object listRequest) {
try {
// 转换请求参数
DevicePageReqVO wfRequest = (DevicePageReqVO) listRequest;
// TODO: 2025/9/25 调用水肥机设备厂商API获取设备列表 GET 请求方式
DevicePageRespVO vendorResponse = callWFDeviceListApi(token, wfRequest);
// 转换为统一响应格式
return convertToUnifiedDeviceResponse(vendorResponse);
} catch (Exception e) {
return DeviceListResponse.error("水肥机设备列表获取失败: " + e.getMessage());
}
}
@Override
protected RealTimeDataResponse doGetRealTimeData(String token, Object realTimeRequest) {
try {
// 转换请求参数
RealTimeDeviceDataReqVO wfRequest = (RealTimeDeviceDataReqVO) realTimeRequest;
// TODO: 2025/9/26 调用水肥机设备厂商API获取实时数据
RealTimeDeviceDataRespVO vendorResponse = callWFRealTimeDataApi(token, wfRequest);
// 直接返回原始响应数据,不进行统一转换
return RealTimeDataResponse.success(vendorResponse);
} catch (Exception e) {
return RealTimeDataResponse.error("水肥机设备实时数据获取失败: " + e.getMessage());
}
}
@Override
protected UpdateDeviceResponse doUpdateDeviceProperties(String token, Object updateRequest) {
try {
// 转换请求参数
UpdateDeviceStatusReqVO wfRequest = (UpdateDeviceStatusReqVO) updateRequest;
// TODO: 2025/9/26 调用水肥机设备厂商API更新设备属性
UpdateDeviceStatusRespVO vendorResponse = callWFUpdateDeviceApi(token, wfRequest);
// 转换为统一响应格式
return convertToUnifiedUpdateResponse(vendorResponse);
} catch (Exception e) {
return UpdateDeviceResponse.error("水肥机设备属性更新失败: " + e.getMessage());
}
}
private LoginRespVO callWFVendorApi(LoginReqVO request) {
// 模拟调用水肥机设备厂商API
LoginRespVO response = new LoginRespVO();
response.setCode("200");
response.setMsg("成功");
response.setToken("wf-token-" + System.currentTimeMillis());
return response;
}
private UnifiedLoginResponse convertToUnifiedResponse(LoginRespVO vendorResponse) {
if (!"200".equals(vendorResponse.getCode())) {
return UnifiedLoginResponse.error(vendorResponse.getMsg());
}
// 水肥机响应缺少一些字段,设置默认值
long currentTime = System.currentTimeMillis();
UnifiedLoginResponse.LoginData loginData = new UnifiedLoginResponse.LoginData(
"wf-login-sign-" + currentTime, // 生成登录标识
currentTime, // token生成时间
currentTime + 3600000 * 2, // 2小时后过期
vendorResponse.getToken() // 实际token
);
return UnifiedLoginResponse.success(loginData);
}
private DevicePageRespVO callWFDeviceListApi(String token, DevicePageReqVO request) {
// 模拟调用水肥机设备厂商API
DevicePageRespVO response = new DevicePageRespVO();
response.setCode(200);
response.setMsg("成功");
response.setTotal(2);
List<DevicePageRespVO.Row> rows = new ArrayList<>();
// 模拟设备数据
DevicePageRespVO.Row row1 = new DevicePageRespVO.Row();
row1.setDeviceId(1L);
row1.setCode("WF_001");
row1.setName("水肥机设备1");
row1.setLng(116.3974);
row1.setLat(39.9093);
rows.add(row1);
DevicePageRespVO.Row row2 = new DevicePageRespVO.Row();
row2.setDeviceId(2L);
row2.setCode("WF_002");
row2.setName("水肥机设备2");
row2.setLng(116.4074);
row2.setLat(39.9193);
rows.add(row2);
response.setRows(rows);
return response;
}
private DeviceListResponse convertToUnifiedDeviceResponse(DevicePageRespVO vendorResponse) {
if (vendorResponse.getCode() != 200) {
return DeviceListResponse.error(vendorResponse.getMsg());
}
List<TokenCacheService.DeviceInfo> unifiedDevices = vendorResponse.getRows().stream()
.map(vendorDevice -> {
TokenCacheService.DeviceInfo device = new TokenCacheService.DeviceInfo();
device.setDeviceCode(vendorDevice.getCode());
device.setDeviceName(vendorDevice.getName());
// 保存原始设备信息到extraInfo
Map<String, Object> extraInfo = new HashMap<>();
extraInfo.put("deviceId", vendorDevice.getDeviceId());
extraInfo.put("deptId", vendorDevice.getDeptId());
extraInfo.put("lng", vendorDevice.getLng());
extraInfo.put("lat", vendorDevice.getLat());
extraInfo.put("farmland", vendorDevice.getFarmland());
device.setExtraInfo(extraInfo);
return device;
})
.collect(Collectors.toList());
return DeviceListResponse.success(unifiedDevices);
}
private RealTimeDeviceDataRespVO callWFRealTimeDataApi(String token, RealTimeDeviceDataReqVO request) {
// 模拟调用水肥机设备厂商API获取实时数据
RealTimeDeviceDataRespVO response = new RealTimeDeviceDataRespVO();
response.setDeviceId(request.getDeviceId());
response.setName("水肥机设备-" + request.getDeviceId());
response.setStateText("在线");
response.setStateValue("online");
List<RealTimeDeviceDataRespVO.ControlProperty> controlProperties = new ArrayList<>();
// 模拟控制属性1 - 水泵状态
RealTimeDeviceDataRespVO.ControlProperty prop1 = new RealTimeDeviceDataRespVO.ControlProperty();
prop1.setId("pump_switch");
prop1.setName("水泵开关");
prop1.setOperateType("switch");
prop1.setOperateValue("on,off");
prop1.setValue("on");
prop1.setFormatValue("开启");
controlProperties.add(prop1);
// 模拟控制属性2 - 施肥状态
RealTimeDeviceDataRespVO.ControlProperty prop2 = new RealTimeDeviceDataRespVO.ControlProperty();
prop2.setId("fertilizer_switch");
prop2.setName("施肥开关");
prop2.setOperateType("switch");
prop2.setOperateValue("on,off");
prop2.setValue("off");
prop2.setFormatValue("关闭");
controlProperties.add(prop2);
// 模拟控制属性3 - 流量监测
RealTimeDeviceDataRespVO.ControlProperty prop3 = new RealTimeDeviceDataRespVO.ControlProperty();
prop3.setId("flow_rate");
prop3.setName("水流量");
prop3.setOperateType("monitor");
prop3.setOperateValue("");
prop3.setValue("12.5");
prop3.setFormatValue("12.5 L/min");
controlProperties.add(prop3);
// 模拟控制属性4 - EC值
RealTimeDeviceDataRespVO.ControlProperty prop4 = new RealTimeDeviceDataRespVO.ControlProperty();
prop4.setId("ec_value");
prop4.setName("EC值");
prop4.setOperateType("monitor");
prop4.setOperateValue("");
prop4.setValue("2.1");
prop4.setFormatValue("2.1 mS/cm");
controlProperties.add(prop4);
// 模拟控制属性5 - PH值
RealTimeDeviceDataRespVO.ControlProperty prop5 = new RealTimeDeviceDataRespVO.ControlProperty();
prop5.setId("ph_value");
prop5.setName("PH值");
prop5.setOperateType("monitor");
prop5.setOperateValue("");
prop5.setValue("6.8");
prop5.setFormatValue("6.8");
controlProperties.add(prop5);
response.setControlProperties(controlProperties);
return response;
}
private UpdateDeviceStatusRespVO callWFUpdateDeviceApi(String token, UpdateDeviceStatusReqVO request) {
// 模拟调用水肥机设备厂商API更新设备属性
UpdateDeviceStatusRespVO response = new UpdateDeviceStatusRespVO();
// 模拟设备控制逻辑
String deviceId = request.getDeviceId();
String switchName = request.getParamStr().getValue1();
String newValue = request.getParamStr().getValue11();
System.out.println("更新水肥机设备属性 - 设备ID: " + deviceId +
", 开关名称: " + switchName + ", 新值: " + newValue);
// 模拟不同的控制逻辑
if ("pump_switch".equals(switchName)) {
if ("on".equals(newValue)) {
System.out.println("水泵开关已开启");
response.setCode(200);
response.setMsg("水泵开启成功");
} else if ("off".equals(newValue)) {
System.out.println("水泵开关已关闭");
response.setCode(200);
response.setMsg("水泵关闭成功");
} else {
response.setCode(400);
response.setMsg("无效的水泵开关值: " + newValue);
}
} else if ("fertilizer_switch".equals(switchName)) {
if ("on".equals(newValue)) {
System.out.println("施肥开关已开启");
response.setCode(200);
response.setMsg("施肥开启成功");
} else if ("off".equals(newValue)) {
System.out.println("施肥开关已关闭");
response.setCode(200);
response.setMsg("施肥关闭成功");
} else {
response.setCode(400);
response.setMsg("无效的施肥开关值: " + newValue);
}
} else if ("water_flow".equals(switchName)) {
try {
double flowRate = Double.parseDouble(newValue);
if (flowRate > 0 && flowRate <= 100) {
System.out.println("水流量设置为: " + flowRate + " L/min");
response.setCode(200);
response.setMsg("水流量设置成功");
} else {
response.setCode(400);
response.setMsg("水流量值超出范围 (0-100 L/min)");
}
} catch (NumberFormatException e) {
response.setCode(400);
response.setMsg("无效的水流量值: " + newValue);
}
} else {
response.setCode(404);
response.setMsg("不支持的设备开关: " + switchName);
}
return response;
}
private UpdateDeviceResponse convertToUnifiedUpdateResponse(UpdateDeviceStatusRespVO vendorResponse) {
if (vendorResponse.getCode() == 200) {
return UpdateDeviceResponse.success(vendorResponse);
} else {
return UpdateDeviceResponse.error(vendorResponse.getMsg());
}
}
}

View File

@@ -378,6 +378,7 @@ public class BaseInfoServiceImpl implements BaseInfoService {
public CommonResult<List<FarmBaseInfoRespVO>> getBaseInfoList(FarmBaseInfoReqVO req) {
LambdaQueryWrapperX<BaseInfoDO> wrapperX = new LambdaQueryWrapperX<>();
wrapperX.eq(BaseInfoDO::getTenantId, req.getTenantId());
wrapperX.eqIfPresent(BaseInfoDO::getId, req.getBaseId());
wrapperX.eq(BaseInfoDO::getDeleted, CommonConstants.NO_CODE);
List<BaseInfoDO> list = baseInfoMapper.selectList(wrapperX);

View File

@@ -103,6 +103,11 @@ public interface DeviceInfoService {
* @return
*/
List<DeviceInfoDO> getBaseAndDeviceInfoList(String baseId);
List<DeviceInfoDO> getBaseAndDeviceInfoList(String baseId,String plotId);
Map<String, String> getDeviceTypeNameMap(String tenantId);
List<String> getImage(String deviceType);
DeviceStateRespVO countDeviceState(String baseId, String tenantId);
}

View File

@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.farm.service.deviceinfo;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.farm.controller.admin.api.vo.*;
import cn.iocoder.yudao.module.farm.controller.admin.deviceinfo.vo.DeviceInfoPageReqVO;
import cn.iocoder.yudao.module.farm.controller.admin.deviceinfo.vo.DeviceInfoSaveReqVO;
@@ -56,8 +57,8 @@ public class DeviceInfoServiceImpl implements DeviceInfoService {
private BaseInfoMapper baseInfoMapper;
@Resource
private PlotInfoMapper plotInfoMapper;
@Resource
private DeviceManager deviceManager;
// @Resource
// private DeviceManager deviceManager;
@Override
public String createDeviceInfo(DeviceInfoSaveReqVO createReqVO) {
@@ -139,9 +140,9 @@ public class DeviceInfoServiceImpl implements DeviceInfoService {
target.setDeviceTypeName(map.getOrDefault(target.getDeviceType(), ""));
// 从设备商服务 获取设备实时/历史数据
DeviceInfoDetailV2RespVO realData = deviceManager.getRealData(id);
target.setSoilInfo(realData.getSoilInfo());
target.setWfInfo(realData.getWfInfo());
// DeviceInfoDetailV2RespVO realData = deviceManager.getRealData(id);
// target.setSoilInfo(realData.getSoilInfo());
// target.setWfInfo(realData.getWfInfo());
return target;
}
@@ -181,7 +182,7 @@ public class DeviceInfoServiceImpl implements DeviceInfoService {
dtList.add(new DeviceStateRespVO.DeviceType(v1, v2));
}
DeviceStateRespVO res = deviceInfoMapper.countDeviceState(req.getTenantId());
DeviceStateRespVO res = deviceInfoMapper.countDeviceState("",req.getTenantId());
res.setDeviceTypeList(dtList);
return success(res);
}
@@ -196,6 +197,17 @@ public class DeviceInfoServiceImpl implements DeviceInfoService {
return deviceInfoMapper.selectList(DeviceInfoDO::getBaseId,baseId);
}
@Override
public List<DeviceInfoDO> getBaseAndDeviceInfoList(String baseId, String plotId) {
LambdaQueryWrapperX<DeviceInfoDO> wrapperX = new LambdaQueryWrapperX<>();
wrapperX.eq(DeviceInfoDO::getTenantId, String.valueOf(getLoginUser().getTenantId()));
wrapperX.eq(DeviceInfoDO::getDeleted, CommonConstants.NO_CODE);
wrapperX.eqIfPresent(DeviceInfoDO::getBaseId, baseId);
wrapperX.eqIfPresent(DeviceInfoDO::getPlotId, plotId);
return deviceInfoMapper.selectList(wrapperX);
}
@Override
public Map<String, String> getDeviceTypeNameMap(String tenantId) {
List<DeviceClassifyDO> deviceClassifyDOS = deviceClassifyMapper.selectList(DeviceClassifyDO::getTenantId, tenantId);
@@ -204,4 +216,14 @@ public class DeviceInfoServiceImpl implements DeviceInfoService {
.collect(Collectors.toMap(e -> e.getId().toString(), DeviceClassifyDO::getName));
}
@Override
public List<String> getImage(String deviceType) {
DeviceClassifyDO dc = deviceClassifyMapper.selectOne(DeviceClassifyDO::getId, deviceType);
return dc.getImageList();
}
@Override
public DeviceStateRespVO countDeviceState(String baseId,String tenantId) {
return deviceInfoMapper.countDeviceState(baseId,tenantId);
}
}

View File

@@ -250,7 +250,7 @@ public class HomeServiceImpl implements HomeService {
private BaseOverviewInfo getCompanyInfo() {
LoginUser user = getLoginUser();
int baseCount = baseInfoService.getBaseCount(user.getTenantId()); // 基地数量
int cropCount = cropFarmingCenterService.getCropCount(user.getTenantId()); // 作物数mong量
int cropCount = cropFarmingCenterService.getCropCount(user.getTenantId()); // 作物数量
// Double temp = cropFarmingCenterService.getTotalArea(user.getTenantId()); // 总面积
Double temp = baseInfoService.getBaseTotalArea(user.getTenantId().toString()); // 总面积
int totalArea = 0;

View File

@@ -8,6 +8,9 @@ import cn.iocoder.yudao.module.farm.controller.admin.productionplan.vo.Productio
import cn.iocoder.yudao.module.farm.dal.dataobject.productionplan.ProductionPlanDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.util.List;
@@ -201,4 +204,27 @@ public interface ProductionPlanService {
* @return 获取该地块的计划数量
*/
int getPlanCount(String plotId);
}
/**
* 基地维护 统计在种物种,地块,地块面积
* @param baseId
* @param tenantId
* @return int[0]: 在种作物数量
* int[1]: 在种地块数量
* int[2]: 在种地块面积
*/
CultivatingInfo getCultivatingInfo(String baseId, String tenantId);
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class CultivatingInfo {
// 在种物种数量
private Integer cultivatingSpeciesCount;
// 在种地块数量
private Integer cultivatingPlotCount;
// 在种地块面积
// private Integer cultivatingPlotAreaCount;
}
}

View File

@@ -12,6 +12,7 @@ import cn.iocoder.yudao.module.farm.dal.dataobject.baseinfo.BaseInfoDO;
import cn.iocoder.yudao.module.farm.dal.dataobject.specieslibrary.SpeciesLibraryDO;
import cn.iocoder.yudao.module.farm.dal.mysql.baseinfo.BaseInfoMapper;
import cn.iocoder.yudao.module.farm.dal.mysql.specieslibrary.SpeciesLibraryMapper;
import cn.iocoder.yudao.module.farm.enums.CommonConstants;
import cn.iocoder.yudao.module.farm.utils.DateUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.commons.lang3.ObjectUtils;
@@ -141,6 +142,7 @@ public class ProductionPlanServiceImpl implements ProductionPlanService {
QueryWrapper<ProductionPlanDO> qw = new QueryWrapper<>();
qw.eq("tenant_id", user.getTenantId());
qw.eq("plan_status", PLAN_STATUS_2);
List<ProductionPlanDO> productionPlanDOS = productionPlanMapper.selectList(qw);
// 按基地ID分组
@@ -406,4 +408,13 @@ public class ProductionPlanServiceImpl implements ProductionPlanService {
List<ProductionPlanDO> planDOList = productionPlanMapper.selectList("plot_id", plotId);
return planDOList.size();
}
@Override
public CultivatingInfo getCultivatingInfo(String baseId, String tenantId) {
int cultivatingSpeciesCount = this.getGrowingCropCount(baseId);
int cultivatingPlotCount = this.getGrowingPlotCount(baseId);
return new CultivatingInfo(cultivatingSpeciesCount,cultivatingPlotCount);
}
}

View File

@@ -36,6 +36,15 @@
SUM(CASE WHEN device_state = 1 THEN 1 ELSE 0 END) as totalOnlineDevices,
SUM(CASE WHEN device_state != 1 THEN 1 ELSE 0 END) as totalOfflineDevices
FROM farm_device_info
WHERE tenant_id = #{tenantId} AND deleted = b'0'
<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>

View File

@@ -14,9 +14,9 @@
resultType="cn.iocoder.yudao.module.farm.controller.admin.api.vo.FarmingDistributionRespVO">
SELECT
COUNT(*) as totalCount,
SUM(CASE WHEN task_status = 0 THEN 1 ELSE 0 END) as notStart,
SUM(CASE WHEN task_status = 1 THEN 1 ELSE 0 END) as executingStatusCount,
SUM(CASE WHEN task_status = 2 THEN 1 ELSE 0 END) as completedStatusCount
COALESCE(SUM(CASE WHEN task_status = 0 THEN 1 ELSE 0 END),0) as notStart,
COALESCE(SUM(CASE WHEN task_status = 1 THEN 1 ELSE 0 END),0) as executingStatusCount,
COALESCE(SUM(CASE WHEN task_status = 2 THEN 1 ELSE 0 END),0) as completedStatusCount
FROM farm_farming_record
<where>
del_status = 0

View File

@@ -14,6 +14,7 @@
from farm_production_plan
where tenant_id = #{tenantId}
and deleted = 0
and plan_status = 2
</select>
<select id="getTotalArea" parameterType="Long" resultType="java.lang.Double">