ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

C#条码打印软件实战:从Code128到ZPL的核心技术拆解

C#条码打印软件实战:从Code128到ZPL的核心技术拆解 1. 为什么我会用C#来做条码打印软件选型逻辑和项目背景事情是这样的。前阵子公司产线那边提了个需求说原来的条码打印工具是在一台老掉牙的XP机器上跑的用的是别人十几年前写的VB6小程序每次换标签样式的流程特别痛苦得先在另一个软件里改模板再导出格式最后通过共享文件夹的方式拷到那台机器上然后重新打开程序加载。而且那个程序只支持最老式的串口条码机现在换了几台USB接口的打印机之后完全没法用。我当时分析了一下这个需求的核心产线工人需要快速、准确地打印带有品名、批次、序列号的标签标签上既有一维条码也有二维码同时还需要一定的灵活性来适配不同的纸张尺寸和打印设备。说白了这就是一个典型的桌面工具软件。在选择技术栈的时候我心里其实列过几个候选Python加Tkinter、Electron、C# WinForms。Python的方案我熟但到了部署环节容易出幺蛾子产线那种没有网的环境下装Python环境本身就是个麻烦事。Electron打包体积大不说有些老产线电脑跑起来还卡。最终我选了C# WinForms一个重要理由是它在Windows环境下打包部署极其省心——只需要装一个.NET Framework那是Windows系统自带的东西另一个理由是我可以直接调用Windows的打印API和驱动体系做打印控制的时候省掉很多中间层。这篇文章不会给你写一个完整的商业级源码但我可以把整个软件的骨架、核心类的设计思路、条码算法的基础原理、打印控制的关键代码以及我在实际开发中踩过的坑全部拆开讲清楚。适合的人群是已经入门C#、想做点实用工具练手的人以及工作中需要做产线工具、打印相关项目的开发者。你可以直接参考代码结构然后根据你的业务场景去改。现在从条码本身的底层知识说起因为它决定了你的代码怎么写。2. 条码格式的核心知识Code128和QR码的底层细节很多没做过条码的人会以为条码就是个图片生成出来往界面上放就行。真实情况是条码生成这件事的复杂度完全取决于你选的编码格式而你选的编码格式又取决于你要存进去多少数据、标签上能腾出多大空间、扫描枪是什么级别的设备。2.1 Code128的字符集切换和校验位算法我这次用的是一维条码Code128原因很简单它密度高、支持全ASCII字符、在行业里通用性极强。但它有一个必须搞明白的东西字符集切换。Code128内部有三套字符集分别叫Code A、Code B、Code C。Code A偏向大写字母和控制字符Code B偏大写加小写字母Code C只能编码数字而且是两位一组编码。在实际应用中一串ABC123456如果全程用Code B写长度是9个字符但如果能在数字部分切到Code C数字部分就只需要3个字符就能表示6位数字整个条码长度会明显变短。这就是为什么高级一点的条码生成逻辑里都有自动切换字符集这种优化。我实际项目里直接用了开源的BarcodeLib库来折腾但如果你想自己实现必须得处理切换字符。举个例子条码内容从AB12CD开始编码顺序可能是Start B - A - B - Switch C - 12 - Switch B - C - D - Check Digit - Stop。其中Switch字符本身也占一个码位所以短数据里频繁切换反而没好处这是个需要权衡的地方。校验位的算法是Code128的必考点每个字符有一个值0到106先给开始符赋权重1后面的每个字符的值乘以它在条码里的位置序号从1开始数把Start符和所有数据字符的加权值求和取对103的余数结果就是校验字符。这个校验码在生成条码图像时必须算进去不然扫描枪会判定条码无效。2.2 QR码的容量选择和编码模式二维码部分我选了QR Code。QR码的核心知识点是编码模式数字模式、字母数字模式、字节模式、汉字模式。很多人上来就用字节模式硬编码中文结果图片一旦内容稍长就糊成一团扫描不出来。实际情况是如果你的数据里既有数字又有中文应该优先用字节模式把整段编码成一个GBK或者UTF-8的字节序列再交给容错级别去兜底。对于产线标签这种使用场景数据量通常不会超过50个字符我一般选纠错级别M15%容错点阵密度适中打印效果也稳定。QR码生成部分我建议直接用QRCoder库没必要自己写图像编码。一个容易忽略的问题是QR码边界的静区宽度最少是4个模块如果你直接把QR码塞进标签最边缘扫描成功率会直线下降。所以模板设计里我给二维码留了至少2毫米的边距。2.3 为什么不是EAN-13或者Code39很多教程一上来就教EAN-13但我这次没有选。EAN-13是零售商品条码需要申请厂商代码产线内部用的条码完全没必要用这个标准。Code39倒是能做字母数字但密度也太低了同样长度的内容Code39的物理宽度比Code128大得多小标签根本放不下。选择条码格式的标准就一句话你的条码最终是给谁扫的——零售结算用EAN物流仓储用Code128或DataMatrix产线内部追溯用Code128完全够用。3. 项目架构设计从界面到打印的清晰分层代码结构这东西项目小的时候看不出差别等项目变大哪怕一点点如果从一开始没分好层后面加需求就是个无底洞。我这次一开始就按三层来设计UI层、业务逻辑层、硬件抽象层。3.1 每层各干什么UI层很简单就是一个WinForms主窗口加几个模板配置页。主窗口长这样左侧是标签参数区中间是实时预览区域右侧是打印队列和日志区。界面上还有一批预设的标签模板按钮点一下就能切换模板。实际产线工人真正关心的就三件事一扫枪就能干活、按一下就能打、出了问题能知道卡在哪里。业务逻辑层处理的是标签数据的组装、校验和模板匹配。比如说从扫描枪扫进来一个产品编号我得先解析出这个编号的含义前4位是型号中间4位是批次后6位是序列号。这个解析规则写在独立的Parser类里后续如果换编号规则只需要改这个类就行。硬件抽象层是我这次特别强调的。原因很简单产线上不可能只有一个品牌的打印机。虽然暂时用Zebra但保不齐哪天京东采购单上出现个别的牌子。所以我定义了一个IPrintProvider接口里面有PrintLabel方法、Connect方法、Disconnect方法然后用不同的实现类去适配不同打印机品牌。Zebra走ZPL指令普通Windows打印机走GDI绘图打印冷链专用的那种热转印打印机走串口指令。这个抽象带来的收益是立竿见影的第二台打印机接入的时候我只写了一个新类UI和业务层一行代码没动。3.2 标签模板怎么设计才灵活标签模板如果写死在代码里客户产线主管哪天说一句我想把日期挪到右边你就得改代码重新编译部署。所以我用了模板化的思路把标签定义成一张配置表模板内容用XML存储。以我的经验标签模板里的字段类型就那么几类静态文本、变量文本、一维条码、二维码、图片。每个字段有一组属性位置X、位置Y、字体大小、旋转角度、数据来源。数据来源又分成三类直接固定值、来自UI输入、来自数据库查询。把这些信息组织成XML之后软件启动时去解析模板动态生成对应的绘制指令。每张标签纸的尺寸也是模板的一部分因为换纸尺寸之后所有字段的坐标都要跟着变。我用毫米来做所有尺寸的单位然后在渲染时才换算成像素这样无论是在屏幕上预览还是输出到打印机都不会出现单位混乱。4. 核心源代码拆解条码生成、图像渲染和打印输出代码是最多人爱看的部分我直接拆关键点讲。4.1 标签模型和条码对象的设计我首先定义了一个抽象基类LabelElement所有标签元素都继承它public abstract class LabelElement { public float X { get; set; } // 坐标单位毫米 public float Y { get; set; } public float Rotation { get; set; } public abstract void Render(Graphics g, float dpi, object data); }条码元素长这样public class BarcodeElement : LabelElement { public BarcodeFormat Format { get; set; } // Code128 / QRCode public string DataSource { get; set; } // 字段名运行时替换 public int BarHeight { get; set; } // 条码高度 public bool ShowText { get; set; } // 是否在条码下方显示文本 public override void Render(Graphics g, float dpi, object data) { string value ResolveValue(DataSource, data); var image BarcodeFactory.CreateBarcode(Format, value); // 转成像素尺寸绘制到Graphics } }这里有个小设计点X和Y都用毫米存储渲染的时候乘DPI系数转像素。很多新手直接写死像素值换一个分辨率的打印机或者换一种标签纸位置全跑偏最后只能在代码里硬调坐标越调越乱。以毫米为内部单位是正确做法。4.2 条码工厂和缓存机制条码生成虽然快但批量打印时如果每次重新生成一遍图像性能瓶颈就会被放大。我写了个BarcodeFactory生成过的条码用内容做Key缓存起来public static class BarcodeFactory { private static Dictionarystring, Image _cache new Dictionarystring, Image(); public static Image CreateBarcode(BarcodeFormat format, string content) { string key format _ content; if (_cache.ContainsKey(key)) return _cache[key]; Image img null; if (format BarcodeFormat.Code128) img Code128Generator.Render(content); else if (format BarcodeFormat.QRCode) img QRCodeGenerator.Render(content); _cache[key] img; return img; } }缓存机制在实际产线场景里特别重要。比如一卷标签打印2000张其中型号相同的数据可能有数百张重复的条码缓存直接把重复计算省掉。但要注意一个问题条码图片占内存不小长时间跑缓存会增长失控。我的做法是限制缓存上限超过500个条目就清掉最旧的一批。实现很简单用Queue记录插入顺序清理时按顺序移除。4.3 用Windows GDI渲染标签渲染那块我直接用System.Drawing来实现。对于热敏和热转印打印机这种方式有一个好处直接用Windows驱动打印几乎兼容所有常见打印机开发部署都简单。关键代码逻辑很直接using (var bmp new Bitmap(pixelWidth, pixelHeight)) using (var g Graphics.FromImage(bmp)) { g.Clear(Color.White); g.SmoothingMode System.Drawing.Drawing2D.SmoothingMode.AntiAlias; g.TextRenderingHint System.Drawing.Text.TextRenderingHint.AntiAliasGridFit; // 设置映射模式毫米转像素 g.PageUnit GraphicsUnit.Millimeter; foreach (var element in template.Elements) { element.Render(g, dpi, data); } // 打印 var doc new PrintDocument(); doc.PrinterSettings.PrinterName printerName; doc.DefaultPageSettings.PaperSize new PaperSize(Custom, pixelWidth, pixelHeight); doc.PrintPage (s, e) e.Graphics.DrawImage(bmp, 0, 0); doc.Print(); }这里有一个非常容易踩坑的点PageUnit设置为Millimeter之后绘制的坐标单位就自动变成毫米了但你设置的线条宽度和字体大小单位也会跟着变。如果你把Font大小写成16实际渲染出来就会大到离谱因为字体的Unit默认是Point和Graphics.PageUnit不一致时需要进行换算。我建议要么全部用像素只在边界处做一次毫米转像素的换算要么花点时间理解Graphics的Unit机制再动手。我在开发初期就是在这里栽了跟头预览和打印出来总是对不上。4.4 ZPL直连打印模式GDI打印虽然兼容性好但在高热敏打印效率和精度要求极高的场景下我通常直接用ZPL指令。ZPL是Zebra打印机的指令集本质上是把文本和条码的描述语发给打印机打印机自己负责栅格化。这种方式绕过Windows打印驱动速度快而且打印机的原生分辨率能完整发挥出来。生成ZPL的核心逻辑是这样的public string BuildZpl(TemplateConfig config, LabelData data) { var sb new StringBuilder(); sb.AppendLine(^XA); // 开始 sb.AppendLine($^LH0,0); // 原点 sb.AppendLine($^PW{config.LabelWidthInDots}); // 标签宽度单位dot // 文本字段 sb.AppendLine($^FO{xDots},{yDots}^A0N,{heightDots},{widthDots}^FD{text}^FS); // 一维条码 sb.AppendLine($^FO{xDots},{yDots}^BY2,3,{barHeightDots}^BCN,{barHeightDots}^FD{content}^FS); // 二维码 sb.AppendLine($^FO{xDots},{yDots}^BQN,2,8^FDMM,{content}^FS); sb.AppendLine(^XZ); // 结束 return sb.ToString(); }ZPL换算有个必须记住的事ZPL里所有坐标和尺寸都是dot一个dot等于1/打印机DPI英寸。你的标签宽度是60毫米203DPI的打印机就有60/25.4*203约等于479个dot。搞错了坐标系打印出来的内容位置全跑偏。建议写一个小工具方法做单位换算统一入口。5. 实测中踩过的坑DPI、中文编码、连接方式代码能跑起来和代码能稳定在产线跑一年不报错是两回事。这一章把我实测踩过的坑全列出来你能少走很多弯路。5.1 屏幕上预览和打印出来尺寸不一致这个问题的本质是分辨率不一致。屏幕一般是96DPI打印机是203或者300DPI。如果你在屏幕上用一个16像素高的条码看着正合适拿到打印机上它还是16像素高但物理尺寸就小了将近一半。我的做法是预览时也按打印机的真实DPI去模拟渲染这样屏幕上的视觉尺寸约等于实物尺寸。刚开始这样做会感觉标签在屏幕上显得很大但这才是真实的打印效果。5.2 用ZPL打印中文时的字体问题ZPL解释器本身不认识中文如果你直接往^FD里塞中文字符打印出来的要么是空白要么是一堆乱码。Zebra打印机的解决方案有两类一是打印机内部有中文字库用^A指定字体时选择支持中文的字体索引二是先把中文渲染成图片再把图片以GRF格式发给打印机。第二种方案最稳妥。做法是在PC端用GDI把中文字段绘制到Bitmap上然后转成单色位图再转成ZPL的GRF十六进制数据。示例代码逻辑public string ConvertImageToGrf(Bitmap bmp) { var sb new StringBuilder(); sb.Append(^GFA,); int totalBytes (bmp.Width 7) / 8 * bmp.Height; sb.Append(totalBytes).Append(,); sb.Append(totalBytes).Append(,); sb.Append((bmp.Width 7) / 8).AppendLine(,); // 逐行转换像素为十六进制数据 for (int y 0; y bmp.Height; y) { byte[] rowData new byte[(bmp.Width 7) / 8]; for (int x 0; x bmp.Width; x) { if (bmp.GetPixel(x, y).GetBrightness() 0.5f) rowData[x / 8] | (byte)(0x80 (x % 8)); } sb.AppendLine(BitConverter.ToString(rowData).Replace(-, )); } return sb.ToString(); }这里要注意GRF数据量很大一张稍微复杂点的图就是几KB的字符串。如果你用串口通讯打印波特率不够高的情况下传输等半天产线上的工人会以为机器死机了。所以直连打印ZPL我只推荐在USB或者网口连接时用。5.3 打印队列的丢失和重复问题产线场景里工人有时候会连续不停地点打印按钮。如果你直接用PrintDocument.Print()同一时间多次调用会产生队列竞争轻则丢任务重则UI卡死。我的做法是引入一个简单的打印任务队列private ConcurrentQueuePrintTask _printQueue new ConcurrentQueuePrintTask(); private bool _isPrinting false; private void EnqueuePrint(LabelData data) { _printQueue.Enqueue(new PrintTask(data)); if (!_isPrinting) StartProcessQueue(); } private async void StartProcessQueue() { _isPrinting true; while (_printQueue.TryDequeue(out var task)) { await Task.Run(() ExecutePrint(task)); } _isPrinting false; }这个队列的意义不只是防止并发冲突还在于你可以扩展重试机制。比如打印机缺纸或者卡纸报错时任务不应该直接丢失而应该标记为待重试等状态恢复之后自动补打。我后来还加了个功能每次打印任务完成后记录日志内容包括打印内容、打印时间、打印机状态这样出问题能查追溯。5.4 USB和串口打印的不同处理逻辑产线上的老打印机好多是串口接口需要打开COM端口按特定的波特率发指令。这个和USB打印完全是两套逻辑。使用串口打印时关键代码是这样using (var sp new SerialPort(COM3, 9600, Parity.None, 8, StopBits.One)) { sp.Handshake Handshake.XOnXOff; sp.Open(); byte[] data Encoding.ASCII.GetBytes(zplCommand); sp.Write(data, 0, data.Length); Thread.Sleep(500); // 等打印机处理完防止立即关闭端口导致数据丢失 }串口打印有个神奇的老毛病发送完指令立刻关闭串口有时最后几个字节还没从缓冲区发出去结果标签最后缺一块或者二维码的结束符号丢了完全扫不出来。所以必须加一个延时可靠起见最好检查一下打印机返回的应答状态。串口的握手协议也建议用XOnXOff流控纯软件流控在老设备上比硬件流控兼容性好。6. 条码内容的数据校验和防呆逻辑产线标签最怕的不是打不出来而是打出错内容。比如批次号少一位、序列号重复、日期格式不对。这种错误有时候肉眼根本发现不了直到下游质检扫出来才发现一整卷标签报废。所以数据校验必须写在打印之前。6.1 数据解析和规则校验每条产线数据有不同的格式规则我用正则和自定义校验规则来双重验证。举个例子产品编号规则是4位型号2位年份2位周数4位流水号。那么校验逻辑就应该是public class PartNumberValidator { private static readonly Regex Pattern new Regex(^[A-Z]{4}-\d{2}-\d{2}-\d{4}$); public static bool Validate(string input, out string errorMsg) { if (!Pattern.IsMatch(input)) { errorMsg 编号格式不正确应为型号(4位字母)-年份(2位)-周数(2位)-流水号(4位); return false; } // 验证周数范围 int week int.Parse(input.Substring(7, 2)); if (week 1 || week 53) { errorMsg 周数超出合法范围(01-53); return false; } return true; } }具体业务规则肯定跟你的场景相关但核心思想一样打印前在内存里把所有数据验证一遍任何一个字段不合法就直接拦下来并提示工人而不是等打成实物再去检验。6.2 动态日期时间的时区处理标签上经常要打印生产日期、有效日期。这个看起来简单其实也有坑。如果软件跑在Windows电脑上而电脑时间被手动修改过产线电脑经常出这种事标签上的日期就是错的。我的做法是联网时用网络时间源校准如果不联网就用Windows时间但显示里加一个当前系统时间已被修改的提示。日期格式上产线内部建议用YYYYMMDD这种纯数字格式不要用斜杠或者横杠因为有的扫描枪和下游系统会把分隔符当成结束符。6.3 扫描枪输入模拟键盘的防重复问题产线的扫描枪大多数模拟键盘输入也就是扫一下等于在输入框里敲了一串字符加回车。这个方案的天然问题是焦点如果在打印按钮上扫枪的回车等于又触发了一次打印。我在UI层做了一个全局的键事件拦截扫描枪输入期间禁用打印按钮等数据校验完毕再恢复。另外扫描枪输入是逐字到达的如果处理过程中又来了一次扫描数据会被插到一半的地方非常容易出错。所以输入框要设置一个短暂的锁定窗口收到第一个字符后300毫秒内不接收新的扫描输入。7. 项目扩展方向数据库对接、批量打印、远程维护这个项目做出来之后我花了将近一半的时间在周边功能上。原因是产线的实际工艺流程永远比你想象的复杂标签软件不能只干输入内容再打印这一件事它往往要成为整个产线追溯系统的一个节点。7.1 从Excel导入到对接生产管理系统的演进最开始的需求很简单一批货要打一批序列号。如果手工一个一个输入几十上百个号能把人输到崩溃。所以我先做了一个从Excel导入的功能读取一个特定格式的Excel每一行是一条标签数据导入后显示在表格里勾选哪几条就打哪几条。这个功能上线当天产线效率就提高了至少一倍。再往后走需求变成了序列号应该由系统自动生成不能手动输入这就牵扯到了数据库。我用SQLite做本地库存产品的当前最大流水号每次生成新序列号时锁定表、查询、加一、更新、释放。这一步避免了多个工位同时生成序列号导致重复的问题。public string GenerateSerialNumber(string modelCode) { lock (_lockObj) { int currentMax GetCurrentMaxSerial(modelCode); int nextSerial currentMax 1; UpdateMaxSerial(modelCode, nextSerial); return ${modelCode}-{DateTime.Now:yy}-{GetWeekOfYear(DateTime.Now):00}-{nextSerial:0000}; } }7.2 批量打印的进度恢复和实时反馈批量打印几百张标签的时候中途打印机突然报错停机了。如果没有恢复机制前面打好的几十张还好说后面没打完的那些就麻烦了直接重新打印会把已经打过的部分也重新打一遍浪费标签不说扫到重复条码会引发后续追溯到混乱。我的方案是待打印任务持久化到一个本地队列文件里每成功打印一张就写一条完成记录。程序重启后检查完成记录从下一个未完成的序号继续打。同时界面上显示当前进度第几条、总数多少、剩余多少。实时的进度反馈也不能缺。打印状态通过事件通知到UI线程界面上的进度条和时间估算都会更新。这里还涉及一个技巧UI线程不能直接访问打印机线程的数据需要用BeginInvoke或者使用TaskScheduler.FromCurrentSynchronizationContext()。新手经常在这里偶发崩溃就是因为跨线程访问没有正确切换。7.3 软件自动升级和配置远程管理产线软件有个很尴尬的情况软件部署下去了后面要改配置或者升级功能还得跑一趟车间有时候车间还不让进。所以我做了个极简的远程配置方案软件启动时向内网的配置服务器请求一个JSON文件里面放着当前可用的模板列表、打印机默认配置、标签规范参数。如果JSON里带的版本号和本地不一致就自动下载新版本下次启动时自动替换。这个方案不需要外网完全跑在企业内网安全又可控。7.4 多语言和打印主题导出后面我意识到另一个实际需求有的产线工人是轮班制不同班次的人可能习惯看不同的界面语言。我把界面文字资源全部抽到了资源文件同时把打印模板和界面主题拆开——模板只管标签的布局内容界面主题只管软件外观。这样换班的时候只需要切换用户配置工人不需要重新适应版本差异。把界面和模板彻底解耦是后期维护省心的关键。8. 从这几个开源库的对比看你该怎么选条码生成这件事其实根本不需要自己从零实现条码算法用库就够了。但选库的时候有几个坑要注意我之前分别用过几款主流的.NET条码库说说实际差异。8.1 BarcodeLib和ZXing.Net的对比BarcodeLib在Code128、Code39这类一维条码上表现不错API简洁生成速度快出来的条码图像质量稳定。它的弱项是二维码支持得一般所以二维码我不用它。ZXing.Net是Java世界ZXing的移植版在二维码上非常强尤其是在处理乱码和编码模式自动选择方面表现更好。但ZXing.Net的一维条码生成质量不太稳定尤其是Code128在某些内容组合下会出现冗余的字符集切换。所以我的最终选型策略是一维条码用BarcodeLib二维码用QRCoder各干各擅长的事。8.2 QRCoder使用时的编码坑QRCoder的API看起来简单但如果中文内容不指定编码可能出现二维码表面能解码但解出来是乱码的情况。正确做法是明确指定编码格式为UTF-8或者GBK并且生成时选用ECCLevel.M。var generator new QRCodeGenerator(); var data generator.CreateQrCode(content, QRCodeGenerator.ECCLevel.M); var qrCode new QRCode(data); var image qrCode.GetGraphic(4, Color.Black, Color.White, true);注意第四参数drawQuietZones一定要传true否则二维码没有静区打印出来某些扫描枪死活不识别。8.3 自己写条码渲染和用库里渲染的取舍我见过有团队非要自己写Code128渲染器理由是第三方库打包太大。对于产线工具这种项目我极力劝退自己写渲染器。条码渲染涉及的细则很多条宽比例、空白宽度、放大倍数、校验位、字符集切换策略任何一个细节不对都可能让扫描枪读取失败。而这些问题在成熟库里都已经被大量真实用户验证过了。你省下的那几百KB体积远远比不上排查条码无法识别问题时耗费的时间成本。如果真的想学习自己实现一遍了解原理没问题但生产环境用成熟库。9. 整体部署和产线落地的实际经验最后聊一点代码之外的东西。软件好不好用除了功能还有部署和维护。9.1 打包发布和权限控制我建议发布的时候用.NET Framework 4.6以上的版本做Release配置发布。产线电脑有的配置很低如果软件里用了太新的语言特性比如某些C# 11的语法在旧框架的机器上会有兼容问题。我开发的时候刻意把代码保持在C# 7.3语法级别避开了记录类型、文件作用域命名空间这类新特性确保在任何一台Windows 7以上的机器都能直接跑起来。安装目录建议放在固定位置比如C:\LabelTool然后给产线工人一个快捷方式。软件运行需要的写权限最好控制在生产数据库中而不是让软件往安装目录里写文件否则在开了安全软件的机器上会出现各种莫名其妙的问题。9.2 日志系统是必需品这个我一定要劝你从一开始就加日志。我在项目早期没有日志产线一报问题就只能亲自去车间复现相当痛苦。后来加了一个简单的文本日志软件启动、模板加载、扫码输入、数据校验、打印请求、打印机应答、打印完成、异常信息都记录到日志文件里。日志按天滚动只保留最近30天。出了故障产线主管把日志发过来远程一看就定位到具体是数据格式错误、打印机通信断了还是用户操作不当。9.3 快速换模板的设计细节换标签纸的时候工人需要切换到对应的模板。直接在界面上的模板下拉框里选就行了。但问题在于不同标签纸对应不同的打印机设置——打印浓度、速度、撕纸位置可能不一样。我的设计是把打印机设置和模板绑定在一起切换模板时自动下发打印机配置指令避免工人手工去调打印机面板。这一步很细节但产线效率的提升是实打实的。9.4 备机策略一台打印机挂了怎么办产线打印机总有宕机的一天。一个简单的保障策略是软件里设置主打印机和备用打印机主打印机连不上时提示工人是否切换到备用打印机。因为我的硬件抽象层已经做了IPrintProvider所以切换打印机只需要修改配置界面上的操作则是点一个按钮确认。这个功能在旺季赶产量的时候能救急值得提前做。做完整套软件之后我最大的感受是条码打印软件本身技术难度不大难的是你要围绕产线场景考虑各种边界情况——数据有效性、设备稳定性、操作容错性。希望这篇拆解能帮你省掉一些摸索时间。
返回列表