大族打标机 TCP 工具类优先设计 + 追溯打标业务落地
本文按工程实施顺序组织大族 TCP 客户端工具类源码追溯打标业务源码IP、端口、模板名动态配置方案含建表 SQL。一、大族打标机 TCP 工具类1.1 协议约定大族打标常见指令ASCII初始化模板$Initialize_模板名下发数据$Data_二维码,明码1,明码2...启动打标$MarkStart_通讯帧格式帧头0x02指令体ASCII帧尾0x03即0x02 ASCII(command) 0x03这部分协议看起来很简单但它是整个系统稳定性的基础。实际项目中90% 的“打标失败”问题都发生在这一层要么是帧格式不正确要么是字符编码不一致要么是设备响应超时没有被正确识别。所以建议在工具类里把协议收发固定下来业务层不要重复拼装帧避免不同模块各自实现导致线上行为不一致。下面给出完整工具类源码。该类定位为“纯通讯层”不包含任何工位、规则、流水号等业务逻辑便于在不同项目中复用。这样拆分的价值是当你未来更换设备型号、增加中间层例如网关服务、或者要做指令重试机制时只需要调整这个工具类不需要改业务服务的代码结构。1.2 工具类源码package blog.demo.marking.tcp; import lombok.extern.slf4j.Slf4j; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketTimeoutException; import java.nio.charset.StandardCharsets; /** * 大族打标 TCP 通讯工具类 * * 约定 * 1) 指令文本不含头尾 * 2) 工具类统一封装 0x02/0x03 帧 * 3) 每次命令短连接简单稳定便于排障 * 4) 此类只负责“设备通讯”不做业务规则判断单一职责 * * 设计说明 * - 业务层只需要调用 doMarking()无需感知底层帧协议 * - 遇到设备或网络异常时直接抛 RuntimeException 给上层统一兜底 * - 如需长连接优化可在本类基础上演进不影响业务层调用签名 * * author liwj */ Slf4j public class HansLaserTcpClient { private static final byte HEAD 0x02; private static final byte TAIL 0x03; private final String host; private final int port; private final int connectTimeoutMs; private final int readTimeoutMs; /** * param host 大族打标机 IP * param port 大族打标机端口 * param connectTimeoutMs 建连超时毫秒 * param readTimeoutMs 读响应超时毫秒 * author liwj */ public HansLaserTcpClient(String host, int port, int connectTimeoutMs, int readTimeoutMs) { if (host null || host.trim().isEmpty()) { throw new IllegalArgumentException(host 不能为空); } if (port 0) { throw new IllegalArgumentException(port 非法); } this.host host.trim(); this.port port; this.connectTimeoutMs Math.max(connectTimeoutMs, 1000); this.readTimeoutMs Math.max(readTimeoutMs, 1000); } /** * 打标流程初始化模板 - 下发数据 - 启动打标 * * param templateName 模板名大族软件中的模板 * param qrCode 二维码内容 * param plainSegments 明码分段 * return true 成功 * author liwj */ public boolean doMarking(String templateName, String qrCode, String[] plainSegments) { // 第一步初始化模板告诉设备“这次按哪个版式打标” if (!initialize(templateName)) { throw new RuntimeException(初始化模板失败: templateName); } // 第二步下发二维码和明码文本按模板对象顺序 if (!sendData(qrCode, plainSegments)) { throw new RuntimeException(下发数据失败); } // 第三步触发打标动作 if (!markStart()) { throw new RuntimeException(启动打标失败); } return true; } /** * 初始化大族模板 * * param templateName 模板名 * return true: 设备返回 $Initialize_OK * author liwj */ public boolean initialize(String templateName) { if (templateName null || templateName.trim().isEmpty()) { throw new IllegalArgumentException(templateName 不能为空); } String resp sendCommand($Initialize_ templateName.trim()); return $Initialize_OK.equals(resp); } /** * 下发二维码和明码数据 * * param qrCode 二维码内容 * param plainSegments 明码分段数组顺序需与模板文本对象顺序一致 * return true: 设备返回 $Receive_OK 或 $Data_OK * author liwj */ public boolean sendData(String qrCode, String[] plainSegments) { String[] segments (plainSegments null || plainSegments.length 0) ? new String[]{} : plainSegments; StringBuilder cmd new StringBuilder($Data_); cmd.append(qrCode null ? : qrCode); for (String seg : segments) { cmd.append(,).append(seg null ? : seg); } String resp sendCommand(cmd.toString()); return $Receive_OK.equals(resp) || $Data_OK.equals(resp); } /** * 启动打标 * * return true: 设备返回 $MarkStart_OK 前缀 * author liwj */ public boolean markStart() { String resp sendCommand($MarkStart_); return resp ! null resp.startsWith($MarkStart_OK); } /** * 发送单条指令并解析响应 * * param command 指令文本不含头尾 * return 响应文本不含头尾 * author liwj */ public String sendCommand(String command) { Socket socket null; OutputStream outputStream null; InputStream inputStream null; try { // 1) 建立 TCP 连接 socket new Socket(); socket.connect(new InetSocketAddress(host, port), connectTimeoutMs); socket.setSoTimeout(readTimeoutMs); // 2) 获取输入输出流 outputStream socket.getOutputStream(); inputStream socket.getInputStream(); // 3) 协议组帧后发送0x02 ASCII 0x03 outputStream.write(buildFrame(command)); outputStream.flush(); // 4) 读取响应并解帧 byte[] buffer new byte[2048]; int len inputStream.read(buffer); if (len 0) { return null; } return parseFrame(buffer, len); } catch (SocketTimeoutException e) { throw new RuntimeException(打标机通信超时, e); } catch (IOException e) { throw new RuntimeException(打标机通信异常: e.getMessage(), e); } finally { closeQuietly(inputStream); closeQuietly(outputStream); closeQuietly(socket); } } private byte[] buildFrame(String command) { // 设备协议要求 ASCII 编码文本 byte[] body command.getBytes(StandardCharsets.US_ASCII); byte[] frame new byte[body.length 2]; frame[0] HEAD; System.arraycopy(body, 0, frame, 1, body.length); frame[frame.length - 1] TAIL; return frame; } private String parseFrame(byte[] buffer, int len) { // 防御性校验非标准帧直接返回 null交由上层判失败 if (len 2 || buffer[0] ! HEAD || buffer[len - 1] ! TAIL) { return null; } return new String(buffer, 1, len - 2, StandardCharsets.US_ASCII); } private void closeQuietly(AutoCloseable closeable) { if (closeable ! null) { try { closeable.close(); } catch (Exception e) { log.debug(close ignore, e); } } } }二、追溯打标业务如何调用工具类业务分层说明ControllerPDA 调preview、submitService根据工位/工件类型/规则选模板、生成码、拆明码Repository查规则、查工位、写事件、占流水TCP Client只负责和大族通讯。这里的重点是“职责边界”要清晰业务层负责“打什么”规则、模板、流水、明码分段通讯层负责“怎么发”TCP 连接、组帧、收响应。只要这个边界保持住后续维护成本会明显下降。2.1 业务核心代码示例package blog.demo.marking.biz; import blog.demo.marking.tcp.HansLaserTcpClient; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; Service RequiredArgsConstructor public class TraceMarkingService { private final DeviceConfigService deviceConfigService; private final MarkingRuleService markingRuleService; private final MarkingSerialService markingSerialService; private final TraceEventService traceEventService; /** * 真实追溯打标提交 * * param stationCode 工位编码决定 OP10/OP50 * param workpieceType 工件类型1/2/3 * return 打标二维码 * author liwj */ public String submit(String stationCode, String workpieceType) { boolean op50 stationCode ! null stationCode.toUpperCase().contains(OP50); // 1) 查工位绑定设备配置IP/端口/超时 DeviceTcpConfig device deviceConfigService.getDeviceByStation(stationCode); // 2) 查打标规则物料、备用码、生产阶段、版本标识等 MarkingRule rule markingRuleService.getLatestRuleByWorkpieceType(workpieceType); // 3) 占流水并发安全 int seq markingSerialService.claimNextSeq(rule.getTemplateBizKey(), rule.getSpareCode()); String serialNo SerialNoUtil.buildTodaySerial(seq); // 比如 T260413A0001 // 4) 组二维码/明码 String productTypeCode ProductTypeMapper.toProductTypeCode(workpieceType); // DZK/GXJ/ZDJ String qrCode serialNo productTypeCode rule.getProductionPhase(); String plainCode qrCode; // 5) 按工位解析模板 String templateName op50 ? deviceConfigService.getOp50TemplateBySpareCode(device.getDeviceCode(), rule.getSpareCode()) : deviceConfigService.getOp10TemplateByProductType(device.getDeviceCode(), productTypeCode); // 6) 明码拆分BTZ11/BTZ22/BTZ33 String[] plainSegments PlainSplitUtil.splitBySpareCode(rule.getSpareCode(), plainCode); // 7) 调大族 TCP 客户端 HansLaserTcpClient client new HansLaserTcpClient( device.getIp(), device.getPort(), device.getConnectTimeoutMs(), device.getReadTimeoutMs()); client.doMarking(templateName, qrCode, plainSegments); // 8) 记录追溯事件 traceEventService.saveMarkingEvent(stationCode, qrCode, plainCode, rule.getRuleId()); return qrCode; } }这段业务代码覆盖追溯打标核心动作占流水按工位和产品选模板打标成功后写追溯事件与设备通讯逻辑彻底解耦。另外在真实产线里建议把“打标请求号requestId”贯穿日志。这样一旦现场反馈某一件产品异常可以从业务日志快速关联到 TCP 指令日志和设备响应日志排查效率会高很多。三、动态配置IP、端口、模板名不要写死推荐最小可用模型为 3 张表设备连接表IP、端口、超时工位-设备绑定表模板映射表OP10 产品类型 / OP50 备用码动态配置的本质不只是“方便改参数”而是把“部署信息”和“业务逻辑”分离开。对于多产线、多设备、多机型场景这一点非常关键同一套程序可以在不同车间复用只需要调整配置数据即可。3.1 设备连接表CREATE TABLE marking_device_tcp_config ( id varchar(32) NOT NULL COMMENT 主键, device_code varchar(64) NOT NULL COMMENT 设备编码, device_name varchar(128) DEFAULT NULL COMMENT 设备名称, ip varchar(64) NOT NULL COMMENT 打标机IP, port int NOT NULL COMMENT 打标机端口, connect_timeout_ms int NOT NULL DEFAULT 15000 COMMENT 连接超时ms, read_timeout_ms int NOT NULL DEFAULT 300000 COMMENT 读超时ms, enabled tinyint(1) NOT NULL DEFAULT 1 COMMENT 是否启用, del_flag tinyint(1) NOT NULL DEFAULT 0 COMMENT 删除标记, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_device_code (device_code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT打标机TCP连接配置;3.2 工位绑定设备表CREATE TABLE marking_station_device_bind ( id varchar(32) NOT NULL, station_code varchar(64) NOT NULL COMMENT 工位编码, device_code varchar(64) NOT NULL COMMENT 设备编码, priority int NOT NULL DEFAULT 1 COMMENT 优先级数值越小越优先, enabled tinyint(1) NOT NULL DEFAULT 1, del_flag tinyint(1) NOT NULL DEFAULT 0, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_station_code (station_code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT工位-打标机绑定;3.3 模板映射表核心CREATE TABLE marking_template_mapping ( id varchar(32) NOT NULL, device_code varchar(64) NOT NULL COMMENT 设备编码, mapping_type varchar(32) NOT NULL COMMENT 映射类型OP10_PRODUCT_TYPE / OP50_SPARE_CODE, mapping_key varchar(64) NOT NULL COMMENT 映射键DZK/GXJ/ZDJ 或 BTZ11/BTZ22/BTZ33, template_name varchar(128) NOT NULL COMMENT 大族模板名, enabled tinyint(1) NOT NULL DEFAULT 1, del_flag tinyint(1) NOT NULL DEFAULT 0, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_device_type_key (device_code, mapping_type, mapping_key) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT打标模板映射;四、动态配置读取策略为了兼顾性能和可维护性建议使用“本地缓存 定时刷新”请求先读本地缓存低延迟缓存 miss 再查库并回填缓存每 30~60 秒定时全量刷新管理端改配置后可主动触发刷新接口。如果你们现场对配置变更实时性要求很高可以把“定时刷新”改成“消息通知刷新”例如 MQ 或配置中心回调。但无论采用哪种方式都建议保留“兜底查库”能力避免缓存异常导致整条链路不可用。伪代码DeviceTcpConfig getDeviceByStation(String stationCode) { DeviceTcpConfig cfg cache.get(stationCode); if (cfg ! null) { return cfg; } cfg mapper.selectByStation(stationCode); if (cfg null) { throw new BizException(工位未绑定打标设备: stationCode); } cache.put(stationCode, cfg); return cfg; }五、文档结构说明本文结构即实际落地顺序工具类解决设备通讯问题业务服务解决追溯流程问题动态配置解决运维和扩展问题。六、常见问题简版初始化模板失败模板名不对或设备里没这个模板。通信超时IP/端口不通、设备未监听、超时过小。明码错位BTZ11/22/33 分段规则与模板文本对象顺序不一致。OP10/OP50 串机工位绑定设备配置错误。补充一个实践经验在首次联调时建议先只测initialize和sendData确认模板和文本对象正确后再执行markStart。这样可以减少误打标对现场物料造成的干扰。七、总结在产线追溯场景里打标不是“能发指令就行”而是“设备通讯 业务规则 可运维配置”三件事同时成立。把 TCP 客户端先抽出来再把业务落地最后做动态配置这套架构就能长期跑得稳、改得动、排得了障。从工程角度看这套设计有三个长期收益稳定性收益协议层统一封装减少重复实现造成的偶发故障维护性收益业务规则和设备参数分离修改影响范围可控扩展性收益未来新增设备、工位、模板映射时无需重构主流程。如果你的目标是“先上线再可持续迭代”这种分层方式是非常稳妥的落地路径。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2517264.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!