Commit 2a1ab005 authored by wanghao's avatar wanghao

1 老化过程中 获取卡号,获取IOT状态调整中

parent a7ca757c
......@@ -203,7 +203,6 @@
<artifactId>zhmes-agecal-common</artifactId>
<version>${zehong.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
......
......@@ -134,6 +134,11 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.2.0-jre</version> <!-- 建议使用最新稳定版 -->
</dependency>
</dependencies>
</project>
......@@ -134,6 +134,8 @@ public class Constants
public static final String IOT_LOGIN_USERNAME = "iot.login.username";
public static final String IOT_LOGIN_ADDRESS = "iot.login.url";
public static final String IOT_LOGIN_PASSWORD = "iot.login.password";
public static final String IOT_LOGIN_TOKEN = "iot.login.token";
......
......@@ -219,7 +219,7 @@ public class PalletDeviceBinding extends BaseEntity
/**
* iot状态
* 0-异常 1-正常
* 0-异常 1-正常 2-Nb码为空 3-网络状态异常
*/
private Integer iotStatus;
......
......@@ -86,6 +86,8 @@ public interface PalletDeviceBindingMapper
public int batchUpdateNbCode(@Param("palletDeviceBindingList") List<PalletDeviceBinding> palletDeviceBindingList);
public int batchUpdateIotStatus(@Param("palletDeviceBindingList") List<PalletDeviceBinding> palletDeviceBindingList);
public int batchUpdateDeviceCodeAndUnbindingTime(@Param("palletDeviceBindingList") List<PalletDeviceBinding> palletDeviceBindingList);
public int unbindAllDevice(Long trayId);
......
......@@ -41,6 +41,8 @@ public interface IPalletDeviceBindingService
public void batchUpdateNbCode(List<PalletDeviceBinding> palletDeviceBindingList);
public void batchUpdateIotStatus(List<PalletDeviceBinding> palletDeviceBindingList);
/**
* 新增托盘绑定的设备列
*
......
......@@ -142,6 +142,11 @@ public class PalletDeviceBindingServiceImpl implements IPalletDeviceBindingServi
palletDeviceBindingMapper.batchUpdateNbCode(palletDeviceBindingList);
}
@Override
public void batchUpdateIotStatus(List<PalletDeviceBinding> palletDeviceBindingList) {
palletDeviceBindingMapper.batchUpdateIotStatus(palletDeviceBindingList);
}
/**
* 新增托盘绑定的设备列
*
......
......@@ -190,6 +190,8 @@ public class RobotArmCommandServiceImpl implements IRobotArmCommandService
Modbus4jUtils.writeCoil(master, 1, registerOffsetInt, false);
log.info("已发送断电指令 - 设备:{} 层:{}", tEquipmentInfo.getfEquipmentCode(), layer);
master.destroy();
// 如果正常 则发送 IOT请求
} catch (ModbusTransportException | ModbusInitException e) {
log.info("下料发送断电指令失败 - 设备:{} 层:{}", tEquipmentInfo.getfEquipmentCode(), layer);
throw new RuntimeException(e);
......
......@@ -2,11 +2,15 @@ package com.zehong.system.task;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.util.concurrent.RateLimiter;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.exception.ErrorResponseException;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.zehong.common.constant.Constants;
import com.zehong.common.constant.RoboticArmConstans;
import com.zehong.common.core.domain.AjaxResult;
import com.zehong.common.core.redis.RedisCache;
import com.zehong.common.utils.DateUtils;
import com.zehong.common.utils.http.HttpUtils;
import com.zehong.system.domain.*;
......@@ -18,20 +22,19 @@ import com.zehong.system.modbus.util.Modbus4jUtils;
import com.zehong.system.service.*;
import com.zehong.system.service.websocket.RobotArmWebSocketHandler;
import com.zehong.system.task.event.CheckPowerOnCommandEvent;
import com.zehong.system.task.event.IotStatusEvent;
import com.zehong.system.task.event.PowerOffCommandEvent;
import com.zehong.system.task.event.ReceivedNbCodeEvent;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
......@@ -58,7 +61,7 @@ public class AllCommandHandler {
@Resource
private DeviceTaskScheduler deviceTaskScheduler;
@Autowired
@Resource
private ISysConfigService configService;
@Resource
......@@ -72,9 +75,21 @@ public class AllCommandHandler {
@Resource
private IReceivedNbFailureHistoryService receivedNbFailureHistoryService;
@Resource
private RedisCache redisCache;
@Resource
private ISysConfigService sysConfigService;
// 使用常量定义索引,避免魔法数字
private static final int AGING_STAGE_INDEX = 3;
private static final int DEFAULT_HOURS = 72;
// 根据 IOT 接口文档的 QPS 限制调整,如果接口说限制 50 QPS,就设 45 留余量
private static final RateLimiter RATE_LIMITER = RateLimiter.create(2);
// 如果想更平滑(防止突发流量冲击),可以带预热期:
// private static final RateLimiter RATE_LIMITER = RateLimiter.create(20.0, 3, TimeUnit.SECONDS);
/**
* check是否启动 ,没启动就启动下 开始老化
*
......@@ -202,6 +217,272 @@ public class AllCommandHandler {
}
}
/**
* 接收到IOT状态
* @param event event
*/
/**
* 接收到IOT状态
*/
@Async("threadPoolTaskExecutor")
@EventListener(IotStatusEvent.class)
public void handleIotStatus(IotStatusEvent event) {
String storeyCode = event.getStoreyCode();
if (storeyCode == null) {
return;
}
// 1. 查询数据
List<PalletDeviceBinding> bindings = palletDeviceBindingService.listByStoreyCode(storeyCode);
if (bindings.isEmpty()) {
return;
}
// 2. 按主板码分组,并过滤掉无效数据(同时提前处理无需调接口的)
Map<String, List<PalletDeviceBinding>> groupByMotherboard = bindings.stream()
.filter(b -> StringUtils.isNotBlank(b.getNbCode())) // 只留 nbCode 有效的
.collect(Collectors.groupingBy(PalletDeviceBinding::getMotherboardCode));
// 处理 nbCode 为空的(直接设状态为 2,加入待更新列表)
List<PalletDeviceBinding> needUpdateList = new ArrayList<>();
for (PalletDeviceBinding binding : bindings) {
if (StringUtils.isBlank(binding.getNbCode())) {
binding.setIotStatus(2);
needUpdateList.add(binding);
}
// 顺便处理网络状态异常(但不要覆盖后续 IOT 查询结果,网络异常只是前置标记)
// 注意:如果网络状态 !=1,但 IOT 查询成功,应该以 IOT 为准,这里先不 set,留给后面判断
}
if (groupByMotherboard.isEmpty()) {
// 如果没有需要调接口的,直接批量更新后返回
if (!needUpdateList.isEmpty()) {
palletDeviceBindingService.batchUpdateIotStatus(needUpdateList);
}
return;
}
// 3. 获取 IOT Token(只获取一次)
String iotToken = getIOTToken();
if (StringUtils.isBlank(iotToken)) {
throw new RuntimeException("IOT token未获取");
}
// 4. 构造通用请求头
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", iotToken);
headers.put("Content-Type", "application/json");
String iotApiUrl = sysConfigService.directSelectConfigByKey(Constants.IOT_DEVICES_SCAN);
// 5. 循环处理每个主板码(带限流)
for (Map.Entry<String, List<PalletDeviceBinding>> entry : groupByMotherboard.entrySet()) {
String motherboardCode = entry.getKey();
List<PalletDeviceBinding> sameBoardDevices = entry.getValue();
// 取任意一个设备的 nbCode(因为同一主板下的 nbCode 理论上一致,但为了严谨取第一个非空的)
String nbCode = sameBoardDevices.stream()
.map(PalletDeviceBinding::getNbCode)
.filter(StringUtils::isNotBlank)
.findFirst()
.orElse(null);
if (nbCode == null) {
continue;
}
// ★★★ Guava 限流(阻塞等待令牌) ★★★
RATE_LIMITER.acquire();
// 调用 IOT 接口
Map<String, Object> params = new HashMap<>();
params.put("number", nbCode);
String result = null;
try {
result = HttpUtils.get(iotApiUrl, headers, params);
} catch (Exception e) {
log.error("调用IOT接口异常, motherboardCode: {}, nbCode: {}", motherboardCode, nbCode, e);
}
// 6. 根据结果设置状态(应用到该主板下的所有设备)
Integer statusToSet;
if (StringUtils.isNotBlank(result)) {
Map<String, String> dataMap = processIotData(result);
if (dataMap != null && !dataMap.isEmpty()) {
statusToSet = 1; // IOT数据正常
} else {
statusToSet = 0; // IOT数据为空
}
} else {
statusToSet = 0; // 请求失败
}
// 将状态设置给该组所有设备,并加入待更新列表
for (PalletDeviceBinding device : sameBoardDevices) {
device.setIotStatus(statusToSet);
needUpdateList.add(device);
}
}
// 7. 批量持久化所有改动(一次性提交)
if (!needUpdateList.isEmpty()) {
palletDeviceBindingService.batchUpdateIotStatus(needUpdateList);
}
}
/**
* 处理MES返回的数据
*/
private Map<String,String > processIotData(String data ) {
Map<String,String > deviceStatus = new HashMap<>();
// 根据实际业务需求处理数据
// 这里只是示例,具体实现根据您的业务逻辑
try {
JSONObject jsonObject = JSON.parseObject(data);
Object data1 = jsonObject.get("data");
if(data1 == null) {
return deviceStatus;
}
JSONObject jsonData = JSON.parseObject(data1.toString());
deviceStatus.put("number", jsonData.getString("number") == null ? "" : jsonData.getString("number"));
deviceStatus.put("title", jsonData.getString("title") == null ? "" : jsonData.getString("title"));
deviceStatus.put("deviceType", jsonData.getString("devices_type") == null ? "" : jsonData.getString("devices_type"));
deviceStatus.put("modelTitle", jsonData.getString("model_title") == null ? "" : jsonData.getString("model_title"));
deviceStatus.put("batchNum", jsonData.getString("batchNum") == null ? "" : jsonData.getString("batchNum"));
deviceStatus.put("gas", jsonData.getString("gas") == null ? "" : jsonData.getString("gas"));
deviceStatus.put("unit", jsonData.getString("unit") == null ? "" : jsonData.getString("unit"));
deviceStatus.put("status", jsonData.getString("report_status") == null ? "" : jsonData.getString("report_status"));
deviceStatus.put("concentration", jsonData.getString("report_nd") == null ? "" : jsonData.getString("report_nd"));
deviceStatus.put("reportTime", jsonData.getString("report_time") == null ? "" : jsonData.getString("report_time"));
// 处理数据逻辑...
return deviceStatus;
} catch (Exception e) {
return new HashMap<>();
}
}
/**
* 获取MES token
*/
private String getIOTToken() {
// 1. 先从Redis中获取token
Object tokenObj = redisCache.getCacheObject(Constants.IOT_LOGIN_TOKEN);
if (tokenObj != null) {
String token = tokenObj.toString();
// 检查token是否即将过期(提前5分钟刷新)
long expireTime = redisCache.getExpire(Constants.IOT_LOGIN_TOKEN, TimeUnit.MINUTES);
if (expireTime > 10) { // 大于10分钟,直接返回
return token;
}
}
// 2. 如果Redis中没有或即将过期,重新获取token
return refreshIOTToken();
}
/**
* 刷新MES token
*/
private String refreshIOTToken() {
try {
// 1. 获取登录配置信息
String loginUrl = sysConfigService.directSelectConfigByKey(Constants.IOT_LOGIN_ADDRESS);
String username = sysConfigService.directSelectConfigByKey(Constants.IOT_LOGIN_USERNAME);
String password = sysConfigService.directSelectConfigByKey(Constants.IOT_LOGIN_PASSWORD);
if (org.apache.commons.lang3.StringUtils.isAnyBlank(loginUrl, username, password)) {
throw new RuntimeException("MES登录配置信息不完整");
}
// 2. 构造登录参数
Map<String, String> loginParams = new HashMap<>();
loginParams.put("username", username);
loginParams.put("password", password);
// 3. 添加请求头
Map<String, String> headers = new HashMap<>();
// 3. 调用登录接口
String response = HttpUtils.postJson(loginUrl, headers ,loginParams);
// 5. 解析响应
if (org.apache.commons.lang3.StringUtils.isNotBlank(response)) {
// 尝试解析为JSON
try {
Map<String, Object> responseMap = JSON.parseObject(response, Map.class);
// 检查是否有错误
if (responseMap.containsKey("code") && !"200".equals(String.valueOf(responseMap.get("code")))) {
String errorMsg = responseMap.containsKey("msg") ?
String.valueOf(responseMap.get("msg")) : "登录失败";
System.err.println("登录失败: " + errorMsg);
return null;
}
// 根据实际响应结构提取token
String token = extractTokenFromResponse(responseMap);
if (org.apache.commons.lang3.StringUtils.isNotBlank(token)) {
// 存储token到Redis
redisCache.setCacheObject(Constants.IOT_LOGIN_TOKEN, token, 1, TimeUnit.HOURS);
System.out.println("登录成功,token: " + token);
return token;
}
} catch (Exception e) {
System.err.println("解析响应失败: " + e.getMessage());
e.printStackTrace();
}
}
return null;
} catch (Exception e) {
throw new RuntimeException("获取MES token失败");
}
}
/**
* 从响应中提取token(根据实际响应格式调整)
*/
private String extractTokenFromResponse(Map<String, Object> responseMap) {
// 根据实际响应结构提取token
// 例如:responseMap.get("data").get("token")
// 示例1:如果token在顶层
if (responseMap.containsKey("token")) {
return String.valueOf(responseMap.get("token"));
}
// 示例2:如果token在data对象中
if (responseMap.containsKey("data")) {
Object data = responseMap.get("data");
if (data instanceof Map) {
Map<?, ?> dataMap = (Map<?, ?>) data;
if (dataMap.containsKey("access_token")) {
return String.valueOf(dataMap.get("access_token"));
}
}
}
// 示例3:如果token在其他字段中
String[] possibleTokenKeys = {"access_token", "accessToken", "Authorization", "authToken"};
for (String key : possibleTokenKeys) {
if (responseMap.containsKey(key)) {
return String.valueOf(responseMap.get(key));
}
}
// 如果都找不到,打印整个响应用于调试
System.out.println("无法找到token,完整响应: " + JSON.toJSONString(responseMap));
return null;
}
/**
* 接收到NB码
* @param event event
......
package com.zehong.system.task.event;
import org.springframework.context.ApplicationEvent;
/**
* @author lenovo
* @date 2026/7/18
* @description IOTstatus event
* @description IOT status event
*/
public class IotStatusEvent{
public class IotStatusEvent extends ApplicationEvent {
private String storeyCode;
public IotStatusEvent(Object source, String storeyCode) {
super(source);
this.storeyCode = storeyCode;
}
public String getStoreyCode() {
return storeyCode;
}
public void setStoreyCode(String storeyCode) {
this.storeyCode = storeyCode;
}
}
......@@ -154,10 +154,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<select id="listByTrayCode" parameterType="string" resultMap="PalletDeviceBindingResult">
<include refid="selectPalletDeviceBindingVo"/>
where trayInfo.f_tray_code = #{trayCode}
and palDeviceBinding.f_motherboard_code is not null
and palDeviceBinding.f_motherboard_code != ''
</select>
<select id="listByStoreyCode" parameterType="string" resultMap="PalletDeviceBindingResult">
<include refid="selectPalletDeviceBindingVo"/>
where palDeviceBinding.f_storey_code = #{storeyCode}
and palDeviceBinding.f_motherboard_code is not null
and palDeviceBinding.f_motherboard_code != ''
</select>
<select id="selectPalletDeviceBindingById" parameterType="Long" resultMap="PalletDeviceBindingResult">
......@@ -437,6 +441,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where f_pallet_device_binding_id = #{item.palletDeviceBindingId} and f_motherboard_code is not null
</foreach>
</update>
<update id="batchUpdateIotStatus" parameterType="list">
<foreach collection="palletDeviceBindingList" item="item" index="index" separator=";">
UPDATE t_pallet_device_binding
SET f_iot_status = #{item.iotStatus}
where f_pallet_device_binding_id = #{item.palletDeviceBindingId}
</foreach>
</update>
<update id="batchUpdateDeviceCodeAndUnbindingTime" parameterType="list">
<foreach collection="palletDeviceBindingList" item="item" index="index" separator=";">
UPDATE t_pallet_device_binding
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment