初始代码

This commit is contained in:
hhh
2026-04-02 10:38:23 +08:00
parent d8b4140f50
commit aed67ce1fd
1937 changed files with 447678 additions and 1 deletions

View File

@@ -0,0 +1,371 @@
# YJH-MES v2.0.004 — 8Multi di_reg 报工 & 设备状态接口
> **版本**: v2.0.004
> **更新日期**: 2026-03-06
> **项目**: YJH-MES (yjh-mes + mes-ui)
> **说明**: 在 8Multi JSON 协议中新增 `di_reg` 字段处理,实现硬件触发报工和设备状态修改
---
## 一、需求概述
### 1.1 新增字段
原 8Multi JSON 格式中新增一个字段:
```json
"di_reg": %d // 0000-0111 触发报工1000-1111 修改设备状态
```
### 1.2 di_reg 编码规则
| 十进制值 | 二进制 | 动作类型 | 含义 |
|---------|--------|---------|------|
| 0 | 0000 | 报工 | 对工序路线中**第1道工序**的全部在产工单执行报工 |
| 1 | 0001 | 报工 | 对工序路线中**第2道工序**的全部在产工单执行报工 |
| 2 | 0010 | 报工 | 对工序路线中**第3道工序**的全部在产工单执行报工 |
| 3 | 0011 | 报工 | 对工序路线中**第4道工序**的全部在产工单执行报工 |
| 4 | 0100 | 报工 | 对工序路线中**第5道工序**的全部在产工单执行报工 |
| 5 | 0101 | 报工 | 对工序路线中**第6道工序**的全部在产工单执行报工 |
| 6 | 0110 | 报工 | 对工序路线中**第7道工序**的全部在产工单执行报工 |
| 7 | 0111 | 报工 | 对工序路线中**第8道工序**的全部在产工单执行报工 |
| 8 | 1000 | 设备状态 | 计划停机cn_reg = 0 |
| 9 | 1001 | 设备状态 | 正常工作cn_reg = 1 |
| 10 | 1010 | 设备状态 | 待机cn_reg = 2 |
| 11 | 1011 | 设备状态 | 故障cn_reg = 3 |
| 12 | 1100 | 设备状态 | 在修cn_reg = 4 |
| 13 | 1101 | 设备状态 | 缺人cn_reg = 5 |
| 14 | 1110 | 设备状态 | 缺料cn_reg = 6 |
| 15 | 1111 | 设备状态 | 清零cn_reg = 7 |
| null/其他 | — | 忽略 | 不触发任何动作 |
**判断逻辑**
```
di_reg < 8 → 报工模式processSort = di_reg + 1
di_reg >= 8 → 设备状态模式cn_reg_new = di_reg - 8
```
---
## 二、数据库设计
### 2.1 表设计总览(报工与设备状态两个功能独立)
| 功能 | 表 | 说明 |
|------|----|------|
| 报工结果 | `pro_report`(已有)| 生成报工单,工单状态变更为 D |
| 设备状态 | `device_data.cn_reg`(已有)| 本次上报的 cn_reg 被 di_reg 覆盖 |
| di_reg 原始值 | `device_data.di_reg`**新增列**| 随每次上报落库 |
| **触发动作日志** | `device_di_reg_log`**新建表**| 记录两类触发事件,用于追溯和审计 |
### 2.2 DDL — device_data 新增列
```sql
ALTER TABLE `device_data`
ADD COLUMN `di_reg` INT DEFAULT NULL COMMENT '输入寄存器(0-7报工/8-15设备状态)';
```
### 2.3 DDL — device_di_reg_log 新建表
```sql
CREATE TABLE `device_di_reg_log` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
`device_id` BIGINT NOT NULL COMMENT '设备ID(关联device.id)',
`device_no` INT NOT NULL COMMENT '设备号(device.device_no)',
`di_reg` INT NOT NULL COMMENT 'di_reg原始值(0-15)',
`action_type` VARCHAR(16) NOT NULL COMMENT '动作类型: REPORT=报工 / STATUS=设备状态',
`process_sort` INT DEFAULT NULL COMMENT '工序顺序(REPORT时有效, = di_reg + 1)',
`cn_reg_new` INT DEFAULT NULL COMMENT '新设备状态值(STATUS时有效, = di_reg - 8)',
`affected_count` INT DEFAULT 0 COMMENT '本次处理的工单数量(REPORT时)',
`work_order_ids` VARCHAR(512) DEFAULT NULL COMMENT '处理的工单ID列表,逗号分隔(REPORT时)',
`trigger_time` DATETIME NOT NULL COMMENT '触发时间',
`remark` VARCHAR(512) DEFAULT NULL COMMENT '备注',
`del_flag` CHAR(1) NOT NULL DEFAULT '0' COMMENT '删除标志(0正常/1删除)',
PRIMARY KEY (`id`),
KEY `idx_device_id_time` (`device_id`, `trigger_time`),
KEY `idx_action_type_time` (`action_type`, `trigger_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='8Multi di_reg触发动作日志表';
```
> **字段说明**`action_type=REPORT` 时 `process_sort`、`affected_count`、`work_order_ids` 有效;`action_type=STATUS` 时 `cn_reg_new` 有效。
---
## 三、后端实现方案
### 3.1 涉及文件
```
yjh-mes/src/main/java/cn/sourceplan/equipment/service/
└── Multi8ProtocolService.java ← 主改动di_reg 解析分发、日志写入
yjh-mes/src/main/java/cn/sourceplan/equipment/domain/
├── DeviceData.java ← 新增 diReg 字段
└── DeviceDiRegLog.java ← 新建:日志实体
yjh-mes/src/main/java/cn/sourceplan/equipment/mapper/
└── DeviceDiRegLogMapper.java ← 新建:日志 Mapperextends BaseMapper
yjh-mes/src/main/java/cn/sourceplan/production/mapper/
└── WorkOrderEntryMapper.java ← 新增 selectActiveEntriesByProcessSort 方法
yjh-mes/src/main/resources/mapper/production/
└── WorkOrderEntryMapper.xml ← 新建:对应 SQL
.sql/
└── 2026-03-06_v2.0.004_周启威_8Multi报工、设备状态修改.sql ← DDL 脚本
```
### 3.2 Multi8ProtocolService — parse8MultiJson 改动
`deviceDataMapper.insert(data)` 之前,解析 `di_reg` 并路由:
```java
// 解析 di_reg
Integer diReg = parseInt(json.getStr("di_reg"));
data.setDiReg(diReg);
// di_reg 分发处理
if (diReg != null) {
if (diReg >= 0 && diReg <= 7) {
// 报工模式processSort = diReg + 1全局、全量、幂等
handleAutoReport(device, diReg + 1);
} else if (diReg >= 8 && diReg <= 15) {
// 设备状态模式:强制覆盖 cn_reg在 insert 之前覆盖,确保落库值正确)
int newCnReg = diReg - 8;
data.setCnReg(newCnReg);
log.info("di_reg={} → 设备状态覆盖 cn_reg={}", diReg, newCnReg);
// saveStatusLog 在 deviceDataMapper.insert(data) 之后调用见注意事项4
}
}
```
### 3.3 handleAutoReport — 报工逻辑
**三条核心原则**
1. **报工数量** = 工单计划数量(全量报工,一次完成)
2. **幂等性** = 报工后工单状态置为 D已完成下次查询自动过滤无需额外判断
3. **工单范围** = 全局所有工单,不按工位/车间过滤
```java
/**
* di_reg 触发报工
* 全局查找 sort=processSort 且 pro_status IN('A','B') 的工单分录,全量报工后置为已完成
* 幂等保证:已完成(D)的工单在查询时自动过滤,无需重复判断
*
* @param device 上报设备(用于写日志)
* @param processSort 工序顺序1-based= di_reg + 1
*/
@Transactional(rollbackFor = Exception.class) // 保证报工单创建与工单状态更新原子性
private void handleAutoReport(MesDevice device, int processSort) {
List<WorkOrderEntry> entries = workOrderEntryMapper.selectActiveEntriesByProcessSort(processSort);
if (entries == null || entries.isEmpty()) {
log.info("di_reg 报工:未找到第{}道工序的在产工单分录", processSort);
saveReportLog(device, processSort - 1, processSort, 0, "");
return;
}
List<Long> processedIds = new ArrayList<>();
for (WorkOrderEntry entry : entries) {
try {
WorkOrder workOrder = workOrderMapper.selectById(entry.getWorkorderId());
if (workOrder == null || "D".equals(workOrder.getProStatus())) {
continue;
}
// 报工数量 = 工单计划数量(全量)
BigDecimal reportQty = workOrder.getQuantity() != null
? workOrder.getQuantity() : BigDecimal.ONE;
// 构建报工单
Report report = new Report();
report.setWorkOrderEntryId(entry.getId());
report.setWorkOrderId(workOrder.getId());
report.setProcessName(entry.getProcessName());
report.setProcessId(entry.getProcessId());
report.setWorkshopId(entry.getWorkshopId());
report.setWorkshopName(entry.getWorkshopName());
report.setStationId(entry.getStationId());
report.setStationName(entry.getStationName());
report.setReportTime(new Date());
report.setReportChannel("8Multi");
report.setReportQuantity(reportQty);
report.setQualifiedQuantity(reportQty);
report.setUnqualifiedQuantity(BigDecimal.ZERO);
report.setQualityStatus("A"); // 免检
// 创建报工单insertReportSimple 不处理工单状态,需手动更新)
reportService.insertReportSimple(report);
// 手动将工单标记为已完成 → 下次同 di_reg 触发时自动幂等跳过
// 注意:两步操作在同一 @Transactional 内,任一失败均回滚
workOrder.setTotalReportedQuantity(reportQty);
workOrder.setProStatus("D");
workOrder.setActualEndTime(new Date());
workOrder.setRealFinishDate(new Date());
workOrderMapper.updateById(workOrder);
processedIds.add(workOrder.getId());
log.info("di_reg 报工成功workOrderId={}, processSort={}, qty={}",
workOrder.getId(), processSort, reportQty);
} catch (Exception e) {
log.error("di_reg 报工失败entryId={}", entry.getId(), e);
}
}
// 写触发日志
String woIds = processedIds.stream().map(String::valueOf)
.collect(java.util.stream.Collectors.joining(","));
saveReportLog(device, processSort - 1, processSort, processedIds.size(), woIds);
}
```
### 3.4 日志写入方法
```java
private void saveReportLog(MesDevice device, int diReg, int processSort, int count, String woIds) {
DeviceDiRegLog entry = new DeviceDiRegLog();
entry.setDeviceId(device.getId());
entry.setDeviceNo(device.getDeviceNo());
entry.setDiReg(diReg);
entry.setActionType("REPORT");
entry.setProcessSort(processSort);
entry.setAffectedCount(count);
entry.setWorkOrderIds(woIds);
entry.setTriggerTime(new Date());
deviceDiRegLogMapper.insert(entry);
}
private void saveStatusLog(MesDevice device, int diReg, int cnRegNew) {
DeviceDiRegLog entry = new DeviceDiRegLog();
entry.setDeviceId(device.getId());
entry.setDeviceNo(device.getDeviceNo());
entry.setDiReg(diReg);
entry.setActionType("STATUS");
entry.setCnRegNew(cnRegNew);
entry.setTriggerTime(new Date());
deviceDiRegLogMapper.insert(entry);
}
```
### 3.5 WorkOrderEntryMapper — 新增查询方法
**接口**WorkOrderEntryMapper.java
```java
/**
* 查询所有在产工单非D状态中 processSort = processSort 的工单分录
*/
List<WorkOrderEntry> selectActiveEntriesByProcessSort(@Param("processSort") int processSort);
```
**XML**WorkOrderEntryMapper.xml新建文件
```xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.sourceplan.production.mapper.WorkOrderEntryMapper">
<select id="selectActiveEntriesByProcessSort"
resultType="cn.sourceplan.production.domain.WorkOrderEntry">
SELECT e.*
FROM pro_workorder_entry e
INNER JOIN pro_workorder w ON w.id = e.workorder_id
WHERE e.sort = #{processSort}
AND w.pro_status IN ('A', 'B')
</select>
</mapper>
```
> **说明**`sort` 对应 `WorkOrderEntry.sort`工序顺序1-based只取状态为 A未开始或 B生产中的工单已完成D的工单自动排除实现天然幂等。
---
## 四、设备状态覆盖说明
`di_reg >= 8` 时,本次上报的 `cn_reg` 值将被 `di_reg - 8` **覆盖**(无论 JSON 中 `cn_reg` 是什么)。这样 OEE 时序图、设备状态页面均可立即反映该状态,无需额外接口。
| di_reg | 覆盖后 cn_reg | 含义 |
|--------|------------|------|
| 8 | 0 | 计划停机 |
| 9 | 1 | 正常工作 |
| 10 | 2 | 待机 |
| 11 | 3 | 故障 |
| 12 | 4 | 在修 |
| 13 | 5 | 缺人 |
| 14 | 6 | 缺料 |
| 15 | 7 | 清零 |
---
## 五、完整 JSON 示例(含 di_reg
### 5.1 报工示例第2道工序
```json
{
"header": "+YAV",
"Card": "8multi",
"id": 101126010002,
"dt": 300,
"a1": 5.2,
"a2": 0.0,
"q1": 0.0,
"q2": 0.0,
"t1": 26.5,
"t2": 25.0,
"dht1": 60.0,
"dht2": 58.0,
"cn_reg": 1,
"f1": 50.00,
"f2": 0.00,
"c1": 50,
"c2": 0,
"di_reg": 1,
"do_reg": 0,
"reg1": 0.000,
"reg2": 0.000,
"tv": 0,
"save": 0,
"oee": 100,
"end": "EEFF"
}
```
→ 全局查找 `sort=2``pro_status IN('A','B')` 的工单分录 → 报工数量 = 工单计划数量 → 工单状态置为 D
再次收到 `di_reg=1` → 同工单已为 D查询结果为空 → 跳过,**天然幂等**
### 5.2 设备状态示例设置为故障di_reg=11
```json
{
"cn_reg": 1,
"di_reg": 11,
...
}
```
`cn_reg` 强制覆盖为 3故障`device_di_reg_log` 写入 STATUS 记录OEE 状态显示红色
---
## 六、实施步骤(清单)
- [ ] 执行 DDL 脚本 `2026-03-06_v2.0.004_周启威_8Multi报工、设备状态修改.sql`ALTER + CREATE TABLE
- [ ] `DeviceData.java` 新增 `diReg` 字段(`@TableField("di_reg")`
- [ ] 新建 `DeviceDiRegLog.java` 实体(对应 `device_di_reg_log` 表)
- [ ] 新建 `DeviceDiRegLogMapper.java``extends BaseMapper<DeviceDiRegLog>`
- [ ] `WorkOrderEntryMapper.java` 新增 `selectActiveEntriesByProcessSort` 方法
- [ ] 新建 `WorkOrderEntryMapper.xml`,添加对应 SQL
- [ ] `Multi8ProtocolService.java` 注入 `WorkOrderMapper``WorkOrderEntryMapper``IReportService``DeviceDiRegLogMapper`
- [ ] `Multi8ProtocolService.java``parse8MultiJson` 中新增 `di_reg` 解析分发
- [ ] `Multi8ProtocolService.java` 新增 `handleAutoReport``saveReportLog``saveStatusLog` 方法
- [ ] 联调测试di_reg=0 触发报工di_reg=11 触发故障状态
---
## 七、注意事项
1. **幂等性**:第一次触发时全量报工并将工单置为 D已完成后续同 `di_reg` 触发时,`pro_status IN('A','B')` 查询自动排除 D 状态工单,**无需额外判断**。
2. **报工数量**:固定取 `workOrder.getQuantity()`(计划数量),全量一次性完成,与 c1 计数器无关。
3. **di_reg = null**:旧版固件不携带此字段时,`parseInt` 返回 null分发代码不执行**不影响原有解析逻辑**。
4. **`saveStatusLog` 调用时机**:应在 `deviceDataMapper.insert(data)` **之后**调用,避免 insert 失败时日志已落库;`parse8MultiJson` 中需将 `saveStatusLog` 调用挪至 insert 语句后。
5. **`DeviceDiRegLog` 不继承 BaseEntity**:该实体仅需 `@TableName``@TableId` 注解,手动设置 `triggerTime`,不依赖框架自动填充的 `create_time`/`create_by` 等字段DDL 也不必添加这些列。
6. **事务隔离**`handleAutoReport``@Transactional`,每个工单报工+状态更新原子提交;但各工单之间彼此独立(外层 try-catch单个工单失败不影响其他工单。