add:增加7月份的一些新增需求实现;

This commit is contained in:
2026-07-30 11:13:11 +08:00
parent 45e7b12dbd
commit cac00113a3
25 changed files with 731 additions and 177 deletions

View File

@@ -66,6 +66,8 @@ public interface AcsConfig {
String AutoCleanDays = "AutoCleanDays";
//最大任务下发时间
String MAXSENDTASKTIME = "maxSendTaskTime";
//任务超时时间阈值(分钟),超过该时间未完成的任务视为超时异常
String TASKTIMEOUTMINUTES = "taskTimeoutMinutes";
//指令下发立库
String INSTSENDLK = "instSendLk";
/**

View File

@@ -23,12 +23,14 @@ import org.nl.acs.ext.wms.service.AcsToWmsService;
import org.nl.acs.ext.wms.service.impl.AcsToWmsServiceImpl;
import org.nl.acs.history.ErrorUtil;
import org.nl.acs.instruction.domain.Instruction;
import org.nl.acs.instruction.enums.InstructionStatusEnum;
import org.nl.acs.instruction.service.InstructionService;
import org.nl.acs.instruction.service.impl.InstructionServiceImpl;
import org.nl.acs.log.LokiLog;
import org.nl.acs.log.LokiLogType;
import org.nl.acs.log.service.DeviceExecuteLogService;
import org.nl.acs.opc.DeviceAppService;
import org.nl.acs.task.enums.TaskStatusEnum;
import org.nl.acs.task.service.TaskService;
import org.nl.acs.task.service.dto.TaskDto;
import org.nl.acs.task.service.impl.TaskServiceImpl;
@@ -93,8 +95,9 @@ public class AgvNdcOneDeviceDriver extends AbstractDeviceDriver implements Devic
*/
@Data
private static class FeiShuErrorRecord {
private int num = 0; // 收到同一报警的次数
private long lastSendTime = 0; // 最后一次推送的时间戳(毫秒)
private int pushCount = 0; // 已推送同一报警的次数
private long lastSendTime = 0; // 最后一次推送的时间戳(毫秒)
private long lastReceiveTime = 0; // 最后一次收到报警的时间戳(毫秒),用于缓存过期判断
}
/**
@@ -103,7 +106,8 @@ public class AgvNdcOneDeviceDriver extends AbstractDeviceDriver implements Devic
private volatile String feiShuSendTime = "8:00-22:00";
private volatile String feiShuSendFre = "2";
private volatile long lastParamLoadTime = 0;
private static final long PARAM_CACHE_TIMEOUT = 600000; // 参数缓存超时时间:120秒
private static final long PARAM_CACHE_TIMEOUT = 600000; // 参数缓存超时时间:600秒
private static final long FEI_SHU_ERROR_CACHE_TIMEOUT = 5 * 60 * 1000; // 飞书报警缓存过期时间5分钟
String transportOrder = "";
boolean isCharge = false;
@@ -197,7 +201,7 @@ public class AgvNdcOneDeviceDriver extends AbstractDeviceDriver implements Devic
//到达取货点
//(需要WCS反馈)
} else if (phase == 0x03) {
inst.setExecute_status("1");
inst.setExecute_status(InstructionStatusEnum.BUSY.getIndex());
instructionService.update(inst);
//添加车号
if (StringUtils.isBlank(task.getCar_no())) {
@@ -212,7 +216,9 @@ public class AgvNdcOneDeviceDriver extends AbstractDeviceDriver implements Devic
//(需要WCS反馈)
} else if (phase == 0x05) {
data = ndcAgvService.sendAgvOneModeInst(phase, index, 0);
task.setTask_status("4");
inst.setExecute_status(InstructionStatusEnum.PICKUP.getIndex());
instructionService.update(inst);
task.setTask_status(TaskStatusEnum.FINISHEDMOVE.getIndex());
//车辆执行任务开始计时字段
if (StringUtils.isBlank(task.getTo_x())) {
task.setTo_x(DateUtil.now());
@@ -384,35 +390,28 @@ public class AgvNdcOneDeviceDriver extends AbstractDeviceDriver implements Devic
int endMinutes = endHour * 60 + endMinute;
// 判断当前时间是否在工作时间内
if (nowMinutes < startMinutes || nowMinutes >= endMinutes) {
// log.info("当前时间{}不在工作时间范围内({}),跳过发送飞书消息",
// DateUtil.formatTime(now), feiShuSendTime);
return;
}
}
}
// 2. 计数与间隔推送逻辑第1次收到不推送第2次收到首次推送之后按间隔推送
String cacheKey = this.getDeviceCode() + errorMessage;
// 2. 全局缓存过期清理清除超过5分钟未收到报警的记录
long currentTime = System.currentTimeMillis();
cleanExpiredFeiShuErrorCache(currentTime);
// 3. 计数与间隔推送逻辑首次推送至feiShuUrl1累计推送5次后同时推送至feiShuUrl1和feiShuUrl2
String cacheKey = this.getDeviceCode() + errorMessage;
FeiShuErrorRecord record = feiShuErrorCache.get(cacheKey);
if (record == null) {
// 第一次收到该报警num=1不推送
record = new FeiShuErrorRecord();
record.setNum(1);
feiShuErrorCache.put(cacheKey, record);
return;
}
// 每次收到报警,计数+1超过5次则清除缓存重新开始计数
record.setNum(record.getNum() + 1);
if (record.getNum() > 5) {
feiShuErrorCache.remove(cacheKey);
return;
}
if (record.getNum() > 2) {
// num > 2已推送过按间隔时间控制后续推送
// 更新最后收到报警的时间
record.setLastReceiveTime(currentTime);
// 频率控制:非首次推送时,检查是否在推送间隔内
if (record.getPushCount() > 0) {
int feiShuSendFreMin = Integer.parseInt(feiShuSendFre);
long diffTime = currentTime - record.getLastSendTime();
if (diffTime < feiShuSendFreMin * 60000L) {
log.debug("设备{}的{}在{}分钟内已有过提醒,过滤掉本次重复提醒(距今{}ms",
log.info("设备{}的{}在{}分钟内已有过提醒,过滤掉本次重复提醒(距今{}ms",
this.getDeviceCode(), errorMessage, feiShuSendFreMin, diffTime);
return;
}
@@ -460,22 +459,35 @@ public class AgvNdcOneDeviceDriver extends AbstractDeviceDriver implements Devic
post.put("zh_cn", zh_cn);
content.put("post", post);
requestBody.put("content", content);
String feiShuUrl = "https://open.feishu.cn/open-apis/bot/v2/hook/c3fcb100-72e0-48e6-bdf6-e266e67858a1";
// 5. 发送 HTTP 请求到飞书机器人
String response = HttpRequest.post(feiShuUrl)
String feiShuUrl1 = "https://open.feishu.cn/open-apis/bot/v2/hook/c3fcb100-72e0-48e6-bdf6-e266e67858a1";
String feiShuUrl2 = "https://open.feishu.cn/open-apis/bot/v2/hook/c3fcb100-72e0-48e6-bdf6-e266e67858a2";
// 5. 发送 HTTP 请求到飞书机器人(首次~第5次仅推送到feiShuUrl1第6次起同时推送到feiShuUrl1和feiShuUrl2
boolean pushToBoth = record.getPushCount() >= 5;
String response1 = HttpRequest.post(feiShuUrl1)
.header("Content-Type", "application/json")
.body(requestBody.toJSONString())
.timeout(15000) // 5秒超时
.timeout(15000) // 15秒超时
.execute()
.body();
if (pushToBoth) {
log.info("设备{}的{}已连续推送{}次同时推送至feiShuUrl2",
this.getDeviceCode(), errorMessage, record.getPushCount() + 1);
HttpRequest.post(feiShuUrl2)
.header("Content-Type", "application/json")
.body(requestBody.toJSONString())
.timeout(15000)
.execute()
.body();
}
// 6. 检查响应结果并记录日志
JSONObject jsonResponse = JSONObject.parseObject(response);
JSONObject jsonResponse = JSONObject.parseObject(response1);
if (jsonResponse != null && jsonResponse.getIntValue("code") == 0) {
//log.info("设备{}的故障消息发送成功:{}", this.getDeviceCode(), errorMessage);
// 更新记录:记录最后发送时间
log.info("设备{}的报警消息发送成功:{}", this.getDeviceCode(), errorMessage);
// 更新记录:记录最后发送时间和推送计数
record.setLastSendTime(currentTime);
record.setPushCount(record.getPushCount() + 1);
} else {
log.error("设备{}的故障消息发送失败:{}", this.getDeviceCode(), response);
log.error("设备{}的故障消息发送失败:{}", this.getDeviceCode(), response1);
}
} catch (Exception e) {
@@ -507,5 +519,16 @@ public class AgvNdcOneDeviceDriver extends AbstractDeviceDriver implements Devic
}
}
/**
* 清理过期的飞书报警错误缓存遍历所有缓存条目清除超过5分钟未收到报警的记录
*
* @param currentTime 当前时间戳(毫秒)
*/
private void cleanExpiredFeiShuErrorCache(long currentTime) {
long expireThreshold = currentTime - FEI_SHU_ERROR_CACHE_TIMEOUT;
feiShuErrorCache.entrySet().removeIf(entry ->
entry.getValue().getLastReceiveTime() > 0
&& entry.getValue().getLastReceiveTime() < expireThreshold);
}
}

