Files
ww-jst/wmsjst/README.md
T
2026-09-02 16:31:50 +08:00

16 KiB
Raw Blame History

PaperMES — 原纸物流 MES 系统(武威金士泰 AGV)

造纸厂原纸(纸卷/纸板)智能仓储物流系统。打通「生产线 → 立体库 → 跨楼层搬运 → 销售出库」全流程, 由 AGV 自动导引车 + 电梯 PLC + 条码扫描 协同完成原纸的自动存取与调度。

解决方案文件:WMS.sln(Visual Studio 2022,.NET 8)


1. 技术栈

类别 技术
运行时 .NET 8(主工程)、.NET 6-windows(WPF 客户端 Modules)、.NET 6(MqttClient)
Web ASP.NET Core,以 Windows 服务 方式部署(UseWindowsService),监听 http://*:8078
ORM SqlSugar(SQL Server,库 wms_jst2.0)
IoC Autofac(按程序集批量注册 *Repository/*Service/*Strategy/*Handler)
任务调度 Quartz
通信 DotNetty(TCP/UDP 条码)、NModbus(电梯 PLC)、RestSharp(HTTP 调 ERP/RCS)、SignalR.Client、MQTT
其他 AutoMapper、FluentValidation、NLog、Polly、SSE(服务端推送)、NPOI(Excel)、FastReport

2. 解决方案结构

逻辑分组

分组 包含项目 职责
DataBase Domain、App 领域模型与数据访问
Utils Infrastructure、Business、Common、Job 基础设施、业务、公共、后台作业
CommunicationMiddleware PLC、RPC、Netty 通信中间件
WebHost WebHost、Authorization Web 服务与鉴权
UnitTest RPCTest 单元测试

12 个工程项目

项目 说明
JSMachine.WMS.Domain 领域层:实体(AGVTask/ERPTask/RcsTask/StorageRack/ElevatorPlcQueue…)+ 仓储接口/实现(SqlSugar)
JSMachine.WMS.App 应用层:Dto + IService/ServiceImpl(每个实体一个服务)
JSMachine.WMS.Business 业务层:入库/出库/RCS 回调/SSE/公共(核心业务 + 大量设计模式)
JSMachine.WMS.Infrastructure 基础设施:AppSettings、SqlSugar、Lambda 表达式树、日志、序列化、加密等
JSMachine.WMS.Common 公共:缓存、枚举、Dto、Helper、Htmls
JSMachine.WMS.Job 后台作业(Quartz):AGV/电梯/ERP/中转区/数据清理等定时任务
JSMachine.WMS.PLC 电梯 PLC(Modbus 读写,NModbus)
JSMachine.WMS.RPC 对外 HTTP 调用:ERP(晨龙/旺泰/新荣/IBS)、PMS(ACS/DCS)、RCS、IBS
JSMachine.WMS.Netty 基于 DotNetty:条码扫描客户端/服务端/UDP(心跳、设备动态)
JSMachine.WMS.WebHost Web 宿主(入口 Program.cs → WebHostEngine1.Start()),Controllers/Filters/Validators
JSMachine.WMS.Authorization 身份认证/授权(IdentityServer4,当前未启用)
JSMachine.WMS.RPCTest RPC 调用测试(MSTest)

目录下另有 JSMachine.WMS.Modules(WPF 客户端,Prism + HandyControl)、JSMachine.WMS.MqttClient, 未纳入 WMS.sln,需手动加载。


3. 分层架构(DDD)

WebHost(表现层,端口 8078) + Authorization(认证)
        ↓
Application(应用层:Dto + IService/ServiceImpl)
        ↓
Business(业务层:入库/出库/RCS回调/SSE)
        ↓
Domain(领域层:Entity + IRepository/Repository)
Infrastructure(基础设施:SqlSugar/配置/日志/Lambda)
Common(公共:缓存/枚举/Helper)
        ↓
通信中间件:PLC / RPC / Netty / MqttClient
后台作业:Job(Quartz)

依赖方向自上而下;Domain 定义实体与仓储接口,Infrastructure 提供 SqlSugar 实现。


4. 核心业务流程

4.1 入库(PDA 扫码入库)Business/StockIn

StockInService.StockIn(PDAInParam) 主流程:

  1. 查物料信息是否存在、查库位是否存在
  2. 校验同「合格证号」是否已有未完成任务(防重扫)
  3. 按 放置策略 + 责任链 计算目标库位并锁定(先锁库再写任务,保证一致性)
  4. 写入 ERPTask,触发 AGV 搬运任务

4.2 出库(销售出库)Business/StockOut

StockOutService.Stockout 主流程:

  1. 校验销售订单号是否重复
  2. 计算可出库存:同批次库存 − 锁定/禁用 − 未完结任务 − 撤单数
  3. 生成 ERPTask 出库任务

4.3 AGV 状态回调 Business/RcsCallBack

RCS 上报 AGV 任务状态 → RcsCallBackService.DealwithExcutionStatus: 失败(推 PDA 报警)→ 取消 → 成功,按状态码分发到不同处理器(见第 6 节)。


5. 对接外部系统

系统 项目 说明
ERP(四选一) RPC/ErpRPC 企望(0) / IBS(1) / 晨龙(2) / 新荣(3),当前用 新荣;含生产入库、销售出库、生产退库、物料查询等接口
RCS 机器人调度 RPC/RcsRPC 创建任务 ics/taskOrder/addTask、取消任务 ics/out/task/cancelTask
PMS/DCS/ACS 备料 RPC/PmsRPC AcsPaperPreparService、DcsPaperPreparService
IBS 原料查询 RPC/IBSRPC 纸卷基础信息查询

6. 设计模式应用

6.1 责任链模式(入库库位计算)

责任流动方向:StockInByExist → StockInByNew → StockInByMixing

节点 职责
StockInByExist 优先放「已有同批次」的列;同批次放满一列再开新列(最多 2 列,超出走中转区)
StockInByNew 放「全新空白列」
StockInByMixing 与其他批次货物混放

6.2 策略模式(入库放置策略)

StrategyFactory.CreateStockInStrategy 根据 PlcaementMod 配置返回不同策略:

模式 配置值 策略 责任链
完全不混放 0 NoMixingStrategy Exist → New
混合模式(默认) 1 MixedModeStrategy Exist → New → Mixing
完全混放 2 CompleteMixingStrategy 直接找空列

一楼(FirstFloor)强制走「不混放」逻辑。

6.3 状态模式 + 策略模式(RCS 回调)

RcsCallbackHandlerFactory.CreateHandler(status, destWarehouse) 按 AGV 状态码 分发:

状态码 处理器 说明
21 PickupCompletedStateHandler 取货完成
23 按 Transport 二次分发 放货完成

状态 23 按目标区域二次分发:

目标区域(Transport) 处理器
北区/南区/出库中区/退库区 StockOutWharfHandler(出库月台)
库区(NormalArea) StockAreaHandler
中转区(TransitArea) TransitAreaHandler
电梯(Elevator) ElevatorDeliveryHandler

7. 电梯 PLC 通信(PLC)

ElevatorPlc : ModbusEngineBase 初始化时按 IP 匹配电梯编号,注册到 PLCManager 后连接(NModbus,端口 502/503)。

每台电梯 8 个通道(读/写寄存器位):

通道 网带号 读数据位 写数据位
入库放货通道2 1001 0 8
入库放货通道1 1011 2 10
入库取货通道2 2004 5 13
入库取货通道1 2014 7 15
出库放货通道2 2001 4 12
出库放货通道1 2011 6 14
出库取货通道2 1006 1 9
出库取货通道1 1016 3 11
  • 读:ElevatorStatusRead.ReadElevatorStatus(no),读 20 个 ushort(寄存器地址 1 起)
  • 写:ElevatorWrite.WriteElevator(status),逐通道写「放货/取货完成」信号

8. 后台作业(Quartz,Job)

AgvTaskJob、ElevatorJob、ElevatorOutJob(电梯出库)、ErpTaskJob、RcsCallbackJob、 StockInSecondFloorJob(二楼入库)、TransferAreaJob(中转区)、DataCleanJob(数据清理)、XinRongErpJob(新荣 ERP 定时回传)。

通过 JobEngine + JobListener 启动。


9. 配置说明(JSMachine.WMS.WebHost/appSettingsWebhost.json)

配置项 说明 当前值
urls 服务监听地址 http://*:8078
DataBaseCon SQL Server 连接串 wms_jst2.0(本地)
ErpConfig.ErpType ERP 类型 3(新荣)
ErpConfig.RetryNum 上传重试次数 5
RcsConfig RCS 创建/取消任务 URL 见上
ElevatorConfig 电梯列表 2 台(端口 502/503)
PlcaementMod 放置模式 1(混合模式)

⚠️ 配置文件包含明文数据库密码,生产部署前应改为环境变量/加密配置。


10. 部署与启动

  1. 还原数据库:执行 ../dbo.sql
  2. 配置 appSettingsWebhost.json(数据库连接、ERP 类型、电梯 IP/端口)
  3. 编译 WMS.sln(.NET 8 SDK)
  4. 启动:JSMachine.WMS.WebHost 为 Windows 服务(WebHostEngine1.Start() 调用 UseWindowsService), 也可直接运行调试;服务监听 8078 端口
  5. 前端:../wms_front(Web)、../wmsjstpda(PDA 手持终端)

11. 注意事项

  1. 鉴权未启用:Startup.cs 中 JWT/IdentityServer 认证逻辑整体被注释,UseAuthentication/UseAuthorization 空转。
  2. 测试覆盖薄:RPCTest 仅含 RPC 调用测试(MSTest)。
  3. 两个工程未入解决方案:Modules(WPF)、MqttClient 需手动加载。
  4. 并发控制:入库责任链用 SemaphoreSlim(1,1) 串行化,保证库位锁定的数据一致性。

12. 代码级深入分析

12.1 入库库位计算(策略 + 责任链)

入口 StockInService 调用 StrategyFactory.CreateStockInStrategy:一楼强制走「不混放」,其余楼层按 PlcaementMod 决定策略。

责任链节点职责:

节点 职责
StockInByExist 优先放「已有同批次」的列,同批次堆满一列再开新列(最多 2 列)
StockInByNew 放「整列全空」的新列,取最底层
StockInByMixing 与其他批次混放;仍无则落中转区兜底

StockInByExist 关键逻辑:

  1. GetStorageRackExceptStockout 先剔除「当天有出库任务的列」,避免入库与掏货出库冲突
  2. SemaphoreSlim(1,1) 串行化,防并发抢库位
  3. 列被锁时的分支:有未锁空列→直接取;空列≤1→开新列;一楼已开两列被锁→return null 等待解锁;物料已在中转区→return null 防重复
  4. 选定后先 Lock 再返回,保证 DB 一致性

12.2 电梯 PLC 通信

ElevatorPlc.Init 时序关键:先 PLCManager.AddPLC 再 Connect,否则 Job 先执行拿不到实例无法重连。

每台电梯 8 通道寄存器映射(读位/写位):

动作 通道2 网带 通道1 网带 读位 写位
入库放货 1001 1011 0 / 2 8 / 10
入库取货 2004 2014 5 / 7 13 / 15
出库放货 2001 2011 4 / 6 12 / 14
出库取货 1006 1016 1 / 3 9 / 11

电梯编号靠库位名称是否含「1号」判定(StorageRackName.Contains("1号") ? 1 : 2)。

12.3 RCS 回调双重分派(状态 + 策略)

RcsCallbackHandlerFactory.CreateHandler(status, destWarehouse):

  • 状态 21 → PickupCompletedStateHandler(取货完成)
  • 状态 23 → 按 Transport 二次分发到 StockOutWharfHandler / StockAreaHandler / TransitAreaHandler / ElevatorDeliveryHandler

ElevatorDeliveryHandler 完整链路:写 PLC 放货信号 → 写 PLC 队列 → 电梯计数 → 更新 AGV 任务完成 → 解锁库位 → SSE 广播刷新前端。


12.4 后台作业调度(Job,Quartz)

声明式注册:JobEngine 反射扫描所有「实现 IJob 且带 [Job] 特性」的类注册到 Quartz,新增作业只需打特性。

9 个作业(均 [DisallowConcurrentExecution] 禁止并发):

作业 间隔 职责
AgvTaskJob 1s 把 Initial 状态 AGV 任务下发给 RCS
ErpTaskJob 2s ERP 任务拆解为 AGV 搬运任务(一楼 AGV 限流 ≤4)
RcsCallbackJob 1s 消费 RCS 回调队列,幂等去重后处理状态流转
ElevatorJob 0.5s 轮询 PLC 入库取货信号,上升沿触发,入队二楼
ElevatorOutJob 0.5s 轮询 PLC 出库取货信号,生成一楼出库任务
StockInSecondFloorJob 2s 消费二楼入库队列,策略工厂算库位,无位回写队列自旋
TransferAreaJob 2s 调度中转区滞留物料
XinRongErpJob 2s 已完成任务回传新荣 ERP(含重试)
DataCleanJob 24h 清理 12 个月前历史数据 + 日志文件

作业间通信用 ConcurrentQueue 静态缓存做生产者-消费者(StockInSecondFloorCache、RcsCallbackCache);PLC 信号用 static bool 前值做上升沿检测。

12.5 Netty 条码协议

三套同构实现(客户端/服务端/UDP):

角色 场景
NettyAsClient WMS 主动连条码设备(基恩士/倍加福/喷码机),收扫码结果写内存缓存
NettyAsServer WMS 监听端口,发 LON\r\n 触发扫码,轮询缓存回读结果
NettyUdp 广播监听 503 端口,收设备心跳/动态

分帧:DelimiterBasedFrameDecoder(分隔符)或 LengthFieldBasedFrameDecoder(2 字节长度域)。分发:MEF [InheritedExport] 装配全部 Handler,责任链逐个 Handle,首个返回 true 终止。

12.6 出库库存计算

可出库存公式:物理在库 + 在途AGV任务数 + 撤单数 − 已下达出库命令总数 ≥ 本次出库数

CalculateOutMaterial 决策树:优先出「拖数最少」的列 → 锁定列等待解锁 → 挡路不同批次则触发掏货中转。

12.7 RPC 对接层

四家 ERP 中只有新荣(ErpType=3)完整可用,其余三家接口签名已漂移断链。认证方式:辰龙/企望 MD5 签名、IBS Header AppId/AppKey、新荣 body 内 user_key。RCS 返回 code==1000 成功。

12.8 WebHost 与认证

60+ HTTP 接口(入库/出库/撤单/搬运/库位维护/统计/SSE 推送)。当前认证完全关闭:JWT/IdentityServer 全部注释,登录为明文密码比对 + 无签名 GUID token,所有接口匿名可访问。限流过滤器、FluentValidation 规则均未启用。

12.9 基础设施与遗留模块

  • 动态 Lambda 查询(Infrastructure/LambdaHelp):把「字段+操作符+值」动态编译为 Expression<Func<T,bool>>,组内 OR、组间 AND,供 SqlSugar 动态筛选/排序——全项目最核心的通用能力。
  • ViKey 加密狗:P/Invoke 硬件授权读取。
  • Modules(WPF):Prism + HandyControl + FastReport,生产现场操作终端(设备连接监控 + 新卷入库查询/标签打印)。
  • MqttClient:空壳工程(仅 Class1),MQTT 未实现。
  • Infrastructure 从 DCS/Accelerider 项目迁移而来,保留旧命名空间/AssemblyTitle。

13. 技术债与改进点

并发/性能

  1. _SemaphoreSlim(1,1) 静态单例全局串行化所有入库计算,高并发吞吐瓶颈。
  2. GetStorageRackExceptStockout 每次查当天全部出库任务,宜加索引/缓存。
  3. 服务端扫码回读靠 100ms 轮询 + 2s 超时阻塞。

逻辑 bug(已定位) 4. RcsCallBackService.HandleStocOutFirstFloor 守卫误写 Floor==SecondFloor,一楼销售出库失败报警分支失效。 5. CalculateDestWarehouse_SaleOutMiddle 双重 OrderBy 覆盖,前一排序失效。 6. LambdaBuider 中 long 类型判断误写 PropertyInfo == typeof(long),恒 false。

死代码/未接线 7. 策略模式整套(StorageAreaStrategy/ElevatorStrategy/TaskCompletionStrategyFactory)无调用方,与状态模式功能重叠。 8. Job:JobListener 未注册、ScannerSceneAttribute 孤儿、JobGroup 枚举未用、PlateList 遗留。 9. Netty:两个 Decoder.cs 死代码且有 bug、PenmoReadCmd 无 Handler、UDP 心跳未落地。 10. RCS 回调 RcsCallBackService 内 4 个私有方法、StockOutCreationDetail、FailedStateHandler 均未引用。 11. ScheduleType 仅实现 Simple,Corn/Calendar/XML/JSON 未落地。

安全 12. 认证完全关闭,60+ 接口匿名可访问;登录明文密码 + 无签名 token;CORS 全开;IdentityServer 开发签名 + 弱口令 123456。 13. 配置含明文数据库密码。

工程/维护 14. 三家 ERP(辰龙/企望/IBS)接口签名漂移断链,仅新荣可用;ErpType/PmsType 特性未接线,实现选择依赖 Autofac「最后注册生效」。 15. PmsRPC(ACS/DCS)为占位半成品;IBSRPC 缺 await 不可用。 16. Modules.csproj 引用不存在的 ..\Server.MqttClient\;MqttClient 空壳。 17. Infrastructure 保留 DCS/Accelerider 迁移残留(命名空间/AssemblyTitle)。