写入逻辑
This commit is contained in:
@@ -61,6 +61,59 @@ public class OpcMessageSend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用写入方法,用于向指定 OPC 节点写入一个值
|
||||||
|
* @param address OPC 节点地址 (e.g., "ns=2;s=ProcessCGL.PLCLine.ExitCut.cutLength")
|
||||||
|
* @param value 要写入的值
|
||||||
|
*/
|
||||||
|
public void writeNode(String address, Object value) {
|
||||||
|
try {
|
||||||
|
List<ReadWriteEntity> entities = new ArrayList<>();
|
||||||
|
entities.add(ReadWriteEntity.builder()
|
||||||
|
.identifier(address)
|
||||||
|
.value(value)
|
||||||
|
.build());
|
||||||
|
miloService.writeToOpcUa(entities);
|
||||||
|
log.info("写入 OPC 成功, node={}, value={}", address, value);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("写入 OPC 失败, node={}, value={}, 原因: {}", address, value, e.getMessage(), e);
|
||||||
|
// 抛出运行时异常,以便上层调用者(如 SendJobServiceImpl)可以捕获并处理失败状态
|
||||||
|
throw new RuntimeException("写入 OPC 失败: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 写入 OPC 节点(增强版):根据字符串值尝试转换为数值/布尔,再写入
|
||||||
|
* 规则:
|
||||||
|
* 1) 先尝试转 Integer
|
||||||
|
* 2) 再尝试转 Double
|
||||||
|
* 3) 再尝试转 Boolean(true/false/1/0)
|
||||||
|
* 4) 都不行则按原字符串写入
|
||||||
|
*/
|
||||||
|
public void writeNode(String address, String valueRaw) {
|
||||||
|
Object v = valueRaw;
|
||||||
|
if (valueRaw != null) {
|
||||||
|
String s = valueRaw.trim();
|
||||||
|
// boolean
|
||||||
|
if ("1".equals(s) || "0".equals(s) || "true".equalsIgnoreCase(s) || "false".equalsIgnoreCase(s)) {
|
||||||
|
v = ("1".equals(s) || "true".equalsIgnoreCase(s));
|
||||||
|
} else {
|
||||||
|
// int
|
||||||
|
try {
|
||||||
|
v = Integer.parseInt(s);
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
// double
|
||||||
|
try {
|
||||||
|
v = Double.parseDouble(s);
|
||||||
|
} catch (Exception ignore2) {
|
||||||
|
v = valueRaw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeNode(address, v);
|
||||||
|
}
|
||||||
|
|
||||||
private List<ReadWriteEntity> getWriteEntities(OpcMessage msg, Map<String,String> msgIds) {
|
private List<ReadWriteEntity> getWriteEntities(OpcMessage msg, Map<String,String> msgIds) {
|
||||||
List<ReadWriteEntity> entities = new ArrayList<>();
|
List<ReadWriteEntity> entities = new ArrayList<>();
|
||||||
for (String key : msgIds.keySet()) {
|
for (String key : msgIds.keySet()) {
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.fizz.business.controller;
|
||||||
|
|
||||||
|
import com.fizz.business.domain.vo.BizSendTemplateVO;
|
||||||
|
import com.fizz.business.service.IBizSendTemplateService;
|
||||||
|
import com.ruoyi.common.core.controller.BaseController;
|
||||||
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送模板配置 Controller
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/business/sendTemplate")
|
||||||
|
public class BizSendTemplateController extends BaseController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IBizSendTemplateService templateService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按模板编码获取模板(含明细)
|
||||||
|
*/
|
||||||
|
@GetMapping("/{templateCode}")
|
||||||
|
public AjaxResult getByCode(@PathVariable String templateCode) {
|
||||||
|
BizSendTemplateVO vo = templateService.getTemplateWithItems(templateCode);
|
||||||
|
if (vo == null) {
|
||||||
|
return AjaxResult.error("Template not found");
|
||||||
|
}
|
||||||
|
return AjaxResult.success(vo);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package com.fizz.business.controller;
|
||||||
|
|
||||||
|
import com.fizz.business.domain.BizSendJob;
|
||||||
|
import com.fizz.business.domain.dto.SendJobCreateDTO;
|
||||||
|
import com.fizz.business.domain.dto.SendJobQueryDTO;
|
||||||
|
import com.fizz.business.domain.vo.SendJobDetailVO;
|
||||||
|
import com.fizz.business.service.ISendJobService;
|
||||||
|
import com.ruoyi.common.core.controller.BaseController;
|
||||||
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import com.ruoyi.common.core.page.TableDataInfo;
|
||||||
|
import com.ruoyi.common.enums.BusinessType;
|
||||||
|
import com.fizz.business.domain.vo.SendJobLastSuccessVO;
|
||||||
|
import com.fizz.business.service.ISendJobQueryService;
|
||||||
|
import com.ruoyi.common.annotation.Log;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送任务 Controller
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/business/sendJob")
|
||||||
|
public class SendJobController extends BaseController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ISendJobService sendJobService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ISendJobQueryService sendJobQueryService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建发送任务
|
||||||
|
*/
|
||||||
|
@Log(title = "发送任务", businessType = BusinessType.INSERT)
|
||||||
|
@PostMapping
|
||||||
|
public AjaxResult create(@Validated @RequestBody SendJobCreateDTO dto) {
|
||||||
|
Integer jobId = sendJobService.createSendJob(dto);
|
||||||
|
return success(jobId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询发送任务列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/list")
|
||||||
|
public TableDataInfo list(SendJobQueryDTO query) {
|
||||||
|
startPage(); // 使用BaseController的分页方法
|
||||||
|
List<BizSendJob> list = sendJobService.selectSendJobList(query);
|
||||||
|
return getDataTable(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取发送任务详情
|
||||||
|
*/
|
||||||
|
@GetMapping("/{jobId}")
|
||||||
|
public AjaxResult getDetail(@PathVariable Integer jobId) {
|
||||||
|
SendJobDetailVO detail = sendJobService.selectSendJobDetail(jobId);
|
||||||
|
return success(detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除发送任务(逻辑删除)
|
||||||
|
*/
|
||||||
|
@Log(title = "发送任务", businessType = BusinessType.DELETE)
|
||||||
|
@DeleteMapping("/{jobIds}")
|
||||||
|
public AjaxResult remove(@PathVariable Integer[] jobIds) {
|
||||||
|
return toAjax(sendJobService.deleteSendJobByJobIds(jobIds));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行发送任务
|
||||||
|
*/
|
||||||
|
@Log(title = "发送任务", businessType = BusinessType.UPDATE)
|
||||||
|
@PostMapping("/{jobId}/execute")
|
||||||
|
public AjaxResult execute(@PathVariable Integer jobId) {
|
||||||
|
return toAjax(sendJobService.executeSendJob(jobId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询最近一次成功发送(用于界面显示上次发送时间 + 推荐上次值)
|
||||||
|
* @param groupType DRIVE / FURNACE
|
||||||
|
*/
|
||||||
|
@GetMapping("/lastSuccess")
|
||||||
|
public AjaxResult lastSuccess(@RequestParam String groupType) {
|
||||||
|
SendJobLastSuccessVO vo = sendJobQueryService.getLastSuccess(groupType);
|
||||||
|
return AjaxResult.success(vo);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.fizz.business.domain;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.ruoyi.common.core.domain.BaseEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务发送批次表 biz_send_job
|
||||||
|
*
|
||||||
|
* @author Cascade
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("biz_send_job")
|
||||||
|
public class BizSendJob extends BaseEntity {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/** 批次ID */
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Integer jobId;
|
||||||
|
|
||||||
|
/** 业务唯一键, 用于幂等控制 */
|
||||||
|
private String bizKey;
|
||||||
|
|
||||||
|
/** 目标设备/产线名称 (如 CGL_LINE_1, FURNACE_A) */
|
||||||
|
private String deviceName;
|
||||||
|
|
||||||
|
/** 计划发送时间 */
|
||||||
|
private Date planSendTime;
|
||||||
|
|
||||||
|
/** 实际开始发送时间 */
|
||||||
|
private Date actualSendTime;
|
||||||
|
|
||||||
|
/** 发送完成时间 */
|
||||||
|
private Date finishTime;
|
||||||
|
|
||||||
|
/** 批次状态: PENDING, IN_PROGRESS, COMPLETED, PARTIAL_SUCCESS, FAILED */
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/** 操作员ID */
|
||||||
|
private Long operatorId;
|
||||||
|
|
||||||
|
/** 操作员姓名 */
|
||||||
|
private String operatorName;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.fizz.business.domain;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.ruoyi.common.core.domain.BaseEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务发送分组表 biz_send_job_group
|
||||||
|
*
|
||||||
|
* @author Cascade
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("biz_send_job_group")
|
||||||
|
public class BizSendJobGroup extends BaseEntity {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/** 分组ID */
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Integer groupId;
|
||||||
|
|
||||||
|
/** 所属批次ID */
|
||||||
|
private Integer jobId;
|
||||||
|
|
||||||
|
/** 在本批次内的组序号 */
|
||||||
|
private Integer groupNo;
|
||||||
|
|
||||||
|
/** 组类型: DRIVE(传动), FURNACE(炉火) */
|
||||||
|
private String groupType;
|
||||||
|
|
||||||
|
/** 组名称 (可选) */
|
||||||
|
private String groupName;
|
||||||
|
|
||||||
|
/** 分组状态: PENDING, IN_PROGRESS, COMPLETED, FAILED */
|
||||||
|
private String status;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package com.fizz.business.domain;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.ruoyi.common.core.domain.BaseEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务发送项历史表 biz_send_job_item
|
||||||
|
*
|
||||||
|
* @author Cascade
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("biz_send_job_item")
|
||||||
|
public class BizSendJobItem extends BaseEntity {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/** 发送项ID */
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Integer itemId;
|
||||||
|
|
||||||
|
/** 所属批次ID (冗余) */
|
||||||
|
private Integer jobId;
|
||||||
|
|
||||||
|
/** 所属分组ID */
|
||||||
|
private Integer groupId;
|
||||||
|
|
||||||
|
/** 参数业务编码 */
|
||||||
|
private String paramCode;
|
||||||
|
|
||||||
|
/** 设定地址 (OPC地址) */
|
||||||
|
private String address;
|
||||||
|
|
||||||
|
/** 设定的原始值 */
|
||||||
|
private String valueRaw;
|
||||||
|
|
||||||
|
/** 设定值的数值形式 */
|
||||||
|
private BigDecimal valueNum;
|
||||||
|
|
||||||
|
/** 参数的设定时间 */
|
||||||
|
private Date setTime;
|
||||||
|
|
||||||
|
/** 发送结果: PENDING, SUCCESS, FAILED */
|
||||||
|
private String resultStatus;
|
||||||
|
|
||||||
|
/** 结果消息 */
|
||||||
|
private String resultMsg;
|
||||||
|
|
||||||
|
/** 重试次数 */
|
||||||
|
private Integer retryCount;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.fizz.business.domain;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.ruoyi.common.core.domain.BaseEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送默认模板主表
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("biz_send_template")
|
||||||
|
public class BizSendTemplate extends BaseEntity {
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Integer templateId;
|
||||||
|
|
||||||
|
/** 模板编码 */
|
||||||
|
private String templateCode;
|
||||||
|
|
||||||
|
/** 模板名称 */
|
||||||
|
private String templateName;
|
||||||
|
|
||||||
|
/** 默认设备名称 */
|
||||||
|
private String deviceName;
|
||||||
|
|
||||||
|
/** 组类型 DRIVE / FURNACE */
|
||||||
|
private String groupType;
|
||||||
|
|
||||||
|
/** 是否启用 */
|
||||||
|
private Integer enabled;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.fizz.business.domain;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.ruoyi.common.core.domain.BaseEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送默认模板明细表
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("biz_send_template_item")
|
||||||
|
public class BizSendTemplateItem extends BaseEntity {
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Integer templateItemId;
|
||||||
|
|
||||||
|
private Integer templateId;
|
||||||
|
|
||||||
|
/** 明细序号 */
|
||||||
|
private Integer itemNo;
|
||||||
|
|
||||||
|
/** 参数编码 */
|
||||||
|
private String paramCode;
|
||||||
|
|
||||||
|
/** 英文显示名 */
|
||||||
|
private String labelEn;
|
||||||
|
|
||||||
|
/** 英文分组名 */
|
||||||
|
private String groupNameEn;
|
||||||
|
|
||||||
|
/** OPC地址 */
|
||||||
|
private String address;
|
||||||
|
|
||||||
|
/** 默认值(字符串) */
|
||||||
|
private String defaultValueRaw;
|
||||||
|
|
||||||
|
/** 是否启用 */
|
||||||
|
private Integer enabled;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package com.fizz.business.domain.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.validation.Valid;
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import javax.validation.constraints.Size;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建发送任务 DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SendJobCreateDTO {
|
||||||
|
|
||||||
|
/** 目标设备/产线名称 (如 CGL_LINE_1, FURNACE_A) */
|
||||||
|
@NotBlank(message = "deviceName不能为空")
|
||||||
|
private String deviceName;
|
||||||
|
|
||||||
|
/** 计划发送时间 */
|
||||||
|
private Date planSendTime;
|
||||||
|
|
||||||
|
/** 操作员ID */
|
||||||
|
private Long operatorId;
|
||||||
|
|
||||||
|
/** 操作员姓名 */
|
||||||
|
private String operatorName;
|
||||||
|
|
||||||
|
/** 分组列表 */
|
||||||
|
@Valid
|
||||||
|
@NotNull(message = "groups不能为空")
|
||||||
|
@Size(min = 1, message = "至少需要一个分组")
|
||||||
|
private List<GroupDTO> groups;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class GroupDTO {
|
||||||
|
|
||||||
|
/** 在本批次内的组序号 */
|
||||||
|
@NotNull(message = "groupNo不能为空")
|
||||||
|
private Integer groupNo;
|
||||||
|
|
||||||
|
/** 组类型: DRIVE(传动), FURNACE(炉火) */
|
||||||
|
@NotBlank(message = "groupType不能为空")
|
||||||
|
private String groupType;
|
||||||
|
|
||||||
|
/** 组名称 (可选) */
|
||||||
|
private String groupName;
|
||||||
|
|
||||||
|
/** 参数项列表 */
|
||||||
|
@Valid
|
||||||
|
@NotNull(message = "items不能为空")
|
||||||
|
@Size(min = 1, message = "至少需要一个参数项")
|
||||||
|
private List<ItemDTO> items;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class ItemDTO {
|
||||||
|
|
||||||
|
/** 参数业务编码 */
|
||||||
|
private String paramCode;
|
||||||
|
|
||||||
|
/** 设定地址 (OPC地址) */
|
||||||
|
@NotBlank(message = "address不能为空")
|
||||||
|
private String address;
|
||||||
|
|
||||||
|
/** 设定的原始值 */
|
||||||
|
@NotBlank(message = "valueRaw不能为空")
|
||||||
|
private String valueRaw;
|
||||||
|
|
||||||
|
/** 参数的设定时间 */
|
||||||
|
private Date setTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.fizz.business.domain.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送任务查询 DTO
|
||||||
|
* 说明:分页参数沿用 RuoYi BaseController 的 startPage() 机制,从 request 里取 pageNum/pageSize。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SendJobQueryDTO {
|
||||||
|
|
||||||
|
/** 设备/产线名称 */
|
||||||
|
private String deviceName;
|
||||||
|
|
||||||
|
/** 状态: PENDING, IN_PROGRESS, COMPLETED, PARTIAL_SUCCESS, FAILED, DELETED */
|
||||||
|
private String status;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.fizz.business.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模板明细 VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class BizSendTemplateItemVO {
|
||||||
|
private Integer templateItemId;
|
||||||
|
private Integer templateId;
|
||||||
|
private Integer itemNo;
|
||||||
|
private String paramCode;
|
||||||
|
private String labelEn;
|
||||||
|
private String groupNameEn;
|
||||||
|
private String address;
|
||||||
|
private String defaultValueRaw;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.fizz.business.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模板主VO(含明细)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class BizSendTemplateVO {
|
||||||
|
private Integer templateId;
|
||||||
|
private String templateCode;
|
||||||
|
private String templateName;
|
||||||
|
private String deviceName;
|
||||||
|
private String groupType;
|
||||||
|
private Integer enabled;
|
||||||
|
|
||||||
|
private List<BizSendTemplateItemVO> items;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.fizz.business.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送任务详情 VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class SendJobDetailVO extends SendJobVO {
|
||||||
|
|
||||||
|
private Date planSendTime;
|
||||||
|
|
||||||
|
private Date actualSendTime;
|
||||||
|
|
||||||
|
private Date finishTime;
|
||||||
|
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
private List<SendJobGroupVO> groups;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.fizz.business.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送任务分组 VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SendJobGroupVO {
|
||||||
|
|
||||||
|
private Integer groupId;
|
||||||
|
|
||||||
|
private Integer jobId;
|
||||||
|
|
||||||
|
private Integer groupNo;
|
||||||
|
|
||||||
|
private String groupType;
|
||||||
|
|
||||||
|
private String groupName;
|
||||||
|
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
private List<SendJobItemVO> items;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.fizz.business.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送任务明细项 VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SendJobItemVO {
|
||||||
|
|
||||||
|
private Integer itemId;
|
||||||
|
|
||||||
|
private Integer jobId;
|
||||||
|
|
||||||
|
private Integer groupId;
|
||||||
|
|
||||||
|
private String paramCode;
|
||||||
|
|
||||||
|
private String address;
|
||||||
|
|
||||||
|
private String valueRaw;
|
||||||
|
|
||||||
|
private BigDecimal valueNum;
|
||||||
|
|
||||||
|
private Date setTime;
|
||||||
|
|
||||||
|
private String resultStatus;
|
||||||
|
|
||||||
|
private String resultMsg;
|
||||||
|
|
||||||
|
private Integer retryCount;
|
||||||
|
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.fizz.business.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最近一次成功发送结果(用于前端推荐默认值 + 显示上次发送时间)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SendJobLastSuccessVO {
|
||||||
|
|
||||||
|
/** 最近一次成功发送时间(job.finish_time) */
|
||||||
|
private Date lastSendTime;
|
||||||
|
|
||||||
|
/** paramCode -> valueRaw */
|
||||||
|
private Map<String, String> values;
|
||||||
|
|
||||||
|
/** 最近一次成功的jobId(可选) */
|
||||||
|
private Integer jobId;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.fizz.business.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送任务列表 VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SendJobVO {
|
||||||
|
|
||||||
|
private Integer jobId;
|
||||||
|
|
||||||
|
private String bizKey;
|
||||||
|
|
||||||
|
private String deviceName;
|
||||||
|
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
private Long operatorId;
|
||||||
|
|
||||||
|
private String operatorName;
|
||||||
|
|
||||||
|
private Date createTime;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.fizz.business.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.fizz.business.domain.BizSendJobGroup;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface BizSendJobGroupMapper extends BaseMapper<BizSendJobGroup> {
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.fizz.business.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.fizz.business.domain.BizSendJobItem;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface BizSendJobItemMapper extends BaseMapper<BizSendJobItem> {
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.fizz.business.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.fizz.business.domain.BizSendJob;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface BizSendJobMapper extends BaseMapper<BizSendJob> {
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.fizz.business.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.fizz.business.domain.BizSendTemplateItem;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface BizSendTemplateItemMapper extends BaseMapper<BizSendTemplateItem> {
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.fizz.business.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.fizz.business.domain.BizSendTemplate;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface BizSendTemplateMapper extends BaseMapper<BizSendTemplate> {
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.fizz.business.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.fizz.business.domain.BizSendTemplate;
|
||||||
|
import com.fizz.business.domain.vo.BizSendTemplateVO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送默认模板 Service
|
||||||
|
*/
|
||||||
|
public interface IBizSendTemplateService extends IService<BizSendTemplate> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按模板编码获取模板(含明细)
|
||||||
|
*/
|
||||||
|
BizSendTemplateVO getTemplateWithItems(String templateCode);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.fizz.business.service;
|
||||||
|
|
||||||
|
import com.fizz.business.domain.vo.SendJobLastSuccessVO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送任务查询扩展(用于推荐值、上次发送时间)
|
||||||
|
*/
|
||||||
|
public interface ISendJobQueryService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询最近一次成功发送(按 groupType 过滤:DRIVE / FURNACE)
|
||||||
|
*/
|
||||||
|
SendJobLastSuccessVO getLastSuccess(String groupType);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.fizz.business.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.fizz.business.domain.BizSendJob;
|
||||||
|
import com.fizz.business.domain.dto.SendJobCreateDTO;
|
||||||
|
import com.fizz.business.domain.dto.SendJobQueryDTO;
|
||||||
|
import com.fizz.business.domain.vo.SendJobDetailVO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送任务 Service
|
||||||
|
*/
|
||||||
|
public interface ISendJobService extends IService<BizSendJob> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建发送任务(包含分组与明细)
|
||||||
|
*/
|
||||||
|
Integer createSendJob(SendJobCreateDTO dto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询发送任务列表(分页由 Controller 的 startPage() 控制)
|
||||||
|
*/
|
||||||
|
List<BizSendJob> selectSendJobList(SendJobQueryDTO query);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询发送任务详情(包含分组与明细)
|
||||||
|
*/
|
||||||
|
SendJobDetailVO selectSendJobDetail(Integer jobId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除任务(逻辑删除:status=DELETED)
|
||||||
|
*/
|
||||||
|
Boolean deleteSendJobByJobIds(Integer[] jobIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行发送:写入 OPC,并将发送结果保存为历史(更新 job/group/item 状态)
|
||||||
|
*/
|
||||||
|
Boolean executeSendJob(Integer jobId);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package com.fizz.business.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.fizz.business.domain.BizSendTemplate;
|
||||||
|
import com.fizz.business.domain.BizSendTemplateItem;
|
||||||
|
import com.fizz.business.domain.vo.BizSendTemplateItemVO;
|
||||||
|
import com.fizz.business.domain.vo.BizSendTemplateVO;
|
||||||
|
import com.fizz.business.mapper.BizSendTemplateItemMapper;
|
||||||
|
import com.fizz.business.mapper.BizSendTemplateMapper;
|
||||||
|
import com.fizz.business.service.IBizSendTemplateService;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class BizSendTemplateServiceImpl extends ServiceImpl<BizSendTemplateMapper, BizSendTemplate> implements IBizSendTemplateService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private BizSendTemplateItemMapper templateItemMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BizSendTemplateVO getTemplateWithItems(String templateCode) {
|
||||||
|
BizSendTemplate template = this.lambdaQuery()
|
||||||
|
.eq(BizSendTemplate::getTemplateCode, templateCode)
|
||||||
|
.eq(BizSendTemplate::getEnabled, 1)
|
||||||
|
.one();
|
||||||
|
|
||||||
|
if (template == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<BizSendTemplateItem> items = templateItemMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<BizSendTemplateItem>()
|
||||||
|
.eq(BizSendTemplateItem::getTemplateId, template.getTemplateId())
|
||||||
|
.eq(BizSendTemplateItem::getEnabled, 1)
|
||||||
|
.orderByAsc(BizSendTemplateItem::getItemNo)
|
||||||
|
);
|
||||||
|
|
||||||
|
BizSendTemplateVO vo = new BizSendTemplateVO();
|
||||||
|
BeanUtils.copyProperties(template, vo);
|
||||||
|
|
||||||
|
List<BizSendTemplateItemVO> itemVOs = items.stream().map(item -> {
|
||||||
|
BizSendTemplateItemVO it = new BizSendTemplateItemVO();
|
||||||
|
BeanUtils.copyProperties(item, it);
|
||||||
|
return it;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
|
||||||
|
vo.setItems(itemVOs);
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package com.fizz.business.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.fizz.business.domain.BizSendJob;
|
||||||
|
import com.fizz.business.domain.BizSendJobGroup;
|
||||||
|
import com.fizz.business.domain.BizSendJobItem;
|
||||||
|
import com.fizz.business.domain.vo.SendJobLastSuccessVO;
|
||||||
|
import com.fizz.business.mapper.BizSendJobGroupMapper;
|
||||||
|
import com.fizz.business.mapper.BizSendJobItemMapper;
|
||||||
|
import com.fizz.business.mapper.BizSendJobMapper;
|
||||||
|
import com.fizz.business.service.ISendJobQueryService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class SendJobQueryServiceImpl implements ISendJobQueryService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private BizSendJobMapper jobMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private BizSendJobGroupMapper groupMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private BizSendJobItemMapper itemMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SendJobLastSuccessVO getLastSuccess(String groupType) {
|
||||||
|
// 1) 先找最近一次 COMPLETED 的 job(倒序)
|
||||||
|
BizSendJob lastJob = jobMapper.selectOne(
|
||||||
|
new LambdaQueryWrapper<BizSendJob>()
|
||||||
|
.eq(BizSendJob::getStatus, "COMPLETED")
|
||||||
|
.orderByDesc(BizSendJob::getFinishTime)
|
||||||
|
.last("LIMIT 1")
|
||||||
|
);
|
||||||
|
if (lastJob == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) 找该 job 下对应 groupType 的 groups
|
||||||
|
List<BizSendJobGroup> groups = groupMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<BizSendJobGroup>()
|
||||||
|
.eq(BizSendJobGroup::getJobId, lastJob.getJobId())
|
||||||
|
.eq(groupType != null && !groupType.trim().isEmpty(), BizSendJobGroup::getGroupType, groupType)
|
||||||
|
);
|
||||||
|
if (groups == null || groups.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Integer> groupIds = groups.stream().map(BizSendJobGroup::getGroupId).collect(java.util.stream.Collectors.toList());
|
||||||
|
|
||||||
|
// 3) 取这些 group 的 item,按 paramCode 聚合最后一次值(同 paramCode 取最后一条 itemId 最大)
|
||||||
|
List<BizSendJobItem> items = itemMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<BizSendJobItem>()
|
||||||
|
.in(BizSendJobItem::getGroupId, groupIds)
|
||||||
|
.eq(BizSendJobItem::getResultStatus, "SUCCESS")
|
||||||
|
.orderByAsc(BizSendJobItem::getItemId)
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, String> values = new HashMap<>();
|
||||||
|
for (BizSendJobItem it : items) {
|
||||||
|
if (it.getParamCode() == null) continue;
|
||||||
|
values.put(it.getParamCode(), it.getValueRaw());
|
||||||
|
}
|
||||||
|
|
||||||
|
SendJobLastSuccessVO vo = new SendJobLastSuccessVO();
|
||||||
|
vo.setJobId(lastJob.getJobId());
|
||||||
|
vo.setLastSendTime(lastJob.getFinishTime());
|
||||||
|
vo.setValues(values);
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,320 @@
|
|||||||
|
package com.fizz.business.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.fizz.business.domain.BizSendJob;
|
||||||
|
import com.fizz.business.domain.BizSendJobGroup;
|
||||||
|
import com.fizz.business.domain.BizSendJobItem;
|
||||||
|
import com.fizz.business.domain.dto.SendJobCreateDTO;
|
||||||
|
import com.fizz.business.domain.dto.SendJobQueryDTO;
|
||||||
|
import com.fizz.business.domain.vo.SendJobDetailVO;
|
||||||
|
import com.fizz.business.domain.vo.SendJobGroupVO;
|
||||||
|
import com.fizz.business.domain.vo.SendJobItemVO;
|
||||||
|
import com.fizz.business.mapper.BizSendJobGroupMapper;
|
||||||
|
import com.fizz.business.mapper.BizSendJobItemMapper;
|
||||||
|
import com.fizz.business.mapper.BizSendJobMapper;
|
||||||
|
import com.fizz.business.service.ISendJobService;
|
||||||
|
import com.fizz.business.service.manager.OpcMessageIdsManager;
|
||||||
|
import com.ruoyi.common.utils.SecurityUtils;
|
||||||
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class SendJobServiceImpl extends ServiceImpl<BizSendJobMapper, BizSendJob> implements ISendJobService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private com.fizz.business.comm.OPC.OpcMessageSend opcMessageSend;
|
||||||
|
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private BizSendJobGroupMapper jobGroupMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private BizSendJobItemMapper jobItemMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private OpcMessageIdsManager opcMessageIdsManager;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Integer createSendJob(SendJobCreateDTO dto) {
|
||||||
|
// 1. 创建批次
|
||||||
|
BizSendJob job = new BizSendJob();
|
||||||
|
BeanUtils.copyProperties(dto, job);
|
||||||
|
|
||||||
|
// 生成业务唯一键
|
||||||
|
String bizKey = opcMessageIdsManager.generateMessageId("SEND_JOB");
|
||||||
|
job.setBizKey(bizKey);
|
||||||
|
job.setStatus("PENDING");
|
||||||
|
|
||||||
|
// 设置操作人
|
||||||
|
String username = SecurityUtils.getUsername();
|
||||||
|
job.setCreateBy(username);
|
||||||
|
job.setCreateTime(new Date());
|
||||||
|
|
||||||
|
// 保存批次
|
||||||
|
baseMapper.insert(job);
|
||||||
|
|
||||||
|
// 2. 保存分组与明细
|
||||||
|
if (dto.getGroups() != null) {
|
||||||
|
for (SendJobCreateDTO.GroupDTO groupDTO : dto.getGroups()) {
|
||||||
|
// 2.1 保存分组
|
||||||
|
BizSendJobGroup group = new BizSendJobGroup();
|
||||||
|
BeanUtils.copyProperties(groupDTO, group);
|
||||||
|
group.setJobId(job.getJobId());
|
||||||
|
group.setStatus("PENDING");
|
||||||
|
group.setCreateBy(username);
|
||||||
|
group.setCreateTime(new Date());
|
||||||
|
jobGroupMapper.insert(group);
|
||||||
|
|
||||||
|
// 2.2 保存明细项
|
||||||
|
if (groupDTO.getItems() != null) {
|
||||||
|
for (SendJobCreateDTO.ItemDTO itemDTO : groupDTO.getItems()) {
|
||||||
|
BizSendJobItem item = new BizSendJobItem();
|
||||||
|
BeanUtils.copyProperties(itemDTO, item);
|
||||||
|
item.setJobId(job.getJobId());
|
||||||
|
item.setGroupId(group.getGroupId());
|
||||||
|
item.setResultStatus("PENDING");
|
||||||
|
item.setCreateBy(username);
|
||||||
|
item.setCreateTime(new Date());
|
||||||
|
|
||||||
|
// 尝试将valueRaw转为数值
|
||||||
|
try {
|
||||||
|
item.setValueNum(new BigDecimal(itemDTO.getValueRaw()));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("转换数值失败: {}", itemDTO.getValueRaw(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
jobItemMapper.insert(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return job.getJobId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<BizSendJob> selectSendJobList(SendJobQueryDTO query) {
|
||||||
|
LambdaQueryWrapper<BizSendJob> qw = new LambdaQueryWrapper<>();
|
||||||
|
qw.eq(StringUtils.isNotBlank(query.getDeviceName()),
|
||||||
|
BizSendJob::getDeviceName, query.getDeviceName())
|
||||||
|
.eq(StringUtils.isNotBlank(query.getStatus()),
|
||||||
|
BizSendJob::getStatus, query.getStatus())
|
||||||
|
.orderByDesc(BizSendJob::getCreateTime);
|
||||||
|
return baseMapper.selectList(qw);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SendJobDetailVO selectSendJobDetail(Integer jobId) {
|
||||||
|
// 1. 查询任务
|
||||||
|
BizSendJob job = baseMapper.selectById(jobId);
|
||||||
|
if (job == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 转换为VO
|
||||||
|
SendJobDetailVO detailVO = new SendJobDetailVO();
|
||||||
|
BeanUtils.copyProperties(job, detailVO);
|
||||||
|
|
||||||
|
// 3. 查询分组
|
||||||
|
List<BizSendJobGroup> groups = jobGroupMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<BizSendJobGroup>()
|
||||||
|
.eq(BizSendJobGroup::getJobId, jobId)
|
||||||
|
.orderByAsc(BizSendJobGroup::getGroupNo)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (groups.isEmpty()) {
|
||||||
|
return detailVO;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 查询所有明细项
|
||||||
|
List<Integer> groupIds = groups.stream()
|
||||||
|
.map(BizSendJobGroup::getGroupId)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
List<BizSendJobItem> items = jobItemMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<BizSendJobItem>()
|
||||||
|
.in(BizSendJobItem::getGroupId, groupIds)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 5. 按groupId分组
|
||||||
|
Map<Integer, List<BizSendJobItem>> groupItemsMap = items.stream()
|
||||||
|
.collect(Collectors.groupingBy(BizSendJobItem::getGroupId));
|
||||||
|
|
||||||
|
// 6. 构建分组VO
|
||||||
|
List<SendJobGroupVO> groupVOs = groups.stream().map(group -> {
|
||||||
|
SendJobGroupVO groupVO = new SendJobGroupVO();
|
||||||
|
BeanUtils.copyProperties(group, groupVO);
|
||||||
|
|
||||||
|
// 设置分组下的明细项
|
||||||
|
List<BizSendJobItem> groupItems = groupItemsMap
|
||||||
|
.getOrDefault(group.getGroupId(), Collections.emptyList());
|
||||||
|
|
||||||
|
List<SendJobItemVO> itemVOs = groupItems.stream().map(item -> {
|
||||||
|
SendJobItemVO itemVO = new SendJobItemVO();
|
||||||
|
BeanUtils.copyProperties(item, itemVO);
|
||||||
|
return itemVO;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
|
||||||
|
groupVO.setItems(itemVOs);
|
||||||
|
return groupVO;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
|
||||||
|
detailVO.setGroups(groupVOs);
|
||||||
|
return detailVO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Boolean deleteSendJobByJobIds(Integer[] jobIds) {
|
||||||
|
if (jobIds == null || jobIds.length == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 更新任务状态为已删除
|
||||||
|
BizSendJob updateJob = new BizSendJob();
|
||||||
|
updateJob.setStatus("DELETED");
|
||||||
|
updateJob.setUpdateBy(SecurityUtils.getUsername());
|
||||||
|
updateJob.setUpdateTime(new Date());
|
||||||
|
|
||||||
|
LambdaQueryWrapper<BizSendJob> qw = new LambdaQueryWrapper<>();
|
||||||
|
qw.in(BizSendJob::getJobId, Arrays.asList(jobIds));
|
||||||
|
|
||||||
|
return update(updateJob, qw);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行发送:写入 OPC,并将发送结果保存为历史(更新 job/group/item 状态)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Boolean executeSendJob(Integer jobId) {
|
||||||
|
BizSendJob job = baseMapper.selectById(jobId);
|
||||||
|
if (job == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ("DELETED".equalsIgnoreCase(job.getStatus())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新 job 状态为发送中
|
||||||
|
BizSendJob jobUpd = new BizSendJob();
|
||||||
|
jobUpd.setJobId(jobId);
|
||||||
|
jobUpd.setStatus("IN_PROGRESS");
|
||||||
|
jobUpd.setActualSendTime(new Date());
|
||||||
|
jobUpd.setUpdateBy(SecurityUtils.getUsername());
|
||||||
|
jobUpd.setUpdateTime(new Date());
|
||||||
|
baseMapper.updateById(jobUpd);
|
||||||
|
|
||||||
|
// 查询该job下所有 PENDING 的 item
|
||||||
|
List<BizSendJobItem> items = jobItemMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<BizSendJobItem>()
|
||||||
|
.eq(BizSendJobItem::getJobId, jobId)
|
||||||
|
.eq(BizSendJobItem::getResultStatus, "PENDING")
|
||||||
|
);
|
||||||
|
|
||||||
|
// 如果没有 PENDING 的项,检查是否有其他状态的项
|
||||||
|
if (items == null || items.isEmpty()) {
|
||||||
|
boolean hasItems = jobItemMapper.selectCount(
|
||||||
|
new LambdaQueryWrapper<BizSendJobItem>().eq(BizSendJobItem::getJobId, jobId)
|
||||||
|
) > 0;
|
||||||
|
|
||||||
|
BizSendJob finish = new BizSendJob();
|
||||||
|
finish.setJobId(jobId);
|
||||||
|
finish.setStatus(hasItems ? "COMPLETED" : "FAILED");
|
||||||
|
finish.setFinishTime(new Date());
|
||||||
|
finish.setUpdateBy(SecurityUtils.getUsername());
|
||||||
|
finish.setUpdateTime(new Date());
|
||||||
|
finish.setRemark(hasItems ? "没有待发送的项" : "没有找到任何发送项");
|
||||||
|
baseMapper.updateById(finish);
|
||||||
|
return hasItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询该job下group
|
||||||
|
List<BizSendJobGroup> groups = jobGroupMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<BizSendJobGroup>().eq(BizSendJobGroup::getJobId, jobId)
|
||||||
|
);
|
||||||
|
for (BizSendJobGroup g : groups) {
|
||||||
|
BizSendJobGroup gu = new BizSendJobGroup();
|
||||||
|
gu.setGroupId(g.getGroupId());
|
||||||
|
gu.setStatus("IN_PROGRESS");
|
||||||
|
gu.setUpdateBy(SecurityUtils.getUsername());
|
||||||
|
gu.setUpdateTime(new Date());
|
||||||
|
jobGroupMapper.updateById(gu);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean allSuccess = true;
|
||||||
|
|
||||||
|
// 按 address 分组,只发送最后一条记录
|
||||||
|
Map<String, List<BizSendJobItem>> itemsByAddr = items.stream()
|
||||||
|
.collect(Collectors.groupingBy(BizSendJobItem::getAddress));
|
||||||
|
|
||||||
|
for (Map.Entry<String, List<BizSendJobItem>> entry : itemsByAddr.entrySet()) {
|
||||||
|
List<BizSendJobItem> addrItems = entry.getValue();
|
||||||
|
// 取最后一条(按 itemId 最大 || createTime 最新)
|
||||||
|
addrItems.sort(Comparator.comparing(BizSendJobItem::getItemId));
|
||||||
|
BizSendJobItem last = addrItems.get(addrItems.size() - 1);
|
||||||
|
boolean success = true;
|
||||||
|
String failMsg = null;
|
||||||
|
try {
|
||||||
|
opcMessageSend.writeNode(last.getAddress(), last.getValueRaw());
|
||||||
|
} catch (Exception ex) {
|
||||||
|
success = false;
|
||||||
|
allSuccess = false;
|
||||||
|
failMsg = ex.getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新该 address 下所有项的状态(统一)
|
||||||
|
for (BizSendJobItem it : addrItems) {
|
||||||
|
BizSendJobItem upd = new BizSendJobItem();
|
||||||
|
upd.setItemId(it.getItemId());
|
||||||
|
upd.setResultStatus(success ? "SUCCESS" : "FAILED");
|
||||||
|
upd.setResultMsg(success ? "OK" : failMsg);
|
||||||
|
upd.setUpdateBy(SecurityUtils.getUsername());
|
||||||
|
upd.setUpdateTime(new Date());
|
||||||
|
jobItemMapper.updateById(upd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新 group 状态(按 group 下 item 是否全部成功)
|
||||||
|
Map<Integer, List<BizSendJobItem>> itemByGroup = jobItemMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<BizSendJobItem>().eq(BizSendJobItem::getJobId, jobId)
|
||||||
|
).stream().collect(Collectors.groupingBy(BizSendJobItem::getGroupId));
|
||||||
|
|
||||||
|
for (BizSendJobGroup g : groups) {
|
||||||
|
List<BizSendJobItem> gi = itemByGroup.getOrDefault(g.getGroupId(), Collections.emptyList());
|
||||||
|
boolean groupOk = gi.stream().allMatch(x -> "SUCCESS".equalsIgnoreCase(x.getResultStatus()));
|
||||||
|
|
||||||
|
BizSendJobGroup gu = new BizSendJobGroup();
|
||||||
|
gu.setGroupId(g.getGroupId());
|
||||||
|
gu.setStatus(groupOk ? "COMPLETED" : "FAILED");
|
||||||
|
gu.setUpdateBy(SecurityUtils.getUsername());
|
||||||
|
gu.setUpdateTime(new Date());
|
||||||
|
jobGroupMapper.updateById(gu);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新 job 最终状态
|
||||||
|
BizSendJob finish = new BizSendJob();
|
||||||
|
finish.setJobId(jobId);
|
||||||
|
finish.setFinishTime(new Date());
|
||||||
|
finish.setUpdateBy(SecurityUtils.getUsername());
|
||||||
|
finish.setUpdateTime(new Date());
|
||||||
|
finish.setStatus(allSuccess ? "COMPLETED" : "PARTIAL_SUCCESS");
|
||||||
|
finish.setRemark(allSuccess ? "全部发送成功" : "部分发送失败");
|
||||||
|
baseMapper.updateById(finish);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -14,6 +14,17 @@ import java.util.Map;
|
|||||||
@Component
|
@Component
|
||||||
public class OpcMessageIdsManager {
|
public class OpcMessageIdsManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成业务/消息唯一ID(用于发送批次 bizKey 等)
|
||||||
|
* 规则:PREFIX_时间戳_8位随机
|
||||||
|
*/
|
||||||
|
public String generateMessageId(String prefix) {
|
||||||
|
String p = (prefix == null || prefix.trim().isEmpty()) ? "MSG" : prefix.trim();
|
||||||
|
String random = java.util.UUID.randomUUID().toString().replace("-", "").substring(0, 8);
|
||||||
|
return (p + "_" + System.currentTimeMillis() + "_" + random).toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static List<String> msgTriggers = Lists.newArrayList();
|
public static List<String> msgTriggers = Lists.newArrayList();
|
||||||
|
|
||||||
public static Map<String,String> lineMeasureIds = Maps.newHashMap();
|
public static Map<String,String> lineMeasureIds = Maps.newHashMap();
|
||||||
|
|||||||
Reference in New Issue
Block a user