地磅称重系统(DiBangWeightSys)是我团队在过去三年中为多家工业企业开发的一套专业称重管理解决方案。这个系统最初源于某大型建材企业的实际需求,他们每天需要处理200+车次的原材料入库和成品出库称重,原有的手工记录方式效率低下且容易出错。经过多次现场调研和需求分析,我们决定基于C#和.NET Framework 4.0开发这套系统,目前已在5家不同行业的企业稳定运行超过2年。
关键点:系统开发必须考虑工业现场的特殊性,比如网络不稳定、操作人员计算机水平有限等实际问题
系统最核心的价值在于实现了称重业务的全流程数字化管理。从车辆第一次上磅开始,到最终生成财务凭证,所有数据自动流转,避免了人工抄录可能导致的错误。特别是在与用友U8系统的对接上,我们花了大量时间研究U8的数据库结构,确保称重数据能准确生成对应的出入库单据。
系统采用经典的分层架构设计,这是经过多个版本迭代后确定的最优方案:
code复制UI层(用户界面)
↑
BLL层(业务逻辑) ← 常被合并到UI层中
↑
DAL层(数据访问)
↑
DB层(数据库)
在实际开发中,我们发现对于这类工业软件,业务逻辑层(BLL)往往比较简单,因此最终采用了简化版的分层结构:
系统使用了两类数据库:
经验:工业环境中SQL Server最好使用SQL Server身份验证而非Windows身份验证,因为现场域环境往往比较复杂
核心表结构设计考虑:
sql复制CREATE TABLE WeightNotes (
Id INT PRIMARY KEY IDENTITY,
CarNo NVARCHAR(20) NOT NULL, -- 车牌号
FirstWeight DECIMAL(18,2), -- 毛重
SecondWeight DECIMAL(18,2), -- 皮重
NetWeight AS (FirstWeight-SecondWeight), -- 计算列
WeightTime DATETIME NOT NULL, -- 称重时间
Operator NVARCHAR(50), -- 操作员
Status TINYINT DEFAULT 1, -- 1=一次称重 2=完成
U8BillNo NVARCHAR(50) -- U8单据编号
);
这是最常用的业务场景,代码实现的核心逻辑:
csharp复制// 一次称重(毛重)
public bool CreateFirstWeight(string carNo, decimal weight)
{
// 验证车辆是否存在
var car = carService.GetCar(carNo);
if(car == null) throw new Exception("车辆未登记");
// 创建称重记录
var note = new WeightNote {
CarNo = carNo,
FirstWeight = weight,
WeightTime = DateTime.Now,
Status = 1 // 一次称重
};
return weightNoteService.Add(note);
}
// 二次称重(皮重)
public bool CompleteWeight(int noteId, decimal secondWeight)
{
var note = weightNoteService.Get(noteId);
if(note.Status != 1) throw new Exception("状态不正确");
note.SecondWeight = secondWeight;
note.Status = 2;
// 计算净重(自动由计算列完成)
// 生成U8单据
if(Setting.AutoCreateU8Bill)
{
CreateU8PurchaseBill(note);
}
return weightNoteService.Update(note);
}
与采购入库的主要区别是称重顺序相反:
注意:很多新手会混淆这两个流程的顺序,我们在UI上做了明显区分
这是系统中最复杂的部分之一,需要深入了解U8的数据库结构:
csharp复制// 生成采购入库单
private void CreateU8PurchaseBill(WeightNote note)
{
using(var conn = new SqlConnection(U8ConnString))
{
// 获取U8的最大ID值
var maxId = GetMaxId(conn, "UA_Identity");
// 插入主表记录
ExecuteSql(conn, @"
INSERT INTO rdrecord01(cVouchID, dDate, cWhCode, ...)
VALUES(@billNo, @date, @warehouse, ...)",
new {
billNo = "CG" + DateTime.Now.ToString("yyyyMMddHHmmss"),
date = DateTime.Today,
warehouse = GetDefaultWarehouse()
});
// 插入子表记录
ExecuteSql(conn, @"
INSERT INTO rdrecords01(ID, cVouchID, cInvCode, iQuantity, ...)
VALUES(@id, @billNo, @invCode, @quantity, ...)",
new {
id = maxId + 1,
billNo = "CG" + DateTime.Now.ToString("yyyyMMddHHmmss"),
invCode = note.InventoryCode,
quantity = note.NetWeight
});
// 更新UA_Identity
ExecuteSql(conn, "UPDATE UA_Identity SET iFatherId=@id, iChildId=@id WHERE cAcc_Id='U8'",
new { id = maxId + 1 });
}
}
踩坑记录:早期版本没有处理UA_Identity表,导致U8无法识别我们生成的单据
工业现场的地磅设备多种多样,我们总结了以下几种接口方式:
串口直连:最可靠的方式,通过RS232/485直接读取称重仪表数据
csharp复制SerialPort port = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);
port.DataReceived += (s,e) => {
string data = port.ReadLine();
decimal weight = ParseWeight(data);
Invoke((MethodInvoker)delegate {
txtWeight.Text = weight.ToString();
});
};
网络接口:部分新型地磅支持TCP/IP通讯
模拟键盘:通过虚拟串口模拟键盘输入(兼容性最好但最不稳定)
重要:必须添加手动输入功能,因为设备故障在现场很常见
在高峰期(如月底集中发货),系统可能面临多个磅房同时称重的情况。我们的解决方案:
采用SQL Server的乐观并发控制
csharp复制UPDATE WeightNotes SET FirstWeight=@weight
WHERE Id=@id AND FirstWeight=@originalWeight
对关键表添加行版本控制
sql复制ALTER TABLE WeightNotes ADD RowVersion TIMESTAMP
实现简单的本地缓存,减少数据库访问
csharp复制private static Dictionary<string, CarInfo> carCache = new Dictionary<string, CarInfo>();
public CarInfo GetCar(string carNo)
{
if(carCache.TryGetValue(carNo, out var car))
return car;
car = db.Query<CarInfo>("SELECT * FROM Cars WHERE CarNo=@carNo", new {carNo});
carCache[carNo] = car;
return car;
}
根据我们的部署经验,推荐以下配置:
| 组件 | 最低要求 | 推荐配置 |
|---|---|---|
| 操作系统 | Windows 7 | Windows 10 LTSC |
| 内存 | 2GB | 8GB |
| 数据库 | SQL Server 2008 Express | SQL Server 2016 Standard |
| 网络 | 100Mbps局域网 | 独立数据库服务器 |
U8单据生成失败
称重数据不更新
系统运行缓慢
系统设计时预留了多个扩展点:
csharp复制public interface IErpIntegration
{
string CreateBill(WeightNote note);
bool SyncInventory();
}
实际项目中,我们曾为某客户扩展了以下功能:
经过多个版本的迭代,我们总结了以下几点关键经验:
工业软件的UI设计:一定要简单明了,关键信息突出显示。我们采用了以下原则:
数据安全:除了常规的SQL参数化,我们还实现了:
异常处理:工业现场环境复杂,必须考虑各种异常情况:
csharp复制try
{
// 称重操作
}
catch(SerialPortException ex)
{
Logger.Error("串口通信失败", ex);
ShowError("地磅连接失败,请检查设备连接!");
EnableManualInput(); // 启用手动输入
}
这套系统从最初版本到现在已经迭代了12次,最大的体会是:工业软件不仅要考虑功能实现,更要考虑实际使用环境和操作人员的习惯。比如我们添加的"一键补打"功能(F3快捷键),就是根据现场操作人员的反馈加入的,现在已经成为最常用的功能之一。