View File

@@ -17,10 +17,10 @@ public enum InstructionStatusEnum {
*/
READY("0", "READY", "就绪"),
BUSY("1", "BUSY", "执行中"),
PICKUP("1.1", "FINISHED_MOVE", "取货完成"),
RELEASE("1.2", "FINISHED_PUT", "放货完成"),
FINISHED("2", "FINISHED", "完成"),
CANCEL("3", "CANCEL", "取消"),
ERROR("99", "CANCEL", "异常");
/**

View File

@@ -45,6 +45,7 @@ import org.nl.acs.route.service.dto.RouteLineDto;
import org.nl.acs.route.service.impl.RouteLineServiceImpl;
import org.nl.acs.task.TaskInstructionLock;
import org.nl.acs.task.domain.Task;
import org.nl.acs.task.enums.TaskStatusEnum;
import org.nl.acs.task.service.TaskService;
import org.nl.acs.task.service.dto.TaskDto;
import org.nl.acs.task.service.mapper.TaskMapper;
@@ -188,7 +189,9 @@ public class InstructionServiceImpl extends CommonServiceImpl<InstructionMapper,
String priority = (String) whereJson.get("priority");
String is_over = (String) whereJson.get("is_over");
String instruction_type = (String) whereJson.get("instruction_type");
if (StringUtils.isNotBlank(status) && TaskStatusEnum.FINISHEDMOVE.getIndex().equals(status)) {
status = InstructionStatusEnum.PICKUP.getIndex();
}
IPage<InstructionMybatis> queryPage = PageUtil.toMybatisPage(page);
LambdaQueryWrapper<InstructionMybatis> wrapper = new LambdaQueryWrapper<>();
// wrapper.eq(InstructionMybatis::getIs_delete,0);
@@ -199,7 +202,7 @@ public class InstructionServiceImpl extends CommonServiceImpl<InstructionMapper,
wrapper.eq(StringUtils.isNotBlank(status), InstructionMybatis::getInstruction_status, status);
wrapper.and(StringUtils.isNotBlank(point_code), instructionMybatis -> instructionMybatis.like(InstructionMybatis::getStart_point_code, point_code).or().like(InstructionMybatis::getNext_point_code, point_code));
wrapper.eq(StringUtils.isNotBlank(instruction_type), InstructionMybatis::getInstruction_type, instruction_type);
wrapper.le(InstructionMybatis::getInstruction_status, 1);
wrapper.lt(InstructionMybatis::getInstruction_status, InstructionStatusEnum.FINISHED.getIndex());
if (!StrUtil.isEmpty(is_over)) {
if (StrUtil.equals(is_over, CommonFinalParam.ONE)) {
wrapper.ge(InstructionMybatis::getInstruction_status, InstructionStatusEnum.FINISHED.getIndex());
@@ -587,13 +590,11 @@ public class InstructionServiceImpl extends CommonServiceImpl<InstructionMapper,
// + "'")
// .uniqueResult(0);
InstructionMybatis ins = new LambdaQueryChainWrapper<>(instructionMapper)
.lt(InstructionMybatis::getInstruction_status, InstructionStatusEnum.CANCEL.getIndex())
.lt(InstructionMybatis::getInstruction_status, InstructionStatusEnum.FINISHED.getIndex())
.eq(InstructionMybatis::getNext_point_code, dto.getNext_point_code())
.eq(InstructionMybatis::getStart_point_code, dto.getStart_point_code())
.eq(InstructionMybatis::getTask_id, dto.getTask_id())
.one();
if (ins != null) {
throw new Exception(dto.getTask_code() + ":该任务已存在待完成指令!");
}

View File

@@ -185,4 +185,11 @@ public class TaskController {
public ResponseEntity<Object> queryTaskSheet(@RequestBody Map whereJson) {
return new ResponseEntity<>(taskService.queryTaskSheet(whereJson), HttpStatus.OK);
}
@SaIgnore
@Log("查询超时异常任务")
@PostMapping(value = "/queryTimeoutTask")
public ResponseEntity<Object> queryTimeoutTask(@RequestBody Map whereJson) {
return new ResponseEntity<>(taskService.queryTimeoutTask(whereJson), HttpStatus.OK);
}
}

View File

@@ -525,4 +525,11 @@ public interface TaskService extends CommonService<Task> {
List<JSONObject> queryTaskSheet(Map whereJson);
/**
* 查询超时异常任务create_time 到 update_time 超过60分钟
* @param whereJson 查询条件
* @return 超时任务列表
*/
List<JSONObject> queryTimeoutTask(Map whereJson);
}

