feat(send-job): 新增炉火写入钢卷信息功能并优化发送作业服务

- 新增 FurnaceSendCoilInfoVO 类用于存储炉火写入的钢卷信息
- 在 SendJobController 中添加获取上次炉火写入钢卷信息的接口
- 在 SendJobServiceImpl 中实现炉火写入成功后自动存储钢卷信息到 Redis
- 新增 getCurrentOnlineCoil 方法用于获取当前正在线上的钢卷
- 优化 CrmPdiPlanService 接口定义和实现类代码格式
- 修复 SendJobQueryServiceImpl 中的错误提示信息国际化
- 优化 SendJobServiceImpl 中的代码结构和依赖注入
- 添加完整的 Java 代码注释和文档说明
This commit is contained in:
2026-01-04 15:09:55 +08:00
parent a75d47479e
commit d752188d01
8 changed files with 186 additions and 55 deletions

View File

@@ -35,7 +35,7 @@ public class BizSendTemplateController extends BaseController {
public AjaxResult getByCode(@PathVariable String templateCode) {
BizSendTemplateVO vo = templateService.getTemplateWithItems(templateCode);
if (vo == null) {
return AjaxResult.error("Template not found");
return AjaxResult.error("模板未找到");
}
return AjaxResult.success(vo);
}
@@ -46,7 +46,7 @@ public class BizSendTemplateController extends BaseController {
@PutMapping
public AjaxResult updateTemplate(@RequestBody BizSendTemplate template) {
if (template == null || template.getTemplateId() == null) {
return AjaxResult.error("templateId is required");
return AjaxResult.error("模板ID是必需的");
}
template.setUpdateBy(SecurityUtils.getUsername());
template.setUpdateTime(new Date());
@@ -76,7 +76,7 @@ public class BizSendTemplateController extends BaseController {
@PutMapping("/items/batchSave")
public AjaxResult batchSaveItems(@RequestBody SendTemplateItemsBatchSaveDTO dto) {
if (dto == null || dto.getTemplateId() == null) {
return AjaxResult.error("templateId is required");
return AjaxResult.error("模板ID是必需的");
}
try {
Boolean ok = templateItemService.batchSave(

View File

@@ -9,8 +9,11 @@ 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.FurnaceSendCoilInfoVO;
import com.fizz.business.domain.vo.SendJobLastSuccessVO;
import com.fizz.business.service.ISendJobQueryService;
import com.fizz.business.utils.RedisUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.annotation.Log;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
@@ -87,4 +90,24 @@ public class SendJobController extends BaseController {
SendJobLastSuccessVO vo = sendJobQueryService.getLastSuccess(groupType);
return AjaxResult.success(vo);
}
/**
* 获取上次炉火写入的钢卷信息
* @return 上次写入的钢卷信息coilId、planId、sendTime
*/
@GetMapping("/furnace/lastCoilInfo")
public AjaxResult getLastFurnaceSendCoilInfo() {
try {
String jsonValue = RedisUtil.getValue("furnace:send:coil:info");
if (jsonValue == null || jsonValue.isEmpty()) {
return AjaxResult.success(null);
}
ObjectMapper objectMapper = new ObjectMapper();
FurnaceSendCoilInfoVO coilInfo = objectMapper.readValue(jsonValue, FurnaceSendCoilInfoVO.class);
return AjaxResult.success(coilInfo);
} catch (Exception e) {
return AjaxResult.error("获取上次炉火写入钢卷信息失败: " + e.getMessage());
}
}
}

View File

@@ -0,0 +1,22 @@
package com.fizz.business.domain.vo;
import lombok.Data;
import java.util.Date;
/**
* 炉火写入时的钢卷信息(存储到 Redis
*/
@Data
public class FurnaceSendCoilInfoVO {
/** 钢卷号 */
private String coilId;
/** 计划ID */
private String planId;
/** 写入时间 */
private Date sendTime;
}

View File

@@ -13,15 +13,15 @@ import java.util.List;
public interface CrmPdiPlanService extends IService<CrmPdiPlan> {
CrmPdiPlanVO getByCoilIdAndOperId(String coilid);
CrmPdiPlanVO getByCoilIdAndOperId(String coilid);
boolean addCrmPdiPlan(CrmPdiPlan crmPdiPlan);
boolean addCrmPdiPlan(CrmPdiPlan crmPdiPlan);
boolean updateCrmPdiPlan(CrmPdiPlan crmPdiPlan);
boolean updateCrmPdiPlan(CrmPdiPlan crmPdiPlan);
boolean deleteCrmPdiPlan(List<Long> coilid);
boolean deleteCrmPdiPlan(List<Long> coilid);
List<CrmPdiPlanVO> listAll(PlanQueryForm form);
List<CrmPdiPlanVO> listAll(PlanQueryForm form);
/**
* 获取未生产的第一个钢卷(按顺序号或创建时间最早)
@@ -29,4 +29,10 @@ public interface CrmPdiPlanService extends IService<CrmPdiPlan> {
CrmPdiPlan getFirstUnProducedCoil();
void changeStatus(ChangePlanStatusForm build);
/**
* 获取当前正在线上的钢卷优先级ONLINE > PRODUCING > READY/NEW
* @return 当前正在线上的钢卷如果没有则返回null
*/
CrmPdiPlan getCurrentOnlineCoil();
}

View File

@@ -53,11 +53,11 @@ public class BizSendTemplateItemServiceImpl extends ServiceImpl<BizSendTemplateI
Set<String> seen = new HashSet<>();
for (BizSendTemplateItem it : items) {
if (it.getParamCode() == null || it.getParamCode().trim().isEmpty()) {
throw new IllegalArgumentException("paramCode is required");
throw new IllegalArgumentException("参数编码是必需的");
}
String code = it.getParamCode().trim();
if (!seen.add(code)) {
throw new IllegalArgumentException("Duplicate paramCode in request: " + code);
throw new IllegalArgumentException("请求中包含重复参数编码: " + code);
}
it.setParamCode(code);
}
@@ -72,7 +72,7 @@ public class BizSendTemplateItemServiceImpl extends ServiceImpl<BizSendTemplateI
}
long cnt = this.count(qw);
if (cnt > 0) {
throw new IllegalArgumentException("paramCode already exists: " + it.getParamCode());
throw new IllegalArgumentException("参数编码已存在: " + it.getParamCode());
}
}

View File

@@ -60,7 +60,7 @@ public class CrmPdiPlanServiceImpl extends ServiceImpl<CrmPdiPlanMapper, CrmPdiP
* @return 是否添加成功
*/
public boolean addCrmPdiPlan(CrmPdiPlan crmPdiPlan) {
crmPdiPlan.setStatus("READY");
crmPdiPlan.setStatus("READY");
return this.save(crmPdiPlan);
}
@@ -169,4 +169,45 @@ public class CrmPdiPlanServiceImpl extends ServiceImpl<CrmPdiPlanMapper, CrmPdiP
log.info("计划状态更新成功ID: {}, 新状态: {}", build.getId(), build.getOperation());
}
/**
* 获取当前正在线上的钢卷优先级ONLINE > PRODUCING > READY/NEW
* @return 当前正在线上的钢卷如果没有则返回null
*/
@Override
public CrmPdiPlan getCurrentOnlineCoil() {
// 1. 优先查找 PRODUCING 状态的钢卷
CrmPdiPlan producingPlan = this.lambdaQuery()
.eq(CrmPdiPlan::getStatus, "PRODUCING")
.orderByAsc(CrmPdiPlan::getSeqid)
.last("limit 1")
.one();
if (producingPlan != null) {
return producingPlan;
}
// 2. 其次查找 ONLINE 状态的钢卷
CrmPdiPlan onlinePlan = this.lambdaQuery()
.eq(CrmPdiPlan::getStatus, "ONLINE")
.orderByAsc(CrmPdiPlan::getSeqid)
.last("limit 1")
.one();
if (onlinePlan != null) {
return onlinePlan;
}
// 3. 最后查找 READY 或 NEW 状态的钢卷(按顺序号排序,取第一个)
CrmPdiPlan readyOrNewPlan = this.lambdaQuery()
.in(CrmPdiPlan::getStatus, "READY", "NEW")
.orderByAsc(CrmPdiPlan::getSeqid)
.last("limit 1")
.one();
return readyOrNewPlan;
}
}

View File

@@ -160,7 +160,7 @@ public class SendJobQueryServiceImpl implements ISendJobQueryService {
}
if (currentPlan == null) {
return createEmptyResponse("No active or ready plan found.");
return createEmptyResponse("未找到活跃或就绪的计划");
}
// 将 CrmPdiPlan 对象的字段反射为 Map

View File

@@ -13,8 +13,13 @@ 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.domain.CrmPdiPlan;
import com.fizz.business.domain.vo.FurnaceSendCoilInfoVO;
import com.fizz.business.service.CrmPdiPlanService;
import com.fizz.business.service.ISendJobService;
import com.fizz.business.service.manager.OpcMessageIdsManager;
import com.fizz.business.utils.RedisUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
@@ -38,33 +43,38 @@ public class SendJobServiceImpl extends ServiceImpl<BizSendJobMapper, BizSendJob
@Autowired
private BizSendJobGroupMapper jobGroupMapper;
@Autowired
private BizSendJobItemMapper jobItemMapper;
@Autowired
private OpcMessageIdsManager opcMessageIdsManager;
@Autowired
private CrmPdiPlanService crmPdiPlanService;
private static final String REDIS_KEY_FURNACE_SEND_COIL_INFO = "furnace:send:coil:info";
@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()) {
@@ -76,7 +86,7 @@ public class SendJobServiceImpl extends ServiceImpl<BizSendJobMapper, BizSendJob
group.setCreateBy(username);
group.setCreateTime(new Date());
jobGroupMapper.insert(group);
// 2.2 保存明细项
if (groupDTO.getItems() != null) {
for (SendJobCreateDTO.ItemDTO itemDTO : groupDTO.getItems()) {
@@ -87,20 +97,20 @@ public class SendJobServiceImpl extends ServiceImpl<BizSendJobMapper, BizSendJob
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();
}
@@ -108,10 +118,10 @@ public class SendJobServiceImpl extends ServiceImpl<BizSendJobMapper, BizSendJob
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);
BizSendJob::getDeviceName, query.getDeviceName())
.eq(StringUtils.isNotBlank(query.getStatus()),
BizSendJob::getStatus, query.getStatus())
.orderByDesc(BizSendJob::getCreateTime);
List<BizSendJob> jobs = baseMapper.selectList(qw);
if (jobs == null || jobs.isEmpty()) {
@@ -124,9 +134,9 @@ public class SendJobServiceImpl extends ServiceImpl<BizSendJobMapper, BizSendJob
List<Integer> jobIds = jobs.stream().map(BizSendJob::getJobId).collect(Collectors.toList());
List<BizSendJobGroup> groups = jobGroupMapper.selectList(
new LambdaQueryWrapper<BizSendJobGroup>()
.in(BizSendJobGroup::getJobId, jobIds)
.eq(BizSendJobGroup::getGroupType, gt)
new LambdaQueryWrapper<BizSendJobGroup>()
.in(BizSendJobGroup::getJobId, jobIds)
.eq(BizSendJobGroup::getGroupType, gt)
);
if (groups == null || groups.isEmpty()) {
return Collections.emptyList();
@@ -146,55 +156,55 @@ public class SendJobServiceImpl extends ServiceImpl<BizSendJobMapper, BizSendJob
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)
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());
.map(BizSendJobGroup::getGroupId)
.collect(Collectors.toList());
List<BizSendJobItem> items = jobItemMapper.selectList(
new LambdaQueryWrapper<BizSendJobItem>()
.in(BizSendJobItem::getGroupId, groupIds)
new LambdaQueryWrapper<BizSendJobItem>()
.in(BizSendJobItem::getGroupId, groupIds)
);
// 5. 按groupId分组
Map<Integer, List<BizSendJobItem>> groupItemsMap = items.stream()
.collect(Collectors.groupingBy(BizSendJobItem::getGroupId));
.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());
.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;
}
@@ -244,16 +254,16 @@ public class SendJobServiceImpl extends ServiceImpl<BizSendJobMapper, BizSendJob
// 查询该job下所有 PENDING 的 item
List<BizSendJobItem> items = jobItemMapper.selectList(
new LambdaQueryWrapper<BizSendJobItem>()
.eq(BizSendJobItem::getJobId, jobId)
.eq(BizSendJobItem::getResultStatus, "PENDING")
.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)
new LambdaQueryWrapper<BizSendJobItem>().eq(BizSendJobItem::getJobId, jobId)
) > 0;
BizSendJob finish = new BizSendJob();
finish.setJobId(jobId);
finish.setStatus(hasItems ? "COMPLETED" : "FAILED");
@@ -338,6 +348,35 @@ public class SendJobServiceImpl extends ServiceImpl<BizSendJobMapper, BizSendJob
finish.setRemark(allSuccess ? "全部发送成功" : "部分发送失败");
baseMapper.updateById(finish);
// 如果是炉火写入FURNACE获取当前正在线上的钢卷信息并存储到 Redis
try {
boolean isFurnaceSend = groups.stream()
.anyMatch(g -> "FURNACE".equalsIgnoreCase(g.getGroupType()));
if (isFurnaceSend && allSuccess) {
CrmPdiPlan currentCoil = crmPdiPlanService.getCurrentOnlineCoil();
if (currentCoil != null) {
FurnaceSendCoilInfoVO coilInfo = new FurnaceSendCoilInfoVO();
coilInfo.setCoilId(currentCoil.getCoilid());
coilInfo.setPlanId(currentCoil.getPlanid());
coilInfo.setSendTime(new Date());
// 存储到 Redis使用 JSON 格式)
ObjectMapper objectMapper = new ObjectMapper();
String jsonValue = objectMapper.writeValueAsString(coilInfo);
RedisUtil.setValue(REDIS_KEY_FURNACE_SEND_COIL_INFO, jsonValue);
log.info("炉火写入成功,已保存钢卷信息到 Redis: coilId={}, planId={}",
coilInfo.getCoilId(), coilInfo.getPlanId());
} else {
log.warn("炉火写入成功,但未找到当前正在线上的钢卷");
}
}
} catch (Exception e) {
log.error("保存炉火写入钢卷信息到 Redis 失败", e);
// 不影响主流程,只记录日志
}
return true;
}
}