feat: 改造为冷轧双机架二级控制系统
- 删除 ruoyi-flowable 模块,移除所有 Flowable 依赖 - 新建 ruoyi-mill 业务模块: * protocol: iXComPCS UDP 电文编解码器(7条电文完整定义) * udp: UdpServer/UdpSender/TelegramDispatcher * domain: MillPlan / MillProductionResult * service/controller: 生产计划、产出管理骨架 - 前端改造为工业深色主题(深海军蓝 + 工业青色) - 系统标题更新为"冷轧双机架二级控制系统" - application.yml 新增 mill.udp 配置节 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
package com.ruoyi.mill.controller;
|
||||
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.mill.domain.MillPlan;
|
||||
import com.ruoyi.mill.service.IMillPlanService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/mill/plan")
|
||||
public class MillPlanController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private IMillPlanService millPlanService;
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('mill:plan:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(MillPlan query) {
|
||||
startPage();
|
||||
List<MillPlan> list = millPlanService.selectMillPlanList(query);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('mill:plan:query')")
|
||||
@GetMapping("/{planNo}")
|
||||
public AjaxResult getInfo(@PathVariable String planNo) {
|
||||
return AjaxResult.success(millPlanService.selectMillPlanByPlanNo(planNo));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('mill:plan:add')")
|
||||
@PostMapping
|
||||
public AjaxResult add(@RequestBody MillPlan plan) {
|
||||
return toAjax(millPlanService.insertMillPlan(plan));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('mill:plan:edit')")
|
||||
@PutMapping
|
||||
public AjaxResult edit(@RequestBody MillPlan plan) {
|
||||
return toAjax(millPlanService.updateMillPlan(plan));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('mill:plan:cancel')")
|
||||
@PutMapping("/cancel/{planNo}/{matSeqNo}")
|
||||
public AjaxResult cancel(@PathVariable String planNo, @PathVariable String matSeqNo) {
|
||||
return toAjax(millPlanService.cancelMillPlan(planNo, matSeqNo));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.ruoyi.mill.controller;
|
||||
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.mill.domain.MillProductionResult;
|
||||
import com.ruoyi.mill.service.IMillProductionService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/mill/production")
|
||||
public class MillProductionController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private IMillProductionService productionService;
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('mill:production:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(MillProductionResult query) {
|
||||
startPage();
|
||||
List<MillProductionResult> list = productionService.selectResultList(query);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('mill:production:query')")
|
||||
@GetMapping("/{id}")
|
||||
public AjaxResult getInfo(@PathVariable Long id) {
|
||||
return AjaxResult.success(productionService.selectResultById(id));
|
||||
}
|
||||
|
||||
/** 录入产出结果并自动通过 UDP 上报 K12F03 */
|
||||
@PreAuthorize("@ss.hasPermi('mill:production:add')")
|
||||
@PostMapping
|
||||
public AjaxResult add(@RequestBody MillProductionResult result) {
|
||||
return toAjax(productionService.saveAndReport(result));
|
||||
}
|
||||
|
||||
/** 删除计划钢卷并发送 K12F02 */
|
||||
@PreAuthorize("@ss.hasPermi('mill:production:delete')")
|
||||
@DeleteMapping("/coil")
|
||||
public AjaxResult deleteCoil(@RequestParam String planNo,
|
||||
@RequestParam Integer seqNo,
|
||||
@RequestParam String unitCode,
|
||||
@RequestParam String inMatNo,
|
||||
@RequestParam String causeDesc) {
|
||||
return toAjax(productionService.deleteCoilAndNotify(planNo, seqNo, unitCode, inMatNo, causeDesc));
|
||||
}
|
||||
}
|
||||
128
ruoyi-mill/src/main/java/com/ruoyi/mill/domain/MillPlan.java
Normal file
128
ruoyi-mill/src/main/java/com/ruoyi/mill/domain/MillPlan.java
Normal file
@@ -0,0 +1,128 @@
|
||||
package com.ruoyi.mill.domain;
|
||||
|
||||
import com.ruoyi.common.annotation.Excel;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 冷轧生产计划 — 对应 2FK101 作业命令信息
|
||||
*/
|
||||
public class MillPlan extends BaseEntity {
|
||||
|
||||
@Excel(name = "计划号")
|
||||
private String planNo;
|
||||
|
||||
@Excel(name = "序号")
|
||||
private String matSeqNo;
|
||||
|
||||
@Excel(name = "机组代码")
|
||||
private String unitCode;
|
||||
|
||||
@Excel(name = "计划类型")
|
||||
private String planType;
|
||||
|
||||
/** 计划状态: 0-待生产 1-生产中 2-完成 3-撤销 */
|
||||
@Excel(name = "计划状态")
|
||||
private Integer planStatus;
|
||||
|
||||
@Excel(name = "入口钢卷号")
|
||||
private String inMatNo;
|
||||
|
||||
@Excel(name = "入口厚度(mm)")
|
||||
private BigDecimal inMatThick;
|
||||
|
||||
@Excel(name = "入口最大厚度(mm)")
|
||||
private BigDecimal inMatThickMax;
|
||||
|
||||
@Excel(name = "入口最小厚度(mm)")
|
||||
private BigDecimal inMatThickMin;
|
||||
|
||||
@Excel(name = "入口宽度(mm)")
|
||||
private BigDecimal inMatWidth;
|
||||
|
||||
@Excel(name = "入口重量(t)")
|
||||
private BigDecimal inMatWt;
|
||||
|
||||
@Excel(name = "入口长度(m)")
|
||||
private BigDecimal inMatLen;
|
||||
|
||||
@Excel(name = "入口内径(mm)")
|
||||
private BigDecimal inMatInDia;
|
||||
|
||||
@Excel(name = "入口外径(mm)")
|
||||
private BigDecimal inMatDia;
|
||||
|
||||
@Excel(name = "炉号")
|
||||
private String pono;
|
||||
|
||||
@Excel(name = "钢种")
|
||||
private String sgSign;
|
||||
|
||||
@Excel(name = "出口材料号")
|
||||
private String outMatNo;
|
||||
|
||||
@Excel(name = "内部钢卷号")
|
||||
private String custInMatNo;
|
||||
|
||||
@Excel(name = "备注")
|
||||
private String remark;
|
||||
|
||||
// ---- getters / setters ----
|
||||
|
||||
public String getPlanNo() { return planNo; }
|
||||
public void setPlanNo(String v) { this.planNo = v; }
|
||||
|
||||
public String getMatSeqNo() { return matSeqNo; }
|
||||
public void setMatSeqNo(String v) { this.matSeqNo = v; }
|
||||
|
||||
public String getUnitCode() { return unitCode; }
|
||||
public void setUnitCode(String v) { this.unitCode = v; }
|
||||
|
||||
public String getPlanType() { return planType; }
|
||||
public void setPlanType(String v) { this.planType = v; }
|
||||
|
||||
public Integer getPlanStatus() { return planStatus; }
|
||||
public void setPlanStatus(Integer v) { this.planStatus = v; }
|
||||
|
||||
public String getInMatNo() { return inMatNo; }
|
||||
public void setInMatNo(String v) { this.inMatNo = v; }
|
||||
|
||||
public BigDecimal getInMatThick() { return inMatThick; }
|
||||
public void setInMatThick(BigDecimal v) { this.inMatThick = v; }
|
||||
|
||||
public BigDecimal getInMatThickMax() { return inMatThickMax; }
|
||||
public void setInMatThickMax(BigDecimal v) { this.inMatThickMax = v; }
|
||||
|
||||
public BigDecimal getInMatThickMin() { return inMatThickMin; }
|
||||
public void setInMatThickMin(BigDecimal v) { this.inMatThickMin = v; }
|
||||
|
||||
public BigDecimal getInMatWidth() { return inMatWidth; }
|
||||
public void setInMatWidth(BigDecimal v) { this.inMatWidth = v; }
|
||||
|
||||
public BigDecimal getInMatWt() { return inMatWt; }
|
||||
public void setInMatWt(BigDecimal v) { this.inMatWt = v; }
|
||||
|
||||
public BigDecimal getInMatLen() { return inMatLen; }
|
||||
public void setInMatLen(BigDecimal v) { this.inMatLen = v; }
|
||||
|
||||
public BigDecimal getInMatInDia() { return inMatInDia; }
|
||||
public void setInMatInDia(BigDecimal v) { this.inMatInDia = v; }
|
||||
|
||||
public BigDecimal getInMatDia() { return inMatDia; }
|
||||
public void setInMatDia(BigDecimal v) { this.inMatDia = v; }
|
||||
|
||||
public String getPono() { return pono; }
|
||||
public void setPono(String v) { this.pono = v; }
|
||||
|
||||
public String getSgSign() { return sgSign; }
|
||||
public void setSgSign(String v) { this.sgSign = v; }
|
||||
|
||||
public String getOutMatNo() { return outMatNo; }
|
||||
public void setOutMatNo(String v) { this.outMatNo = v; }
|
||||
|
||||
public String getCustInMatNo() { return custInMatNo; }
|
||||
public void setCustInMatNo(String v) { this.custInMatNo = v; }
|
||||
|
||||
public String getRemark() { return remark; }
|
||||
public void setRemark(String v) { this.remark = v; }
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package com.ruoyi.mill.domain;
|
||||
|
||||
import com.ruoyi.common.annotation.Excel;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 冷轧产出信息 — 对应 K12F03 生产信息电文
|
||||
*/
|
||||
public class MillProductionResult extends BaseEntity {
|
||||
|
||||
/** 操作标识: I-新增 U-修改 D-删除 */
|
||||
@Excel(name = "操作标识")
|
||||
private String flag;
|
||||
|
||||
@Excel(name = "计划号")
|
||||
private String planNo;
|
||||
|
||||
@Excel(name = "序号")
|
||||
private Integer seqNo;
|
||||
|
||||
@Excel(name = "机组代码")
|
||||
private String unitCode;
|
||||
|
||||
@Excel(name = "产出序号")
|
||||
private String procSeqNo;
|
||||
|
||||
@Excel(name = "产出类型")
|
||||
private String annealFlag;
|
||||
|
||||
@Excel(name = "入口钢卷号")
|
||||
private String inMatNo;
|
||||
|
||||
@Excel(name = "入口重量(t)")
|
||||
private Integer inMatWt;
|
||||
|
||||
@Excel(name = "入口厚度(mm)")
|
||||
private BigDecimal inMatThick;
|
||||
|
||||
@Excel(name = "入口宽度(mm)")
|
||||
private BigDecimal inMatWidth;
|
||||
|
||||
@Excel(name = "出口钢卷号")
|
||||
private String outMatNo;
|
||||
|
||||
@Excel(name = "内部钢卷号")
|
||||
private String custMatNo;
|
||||
|
||||
@Excel(name = "出口实际重量(t)")
|
||||
private Integer outMatActWt;
|
||||
|
||||
@Excel(name = "出口实际厚度(mm)")
|
||||
private BigDecimal outMatActThick;
|
||||
|
||||
@Excel(name = "出口实际宽度(mm)")
|
||||
private BigDecimal outMatActWidth;
|
||||
|
||||
@Excel(name = "出口实际长度(m)")
|
||||
private BigDecimal outMatActLen;
|
||||
|
||||
@Excel(name = "出口内径(mm)")
|
||||
private BigDecimal outMatActInnerDia;
|
||||
|
||||
@Excel(name = "出口外径(mm)")
|
||||
private BigDecimal outMatActOuterDia;
|
||||
|
||||
@Excel(name = "生产班次")
|
||||
private String prodShiftNo;
|
||||
|
||||
@Excel(name = "生产班组")
|
||||
private String prodShiftGroup;
|
||||
|
||||
@Excel(name = "开始时刻")
|
||||
private String startProdTime;
|
||||
|
||||
@Excel(name = "结束时刻")
|
||||
private String endProdTime;
|
||||
|
||||
@Excel(name = "炉台号")
|
||||
private String socketNo;
|
||||
|
||||
@Excel(name = "备注")
|
||||
private String remark;
|
||||
|
||||
// ---- getters / setters ----
|
||||
|
||||
public String getFlag() { return flag; }
|
||||
public void setFlag(String v) { this.flag = v; }
|
||||
|
||||
public String getPlanNo() { return planNo; }
|
||||
public void setPlanNo(String v) { this.planNo = v; }
|
||||
|
||||
public Integer getSeqNo() { return seqNo; }
|
||||
public void setSeqNo(Integer v) { this.seqNo = v; }
|
||||
|
||||
public String getUnitCode() { return unitCode; }
|
||||
public void setUnitCode(String v) { this.unitCode = v; }
|
||||
|
||||
public String getProcSeqNo() { return procSeqNo; }
|
||||
public void setProcSeqNo(String v) { this.procSeqNo = v; }
|
||||
|
||||
public String getAnnealFlag() { return annealFlag; }
|
||||
public void setAnnealFlag(String v) { this.annealFlag = v; }
|
||||
|
||||
public String getInMatNo() { return inMatNo; }
|
||||
public void setInMatNo(String v) { this.inMatNo = v; }
|
||||
|
||||
public Integer getInMatWt() { return inMatWt; }
|
||||
public void setInMatWt(Integer v) { this.inMatWt = v; }
|
||||
|
||||
public BigDecimal getInMatThick() { return inMatThick; }
|
||||
public void setInMatThick(BigDecimal v) { this.inMatThick = v; }
|
||||
|
||||
public BigDecimal getInMatWidth() { return inMatWidth; }
|
||||
public void setInMatWidth(BigDecimal v) { this.inMatWidth = v; }
|
||||
|
||||
public String getOutMatNo() { return outMatNo; }
|
||||
public void setOutMatNo(String v) { this.outMatNo = v; }
|
||||
|
||||
public String getCustMatNo() { return custMatNo; }
|
||||
public void setCustMatNo(String v) { this.custMatNo = v; }
|
||||
|
||||
public Integer getOutMatActWt() { return outMatActWt; }
|
||||
public void setOutMatActWt(Integer v) { this.outMatActWt = v; }
|
||||
|
||||
public BigDecimal getOutMatActThick() { return outMatActThick; }
|
||||
public void setOutMatActThick(BigDecimal v) { this.outMatActThick = v; }
|
||||
|
||||
public BigDecimal getOutMatActWidth() { return outMatActWidth; }
|
||||
public void setOutMatActWidth(BigDecimal v) { this.outMatActWidth = v; }
|
||||
|
||||
public BigDecimal getOutMatActLen() { return outMatActLen; }
|
||||
public void setOutMatActLen(BigDecimal v) { this.outMatActLen = v; }
|
||||
|
||||
public BigDecimal getOutMatActInnerDia() { return outMatActInnerDia; }
|
||||
public void setOutMatActInnerDia(BigDecimal v) { this.outMatActInnerDia = v; }
|
||||
|
||||
public BigDecimal getOutMatActOuterDia() { return outMatActOuterDia; }
|
||||
public void setOutMatActOuterDia(BigDecimal v) { this.outMatActOuterDia = v; }
|
||||
|
||||
public String getProdShiftNo() { return prodShiftNo; }
|
||||
public void setProdShiftNo(String v) { this.prodShiftNo = v; }
|
||||
|
||||
public String getProdShiftGroup() { return prodShiftGroup; }
|
||||
public void setProdShiftGroup(String v) { this.prodShiftGroup = v; }
|
||||
|
||||
public String getStartProdTime() { return startProdTime; }
|
||||
public void setStartProdTime(String v) { this.startProdTime = v; }
|
||||
|
||||
public String getEndProdTime() { return endProdTime; }
|
||||
public void setEndProdTime(String v) { this.endProdTime = v; }
|
||||
|
||||
public String getSocketNo() { return socketNo; }
|
||||
public void setSocketNo(String v) { this.socketNo = v; }
|
||||
|
||||
public String getRemark() { return remark; }
|
||||
public void setRemark(String v) { this.remark = v; }
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.ruoyi.mill.protocol;
|
||||
|
||||
/**
|
||||
* 电文字段描述符(与 Excel 字段定义一一对应)
|
||||
*/
|
||||
public class FieldDef {
|
||||
|
||||
private final String name;
|
||||
private final FieldType type;
|
||||
/** 字符型长度(字节数);整型固定4,浮点固定4,长整型固定8 */
|
||||
private final int length;
|
||||
/** 浮点精度(仅用于格式化输出,编解码不依赖此值) */
|
||||
private final int precision;
|
||||
|
||||
public FieldDef(String name, FieldType type, int length, int precision) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.length = length;
|
||||
this.precision = precision;
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
public FieldType getType() { return type; }
|
||||
public int getLength() { return type == FieldType.STRING ? length : type == FieldType.LONG ? 8 : 4; }
|
||||
public int getPrecision() { return precision; }
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.ruoyi.mill.protocol;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* iXComPCS UDP 电文编解码器(第18章协议)
|
||||
*
|
||||
* 编码规则:
|
||||
* 字符型 → 固定长度,右侧以 0x20(空格) 填充,超长截断
|
||||
* 整型 → 4 字节,大端序 (BIG_ENDIAN)
|
||||
* 浮点型 → 4 字节 IEEE-754,小端序 (LITTLE_ENDIAN)
|
||||
* 长整型 → 8 字节,大端序
|
||||
*/
|
||||
public class TelegramCodec {
|
||||
|
||||
private TelegramCodec() {}
|
||||
|
||||
/** 将字段 Map 按字段定义顺序序列化为字节数组 */
|
||||
public static byte[] encode(List<FieldDef> schema, Map<String, Object> data) {
|
||||
int totalLen = calcTotalLength(schema);
|
||||
ByteBuffer buf = ByteBuffer.allocate(totalLen);
|
||||
|
||||
for (FieldDef fd : schema) {
|
||||
Object val = data.getOrDefault(fd.getName(), defaultValue(fd));
|
||||
switch (fd.getType()) {
|
||||
case STRING: {
|
||||
String s = val == null ? "" : val.toString();
|
||||
byte[] bytes = s.getBytes(StandardCharsets.US_ASCII);
|
||||
byte[] fixed = new byte[fd.getLength()];
|
||||
Arrays.fill(fixed, (byte) 0x20);
|
||||
System.arraycopy(bytes, 0, fixed, 0, Math.min(bytes.length, fixed.length));
|
||||
buf.put(fixed);
|
||||
break;
|
||||
}
|
||||
case INT: {
|
||||
int i = val instanceof Number ? ((Number) val).intValue() : 0;
|
||||
buf.order(ByteOrder.BIG_ENDIAN).putInt(i);
|
||||
break;
|
||||
}
|
||||
case FLOAT: {
|
||||
float f = val instanceof Number ? ((Number) val).floatValue() : 0f;
|
||||
buf.order(ByteOrder.LITTLE_ENDIAN).putFloat(f);
|
||||
break;
|
||||
}
|
||||
case LONG: {
|
||||
long l = val instanceof Number ? ((Number) val).longValue() : 0L;
|
||||
buf.order(ByteOrder.BIG_ENDIAN).putLong(l);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf.array();
|
||||
}
|
||||
|
||||
/** 将字节数组按字段定义顺序反序列化为字段 Map */
|
||||
public static Map<String, Object> decode(List<FieldDef> schema, byte[] raw) {
|
||||
ByteBuffer buf = ByteBuffer.wrap(raw);
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
|
||||
for (FieldDef fd : schema) {
|
||||
if (buf.remaining() < fd.getLength()) break;
|
||||
switch (fd.getType()) {
|
||||
case STRING: {
|
||||
byte[] bytes = new byte[fd.getLength()];
|
||||
buf.get(bytes);
|
||||
String s = new String(bytes, StandardCharsets.US_ASCII).stripTrailing();
|
||||
result.put(fd.getName(), s);
|
||||
break;
|
||||
}
|
||||
case INT: {
|
||||
result.put(fd.getName(), buf.order(ByteOrder.BIG_ENDIAN).getInt());
|
||||
break;
|
||||
}
|
||||
case FLOAT: {
|
||||
result.put(fd.getName(), buf.order(ByteOrder.LITTLE_ENDIAN).getFloat());
|
||||
break;
|
||||
}
|
||||
case LONG: {
|
||||
result.put(fd.getName(), buf.order(ByteOrder.BIG_ENDIAN).getLong());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int calcTotalLength(List<FieldDef> schema) {
|
||||
return schema.stream().mapToInt(FieldDef::getLength).sum();
|
||||
}
|
||||
|
||||
private static Object defaultValue(FieldDef fd) {
|
||||
switch (fd.getType()) {
|
||||
case INT: return 0;
|
||||
case FLOAT: return 0.0f;
|
||||
case LONG: return 0L;
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.ruoyi.mill.protocol;
|
||||
|
||||
/**
|
||||
* iXComPCS 电文字段类型定义(对应 Sheet2 编码规则)
|
||||
* 字符型: 空格右填充的定长 ASCII 字符串
|
||||
* 整型: 4字节有符号整数,大端序
|
||||
* 浮点型: 4字节 IEEE-754 单精度,小端序
|
||||
* 长整型: 8字节有符号整数,大端序
|
||||
*/
|
||||
public enum FieldType {
|
||||
STRING, // 字符型 ' '
|
||||
INT, // 整型 '>'
|
||||
FLOAT, // 浮点型 '<'
|
||||
LONG // 长整型 '|'
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
package com.ruoyi.mill.protocol;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static com.ruoyi.mill.protocol.FieldType.*;
|
||||
|
||||
/**
|
||||
* 7 条电文的字段 Schema 定义(与 电文/*.xlsx 完全对应)
|
||||
*
|
||||
* L3→L2(接收):
|
||||
* 2FK101 作业命令信息
|
||||
* 2FK102 作业命令撤销
|
||||
* 2FK103 作业命令应答
|
||||
* 2FK104 产出信息应答
|
||||
*
|
||||
* L2→L3(发送):
|
||||
* K12F01 计划信息应答
|
||||
* K12F02 计划钢卷删除
|
||||
* K12F03 生产信息电文
|
||||
*/
|
||||
public final class TelegramSchema {
|
||||
|
||||
private TelegramSchema() {}
|
||||
|
||||
private static FieldDef s(String name, int len) { return new FieldDef(name, STRING, len, 0); }
|
||||
private static FieldDef i(String name) { return new FieldDef(name, INT, 4, 0); }
|
||||
private static FieldDef f(String name, int len, int precision) { return new FieldDef(name, FLOAT, len, precision); }
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 2FK101 作业命令信息 L3→L2
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
public static final String ID_2FK101 = "2FK101";
|
||||
public static final List<FieldDef> SCHEMA_2FK101 = Collections.unmodifiableList(Arrays.asList(
|
||||
s("PLAN_NO", 20),
|
||||
s("MAT_SEQ_NO", 3),
|
||||
s("UNIT_CODE", 4),
|
||||
s("PLAN_TYPE", 1),
|
||||
s("IN_MAT_NO", 20),
|
||||
f("IN_MAT_THICK", 9, 3),
|
||||
f("IN_MAT_THICK_MAX", 9, 3),
|
||||
f("IN_MAT_THICK_MIN", 9, 3),
|
||||
f("IN_MAT_WIDTH", 10, 3),
|
||||
f("IN_MAT_WT", 5, 0),
|
||||
f("IN_MAT_LEN", 12, 3),
|
||||
f("IN_MAT_IN_DIA", 8, 2),
|
||||
f("IN_MAT_DIA", 8, 2),
|
||||
s("PONO", 10),
|
||||
s("SG_SIGN", 24),
|
||||
s("OUT_MAT_NO", 20),
|
||||
s("CUST_IN_MAT_NO", 20),
|
||||
s("REMARK", 250),
|
||||
s("SPARE_1", 20),
|
||||
s("SPARE_2", 20),
|
||||
s("SPARE_3", 20),
|
||||
s("SPARE_4", 20),
|
||||
s("SPARE_5", 20),
|
||||
f("SPARE_6", 13, 3),
|
||||
f("SPARE_7", 13, 3),
|
||||
f("SPARE_8", 13, 3),
|
||||
f("SPARE_9", 13, 3),
|
||||
f("SPARE_10", 13, 3)
|
||||
));
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 2FK102 作业命令撤销 L3→L2
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
public static final String ID_2FK102 = "2FK102";
|
||||
public static final List<FieldDef> SCHEMA_2FK102 = Collections.unmodifiableList(Arrays.asList(
|
||||
s("PLAN_NO", 20),
|
||||
i("MAT_SEQ_NO"),
|
||||
s("UNIT_CODE", 4),
|
||||
s("IN_MAT_NO", 20),
|
||||
s("SPARE_1", 20),
|
||||
s("SPARE_2", 20),
|
||||
s("SPARE_3", 20),
|
||||
s("SPARE_4", 20),
|
||||
s("SPARE_5", 20)
|
||||
));
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 2FK103 作业命令应答 L3→L2
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
public static final String ID_2FK103 = "2FK103";
|
||||
public static final List<FieldDef> SCHEMA_2FK103 = Collections.unmodifiableList(Arrays.asList(
|
||||
s("TC_NO", 6),
|
||||
s("PLAN_NO", 20),
|
||||
i("SEQ_NO"),
|
||||
s("UNIT_CODE", 4),
|
||||
s("IN_MAT_NO", 20),
|
||||
s("FLAG", 1),
|
||||
s("DEAL_RESULT", 50),
|
||||
s("SPARE_1", 20),
|
||||
s("SPARE_2", 20),
|
||||
s("SPARE_3", 20),
|
||||
s("SPARE_4", 20),
|
||||
s("SPARE_5", 20)
|
||||
));
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 2FK104 产出信息应答 L3→L2
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
public static final String ID_2FK104 = "2FK104";
|
||||
public static final List<FieldDef> SCHEMA_2FK104 = Collections.unmodifiableList(Arrays.asList(
|
||||
s("TC_NO", 6),
|
||||
s("PLAN_NO", 20),
|
||||
i("SEQ_NO"),
|
||||
s("UNIT_CODE", 4),
|
||||
s("OUT_MAT_NO", 20),
|
||||
s("FLAG", 1),
|
||||
s("DEAL_RESULT", 50),
|
||||
s("SPARE_1", 20),
|
||||
s("SPARE_2", 20),
|
||||
s("SPARE_3", 20),
|
||||
s("SPARE_4", 20),
|
||||
s("SPARE_5", 20)
|
||||
));
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// K12F01 计划信息应答 L2→L3
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
public static final String ID_K12F01 = "K12F01";
|
||||
public static final List<FieldDef> SCHEMA_K12F01 = Collections.unmodifiableList(Arrays.asList(
|
||||
s("TC_NO", 6),
|
||||
s("PLAN_NO", 20),
|
||||
i("SEQ_NO"),
|
||||
s("UNIT_CODE", 4),
|
||||
s("IN_MAT_NO", 20),
|
||||
s("FLAG", 1),
|
||||
s("DEAL_RESULT", 50),
|
||||
s("SPARE_1", 20),
|
||||
s("SPARE_2", 20),
|
||||
s("SPARE_3", 20),
|
||||
s("SPARE_4", 20),
|
||||
s("SPARE_5", 20)
|
||||
));
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// K12F02 计划钢卷删除 L2→L3
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
public static final String ID_K12F02 = "K12F02";
|
||||
public static final List<FieldDef> SCHEMA_K12F02 = Collections.unmodifiableList(Arrays.asList(
|
||||
s("PLAN_NO", 20),
|
||||
i("SEQ_NO"),
|
||||
s("UNIT_CODE", 4),
|
||||
s("IN_MAT_NO", 20),
|
||||
s("CAUSE_DESC", 50),
|
||||
s("DO_TIME", 14),
|
||||
s("SPARE_1", 20),
|
||||
s("SPARE_2", 20),
|
||||
s("SPARE_3", 20),
|
||||
s("SPARE_4", 20),
|
||||
s("SPARE_5", 20)
|
||||
));
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// K12F03 生产信息电文 L2→L3
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
public static final String ID_K12F03 = "K12F03";
|
||||
public static final List<FieldDef> SCHEMA_K12F03 = Collections.unmodifiableList(Arrays.asList(
|
||||
s("FLAG", 1),
|
||||
s("PLAN_NO", 20),
|
||||
i("SEQ_NO"),
|
||||
s("UNIT_CODE", 4),
|
||||
s("PROC_SEQ_NO", 4),
|
||||
s("ANNEAL_FLAG", 16),
|
||||
s("IN_MAT_NO", 20),
|
||||
i("IN_MAT_WT"),
|
||||
f("IN_MAT_THICK", 9, 3),
|
||||
f("IN_MAT_WIDTH", 10, 3),
|
||||
s("OUT_MAT_NO", 20),
|
||||
s("CUST_MAT_NO", 20),
|
||||
i("OUT_MAT_ACT_WT"),
|
||||
f("OUT_MAT_ACT_THICK", 9, 3),
|
||||
f("OUT_MAT_ACT_WIDTH", 10, 3),
|
||||
f("OUT_MAT_ACT_LEN", 15, 3),
|
||||
f("OUT_MAT_ACT_INNER_DIA", 8, 2),
|
||||
f("OUT_MAT_ACT_OUTER_DIA", 8, 2),
|
||||
s("PROD_SHIFT_NO", 1),
|
||||
s("PROD_SHIFT_GROUP", 1),
|
||||
s("START_PROD_TIME", 14),
|
||||
s("END_PROD_TIME", 14),
|
||||
s("SOCKET_NO", 2),
|
||||
s("REMARK", 100),
|
||||
s("SPARE_1", 20),
|
||||
s("SPARE_2", 20),
|
||||
s("SPARE_3", 20),
|
||||
s("SPARE_4", 20),
|
||||
s("SPARE_5", 20)
|
||||
));
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.ruoyi.mill.service;
|
||||
|
||||
import com.ruoyi.mill.domain.MillPlan;
|
||||
import java.util.List;
|
||||
|
||||
public interface IMillPlanService {
|
||||
|
||||
List<MillPlan> selectMillPlanList(MillPlan query);
|
||||
|
||||
MillPlan selectMillPlanByPlanNo(String planNo);
|
||||
|
||||
int insertMillPlan(MillPlan plan);
|
||||
|
||||
int updateMillPlan(MillPlan plan);
|
||||
|
||||
/** 撤销计划(L3 下发 2FK102 后调用) */
|
||||
int cancelMillPlan(String planNo, String matSeqNo);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.ruoyi.mill.service;
|
||||
|
||||
import com.ruoyi.mill.domain.MillProductionResult;
|
||||
import java.util.List;
|
||||
|
||||
public interface IMillProductionService {
|
||||
|
||||
List<MillProductionResult> selectResultList(MillProductionResult query);
|
||||
|
||||
MillProductionResult selectResultById(Long id);
|
||||
|
||||
/** 保存产出并通过 UDP 发送 K12F03 */
|
||||
int saveAndReport(MillProductionResult result);
|
||||
|
||||
/** 删除计划钢卷并发送 K12F02 */
|
||||
int deleteCoilAndNotify(String planNo, Integer seqNo, String unitCode,
|
||||
String inMatNo, String causeDesc);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.ruoyi.mill.service.impl;
|
||||
|
||||
import com.ruoyi.mill.domain.MillPlan;
|
||||
import com.ruoyi.mill.service.IMillPlanService;
|
||||
import com.ruoyi.mill.udp.UdpSender;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class MillPlanServiceImpl implements IMillPlanService {
|
||||
|
||||
@Autowired
|
||||
private UdpSender udpSender;
|
||||
|
||||
// TODO: 注入 MillPlanMapper 并替换以下占位实现
|
||||
|
||||
@Override
|
||||
public List<MillPlan> selectMillPlanList(MillPlan query) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MillPlan selectMillPlanByPlanNo(String planNo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int insertMillPlan(MillPlan plan) {
|
||||
// TODO: mapper.insert(plan)
|
||||
// 写入成功后回复 K12F01 计划信息应答
|
||||
sendK12F01Ack(plan.getPlanNo(), plan.getMatSeqNo(),
|
||||
plan.getUnitCode(), plan.getInMatNo(), "1", "OK");
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int updateMillPlan(MillPlan plan) {
|
||||
return 0; // TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public int cancelMillPlan(String planNo, String matSeqNo) {
|
||||
// TODO: mapper 软删除
|
||||
return 1;
|
||||
}
|
||||
|
||||
private void sendK12F01Ack(String planNo, String seqNo, String unitCode,
|
||||
String inMatNo, String flag, String dealResult) {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("TC_NO", "K12F01");
|
||||
data.put("PLAN_NO", planNo);
|
||||
data.put("SEQ_NO", 0);
|
||||
data.put("UNIT_CODE", unitCode);
|
||||
data.put("IN_MAT_NO", inMatNo);
|
||||
data.put("FLAG", flag);
|
||||
data.put("DEAL_RESULT", dealResult);
|
||||
udpSender.sendK12F01(data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.ruoyi.mill.service.impl;
|
||||
|
||||
import com.ruoyi.mill.domain.MillProductionResult;
|
||||
import com.ruoyi.mill.service.IMillProductionService;
|
||||
import com.ruoyi.mill.udp.UdpSender;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class MillProductionServiceImpl implements IMillProductionService {
|
||||
|
||||
private static final DateTimeFormatter FMT = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
|
||||
|
||||
@Autowired
|
||||
private UdpSender udpSender;
|
||||
|
||||
// TODO: 注入 MillProductionResultMapper
|
||||
|
||||
@Override
|
||||
public List<MillProductionResult> selectResultList(MillProductionResult query) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MillProductionResult selectResultById(Long id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int saveAndReport(MillProductionResult r) {
|
||||
// TODO: mapper.insert(r)
|
||||
// 保存成功后发送 K12F03
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("FLAG", "I");
|
||||
data.put("PLAN_NO", r.getPlanNo());
|
||||
data.put("SEQ_NO", r.getSeqNo() == null ? 0 : r.getSeqNo());
|
||||
data.put("UNIT_CODE", r.getUnitCode());
|
||||
data.put("PROC_SEQ_NO", r.getProcSeqNo());
|
||||
data.put("ANNEAL_FLAG", r.getAnnealFlag());
|
||||
data.put("IN_MAT_NO", r.getInMatNo());
|
||||
data.put("IN_MAT_WT", r.getInMatWt() == null ? 0 : r.getInMatWt());
|
||||
data.put("IN_MAT_THICK", r.getInMatThick() == null ? 0f : r.getInMatThick().floatValue());
|
||||
data.put("IN_MAT_WIDTH", r.getInMatWidth() == null ? 0f : r.getInMatWidth().floatValue());
|
||||
data.put("OUT_MAT_NO", r.getOutMatNo());
|
||||
data.put("CUST_MAT_NO", r.getCustMatNo());
|
||||
data.put("OUT_MAT_ACT_WT", r.getOutMatActWt() == null ? 0 : r.getOutMatActWt());
|
||||
data.put("OUT_MAT_ACT_THICK", r.getOutMatActThick() == null ? 0f : r.getOutMatActThick().floatValue());
|
||||
data.put("OUT_MAT_ACT_WIDTH", r.getOutMatActWidth() == null ? 0f : r.getOutMatActWidth().floatValue());
|
||||
data.put("OUT_MAT_ACT_LEN", r.getOutMatActLen() == null ? 0f : r.getOutMatActLen().floatValue());
|
||||
data.put("OUT_MAT_ACT_INNER_DIA", r.getOutMatActInnerDia() == null ? 0f : r.getOutMatActInnerDia().floatValue());
|
||||
data.put("OUT_MAT_ACT_OUTER_DIA", r.getOutMatActOuterDia() == null ? 0f : r.getOutMatActOuterDia().floatValue());
|
||||
data.put("PROD_SHIFT_NO", r.getProdShiftNo());
|
||||
data.put("PROD_SHIFT_GROUP", r.getProdShiftGroup());
|
||||
data.put("START_PROD_TIME", r.getStartProdTime());
|
||||
data.put("END_PROD_TIME", r.getEndProdTime());
|
||||
data.put("SOCKET_NO", r.getSocketNo());
|
||||
data.put("REMARK", r.getRemark());
|
||||
udpSender.sendK12F03(data);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deleteCoilAndNotify(String planNo, Integer seqNo, String unitCode,
|
||||
String inMatNo, String causeDesc) {
|
||||
// TODO: mapper 软删除
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("PLAN_NO", planNo);
|
||||
data.put("SEQ_NO", seqNo == null ? 0 : seqNo);
|
||||
data.put("UNIT_CODE", unitCode);
|
||||
data.put("IN_MAT_NO", inMatNo);
|
||||
data.put("CAUSE_DESC", causeDesc);
|
||||
data.put("DO_TIME", LocalDateTime.now().format(FMT));
|
||||
udpSender.sendK12F02(data);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.ruoyi.mill.udp;
|
||||
|
||||
import com.ruoyi.mill.protocol.TelegramCodec;
|
||||
import com.ruoyi.mill.protocol.TelegramSchema;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 接收到的 L3→L2 电文分发处理器
|
||||
* 根据电文号前6字节路由到对应业务处理方法
|
||||
*/
|
||||
@Component
|
||||
public class TelegramDispatcher {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(TelegramDispatcher.class);
|
||||
|
||||
public void dispatch(String telegramId, byte[] payload) {
|
||||
log.info("[UDP-RECV] telegramId={} payloadLen={}", telegramId, payload.length);
|
||||
switch (telegramId) {
|
||||
case TelegramSchema.ID_2FK101:
|
||||
handle2FK101(TelegramCodec.decode(TelegramSchema.SCHEMA_2FK101, payload));
|
||||
break;
|
||||
case TelegramSchema.ID_2FK102:
|
||||
handle2FK102(TelegramCodec.decode(TelegramSchema.SCHEMA_2FK102, payload));
|
||||
break;
|
||||
case TelegramSchema.ID_2FK103:
|
||||
handle2FK103(TelegramCodec.decode(TelegramSchema.SCHEMA_2FK103, payload));
|
||||
break;
|
||||
case TelegramSchema.ID_2FK104:
|
||||
handle2FK104(TelegramCodec.decode(TelegramSchema.SCHEMA_2FK104, payload));
|
||||
break;
|
||||
default:
|
||||
log.warn("[UDP-RECV] 未知电文号: {}", telegramId);
|
||||
}
|
||||
}
|
||||
|
||||
/** 2FK101 作业命令信息 — L3 下发生产计划 */
|
||||
private void handle2FK101(Map<String, Object> data) {
|
||||
log.info("[2FK101] 作业命令: planNo={} inMatNo={} unitCode={}",
|
||||
data.get("PLAN_NO"), data.get("IN_MAT_NO"), data.get("UNIT_CODE"));
|
||||
// TODO: 写入计划表,触发 K12F01 应答
|
||||
}
|
||||
|
||||
/** 2FK102 作业命令撤销 — L3 撤销某条计划 */
|
||||
private void handle2FK102(Map<String, Object> data) {
|
||||
log.info("[2FK102] 作业撤销: planNo={} inMatNo={}", data.get("PLAN_NO"), data.get("IN_MAT_NO"));
|
||||
// TODO: 从计划表软删除,记录撤销原因
|
||||
}
|
||||
|
||||
/** 2FK103 作业命令应答 — L3 对 L2 发出计划的回执 */
|
||||
private void handle2FK103(Map<String, Object> data) {
|
||||
log.info("[2FK103] 命令应答: tcNo={} flag={} result={}",
|
||||
data.get("TC_NO"), data.get("FLAG"), data.get("DEAL_RESULT"));
|
||||
// TODO: 更新计划状态
|
||||
}
|
||||
|
||||
/** 2FK104 产出信息应答 — L3 对 L2 上报产出的回执 */
|
||||
private void handle2FK104(Map<String, Object> data) {
|
||||
log.info("[2FK104] 产出应答: tcNo={} flag={} result={}",
|
||||
data.get("TC_NO"), data.get("FLAG"), data.get("DEAL_RESULT"));
|
||||
// TODO: 更新产出确认状态
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.ruoyi.mill.udp;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* UDP 通信参数配置(application.yml: mill.udp.*)
|
||||
*/
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "mill.udp")
|
||||
public class UdpProperties {
|
||||
|
||||
/** 本地监听端口(接收 L3 下发电文) */
|
||||
private int localPort = 9001;
|
||||
|
||||
/** L3 系统 IP */
|
||||
private String remoteHost = "127.0.0.1";
|
||||
|
||||
/** L3 系统端口 */
|
||||
private int remotePort = 9000;
|
||||
|
||||
/** 接收缓冲区大小(字节) */
|
||||
private int bufferSize = 4096;
|
||||
|
||||
public int getLocalPort() { return localPort; }
|
||||
public void setLocalPort(int p) { this.localPort = p; }
|
||||
public String getRemoteHost() { return remoteHost; }
|
||||
public void setRemoteHost(String h){ this.remoteHost = h; }
|
||||
public int getRemotePort() { return remotePort; }
|
||||
public void setRemotePort(int p) { this.remotePort = p; }
|
||||
public int getBufferSize() { return bufferSize; }
|
||||
public void setBufferSize(int s) { this.bufferSize = s; }
|
||||
}
|
||||
62
ruoyi-mill/src/main/java/com/ruoyi/mill/udp/UdpSender.java
Normal file
62
ruoyi-mill/src/main/java/com/ruoyi/mill/udp/UdpSender.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package com.ruoyi.mill.udp;
|
||||
|
||||
import com.ruoyi.mill.protocol.TelegramCodec;
|
||||
import com.ruoyi.mill.protocol.TelegramSchema;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* UDP 发送服务 — L2→L3 上行电文
|
||||
* 帧结构:前6字节电文号 + 电文体
|
||||
*/
|
||||
@Component
|
||||
public class UdpSender {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(UdpSender.class);
|
||||
|
||||
@Autowired
|
||||
private UdpProperties props;
|
||||
|
||||
/** K12F01 计划信息应答 */
|
||||
public void sendK12F01(Map<String, Object> data) {
|
||||
send(TelegramSchema.ID_K12F01, TelegramCodec.encode(TelegramSchema.SCHEMA_K12F01, data));
|
||||
}
|
||||
|
||||
/** K12F02 计划钢卷删除 */
|
||||
public void sendK12F02(Map<String, Object> data) {
|
||||
send(TelegramSchema.ID_K12F02, TelegramCodec.encode(TelegramSchema.SCHEMA_K12F02, data));
|
||||
}
|
||||
|
||||
/** K12F03 生产信息电文 */
|
||||
public void sendK12F03(Map<String, Object> data) {
|
||||
send(TelegramSchema.ID_K12F03, TelegramCodec.encode(TelegramSchema.SCHEMA_K12F03, data));
|
||||
}
|
||||
|
||||
private void send(String tcNo, byte[] payload) {
|
||||
try (DatagramSocket socket = new DatagramSocket()) {
|
||||
// 帧 = 6字节电文号 + payload
|
||||
byte[] tcNoBytes = Arrays.copyOf(
|
||||
tcNo.getBytes(StandardCharsets.US_ASCII), 6);
|
||||
byte[] frame = new byte[6 + payload.length];
|
||||
System.arraycopy(tcNoBytes, 0, frame, 0, 6);
|
||||
System.arraycopy(payload, 0, frame, 6, payload.length);
|
||||
|
||||
InetAddress addr = InetAddress.getByName(props.getRemoteHost());
|
||||
DatagramPacket pkt = new DatagramPacket(frame, frame.length, addr, props.getRemotePort());
|
||||
socket.send(pkt);
|
||||
log.info("[UDP-SEND] tcNo={} -> {}:{} frameLen={}", tcNo,
|
||||
props.getRemoteHost(), props.getRemotePort(), frame.length);
|
||||
} catch (Exception e) {
|
||||
log.error("[UDP-SEND] 发送失败 tcNo={}", tcNo, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
77
ruoyi-mill/src/main/java/com/ruoyi/mill/udp/UdpServer.java
Normal file
77
ruoyi-mill/src/main/java/com/ruoyi/mill/udp/UdpServer.java
Normal file
@@ -0,0 +1,77 @@
|
||||
package com.ruoyi.mill.udp;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* UDP 接收服务
|
||||
* 监听来自 L3 的下行电文,解析电文号后交 TelegramDispatcher 处理
|
||||
*
|
||||
* 电文帧结构(iXComPCS 第18章):
|
||||
* 前6字节 ASCII = 电文号 (TC_NO)
|
||||
* 后续字节 = 电文体 (payload)
|
||||
*/
|
||||
@Component
|
||||
public class UdpServer {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(UdpServer.class);
|
||||
private static final int TC_NO_LEN = 6;
|
||||
|
||||
@Autowired
|
||||
private UdpProperties props;
|
||||
|
||||
@Autowired
|
||||
private TelegramDispatcher dispatcher;
|
||||
|
||||
private DatagramSocket socket;
|
||||
private volatile boolean running;
|
||||
private final ExecutorService executor = Executors.newSingleThreadExecutor(
|
||||
r -> { Thread t = new Thread(r, "udp-receiver"); t.setDaemon(true); return t; });
|
||||
|
||||
@PostConstruct
|
||||
public void start() throws Exception {
|
||||
socket = new DatagramSocket(props.getLocalPort());
|
||||
running = true;
|
||||
executor.submit(this::receiveLoop);
|
||||
log.info("[UDP-SERVER] 已启动,监听端口: {}", props.getLocalPort());
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void stop() {
|
||||
running = false;
|
||||
if (socket != null && !socket.isClosed()) socket.close();
|
||||
executor.shutdownNow();
|
||||
log.info("[UDP-SERVER] 已停止");
|
||||
}
|
||||
|
||||
private void receiveLoop() {
|
||||
byte[] buf = new byte[props.getBufferSize()];
|
||||
while (running) {
|
||||
try {
|
||||
DatagramPacket pkt = new DatagramPacket(buf, buf.length);
|
||||
socket.receive(pkt);
|
||||
byte[] data = Arrays.copyOf(pkt.getData(), pkt.getLength());
|
||||
if (data.length < TC_NO_LEN) {
|
||||
log.warn("[UDP-SERVER] 收到过短数据包,长度={}", data.length);
|
||||
continue;
|
||||
}
|
||||
String tcNo = new String(data, 0, TC_NO_LEN, StandardCharsets.US_ASCII).trim();
|
||||
byte[] payload = Arrays.copyOfRange(data, TC_NO_LEN, data.length);
|
||||
dispatcher.dispatch(tcNo, payload);
|
||||
} catch (Exception e) {
|
||||
if (running) log.error("[UDP-SERVER] 接收异常", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user