From 65338aecc9dfc3709511394bc9fd93a38bb8e5d0 Mon Sep 17 00:00:00 2001 From: guoshengxiong <1923636941@qq.com> Date: Mon, 30 Jun 2025 15:09:10 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=B9=E6=8E=A5=E5=B0=8F=E8=90=A8=E5=A1=94?= =?UTF-8?q?=E5=90=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xmgl/modules/bigdevice/entity/Tower.java | 4 +- .../bigdevice/entity/TowerCurrentData.java | 6 +- .../bigdevice/entity/TowerWorkCycle.java | 2 + .../xmgl/modules/project/entity/Project.java | 9 +- .../java/com/zhgd/xmgl/task/TowerTask.java | 399 +++++++++++++++++- 5 files changed, 413 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/zhgd/xmgl/modules/bigdevice/entity/Tower.java b/src/main/java/com/zhgd/xmgl/modules/bigdevice/entity/Tower.java index 8b789d586..6cd7a58e7 100644 --- a/src/main/java/com/zhgd/xmgl/modules/bigdevice/entity/Tower.java +++ b/src/main/java/com/zhgd/xmgl/modules/bigdevice/entity/Tower.java @@ -266,13 +266,13 @@ public class Tower implements Serializable { */ @Excel(name = "纬度", width = 15) @ApiModelProperty(value = "纬度") - private java.lang.Integer latitude; + private java.lang.String latitude; /** * 经度 */ @Excel(name = "经度", width = 15) @ApiModelProperty(value = "经度") - private java.lang.Integer longtitude; + private java.lang.String longtitude; /** * 设备标识码 */ diff --git a/src/main/java/com/zhgd/xmgl/modules/bigdevice/entity/TowerCurrentData.java b/src/main/java/com/zhgd/xmgl/modules/bigdevice/entity/TowerCurrentData.java index 352b8b07e..a85fbec5f 100644 --- a/src/main/java/com/zhgd/xmgl/modules/bigdevice/entity/TowerCurrentData.java +++ b/src/main/java/com/zhgd/xmgl/modules/bigdevice/entity/TowerCurrentData.java @@ -105,10 +105,10 @@ public class TowerCurrentData implements Serializable { @ApiModelProperty(value = "幅度 必传参数") private java.lang.String ranger; /** - * 高度 必传参数 + * 吊钩运行高度 必传参数 */ - @Excel(name = "高度 必传参数", width = 15) - @ApiModelProperty(value = "高度 必传参数") + @Excel(name = "吊钩运行高度 必传参数", width = 15) + @ApiModelProperty(value = "吊钩运行高度 必传参数") private java.lang.String height; /** * 风速(m/s) diff --git a/src/main/java/com/zhgd/xmgl/modules/bigdevice/entity/TowerWorkCycle.java b/src/main/java/com/zhgd/xmgl/modules/bigdevice/entity/TowerWorkCycle.java index 3a958a96e..b6e2a5964 100644 --- a/src/main/java/com/zhgd/xmgl/modules/bigdevice/entity/TowerWorkCycle.java +++ b/src/main/java/com/zhgd/xmgl/modules/bigdevice/entity/TowerWorkCycle.java @@ -283,6 +283,8 @@ public class TowerWorkCycle implements Serializable { @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @ApiModelProperty(value = "模拟生成时间") private java.util.Date mockTime; + @ApiModelProperty(value = "小萨id") + private java.lang.String xiaoSaId; /** * 工作循环吊程 */ diff --git a/src/main/java/com/zhgd/xmgl/modules/project/entity/Project.java b/src/main/java/com/zhgd/xmgl/modules/project/entity/Project.java index 0d0e7689b..e046a2d41 100644 --- a/src/main/java/com/zhgd/xmgl/modules/project/entity/Project.java +++ b/src/main/java/com/zhgd/xmgl/modules/project/entity/Project.java @@ -338,7 +338,14 @@ public class Project implements Serializable { private Integer enableWorkerSafeWatch; @ApiModelProperty(value = "安全履职报警推送时间") private String workerSafeWatchTime; - + @ApiModelProperty(value = "1开启小萨(塔吊)") + private Integer enableXiaosa; + @ApiModelProperty(value = "小萨项目id") + private String xiaosaProjectId; + @ApiModelProperty(value = "小萨appSecret") + private String xiaosaAppKey; + @ApiModelProperty(value = "小萨appSecret") + private String xiaosaAppSecret; /** * runde平台token diff --git a/src/main/java/com/zhgd/xmgl/task/TowerTask.java b/src/main/java/com/zhgd/xmgl/task/TowerTask.java index 5ae02ebfe..bbd32a51a 100644 --- a/src/main/java/com/zhgd/xmgl/task/TowerTask.java +++ b/src/main/java/com/zhgd/xmgl/task/TowerTask.java @@ -5,6 +5,10 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.date.DateUnit; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpException; +import cn.hutool.http.HttpRequest; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; @@ -28,13 +32,14 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; -import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import java.util.stream.Collectors; @Slf4j @Component @@ -331,5 +336,397 @@ public class TowerTask { } } + /** + * 获取塔吊数据(小萨) 每5分钟触发任务 + */ + @SchedulerLock(name = "getXiaosaTowerData", lockAtMostFor = 1000 * 60 * 5, lockAtLeastFor = 1000 * 60 * 3) + @Scheduled(cron = "0 0/5 * * * ?") + @RequestMapping("getXiaosaTowerData") + public void getXiaosaTowerData() { + try { + log.info("获取塔吊数据(小萨)开始任务"); + List projects = projectMapper.selectList(new LambdaQueryWrapper() + .eq(Project::getEnableXiaosa, 1) + ); + for (Project project : projects) { + // 获取设备列表 + List devList = towerMapper.selectList(new LambdaQueryWrapper() + .eq(Tower::getProjectSn, project.getProjectSn())); + Map devMap = devList.stream().collect(Collectors.toMap(Tower::getDevSn, Function.identity(), (o1, o2) -> o1)); + if (CollUtil.isEmpty(devList)) { + continue; + } + JSONObject body = new JSONObject(); + body.put("appKey", project.getXiaosaAppKey()); + body.put("appSecret", project.getXiaosaAppSecret()); + String url = "https://api.open.ixiaosa.com/auth/app_access_token"; + log.info("获取塔吊数据(小萨)的token的url:{},body:{}", url, JSON.toJSONString(body)); + String result = HttpRequest.post(url) + .body(JSON.toJSONString(body)) + .timeout(2000) + .execute() + .body(); + log.info("获取塔吊数据(小萨)的token的result:{}", result); + if (StrUtil.isBlank(result)) { + continue; + } + com.gexin.fastjson.JSONObject rootJo = JSON.parseObject(result); + if (!Objects.equals(rootJo.getBoolean("success"), true)) { + continue; + } + String tokenHeader = "Bearer " + rootJo.getJSONObject("data").getString("appAccessToken"); + String xiaosaProjectId = project.getXiaosaProjectId(); + url = "https://api.device.open.ixiaosa.com/data/list_crane_param_data/" + xiaosaProjectId; + log.info("萨达 查询项目下塔机参数数据url:{}", url); + result = HttpRequest.get(url) + .header("Authorization", tokenHeader) + .execute().body(); + log.info("萨达 查询项目下塔机参数数据result:{}", result); + Map projectCraneId2DevSnMap = new HashMap<>(); + rootJo = JSON.parseObject(result); + if (!Objects.equals(rootJo.getBoolean("success"), true)) { + continue; + } + com.gexin.fastjson.JSONArray jsonArray = rootJo.getJSONArray("data"); + for (int i = 0; i < jsonArray.size(); i++) { + com.gexin.fastjson.JSONObject jsonObject = jsonArray.getJSONObject(i); + Tower tower = devMap.get(jsonObject.getString("deviceSn")); + if (tower == null) { + continue; + } + projectCraneId2DevSnMap.put(jsonObject.getString("projectCraneId"), tower.getDevSn()); + //修改塔吊设备信息 + //projectId 项目 ID String + //projectCraneId 项目塔机 ID String + //projectCraneNum 塔机名称/编号 String + //craneType 塔机类型 String FlatArm:平臂,SwingArm:动臂,Tower:塔头 + //jibArmLength 起重臂长 Float m + //balanceArmLength 平衡臂长 Float m + //craneHeight 塔机高度 Float m + //craneCapHeight 塔机塔帽高 Float m + //longitude 经度 Double ° + //latitude 纬度 Double ° + // x 相对坐标 X Float + // y 相对坐标 Y Float + //standardArmLength 标准臂长 Float m + //maxRadiusLoad 最大幅度起重量 Float t + //ratedLoad 额定吊重 Float t + tower.setForearmLength(jsonObject.getDouble("jibArmLength")); + tower.setPosteriorArmLength(jsonObject.getDouble("balanceArmLength")); + tower.setLongtitude(jsonObject.getString("longitude")); + tower.setLatitude(jsonObject.getString("latitude")); + tower.setRelatedX(jsonObject.getString("x")); + tower.setRelatedY(jsonObject.getString("y")); + towerMapper.updateById(tower); + } + if (CollUtil.isEmpty(projectCraneId2DevSnMap)) { + continue; + } + //保存塔吊实时数据 + saveTowerCurrentDataForXiaoSa(tokenHeader, xiaosaProjectId, devMap, projectCraneId2DevSnMap); + + //保存塔吊循环数据 + saveTowerWorkCycleForXiaoSa(tokenHeader, xiaosaProjectId, project.getProjectSn(),devMap, projectCraneId2DevSnMap); + + } + } catch (HttpException e) { + log.error("获取塔吊数据(小萨)开始任务err"+e); + } + } + + /** + * 保存塔吊循环数据(小萨) + * + * @param tokenHeader + * @param xiaosaProjectId + * @param projectSn + * @param projectCraneId2DevSnMap + */ + private void saveTowerWorkCycleForXiaoSa(String tokenHeader, String xiaosaProjectId, String projectSn, Map devMap, Map projectCraneId2DevSnMap) { + String url = "https://api.device.open.ixiaosa.com/data/list_crane_work_data/" + xiaosaProjectId; + log.info("萨达 查询项目下塔机实时吊装循环数据url:{}", url); + String result = HttpRequest.get(url) + .header("Authorization", tokenHeader) + .execute().body(); + log.info("萨达 查询项目下塔机实时吊装循环数据result:{}", result); + com.gexin.fastjson.JSONObject rootJo = JSON.parseObject(result); + if (!Objects.equals(rootJo.getBoolean("success"), true)) { + return; + } + com.gexin.fastjson.JSONArray jsonArray1 = rootJo.getJSONArray("data"); + List datas = new ArrayList<>(); + if (CollUtil.isNotEmpty(jsonArray1)) { + for (int i = 0; i < jsonArray1.size(); i++) { + com.gexin.fastjson.JSONObject json = jsonArray1.getJSONObject(i); + String xiansaId = json.getString("id"); + int count = towerWorkCycleService.count(new LambdaQueryWrapper() + .eq(TowerWorkCycle::getXiaoSaId, xiansaId)); + if (count > 0) { + continue; + } + TowerWorkCycle data = new TowerWorkCycle(); + + // 1. projectCraneId → devSn(设备识别码) + data.setDevSn(projectCraneId2DevSnMap.get(json.getString("projectCraneId"))); + Tower tower = devMap.get(data.getDevSn()); + data.setDevName(tower.getDevName()); + // 2. projectCraneNum(项目塔机编号/名称,无直接对应字段,可存储到额外字段或忽略) + // 如果 towerWorkCycle 有类似 craneName 的字段,可以设置: + // t.setCraneName(json.getString("projectCraneNum")); + + // 3. workLoadingType(吊装类型,2:有吊重,无直接对应字段,可用于判断是否设置吊重) +// int workLoadingType = json.getInt("workLoadingType"); +// if (workLoadingType == 2) { + // 有吊重时,设置相关参数 +// } + data.setLoading(String.valueOf(Double.parseDouble(json.getString("maxLoadWeight")) * 1000)); // t → kg + + // 4. upTime → startTime(起吊时间) + data.setStartTime(DateUtil.parseDateTime(json.getString("upTime"))); + + // 5. upAngle → slingStartRotation(起吊回转角度) + data.setSlingStartRotation(json.getString("upAngle")); + + // 6. upRadius → slingStartRange(起吊幅度) + data.setSlingStartRange(json.getString("upRadius")); + + // 7. upHookHeight → slingStartHeight(起吊吊钩高度) + data.setSlingStartHeight(json.getString("upHookHeight")); + + // 8. upLoadWeight(起吊吊重,无直接对应字段,可结合 maxLoadWeight 使用) + + // 9. upSafeLoad(起吊安全吊重,无直接对应字段) + + // 10. upTorquePercent(起吊力矩百分比,无直接对应字段) + + // 11. upFalls → workMultiple(起吊吊绳倍率) + data.setWorkMultiple(json.getInteger("upFalls")); + + // 12. upElevationAngle(起吊俯仰角度,无直接对应字段) + + // 13. upWindSpeed(起吊风速,用于 maxWindSpeed 计算) + double upWindSpeed = json.containsKey("upWindSpeed") ? json.getDouble("upWindSpeed") : 0; + + // 14. maxTime(力矩最大点记录时间,无直接对应字段) + + // 15. maxAngle(用于计算 maxAngle/minAngle) + double maxAngle = json.getDouble("maxAngle"); + + // 16. maxRadius(用于计算 maxRange/minRange) + double maxRadius = json.getDouble("maxRadius"); + + // 17. maxHookHeight(用于计算 maxHeight/minHeight) + double maxHookHeight = json.getDouble("maxHookHeight"); + + // 19. maxSafeLoad(最大安全吊重,无直接对应字段) + + // 20. maxTorquePercent → peakLoad(最大力矩百分比) + data.setPeakLoad(json.getString("maxTorquePercent")); + + // 21. maxFalls(最大吊绳倍率,无直接对应字段) + + // 22. maxElevationAngle(最大俯仰角度,无直接对应字段) + + // 23. maxWindSpeed(用于计算 maxWindSpeed) + double maxWindSpeed = json.containsKey("maxWindSpeed") ? json.getDouble("maxWindSpeed") : 0; + + // 24. downTime → endTime(落吊时间) + data.setEndTime(DateUtil.parseDateTime(json.getString("downTime"))); + + // 25. downAngle → slingEndRotation(落吊回转角度) + data.setSlingEndRotation(json.getString("downAngle")); + + // 26. downRadius → slingEndRange(落吊幅度) + data.setSlingEndRange(json.getString("downRadius")); + + // 27. downHookHeight → slingEndHeight(落吊吊钩高度) + data.setSlingEndHeight(json.getString("downHookHeight")); + + // 28. downLoadWeight(落吊吊重,无直接对应字段) + + // 29. downSafeLoad(落吊安全吊重,无直接对应字段) + + // 30. downTorquePercent(落吊力矩百分比,无直接对应字段) + + // 31. downFalls(落吊吊绳倍率,无直接对应字段) + + // 32. downElevationAngle(落吊俯仰角度,无直接对应字段) + + // 33. downWindSpeed(用于计算 maxWindSpeed) + double downWindSpeed = json.containsKey("downWindSpeed") ? json.getDouble("downWindSpeed") : 0; + + // 34. loadTime → workTime(起吊时长,单位 s) + data.setWorkTime(String.valueOf(DateUtil.between(data.getStartTime(), data.getEndTime(), DateUnit.SECOND))); + + // 35. realLoadWeight(实际吊重,无直接对应字段) + + // 计算最大/最小角度、幅度、高度 + double upAngle = json.getDouble("upAngle"); + double downAngle = json.getDouble("downAngle"); + data.setMaxAngle(String.valueOf(Math.max(upAngle, Math.max(maxAngle, downAngle)))); + data.setMinAngle(String.valueOf(Math.min(upAngle, Math.min(maxAngle, downAngle)))); + + double upRadius = json.getDouble("upRadius"); + double downRadius = json.getDouble("downRadius"); + data.setMaxRange(String.valueOf(Math.max(upRadius, Math.max(maxRadius, downRadius)))); + data.setMinRange(String.valueOf(Math.min(upRadius, Math.min(maxRadius, downRadius)))); + + double upHookHeight = json.getDouble("upHookHeight"); + double downHookHeight = json.getDouble("downHookHeight"); + data.setMaxHeight(String.valueOf(Math.max(upHookHeight, Math.max(maxHookHeight, downHookHeight)))); + data.setMinHeight(String.valueOf(Math.min(upHookHeight, Math.min(maxHookHeight, downHookHeight)))); + + // 计算最大风速 + data.setMaxWindSpeed(String.valueOf(Math.max(upWindSpeed, Math.max(maxWindSpeed, downWindSpeed)))); + data.setProjectSn(projectSn); + data.setAddTime(new Date()); + data.setXiaoSaId(xiansaId); + datas.add(data); + } + } + if (CollUtil.isNotEmpty(datas)) { + towerWorkCycleService.saveBatch(datas); + } + } + + /** + * 保存塔吊实时数据(小萨) + * + * @param tokenHeader + * @param xiaosaProjectId + * @param devMap + * @param projectCraneId2DevSnMap + */ + private void saveTowerCurrentDataForXiaoSa(String tokenHeader, String xiaosaProjectId, Map devMap, Map projectCraneId2DevSnMap) { + String url = "https://api.device.open.ixiaosa.com/data/list_crane_real_time_data/" + xiaosaProjectId; + log.info("萨达 查询项目下塔机实时数据url:{}", url); + String result = HttpRequest.get(url) + .header("Authorization", tokenHeader) + .execute().body(); + log.info("萨达 查询项目下塔机实时数据result:{}", result); + com.gexin.fastjson.JSONObject rootJo = JSON.parseObject(result); + if (!Objects.equals(rootJo.getBoolean("success"), true)) { + return; + } + com.gexin.fastjson.JSONArray jsonArray1 = rootJo.getJSONArray("data"); + List datas = new ArrayList<>(); + if (CollUtil.isNotEmpty(jsonArray1)) { + for (int i = 0; i < jsonArray1.size(); i++) { + //实时数据 + // "time": "2025-06-27 17:56:52", + // "projectCraneId": "1928738212753227778", + // "currentCraneHeight": 0.0, + // "radius": 23.94, + // "hookHeight": 37.64, + // "angle": 307.46, + // "loadWeight": 0.67, + // "safeLoad": 6.0, + // "torque": 16.0398, + // "torquePercent": 11.24, + // "falls": 2, + // "elevationAngle": 0.0, + // "windSpeed": 0.0, + // "infractionTypeList": "", + // "craneDriverName": "", + // "craneDriverIdCard": "", + // "projectCraneNum": "1#" + com.gexin.fastjson.JSONObject json = jsonArray1.getJSONObject(i); + TowerCurrentData data = new TowerCurrentData(); + data.setReciveTime(DateUtil.parseDateTime(json.getString("time"))); + data.setStartTime(data.getReciveTime()); + data.setDevSn(projectCraneId2DevSnMap.get(json.getString("projectCraneId"))); + Tower tower = devMap.get(data.getDevSn()); + data.setDevName(tower.getDevName()); + data.setHeight(json.getString("hookHeight")); + data.setRanger(json.getString("radius")); + data.setAngle(json.getString("angle")); + data.setLoading(Optional.ofNullable(json.getDouble("loadWeight")).map(m -> m * 1000 + "").orElse(null)); + if (json.getDouble("loadWeight") != null && json.getDouble("safeLoad") != null && json.getDouble("safeLoad").compareTo(0D) != 0) { + data.setLoadRatio(NumberUtil.roundStr(json.getDouble("loadWeight") / json.getDouble("safeLoad"), 4)); + } + data.setTorque(json.getString("torque")); + data.setTorqueRatio(json.getString("torquePercent")); + data.setRate(json.getString("falls")); + data.setObliguity(json.getString("elevationAngle")); + data.setWindspeed(json.getString("windSpeed")); + data.setDriverName(json.getString("craneDriverName")); + data.setDriverIdCard(json.getString("craneDriverIdCard")); + data.setRealFlag("1"); + data.setNoError(1); + String infractionTypeList = json.getString("infractionTypeList"); + Integer noAlarm = 1; + if (StrUtil.isNotBlank(infractionTypeList)) { + for (String alarmKey : StrUtil.split(infractionTypeList, ",")) { + switch (alarmKey) { + case "Collision_LeftRotationAlert": + data.setMultiNegAlarm(1); + break; + case "Collision_RightRotationAlert": + data.setMultiPosAlarm(1); + break; + case "Collision_RadiusForthAlert": + data.setMultiOutAlarm(1); + break; + case "Collision_RadiusBackAlert": + data.setMultiBackAlarm(1); + break; + case "RestrictedArea_LeftRotationAlert": + data.setForbidEntryNegAlarm(1); + break; + case "RestrictedArea_RightRotationAlert": + data.setForbidEntryPosAlarm(1); + break; + case "RestrictedArea_RadiusForthAlert": + data.setForbidSuspend4OutAlarm(1); + break; + case "RestrictedArea_RadiusBackAlert": + data.setForbidSuspend4BackAlarm(1); + break; + case "AngleRestrictedArea_LeftRotationAlert": + data.setForbidSuspend2NegAlarm(1); + break; + case "AngleRestrictedArea_RightRotationAlert": + data.setForbidSuspend2PosAlarm(1); + break; + case "Limit_LeftRotationAlert": + data.setNegAngleAlarm(1); + break; + case "Limit_RightRotationAlert": + data.setPosAngleAlarm(1); + break; + case "Limit_RadiusForthAlert": + data.setMaxRangeAlarm(1); + break; + case "Limit_RadiusBackAlert": + data.setMinRangeAlarm(1); + break; + case "Limit_OfHeightTopAlert": + data.setHeightAlarm(1); + break; + case "Limit_HeightBottomAlert": + data.setHeightLowerAlarm(1); + break; + case "Limit_LoadAlert": + data.setMomentAlarm(1); + break; + case "Limit_TorqueAlert": + data.setMomentAlarm(1); + break; + case "Limit_WindSpeedAlert": + data.setWindSpeedAlarm(1); + break; + } + } + noAlarm = 0; + } + data.setNoAlarm(noAlarm); + data.setProjectSn(tower.getProjectSn()); + datas.add(data); + } + } + if (CollUtil.isNotEmpty(datas)) { + towerCurrentDataService.saveBatch(datas); + } + } }