View File

@@ -1032,6 +1032,15 @@ public class TaskServiceImpl extends CommonServiceImpl<TaskMapper, Task> impleme
List<InstructionMybatis> unfinishedList = new LambdaQueryChainWrapper<>(instructionMapper)
.lt(InstructionMybatis::getInstruction_status, InstructionStatusEnum.FINISHED.getIndex())
.eq(InstructionMybatis::getIs_delete, "0").list();
// 检查是否存在相同task_code的指令如果存在则报错
String taskCode = acsTask.getTask_code();
InstructionMybatis existByTaskcode = unfinishedList.stream()
.filter(r -> StrUtil.equals(r.getTask_code(), taskCode))
.findFirst()
.orElse(null);
if (existByTaskcode != null) {
throw new BadRequestException("该任务号已存在指令号为:" + existByTaskcode.getInstruction_code() + "的指令,请核查取消后再创建!");
}
maxManInstNumber = paramService.findByCode(AcsConfig.MAXMANINSTNUMBER).getValue();
unfinishedManInstructionCount = unfinishedList.stream().filter(r -> "2".equals(r.getCreate_type())).count();
long maxManInst = Long.parseLong(maxManInstNumber);
@@ -1732,6 +1741,49 @@ public class TaskServiceImpl extends CommonServiceImpl<TaskMapper, Task> impleme
@Override
public List<JSONObject> queryTimeoutTask(Map whereJson) {
// 日期格式
String create_time = (String) whereJson.get("startDate");
String end_time = (String) whereJson.get("endDate");
if (StrUtil.isEmpty(create_time) || StrUtil.isEmpty(end_time)) {
String today = DateUtil.today();
create_time = today + " 00:00:00";
end_time = today + " 23:59:59";
} else {
if (create_time.length() == 10) {
create_time += " 00:00:00";
}
if (end_time.length() == 10) {
end_time += " 23:59:59";
}
}
// 查询已完成的任务task_status = 99 表示完成)
LambdaQueryWrapper<Task> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Task::getTask_status, TaskStatusEnum.FINISHED.getIndex())
.between(Task::getCreate_time, create_time, end_time)
.orderByDesc(Task::getCreate_time);
List<Task> taskList = taskMapper.selectList(wrapper);
// 过滤出 create_time 到 update_time 超过超时阈值的任务
int timeoutMinutes = getTaskTimeoutMinutes();
List<JSONObject> result = new ArrayList<>();
for (Task task : taskList) {
Double durationMinutes = calculateDuration(task.getCreate_time(), task.getUpdate_time());
if (durationMinutes != null && durationMinutes > timeoutMinutes) {
JSONObject jo = new JSONObject();
jo.put("task_code", task.getTask_code());
jo.put("start_point_code", task.getStart_point_code());
jo.put("next_point_code", task.getNext_point_code());
jo.put("create_time", task.getCreate_time());
jo.put("update_time", task.getUpdate_time());
jo.put("to_y", String.format("%.2f", durationMinutes));
jo.put("vehicle_code", task.getVehicle_code());
result.add(jo);
}
}
log.info("查询超时异常任务,时间范围:{} ~ {},结果数量:{}", create_time, end_time, result.size());
return result;
}
public List<JSONObject> queryTaskSheet(Map whereJson) {
//日期格式"2026-05-21"
String create_time = (String) whereJson.get("startDate");
@@ -1943,6 +1995,23 @@ public class TaskServiceImpl extends CommonServiceImpl<TaskMapper, Task> impleme
}
}
/**
* 获取任务超时时间阈值分钟从系统参数表读取默认60分钟
*
* @return 超时阈值(分钟)
*/
private int getTaskTimeoutMinutes() {
try {
String value = paramService.findByCode(AcsConfig.TASKTIMEOUTMINUTES).getValue();
if (StrUtil.isNotBlank(value)) {
return Integer.parseInt(value);
}
} catch (Exception e) {
log.warn("获取任务超时时间参数失败使用默认值60分钟", e);
}
return 60; // 默认60分钟
}
/**
* 将分钟格式化为 小时h分钟m 的字符串
*/

View File

@@ -140,7 +140,7 @@ public class AutoCreateInst {
continue;
}
List<InstructionMybatis> activeInstructions = new LambdaQueryChainWrapper<>(instructionMapper)
.lt(InstructionMybatis::getInstruction_status, InstructionStatusEnum.FINISHED.getIndex())
.lt(InstructionMybatis::getInstruction_status, InstructionStatusEnum.PICKUP.getIndex())
.eq(InstructionMybatis::getIs_delete, "0")
.list();
//将执行中的任务也加入指令列表参与起点校验