公测版,立库方面基本完善

This commit is contained in:
葛林强 2026-01-22 11:07:07 +08:00
commit 568ff5b261
865 changed files with 76450 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
wcs/log
wcs_web/dist
WCS系统操作手册.pdf
WCS系统系统介绍_2_2501.pdf

32
ReadMe.md Normal file
View File

@ -0,0 +1,32 @@
## JAVA版本WCS说明文档
**项目技术栈:**
VUE3 + SpringBoot3
**涉及知识:**
+ html + css + ts
+ VUE3
+ SpringBoot
+ Mybatis + Mybatis-Plus
+ redis
+ mysql8
+ Element-Plus
+ vue-i18n
**开发环境:**
```
node版本V20.18.3
npm版本10.8.2
jdk版本21.0.6
```
[nodejs下载npm包含在内](https://nodejs.cn/en/download, "nodejs下载")
[JDK下载](https://learn.microsoft.com/zh-cn/java/openjdk/download "微软jdk下载")

View File

@ -0,0 +1,7 @@
### PLC/WCS交互协议
> 江苏菲达宝开电气股份有限公司
>
> 版本号S7.1.0
>
> 更新时间2025年12月20日

View File

@ -0,0 +1,454 @@
## 设备控制系统WCS开放式接口文档
> 江苏菲达宝开电气股份有限公司
>
> 主版本号2.0
>
> 版本号2.0.1
>
> 时间2025年6月6日
### 注意事项:
1、所有接口请勿并发调用高频调用可能会被系统拉黑
2、WCS调用上位系统时若返回值不重要或者仅上报作用时只要调用成功HTTP响应码为 2XX即表示调用成功若上位系统存在异常需要重新调用请不要返回 2XX响应码
3、WCS部分请求若请求失败在一定时间内会重新调用 若失败次数过多或时间过长则不再调用请确保存在内部处理方式不能完全依赖WCS接口
4、系统对于重复的数据会默认返回成功如重复发送的任务都会返回成功
### 统一约定
接口形式webapi
数据格式application/json
### 接口统一返回
| 键名 | 名称 | 数据类型 | 长度 | 是否必填 | 备注 |
| ---------- | -------- | -------- | ---- | -------- | ------------------------------------------------------------ |
| code | 返回码 | int32 | 4 | 是 | 操作成功返回 200其他为异常代码<br />操作成功或者操作已经成功均视为成功,需要返回成功代码 |
| message | 说明信息 | string | 255 | 是 | |
| returnData | 返回数据 | object | | 否 | 在需要返回数据的时候带出数据 |
### 任务交互
#### 1、*WCS向上位系统申请仓储任务
> 客户端:`WCS`
>
> 服务端:`上位系统`
>
> 调用方式POST
>
> 接口地址:<u>上位系统提供</u>
**使用说明:**WCS在需要申请任务时向上位系统请求上位系统返回任务数据只要返回结果为成功则视为成功若不带出任务需要上位系统后续及时发送发送接口为上位系统向WCS发送任务
**请求参数:**
| 键 | 键名称 | 数据类型 | 长度 | 是否必填 | 备注 |
| --------- | -------- | -------- | ---- | -------- | -------------------------- |
| requestId | 请求编号 | string | 64 | Y | 同样的请求编号视为同一请求 |
| vehicleNo | 载具编号 | string | 64 | Y | |
| location | 位置 | string | 64 | Y | |
| size | 尺寸 | int32 | | N | |
| length | 长 | int32 | | N | 计量单位以现场为准 |
| width | 宽 | int32 | | N | 计量单位以现场为准 |
| height | 高 | int32 | | N | 计量单位以现场为准 |
| weight | 重量 | int32 | | N | 计量单位以现场为准 |
```json
{
"requestId":"1122333",
"vehicleNo":"TP0001",
"location":"R1",
"size":1,
"length":200,
"width":150,
"height":30,
"weight":20000
}
```
**响应参数:**returnData
| 键 | 键名称 | 数据类型 | 长度 | 是否必填 | 备注 |
| ----------- | ------------ | -------- | ---- | -------- | ------------------------------------------------------------ |
| taskId | 任务号 | string | 64 | Y | |
| taskGroup | 任务组 | string | 64 | N | |
| taskType | 任务类型 | int32 | | Y | 枚举类型:<br />0 - 自动;<br />1 - 入库;<br />2 - 出库;<br />3 - 输送搬运;<br />9 - 移库;<br /><span style="color: #f455ee">自动任务请咨询我们后使用</span> |
| vehicleNo | 载具编号 | string | 64 | Y | WCS系统以此处返回的生成任务不以请求的载具号 |
| origin | 起点 | string | 32 | Y | |
| destination | 终点 | string | 32 | Y | |
| priority | 优先级 | int32 | | N | 范围0-9<br />默认优先级不传时5<br />数字越大优先级越高 |
| size | 尺寸 | int32 | | N | 默认0 |
| weight | 重量 | int32 | | N | 默认0 |
| sysName | 上位系统名称 | string | 32 | N | 固定值,请联系我们获取,任意传将无法收到任务回告 |
```json
{
"code":200,
"message":"SUCCESS",
"returnData":{
"taskId":"123441123",
"taskGroup":null,
"taskType":0,
"vehicle":"TP123",
"origin":"R2",
"destination":"A1-02-03-1",
"priority":3,
"size":1,
"weight":344,
"sysName":"WMS"
}
}
```
#### 2、上位系统向WCS推送仓储任务
> 客户端:`上位系统`
>
> 服务端:`wcs`
>
> 调用方式POST
>
> 接口地址:/api/pub/task/addStockTask
**使用说明:**上位系统调用此接口向WCS推送任务数据任务号必须唯一若任务号在WCS系统中已经存在则WCS会返回成功。
**请求参数:**
| 键 | 键名称 | 数据类型 | 长度 | 是否必填 | 备注 |
| ----------- | ------------ | -------- | ---- | -------- | ------------------------------------------------------------ |
| taskId | 任务号 | string | 64 | Y | 任务唯一识别号 |
| taskGroup | 任务组 | string | 64 | N | |
| taskType | 任务类型 | int32 | | Y | 枚举类型:<br />0 - 自动;<br />1 - 入库;<br />2 - 出库;<br />3 - 输送搬运;<br />9 - 移库;<br /><span style="color: #f455ee">自动任务请咨询我们后使用</span> |
| vehicleNo | 载具编号 | string | 64 | Y | WCS系统以此处返回的生成任务不以请求的载具号 |
| origin | 起点 | string | 32 | Y | |
| destination | 终点 | string | 32 | Y | |
| priority | 优先级 | int32 | | N | 范围0-9<br />默认优先级不传时5<br />数字越大优先级越高 |
| size | 尺寸 | int32 | | N | 默认0 |
| weight | 重量 | int32 | | N | 默认0 |
| sysName | 上位系统名称 | string | 32 | Y | 固定值,请联系我们获取,任意传将无法收到任务回告 |
```json
{
"taskId":"123441123",
"taskGroup":null,
"taskType":0,
"vehicle":"TP123",
"origin":"R2",
"destination":"A1-02-03-1",
"priority":3,
"size":1,
"weight":344,
"sysName":"WMS"
}
```
**响应参数:**统一响应
#### 3、*WCS上报仓储任务状态
> 客户端:`WCS`
>
> 服务端:`上位系统`
>
> 调用方式POST
>
> 接口地址:<u>上位系统提供</u>
**使用说明:**WCS在任务的节点上报上位系统任务状态只有最终任务状态会失败重试如任务完成任务取消等
**请求参数:**
| 键 | 键名称 | 数据类型 | 长度 | 是否必填 | 备注 |
| ----------- | -------- | -------- | ---- | -------- | ------------------------------------------------------------ |
| taskId | 任务号 | string | 64 | Y | 上位系统下发的任务号 |
| taskType | 任务类型 | int32 | | Y | 枚举类型:<br />0 - 自动;<br />1 - 入库;<br />2 - 出库;<br />3 - 输送搬运;<br />9 - 移库; |
| taskStatus | 任务状态 | int32 | | Y | 枚举类型:<br />1 - 任务开始<br />2 - 任务完成<br />3 - 任务异常<br />4 - 任务取消<br />5 - 目的位置有货<br />6 - 起点无货 |
| destination | 任务终点 | string | 64 | N | |
| vehicleNo | 载具号 | string | 64 | Y | |
| message | 信息 | string | 255 | N | |
```json
{
"taskId":"123777888",
"taskType":1,
"taskStatus":3,
"destination":"A1-01-3",
"vehicle":"TP0002",
"message":"任务被设备取消"
}
```
**响应参数:**统一响应
#### 4、上位系统查询仓储任务详情
> 客户端:`上位系统`
>
> 服务端:`wcs`
>
> 调用方式GET
>
> 接口地址:/api/pub/task/queryStockTaskDetail
**使用说明:**上位系统可调用此接口查询仓库任务信息,注意超出仓库保存时长的数据将无法查询
**请求参数:**<u>url参数</u>
| 键 | 键名称 | 数据类型 | 长度 | 是否必填 | 备注 |
| ------ | ------ | -------- | ---- | -------- | -------------------- |
| taskId | 任务号 | string | 64 | Y | 上位系统下发的任务号 |
```url
http://{ip}:{port}?taskId={taskId}
```
**响应参数:**returnData
| 键 | 键名称 | 数据类型 | 长度 | 是否必填 | 备注 |
| ------------ | ------------ | -------- | ---- | -------- | ------------------------------------------------------------ |
| taskId | 任务号 | string | 64 | Y | WCS系统任务号 |
| taskGroup | 任务组 | string | 64 | Y | |
| upperTaskId | 上位任务号 | string | 64 | Y | 请求的任务号 |
| taskType | 任务类型 | int32 | | Y | 枚举类型:<br />0 - 自动;<br />1 - 入库;<br />2 - 出库;<br />3 - 输送搬运;<br />9 - 移库; |
| origin | 起点 | string | 64 | N | |
| destination | 终点 | string | 64 | N | |
| taskStatus | 任务状态 | int32 | | Y | 枚举类型:<br />0 - 待执行;<br />1 - 排队中;<br />2 - 执行中;<br />3 - 已完成;<br />4 - 已取消;<br />5 - 任务异常;<br />6 - 任务超时; |
| canCancel | 是否允许取消 | int32 | | Y | |
| priority | 优先级 | int32 | | Y | |
| vehicleNo | 载具号 | string | 64 | Y | |
| vehicleSize | 载具尺寸 | int32 | | N | |
| weight | 重量 | decimal | | N | |
| startTime | 开始时间 | date | | Y | |
| completeTime | 完成时间 | date | | N | 任务完成时间 |
| endTime | 结束时间 | date | | N | 任务结束时间,包括完成或者取消 |
| taskMsg | 任务信息 | string | 255 | N | |
#### 5、上位系统要求取消仓储任务
> 客户端:`上位系统`
>
> 服务端:`wcs`
>
> 调用方式DELETE
>
> 接口地址:/api/pub/task/cancelStockTask
**使用说明:**上位系统可以在需要时取消已经下发的任务WCS系统会综合判定是否允许取消
**请求参数:**<u>url参数</u>
| 键 | 键名称 | 数据类型 | 长度 | 是否必填 | 备注 |
| ------ | ------ | -------- | ---- | -------- | -------------------- |
| taskId | 任务号 | string | 64 | Y | 上位系统下发的任务号 |
```url
http://{ip}:{port}?taskId={taskId}
```
**响应参数:**统一响应
#### 6、上位系统向WCS推送简单输送任务
> 客户端:`上位系统`
>
> 服务端:`wcs`
>
> 调用方式POST
>
> 接口地址:/api/pub/task/addConveyTask
**使用说明:** 简单输送任务一般表示为箱式线输送,即仅在关键点控制流向的任务,其余由设备自动输送
**请求参数:**
| 键 | 键名称 | 数据类型 | 长度 | 是否必填 | 备注 |
| --------- | ------------ | -------- | ---- | -------- | ------------------------------------------------------------ |
| taskId | 任务号 | string | 64 | Y | 任务唯一识别号 |
| taskGroup | 任务组 | string | 64 | N | |
| taskType | 任务类型 | int32 | | Y | 枚举类型:<br />1 - 捡选任务;<br />2 - 复核任务;<br />3 - 发货任务;<br />9 - 补货任务; |
| vehicleNo | 载具编号 | string | 64 | Y | |
| orderId | 订单号 | string | 32 | N | |
| location | 终点 | string | 32 | Y | |
| size | 尺寸 | string | 32 | N | |
| weight | 重量 | int32 | | N | 默认0 |
| length | 长 | int32 | | N | 默认0 |
| width | 宽 | int32 | | N | 默认0 |
| height | 高 | int32 | | N | 默认0 |
| sysName | 上位系统名称 | string | 32 | Y | 固定值,请联系我们获取,任意传将无法收到任务回告 |
```json
{
"taskId":"123",
"taskGroup":"334455",
"taskType":1,
"vehicleNo":"T0003",
"orderId":"009901",
"location":"A01",
"size":"-",
"weight":200,
"length":100,
"weight":150,
"height":120,
"sysName":"WMS"
}
```
**响应参数:**统一响应
#### 7、*WCS上报简单输送任务状态
> 客户端:`WCS`
>
> 服务端:`上位系统`
>
> 调用方式POST
>
> 接口地址:<u>上位系统提供</u>
**使用说明:** WCS在简单输送任务关键节点时上报上位系统状态如任务完成任务超时任务取消等
**请求参数:**
| 键 | 键名称 | 数据类型 | 长度 | 是否必填 | 备注 |
| -------------- | -------- | -------- | ---- | -------- | ------------------------------------------------------------ |
| taskId | 任务号 | string | 64 | Y | 上位系统下发的任务号 |
| taskType | 任务类型 | int32 | | Y | 枚举类型:<br />1 - 捡选任务;<br />2 - 复核任务;<br />3 - 发货任务;<br />4 - 补货任务; |
| taskStatus | 任务状态 | int32 | | Y | 枚举类型:<br />1 - 箱子到达某一点位<br />2 - 箱子到达目的地<br />3 - 任务异常<br />4 - 任务取消 |
| arriveLocation | 到达位置 | string | 64 | N | |
| vehicleNo | 载具号 | string | 64 | Y | |
| message | 信息 | string | 255 | N | |
**响应参数:**统一响应
#### 8、上位系统给WCS推送电子标签任务
> 客户端:`上位系统`
>
> 服务端:`wcs`
>
> 调用方式POST
>
> 接口地址:/api/pub/task/addEtagTask
**请求参数:**
| 键 | 键名称 | 数据类型 | 长度 | 是否必填 | 备注 |
| ---------- | ------------ | -------- | ---- | -------- | ------------------------------------------------------------ |
| taskGroup | 任务组 | string | 64 | N | |
| taskType | 任务类型 | int | | Y | 枚举类型:<br />1 - 捡货<br />2 - 上架<br />3 - 盘点<br />4 - 清点<br />5 - 其他 |
| lightModel | 点亮类型 | int | | Y | 枚举类型:<br />1 - 立即点亮<br />2 - 等待触发<br /><span style="color:#f455ee">根据流程确定,请与我们交流方案</span> |
| sysName | 上位系统名称 | string | 32 | Y | 固定值,请联系我们获取,否则将无法收到任务回告 |
| taskList | 任务数据 | list | | Y | |
taskList<span style="color:#f455ee">若任务中存在一个有问题,则所有任务都会判定失败</span>
| 键 | 键名称 | 数据类型 | 长度 | 是否必填 | 备注 |
| --------- | -------- | -------- | ---- | -------- | -------- |
| taskId | 任务号 | string | 64 | Y | 唯一标识 |
| orderId | 订单号 | string | 64 | N | |
| location | 点位 | string | 64 | Y | |
| goodsId | 物料编号 | string | 64 | N | |
| goodsName | 物料名称 | string | 64 | N | |
| lightNum | 点亮数量 | int | | Y | |
**请求示例:**
```json
{
"taskGroup":"2223336678",
"taskType":1,
"lightModel":1,
"sysName":"WMS",
"taskList":[
{
"taskId":"12434543",
"orderId":"889088",
"location":"A02-99-12",
"goodsId":"449802",
"goodsName":"饮用水",
"lightNum":13
}
]
}
```
**响应参数:**统一响应
#### 9、*WCS上报电子标签任务完成
> 客户端:`WCS`
>
> 服务端:`上位系统`
>
> 调用方式POST
>
> 接口地址:<u>上位系统提供</u>
**请求参数:**
| 键 | 键名称 | 数据类型 | 长度 | 是否必填 | 备注 |
| ------------- | ---------- | -------- | ---- | -------- | ---------------- |
| taskId | 任务号 | string | 64 | Y | 上位发来的任务号 |
| confirmNum | 确认的数量 | int | | Y | |
| confirmPerson | 确认人 | string | | N | |
**响应参数:**统一响应

View File

@ -0,0 +1,7 @@
#### WCS系统开发手册
+ 入库任务处理:
若起点为空则解析成单个堆垛机任务,此时若经过托盘线扫码则更新起点到组合入库任务,同时删除解析好的任务,重新解析。

View File

@ -0,0 +1,50 @@
## WCS系统操作手册
> 适用版本V2.2501.X
>
> 江苏菲达宝开电气股份有限公司 ---- 软件设计部
>
> 最新更新时间2025年12月
#### 一、编写目的
本文件旨在帮助用户合理使用本公司提供的WCS设备控制系统不包括其他系统即硬件设备操作以及正确处理系统运行过程中因为各种问题而产生的异常情况确保设备正常运行。
#### 二、面向对象
现场操作人员和设备维护人员。
#### 三、注意事项
+ 本系统设计在内网中使用,请勿将本系统端口或地址暴露在公网环境中,防止信息被窃取或丢失。
+ 本软件仅授权此项目使用,若无书面文件授权,请勿复制、转发本项目任何文件、代码和数据等任何与本软件相关的内容。
+ 本手册内容包括全部允许用户操作的内容,若示例截图中存在页面或者按钮未在此处介绍则表示其并不是为普通用户准备的,请勿操作相关界面或按钮。
+ 我们针对您当前的项目会对部分界面或按钮进行隐藏,不影响您的正常使用本系统,请您直接忽略即可。
+ 为了系统能正确运行,我们会搜集并存储您相关的数据(如库存数据,出入库数据,每天运行时长等),不过该数据仅存在于您的服务器上,未经过您的授权我们不会复制、上传您的数据。
#### 四、账户与密码
请您牢记您设置的账户密码,若忘记密码可联系本公司协助改密。
本系统和本公司WMS系统为独立产品账户密码并不通用请您单独使用。
#### 五、系统使用
##### 1、登录

View File

@ -0,0 +1,240 @@
## WCS设备控制系统 系统介绍
> 适用版本V2.2501.X
>
> 江苏菲达宝开电气股份有限公司
>
> 最新修订时间2025年10月
>
> 文档保密级别:外部公开
![baokaiLogo1](/Users/icewint/Pictures/develop/baokaiLogo1.png)
#### 参考链接
[江苏菲达宝开电气股份有限公司官网](https://www.baokai.cn/)
[江苏菲达宝开电气股份有限公司物流设备](https://www.baokai.cn/product/2/)
### 一、引言
本系统是由江苏菲达宝开电气股份有限公司(以下简称:本公司)自主设计研发。本文档的编写是为了便于您了解熟悉本系统的运作方式。如发现文档中存在错误,请您指正。
### 二、系统概述
#### 1、系统目的
本系统为上位管理系统WMS、ERP等以下简称上位系统和硬件设备的中间系统预留相关上位系统交互接口负责处理上位系统任务数据协调各设备间的接续稳定运行为设备提供调度功能并提供一定的数据查询记录查询功能。
系统为自动化生产线提供设备调度,任务处理等相关功能,能大幅提高生产效率,减少生产成本。
#### 2、适用的硬件设备
箱式线设备、仓储设备堆垛机、类堆垛机设备、托盘输送线等、电子标签、LED显示屏
#### 3、适用的上位系统
支持HTTP协议的上位系统
#### 4、网络架构
<img src="/Users/icewint/Library/Application Support/typora-user-images/image-20260120161313401.png" alt="image-20260120161313401" style="zoom:45%;" />
### 三、系统架构
系统采用B/S架构设计使得在任何地方均可通过浏览器访问本系统<sup>*</sup>
系统服务端支持跨平台发布,可以运行在 windows server、常见 linux 系统中。
系统客户端采用较友好的配色,合理的人机交互体验,支持 Edge、Chrome 等常用浏览器。
> \* 为了保证安全,部分设备控制相关的只能在指定的地方访问。
>
> 系统在其他设备访问时需保证网络畅通。
### 四、系统运作方式
系统通过后台存储的任务数据,判断各个设备的状态,计算最佳执行任务的方式。根据计算结果发送运行指令(基于 TCP/IP 协议)给设备,调度设备执行相关任务,任务完成后自动处理相关数据。
### 五、功能概述
> 提示:下方截图中存在的部分菜单或界面为开发者超级管理界面,您实际的系统中可能无法访问,在此不做介绍的界面对您的使用无任何影响,请您放心。本公司在项目实施过程中可能根据需要(如现场无此设备或相关需求)会屏蔽部分界面,请您悉知。
#### 总览
**登录界面**
![image-20260120155340563](/Users/icewint/Library/Application Support/typora-user-images/image-20260120155340563.png)
#### 登录后主页
![image-20260120155426264](/Users/icewint/Library/Application Support/typora-user-images/image-20260120155426264.png)
#### 主页
系统主页显示系统的基础资料和相关仓库的信息图表(仓库使用率、出入库数量,报警数量),更多图表正在添加中。
![image-20260120134835552](/Users/icewint/Library/Application Support/typora-user-images/image-20260120134835552.png)
#### 1、基础功能
##### 1) 语言相关
系统仅支持简体中文。
##### 2) 用户相关
系统支持自定义用户,自定义用户组(角色),可以为每个用户组分配可以访问的界面,支持管理员重置密码,用户启用禁用等基础功能。
![image-20260120132644475](/Users/icewint/Library/Application Support/typora-user-images/image-20260120132644475.png)
<img src="/Users/icewint/Library/Application Support/typora-user-images/image-20260120132728706.png" alt="image-20260120132728706" style="width:800px;height:400px" />
##### 3) 系统记录相关
**1、外部系统交互记录**
本系统和上位系统每次交互(包括上位发送消息给本系统,本系统发送消息给上位系统)都会在本系统中存有记录,便于查询记录以及定位相关问题。
![image-20260120133941795](/Users/icewint/Library/Application Support/typora-user-images/image-20260120133941795.png)
![image-20260120134003668](/Users/icewint/Library/Application Support/typora-user-images/image-20260120134003668.png)
**2、报警记录**
对于堆垛机等大型设备的报警信息采集和记录,用户可随时查询,部分报警提供相应的处理建议。
![image-20260120134236809](/Users/icewint/Library/Application Support/typora-user-images/image-20260120134236809.png)
**3、扫码记录**
系统将记录每个扫码器触发时的扫码结果,供用户查询和确定问题,若存在需要可以导出相关信息。
![image-20260120134546361](/Users/icewint/Library/Application Support/typora-user-images/image-20260120134546361.png)
#### 2、仓库功能
##### 1) 仓库硬件相关
**堆垛机管理**
系统提供堆垛机状态的查询,任务参数设置(硬件参数设备层会提供),站台设置,可以开启或关闭堆垛机,调整其工作模式,设置堆垛机站台等。
![image-20260120150046695](/Users/icewint/Library/Application Support/typora-user-images/image-20260120150046695.png)
![image-20260120150106819](/Users/icewint/Library/Application Support/typora-user-images/image-20260120150106819.png)
**托盘线(带任务线体)管理**
系统监控所有需要的托盘线控制其上载具托盘或箱子的运行方向并配合其他设备如堆垛机、AGV等进行载具的运转与调度。用户可以手动设置参数或者查询托盘线的状态。
![image-20260120150433960](/Users/icewint/Library/Application Support/typora-user-images/image-20260120150433960.png)
<img src="/Users/icewint/Library/Application Support/typora-user-images/image-20260120150451644.png" alt="image-20260120150451644" style="width:800px;height:400px" />
**箱式线管理**<sup>*</sup>
设备对箱式线特殊功能点位可以进行设置,以满足当前业务需要(如和堆垛机对接等)。
> \* 若箱式线存在实时监控的需求,则箱式线将视为任务线体,可能会按照托盘线的逻辑管理。
![image-20260120151835609](/Users/icewint/Library/Application Support/typora-user-images/image-20260120151835609.png)
<img src="/Users/icewint/Library/Application Support/typora-user-images/image-20260120151901637.png" alt="image-20260120151901637" style="zoom:30%;" />
**扫码器管理**
系统可以自定义扫码器参数或者新增减少扫码器,便于后续扩展扫码器。若有需要,可以手动选择扫码器已经预设好的功能。
![image-20260120152034955](/Users/icewint/Library/Application Support/typora-user-images/image-20260120152034955.png)
<img src="/Users/icewint/Library/Application Support/typora-user-images/image-20260120152130333.png" alt="image-20260120152130333" style="height:400px"/>
##### 5) 仓库数据相关
**货位管理**
系统记录相关货位的状态已经当前货位的载具号,便于用户查询在库数据或控制空闲货位量,保障生产。
![image-20260120150742320](/Users/icewint/Library/Application Support/typora-user-images/image-20260120150742320.png)
![image-20260120150827832](/Users/icewint/Library/Application Support/typora-user-images/image-20260120150827832.png)
**任务管理**
系统可以手动创建或者依赖上位系统自动创建相关出入库任务并调度设备执行对应的动作。执行完成后自动上报上位系统任务状态并保存历史运行的记录供用户查询。
![image-20260120151529946](/Users/icewint/Library/Application Support/typora-user-images/image-20260120151529946.png)
#### 3、箱式线功能
> 此处箱式线为输送分拨作用不与其他设备如堆垛机、AGV对接有对接功能的请参照 <u>**仓库功能**</u> 内的 <u>**箱式线管理**</u>
**箱式线任务**
系统提供箱式线当前任务的查询、新建和编辑等功能,系统根据当前存在的任务进行箱子分拨作业。
系统可以根据用户需要定制其他功能,如请求上位系统根据结果进行分拨、根据条码指定位数的数字进行分拨、根据条码开头或结尾不同的格式进行分拨,您可以自由选择相关模式。
![image-20260120152853550](/Users/icewint/Library/Application Support/typora-user-images/image-20260120152853550.png)
**站台参数设置**
系统支持对发货口,复核口等参数进行设置,如关闭或开启相关站台。
![image-20260120153258331](/Users/icewint/Library/Application Support/typora-user-images/image-20260120153258331.png)
#### 4、电子标签
**电子标签任务**
系统可以通过手动或者接收上位系统发来的任务进行电子标签任务的存储、点亮等业务。支持标签与货位一对一,一堆多等业务逻辑。捡货完成后可以根据需要与上位系统进行交互反馈捡货(播种)结果。
![image-20260120153641606](/Users/icewint/Library/Application Support/typora-user-images/image-20260120153641606.png)
#### 5、附加功能
##### 1) 条码打印
系统提供业务需要的code128码、QR码的打印功能该功能可以不经过登录直接使用便于现场业务开展。
<img src="/Users/icewint/Library/Application Support/typora-user-images/image-20260120153932676.png" alt="image-20260120153932676" style="height:300px;width:800px"/>
<img src="/Users/icewint/Library/Application Support/typora-user-images/image-20260120154044866.png" alt="image-20260120154044866" style="height:300px;width:800px"/>
##### 2) 文件下载
系统提供当前项目的使用手册或者其他文件下载,用户登录系统可以自由下载。
![image-20260120154308341](/Users/icewint/Library/Application Support/typora-user-images/image-20260120154308341.png)
**更多详情,您可以随时联系本公司销售,感谢您的支持!**

58
files/WCS网络图.drawio Normal file
View File

@ -0,0 +1,58 @@
<mxfile host="Electron" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/29.3.0 Chrome/140.0.7339.249 Electron/38.7.2 Safari/537.36" version="29.3.0">
<diagram name="第 1 页" id="2hT-jvviJQxeiUCTyhCD">
<mxGraphModel dx="1261" dy="925" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="3WYJ2srtHzsMTpYCN3Gz-18" edge="1" parent="1" source="3WYJ2srtHzsMTpYCN3Gz-1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.75;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" target="3WYJ2srtHzsMTpYCN3Gz-16">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="3WYJ2srtHzsMTpYCN3Gz-1" parent="1" style="shape=internalStorage;whiteSpace=wrap;html=1;dx=15;dy=15;rounded=1;arcSize=8;strokeWidth=2;fillColor=#dae8fc;strokeColor=#6c8ebf;" value="WCS后台服务" vertex="1">
<mxGeometry height="70" width="160" x="190" y="190" as="geometry" />
</mxCell>
<mxCell id="3WYJ2srtHzsMTpYCN3Gz-2" parent="1" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.database;whiteSpace=wrap;fillColor=#ffe6cc;strokeColor=#d79b00;" value="数据库" vertex="1">
<mxGeometry height="60" width="60" x="140" y="320" as="geometry" />
</mxCell>
<mxCell id="3WYJ2srtHzsMTpYCN3Gz-3" parent="1" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.database;whiteSpace=wrap;fillColor=#ffe6cc;strokeColor=#d79b00;" value="redis" vertex="1">
<mxGeometry height="60" width="60" x="330" y="320" as="geometry" />
</mxCell>
<mxCell id="3WYJ2srtHzsMTpYCN3Gz-4" edge="1" parent="1" source="3WYJ2srtHzsMTpYCN3Gz-1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.25;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" target="3WYJ2srtHzsMTpYCN3Gz-2">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="3WYJ2srtHzsMTpYCN3Gz-5" edge="1" parent="1" source="3WYJ2srtHzsMTpYCN3Gz-1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.75;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" target="3WYJ2srtHzsMTpYCN3Gz-3">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="3WYJ2srtHzsMTpYCN3Gz-9" edge="1" parent="1" source="3WYJ2srtHzsMTpYCN3Gz-8" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" target="3WYJ2srtHzsMTpYCN3Gz-1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="3WYJ2srtHzsMTpYCN3Gz-8" parent="1" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.loop_limit;whiteSpace=wrap;fillColor=#d5e8d4;strokeColor=#82b366;" value="用户客户端&lt;div&gt;(浏览器)&lt;/div&gt;" vertex="1">
<mxGeometry height="60" width="100" x="220" y="40" as="geometry" />
</mxCell>
<mxCell id="3WYJ2srtHzsMTpYCN3Gz-11" edge="1" parent="1" source="3WYJ2srtHzsMTpYCN3Gz-10" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.25;entryY=0;entryDx=0;entryDy=0;" target="3WYJ2srtHzsMTpYCN3Gz-1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="3WYJ2srtHzsMTpYCN3Gz-10" parent="1" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.loop_limit;whiteSpace=wrap;fillColor=#d5e8d4;strokeColor=#82b366;" value="用户移动端设备&lt;div&gt;PDA&lt;/div&gt;" vertex="1">
<mxGeometry height="60" width="100" x="80" y="80" as="geometry" />
</mxCell>
<mxCell id="3WYJ2srtHzsMTpYCN3Gz-13" edge="1" parent="1" source="3WYJ2srtHzsMTpYCN3Gz-12" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" target="3WYJ2srtHzsMTpYCN3Gz-1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="3WYJ2srtHzsMTpYCN3Gz-12" parent="1" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.loop_limit;whiteSpace=wrap;fillColor=#d5e8d4;strokeColor=#82b366;" value="外部展示系统&lt;div&gt;(数据大屏)&lt;/div&gt;" vertex="1">
<mxGeometry height="60" width="100" x="30" y="195" as="geometry" />
</mxCell>
<mxCell id="3WYJ2srtHzsMTpYCN3Gz-14" parent="1" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.document2;whiteSpace=wrap;size=0.25;fillColor=#e1d5e7;strokeColor=#9673a6;" value="上位系统" vertex="1">
<mxGeometry height="70" width="110" x="420" y="190" as="geometry" />
</mxCell>
<mxCell id="3WYJ2srtHzsMTpYCN3Gz-15" edge="1" parent="1" source="3WYJ2srtHzsMTpYCN3Gz-14" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;" target="3WYJ2srtHzsMTpYCN3Gz-1" value="">
<mxGeometry height="50" relative="1" width="50" as="geometry">
<mxPoint x="500" y="340" as="sourcePoint" />
<mxPoint x="550" y="290" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="3WYJ2srtHzsMTpYCN3Gz-16" parent="1" style="label;whiteSpace=wrap;html=1;image=img/clipart/Gear_128x128.png;fillColor=#f8cecc;gradientColor=#ea6b66;strokeColor=#b85450;" value="硬件设备" vertex="1">
<mxGeometry height="60" width="140" x="370" y="80" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

2
wcs/.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
/mvnw text eol=lf
*.cmd text eol=crlf

33
wcs/.gitignore vendored Normal file
View File

@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

View File

@ -0,0 +1,19 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
wrapperVersion=3.3.2
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip

9681
wcs/db/wcs.sql Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

BIN
wcs/lib/lv_led_64.dll Normal file

Binary file not shown.

259
wcs/mvnw vendored Executable file
View File

@ -0,0 +1,259 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Apache Maven Wrapper startup batch script, version 3.3.2
#
# Optional ENV vars
# -----------------
# JAVA_HOME - location of a JDK home dir, required when download maven via java source
# MVNW_REPOURL - repo url base for downloading maven distribution
# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
# ----------------------------------------------------------------------------
set -euf
[ "${MVNW_VERBOSE-}" != debug ] || set -x
# OS specific support.
native_path() { printf %s\\n "$1"; }
case "$(uname)" in
CYGWIN* | MINGW*)
[ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
native_path() { cygpath --path --windows "$1"; }
;;
esac
# set JAVACMD and JAVACCMD
set_java_home() {
# For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
if [ -n "${JAVA_HOME-}" ]; then
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACCMD="$JAVA_HOME/jre/sh/javac"
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACCMD="$JAVA_HOME/bin/javac"
if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
return 1
fi
fi
else
JAVACMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v java
)" || :
JAVACCMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v javac
)" || :
if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
return 1
fi
fi
}
# hash string like Java String::hashCode
hash_string() {
str="${1:-}" h=0
while [ -n "$str" ]; do
char="${str%"${str#?}"}"
h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
str="${str#?}"
done
printf %x\\n $h
}
verbose() { :; }
[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
die() {
printf %s\\n "$1" >&2
exit 1
}
trim() {
# MWRAPPER-139:
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
# Needed for removing poorly interpreted newline sequences when running in more
# exotic environments such as mingw bash on Windows.
printf "%s" "${1}" | tr -d '[:space:]'
}
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
while IFS="=" read -r key value; do
case "${key-}" in
distributionUrl) distributionUrl=$(trim "${value-}") ;;
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
esac
done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
case "${distributionUrl##*/}" in
maven-mvnd-*bin.*)
MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
*AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
:Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
:Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
:Linux*x86_64*) distributionPlatform=linux-amd64 ;;
*)
echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
distributionPlatform=linux-amd64
;;
esac
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
;;
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
esac
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
distributionUrlName="${distributionUrl##*/}"
distributionUrlNameMain="${distributionUrlName%.*}"
distributionUrlNameMain="${distributionUrlNameMain%-bin}"
MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
exec_maven() {
unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
}
if [ -d "$MAVEN_HOME" ]; then
verbose "found existing MAVEN_HOME at $MAVEN_HOME"
exec_maven "$@"
fi
case "${distributionUrl-}" in
*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
esac
# prepare tmp dir
if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
trap clean HUP INT TERM EXIT
else
die "cannot create temp dir"
fi
mkdir -p -- "${MAVEN_HOME%/*}"
# Download and Install Apache Maven
verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
verbose "Downloading from: $distributionUrl"
verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
# select .zip or .tar.gz
if ! command -v unzip >/dev/null; then
distributionUrl="${distributionUrl%.zip}.tar.gz"
distributionUrlName="${distributionUrl##*/}"
fi
# verbose opt
__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
# normalize http auth
case "${MVNW_PASSWORD:+has-password}" in
'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
esac
if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
verbose "Found wget ... using wget"
wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
verbose "Found curl ... using curl"
curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
elif set_java_home; then
verbose "Falling back to use Java to download"
javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
cat >"$javaSource" <<-END
public class Downloader extends java.net.Authenticator
{
protected java.net.PasswordAuthentication getPasswordAuthentication()
{
return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
}
public static void main( String[] args ) throws Exception
{
setDefault( new Downloader() );
java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
}
}
END
# For Cygwin/MinGW, switch paths to Windows format before running javac and java
verbose " - Compiling Downloader.java ..."
"$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
verbose " - Running Downloader.java ..."
"$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
fi
# If specified, validate the SHA-256 sum of the Maven distribution zip file
if [ -n "${distributionSha256Sum-}" ]; then
distributionSha256Result=false
if [ "$MVN_CMD" = mvnd.sh ]; then
echo "Checksum validation is not supported for maven-mvnd." >&2
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
elif command -v sha256sum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
distributionSha256Result=true
fi
elif command -v shasum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
distributionSha256Result=true
fi
else
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
fi
if [ $distributionSha256Result = false ]; then
echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
exit 1
fi
fi
# unzip and move
if command -v unzip >/dev/null; then
unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
else
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
fi
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
clean || :
exec_maven "$@"

149
wcs/mvnw.cmd vendored Normal file
View File

@ -0,0 +1,149 @@
<# : batch portion
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Apache Maven Wrapper startup batch script, version 3.3.2
@REM
@REM Optional ENV vars
@REM MVNW_REPOURL - repo url base for downloading maven distribution
@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
@REM ----------------------------------------------------------------------------
@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
@SET __MVNW_CMD__=
@SET __MVNW_ERROR__=
@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
@SET PSModulePath=
@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
)
@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
@SET __MVNW_PSMODULEP_SAVE=
@SET __MVNW_ARG0_NAME__=
@SET MVNW_USERNAME=
@SET MVNW_PASSWORD=
@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
@echo Cannot start maven from wrapper >&2 && exit /b 1
@GOTO :EOF
: end batch / begin powershell #>
$ErrorActionPreference = "Stop"
if ($env:MVNW_VERBOSE -eq "true") {
$VerbosePreference = "Continue"
}
# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
if (!$distributionUrl) {
Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
}
switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
"maven-mvnd-*" {
$USE_MVND = $true
$distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
$MVN_CMD = "mvnd.cmd"
break
}
default {
$USE_MVND = $false
$MVN_CMD = $script -replace '^mvnw','mvn'
break
}
}
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
if ($env:MVNW_REPOURL) {
$MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
}
$distributionUrlName = $distributionUrl -replace '^.*/',''
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
if ($env:MAVEN_USER_HOME) {
$MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
}
$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
exit $?
}
if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
}
# prepare tmp dir
$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
trap {
if ($TMP_DOWNLOAD_DIR.Exists) {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
}
New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
# Download and Install Apache Maven
Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
Write-Verbose "Downloading from: $distributionUrl"
Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
$webclient = New-Object System.Net.WebClient
if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
$webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
# If specified, validate the SHA-256 sum of the Maven distribution zip file
$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
if ($distributionSha256Sum) {
if ($USE_MVND) {
Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
}
Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
}
}
# unzip and move
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
try {
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
} catch {
if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
Write-Error "fail to move MAVEN_HOME"
}
} finally {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"

219
wcs/pom.xml Normal file
View File

@ -0,0 +1,219 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.ice</groupId>
<artifactId>wcs</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>wcs</name>
<description>wcs</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Sa-Token 权限认证在线文档https://sa-token.cc -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
<version>1.42.0</version>
</dependency>
<!-- Sa-Token 整合 RedisTemplate -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-template</artifactId>
<version>1.42.0</version>
</dependency>
<!-- 提供 Redis 连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- redis springboot 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.7</version>
</dependency>
<!-- mybatis-plus-join -->
<dependency>
<groupId>com.github.yulichang</groupId>
<artifactId>mybatis-plus-join</artifactId>
<version>1.4.4</version>
</dependency>
<!-- 验证框架 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.53</version>
</dependency>
<!-- quartz 核心包 -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
<!-- quartz 工具包 -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.4.0-rc1</version>
</dependency>
<!-- httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- easyPoi -->
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-spring-boot-starter</artifactId>
<version>4.5.0</version>
<exclusions>
<exclusion>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- hslCommunication-->
<dependency>
<groupId>com.hu</groupId>
<artifactId>HslCommunication</artifactId>
<version>3.7.0</version>
<scope>system</scope>
<systemPath>${pom.basedir}/lib/HslCommunication-3.7.0.jar</systemPath>
</dependency>
<!-- 在pom.xml中明确指定javassist版本 -->
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.28.0-GA</version>
</dependency>
<!--获取系统信息-->
<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
<version>6.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
<includeSystemScope>true</includeSystemScope>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<nonFilteredFileExtensions>
<nonFilteredFileExtension>sql</nonFilteredFileExtension>
<nonFilteredFileExtension>xlsx</nonFilteredFileExtension>
<nonFilteredFileExtension>xls</nonFilteredFileExtension>
<nonFilteredFileExtension>docx</nonFilteredFileExtension>
<nonFilteredFileExtension>pdf</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
<resource>
<directory>lib</directory>
<targetPath>BOOT-INF/lib/</targetPath>
<includes>
<include>*.jar</include>
<include>*.dll</include>
</includes>
</resource>
</resources>
</build>
</project>

View File

@ -0,0 +1,36 @@
package org.wcs;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Import;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.wcs.utils.SpringUtils;
@Import(SpringUtils.class)
@EnableTransactionManagement
@SpringBootApplication
public class WcsApplication {
private static ConfigurableApplicationContext applicationContext;
public static void main(String[] args) {
applicationContext = SpringApplication.run(WcsApplication.class, args);
}
/**
* 退出并重启
*/
public static void restart() {
ApplicationArguments args = applicationContext.getBean(ApplicationArguments.class);
Thread thread = new Thread(() -> {
applicationContext.close();
applicationContext = SpringApplication.run(WcsApplication.class, args.getSourceArgs()); // 修改这里
});
thread.setDaemon(false);
thread.start();
}
}

View File

@ -0,0 +1,15 @@
package org.wcs.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 用于标记扫码方法的标记
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ScanMethodTag {
String value();
}

View File

@ -0,0 +1,26 @@
package org.wcs.app;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.wcs.helper.SseHelper;
@Component
@RequiredArgsConstructor
public class ApplicationClose implements ApplicationListener<ContextClosedEvent> {
private final SseHelper sseHelper;
/**
* 监听应用关闭事件
*
* @param event 应用关闭事件
*/
@Override
public void onApplicationEvent(@NonNull ContextClosedEvent event) {
// 关闭所有 SSEEmitter
sseHelper.closeAllEmitter();
}
}

View File

@ -0,0 +1,291 @@
package org.wcs.business.data.impl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.wcs.business.data.intf.IDataLoader;
import org.wcs.mapper.intf.*;
import org.wcs.model.po.app.*;
import org.wcs.plugin.plc.common.PlcCommonData;
import org.wcs.plugin.plc.model.SiemensDBAddress;
import org.wcs.plugin.webHttpClient.WebHttpClient;
import org.wcs.plugin.webHttpClient.model.HttpAddressKeyItem;
import org.wcs.utils.AppStringUtils;
import java.util.*;
/**
* 数据加载器
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class DataLoader implements IDataLoader {
private final StringRedisTemplate stringRedisTemplate;
private final AppStackerInfoService stackerInfoService;
private final AppStackerStandService stackerStandService;
private final AppTrayConveyLocationService trayConveyLocationService;
private final AppConveyLocationService conveyLocationService;
private final AppBaseDbService baseDbService;
private final AppStockScanService stockScanService;
private final AppBaseApiInfoService baseApiInfoService;
/**
* 加载DB地址 ---- 总方法
*/
@Override
public void loadDBAddress() {
clearDBAddressRedis(); // 清理DB地址缓存
loadStackerDBAddress(); // 加载堆垛机DB地址
loadStackerStandDBAddress(); // 加载堆垛机站台DB地址
loadTrayConveyDBAddress(); // 加载托盘输送DB地址
loadConveyDBAddress(); // 加载箱式线输送DB地址
loadScannerDBAddress(); // 加载扫描器DB地址
loadDbTableAddress(); // 加载数据库表地址
}
/**
* 清除DB地址缓存
*/
@Override
public void clearDBAddressRedis() {
Set<String> keys = stringRedisTemplate.keys(PlcCommonData.PLC_DB_ADDRESS_REDIS_KEY + ":*");
long delete = stringRedisTemplate.delete(keys);
log.info("清除DB地址缓存结果{}", delete);
}
/**
* 加载堆垛机数据DB地址
*/
@Override
public void loadStackerDBAddress() {
List<AppStackerInfo> stackerInfos = stackerInfoService.queryAll(); // 堆垛机信息
if(stackerInfos == null) {
log.error("加载堆垛机DB信息时堆垛机信息数据库访问异常");
return; // 堆垛机信息数据库访问异常
}
Map<String, SiemensDBAddress> siemensDBAddressMap = new HashMap<>();
// 加载堆垛机DB数据
for (AppStackerInfo stackerInfo : stackerInfos) {
// 读取堆垛机状态
SiemensDBAddress readStatusDbAddress = new SiemensDBAddress();
readStatusDbAddress.setPlcId(stackerInfo.getPlcId());
readStatusDbAddress.setDbName("*堆垛机状态"+stackerInfo.getStackerId());
readStatusDbAddress.setDbAddress(stackerInfo.getReadStatusAddress());
siemensDBAddressMap.put(readStatusDbAddress.getDbName(), readStatusDbAddress);
// 写入堆垛机任务
SiemensDBAddress writeTaskDbAddress = new SiemensDBAddress();
writeTaskDbAddress.setPlcId(stackerInfo.getPlcId());
writeTaskDbAddress.setDbName("*堆垛机写任务"+stackerInfo.getStackerId());
writeTaskDbAddress.setDbAddress(stackerInfo.getWriteTaskAddress());
siemensDBAddressMap.put(writeTaskDbAddress.getDbName(), writeTaskDbAddress);
// 堆垛机任务确认
if(AppStringUtils.isNotEmpty(stackerInfo.getTaskConfirmAddress())) {
SiemensDBAddress taskConfirmDbAddress = new SiemensDBAddress();
taskConfirmDbAddress.setPlcId(stackerInfo.getPlcId());
taskConfirmDbAddress.setDbName("*堆垛机写任务确认"+stackerInfo.getStackerId());
taskConfirmDbAddress.setDbAddress(stackerInfo.getTaskConfirmAddress());
siemensDBAddressMap.put(taskConfirmDbAddress.getDbName(), taskConfirmDbAddress);
}
// 堆垛机过账区
SiemensDBAddress taskStatusDbAddress = new SiemensDBAddress();
taskStatusDbAddress.setPlcId(stackerInfo.getPlcId());
taskStatusDbAddress.setDbName("*堆垛机过账区"+stackerInfo.getStackerId());
taskStatusDbAddress.setDbAddress(stackerInfo.getTaskStatusAddress());
siemensDBAddressMap.put(taskStatusDbAddress.getDbName(), taskStatusDbAddress);
}
log.info("堆垛机DB数据加载完成数据行数{}", siemensDBAddressMap.size());
siemensDBAddressMap.forEach((dbName, dbAddress) -> {
stringRedisTemplate.opsForValue().set(PlcCommonData.PLC_DB_ADDRESS_REDIS_KEY + ":" + dbName, AppStringUtils.objectToString(dbAddress));
});
}
/**
* 加载堆垛机站台数据DB地址
*/
@Override
public void loadStackerStandDBAddress() {
List<AppStackerStand> stackerStands = stackerStandService.queryAll(); // 堆垛机站台信息
if(stackerStands == null) {
log.error("加载堆垛机DB信息时堆垛机站台信息数据库访问异常");
return; // 堆垛机信息数据库访问异常
}
Map<String, SiemensDBAddress> siemensDBAddressMap = new HashMap<>();
// 加载堆垛机站台DB数据
for (AppStackerStand stackerStand : stackerStands) {
// 读取站台状态
if(AppStringUtils.isNotEmpty(stackerStand.getReadStatusAddress())) {
SiemensDBAddress readStatusDbAddress = new SiemensDBAddress();
readStatusDbAddress.setPlcId(stackerStand.getPlcId());
readStatusDbAddress.setDbName("*输送机状态"+stackerStand.getStandId());
readStatusDbAddress.setDbAddress(stackerStand.getReadStatusAddress());
siemensDBAddressMap.put(readStatusDbAddress.getDbName(), readStatusDbAddress);
}
// 写入站台任务
if(AppStringUtils.isNotEmpty(stackerStand.getWriteTaskAddress())) {
SiemensDBAddress writeTaskDbAddress = new SiemensDBAddress();
writeTaskDbAddress.setPlcId(stackerStand.getPlcId());
writeTaskDbAddress.setDbName("*输送机写任务"+stackerStand.getStandId());
writeTaskDbAddress.setDbAddress(stackerStand.getWriteTaskAddress());
siemensDBAddressMap.put(writeTaskDbAddress.getDbName(), writeTaskDbAddress);
}
}
log.info("堆垛机站台DB数据加载完成数据行数{}", siemensDBAddressMap.size());
siemensDBAddressMap.forEach((dbName, dbAddress) -> {
stringRedisTemplate.opsForValue().set(PlcCommonData.PLC_DB_ADDRESS_REDIS_KEY + ":" + dbName, AppStringUtils.objectToString(dbAddress));
});
}
/**
* 加载托盘传送数据DB地址
*/
@Override
public void loadTrayConveyDBAddress() {
List<AppTrayConveyLocation> trayConveyLocations = trayConveyLocationService.queryAll();
if(trayConveyLocations == null) {
log.error("加载托盘传送DB信息时托盘传送信息数据库访问异常");
return;
}
Map<String, SiemensDBAddress> siemensDBAddressMap = new HashMap<>();
for (AppTrayConveyLocation trayConveyLocation : trayConveyLocations) {
if(AppStringUtils.isNotEmpty(trayConveyLocation.getReadStatusAddress())) {
SiemensDBAddress readStatusDbAddress = new SiemensDBAddress();
readStatusDbAddress.setPlcId(trayConveyLocation.getPlcId());
readStatusDbAddress.setDbName("*输送机状态"+trayConveyLocation.getLocationId());
readStatusDbAddress.setDbAddress(trayConveyLocation.getReadStatusAddress());
siemensDBAddressMap.put(readStatusDbAddress.getDbName(), readStatusDbAddress);
}
if(AppStringUtils.isNotEmpty(trayConveyLocation.getWriteTaskAddress())) {
SiemensDBAddress writeTaskDbAddress = new SiemensDBAddress();
writeTaskDbAddress.setPlcId(trayConveyLocation.getPlcId());
writeTaskDbAddress.setDbName("*输送机写任务"+trayConveyLocation.getLocationId());
writeTaskDbAddress.setDbAddress(trayConveyLocation.getWriteTaskAddress());
siemensDBAddressMap.put(writeTaskDbAddress.getDbName(), writeTaskDbAddress);
}
}
log.info("初始化托盘输送DB数据完成数据行数{}", siemensDBAddressMap.size());
siemensDBAddressMap.forEach((dbName, dbAddress) -> {
stringRedisTemplate.opsForValue().set(PlcCommonData.PLC_DB_ADDRESS_REDIS_KEY + ":" + dbName, AppStringUtils.objectToString(dbAddress));
});
}
/**
* 加载箱式线输送的DB地址
*/
@Override
public void loadConveyDBAddress() {
List<AppConveyLocation> conveyLocations = conveyLocationService.queryAll();
if(conveyLocations == null) {
log.error("加载箱式线输送DB信息时托盘传送信息数据库访问异常");
return;
}
Map<String, SiemensDBAddress> siemensDBAddressMap = new HashMap<>();
for(AppConveyLocation conveyLocation : conveyLocations) {
/* 读取状态地址 */
if(AppStringUtils.isNotEmpty(conveyLocation.getReadStatusAddress())) {
SiemensDBAddress readStatusDbAddress = new SiemensDBAddress();
readStatusDbAddress.setPlcId(conveyLocation.getPlcId());
readStatusDbAddress.setDbName("*输送机状态"+conveyLocation.getLocationId());
readStatusDbAddress.setDbAddress(conveyLocation.getReadStatusAddress());
siemensDBAddressMap.put(readStatusDbAddress.getDbName(), readStatusDbAddress);
}
/* 写入任务地址 */
if(AppStringUtils.isNotEmpty(conveyLocation.getWriteTaskAddress())) {
SiemensDBAddress writeTaskDbAddress = new SiemensDBAddress();
writeTaskDbAddress.setPlcId(conveyLocation.getPlcId());
writeTaskDbAddress.setDbName("*输送机写任务"+conveyLocation.getLocationId());
writeTaskDbAddress.setDbAddress(conveyLocation.getWriteTaskAddress());
siemensDBAddressMap.put(writeTaskDbAddress.getDbName(), writeTaskDbAddress);
}
}
log.info("初始化箱式线输送DB地址完成数据行数{}", siemensDBAddressMap.size());
siemensDBAddressMap.forEach((dbName, dbAddress) -> {
stringRedisTemplate.opsForValue().set(PlcCommonData.PLC_DB_ADDRESS_REDIS_KEY + ":" + dbName, AppStringUtils.objectToString(dbAddress));
});
}
/**
* 加载DB数据表的地址
*/
@Override
public void loadDbTableAddress() {
List<AppBaseDb> appBaseDbs = baseDbService.queryAll();
if(appBaseDbs == null) {
log.error("加载DB数据库表信息时数据库表信息数据库访问异常");
return;
}
Map<String, SiemensDBAddress> siemensDBAddressMap = new HashMap<>();
for(AppBaseDb appBaseDb : appBaseDbs) {
SiemensDBAddress baseDbAddress = new SiemensDBAddress();
baseDbAddress.setPlcId(appBaseDb.getPlcId());
baseDbAddress.setDbName(appBaseDb.getDbName());
baseDbAddress.setDbAddress(appBaseDb.getDbAddress());
siemensDBAddressMap.put(baseDbAddress.getDbName(), baseDbAddress);
}
log.info("初始化DB地址表完成数据行数{}", siemensDBAddressMap.size());
siemensDBAddressMap.forEach((dbName, dbAddress) -> {
stringRedisTemplate.opsForValue().set(PlcCommonData.PLC_DB_ADDRESS_REDIS_KEY + ":" + dbName, AppStringUtils.objectToString(dbAddress));
});
}
/**
* 加载扫描器DB地址
*/
@Override
public void loadScannerDBAddress() {
List<AppStockScan> stockScans = stockScanService.queryAll();
if(stockScans == null) {
log.error("加载扫描器DB信息时扫描器信息数据库访问异常");
return;
}
Map<String, SiemensDBAddress> siemensDBAddressMap = new HashMap<>();
for (AppStockScan stockScan : stockScans) {
/* 读取状态地址 */
if(AppStringUtils.isNotEmpty(stockScan.getReadStatusAddress())) {
SiemensDBAddress readStatusDbAddress = new SiemensDBAddress();
readStatusDbAddress.setPlcId(stockScan.getPlcId());
readStatusDbAddress.setDbName("*扫码器数据"+stockScan.getScanId());
readStatusDbAddress.setDbAddress(stockScan.getReadStatusAddress());
siemensDBAddressMap.put(readStatusDbAddress.getDbName(), readStatusDbAddress);
}
/* 写入任务地址 */
if(AppStringUtils.isNotEmpty(stockScan.getWriteTaskAddress())) {
SiemensDBAddress writeTaskDbAddress = new SiemensDBAddress();
writeTaskDbAddress.setPlcId(stockScan.getPlcId());
writeTaskDbAddress.setDbName("*扫码器写任务"+stockScan.getScanId());
writeTaskDbAddress.setDbAddress(stockScan.getWriteTaskAddress());
siemensDBAddressMap.put(writeTaskDbAddress.getDbName(), writeTaskDbAddress);
}
}
log.info("初始化扫描器DB地址完成数据行数{}", siemensDBAddressMap.size());
siemensDBAddressMap.forEach((dbName, dbAddress) -> {
stringRedisTemplate.opsForValue().set(PlcCommonData.PLC_DB_ADDRESS_REDIS_KEY + ":" + dbName, AppStringUtils.objectToString(dbAddress));
});
}
/**
* 加载API基础资料
*/
@Override
public void loadApiKey() {
List<AppBaseApiInfo> baseApiInfos = baseApiInfoService.queryAll();
if(baseApiInfos == null) {
log.error("加载API基础资料时数据库访问异常");
return;
}
List<HttpAddressKeyItem> addressKeyList = new ArrayList<>();
for (AppBaseApiInfo baseApiInfo : baseApiInfos) {
HttpAddressKeyItem addressKeyItem = new HttpAddressKeyItem();
addressKeyItem.setKey(baseApiInfo.getApiKey());
addressKeyItem.setAddress(baseApiInfo.getAddress());
addressKeyItem.setFatherKey(baseApiInfo.getRootKey());
addressKeyList.add(addressKeyItem);
}
log.info("API基础资料加载完成数据行数{}", addressKeyList.size());
WebHttpClient.setAddressKey(addressKeyList);
}
}

View File

@ -0,0 +1,92 @@
package org.wcs.business.data.impl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.wcs.business.data.intf.IDataRecorder;
import org.wcs.constant.enums.database.EquipmentTypeEnum;
import org.wcs.mapper.intf.AppRecordApiRequestService;
import org.wcs.mapper.intf.AppRecordErrService;
import org.wcs.mapper.intf.AppRecordTaskMsgService;
import org.wcs.model.po.app.AppRecordApiRequest;
import org.wcs.model.po.app.AppRecordErr;
import org.wcs.model.po.app.AppRecordTaskMag;
import org.wcs.plugin.webHttpClient.model.HttpResponse;
import org.wcs.utils.AppStringUtils;
import org.wcs.utils.AppUUIDUtils;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 数据记录器
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class DataRecorder implements IDataRecorder {
private final AppRecordApiRequestService recordApiRequestDao; // 请求外部系统的数据表操作
private final AppRecordTaskMsgService recordStockMsgDao;
private final AppRecordErrService recordErrService;
/**
* 记录接口请求外部数据
* @param response API响应数据
*/
@Override
public void recordApiRequestData(HttpResponse response) {
AppRecordApiRequest recordApiRequest = new AppRecordApiRequest();
recordApiRequest.setRecordId(AppUUIDUtils.getNewUUID());
recordApiRequest.setRequestUrl(AppStringUtils.isEmptyOr(response.getUrl(), "-"));
recordApiRequest.setSuccess(response.isSuccess() ? 1 : 0);
recordApiRequest.setMethod(AppStringUtils.isEmptyOr(response.getMethod(), "-"));
recordApiRequest.setRequestMsg(AppStringUtils.isEmptyOr(response.getRequestText(), "-"));
recordApiRequest.setResponseMsg(AppStringUtils.isEmptyOr(response.getResponseText(), "-"));
recordApiRequest.setRequestTime(response.getRequestTime());
recordApiRequest.setResponseTime(response.getResponseTime());
recordApiRequest.setUseTime(BigDecimal.valueOf(response.getUseTime()));
recordApiRequest.setErrMsg((response.getErrText() == null ? "" : response.getErrText()) + (response.getException() == null ? "" : response.getException().getMessage()));
int insert = recordApiRequestDao.insert(recordApiRequest);
if(insert <= 0) {
log.error("记录接口请求外部数据失败:{}", AppStringUtils.objectToString(response));
}
}
/**
* 记录仓库任务数据
* @param taskId 任务ID
* @param vehicleNo 载具编号
* @param msg 仓库信息
*/
@Override
public void recordStockMsg(String taskId, String vehicleNo, String msg) {
AppRecordTaskMag recordTaskMsg = new AppRecordTaskMag();
recordTaskMsg.setRecordId(AppUUIDUtils.getNewUUID());
recordTaskMsg.setTaskId(AppStringUtils.isEmptyOr(taskId, "-"));
recordTaskMsg.setVehicleNo(AppStringUtils.isEmptyOr(vehicleNo, ""));
recordTaskMsg.setMessage(msg);
recordTaskMsg.setCreateTime(LocalDateTime.now());
boolean insert = recordStockMsgDao.insert(recordTaskMsg);
if(!insert) {
log.error("插入仓库任务信息异常,数据:{}", AppStringUtils.objectToString(recordTaskMsg));
}
}
/**
* 记录设备错误信息
* @param equipmentName 设备名称
* @param equipmentId 设备ID
* @param errorCode 错误码
*/
@Override
public void recordStackerErr(String equipmentName, String equipmentId, Integer errorCode) {
AppRecordErr recordErr = new AppRecordErr();
recordErr.setId(AppUUIDUtils.getNewUUID());
recordErr.setEquipmentName(AppStringUtils.isEmptyOr(equipmentName, "-"));
recordErr.setEquipmentId(AppStringUtils.isEmptyOr(equipmentId, "-"));
recordErr.setEquipmentType(EquipmentTypeEnum.STACKER.getCode());
recordErr.setErrCode(AppStringUtils.isEmptyOr(String.valueOf(errorCode), "-"));
recordErrService.insert(recordErr);
}
}

View File

@ -0,0 +1,58 @@
package org.wcs.business.data.intf;
import org.wcs.plugin.plc.model.SiemensDBAddress;
import java.util.List;
/**
* 数据加载接口
*/
public interface IDataLoader {
/**
* 加载 DB 地址 ---- 总方法
*/
void loadDBAddress();
/**
* 清除 DB 地址缓存
*/
void clearDBAddressRedis();
/**
* 加载堆垛机 DB 地址
*/
void loadStackerDBAddress();
/**
* 加载堆垛机站台 DB 地址
*/
void loadStackerStandDBAddress();
/**
* 加载托盘传送 DB 地址
*/
void loadTrayConveyDBAddress();
/**
* 加载输送 DB 地址
*/
void loadConveyDBAddress();
/**
* 加载DB数据库表地址
*/
void loadDbTableAddress();
/**
* 加载扫描 DB 地址
*/
void loadScannerDBAddress();
/**
* 加载 API KEY
*/
void loadApiKey();
}

View File

@ -0,0 +1,33 @@
package org.wcs.business.data.intf;
import org.wcs.plugin.webHttpClient.model.HttpResponse;
/**
* 数据记录接口
*/
public interface IDataRecorder {
/**
* 记录接口请求外部数据
* @param response 接口响应
*/
void recordApiRequestData(HttpResponse response);
/**
* 记录仓库任务数据
* @param taskId 任务ID
* @param vehicleNo 载具编号
* @param msg 仓库信息
*/
void recordStockMsg(String taskId, String vehicleNo, String msg);
/**
* 记录堆垛机错误信息
* @param equipmentName 设备名称
* @param equipmentId 设备ID
* @param errorCode 错误码
*/
void recordStackerErr(String equipmentName, String equipmentId, Integer errorCode);
}

View File

@ -0,0 +1,77 @@
package org.wcs.business.http;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.wcs.business.data.intf.IDataRecorder;
import org.wcs.constant.ConstantData;
import org.wcs.plugin.webHttpClient.intef.IHttpRequestEvent;
import org.wcs.plugin.webHttpClient.model.HttpRequest;
import org.wcs.plugin.webHttpClient.model.HttpResponse;
import org.wcs.utils.AppStringUtils;
import org.wcs.utils.AppUUIDUtils;
import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;
@Slf4j
@Service("HttpRequestRecord")
@RequiredArgsConstructor
public class HttpRequestRecord implements IHttpRequestEvent {
private final IDataRecorder dataRecorder; // 数据记录类
private final StringRedisTemplate redisTemplate; // redis字符串操作类
private static final String HTTP_REQUEST_CACHE = ConstantData.SYSTEM_NAME + ":HTTP:REQUEST";
/**
* 请求开始之前
* @param request 请求对象
*/
@Override
public void beforeRequest(HttpRequest request) {
}
/**
* 请求结束之后
* @param request 请求对象
* @param response 响应对象
*/
@Override
public void afterRequest(HttpRequest request, HttpResponse response) {
log.info("WCS对外请求请求数据{};响应数据:{}", request, response);
dataRecorder.recordApiRequestData(response);
String redisKey = HTTP_REQUEST_CACHE + ":" + request.getId();
if(request.isAutoRetry() && !response.isBaseDataError() && !response.isSuccess()) {
// 请求失败了
int retryCount = request.getRetryCount();
if(retryCount >= 5000) {
return;
}
request.setRetryCount(retryCount + 1);
LocalDateTime nextRetryTime = request.getNextRetryTime();
if(nextRetryTime == null) {
request.setNextRetryTime(LocalDateTime.now().plusSeconds(5));
} else {
int addRatio = Math.min(retryCount, 10);
request.setNextRetryTime(nextRetryTime.plusSeconds(addRatio * 5L));
}
request.setNextRetryTime(nextRetryTime);
if(AppStringUtils.isEmpty(request.getId())) {
request.setId(AppUUIDUtils.getNewUUID());
}
Long expireTime = redisTemplate.opsForValue().getOperations().getExpire(redisKey, TimeUnit.MINUTES); // 获取过期时间
if(expireTime != null && expireTime == -2 && request.getRetryCount() > 1) {
return; // 重发过但是已经过期了不再重发防止重发定时任务刚重发就过期这边再次添加缓存导致不间断发送
}
expireTime = expireTime == null || expireTime < 0 || request.getRetryCount() == 0 ? 180 : expireTime;
redisTemplate.opsForValue().set(redisKey, AppStringUtils.objectToString(request), expireTime, TimeUnit.MINUTES); // 设置过期时间
} else {
redisTemplate.delete(redisKey);
}
}
}

View File

@ -0,0 +1,56 @@
package org.wcs.business.led;
import org.springframework.stereotype.Service;
import org.wcs.model.bo.tuple.Tuple2;
import org.wcs.model.pojo.led.LedBaseInfo;
import org.wcs.model.pojo.led.LedDataDetail;
import org.wcs.model.pojo.led.LedShowData;
import org.wcs.plugin.lxLed.LxLedCommunication;
import java.util.List;
import java.util.Optional;
/**
* 灵信LED屏操作类
*/
@Service
public class LxLedOperation implements ledOperation {
/**
* 显示LED屏 ---- 基本方法
* @param ledShowData 显示LED屏的数据
* @return 显示结果
*/
@Override
public synchronized String showLedBase(LedShowData ledShowData) {
try {
LxLedCommunication.InitLedType(ledShowData.getLedType()); // 初始化卡型号
// 创建一个节目
long createResult = LxLedCommunication.CreateProgram(ledShowData.getLedWidth(), ledShowData.getLedHeight(), ledShowData.getColorType(), 0, 3);
if (createResult == 0) {
return "节目对象创建失败";
}
List<LedDataDetail> dataDetails = ledShowData.getDataDetails();
if(dataDetails == null || dataDetails.isEmpty()) {
return "显示内容为空";
}
for (int i = 0; i < dataDetails.size(); i++) {
LedDataDetail dataDetail = dataDetails.get(i);
int createAreaResult = LxLedCommunication.AddMultiLineTextToImageTextArea(createResult, 0, i, 0,
dataDetail.getContent(), dataDetail.getFontName(), dataDetail.getFontSize(), dataDetail.getFontColor(), 0, 0, 0,
dataDetail.getInStyle(), dataDetail.getNSpeed(), 0, dataDetail.getNSpeed(), dataDetail.isVCenterIs() ? 1 : 0);
if (createAreaResult != 0) {
return "区域创建失败,错误信息:" + LxLedCommunication.GetErrorCodeInfo(createAreaResult);
}
}
int sendResult = LxLedCommunication.NetWorkSend(ledShowData.getLedIp(), createResult);
if (sendResult != 0) {
return "发送失败,错误信息:" + LxLedCommunication.GetErrorCodeInfo(sendResult);
}
LxLedCommunication.DeleteProgram(createResult);
return "发送成功";
} catch (Exception e) {
return "LED屏显示失败异常信息" + e.getMessage();
}
}
}

View File

@ -0,0 +1,16 @@
package org.wcs.business.led;
import org.wcs.model.pojo.led.LedShowData;
/**
* LED屏的操作接口
*/
public interface ledOperation {
/**
* 显示LED屏基础方法
* @param ledShowData 显示数据
* @return 显示结果
*/
String showLedBase(LedShowData ledShowData);
}

View File

@ -0,0 +1,86 @@
package org.wcs.business.stacker;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.wcs.business.data.intf.IDataRecorder;
import org.wcs.constant.enums.business.SseEventTypeEnum;
import org.wcs.helper.SseHelper;
import org.wcs.model.bo.stacker.StackerStatusData;
import org.wcs.model.po.app.AppStackerInfo;
import org.wcs.plugin.plc.model.StackerStatus;
import org.wcs.utils.AppStringUtils;
import org.wcs.utils.AppThreadUtils;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
/**
* 堆垛机状态监控
*/
@Slf4j
@RequiredArgsConstructor
@Component
public class StackerStatusMonitor {
private final IDataRecorder dataRecorder; // 数据记录器
private final SseHelper sseHelper;
@Getter
private Map<Integer, StackerStatusData> stackerStatusDataMap;
/**
* 设置堆垛机状态
* @param stackerInfo 堆垛机信息
* @param status 堆垛机状态
*/
public void setStackerStatus(AppStackerInfo stackerInfo, StackerStatus status) {
if(stackerStatusDataMap == null) {
stackerStatusDataMap = new ConcurrentHashMap<>();
}
StackerStatusData stackerStatusData = stackerStatusDataMap.get(stackerInfo.getStackerId());
// 还没有存在这个堆垛机的信息
if(stackerStatusData == null) {
StackerStatusData stackerStatusDataItem = new StackerStatusData();
stackerStatusDataItem.setStackerId(stackerInfo.getStackerId());
stackerStatusDataItem.setStackerName(stackerInfo.getStackerName());
stackerStatusDataItem.setUpdateTime(LocalDateTime.now());
stackerStatusDataItem.setStatus(status);
stackerStatusDataMap.put(stackerInfo.getStackerId(), stackerStatusDataItem);
if(status.getErrCode() != 0) {
newStackerError(stackerInfo, Integer.valueOf(status.getErrCode()));
}
return;
}
// 已经存在这个堆垛机的信息
// ---- 检测是否报警
if(!Objects.equals(stackerStatusData.getStatus().getErrCode(), status.getErrCode()) && status.getErrCode() != 0) {
newStackerError(stackerInfo, Integer.valueOf(status.getErrCode()));
}
// 插入新信息
stackerStatusData.setStackerId(stackerInfo.getStackerId());
stackerStatusData.setStackerName(stackerInfo.getStackerName());
stackerStatusData.setUpdateTime(LocalDateTime.now());
stackerStatusData.setStatus(status);
stackerStatusDataMap.put(stackerInfo.getStackerId(), stackerStatusData);
}
/**
* 堆垛机报警
* @param stackerInfo 堆垛机信息
* @param errorCode 报警码
*/
private void newStackerError(AppStackerInfo stackerInfo, Integer errorCode) {
AppThreadUtils.startNewThread(() -> {
log.info("堆垛机{}报警,报警码:{}", stackerInfo.getStackerName(), errorCode);
dataRecorder.recordStackerErr(stackerInfo.getStackerName(), String.valueOf(stackerInfo.getStackerId()), errorCode);
sseHelper.SendEventToAll(SseEventTypeEnum.SYSTEM_INFO, String.format("%s 发生报警:%d", stackerInfo.getStackerName(), errorCode));
});
}
}

View File

@ -0,0 +1,242 @@
package org.wcs.business.stacker;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.wcs.constant.enums.plc.StackerTaskTypeEnum;
import org.wcs.mapper.intf.AppStackerLocationService;
import org.wcs.mapper.intf.AppStackerStandService;
import org.wcs.model.po.app.AppStackerLocation;
import org.wcs.model.po.app.AppStackerStand;
import org.wcs.plugin.plc.model.StackerTask;
import org.wcs.utils.AppStringUtils;
import org.wcs.utils.AppObjectUtils;
import java.util.List;
/**
* 堆垛机任务创建者
*/
@Component
@RequiredArgsConstructor
public class StackerTaskCreator {
private final AppStackerLocationService stackerLocationDao;
private final AppStackerStandService stackerStandDao;
/**
* 创建堆垛机入库任务
* @param plcTaskId PLC任务ID
* @param origin 起点
* @param destination 终点
* @param vehicleNo 载具号
* @param vehicleSize 载具大小
* @param weight 载具重量
* @return 堆垛机入库任务
*/
public StackerTask createStackerInTask(Integer plcTaskId, String origin, String destination, String vehicleNo, Short vehicleSize, Short weight)
{
try {
if(plcTaskId == null || plcTaskId <= 0 || AppStringUtils.isEmpty(origin) || AppStringUtils.isEmpty(destination)) {
return null;
}
List<AppStackerStand> stackerStands = stackerStandDao.queryStandInfo(origin);
if(stackerStands == null || stackerStands.isEmpty()) {
return null;
}
List<AppStackerLocation> destinationLocations = stackerLocationDao.queryStackerLocationById(destination);
if(destinationLocations == null || destinationLocations.isEmpty()) {
return null;
}
AppStackerStand originLocation = stackerStands.getFirst(); // 起点信息
String[] originLocationData = originLocation.getStandLocation().replace("(","").replace(")","").split(",");
AppStackerLocation destinationLocation = destinationLocations.getFirst(); // 终点信息
StackerTask task = new StackerTask();
task.setPlcTaskId(plcTaskId);
task.setTaskType(StackerTaskTypeEnum.IN.getCode().shortValue());
task.setGetStand(AppStringUtils.forceToShort(origin));
task.setGetLaneId(originLocation.getLaneId().shortValue());
task.setSetDepth(destinationLocation.getLaneId().shortValue());
task.setSetStand(AppStringUtils.forceToShort(destination));
task.setGetRow(AppObjectUtils.stringToInteger(originLocationData.length > 0, originLocationData[0]).shortValue());
task.setGetLine(AppObjectUtils.stringToInteger(originLocationData.length > 1, originLocationData[1]).shortValue());
task.setGetLayer(AppObjectUtils.stringToInteger(originLocationData.length > 2, originLocationData[2]).shortValue());
task.setGetDepth(AppObjectUtils.stringToInteger(originLocationData.length > 3, originLocationData[3]).shortValue());
task.setSetRow(destinationLocation.getLRow().shortValue());
task.setSetLine(destinationLocation.getLLine().shortValue());
task.setSetLayer(destinationLocation.getLLayer().shortValue());
task.setSetDepth(destinationLocation.getLDepth().shortValue());
task.setVehicleSize(vehicleSize);
task.setWeight(weight);
task.setVehicleNo(AppStringUtils.forceToInt(vehicleNo));
return task;
} catch (Exception e) {
return null;
}
}
/**
* 创建堆垛机入库任务
* @param plcTaskId PLC任务ID
* @param origin 起点
* @param destination 终点
* @param vehicleNo 载具号
* @return 堆垛机入库任务
*/
public StackerTask createStackerInTask(Integer plcTaskId, String origin, String destination, String vehicleNo) {
return createStackerInTask(plcTaskId, origin, destination, vehicleNo, (short) 0, (short) 0);
}
/**
* 创建堆垛机入口异常搬出任务
* @param plcTaskId PLC任务ID
* @param origin 起点
* @param destination 终点
* @param vehicleNo 载具号
* @return 堆垛机错误任务
*/
public StackerTask createStackerErrTask(Integer plcTaskId, String origin, String destination, String vehicleNo) {
try {
if(plcTaskId == null || plcTaskId <= 0 || AppStringUtils.isEmpty(origin) || AppStringUtils.isEmpty(destination)) {
return null;
}
List<AppStackerStand> stackerOriginStands = stackerStandDao.queryStandInfo(origin);
if(stackerOriginStands == null || stackerOriginStands.isEmpty()) {
return null;
}
List<AppStackerStand> stackerDestinationStands = stackerStandDao.queryStandInfo(destination);
if(stackerDestinationStands == null || stackerDestinationStands.isEmpty()) {
return null;
}
AppStackerStand originLocation = stackerOriginStands.getFirst(); // 起点信息
String[] originLocationData = originLocation.getStandLocation().replace("(","").replace(")","").split(",");
AppStackerStand destinationLocation = stackerOriginStands.getFirst(); // 终点信息
String[] destinationLocationData = originLocation.getStandLocation().replace("(","").replace(")","").split(",");
StackerTask task = new StackerTask();
task.setPlcTaskId(plcTaskId);
task.setTaskType(StackerTaskTypeEnum.ERROR.getCode().shortValue());
task.setGetStand(AppStringUtils.forceToShort(origin));
task.setGetLaneId(originLocation.getLaneId().shortValue());
task.setSetDepth(destinationLocation.getLaneId().shortValue());
task.setSetStand(AppStringUtils.forceToShort(destination));
task.setGetRow(AppObjectUtils.stringToInteger(originLocationData.length > 0, originLocationData[0]).shortValue());
task.setGetLine(AppObjectUtils.stringToInteger(originLocationData.length > 1, originLocationData[1]).shortValue());
task.setGetLayer(AppObjectUtils.stringToInteger(originLocationData.length > 2, originLocationData[2]).shortValue());
task.setGetDepth(AppObjectUtils.stringToInteger(originLocationData.length > 3, originLocationData[3]).shortValue());
task.setSetRow(AppObjectUtils.stringToInteger(destinationLocationData.length > 0, destinationLocationData[0]).shortValue());
task.setSetLine(AppObjectUtils.stringToInteger(destinationLocationData.length > 1, destinationLocationData[1]).shortValue());
task.setSetLayer(AppObjectUtils.stringToInteger(destinationLocationData.length > 2, destinationLocationData[2]).shortValue());
task.setSetDepth(AppObjectUtils.stringToInteger(destinationLocationData.length > 3, destinationLocationData[3]).shortValue());
task.setVehicleSize((short)0);
task.setWeight((short)0);
task.setVehicleNo(AppStringUtils.forceToInt(vehicleNo));
return task;
} catch (Exception e) {
return null;
}
}
/**
* 创建堆垛机出库任务
* @param plcTaskId PLC任务ID
* @param origin 起点
* @param destination 终点
* @param vehicleNo 载具号
* @param vehicleSize 载具尺寸
* @param weight 载具重量
* @return 堆垛机出库任务
*/
public StackerTask createStackerOutTask(Integer plcTaskId, String origin, String destination, String vehicleNo, Short vehicleSize, Short weight)
{
try {
if(plcTaskId == null || plcTaskId <= 0 || AppStringUtils.isEmpty(origin) || AppStringUtils.isEmpty(destination)) {
return null;
}
// 起点
List<AppStackerLocation> originLocations = stackerLocationDao.queryStackerLocationById(origin);
if(originLocations == null || originLocations.isEmpty()) {
return null;
}
// 终点
List<AppStackerStand> stackerStands = stackerStandDao.queryStandInfo(destination);
if(stackerStands == null || stackerStands.isEmpty()) {
return null;
}
AppStackerStand destinationLocation = stackerStands.getFirst(); // 终点信息
String[] destinationLocationData = destinationLocation.getStandLocation().replace("(","").replace(")","").split(",");
AppStackerLocation originLocation = originLocations.getFirst(); // 起点信息
StackerTask task = new StackerTask();
task.setPlcTaskId(plcTaskId);
task.setTaskType(StackerTaskTypeEnum.OUT.getCode().shortValue());
task.setGetStand(AppStringUtils.forceToShort(origin));
task.setGetLaneId(originLocation.getLaneId().shortValue());
task.setSetDepth(destinationLocation.getLaneId().shortValue());
task.setSetStand(AppStringUtils.forceToShort(destination));
task.setGetRow(originLocation.getLRow().shortValue());
task.setGetLine(originLocation.getLLine().shortValue());
task.setGetLayer(originLocation.getLLayer().shortValue());
task.setGetDepth(originLocation.getLDepth().shortValue());
task.setSetRow(AppObjectUtils.stringToInteger(destinationLocationData.length > 0, destinationLocationData[0]).shortValue());
task.setSetLine(AppObjectUtils.stringToInteger(destinationLocationData.length > 1, destinationLocationData[1]).shortValue());
task.setSetLayer(AppObjectUtils.stringToInteger(destinationLocationData.length > 2, destinationLocationData[2]).shortValue());
task.setSetDepth(AppObjectUtils.stringToInteger(destinationLocationData.length > 3, destinationLocationData[3]).shortValue());
task.setVehicleSize(vehicleSize);
task.setWeight(weight);
task.setVehicleNo(AppStringUtils.forceToInt(vehicleNo));
return task;
} catch (Exception e) {
return null;
}
}
/**
* 创建堆垛机移库任务
* @param plcTaskId PLC任务ID
* @param origin 起点
* @param destination 终点
* @param vehicleNo 载具号
* @param vehicleSize 载具尺寸
* @param weight 载具重量
* @return 堆垛机移库任务
*/
public StackerTask createStackerMoveTask(Integer plcTaskId, String origin, String destination, String vehicleNo, Short vehicleSize, Short weight) {
try {
if(plcTaskId == null || plcTaskId <= 0 || AppStringUtils.isEmpty(origin) || AppStringUtils.isEmpty(destination)) {
return null;
}
// 起点
List<AppStackerLocation> originLocations = stackerLocationDao.queryStackerLocationById(origin);
if(originLocations == null || originLocations.isEmpty()) {
return null;
}
// 终点
List<AppStackerLocation> destinationLocations = stackerLocationDao.queryStackerLocationById(destination);
if(destinationLocations == null || destinationLocations.isEmpty()) {
return null;
}
AppStackerLocation destinationLocation = destinationLocations.getFirst(); // 终点信息
AppStackerLocation originLocation = originLocations.getFirst(); // 起点信息
StackerTask task = new StackerTask();
task.setPlcTaskId(plcTaskId);
task.setTaskType(StackerTaskTypeEnum.MOVE.getCode().shortValue());
task.setGetStand(AppStringUtils.forceToShort(origin));
task.setGetLaneId(originLocation.getLaneId().shortValue());
task.setSetDepth(destinationLocation.getLaneId().shortValue());
task.setSetStand(AppStringUtils.forceToShort(destination));
task.setGetRow(originLocation.getLRow().shortValue());
task.setGetLine(originLocation.getLLine().shortValue());
task.setGetLayer(originLocation.getLLayer().shortValue());
task.setGetDepth(originLocation.getLDepth().shortValue());
task.setSetRow(destinationLocation.getLRow().shortValue());
task.setSetLine(destinationLocation.getLLine().shortValue());
task.setSetLayer(destinationLocation.getLLayer().shortValue());
task.setSetDepth(destinationLocation.getLDepth().shortValue());
task.setVehicleSize(vehicleSize);
task.setWeight(weight);
task.setVehicleNo(AppStringUtils.forceToInt(vehicleNo));
return task;
} catch (Exception e) {
return null;
}
}
}

View File

@ -0,0 +1,570 @@
package org.wcs.business.stacker.impl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.wcs.business.stacker.StackerTaskCreator;
import org.wcs.business.stacker.intf.IExecuteStackerTaskService;
import org.wcs.business.stock.intf.IStockSingleTaskExecuteEvent;
import org.wcs.constant.ConstantData;
import org.wcs.constant.enums.business.SseEventTypeEnum;
import org.wcs.constant.enums.common.TrueOrFalseEnum;
import org.wcs.constant.enums.database.StackerStandInTypeEnum;
import org.wcs.constant.enums.database.StockSingleTaskStatusEnum;
import org.wcs.constant.enums.database.StockSingleTaskTypeEnum;
import org.wcs.helper.PlcTaskIdHelper;
import org.wcs.helper.SseHelper;
import org.wcs.mapper.intf.AppStackerStandService;
import org.wcs.mapper.intf.AppStockSingleTaskService;
import org.wcs.model.bo.tuple.Tuple2;
import org.wcs.model.po.app.AppStackerInfo;
import org.wcs.model.po.app.AppStackerStand;
import org.wcs.model.po.app.AppStockSingleTask;
import org.wcs.plugin.plc.PlcCommunicationFactory;
import org.wcs.plugin.plc.model.StackerTask;
import org.wcs.plugin.plc.model.TaskConveyStatus;
import org.wcs.utils.AppListUtils;
import org.wcs.utils.AppStringUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* 执行堆垛机任务方法
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class ExecuteStackerTaskService implements IExecuteStackerTaskService {
private final AppStockSingleTaskService stockSingleTaskDao; // 任务表操作类
private final AppStackerStandService stackerStandDao; // 堆垛机站台表操作类
private final StackerTaskCreator stackerTaskCreator; // 堆垛机任务创建类
private final PlcTaskIdHelper plcTaskIdHelper; // PLC任务ID生成类
private final IStockSingleTaskExecuteEvent stockSingleTaskExecuteEvent; // 独立任务执行 事件
private final SseHelper sseHelper; // SSE连接
/**
* 执行堆垛机入库任务
* @param stackerInfo 堆垛机信息
* @return 是否执行了任务
*/
@Override
public boolean executeStackerInTask(AppStackerInfo stackerInfo) {
List<AppStockSingleTask> stockSingleTasks = stockSingleTaskDao.queryTask(StockSingleTaskStatusEnum.CREATE.getCode());
if(stockSingleTasks == null || stockSingleTasks.isEmpty()) {
return false; // 查询失败或者没有任务不执行下面动作
}
// 查找这个堆垛机所有的入库站台
List<AppStackerStand> stackerStands = stackerStandDao.queryStackerStand(stackerInfo.getStackerId());
if(stackerStands == null || stackerStands.isEmpty()) {
return false; // 堆垛机没有站台不执行下面动作
}
List<AppStackerStand> stackerInStands = stackerStands.stream().filter(stand -> stand.getAllowIn() == TrueOrFalseEnum.TRUE.getCode()).toList();
if(stackerInStands.isEmpty()) {
return false; // 堆垛机没有入库站台不执行下面动作
}
for (AppStackerStand stackerInStand : stackerInStands) {
if(stackerInStand.getInType().equals(StackerStandInTypeEnum.CHECK_CODE.getCode())) {
// 根据站台条码入库
var isexe = executeStackerCheckCodeTask(stackerInfo, stackerInStand);
if(isexe) {
return true;
}
}
if(stackerInStand.getInType().equals(StackerStandInTypeEnum.IMMEDIATELY.getCode())) {
// 站台检测有货立即入库
var isexe = executeStackerImmediatelyTask(stackerInfo, stackerInStand);
if(isexe) {
return true;
}
}
}
return false;
}
/**
* 执行堆垛机出库任务
* @param stackerInfo 堆垛机信息
* @return 是否执行了任务
*/
@Override
public boolean executeStackerOutTask(AppStackerInfo stackerInfo) {
Integer stackerId = stackerInfo.getStackerId();
/* 判断出库最高优先级和移库最高优先级哪个高 */
List<AppStockSingleTask> stockSingleOutTasks = stockSingleTaskDao.queryStackerTask(stackerId, StockSingleTaskStatusEnum.CREATE.getCode(), StockSingleTaskTypeEnum.STACKER_OUT.getCode());
if(stockSingleOutTasks == null || stockSingleOutTasks.isEmpty()) {
return false; // 查询失败或者没有任务不执行下面动作
}
Integer outPriority = stockSingleOutTasks.getFirst().getPriority();
List<AppStockSingleTask> stockSingleMoveTasks = stockSingleTaskDao.queryStackerTask(stackerId, StockSingleTaskStatusEnum.CREATE.getCode(), StockSingleTaskTypeEnum.STACKER_MOVE.getCode());
Integer movePriority = stockSingleMoveTasks == null || stockSingleMoveTasks.isEmpty() ? 0 : stockSingleMoveTasks.getFirst().getPriority();
if(outPriority.compareTo(movePriority) < 0) {
// 出库优先级低于移库优先级出库暂停执行
return false;
}
/* 校验货叉状态 */
String forkStatus = stackerInfo.getForkStatus();
if(AppStringUtils.isEmpty(forkStatus)) {
return false;
}
// 查询站台信息
List<AppStackerStand> stackerStands = stackerStandDao.queryStackerStand(stackerId);
if(stackerStands == null || stackerStands.isEmpty()) {
return false; // 堆垛机站台信息查询失败
}
List<AppStackerStand> outStands = stackerStands.stream().filter(stand -> stand.getAllowOut() == TrueOrFalseEnum.TRUE.getCode()).toList();
if(outStands.isEmpty()) {
return false; // 堆垛机没有出库站台
}
/* 处理拉取到的任务 */
// 查找需要执行的任务
Tuple2<List<AppStockSingleTask>, AppStackerStand> needOutTaskListTuple = getExecuteStackerOutTasks(stockSingleOutTasks, outStands, forkStatus.length());
List<AppStockSingleTask> needOutTaskList = needOutTaskListTuple.item1;
AppStackerStand standInfo = needOutTaskListTuple.item2;
if(needOutTaskList == null || needOutTaskList.isEmpty()) {
return false; // 没有要执行的
}
List<AppStockSingleTask> executeTasks = new ArrayList<>(); // 将要执行的任务
List<StackerTask> tasks = new ArrayList<>(); // 即将要写入堆垛机的任务
for (int i = 0; i < forkStatus.length(); i++) {
if (needOutTaskList.size() <= i || !String.valueOf(forkStatus.charAt(i)).equals(String.valueOf(TrueOrFalseEnum.TRUE.getCode()))) {
tasks.add(null);
continue;
}
AppStockSingleTask stockSingleTask = needOutTaskList.get(i); // 获取该游标的任务
StackerTask task = stackerTaskCreator.createStackerOutTask(stockSingleTask.getPlcTaskId(), stockSingleTask.getOrigin(), standInfo.getStandId(), stockSingleTask.getVehicleNo(), stockSingleTask.getVehicleSize().shortValue(), stockSingleTask.getWeight().shortValue());
if(task == null) {
tasks.add(null);
} else {
tasks.add(task);
executeTasks.add(stockSingleTask);
}
}
// 校验任务是不是都是 null 或者执行的任务是空
if(tasks.stream().allMatch(Objects::isNull) || executeTasks.isEmpty()) {
return false;
}
// 写入任务
StackerTask[] tasksArray = tasks.toArray(new StackerTask[0]);
String writeStackerTaskErrTask = PlcCommunicationFactory.getPlcCommunicationByPlcId(stackerInfo.getPlcId()).writeStackerTask(stackerId, tasksArray);
if(AppStringUtils.isNotEmpty(writeStackerTaskErrTask)) {
log.info("堆垛机写入出库任务失败:{};写入的任务为:{}", writeStackerTaskErrTask, AppListUtils.listObjectToSting(tasks));
sseHelper.SendEventToAll(SseEventTypeEnum.RUNNING_INFO, "堆垛机写入出库任务失败:" + tasks.getFirst().getPlcTaskId() + "" + tasks.size() + "个任务");
return false;
}
// 堆垛机写入任务成功
log.info("堆垛机写入出库任务成功:{}", AppListUtils.listObjectToSting(tasks));
sseHelper.SendEventToAll(SseEventTypeEnum.RUNNING_INFO, "堆垛机写入出库任务:" + tasks.getFirst().getPlcTaskId() + "" + tasks.size() + "个任务");
// 触发任务开始执行事件
for (AppStockSingleTask executeTask : executeTasks) {
stockSingleTaskExecuteEvent.taskStart(executeTask, "任务开始执行");
}
return true;
}
/**
* 获取堆垛机出库任务
* @param stockSingleTaskList 堆垛机任务列表
* @param outStands 出库站台列表
* @param selectNum 需要选择的数量
* @return 堆垛机出库任务列表
*/
private Tuple2<List<AppStockSingleTask>, AppStackerStand> getExecuteStackerOutTasks(List<AppStockSingleTask> stockSingleTaskList,List<AppStackerStand> outStands, int selectNum) {
List<AppStockSingleTask> returnList = new ArrayList<>(); // 需要返回的任务
AppStackerStand standInfo = null; // 本次的出库站台
for (AppStockSingleTask stockSingleTaskItem : stockSingleTaskList) {
if(returnList.size() >= selectNum) {
return new Tuple2<>(returnList, standInfo); // 已经选择够数量了
}
if(AppStringUtils.isEmpty(stockSingleTaskItem.getDestination())) { // 纯空的
// 获取一个可以出库的站台
if(standInfo == null) {
standInfo = checkOutStandCanUse(outStands);
}
if(standInfo == null) {
return new Tuple2<>(returnList, standInfo); // 这种随便选的都没有满足条件的就表示所有的都不满足直接返回了
}
stockSingleTaskItem.setDestination(standInfo.getStandId());
returnList.add(stockSingleTaskItem);
continue;
}
if(stockSingleTaskItem.getDestination().startsWith("?")) { // 问号开头的表示备选的站台已经确定只能在这几个站台内选择
String[] spareStands = stockSingleTaskItem.getDestination().replace("?", "").split(";"); // 备选站台
AppStackerStand finalStandInfo = standInfo;
// 如果本次选择已经确定了站台并且此任务也可以选择此站台则直接将此任务加入
if(finalStandInfo != null && Arrays.stream(spareStands).anyMatch(standId -> standId.equals(finalStandInfo.getStandId()))) {
stockSingleTaskItem.setDestination(standInfo.getStandId());
returnList.add(stockSingleTaskItem);
continue;
}
if(standInfo == null) {
standInfo = checkOutStandCanUseWithStandIdList(Arrays.stream(spareStands).toList());
if(standInfo == null) {
continue; // 没找到合适位置
}
// 找到了合适的位置
stockSingleTaskItem.setDestination(standInfo.getStandId());
returnList.add(stockSingleTaskItem);
continue;
}
// 本次寻找的站台已经确定并且此任务可以出库的站台不是已经确定的站台
continue;
}
// 任务终点内已经确定站台的出库
String destination = stockSingleTaskItem.getDestination(); // 终点站台
if(standInfo != null && !destination.equals(standInfo.getStandId())) {
continue; // 站台已经确定但是终点站台和站台不匹配
}
if(standInfo == null) {
standInfo = checkOutStandCanUseWithStandIdList(List.of(destination)); // 站台没确定此处主要检验一下是否能出库
}
if(standInfo == null) {
continue; // 站台不存在或者不允许出库
}
stockSingleTaskItem.setDestination(standInfo.getStandId());
returnList.add(stockSingleTaskItem);
continue;
}
return new Tuple2<>(returnList, standInfo);
}
/**
* 执行堆垛机移库任务
* @param stackerInfo 堆垛机信息
* @return 是否执行了任务
*/
@Override
public boolean executeStackerMoveTask(AppStackerInfo stackerInfo) {
Integer stackerId = stackerInfo.getStackerId();
/* 判断移库最高优先级和出库最高优先级哪个高 */
List<AppStockSingleTask> stockSingleMoveTasks = stockSingleTaskDao.queryStackerTask(stackerId, StockSingleTaskStatusEnum.CREATE.getCode(), StockSingleTaskTypeEnum.STACKER_MOVE.getCode());
if(stockSingleMoveTasks == null || stockSingleMoveTasks.isEmpty()) {
return false; // 查询失败或者没有任务不执行下面动作
}
Integer movePriority = stockSingleMoveTasks.getFirst().getPriority();
List<AppStockSingleTask> stockSingleOutTasks = stockSingleTaskDao.queryStackerTask(stackerId, StockSingleTaskStatusEnum.CREATE.getCode(), StockSingleTaskTypeEnum.STACKER_OUT.getCode());
Integer outPriority = stockSingleOutTasks == null || stockSingleOutTasks.isEmpty() ? 0 : stockSingleOutTasks.getFirst().getPriority();
if(movePriority.compareTo(outPriority) < 0) {
// 移库优先级低于出库优先级移库暂停执行
return false;
}
/* 校验货叉状态 */
String forkStatus = stackerInfo.getForkStatus();
if(AppStringUtils.isEmpty(forkStatus)) {
return false;
}
List<AppStockSingleTask> executeTasks = new ArrayList<>(); // 执行的任务
List<StackerTask> tasks = new ArrayList<>(); // 即将要写入堆垛机的任务
for(int i = 0; i < forkStatus.length(); i++) {
if (!String.valueOf(forkStatus.charAt(i)).equals(String.valueOf(TrueOrFalseEnum.TRUE.getCode())) || stockSingleMoveTasks.size() <= i) {
tasks.add(null);
continue;
}
AppStockSingleTask stockSingleTask = stockSingleMoveTasks.get(i); // 移库任务
StackerTask task = stackerTaskCreator.createStackerMoveTask(stockSingleTask.getPlcTaskId(), stockSingleTask.getOrigin(), stockSingleTask.getDestination(), stockSingleTask.getVehicleNo(), stockSingleTask.getVehicleSize().shortValue(), stockSingleTask.getWeight().shortValue());
if(task == null) {
tasks.add(null);
} else {
tasks.add(task);
executeTasks.add(stockSingleTask);
}
}
// 校验任务是不是都是 null
if(tasks.stream().allMatch(Objects::isNull) || executeTasks.isEmpty()) {
return false;
}
// 写入任务
StackerTask[] tasksArray = tasks.toArray(new StackerTask[0]);
String writeStackerTaskErrTask = PlcCommunicationFactory.getPlcCommunicationByPlcId(stackerInfo.getPlcId()).writeStackerTask(stackerId, tasksArray);
if(AppStringUtils.isNotEmpty(writeStackerTaskErrTask)) {
log.info("堆垛机写入移库任务失败:{};写入的任务为:{}", writeStackerTaskErrTask, AppListUtils.listObjectToSting(tasks));
sseHelper.SendEventToAll(SseEventTypeEnum.RUNNING_INFO, "堆垛机写入移库任务失败:" + tasks.getFirst().getPlcTaskId() + "" + tasks.size() + "个任务");
return false;
}
// 堆垛机写入任务成功
log.info("堆垛机写入移库任务成功:{}", AppListUtils.listObjectToSting(tasks));
sseHelper.SendEventToAll(SseEventTypeEnum.RUNNING_INFO, "堆垛机写入移库任务:" + tasks.getFirst().getPlcTaskId() + "" + tasks.size() + "个任务");
// 触发任务开始执行事件
for (AppStockSingleTask executeTask : executeTasks) {
stockSingleTaskExecuteEvent.taskStart(executeTask, "任务开始执行");
}
return true;
}
/**
* 执行堆垛机入库任务 ---- 检验入库站台条码
* @param stackerInfo 堆垛机信息
* @param standInfo 站台信息
* @return 是否执行了任务
*/
private boolean executeStackerCheckCodeTask(AppStackerInfo stackerInfo, AppStackerStand standInfo) {
Integer stackerId = stackerInfo.getStackerId();
/* 校验货叉状态 */
String forkStatus = stackerInfo.getForkStatus();
if(AppStringUtils.isEmpty(forkStatus)) {
return false;
}
Tuple2<String, List<TaskConveyStatus>> multiConveyStatusResult = PlcCommunicationFactory.getPlcCommunicationByPlcId(standInfo.getPlcId()).readTaskMultiConveyStatus(standInfo.getStandId());
if(AppStringUtils.isNotEmpty(multiConveyStatusResult.item1)) {
return false; // 读取站台信息存在异常
}
List<TaskConveyStatus> conveyStatuses = multiConveyStatusResult.item2;
if(conveyStatuses.size() != forkStatus.length()) {
return false; // 站台信息数量和货叉数量不一致
}
List<AppStockSingleTask> executeTasks = new ArrayList<>(); // 执行的任务
List<StackerTask> tasks = new ArrayList<>(); // 即将要写入堆垛机的任务
for (int i = 0; i < conveyStatuses.size(); i++) {
if (!String.valueOf(forkStatus.charAt(i)).equals(String.valueOf(TrueOrFalseEnum.TRUE.getCode()))) {
tasks.add(null);
continue;
}
TaskConveyStatus conveyStatus = conveyStatuses.get(i);
String vehicleNo = conveyStatus.getVehicleNo(); // 载具号
if(conveyStatus.getPlcTaskId() != 0 || conveyStatus.getDeviceStatus().intValue() != 5 || conveyStatus.getTaskAllow().intValue() != TrueOrFalseEnum.TRUE.getCode()
|| conveyStatus.getSensor().intValue() == TrueOrFalseEnum.TRUE.getCode() || AppStringUtils.isEmpty(vehicleNo)) {
// 任务号不为0设备状态不为5任务不允许载具号为空 ---- 这些情况不搬运
tasks.add(null);
continue;
}
/* 检查这个载具号的入库任务 */
List<AppStockSingleTask> stockSingleTasks = vehicleNo.equals(ConstantData.NO_READ) ? new ArrayList<>() : stockSingleTaskDao.queryStackerTask(stackerId, vehicleNo, StockSingleTaskStatusEnum.CREATE.getCode(), StockSingleTaskTypeEnum.STACKER_IN.getCode());
if(stockSingleTasks == null) { // 数据库查询失败
tasks.add(null);
continue;
}
if(stockSingleTasks.isEmpty()) { // 这个载具没有任务或者读码失败
AppStackerStand canUseOutStand = getCanUseOutStand(stackerId); // 获取一个出库站台
if(canUseOutStand == null) { // 没有可用的出库站台
return false;
}
Integer plcTaskId = plcTaskIdHelper.newTaskId(); // 获取一个 PLC任务ID
if(plcTaskId == null) {
return false;
}
// 构造异常搬出的任务
StackerTask stackerErrTask = stackerTaskCreator.createStackerErrTask(plcTaskId, standInfo.getStandName(), canUseOutStand.getStandName(), vehicleNo);
tasks.add(stackerErrTask);
log.info("堆垛机:{} 载具:{} 在:{} 找不到入库任务,生成异常任务,搬运到:{}", stackerId, vehicleNo, standInfo.getStandId(), canUseOutStand.getStandId());
continue;
}
// 载具存在入库任务
AppStockSingleTask stockSingleTask = stockSingleTasks.getFirst(); // 要执行的入库任务
StackerTask stackerInTask = stackerTaskCreator.createStackerInTask(stockSingleTask.getPlcTaskId(), standInfo.getStandName(), stockSingleTask.getDestination(), vehicleNo, stockSingleTask.getVehicleSize().shortValue(), stockSingleTask.getWeight().shortValue());
if(stackerInTask == null) {
tasks.add(null);
} else {
tasks.add(stackerInTask);
executeTasks.add(stockSingleTask);
}
}
// 校验任务是不是都是 null
if(tasks.stream().allMatch(Objects::isNull) || executeTasks.isEmpty()) {
return false;
}
// 写入堆垛机任务
StackerTask[] tasksArray = tasks.toArray(new StackerTask[0]);
String writeStackerTaskErrTask = PlcCommunicationFactory.getPlcCommunicationByPlcId(stackerInfo.getPlcId()).writeStackerTask(stackerId, tasksArray);
if(AppStringUtils.isNotEmpty(writeStackerTaskErrTask)) {
log.info("堆垛机写入入库任务失败:{};写入的任务为:{}", writeStackerTaskErrTask, AppListUtils.listObjectToSting(tasks));
return false;
}
// 堆垛机写入任务成功
log.info("堆垛机写入入库任务成功:{}", AppListUtils.listObjectToSting(tasks));
// 触发任务开始执行事件
for (AppStockSingleTask executeTask : executeTasks) {
stockSingleTaskExecuteEvent.taskStart(executeTask, "任务开始执行");
}
return true;
}
/**
* 执行堆垛机入库任务 ---- 立即入库
* @param stackerInfo 堆垛机信息
* @param standInfo 站台信息
* @return 是否执行了任务
*/
private boolean executeStackerImmediatelyTask(AppStackerInfo stackerInfo, AppStackerStand standInfo) {
Integer stackerId = stackerInfo.getStackerId();
/* 查询入库任务 */
List<AppStockSingleTask> stockSingleTasks = stockSingleTaskDao.queryStackerTask(stackerId, StockSingleTaskStatusEnum.CREATE.getCode(), StockSingleTaskTypeEnum.STACKER_IN.getCode());
if(stockSingleTasks == null || stockSingleTasks.isEmpty()) {
return false;
}
/* 检查各种任务优先级 */
Integer inMaxPriority = stockSingleTasks.getFirst().getPriority(); // 入库最高优先级
List<AppStockSingleTask> stockSingleOutTasks = stockSingleTaskDao.queryStackerTask(stackerId, StockSingleTaskStatusEnum.CREATE.getCode(), StockSingleTaskTypeEnum.STACKER_OUT.getCode());
Integer outPriority = stockSingleOutTasks == null || stockSingleOutTasks.isEmpty() ? 0 : stockSingleOutTasks.getFirst().getPriority(); // 出库高优先级
List<AppStockSingleTask> stockSingleMoveTasks = stockSingleTaskDao.queryStackerTask(stackerId, StockSingleTaskStatusEnum.CREATE.getCode(), StockSingleTaskTypeEnum.STACKER_MOVE.getCode());
Integer movePriority = stockSingleMoveTasks == null || stockSingleMoveTasks.isEmpty() ? 0 : stockSingleMoveTasks.getFirst().getPriority(); // 移库最高优先级
if(inMaxPriority.compareTo(outPriority) < 0 || inMaxPriority.compareTo(movePriority) < 0) {
return false; // 入库优先级低先不执行
}
/* 校验货叉状态 */
String forkStatus = stackerInfo.getForkStatus();
if(AppStringUtils.isEmpty(forkStatus)) {
return false;
}
// 读取站台信息这里考虑多工位会返回多个单工位只会返回一个
Tuple2<String, List<TaskConveyStatus>> multiConveyStatusResult = PlcCommunicationFactory.getPlcCommunicationByPlcId(standInfo.getPlcId()).readTaskMultiConveyStatus(standInfo.getStandId());
if(AppStringUtils.isNotEmpty(multiConveyStatusResult.item1)) {
return false; // 读取站台信息存在异常
}
List<TaskConveyStatus> conveyStatuses = multiConveyStatusResult.item2;
if(conveyStatuses.size() != forkStatus.length()) {
return false; // 站台信息数量和货叉数量不一致
}
List<AppStockSingleTask> executeTasks = new ArrayList<>(); // 执行的任务
List<StackerTask> tasks = new ArrayList<>(); // 即将要写入堆垛机的任务
for(int i = 0; i < forkStatus.length(); i++) {
// 货叉开放检查
if (!String.valueOf(forkStatus.charAt(i)).equals(String.valueOf(TrueOrFalseEnum.TRUE.getCode()))) {
tasks.add(null);
continue;
}
TaskConveyStatus conveyStatus = conveyStatuses.get(i);
//String vehicleNo = conveyStatus.getVehicleNo(); // 载具号
if(conveyStatus.getPlcTaskId() != 0 || conveyStatus.getDeviceStatus().intValue() != 5 || conveyStatus.getTaskAllow().intValue() != TrueOrFalseEnum.TRUE.getCode()
|| conveyStatus.getSensor().intValue() == TrueOrFalseEnum.TRUE.getCode() || conveyStatus.getErrCode() != 0) {
// 任务号不为0设备状态不为5任务不允许载具号为空存在AGV ---- 这些情况不搬运
tasks.add(null);
continue;
}
StackerTask stackerInTask = null; // 要写入的任务
// 查询站台数量
List<AppStackerStand> stackerStands = stackerStandDao.queryStackerStand(stackerId);
if(stackerStands == null) {
return false; // 堆垛机查询站台失败
}
int standCount = stackerStands.size();
// 轮询任务
for (AppStockSingleTask stockSingleTask : stockSingleTasks) {
// 检查任务是否上回已经添加进去了
List<AppStockSingleTask> checkIsExecute = executeTasks.stream().filter(task -> task.getTaskId().equals(stockSingleTask.getTaskId())).toList();
if(!checkIsExecute.isEmpty()) {
continue;
}
/* 校验入库位置是否为空,若不为空则判断入库位置是否和现在站台一致 */
String origin = stockSingleTask.getOrigin();
// 入库站台为空判断这个堆垛机是否只有一个入库站台不是则跳过
if(AppStringUtils.isEmpty(origin) && standCount > 1) {
continue;
}
// 不是空
if(AppStringUtils.isNotEmpty(origin) && !origin.equals(standInfo.getStandId())) {
continue; // 不是空但是入库站台不对应跳过
}
stackerInTask = stackerTaskCreator.createStackerInTask(stockSingleTask.getPlcTaskId(), standInfo.getStandId(), stockSingleTask.getDestination(), stockSingleTask.getVehicleNo(), stockSingleTask.getVehicleSize().shortValue(), stockSingleTask.getWeight().shortValue());
if(stackerInTask == null) {
continue;
}
tasks.add(stackerInTask);
executeTasks.add(stockSingleTask);
break;
}
if(stackerInTask == null) {
tasks.add(null);
}
}
// 校验任务是不是都是 null
if(tasks.stream().allMatch(Objects::isNull) || executeTasks.isEmpty()) {
return false;
}
// 写入堆垛机任务
StackerTask[] tasksArray = tasks.toArray(new StackerTask[0]);
String writeStackerTaskErrTask = PlcCommunicationFactory.getPlcCommunicationByPlcId(stackerInfo.getPlcId()).writeStackerTask(stackerId, tasksArray);
if(AppStringUtils.isNotEmpty(writeStackerTaskErrTask)) {
log.info("堆垛机写入直入式入库任务失败:{};写入的任务为:{}", writeStackerTaskErrTask, AppListUtils.listObjectToSting(tasks));
sseHelper.SendEventToAll(SseEventTypeEnum.RUNNING_INFO, "堆垛机写入入库任务失败:" + tasks.getFirst().getPlcTaskId() + "" + tasks.size() + "个任务");
return false;
}
// 堆垛机写入任务成功
log.info("堆垛机写入直入式入库任务成功:{}", AppListUtils.listObjectToSting(tasks));
sseHelper.SendEventToAll(SseEventTypeEnum.RUNNING_INFO, "堆垛机写入入库任务:" + tasks.getFirst().getPlcTaskId() + "" + tasks.size() + "个任务");
// 触发任务开始执行事件
for (AppStockSingleTask executeTask : executeTasks) {
stockSingleTaskExecuteEvent.taskStart(executeTask, "任务开始执行");
}
return true;
}
/* ************************************* 外部工具方法 ****************************************** */
/**
* 获取可出库的站台信息
* @param stackerId 堆垛机ID
* @return 可出库的站台信息
*/
private AppStackerStand getCanUseOutStand(Integer stackerId) {
List<AppStackerStand> stackerStands = stackerStandDao.queryStackerStand(stackerId);
return checkOutStandCanUse(stackerStands);
}
/**
* 获取可出库的站台信息
* @param standInfoList 站台信息列表
* @return 可出库的站台信息
*/
private AppStackerStand checkOutStandCanUse(List<AppStackerStand> standInfoList) {
if(standInfoList == null || standInfoList.isEmpty()) {
return null;
}
for (AppStackerStand standInfo : standInfoList) {
if(!standInfo.getAllowOut().equals(TrueOrFalseEnum.TRUE.getCode())) {
continue; // 站台不允许出库
}
if(checkOutStandCanUse(standInfo)) {
return standInfo;
}
}
return null;
}
/**
* 获取可出库的站台信息
* @param standList 站台ID列表
* @return 可出库的站台信息
*/
private AppStackerStand checkOutStandCanUseWithStandIdList(List<String> standList) {
List<AppStackerStand> stackerStands = stackerStandDao.queryStackerStand(standList);
return checkOutStandCanUse(stackerStands);
}
/**
* 检查出库站台是否可用
* @param standInfo 站台信息
* @return 是否可用
*/
private boolean checkOutStandCanUse(AppStackerStand standInfo) {
/* 检查站台状态是否可用 */
Tuple2<String, List<TaskConveyStatus>> multiConveyStatusResult = PlcCommunicationFactory.getPlcCommunicationByPlcId(standInfo.getPlcId()).readTaskMultiConveyStatus(standInfo.getStandId());
if(AppStringUtils.isNotEmpty(multiConveyStatusResult.item1) || multiConveyStatusResult.item2 == null || multiConveyStatusResult.item2.isEmpty()) {
return false; // 读取站台失败
}
// 判断站台的状态
boolean canUse = true;
List<TaskConveyStatus> multiConveyStatus = multiConveyStatusResult.item2;
for(TaskConveyStatus conveyStatus : multiConveyStatus) {
if (conveyStatus.getPlcTaskId() == 0 && conveyStatus.getDeviceStatus().intValue() != 5 && conveyStatus.getTaskAllow().intValue() == TrueOrFalseEnum.TRUE.getCode()
&& conveyStatus.getSensor().intValue() == TrueOrFalseEnum.FALSE.getCode() && conveyStatus.getErrCode().intValue() == 0) {
continue; // 站台状态可用
}
canUse = false;
}
return canUse;
}
/* ************************************* 外部工具方法 END ****************************************** */
}

View File

@ -0,0 +1,31 @@
package org.wcs.business.stacker.intf;
import org.wcs.model.po.app.AppStackerInfo;
/**
* 堆垛机任务执行方法接口
*/
public interface IExecuteStackerTaskService {
/**
* 执行堆垛机入库任务
* @param stackerInfo 堆垛机编号
* @return 是否执行了任务
*/
boolean executeStackerInTask(AppStackerInfo stackerInfo);
/**
* 执行堆垛机出库任务
* @param stackerInfo 堆垛机编号
* @return 是否执行了任务
*/
boolean executeStackerOutTask(AppStackerInfo stackerInfo);
/**
* 执行堆垛机移库任务
* @param stackerInfo 堆垛机编号
* @return 是否执行了任务
*/
boolean executeStackerMoveTask(AppStackerInfo stackerInfo);
}

View File

@ -0,0 +1,519 @@
package org.wcs.business.stock;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.wcs.constant.ConstantData;
import org.wcs.constant.enums.business.LocationTypeEnum;
import org.wcs.mapper.intf.AppConveyPickStandService;
import org.wcs.mapper.intf.AppStackerLocationService;
import org.wcs.mapper.intf.AppStackerStandService;
import org.wcs.mapper.intf.AppTrayConveyLocationService;
import org.wcs.model.bo.stock.StockLocation;
import org.wcs.model.bo.tuple.Tuple2;
import org.wcs.model.po.app.AppConveyPickStand;
import org.wcs.model.po.app.AppStackerLocation;
import org.wcs.model.po.app.AppStackerStand;
import org.wcs.model.po.app.AppTrayConveyLocation;
import java.util.*;
/**
* 位置管理类
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class LocationManage {
private final AppStackerLocationService stackerLocationService; // 堆垛机库位表操作类
private final AppStackerStandService stackerStandService; // 堆垛机站台表操作类
private final AppTrayConveyLocationService trayConveyLocationService; // 托盘传送位置表操作类
private final AppConveyPickStandService conveyPickStandService; // 输送线捡货站台表操作类
private boolean isLoad = false;
private List<StockLocation> stockLocationList; // 所有位置列表
/**
* 交界处列表 < 点位区域>
*/
private List<Tuple2<String, List<String>>> finalJunctionList; // 过度点位
/**
* 区域之间连接点位 < 区域连接的其他区域>
*/
private Map<String, List<String>> areaJunctionMap; // 区域之间连接点位
/**
* 加载一次位置信息
*/
public void loadOnceLocation(){
if(isLoad) {
return;
}
loadStackerLocation(); // 加载位置信息
loadTrayConveyLocation(); // 加载托盘输送位置信息
loadConveyStandLocation(); // 加载输送线站台位置信息
calculateJunction(); // 计算交界处
isLoad = true;
}
/**
* 重新加载位置信息
*/
public void reloadLocation(){
stockLocationList = null;
loadOnceLocation(); // 加载信息
}
/**
* 判断位置是否存在
* @param locationId 位置ID
* @return 是否存在
*/
public boolean isLocationExist(String locationId) {
if(stockLocationList == null) {
loadOnceLocation();
}
for (StockLocation stockLocation : stockLocationList) {
if(stockLocation.getLocationId().equals(locationId)) {
return true;
}
}
return false;
}
/**
* 获取位置信息
* @param locationId 位置ID
* @return 位置信息
*/
public StockLocation getLocationInfo(String locationId) {
if(stockLocationList == null) {
loadOnceLocation();
}
if(stockLocationList == null) {
return null;
}
for (StockLocation stockLocation : stockLocationList) {
if(stockLocation.getLocationId().equals(locationId)) {
return stockLocation;
}
}
return null;
}
/**
* 判断两个位置是否在同一区域
* @param locationId1 位置ID1
* @param locationId2 位置ID2
* @return 是否在同一区域
*/
public Boolean isSameArea(String locationId1, String locationId2) {
if(stockLocationList == null) {
loadOnceLocation();
}
if(stockLocationList == null) {
return null;
}
Optional<StockLocation> stockLocation1 = stockLocationList.stream().filter(stockLocation -> stockLocation.getLocationId().equals(locationId1)).findFirst();
Optional<StockLocation> stockLocation2 = stockLocationList.stream().filter(stockLocation -> stockLocation.getLocationId().equals(locationId2)).findFirst();
if(stockLocation1.isEmpty() || stockLocation2.isEmpty()) {
return null; // 位置不存在
}
return stockLocation1.get().getAreaId().equals(stockLocation2.get().getAreaId());
}
/**
* 路径计算
* @param fromLocation 起始位置
* @param toLocationId 目标位置
* @return 路径信息 < 点位类型 点位>
*/
public List<Tuple2<LocationTypeEnum, String>> courseCalculate(String fromLocation, String toLocationId) {
if(fromLocation == null || toLocationId == null) {
return null;
}
if(stockLocationList == null) {
loadOnceLocation();
}
if(stockLocationList == null) {
return null;
}
List<Tuple2<LocationTypeEnum, String>> courseList = new ArrayList<>(); // 路径列表
List<StockLocation> fromLocationInfoList = stockLocationList.stream().filter(stockLocation -> stockLocation.getLocationId().equals(fromLocation)).toList();
if(fromLocationInfoList.isEmpty()) {
return null; // 起始位置不存在
}
Optional<StockLocation> toLocationInfo = stockLocationList.stream().filter(stockLocation -> stockLocation.getLocationId().equals(toLocationId)).findFirst();
if(toLocationInfo.isEmpty()) {
return null; // 目标位置不存在
}
List<String> areaList = new ArrayList<>(); // 存储区域的列表
StockLocation fromLocationInfo = null; // 起点动态决定的起点
// 起点信息存在多个情况下
if(fromLocationInfoList.size() > 1) {
// 判断是否有起点区域包含终点区域
for (StockLocation fromLocationInfoItem : fromLocationInfoList) {
if(fromLocationInfoItem.getLocationType().equals(toLocationInfo.get().getLocationType())) {
Tuple2<LocationTypeEnum, String> firstLocation = new Tuple2<>(fromLocationInfoItem.getLocationType(), fromLocationInfoItem.getLocationId());
courseList.add(firstLocation); // 第一个位置
// 同样的区域直接返回路径
courseList.add(new Tuple2<>(toLocationInfo.get().getLocationType(), toLocationInfo.get().getLocationId()));
return courseList;
}
}
// 不包含终点区域 -- 需要计算区域路径
for (StockLocation fromLocationInfoItem : fromLocationInfoList) {
// 不同区域 // 找出区域路径即需要经过哪些区域
List<String> areaListTemp = calculateAreaCourse(fromLocationInfoItem.getAreaId(), toLocationInfo.get().getAreaId());
if(areaListTemp != null && (areaList.isEmpty() || areaList.size() > areaListTemp.size())) {
areaList = areaListTemp; // 多个起点 取经过区域最少的点
fromLocationInfo = fromLocationInfoItem;
courseList = new ArrayList<>();
Tuple2<LocationTypeEnum, String> firstLocation = new Tuple2<>(fromLocationInfoItem.getLocationType(), fromLocationInfoItem.getLocationId());
courseList.add(firstLocation); // 第一个位置
}
}
} else {
fromLocationInfo = fromLocationInfoList.getFirst();
Tuple2<LocationTypeEnum, String> firstLocation = new Tuple2<>(fromLocationInfo.getLocationType(), fromLocationInfo.getLocationId());
courseList.add(firstLocation); // 第一个位置
areaList = calculateAreaCourse(fromLocationInfo.getAreaId(), toLocationInfo.get().getAreaId());
if(areaList == null) {
return null;
}
}
if(areaList.isEmpty()) {
return null; // 区域路径计算失败
}
for (int i = 0; i < areaList.size(); i++) {
if(i == areaList.size() - 1) {
// 最后一个区域
courseList.add(new Tuple2<>(toLocationInfo.get().getLocationType(), toLocationInfo.get().getLocationId()));
} else {
// 其他区域
String fromAreaItem = areaList.get(i);
String toAreaItem = areaList.get(i + 1);
// 查找这两区域的共同的点
List<Tuple2<String, List<String>>> junctionLocation = finalJunctionList.stream().filter(tuple2 -> tuple2.item2.contains(fromAreaItem) && tuple2.item2.contains(toAreaItem)).toList();
if(junctionLocation.isEmpty()) {
return null; // 区域路径计算失败 ---- 两区域没有交接点按道理不会
}
if(junctionLocation.size() > 1) {
// 存在多个交接点只生成 一个流程点后续再动态生成
StringBuilder junctionLocationId = new StringBuilder("?");
for (Tuple2<String, List<String>> junctionLocationItem : junctionLocation) {
junctionLocationId.append(junctionLocationItem.item1).append(";");
}
junctionLocationId = new StringBuilder(junctionLocationId.substring(0, junctionLocationId.length() - 1));
courseList.add(new Tuple2<>(fromLocationInfo.getLocationType(), junctionLocationId.toString())); // 类型设置为和起点一样
return courseList;
}
// 找到区域交接点且只存在一个
Tuple2<String, List<String>> junctionLocationItem = junctionLocation.getFirst();
List<StockLocation> stockLocations = stockLocationList.stream().filter(stockLocation -> stockLocation.getLocationId().equals(junctionLocationItem.item1)).toList();
if(stockLocations.isEmpty()) {
return null; // 区域交接点不存在 --- 正常不会出现
}
for (StockLocation stockLocation : stockLocations) {
if(stockLocation.getAreaId().equals(fromAreaItem)) {
courseList.add(new Tuple2<>(stockLocation.getLocationType(), stockLocation.getLocationId()));
}
}
for (StockLocation stockLocation : stockLocations) {
if(stockLocation.getAreaId().equals(toAreaItem)) {
courseList.add(new Tuple2<>(stockLocation.getLocationType(), stockLocation.getLocationId()));
}
}
}
}
return courseList;
}
/**
* 加载堆垛机位置信息
*/
private void loadStackerLocation() {
// 库位信息
List<AppStackerLocation> appStackerLocations = stackerLocationService.queryStackerLocationAll();
if(appStackerLocations == null) {
log.error("堆垛机库位信息加载失败");
return;
}
List<StockLocation> stackerLocationList = new ArrayList<>();
for (AppStackerLocation appStackerLocation : appStackerLocations) {
StockLocation stockLocation = new StockLocation();
stockLocation.setLocationId(appStackerLocation.getLocationId());
stockLocation.setLocationType(LocationTypeEnum.STACKER);
stockLocation.setAreaId(ConstantData.STACKER_AREA_PREFIX + appStackerLocation.getMachineId());
stackerLocationList.add(stockLocation);
}
// 站台信息
List<AppStackerStand> appStackerStands = stackerStandService.queryAll();
if(appStackerStands == null) {
log.error("堆垛机站台信息加载失败");
return;
}
for (AppStackerStand appStackerStand : appStackerStands) {
StockLocation stockLocation = new StockLocation();
stockLocation.setLocationId(appStackerStand.getStandId());
stockLocation.setLocationType(LocationTypeEnum.STACKER);
stockLocation.setAreaId(ConstantData.STACKER_AREA_PREFIX + appStackerStand.getStackerId());
stackerLocationList.add(stockLocation);
// StockLocation stockLocationConvey = new StockLocation();
// stockLocationConvey.setLocationId(appStackerStand.getStandId());
// stockLocationConvey.setLocationType(LocationTypeEnum.CONVEY);
// stockLocationConvey.setAreaId(ConstantData.CONVEY_AREA_PREFIX + appStackerStand.getAreaId());
// stackerLocationList.add(stockLocationConvey);
}
if(stockLocationList == null) {
stockLocationList = new ArrayList<>();
}
stockLocationList.addAll(stackerLocationList);
}
/**
* 加载托盘输送位置信息
*/
private void loadTrayConveyLocation() {
List<AppTrayConveyLocation> trayConveyLocations = trayConveyLocationService.queryAll();
if(trayConveyLocations == null) {
log.error("托盘传送位置信息加载失败");
return;
}
// 托盘输送位置信息
List<StockLocation> trayConveyLocationList = new ArrayList<>();
for (AppTrayConveyLocation trayConveyLocation : trayConveyLocations) {
String areaId = trayConveyLocation.getAreaId();
if(areaId == null) {
continue;
}
String[] areaIds = areaId.split(";"); // 存在多个区域情况下用分号隔开
for (String areaIdItem : areaIds) {
StockLocation stockLocation = new StockLocation();
stockLocation.setLocationId(trayConveyLocation.getLocationId());
stockLocation.setLocationType(LocationTypeEnum.CONVEY);
stockLocation.setAreaId(ConstantData.CONVEY_AREA_PREFIX + areaIdItem);
trayConveyLocationList.add(stockLocation);
}
}
if(stockLocationList == null) {
stockLocationList = new ArrayList<>();
}
stockLocationList.addAll(trayConveyLocationList);
}
/**
* 加载输送线站台位置信息
*/
private void loadConveyStandLocation() {
// 输送线捡货站台信息
List<AppConveyPickStand> conveyPickStands = conveyPickStandService.queryAll();
if(conveyPickStands == null) {
log.error("输送线捡货站台信息加载失败");
return;
}
List<StockLocation> conveyPickStandLocationList = new ArrayList<>();
for (AppConveyPickStand conveyPickStand : conveyPickStands) {
StockLocation stockLocation = new StockLocation();
stockLocation.setLocationId(conveyPickStand.getStandId());
stockLocation.setLocationType(LocationTypeEnum.CONVEY);
stockLocation.setAreaId(ConstantData.CONVEY_AREA_PREFIX + conveyPickStand.getAreaId());
conveyPickStandLocationList.add(stockLocation);
}
if(stockLocationList == null) {
stockLocationList = new ArrayList<>();
}
stockLocationList.addAll(conveyPickStandLocationList);
}
/**
* 计算交界处
*/
private void calculateJunction() {
if(stockLocationList == null) {
loadOnceLocation();
}
if(stockLocationList == null) {
return;
}
// <点位所属的区域列表> ----- 过度信息
List<Tuple2<String, List<String>>> junctionList = new ArrayList<>();
for (StockLocation stockLocation : stockLocationList) {
// 上述列表内是否存在这个点位
Optional<Tuple2<String, List<String>>> junctionData = junctionList.stream().filter(tuple2 ->
tuple2.item1.equals(stockLocation.getLocationId())).findFirst();
if(junctionData.isEmpty()) {
// 不存在点位创建个新的数据加进去
List<String> locationAreaList = new ArrayList<>();
locationAreaList.add(stockLocation.getAreaId());
junctionList.add(new Tuple2<>(stockLocation.getLocationId(), locationAreaList));
continue;
}
junctionData.get().item2.add(stockLocation.getAreaId());
}
// <点位所属的区域列表> ----- 最终获取到的过渡点
List<Tuple2<String, List<String>>> finalJunctionList = new ArrayList<>();
// 循环列表找出不止一种点位类型的元素
for (Tuple2<String, List<String>> junctionData : junctionList) {
if(junctionData.item2.size() > 1) {
finalJunctionList.add(junctionData);
}
}
this.finalJunctionList = finalJunctionList;
}
/**
* 计算区域交界即每个区域分别和那些区域相连接
* 区域权重暂定为1即任意两个区域之间连接的权重为1
*/
private void calculateAreaJunction() {
if(stockLocationList == null) {
loadOnceLocation();
}
if(stockLocationList == null) {
return;
}
if(finalJunctionList == null) {
calculateJunction();
}
if(finalJunctionList == null) {
return;
}
// 寻找点位列表中不同的区域
List<String> areaList = stockLocationList.stream().map(StockLocation::getAreaId).toList();
// 区域连接关系暂存
areaJunctionMap = new HashMap<>();
// 轮询区域寻找区域之间的连接关系
for (String areaItem : areaList) {
List<String> junctionList = new ArrayList<>();
for (Tuple2<String, List<String>> finalJunction : finalJunctionList) {
List<String> locationAreaList = finalJunction.item2;
// 不包含该区域
if(!locationAreaList.contains(areaItem)) {
continue;
}
// 包含区域则添加
for (String areaItem2 : locationAreaList) {
if(areaItem.equals(areaItem2)) {
continue;
}
junctionList.add(areaItem2);
}
}
areaJunctionMap.put(areaItem, junctionList);
}
}
/**
* 计算区域路径
* @param startAreaId 起点区域
* @param endAreaId 终点区域
* @return 区域路径
*/
private List<String> calculateAreaCourse(String startAreaId, String endAreaId) {
if(startAreaId.equals(endAreaId)) {
return List.of(startAreaId);
}
if(areaJunctionMap == null) {
calculateAreaJunction();
}
if(areaJunctionMap == null) {
return null;
}
// 正是开始计算
List<String> usedAreaList = new ArrayList<>(); // 已处理过的区域
// 权重map
Map<String, Integer> areaWeightMap = createAreaWeightMap(startAreaId);
// 前驱map
Map<String, String> beforeAreaMap = createBeforeAreaMap();
// 优先队列
List<Tuple2<Integer, String>> priorityQueue = new ArrayList<>();
priorityQueue.add(new Tuple2<>(0, startAreaId));
// Dijkstra算法
while (!priorityQueue.isEmpty()) {
// 队列按照权重进行排序
priorityQueue.sort(Comparator.comparingInt(Tuple2::getItem1));
// 取出第一个应该是最小权重的
Tuple2<Integer, String> priorityQueueItem = priorityQueue.getFirst();
priorityQueue.remove(priorityQueueItem);
// 如果该区域已处理过则跳过
if(usedAreaList.contains(priorityQueueItem.item2)) {
continue;
}
usedAreaList.add(priorityQueueItem.item2);
// 获取该区域连接的区域
List<String> junctionList = areaJunctionMap.get(priorityQueueItem.item2);
for (String junctionItem : junctionList) {
// 如果该区域已处理过则跳过
if(usedAreaList.contains(junctionItem)) {
continue;
}
// 如果该区域权重比当前区域权重更小则更新
if(areaWeightMap.get(junctionItem) > areaWeightMap.get(priorityQueueItem.item2) + 1) {
areaWeightMap.put(junctionItem, areaWeightMap.get(priorityQueueItem.item2) + 1);
}
// 添加到优先队列中
priorityQueue.add(new Tuple2<>(areaWeightMap.get(junctionItem), junctionItem));
// 添加前驱
beforeAreaMap.put(junctionItem, priorityQueueItem.item2);
// 找到终点
if(junctionItem.equals(endAreaId)) {
// 找到终点返回路径
List<String> courseList = new ArrayList<>();
String currentArea = junctionItem;
// 倒序
while (currentArea != null) {
courseList.add(currentArea);
currentArea = beforeAreaMap.get(currentArea);
}
// 倒序
return courseList.reversed();
}
}
}
return null;
}
/**
* 创建区域权重Map
* @param startAreaId 开始区域
* @return 区域权重Map
*/
private Map<String, Integer> createAreaWeightMap(String startAreaId) {
Map<String, Integer> areaWeightMap = new HashMap<>();
// 寻找点位列表中不同的区域
List<String> areaList = stockLocationList.stream().map(StockLocation::getAreaId).toList();
for (String areaItem : areaList) {
if(areaItem.equals(startAreaId)) {
areaWeightMap.put(areaItem, 0);
continue;
}
areaWeightMap.put(areaItem, Integer.MAX_VALUE);
}
return areaWeightMap;
}
/**
* 创建区域前驱Map
* @return 区域前驱Map
*/
private Map<String, String> createBeforeAreaMap() {
Map<String, String> beforeAreaMap = new HashMap<>();
// 寻找点位列表中不同的区域
List<String> areaList = stockLocationList.stream().map(StockLocation::getAreaId).toList();
for (String areaItem : areaList) {
beforeAreaMap.put(areaItem, null);
}
return beforeAreaMap;
}
}

View File

@ -0,0 +1,905 @@
package org.wcs.business.stock;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.wcs.constant.enums.business.LocationTypeEnum;
import org.wcs.constant.enums.business.SimpleTaskEnum;
import org.wcs.constant.enums.common.TrueOrFalseEnum;
import org.wcs.constant.enums.database.StockComposeTaskTypeEnum;
import org.wcs.constant.enums.database.StockExecuteEquipmentEnum;
import org.wcs.constant.enums.database.StockSingleTaskStatusEnum;
import org.wcs.constant.enums.database.StockSingleTaskTypeEnum;
import org.wcs.helper.PlcTaskIdHelper;
import org.wcs.mapper.intf.AppConveyLocationService;
import org.wcs.mapper.intf.AppStackerLocationService;
import org.wcs.mapper.intf.AppStackerStandService;
import org.wcs.mapper.intf.AppTrayConveyLocationService;
import org.wcs.model.bo.tuple.Tuple2;
import org.wcs.model.po.app.*;
import org.wcs.model.pojo.task.SimpleTask;
import org.wcs.utils.AppStringUtils;
import org.wcs.utils.AppUUIDUtils;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 核心仓库组合任务管理数据处理
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class StockComposeTaskManage {
private final StringRedisTemplate stringRedisTemplate;
private final AppStackerLocationService stackerLocationService;
private final AppStackerStandService stackerStandService;
private final AppTrayConveyLocationService trayConveyLocationService;
private final AppConveyLocationService conveyLocationService;
private final PlcTaskIdHelper plcTaskIdHelper;
private final LocationManage locationManage; // 位置管理
// 点位暂存区存放点位信息 <点位 基础资料>
Map<String, AppStackerLocation> stackerLocationMap = new ConcurrentHashMap<>();
Map<String, AppStackerStand> stackerStandMap = new ConcurrentHashMap<>();
Map<String, AppTrayConveyLocation> trayConveyLocationMap = new ConcurrentHashMap<>();
Map<String, AppConveyLocation> conveyLocationMap = new ConcurrentHashMap<>();
String areaLocationRedisKey = "WCS:Location:Area"; // 存储每个区域所包含的位置
Map<String, List<String>> locationAreaMap = new ConcurrentHashMap<>(); // <位置号所属的区域列表>
List<String> areas = new ArrayList<>(); // 存储所有的区域
List<String> locations = new ArrayList<>(); // 存储所有的点位信息
/**
* 初始化
*/
public void initialize() {
long deleteDataCount = deleteLocationIdArea(); // 删除Redis中的位置区域信息
log.info("删除Redis中的位置区域信息{}", deleteDataCount);
addStackerLocationIdToRedisList(); // 添加堆垛机位置信息到Redis中
addStackerStandIdToRedisList(); // 添加堆垛机站台信息到Redis中
addTrayLocationIdToRedisList(); // 添加托盘位置信息到Redis中
addConveyLocationToRedisList(); // 添加普通输送信息到Redis中
summaryLocationArea(); // 汇总位置区域信息
summaryLocation(); // 汇总位置信息
}
/**
* 将堆垛机位置添加到Redis和系统缓存中按区域添加
*/
public void addStackerLocationIdToRedisList() {
List<AppStackerLocation> appStackerLocations = stackerLocationService.queryStackerLocationAll();
if(appStackerLocations == null) {
log.error("堆垛机点位数据库查询失败");
return; // 数据库查询失败
}
// 添加到缓存中
stackerLocationMap = appStackerLocations.stream().collect(Collectors.toMap(AppStackerLocation::getBusinessLocation, appStackerLocation -> appStackerLocation));
// 添加到 redis
Map<Integer, List<AppStackerLocation>> machineLocation = appStackerLocations.stream().collect(Collectors.groupingBy(AppStackerLocation::getMachineId));
machineLocation.forEach((machineId, locations) -> {
// 拿出locations里面所有的 businessLocationId形成list
List<String> businessLocationIds = locations.stream().map(AppStackerLocation::getBusinessLocation).toList();
stringRedisTemplate.opsForList().leftPushAll(areaLocationRedisKey + ":" + machineId, businessLocationIds);
});
}
/**
* 添加堆垛机站台信息到redis和系统缓存中
*/
public void addStackerStandIdToRedisList() {
List<AppStackerStand> stackerStands = stackerStandService.queryAll();
if(stackerStands == null) {
log.error("堆垛机站台信息数据库访问异常");
return; // 堆垛机信息数据库访问异常
}
// 添加到缓存中
stackerStandMap = stackerStands.stream().collect(Collectors.toMap(AppStackerStand::getStandId, stackerStand -> stackerStand));
// 按堆垛机区域添加
Map<Integer, List<AppStackerStand>> stackerGroup = stackerStands.stream().collect(Collectors.groupingBy(AppStackerStand::getStackerId));
stackerGroup.forEach((stackerId, stackerStandList) -> {
List<String> trayLocationIds = stackerStands.stream().map(AppStackerStand::getStandId).collect(Collectors.toList());
stringRedisTemplate.opsForList().leftPushAll(areaLocationRedisKey + ":" + stackerId, trayLocationIds);
});
// 按区域添加
Map<String, List<AppStackerStand>> areaGroup = stackerStands.stream().collect(Collectors.groupingBy(AppStackerStand::getAreaId));
areaGroup.forEach((areaId, stackerStandList) -> {
List<String> trayLocationIds = stackerStands.stream().map(AppStackerStand::getStandId).collect(Collectors.toList());
stringRedisTemplate.opsForList().leftPushAll(areaLocationRedisKey + ":" + areaId, trayLocationIds);
});
}
/**
* 添加托盘位置id到redis和系统缓存中
*/
public void addTrayLocationIdToRedisList() {
List<AppTrayConveyLocation> trayConveyLocations = trayConveyLocationService.queryAll();
if(trayConveyLocations == null) {
log.error("托盘输送点位数据库加载失败");
return;
}
// 添加到缓存中
trayConveyLocationMap = trayConveyLocations.stream().collect(Collectors.toMap(AppTrayConveyLocation::getBusinessLocationId, trayConveyLocation -> trayConveyLocation));
// 添加到redis中
Map<String, List<AppTrayConveyLocation>> trayConveyLocation = trayConveyLocations.stream().collect(Collectors.groupingBy(AppTrayConveyLocation::getAreaId));
trayConveyLocation.forEach((areaId, trayConveyLocationList) -> {
List<String> businessLocationIds = trayConveyLocationList.stream().map(AppTrayConveyLocation::getBusinessLocationId).toList();
stringRedisTemplate.opsForList().leftPushAll(areaLocationRedisKey + ":" + areaId, businessLocationIds);
});
}
/**
* 添加普通输送点位到 Redis和系统缓存中
*/
public void addConveyLocationToRedisList() {
List<AppConveyLocation> appConveyLocations = conveyLocationService.queryAll();
if(appConveyLocations == null) {
log.error("普通输送点位数据库加载失败");
return;
}
// 添加到缓存中
conveyLocationMap = appConveyLocations.stream().collect(Collectors.toMap(AppConveyLocation::getBusinessLocationId, v -> v));
// 添加到 Redis
Map<String, List<AppConveyLocation>> conveyLocationList = appConveyLocations.stream().collect(Collectors.groupingBy(AppConveyLocation::getAreaId));
conveyLocationList.forEach((areaId, conveyLocations) -> {
List<String> businessLocationIds = conveyLocations.stream().map(AppConveyLocation::getBusinessLocationId).toList();
stringRedisTemplate.opsForList().leftPushAll(areaLocationRedisKey + ":" + areaId, businessLocationIds);
});
}
/**
* 删除redis中所有点位
* @return 删除数量
*/
public long deleteLocationIdArea() {
Set<String> keys = stringRedisTemplate.keys(areaLocationRedisKey + ":*");
return stringRedisTemplate.delete(keys);
}
/**
* 计算汇总哪些点对应的不止一个区域
* 这些点就是中转点单独存到map里面方便后续处理
*/
public void summaryLocationArea() {
Set<String> keys = stringRedisTemplate.keys(areaLocationRedisKey + ":*");
if(keys.isEmpty()) {
log.warn("汇总点位区域时 无区域点位");
return;
}
// 获取所有区域
List<String> areas = new ArrayList<>(); // 所有的区域
keys.forEach(key -> {
String[] strings = key.split(":");
areas.add(strings[strings.length - 1]);
});
// 计算区域共同点位
if(areas.size() == 1) {
return;
}
List<Tuple2<String, List<String>>> areaLocationList = new ArrayList<>();
/* 将每一个点位列表和其他列表对比,找出相同的,这里找出的结果必然有重复的,下面会重新去重并组合 */
for (int i = 0; i < areas.size() - 1; i++) {
String key1 = areaLocationRedisKey + areas.get(i);
for(int p = i + 1; p < areas.size(); p++) {
String key2 = areaLocationRedisKey + areas.get(p);
List<String> area1List = stringRedisTemplate.opsForList().range(key1, 0, -1);
List<String> area2List = stringRedisTemplate.opsForList().range(key2, 0, -1);
if(area1List == null || area2List == null) {
continue;
}
// 查找两个list的相同的项
List<String> sameLocation = area1List.stream().filter(area2List::contains).toList();
if(sameLocation.isEmpty()) {
continue;
}
for(String location : sameLocation) {
Tuple2<String, List<String>> tuple2 = new Tuple2<>(location, List.of(areas.get(i), areas.get(p)));
areaLocationList.add(tuple2);
}
}
}
this.areas = areas; // 赋值给全局变量
// -- 去重并组合
List<String> distinctLocationList = areaLocationList.stream().map(tuple2 -> tuple2.item1).distinct().toList();
for (String distinctLocation : distinctLocationList) {
List<Tuple2<String, List<String>>> areaDataList = areaLocationList.stream().filter(tuple2 -> tuple2.item1.equals(distinctLocation)).toList();
List<String> locationAreas = areaDataList.stream().map(tuple2 -> tuple2.item2).flatMap(List::stream).distinct().toList();
locationAreaMap.put(distinctLocation, locationAreas);
}
}
/**
* 汇总所有点位信息
*/
public void summaryLocation() {
if(areas.isEmpty()) {
return;
}
areas.forEach(area -> {
String key = areaLocationRedisKey + ":" + area;
List<String> locationList = stringRedisTemplate.opsForList().range(key, 0, -1);
if(locationList == null || locationList.isEmpty()) {
return;
}
locationList.forEach(location -> {
if(!locations.contains(location)) {
locations.add(location);
}
});
});
}
/**
* 检查某一点位是否存在
* @param location 位点
* @return 是否存在
*/
public boolean checkLocationExist(String location) {
if(location == null || location.trim().isEmpty()) {
return false;
}
if(locations.isEmpty()) {
summaryLocation();
}
return locations.contains(location);
}
/**
* 检查一个点位的区域若这个点位在多个区域中则返回多个
* @param location 待检查的点位
* @return 该点位的区域
*/
private List<String> getLocationArea(String location) {
if(AppStringUtils.isEmpty(location)) {
return null;
}
List<String> locationAreaList = new ArrayList<>();
// 检查是否是堆垛机点位
AppStackerLocation stackerLocation = stackerLocationMap.get(location);
if(stackerLocation != null) {
locationAreaList.add(stackerLocation.getMachineId().toString());
}
// 检查是否是站台点位
AppStackerStand standLocation = stackerStandMap.get(location);
if(standLocation != null) {
locationAreaList.add(standLocation.getStackerId().toString());
locationAreaList.add(standLocation.getAreaId());
}
// 检查是否是托盘输送点位
AppTrayConveyLocation trayConveyLocation = trayConveyLocationMap.get(location);
if(trayConveyLocation != null) {
locationAreaList.add(trayConveyLocation.getAreaId());
}
// 检查是否是普通输送任务
AppConveyLocation conveyLocation = conveyLocationMap.get(location);
if(conveyLocation != null) {
locationAreaList.add(conveyLocation.getAreaId());
}
if(locationAreaList.isEmpty()) {
return null;
}
return locationAreaList.stream().distinct().toList();
}
/**
* 获取两个区域的中转点
* @param originArea 起点区域
* @param destinationArea 终点区域
* @return 中转点
*/
private String getTransferLocation(String originArea, String destinationArea) {
final String[] transferLocation = {null};
locationAreaMap.forEach((location, areaList) -> {
if(transferLocation[0] != null) {
return;
}
if(areaList.contains(originArea) && areaList.contains(destinationArea)) {
transferLocation[0] = location;
}
});
return transferLocation[0];
}
/**
* 检查一个点位的类型若这个点位在多个类型中则返回多个
* @param location 待检查的点位
* @return 该点位的类型
*/
private List<LocationTypeEnum> getLocationType(String location) {
if(AppStringUtils.isEmpty(location)) {
return List.of(LocationTypeEnum.UNKNOWN);
}
List<LocationTypeEnum> locationTypeList = new ArrayList<>();
// 检查是否是堆垛机点位
AppStackerLocation stackerLocation = stackerLocationMap.get(location);
if(stackerLocation != null) {
locationTypeList.add(LocationTypeEnum.STACKER);
}
// 检查是否是站台点位
AppStackerStand standLocation = stackerStandMap.get(location);
if(standLocation != null) {
locationTypeList.add(LocationTypeEnum.STACKER);
locationTypeList.add(LocationTypeEnum.CONVEY);
}
// 检查是否是托盘输送点位
AppTrayConveyLocation trayConveyLocation = trayConveyLocationMap.get(location);
if(trayConveyLocation != null) {
locationTypeList.add(LocationTypeEnum.TRAY_CONVEY);
}
// 检查是否是普通输送任务
AppConveyLocation conveyLocation = conveyLocationMap.get(location);
if(conveyLocation != null) {
locationTypeList.add(LocationTypeEnum.CONVEY);
}
if(locationTypeList.isEmpty()) {
return List.of(LocationTypeEnum.UNKNOWN);
}
return locationTypeList.stream().distinct().toList();
}
/**
* 计算任务路径 ---- 目前仅支持一个中转点
* @param origin 起点
* @param destination 终点
* @return 任务路径 <计算结果路径>若正常则计算结果为 null,不为 null则返回错误
*/
public Tuple2<String, List<SimpleTask>> calculateTask(String origin, String destination) {
if(AppStringUtils.isEmpty(origin) || AppStringUtils.isEmpty(destination)) {
return new Tuple2<>("起点或者终点为空", null);
}
if(areas.isEmpty()) {
return new Tuple2<>("没有区域信息,请先初始化或者检查基础资料", null);
}
/* 校验起点终点是否存在 */
if(locations.isEmpty()) {
summaryLocation(); // 获取位置
}
if(locations.isEmpty()) {
return new Tuple2<>("没有位置信息,请先初始化或者检查基础资料", null);
}
if(!locations.contains(origin)) {
return new Tuple2<>("起点不存在", null);
}
if(!locations.contains(destination)) {
return new Tuple2<>("终点不存在", null);
}
List<String> originLocationAreas = getLocationArea(origin);
if(originLocationAreas == null) {
return new Tuple2<>("起点是不受支持的点位", null);
}
List<String> destinationLocationAreas = getLocationArea(destination);
if(destinationLocationAreas == null) {
return new Tuple2<>("终点是不受支持的点位", null);
}
// 判断起点和终点是否是同一种点位类型
List<String> sameAreaList = originLocationAreas.stream().filter(destinationLocationAreas::contains).toList();
if(!sameAreaList.isEmpty()) {
// 不是空的说明是同一种类型
List<LocationTypeEnum> originLocationTypes = getLocationType(origin);
List<LocationTypeEnum> destinationLocationTypes = getLocationType(destination);
if(originLocationTypes.contains(LocationTypeEnum.UNKNOWN) || destinationLocationTypes.contains(LocationTypeEnum.UNKNOWN)) {
return new Tuple2<>("起点和终点属于同一区域,但检索不到点位类型,请检查基础资料", null); // 正常不会发生
}
// 查找两个list中相同的项
List<LocationTypeEnum> sameLocationType = originLocationTypes.stream().filter(destinationLocationTypes::contains).toList();
if(sameLocationType.isEmpty()) {
return new Tuple2<>("起点和终点属于同一区域,其点位类型不一致,不允许此设置,请检查区域设置是否合法", null);
}
SimpleTask simpleTask = new SimpleTask();
simpleTask.setTaskType(SimpleTaskEnum.getSimpleTaskEnum(sameLocationType.getFirst()));
simpleTask.setOrigin(origin);
simpleTask.setDestination(destination);
simpleTask.setTaskIndex(1);
return new Tuple2<>(null, List.of(simpleTask));
}
// 不是同一类型的点位跨区任务 ----- 目前仅支持一个中转点葛林强2025年8月13日
String transferLocation = null;
for (String originLocationArea : originLocationAreas) {
if(transferLocation != null) {
break;
}
for (String destinationLocationArea : destinationLocationAreas) {
transferLocation = getTransferLocation(originLocationArea, destinationLocationArea);
if(transferLocation != null) {
break;
}
}
}
if(AppStringUtils.isEmpty(transferLocation)) {
log.info("无法找到中转位置,目前仅支持一个中专点,起点:{}, 终点:{},起点区域:{}, 终点区域:{}", origin, destination, String.join(";", originLocationAreas), String.join(";", destinationLocationAreas));
return new Tuple2<>("无法找到中转位置", null);
}
// origin ---> transferLocation ---> destination
List<SimpleTask> simpleTasks = new ArrayList<>();
// 起点 ---> 中转
// 不是空的说明是同一种类型
List<LocationTypeEnum> originLocationTypes = getLocationType(origin);
List<LocationTypeEnum> transferLocationTypes = getLocationType(transferLocation);
if(originLocationTypes.contains(LocationTypeEnum.UNKNOWN) || transferLocationTypes.contains(LocationTypeEnum.UNKNOWN)) {
return new Tuple2<>("起点和中转点属于同一区域,但检索不到点位类型,请检查基础资料", null); // 正常不会发生
}
// 查找两个list中相同的项
List<LocationTypeEnum> sameLocationType = originLocationTypes.stream().filter(transferLocationTypes::contains).toList();
if(sameLocationType.isEmpty()) {
return new Tuple2<>("起点和中转点属于同一区域,其点位类型不一致,不允许此设置,请检查区域设置是否合法", null);
}
SimpleTask simpleTask1 = new SimpleTask();
simpleTask1.setOrigin(origin);
simpleTask1.setDestination(transferLocation);
simpleTask1.setTaskType(SimpleTaskEnum.getSimpleTaskEnum(sameLocationType.getFirst()));
simpleTask1.setTaskIndex(1);
simpleTasks.add(simpleTask1);
// 中转点 ---> 目标点
// 不是空的说明是同一种类型
List<LocationTypeEnum> destinationLocationTypes = getLocationType(destination);
if(originLocationTypes.contains(LocationTypeEnum.UNKNOWN) || transferLocationTypes.contains(LocationTypeEnum.UNKNOWN)) {
return new Tuple2<>("终点和中转点属于同一区域,但检索不到点位类型,请检查基础资料", null); // 正常不会发生
}
// 查找两个list中相同的项
List<LocationTypeEnum> sameLocationType2 = destinationLocationTypes.stream().filter(transferLocationTypes::contains).toList();
if(sameLocationType2.isEmpty()) {
return new Tuple2<>("终点和中转点属于同一区域,其点位类型不一致,不允许此设置,请检查区域设置是否合法", null);
}
SimpleTask simpleTask2 = new SimpleTask();
simpleTask1.setOrigin(origin);
simpleTask1.setDestination(transferLocation);
simpleTask1.setTaskType(SimpleTaskEnum.getSimpleTaskEnum(sameLocationType.getFirst()));
simpleTask1.setTaskIndex(2);
simpleTasks.add(simpleTask2);
return new Tuple2<>(null, simpleTasks);
}
/**
* 创建第一个自动任务的子任务
* @param composeTask 组合任务
* @return < 异常信息 子任务 >
*/
public Tuple2<String, AppStockSingleTask> createFirstAutoStockSingleTask(AppStockComposeTask composeTask) {
Tuple2<String, List<SimpleTask>> calculateTaskResult = calculateTask(composeTask.getOrigin(), composeTask.getDestination());
if(calculateTaskResult.getItem1() != null) {
return new Tuple2<>(calculateTaskResult.getItem1(), null);
}
List<SimpleTask> simpleTaskList = calculateTaskResult.getItem2();
if(simpleTaskList == null || simpleTaskList.isEmpty()) {
return new Tuple2<>("任务路径无法计算", null);
}
SimpleTask simpleTask = simpleTaskList.getFirst();
StockSingleTaskTypeEnum simpleTaskType = StockSingleTaskTypeEnum.getBySimpleTaskType(simpleTask.getTaskType());
if(simpleTaskType == null || simpleTaskType == StockSingleTaskTypeEnum.UNKNOWN) {
return new Tuple2<>("任务类型无法识别", null);
}
Integer newTaskId = plcTaskIdHelper.newTaskId();
if(newTaskId == null || newTaskId == 0) {
return new Tuple2<>("数据服务异常", null);
}
AppStockSingleTask appStockSingleTask = new AppStockSingleTask();
appStockSingleTask.setTaskId(AppUUIDUtils.getNewUUID());
appStockSingleTask.setTaskGroup(composeTask.getTaskGroup());
appStockSingleTask.setPlcTaskId(newTaskId);
appStockSingleTask.setUpperTaskId(composeTask.getTaskId());
appStockSingleTask.setExecuteMachine(0);
appStockSingleTask.setTaskType(StockSingleTaskTypeEnum.STACKER_AUTO.getCode()); // 暂时赋值下面会重新具体赋值
appStockSingleTask.setOrigin(simpleTask.getOrigin());
appStockSingleTask.setDestination(simpleTask.getDestination());
appStockSingleTask.setComposeDestination(composeTask.getDestination());
appStockSingleTask.setTaskStatus(StockSingleTaskStatusEnum.CREATE.getCode());
appStockSingleTask.setCanCancel(TrueOrFalseEnum.TRUE.getCode());
appStockSingleTask.setPriority(composeTask.getPriority());
appStockSingleTask.setVehicleNo(composeTask.getVehicleNo());
appStockSingleTask.setVehicleNoNumber(AppStringUtils.forceToInt(composeTask.getVehicleNo()));
appStockSingleTask.setVehicleSize(composeTask.getVehicleSize());
appStockSingleTask.setWeight(composeTask.getWeight());
appStockSingleTask.setCreateTime(LocalDateTime.now());
appStockSingleTask.setUpdateTime(LocalDateTime.now());
appStockSingleTask.setTaskSource(composeTask.getTaskSource());
appStockSingleTask.setCreatePerson(composeTask.getCreatePerson());
// 堆垛机任务
if(simpleTaskType == StockSingleTaskTypeEnum.STACKER_AUTO) {
List<LocationTypeEnum> originLocationType = getLocationType(simpleTask.getOrigin());
List<LocationTypeEnum> destinationLocationType = getLocationType(simpleTask.getDestination());
if(originLocationType.contains(LocationTypeEnum.UNKNOWN) || destinationLocationType.contains(LocationTypeEnum.UNKNOWN)) {
return new Tuple2<>("任务起点或目标位置未知", null);
}
// 检查其起点和终点是什么类型
if(originLocationType.contains(LocationTypeEnum.STACKER) && destinationLocationType.contains(LocationTypeEnum.STACKER)) {
appStockSingleTask.setTaskType(StockSingleTaskTypeEnum.STACKER_MOVE.getCode());
} else if(originLocationType.contains(LocationTypeEnum.CONVEY) && destinationLocationType.contains(LocationTypeEnum.STACKER)) {
appStockSingleTask.setTaskType(StockSingleTaskTypeEnum.STACKER_IN.getCode());
}else if(originLocationType.contains(LocationTypeEnum.STACKER) && destinationLocationType.contains(LocationTypeEnum.CONVEY)) {
appStockSingleTask.setTaskType(StockSingleTaskTypeEnum.STACKER_OUT.getCode());
}
return new Tuple2<>(null, appStockSingleTask);
} else if (simpleTaskType == StockSingleTaskTypeEnum.CONVEY_MOVE) {
appStockSingleTask.setTaskType(StockSingleTaskTypeEnum.CONVEY_MOVE.getCode());
return new Tuple2<>(null, appStockSingleTask);
} else if (simpleTaskType == StockSingleTaskTypeEnum.AGV) {
appStockSingleTask.setTaskType(StockSingleTaskTypeEnum.AGV.getCode());
return new Tuple2<>(null, appStockSingleTask);
} else if (simpleTaskType == StockSingleTaskTypeEnum.SHUTTLE) {
appStockSingleTask.setTaskType(StockSingleTaskTypeEnum.SHUTTLE.getCode());
return new Tuple2<>(null, appStockSingleTask);
}
return new Tuple2<>("任务类型异常", null);
}
/**
* 创建下一个自动任务的子任务
* @param composeTask 组合任务
* @param lastStockSingleTask 上一个子任务
* @return < 错误信息 子任务 >
*/
public Tuple2<String, AppStockSingleTask> createNextAutoStockSingleTask(AppStockComposeTask composeTask, AppStockSingleTask lastStockSingleTask) {
Tuple2<String, List<SimpleTask>> calculateTaskResult = calculateTask(lastStockSingleTask.getDestination(), composeTask.getDestination());
if(calculateTaskResult.getItem1() != null) {
return new Tuple2<>(calculateTaskResult.getItem1(), null);
}
List<SimpleTask> simpleTaskList = calculateTaskResult.getItem2();
if(simpleTaskList == null || simpleTaskList.isEmpty()) {
return new Tuple2<>("任务路径无法计算", null);
}
SimpleTask simpleTask = simpleTaskList.getFirst();
StockSingleTaskTypeEnum simpleTaskType = StockSingleTaskTypeEnum.getBySimpleTaskType(simpleTask.getTaskType());
if(simpleTaskType == null || simpleTaskType == StockSingleTaskTypeEnum.UNKNOWN) {
return new Tuple2<>("任务类型无法识别", null);
}
Integer newTaskId = plcTaskIdHelper.newTaskId();
if(newTaskId == null || newTaskId == 0) {
return new Tuple2<>("数据服务异常", null);
}
AppStockSingleTask appStockSingleTask = new AppStockSingleTask();
appStockSingleTask.setTaskId(AppUUIDUtils.getNewUUID());
appStockSingleTask.setTaskGroup(composeTask.getTaskGroup());
appStockSingleTask.setPlcTaskId(newTaskId);
appStockSingleTask.setUpperTaskId(composeTask.getTaskId());
appStockSingleTask.setExecuteMachine(0);
appStockSingleTask.setTaskType(StockSingleTaskTypeEnum.STACKER_AUTO.getCode()); // 暂时赋值下面会重新具体赋值
appStockSingleTask.setOrigin(simpleTask.getOrigin());
appStockSingleTask.setDestination(simpleTask.getDestination());
appStockSingleTask.setComposeDestination(composeTask.getDestination());
appStockSingleTask.setTaskStatus(StockSingleTaskStatusEnum.CREATE.getCode());
appStockSingleTask.setCanCancel(TrueOrFalseEnum.TRUE.getCode());
appStockSingleTask.setPriority(composeTask.getPriority());
appStockSingleTask.setVehicleNo(composeTask.getVehicleNo());
appStockSingleTask.setVehicleNoNumber(AppStringUtils.forceToInt(composeTask.getVehicleNo()));
appStockSingleTask.setVehicleSize(composeTask.getVehicleSize());
appStockSingleTask.setWeight(composeTask.getWeight());
appStockSingleTask.setCreateTime(LocalDateTime.now());
appStockSingleTask.setUpdateTime(LocalDateTime.now());
appStockSingleTask.setTaskSource(composeTask.getTaskSource());
appStockSingleTask.setCreatePerson(composeTask.getCreatePerson());
// 堆垛机任务
if(simpleTaskType == StockSingleTaskTypeEnum.STACKER_AUTO) {
List<LocationTypeEnum> originLocationType = getLocationType(simpleTask.getOrigin());
List<LocationTypeEnum> destinationLocationType = getLocationType(simpleTask.getDestination());
if(originLocationType.contains(LocationTypeEnum.UNKNOWN) || destinationLocationType.contains(LocationTypeEnum.UNKNOWN)) {
return new Tuple2<>("任务起点或目标位置未知", null);
}
// 检查其起点和终点是什么类型
if(originLocationType.contains(LocationTypeEnum.STACKER) && destinationLocationType.contains(LocationTypeEnum.STACKER)) {
appStockSingleTask.setTaskType(StockSingleTaskTypeEnum.STACKER_MOVE.getCode());
} else if(originLocationType.contains(LocationTypeEnum.CONVEY) && destinationLocationType.contains(LocationTypeEnum.STACKER)) {
appStockSingleTask.setTaskType(StockSingleTaskTypeEnum.STACKER_IN.getCode());
}else if(originLocationType.contains(LocationTypeEnum.STACKER) && destinationLocationType.contains(LocationTypeEnum.CONVEY)) {
appStockSingleTask.setTaskType(StockSingleTaskTypeEnum.STACKER_OUT.getCode());
}
return new Tuple2<>(null, appStockSingleTask);
} else if (simpleTaskType == StockSingleTaskTypeEnum.CONVEY_MOVE) {
appStockSingleTask.setTaskType(StockSingleTaskTypeEnum.CONVEY_MOVE.getCode());
return new Tuple2<>(null, appStockSingleTask);
} else if (simpleTaskType == StockSingleTaskTypeEnum.AGV) {
appStockSingleTask.setTaskType(StockSingleTaskTypeEnum.AGV.getCode());
return new Tuple2<>(null, appStockSingleTask);
} else if (simpleTaskType == StockSingleTaskTypeEnum.SHUTTLE) {
appStockSingleTask.setTaskType(StockSingleTaskTypeEnum.SHUTTLE.getCode());
return new Tuple2<>(null, appStockSingleTask);
}
return new Tuple2<>("任务类型异常", null);
}
/**
* 创建子任务 --- 如果不是入库任务需要判断起点是不是为空如果为空则不调用此方法
* @param composeTask 组合任务
* @param origin 起点
* @param destination 目标位置
* @return 子任务
*/
public Tuple2<String, AppStockSingleTask> createStockSingleTask(AppStockComposeTask composeTask, String origin, String destination) {
Integer newTaskId = plcTaskIdHelper.newTaskId(); // 获取PLC任务ID
if(newTaskId == null || newTaskId == 0) {
return new Tuple2<>("数据服务异常", null);
}
if(composeTask.getTaskType().equals(StockComposeTaskTypeEnum.IN.getCode()) && AppStringUtils.isEmpty(composeTask.getOrigin()) && AppStringUtils.isNotEmpty(destination)) { // 入库任务并且起点是空的默认只生成一个堆垛机入库任务
AppStockSingleTask appStockSingleTask = new AppStockSingleTask();
appStockSingleTask.setTaskId(AppUUIDUtils.getNewUUID());
appStockSingleTask.setTaskGroup(composeTask.getTaskGroup());
appStockSingleTask.setPlcTaskId(newTaskId);
appStockSingleTask.setUpperTaskId(composeTask.getTaskId());
appStockSingleTask.setExecuteMachine(StockExecuteEquipmentEnum.STACKER.getCode());
appStockSingleTask.setTaskType(StockSingleTaskTypeEnum.STACKER_IN.getCode());
appStockSingleTask.setOrigin(origin);
appStockSingleTask.setDestination(destination);
appStockSingleTask.setComposeDestination(composeTask.getDestination());
appStockSingleTask.setTaskStatus(StockSingleTaskStatusEnum.CREATE.getCode());
appStockSingleTask.setCanCancel(TrueOrFalseEnum.TRUE.getCode());
appStockSingleTask.setPriority(composeTask.getPriority());
appStockSingleTask.setVehicleNo(composeTask.getVehicleNo());
appStockSingleTask.setVehicleNoNumber(AppStringUtils.forceToInt(composeTask.getVehicleNo()));
appStockSingleTask.setVehicleSize(composeTask.getVehicleSize());
appStockSingleTask.setWeight(composeTask.getWeight());
appStockSingleTask.setCreateTime(LocalDateTime.now());
appStockSingleTask.setUpdateTime(LocalDateTime.now());
appStockSingleTask.setTaskSource(composeTask.getTaskSource());
appStockSingleTask.setCreatePerson(composeTask.getCreatePerson());
return new Tuple2<>(null, appStockSingleTask);
}
if(composeTask.getTaskType().equals(StockComposeTaskTypeEnum.OUT.getCode()) && AppStringUtils.isEmpty(composeTask.getDestination()) && AppStringUtils.isNotEmpty(origin)) { // 出库任务并且终点是空的默认只生成一个堆垛机出库任务
AppStockSingleTask appStockSingleTask = new AppStockSingleTask();
appStockSingleTask.setTaskId(AppUUIDUtils.getNewUUID());
appStockSingleTask.setTaskGroup(composeTask.getTaskGroup());
appStockSingleTask.setPlcTaskId(newTaskId);
appStockSingleTask.setUpperTaskId(composeTask.getTaskId());
appStockSingleTask.setExecuteMachine(StockExecuteEquipmentEnum.STACKER.getCode());
appStockSingleTask.setTaskType(StockSingleTaskTypeEnum.STACKER_OUT.getCode());
appStockSingleTask.setOrigin(origin);
appStockSingleTask.setDestination(destination);
appStockSingleTask.setComposeDestination(composeTask.getDestination());
appStockSingleTask.setTaskStatus(StockSingleTaskStatusEnum.CREATE.getCode());
appStockSingleTask.setCanCancel(TrueOrFalseEnum.TRUE.getCode());
appStockSingleTask.setPriority(composeTask.getPriority());
appStockSingleTask.setVehicleNo(composeTask.getVehicleNo());
appStockSingleTask.setVehicleNoNumber(AppStringUtils.forceToInt(composeTask.getVehicleNo()));
appStockSingleTask.setVehicleSize(composeTask.getVehicleSize());
appStockSingleTask.setWeight(composeTask.getWeight());
appStockSingleTask.setCreateTime(LocalDateTime.now());
appStockSingleTask.setUpdateTime(LocalDateTime.now());
appStockSingleTask.setTaskSource(composeTask.getTaskSource());
appStockSingleTask.setCreatePerson(composeTask.getCreatePerson());
return new Tuple2<>(null, appStockSingleTask);
}
if(AppStringUtils.isEmpty(destination)) {
return new Tuple2<>("目标位置不能为空", null);
}
// 其他任务动态计算路径 ---- 注意此处算的如果是输送线任务是直接到独立任务表不是到单独的 输送线任务表
if(AppStringUtils.isEmpty(origin)) {
return new Tuple2<>("起点位置不能为空", null); // 该判断需在调用本方法之前判断否则任务会被以异常状态取消
}
List<Tuple2<LocationTypeEnum, String>> courseCalculateResult = locationManage.courseCalculate(origin, destination);
if(courseCalculateResult == null || courseCalculateResult.isEmpty()) {
return new Tuple2<>("无法计算路径", null);
}
if(courseCalculateResult.size() < 2) {
return new Tuple2<>("计算的路径不正确", null);
}
Tuple2<LocationTypeEnum, String> originCourseInfo = courseCalculateResult.getFirst(); // 取第一个路径
Tuple2<LocationTypeEnum, String> destinationCourseInfo = courseCalculateResult.get(1);
// 生成任务
StockExecuteEquipmentEnum executeEquipment = StockExecuteEquipmentEnum.getExecuteEquipment(originCourseInfo.getItem1());
StockSingleTaskTypeEnum simpleTaskType = StockSingleTaskTypeEnum.getByComposeTaskTypeAndExecuteMachine(StockComposeTaskTypeEnum.getByCode(composeTask.getTaskType()), executeEquipment);
AppStockSingleTask appStockSingleTask = new AppStockSingleTask();
appStockSingleTask.setTaskId(AppUUIDUtils.getNewUUID());
appStockSingleTask.setTaskGroup(composeTask.getTaskGroup());
appStockSingleTask.setPlcTaskId(newTaskId);
appStockSingleTask.setUpperTaskId(composeTask.getTaskId());
appStockSingleTask.setExecuteMachine(executeEquipment.getCode());
appStockSingleTask.setTaskType(simpleTaskType.getCode());
appStockSingleTask.setOrigin(originCourseInfo.getItem2());
appStockSingleTask.setDestination(destinationCourseInfo.getItem2());
appStockSingleTask.setComposeDestination(composeTask.getDestination());
appStockSingleTask.setTaskStatus(StockSingleTaskStatusEnum.CREATE.getCode());
appStockSingleTask.setCanCancel(TrueOrFalseEnum.TRUE.getCode());
appStockSingleTask.setPriority(composeTask.getPriority());
appStockSingleTask.setVehicleNo(composeTask.getVehicleNo());
appStockSingleTask.setVehicleNoNumber(AppStringUtils.forceToInt(composeTask.getVehicleNo()));
appStockSingleTask.setVehicleSize(composeTask.getVehicleSize());
appStockSingleTask.setWeight(composeTask.getWeight());
appStockSingleTask.setCreateTime(LocalDateTime.now());
appStockSingleTask.setUpdateTime(LocalDateTime.now());
appStockSingleTask.setTaskSource(composeTask.getTaskSource());
appStockSingleTask.setCreatePerson(composeTask.getCreatePerson());
return new Tuple2<>(null, appStockSingleTask);
}
/**
* 创建入库任务
*
* @param composeTask 组合任务
* @return 入库任务结果
*/
public Tuple2<String, AppStockSingleTask> createStockInSingleTask(AppStockComposeTask composeTask) {
String origin = composeTask.getOrigin();
String destination = composeTask.getDestination();
if(AppStringUtils.isEmpty(origin) && AppStringUtils.isEmpty(destination)) {
return new Tuple2<>("起点或者终点都为空", null);
}
List<String> originLocationAreas = getLocationArea(origin);
List<String> destinationLocationAreas = getLocationArea(destination);
if(originLocationAreas == null && destinationLocationAreas == null) {
return new Tuple2<>("起点和终点均是不受支持的点位", null);
}
// 入库任务以终点为最终目标
// 判断终点点位类型
List<LocationTypeEnum> destinationLocationTypes = getLocationType(destination);
if(destinationLocationTypes.contains(LocationTypeEnum.UNKNOWN)) {
return new Tuple2<>("终点点位类型未知", null);
}
// 这里终点只能有一个点位类型多个计报错
if(destinationLocationTypes.size() > 1) {
return new Tuple2<>("终点点位类型不能为多个,请检查基础资料", null);
}
SimpleTaskEnum simpleTaskEnum = SimpleTaskEnum.getSimpleTaskEnum(destinationLocationTypes.getFirst());
StockSingleTaskTypeEnum simpleTaskType = StockSingleTaskTypeEnum.getBySimpleTaskType(simpleTaskEnum);
if(simpleTaskEnum.equals(SimpleTaskEnum.STACKER)) {
simpleTaskType = StockSingleTaskTypeEnum.STACKER_IN;
}
Integer newTaskId = plcTaskIdHelper.newTaskId();
if(newTaskId == null || newTaskId == 0) {
return new Tuple2<>("数据服务异常", null);
}
AppStockSingleTask appStockSingleTask = new AppStockSingleTask();
appStockSingleTask.setTaskId(AppUUIDUtils.getNewUUID());
appStockSingleTask.setTaskGroup(composeTask.getTaskGroup());
appStockSingleTask.setPlcTaskId(newTaskId);
appStockSingleTask.setUpperTaskId(composeTask.getTaskId());
appStockSingleTask.setExecuteMachine(StockExecuteEquipmentEnum.STACKER.getCode());
appStockSingleTask.setTaskType(simpleTaskType.getCode());
appStockSingleTask.setOrigin(origin);
appStockSingleTask.setDestination(destination);
appStockSingleTask.setComposeDestination(composeTask.getDestination());
appStockSingleTask.setTaskStatus(StockSingleTaskStatusEnum.CREATE.getCode());
appStockSingleTask.setCanCancel(TrueOrFalseEnum.TRUE.getCode());
appStockSingleTask.setPriority(composeTask.getPriority());
appStockSingleTask.setVehicleNo(composeTask.getVehicleNo());
appStockSingleTask.setVehicleNoNumber(AppStringUtils.forceToInt(composeTask.getVehicleNo()));
appStockSingleTask.setVehicleSize(composeTask.getVehicleSize());
appStockSingleTask.setWeight(composeTask.getWeight());
appStockSingleTask.setCreateTime(LocalDateTime.now());
appStockSingleTask.setUpdateTime(LocalDateTime.now());
appStockSingleTask.setTaskSource(composeTask.getTaskSource());
appStockSingleTask.setCreatePerson(composeTask.getCreatePerson());
return new Tuple2<>(null, appStockSingleTask);
}
/**
* 创建出库任务
*
* @param composeTask 组合任务
* @return 出库任务结果
*/
public Tuple2<String, AppStockSingleTask> createStockOutSingleTask(AppStockComposeTask composeTask) {
String origin = composeTask.getOrigin();
String destination = composeTask.getDestination();
if(AppStringUtils.isEmpty(origin) && AppStringUtils.isEmpty(destination)) {
return new Tuple2<>("起点或者终点都为空", null);
}
List<String> originLocationAreas = getLocationArea(origin);
List<String> destinationLocationAreas = getLocationArea(destination);
if(originLocationAreas == null && destinationLocationAreas == null) {
return new Tuple2<>("起点和终点均是不受支持的点位", null);
}
// 出库任务以起点为最终数据
// 判断起点点位类型
List<LocationTypeEnum> originLocationTypes = getLocationType(origin);
if(originLocationTypes.contains(LocationTypeEnum.UNKNOWN)) {
return new Tuple2<>("起点点位类型未知", null);
}
// 这里起点只能有一个点位类型多个计报错
if(originLocationTypes.size() > 1) {
return new Tuple2<>("起点点位类型不能为多个,请检查基础资料", null);
}
SimpleTaskEnum simpleTaskEnum = SimpleTaskEnum.getSimpleTaskEnum(originLocationTypes.getFirst());
StockSingleTaskTypeEnum simpleTaskType = StockSingleTaskTypeEnum.getBySimpleTaskType(simpleTaskEnum);
if(simpleTaskEnum.equals(SimpleTaskEnum.STACKER)) {
simpleTaskType = StockSingleTaskTypeEnum.STACKER_OUT;
}
Integer newTaskId = plcTaskIdHelper.newTaskId();
if(newTaskId == null || newTaskId == 0) {
return new Tuple2<>("数据服务异常", null);
}
AppStockSingleTask appStockSingleTask = new AppStockSingleTask();
appStockSingleTask.setTaskId(AppUUIDUtils.getNewUUID());
appStockSingleTask.setTaskGroup(composeTask.getTaskGroup());
appStockSingleTask.setPlcTaskId(newTaskId);
appStockSingleTask.setUpperTaskId(composeTask.getTaskId());
appStockSingleTask.setExecuteMachine(StockExecuteEquipmentEnum.STACKER.getCode());
appStockSingleTask.setTaskType(simpleTaskType.getCode());
appStockSingleTask.setOrigin(origin);
appStockSingleTask.setDestination(destination);
appStockSingleTask.setComposeDestination(composeTask.getDestination());
appStockSingleTask.setTaskStatus(StockSingleTaskStatusEnum.CREATE.getCode());
appStockSingleTask.setCanCancel(TrueOrFalseEnum.TRUE.getCode());
appStockSingleTask.setPriority(composeTask.getPriority());
appStockSingleTask.setVehicleNo(composeTask.getVehicleNo());
appStockSingleTask.setVehicleNoNumber(AppStringUtils.forceToInt(composeTask.getVehicleNo()));
appStockSingleTask.setVehicleSize(composeTask.getVehicleSize());
appStockSingleTask.setWeight(composeTask.getWeight());
appStockSingleTask.setCreateTime(LocalDateTime.now());
appStockSingleTask.setUpdateTime(LocalDateTime.now());
appStockSingleTask.setTaskSource(composeTask.getTaskSource());
appStockSingleTask.setCreatePerson(composeTask.getCreatePerson());
return new Tuple2<>(null, appStockSingleTask);
}
/**
* 创建移库任务
*
* @param composeTask 组合任务
* @return 移库任务结果
*/
public Tuple2<String, AppStockSingleTask> createStockMoveSingleTask(AppStockComposeTask composeTask) {
String origin = composeTask.getOrigin();
String destination = composeTask.getDestination();
if(AppStringUtils.isEmpty(origin) && AppStringUtils.isEmpty(destination)) {
return new Tuple2<>("起点或者终点都为空", null);
}
// 移库任务
List<String> originLocationAreas = getLocationArea(origin);
if(originLocationAreas == null) {
return new Tuple2<>("起点是不受支持的点位", null);
}
List<String> destinationLocationAreas = getLocationArea(destination);
if(destinationLocationAreas == null) {
return new Tuple2<>("终点是不受支持的点位", null);
}
// 判断起点和终点是否是同一种点位区域
List<String> sameAreaList = originLocationAreas.stream().filter(destinationLocationAreas::contains).toList();
if(sameAreaList.isEmpty()) {
return new Tuple2<>("起点和终点不是同区域点位", null);
}
// 不是空的说明是同一种类型
List<LocationTypeEnum> originLocationTypes = getLocationType(origin);
List<LocationTypeEnum> destinationLocationTypes = getLocationType(destination);
if(originLocationTypes.contains(LocationTypeEnum.UNKNOWN) || destinationLocationTypes.contains(LocationTypeEnum.UNKNOWN)) {
return new Tuple2<>("起点和终点属于同一区域,但检索不到点位类型,请检查基础资料", null); // 正常不会发生
}
// 查找两个list中相同的项
List<LocationTypeEnum> sameLocationType = originLocationTypes.stream().filter(destinationLocationTypes::contains).toList();
if(sameLocationType.isEmpty()) {
return new Tuple2<>("起点和终点属于同一区域,其点位类型不一致,不允许此设置,请检查区域设置是否合法", null);
}
SimpleTaskEnum simpleTaskEnum = SimpleTaskEnum.getSimpleTaskEnum(sameLocationType.getFirst());
StockSingleTaskTypeEnum simpleTaskType = StockSingleTaskTypeEnum.getBySimpleTaskType(simpleTaskEnum);
if(simpleTaskEnum.equals(SimpleTaskEnum.STACKER)) {
simpleTaskType = StockSingleTaskTypeEnum.STACKER_MOVE;
}
Integer newTaskId = plcTaskIdHelper.newTaskId();
if(newTaskId == null || newTaskId == 0) {
return new Tuple2<>("数据服务异常", null);
}
AppStockSingleTask appStockSingleTask = new AppStockSingleTask();
appStockSingleTask.setTaskId(AppUUIDUtils.getNewUUID());
appStockSingleTask.setTaskGroup(composeTask.getTaskGroup());
appStockSingleTask.setPlcTaskId(newTaskId);
appStockSingleTask.setUpperTaskId(composeTask.getTaskId());
appStockSingleTask.setExecuteMachine(StockExecuteEquipmentEnum.STACKER.getCode());
appStockSingleTask.setTaskType(simpleTaskType.getCode());
appStockSingleTask.setOrigin(origin);
appStockSingleTask.setDestination(destination);
appStockSingleTask.setComposeDestination(composeTask.getDestination());
appStockSingleTask.setTaskStatus(StockSingleTaskStatusEnum.CREATE.getCode());
appStockSingleTask.setCanCancel(TrueOrFalseEnum.TRUE.getCode());
appStockSingleTask.setPriority(composeTask.getPriority());
appStockSingleTask.setVehicleNo(composeTask.getVehicleNo());
appStockSingleTask.setVehicleNoNumber(AppStringUtils.forceToInt(composeTask.getVehicleNo()));
appStockSingleTask.setVehicleSize(composeTask.getVehicleSize());
appStockSingleTask.setWeight(composeTask.getWeight());
appStockSingleTask.setCreateTime(LocalDateTime.now());
appStockSingleTask.setUpdateTime(LocalDateTime.now());
appStockSingleTask.setTaskSource(composeTask.getTaskSource());
appStockSingleTask.setCreatePerson(composeTask.getCreatePerson());
return new Tuple2<>(null, appStockSingleTask);
}
}

View File

@ -0,0 +1,233 @@
package org.wcs.business.stock.impl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.wcs.business.stock.intf.IComposeTaskStatusReporter;
import org.wcs.constant.ConstantData;
import org.wcs.constant.enums.http.StockCallBackStatusEnum;
import org.wcs.model.bo.tuple.Tuple2;
import org.wcs.model.dto.client.StockTaskStatusUploadReq;
import org.wcs.model.po.app.AppStockComposeTask;
import org.wcs.plugin.webHttpClient.WebHttpClient;
import org.wcs.plugin.webHttpClient.model.HttpRequest;
import org.wcs.plugin.webHttpClient.model.HttpResponse;
import org.wcs.utils.AppStringUtils;
@Slf4j
@Service
@RequiredArgsConstructor
public class ComposeTaskStatusReporter implements IComposeTaskStatusReporter {
private static final String callBackAddressKey = "_STOCK_TASK_STATUS_CALLBACK";
/**
* 任务开始
* @param composeTask 任务信息
* @param message 消息
* @return <错误信息响应数据 >
*/
@Override
public Tuple2<String, String> taskStart(AppStockComposeTask composeTask, String message) {
if(composeTask.getTaskSource().equals(ConstantData.SYSTEM_NAME)) {
return new Tuple2<>(null, "内部任务不上报"); // WCS 系统自己创建的不执行上传动作
}
StockTaskStatusUploadReq stockTaskStatusUploadReq = new StockTaskStatusUploadReq();
stockTaskStatusUploadReq.setTaskId(composeTask.getUpperTaskId());
stockTaskStatusUploadReq.setTaskStatus(StockCallBackStatusEnum.START.getCode());
stockTaskStatusUploadReq.setDestination("");
stockTaskStatusUploadReq.setVehicleNo(composeTask.getVehicleNo());
stockTaskStatusUploadReq.setMessage(message);
/* 构造请求体 */
HttpRequest httpRequest = HttpRequest.buildStart().post().setAddressKey(composeTask.getTaskSource() + callBackAddressKey)
.setBody(AppStringUtils.objectToString(stockTaskStatusUploadReq)).buildEnd();
HttpResponse httpResponse = WebHttpClient.httpRequest(httpRequest);
log.info("{} 任务开始上报上位系统,请求结果:{}", composeTask.getUpperTaskId(), httpResponse.isSuccess());
if(!httpResponse.isBaseDataError() && httpResponse.isSuccess()) {
return new Tuple2<>(null, httpResponse.getResponseText());
}
String errText = "";
if(AppStringUtils.isNotEmpty(httpResponse.getErrText())) {
errText += httpResponse.getErrText();
}
if(httpResponse.getException() != null) {
errText += httpResponse.getException().getMessage();
}
return new Tuple2<>(errText, null);
}
/**
* 任务完成
* @param composeTask 任务信息
* @param message 消息
* @return <错误信息响应数据 >
*/
@Override
public Tuple2<String, String> taskFinish(AppStockComposeTask composeTask, String message) {
if(composeTask.getTaskSource().equals(ConstantData.SYSTEM_NAME)) {
return new Tuple2<>(null, "内部任务不上报"); // WCS 系统自己创建的不执行上传动作
}
StockTaskStatusUploadReq stockTaskStatusUploadReq = new StockTaskStatusUploadReq();
stockTaskStatusUploadReq.setTaskId(composeTask.getUpperTaskId());
stockTaskStatusUploadReq.setTaskStatus(StockCallBackStatusEnum.FINISH.getCode());
stockTaskStatusUploadReq.setDestination("");
stockTaskStatusUploadReq.setVehicleNo(composeTask.getVehicleNo());
stockTaskStatusUploadReq.setMessage(message);
/* 构造请求体 */
HttpRequest httpRequest = HttpRequest.buildStart().post().setAddressKey(composeTask.getTaskSource() + callBackAddressKey)
.setBody(AppStringUtils.objectToString(stockTaskStatusUploadReq)).buildEnd();
HttpResponse httpResponse = WebHttpClient.httpRequest(httpRequest);
log.info("{} 任务完成上报上位系统,请求结果:{}", composeTask.getUpperTaskId(), httpResponse.isSuccess());
if(!httpResponse.isBaseDataError() && httpResponse.isSuccess()) {
return new Tuple2<>(null, httpResponse.getResponseText());
}
String errText = "";
if(AppStringUtils.isNotEmpty(httpResponse.getErrText())) {
errText += httpResponse.getErrText();
}
if(httpResponse.getException() != null) {
errText += httpResponse.getException().getMessage();
}
return new Tuple2<>(errText, null);
}
/**
* 任务错误
* @param composeTask 任务信息
* @param message 消息
* @return <错误信息响应数据 >
*/
@Override
public Tuple2<String, String> taskError(AppStockComposeTask composeTask, String message) {
if(composeTask.getTaskSource().equals(ConstantData.SYSTEM_NAME)) {
return new Tuple2<>(null, "内部任务不上报"); // WCS 系统自己创建的不执行上传动作
}
StockTaskStatusUploadReq stockTaskStatusUploadReq = new StockTaskStatusUploadReq();
stockTaskStatusUploadReq.setTaskId(composeTask.getUpperTaskId());
stockTaskStatusUploadReq.setTaskStatus(StockCallBackStatusEnum.ERROR.getCode());
stockTaskStatusUploadReq.setDestination("");
stockTaskStatusUploadReq.setVehicleNo(composeTask.getVehicleNo());
stockTaskStatusUploadReq.setMessage(message);
/* 构造请求体 */
HttpRequest httpRequest = HttpRequest.buildStart().post().setAddressKey(composeTask.getTaskSource() + callBackAddressKey)
.setBody(AppStringUtils.objectToString(stockTaskStatusUploadReq)).buildEnd();
HttpResponse httpResponse = WebHttpClient.httpRequest(httpRequest);
log.info("{} 任务错误上报上位系统,请求结果:{}", composeTask.getUpperTaskId(), httpResponse.isSuccess());
if(!httpResponse.isBaseDataError() && httpResponse.isSuccess()) {
return new Tuple2<>(null, httpResponse.getResponseText());
}
String errText = "";
if(AppStringUtils.isNotEmpty(httpResponse.getErrText())) {
errText += httpResponse.getErrText();
}
if(httpResponse.getException() != null) {
errText += httpResponse.getException().getMessage();
}
return new Tuple2<>(errText, null);
}
/**
* 任务取消
* @param composeTask 任务信息
* @param message 消息
* @return <错误信息响应数据 >
*/
@Override
public Tuple2<String, String> taskCancel(AppStockComposeTask composeTask, String message) {
if(composeTask.getTaskSource().equals(ConstantData.SYSTEM_NAME)) {
return new Tuple2<>(null, "内部任务不上报"); // WCS 系统自己创建的不执行上传动作
}
StockTaskStatusUploadReq stockTaskStatusUploadReq = new StockTaskStatusUploadReq();
stockTaskStatusUploadReq.setTaskId(composeTask.getUpperTaskId());
stockTaskStatusUploadReq.setTaskStatus(StockCallBackStatusEnum.CANCEL.getCode());
stockTaskStatusUploadReq.setDestination("");
stockTaskStatusUploadReq.setVehicleNo(composeTask.getVehicleNo());
stockTaskStatusUploadReq.setMessage(message);
/* 构造请求体 */
HttpRequest httpRequest = HttpRequest.buildStart().post().setAddressKey(composeTask.getTaskSource() + callBackAddressKey)
.setBody(AppStringUtils.objectToString(stockTaskStatusUploadReq)).buildEnd();
HttpResponse httpResponse = WebHttpClient.httpRequest(httpRequest);
log.info("{} 任务取消上报上位系统,请求结果:{}", composeTask.getUpperTaskId(), httpResponse.isSuccess());
if(!httpResponse.isBaseDataError() && httpResponse.isSuccess()) {
return new Tuple2<>(null, httpResponse.getResponseText());
}
String errText = "";
if(AppStringUtils.isNotEmpty(httpResponse.getErrText())) {
errText += httpResponse.getErrText();
}
if(httpResponse.getException() != null) {
errText += httpResponse.getException().getMessage();
}
return new Tuple2<>(errText, null);
}
/**
* 任务重复入库
* @param composeTask 任务信息
* @param message 消息
* @return <错误信息响应数据 >
*/
@Override
public Tuple2<String, String> taskDoubleIn(AppStockComposeTask composeTask, String message) {
if(composeTask.getTaskSource().equals(ConstantData.SYSTEM_NAME)) {
return new Tuple2<>(null, "内部任务不上报"); // WCS 系统自己创建的不执行上传动作
}
StockTaskStatusUploadReq stockTaskStatusUploadReq = new StockTaskStatusUploadReq();
stockTaskStatusUploadReq.setTaskId(composeTask.getUpperTaskId());
stockTaskStatusUploadReq.setTaskStatus(StockCallBackStatusEnum.DOUBLE_IN.getCode());
stockTaskStatusUploadReq.setDestination("");
stockTaskStatusUploadReq.setVehicleNo(composeTask.getVehicleNo());
stockTaskStatusUploadReq.setMessage(message);
/* 构造请求体 */
HttpRequest httpRequest = HttpRequest.buildStart().post().setAddressKey(composeTask.getTaskSource() + callBackAddressKey)
.setBody(AppStringUtils.objectToString(stockTaskStatusUploadReq)).buildEnd();
HttpResponse httpResponse = WebHttpClient.httpRequest(httpRequest);
log.info("{} 任务重复入库上报上位系统,请求结果:{}", composeTask.getUpperTaskId(), httpResponse.isSuccess());
if(!httpResponse.isBaseDataError() && httpResponse.isSuccess()) {
return new Tuple2<>(null, httpResponse.getResponseText());
}
String errText = "";
if(AppStringUtils.isNotEmpty(httpResponse.getErrText())) {
errText += httpResponse.getErrText();
}
if(httpResponse.getException() != null) {
errText += httpResponse.getException().getMessage();
}
return new Tuple2<>(errText, null);
}
/**
* 任务空出库
* @param composeTask 任务信息
* @param message 消息
* @return <错误信息响应数据 >
*/
@Override
public Tuple2<String, String> taskEmptyOut(AppStockComposeTask composeTask, String message) {
if(composeTask.getTaskSource().equals(ConstantData.SYSTEM_NAME)) {
return new Tuple2<>(null, "内部任务不上报"); // WCS 系统自己创建的不执行上传动作
}
StockTaskStatusUploadReq stockTaskStatusUploadReq = new StockTaskStatusUploadReq();
stockTaskStatusUploadReq.setTaskId(composeTask.getUpperTaskId());
stockTaskStatusUploadReq.setTaskStatus(StockCallBackStatusEnum.EMPTY_OUT.getCode());
stockTaskStatusUploadReq.setDestination("");
stockTaskStatusUploadReq.setVehicleNo(composeTask.getVehicleNo());
stockTaskStatusUploadReq.setMessage(message);
/* 构造请求体 */
HttpRequest httpRequest = HttpRequest.buildStart().post().setAddressKey(composeTask.getTaskSource() + callBackAddressKey)
.setBody(AppStringUtils.objectToString(stockTaskStatusUploadReq)).buildEnd();
HttpResponse httpResponse = WebHttpClient.httpRequest(httpRequest);
log.info("{} 任务空出库上报上位系统,请求结果:{}", composeTask.getUpperTaskId(), httpResponse.isSuccess());
if(!httpResponse.isBaseDataError() && httpResponse.isSuccess()) {
return new Tuple2<>(null, httpResponse.getResponseText());
}
String errText = "";
if(AppStringUtils.isNotEmpty(httpResponse.getErrText())) {
errText += httpResponse.getErrText();
}
if(httpResponse.getException() != null) {
errText += httpResponse.getException().getMessage();
}
return new Tuple2<>(errText, null);
}
}

View File

@ -0,0 +1,393 @@
package org.wcs.business.stock.impl;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.TypeReference;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.wcs.annotation.ScanMethodTag;
import org.wcs.business.stock.intf.IScanMethod;
import org.wcs.constant.ConstantData;
import org.wcs.constant.enums.common.TrueOrFalseEnum;
import org.wcs.constant.enums.database.StackerLocationStatusEnum;
import org.wcs.constant.enums.database.StockComposeTaskStatusEnum;
import org.wcs.constant.enums.database.StockComposeTaskStepStatusEnum;
import org.wcs.constant.enums.database.StockComposeTaskTypeEnum;
import org.wcs.helper.PlcTaskIdHelper;
import org.wcs.mapper.intf.AppStackerLocationService;
import org.wcs.mapper.intf.AppStockComposeTaskService;
import org.wcs.model.dto.client.StockLoginDataResp;
import org.wcs.model.dto.client.StockLoginReq;
import org.wcs.model.dto.client.UpperSystemDataResponse;
import org.wcs.model.po.app.AppStackerLocation;
import org.wcs.model.po.app.AppStockComposeTask;
import org.wcs.model.po.app.AppStockScan;
import org.wcs.plugin.plc.PlcCommunicationFactory;
import org.wcs.plugin.plc.model.ScanInfo;
import org.wcs.plugin.plc.model.ScanTask;
import org.wcs.plugin.webHttpClient.WebHttpClient;
import org.wcs.plugin.webHttpClient.model.HttpRequest;
import org.wcs.plugin.webHttpClient.model.HttpResponse;
import org.wcs.utils.AppStringUtils;
import org.wcs.utils.AppUUIDUtils;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
/**
* 扫码方法
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class ScanMethod implements IScanMethod {
private final PlcTaskIdHelper plcTaskIdHelper;
private final AppStockComposeTaskService stockComposeTaskService;
private final AppStackerLocationService stackerLocationService;
/**
* 注册堆垛机任务仅注册成功则继续执行失败则退回成功写1失败写2
* @param stockScan 扫码器信息
* @param scanInfo 扫码信息
*/
@Override
@ScanMethodTag("LOGIN_STACKER")
public void loginStackerTask(AppStockScan stockScan, ScanInfo scanInfo) {
if(stockScan == null || scanInfo == null) {
log.info("堆垛机 注册扫码器信息数据为空");
return;
}
if(scanInfo.getTag() != TrueOrFalseEnum.TRUE.getCode()) {
return;
}
log.info("注册扫码器信息: {},获得扫码数据:{}", stockScan, scanInfo);
// 检查是否扫码失败
String code = scanInfo.getVehicleNo();
if(AppStringUtils.isEmpty(code)) {
return;
}
code = code.trim().replaceAll("\\W+", ""); // 获取条码简单处理
// 读码失败
if(code.equals(ConstantData.NO_READ)) {
log.info("扫码:{},扫码失败,条码:{}",stockScan.getScanId(), code);
writeSimpleScanTask(stockScan, (short)2);
return;
}
// 读码成功
// 检查是否有该条码的任务
AppStockComposeTask stockComposeTaskCheck = new AppStockComposeTask();
stockComposeTaskCheck.setVehicleNo(code);
List<AppStockComposeTask> stockComposeTaskCheckList = stockComposeTaskService.query(stockComposeTaskCheck);
if(stockComposeTaskCheckList == null) {
log.info("扫码:{},检索堆垛机任务失败,条码:{}",stockScan.getScanId(), code);
writeSimpleScanTask(stockScan, (short)2);
return;
}
// -- 找出是否存在未执行的堆垛机入库任务
List<AppStockComposeTask> stockComposeTasks = stockComposeTaskCheckList.stream().filter(task -> StockComposeTaskStatusEnum.getAllNotExecuteStatus().contains(task.getTaskStatus()) &&
(task.getTaskType().equals(StockComposeTaskTypeEnum.AUTO.getCode()) || task.getTaskType().equals(StockComposeTaskTypeEnum.IN.getCode()))).toList();
if(!stockComposeTasks.isEmpty()) {
// 存在任务直接放行
log.info("箱号:{} 在注册位置:{},存在任务,直接放行", code, stockScan.getScanId());
writeSimpleScanTask(stockScan, (short)1);
return;
}
// 请求上位系统
StockLoginReq stockLoginReq = new StockLoginReq();
stockLoginReq.setRequestId(AppUUIDUtils.getNewUUID());
stockLoginReq.setVehicleNo(scanInfo.getVehicleNo());
stockLoginReq.setLocation(stockScan.getScanId());
stockLoginReq.setSize(Integer.valueOf(scanInfo.getVehicleSize() == null ? 0 : scanInfo.getVehicleSize()));
stockLoginReq.setLength(scanInfo.getLength() == null ? 0 : scanInfo.getLength());
stockLoginReq.setWidth(scanInfo.getWidth() == null ? 0 : scanInfo.getWidth());
stockLoginReq.setHeight(scanInfo.getHeight() == null ? 0 : scanInfo.getHeight());
stockLoginReq.setWeight(scanInfo.getWeight() == null ? 0 : scanInfo.getWeight());
HttpRequest httpRequest = HttpRequest.buildStart()
.post()
.setAddressKey(stockScan.getParam1() + "_LOGIN_STACKER")
.setBody(AppStringUtils.objectToString(stockLoginReq))
.buildEnd();
HttpResponse httpResponse = WebHttpClient.httpRequest(httpRequest);
if(!httpResponse.isSuccess()) {
log.info("扫码:{} 申请堆垛机任务失败,异常信息:{}", stockScan.getScanId(), httpResponse.getException().toString());
writeSimpleScanTask(stockScan, (short)2);
return;
}
String responseText = httpResponse.getResponseText();
UpperSystemDataResponse<StockLoginDataResp> upperSystemDataResponse = JSONObject.parseObject(responseText, new TypeReference<>(){});
if(upperSystemDataResponse == null) {
log.info("扫码:{} 申请堆垛机任务失败,返回的数据无法解析,返回信息:{}", stockScan.getScanId(), responseText);
writeSimpleScanTask(stockScan, (short)2);
return;
}
if(!upperSystemDataResponse.isOk()) {
log.info("扫码:{} 申请堆垛机任务失败,返回信息:{}", stockScan.getScanId(), upperSystemDataResponse.getMessage());
writeSimpleScanTask(stockScan, (short)2);
return;
}
log.info("扫码:{} 申请堆垛机任务成功,信息:{}", stockScan.getScanId(), upperSystemDataResponse.getMessage());
writeSimpleScanTask(stockScan, (short)1);
// 生成任务
StockLoginDataResp returnData = upperSystemDataResponse.getReturnData();
if(returnData == null) {
return;
}
// 检查任务格式是否正确
if(AppStringUtils.isEmpty(returnData.getTaskId())
|| returnData.getTaskType() == null
|| (AppStringUtils.isEmpty(returnData.getOrigin()) && AppStringUtils.isEmpty(returnData.getDestination()))) {
log.info("扫码:{} 注册堆垛机任务,任务数据不正确,任务数据:{}", stockScan.getScanId(), AppStringUtils.objectToString(returnData));
return;
}
if(StockComposeTaskTypeEnum.getByCode(returnData.getTaskType()).equals(StockComposeTaskTypeEnum.UNKNOWN)) {
log.info("扫码:{} 注册堆垛机任务,任务类型不正确,任务数据:{}", stockScan.getScanId(), AppStringUtils.objectToString(returnData));
return;
}
// 检查任务是否存在
List<AppStockComposeTask> stockComposeTaskList = stockComposeTaskService.queryByUpperTaskId(returnData.getTaskId());
if(stockComposeTaskList == null) {
log.info("扫码:{} 注册堆垛机任务查询任务失败", stockScan.getScanId());
return;
}
if(!stockComposeTaskList.isEmpty()) {
log.info("扫码:{} 注册堆垛机任务任务已经已存在任务ID{}", stockScan.getScanId(), returnData.getTaskId());
return;
}
AppStockComposeTask stockComposeTask = new AppStockComposeTask();
stockComposeTask.setTaskId(AppUUIDUtils.getNewUUID());
stockComposeTask.setTaskGroup(AppStringUtils.isEmptyOr(returnData.getTaskGroup(), returnData.getTaskId()));
stockComposeTask.setUpperTaskId(returnData.getTaskId());
stockComposeTask.setTaskType(returnData.getTaskType());
stockComposeTask.setOrigin(returnData.getOrigin());
stockComposeTask.setDestination(returnData.getDestination());
stockComposeTask.setTaskStatus(StockComposeTaskStatusEnum.CREATE.getCode());
stockComposeTask.setStepStatus(StockComposeTaskStepStatusEnum.CREATE.getCode());
stockComposeTask.setCanCancel(TrueOrFalseEnum.TRUE.getCode());
stockComposeTask.setPriority(returnData.getPriority() == null ? 5 : returnData.getPriority());
stockComposeTask.setVehicleNo(returnData.getVehicleNo());
stockComposeTask.setVehicleSize(returnData.getSize() == null ? 0 : returnData.getSize());
stockComposeTask.setWeight(BigDecimal.valueOf(returnData.getWeight() == null ? 0 : returnData.getWeight()));
stockComposeTask.setCreateTime(LocalDateTime.now());
stockComposeTask.setUpdateTime(LocalDateTime.now());
stockComposeTask.setTaskSource(returnData.getSysName());
stockComposeTask.setCreatePerson("?");
int insertResult = stockComposeTaskService.insert(stockComposeTask);
if(insertResult <= 0) {
log.info("扫码:{} 注册堆垛机任务,任务插入失败,任务数据:{}", returnData.getVehicleNo(), AppStringUtils.objectToString(stockComposeTask));
return;
}
log.info("扫码:{} 注册堆垛机任务,任务插入成功,任务数据:{}", returnData.getVehicleNo(), AppStringUtils.objectToString(stockComposeTask));
}
/**
* 注册任务并根据返回值确定该进那个堆垛机
* @param stockScan 扫码器信息
* @param scanInfo 扫码信息
*/
@Override
@ScanMethodTag("LOGIN_STACKER_SEED")
public void loginStackerTaskSeed(AppStockScan stockScan, ScanInfo scanInfo) {
if(stockScan == null || scanInfo == null) {
log.info("堆垛机 注册扫码器信息数据为空");
return;
}
if(scanInfo.getTag() != TrueOrFalseEnum.TRUE.getCode()) {
return;
}
log.info("注册扫码器信息: {},获得扫码数据:{}", stockScan, scanInfo);
// 检查是否扫码失败
String code = scanInfo.getVehicleNo();
if(AppStringUtils.isEmpty(code)) {
return;
}
code = code.trim().replaceAll("\\W+", ""); // 获取条码简单处理
short errRouter = Short.parseShort(AppStringUtils.isEmptyOr(stockScan.getParam2(), "999")); // 异常路向值
// 读码失败
if(code.equals(ConstantData.NO_READ)) {
log.info("扫码:{},扫码失败,条码:{}",stockScan.getScanId(), code);
writeSimpleScanTask(stockScan, errRouter);
return;
}
// 读码成功
// 检查是否有该条码的任务
AppStockComposeTask stockComposeTaskCheck = new AppStockComposeTask();
stockComposeTaskCheck.setVehicleNo(code);
List<AppStockComposeTask> stockComposeTaskCheckList = stockComposeTaskService.query(stockComposeTaskCheck);
if(stockComposeTaskCheckList == null) {
log.info("扫码:{},检索堆垛机任务失败,条码:{}",stockScan.getScanId(), code);
writeSimpleScanTask(stockScan, errRouter);
return;
}
// -- 找出是否存在未执行的堆垛机入库任务
List<AppStockComposeTask> stockComposeTasks = stockComposeTaskCheckList.stream().filter(task -> StockComposeTaskStatusEnum.getAllNotExecuteStatus().contains(task.getTaskStatus()) &&
(task.getTaskType().equals(StockComposeTaskTypeEnum.AUTO.getCode()) || task.getTaskType().equals(StockComposeTaskTypeEnum.IN.getCode()))).toList();
if(!stockComposeTasks.isEmpty()) {
// 存在任务
AppStockComposeTask stockComposeTask = stockComposeTasks.getFirst();
List<AppStackerLocation> stackerLocations = stackerLocationService.queryStackerLocationById(stockComposeTask.getDestination());
if(stackerLocations == null) {
log.info("扫码:{} 注册堆垛机任务,终点位置查询数据库失败,任务数据:{}", stockScan.getScanId(), AppStringUtils.objectToString(stockComposeTask));
writeSimpleScanTask(stockScan, errRouter);
return;
}
if(stackerLocations.isEmpty()) {
log.info("扫码:{} 注册堆垛机任务,终点位置不存在,任务数据:{}", stockScan.getScanId(), AppStringUtils.objectToString(stockComposeTask));
writeSimpleScanTask(stockScan, errRouter);
return;
}
AppStackerLocation stackerLocation = stackerLocations.getFirst();
short router = stackerLocation.getLaneId().shortValue();
log.info("箱号:{} 在注册位置:{},存在入库任务任务,巷道:{}", code, stockScan.getScanId(), router);
writeSimpleScanTask(stockScan, router);
return;
}
/* 没有任务,注册任务 */
// 请求上位系统
StockLoginReq stockLoginReq = new StockLoginReq();
stockLoginReq.setRequestId(AppUUIDUtils.getNewUUID());
stockLoginReq.setVehicleNo(scanInfo.getVehicleNo());
stockLoginReq.setLocation(stockScan.getScanId());
stockLoginReq.setSize(Integer.valueOf(scanInfo.getVehicleSize() == null ? 0 : scanInfo.getVehicleSize()));
stockLoginReq.setLength(scanInfo.getLength() == null ? 0 : scanInfo.getLength());
stockLoginReq.setWidth(scanInfo.getWidth() == null ? 0 : scanInfo.getWidth());
stockLoginReq.setHeight(scanInfo.getHeight() == null ? 0 : scanInfo.getHeight());
stockLoginReq.setWeight(scanInfo.getWeight() == null ? 0 : scanInfo.getWeight());
HttpRequest httpRequest = HttpRequest.buildStart()
.post()
.setAddressKey(stockScan.getParam1() + "_LOGIN_STACKER")
.setBody(AppStringUtils.objectToString(stockLoginReq))
.buildEnd();
HttpResponse httpResponse = WebHttpClient.httpRequest(httpRequest);
if(!httpResponse.isSuccess()) {
log.info("扫码:{} 申请堆垛机任务失败,异常信息:{}", stockScan.getScanId(), httpResponse.getException().toString());
writeSimpleScanTask(stockScan, errRouter);
return;
}
String responseText = httpResponse.getResponseText();
UpperSystemDataResponse<StockLoginDataResp> upperSystemDataResponse = JSONObject.parseObject(responseText, new TypeReference<>(){});
if(upperSystemDataResponse == null) {
log.info("扫码:{} 申请堆垛机任务失败,返回的数据无法解析,返回信息:{}", stockScan.getScanId(), responseText);
writeSimpleScanTask(stockScan, errRouter);
return;
}
if(!upperSystemDataResponse.isOk()) {
log.info("扫码:{} 申请堆垛机任务失败,返回信息:{}", stockScan.getScanId(), upperSystemDataResponse.getMessage());
writeSimpleScanTask(stockScan, errRouter);
return;
}
log.info("扫码:{} 申请堆垛机任务成功,信息:{}", stockScan.getScanId(), upperSystemDataResponse.getMessage());
// 生成任务
StockLoginDataResp returnData = upperSystemDataResponse.getReturnData();
if(returnData == null) {
return;
}
// 检查任务格式是否正确
if(AppStringUtils.isEmpty(returnData.getTaskId())
|| returnData.getTaskType() == null
|| (AppStringUtils.isEmpty(returnData.getOrigin()) && AppStringUtils.isEmpty(returnData.getDestination()))) {
writeSimpleScanTask(stockScan, errRouter);
log.info("扫码:{} 注册堆垛机任务,任务数据不正确,任务数据:{}", stockScan.getScanId(), AppStringUtils.objectToString(returnData));
return;
}
if(StockComposeTaskTypeEnum.getByCode(returnData.getTaskType()).equals(StockComposeTaskTypeEnum.UNKNOWN)) {
writeSimpleScanTask(stockScan, errRouter);
log.info("扫码:{} 注册堆垛机任务,任务类型不正确,任务数据:{}", stockScan.getScanId(), AppStringUtils.objectToString(returnData));
return;
}
// 检查任务是否存在
List<AppStockComposeTask> stockComposeTaskList = stockComposeTaskService.queryByUpperTaskId(returnData.getTaskId());
if(stockComposeTaskList == null) {
writeSimpleScanTask(stockScan, errRouter);
log.info("扫码:{} 注册堆垛机任务查询任务失败", stockScan.getScanId());
return;
}
if(!stockComposeTaskList.isEmpty()) {
// 存在任务
AppStockComposeTask stockComposeTask = stockComposeTasks.getFirst();
List<AppStackerLocation> stackerLocations = stackerLocationService.queryStackerLocationById(stockComposeTask.getDestination());
if(stackerLocations == null) {
log.info("扫码:{} 注册堆垛机任务,终点位置查询数据库失败,任务数据:{}", stockScan.getScanId(), AppStringUtils.objectToString(stockComposeTask));
writeSimpleScanTask(stockScan, errRouter);
return;
}
if(stackerLocations.isEmpty()) {
log.info("扫码:{} 注册堆垛机任务,终点位置不存在,任务数据:{}", stockScan.getScanId(), AppStringUtils.objectToString(stockComposeTask));
writeSimpleScanTask(stockScan, errRouter);
return;
}
AppStackerLocation stackerLocation = stackerLocations.getFirst();
short router = stackerLocation.getLaneId().shortValue();
log.info("箱号:{} 在注册位置:{},存在入库任务任务,申请是返回的任务存在,巷道:{}", code, stockScan.getScanId(), router);
writeSimpleScanTask(stockScan, router);
return;
}
AppStockComposeTask stockComposeTask = new AppStockComposeTask();
stockComposeTask.setTaskId(AppUUIDUtils.getNewUUID());
stockComposeTask.setTaskGroup(AppStringUtils.isEmptyOr(returnData.getTaskGroup(), returnData.getTaskId()));
stockComposeTask.setUpperTaskId(returnData.getTaskId());
stockComposeTask.setTaskType(returnData.getTaskType());
stockComposeTask.setOrigin(returnData.getOrigin());
stockComposeTask.setDestination(returnData.getDestination());
stockComposeTask.setTaskStatus(StockComposeTaskStatusEnum.CREATE.getCode());
stockComposeTask.setStepStatus(StockComposeTaskStepStatusEnum.CREATE.getCode());
stockComposeTask.setCanCancel(TrueOrFalseEnum.TRUE.getCode());
stockComposeTask.setPriority(returnData.getPriority() == null ? 5 : returnData.getPriority());
stockComposeTask.setVehicleNo(returnData.getVehicleNo());
stockComposeTask.setVehicleSize(returnData.getSize() == null ? 0 : returnData.getSize());
stockComposeTask.setWeight(BigDecimal.valueOf(returnData.getWeight() == null ? 0 : returnData.getWeight()));
stockComposeTask.setCreateTime(LocalDateTime.now());
stockComposeTask.setUpdateTime(LocalDateTime.now());
stockComposeTask.setTaskSource(returnData.getSysName());
stockComposeTask.setCreatePerson("?");
int insertResult = stockComposeTaskService.insert(stockComposeTask);
if(insertResult <= 0) {
log.info("扫码:{} 注册堆垛机任务,任务插入失败,任务数据:{}", returnData.getVehicleNo(), AppStringUtils.objectToString(stockComposeTask));
writeSimpleScanTask(stockScan, errRouter);
return;
}
List<AppStackerLocation> stackerLocations = stackerLocationService.queryStackerLocationById(stockComposeTask.getDestination());
if(stackerLocations == null) {
log.info("扫码:{} 注册堆垛机任务,终点位置查询数据库失败,任务数据:{}", stockScan.getScanId(), AppStringUtils.objectToString(stockComposeTask));
writeSimpleScanTask(stockScan, errRouter);
return;
}
if(stackerLocations.isEmpty()) {
log.info("扫码:{} 注册堆垛机任务,终点位置不存在,任务数据:{}", stockScan.getScanId(), AppStringUtils.objectToString(stockComposeTask));
writeSimpleScanTask(stockScan, errRouter);
return;
}
AppStackerLocation stackerLocation = stackerLocations.getFirst();
short router = stackerLocation.getLaneId().shortValue();
log.info("箱号:{} 在注册位置:{},任务插入成功,巷道:{},任务数据:{}", code, stockScan.getScanId(), router, AppStringUtils.objectToString(stockComposeTask));
writeSimpleScanTask(stockScan, router);
}
/* ***********常用方法*********** */
/**
* 写入一个简单任务
* @param stockScan 扫描任务
* @param router 路由
*/
private void writeSimpleScanTask(AppStockScan stockScan, Short router) {
ScanTask scanTask = new ScanTask();
scanTask.setPlcTaskId(plcTaskIdHelper.newTaskId());
scanTask.setDestination(router);
String writePlcErrorMessage = PlcCommunicationFactory.getPlcCommunicationByPlcId(stockScan.getPlcId()).writeScannerTask(stockScan.getScanId(), scanTask);
if(AppStringUtils.isNotEmpty(writePlcErrorMessage)) {
log.error("扫码:{},写入扫码任务失败,任务:{},异常信息:{}",stockScan.getScanId(), AppStringUtils.objectToString(scanTask), writePlcErrorMessage);
return;
}
log.info("扫码:{},扫码写入任务成功,任务:{}",stockScan.getScanId(),AppStringUtils.objectToString(scanTask));
return;
}
}

View File

@ -0,0 +1,294 @@
package org.wcs.business.stock.impl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.wcs.business.stock.intf.IComposeTaskStatusReporter;
import org.wcs.business.stock.intf.IStockSingleTaskExecuteEvent;
import org.wcs.constant.enums.common.TrueOrFalseEnum;
import org.wcs.constant.enums.database.*;
import org.wcs.mapper.intf.AppStackerLocationService;
import org.wcs.mapper.intf.AppStockComposeTaskService;
import org.wcs.mapper.intf.AppStockSingleTaskService;
import org.wcs.mapper.intf.AppTrayConveyLocationService;
import org.wcs.model.po.app.AppStockComposeTask;
import org.wcs.model.po.app.AppStockSingleTask;
import org.wcs.model.po.app.AppTrayConveyLocation;
import org.wcs.plugin.plc.PlcCommunicationFactory;
import org.wcs.plugin.plc.model.TaskConveyTask;
import org.wcs.utils.AppStringUtils;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
/**
* 独立任务执行事件
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class StockSingleTaskExecuteEvent implements IStockSingleTaskExecuteEvent {
private final AppStockSingleTaskService stockSingleTaskService;
private final AppStockComposeTaskService stockComposeTaskService;
private final AppStackerLocationService stackerLocationService;
private final IComposeTaskStatusReporter composeTaskStatusReporter;
private final AppTrayConveyLocationService trayConveyLocationService;
/**
* 任务开始
* @param appStockSingleTask 开始的任务
* @param message 信息
* @return 执行结果
*/
@Override
public String taskStart(AppStockSingleTask appStockSingleTask, String message) {
// 更新任务为执行中
AppStockSingleTask stockSingleTaskUpdate = new AppStockSingleTask();
stockSingleTaskUpdate.setTaskId(appStockSingleTask.getTaskId());
stockSingleTaskUpdate.setTaskStatus(StockSingleTaskStatusEnum.EXECUTING.getCode());
stockSingleTaskUpdate.setStartTime(LocalDateTime.now());
stockSingleTaskUpdate.setTaskMsg(message);
boolean singleTaskUpdated = stockSingleTaskService.update(stockSingleTaskUpdate);
log.info("独立任务开始执行更新独立任务数据库行:{} 更新结果:{}", AppStringUtils.objectToString(appStockSingleTask), singleTaskUpdated);
// 更新库位状态
if(appStockSingleTask.getTaskType().equals(StockSingleTaskTypeEnum.STACKER_IN.getCode())) {
// 入库更新库位状态
stackerLocationService.updateStatusAndVehicleNoById(appStockSingleTask.getDestination(), StackerLocationStatusEnum.IN_ING.getCode(), appStockSingleTask.getVehicleNo());
}
if(appStockSingleTask.getTaskType().equals(StockSingleTaskTypeEnum.STACKER_OUT.getCode())) {
// 出库更新库位状态
stackerLocationService.updateStatusAndVehicleNoById(appStockSingleTask.getOrigin(), StackerLocationStatusEnum.OUT_ING.getCode(), appStockSingleTask.getVehicleNo());
}
if(appStockSingleTask.getTaskType().equals(StockSingleTaskTypeEnum.STACKER_MOVE.getCode())) {
// 移库更新库位状态
stackerLocationService.updateStatusAndVehicleNoById(appStockSingleTask.getOrigin(), StackerLocationStatusEnum.OUT_ING.getCode(), appStockSingleTask.getVehicleNo());
stackerLocationService.updateStatusAndVehicleNoById(appStockSingleTask.getDestination(), StackerLocationStatusEnum.IN_ING.getCode(), appStockSingleTask.getVehicleNo());
}
if(AppStringUtils.isEmpty(appStockSingleTask.getUpperTaskId())) {
return null;
}
// 查看主任务状态
AppStockComposeTask stockComposeTask = stockComposeTaskService.queryByTaskId(appStockSingleTask.getUpperTaskId());
if(stockComposeTask == null) {
log.info("独立任务开始执行检索组合任务数据服务异常,上位任务号:{}", appStockSingleTask.getUpperTaskId());
return null;
}
if(!Objects.equals(stockComposeTask.getTaskStatus(), StockComposeTaskStatusEnum.EXECUTING.getCode())) {
AppStockComposeTask stockComposeTaskUpdate = new AppStockComposeTask();
stockComposeTaskUpdate.setTaskId(stockComposeTask.getTaskId());
stockComposeTaskUpdate.setTaskStatus(StockComposeTaskStatusEnum.EXECUTING.getCode());
stockComposeTaskUpdate.setStepStatus(StockComposeTaskStepStatusEnum.RUNNING.getCode());
stockComposeTaskUpdate.setCanCancel(TrueOrFalseEnum.FALSE.getCode());
stockComposeTaskUpdate.setStartTime(LocalDateTime.now());
stockComposeTaskUpdate.setTaskMsg(message);
boolean composeTaskUpdated = stockComposeTaskService.update(stockComposeTaskUpdate);
log.info("独立任务开始执行更新组合任务数据库行:{} 状态:{} 更新结果:{}", AppStringUtils.objectToString(stockComposeTask), StockComposeTaskStatusEnum.EXECUTING.getCode(), composeTaskUpdated);
composeTaskStatusReporter.taskStart(stockComposeTask, message);
}
return null;
}
/**
* 任务完成
* @param appStockSingleTask 开始的任务
* @param message 信息
* @return 执行结果
*/
@Override
public String taskFinish(AppStockSingleTask appStockSingleTask, String message) {
// 更新任务为完成
AppStockSingleTask stockSingleTaskUpdate = new AppStockSingleTask();
stockSingleTaskUpdate.setTaskId(appStockSingleTask.getTaskId());
stockSingleTaskUpdate.setTaskStatus(StockSingleTaskStatusEnum.COMPLETE.getCode());
stockSingleTaskUpdate.setStartTime(LocalDateTime.now());
stockSingleTaskUpdate.setTaskMsg(message);
boolean singleTaskUpdated = stockSingleTaskService.update(stockSingleTaskUpdate);
log.info("独立任务完成更新独立任务数据库行:{} 更新结果:{}", AppStringUtils.objectToString(appStockSingleTask), singleTaskUpdated);
// 更新库位状态
if(appStockSingleTask.getTaskType().equals(StockSingleTaskTypeEnum.STACKER_IN.getCode())) { // 堆垛机入库
// 入库更新库位状态
stackerLocationService.updateStatusAndVehicleNoById(appStockSingleTask.getDestination(), StackerLocationStatusEnum.OCCUPY.getCode(), appStockSingleTask.getVehicleNo());
}
if(appStockSingleTask.getTaskType().equals(StockSingleTaskTypeEnum.STACKER_OUT.getCode())) { // 堆垛机出库
// 出库更新库位状态
stackerLocationService.updateStatusAndVehicleNoById(appStockSingleTask.getOrigin(), StackerLocationStatusEnum.EMPTY.getCode(), "");
// 出库完成后绑定载具号到输送机
List<AppTrayConveyLocation> destinationInfoList = trayConveyLocationService.getDataByLocationId(appStockSingleTask.getDestination());
if(destinationInfoList != null && !destinationInfoList.isEmpty()) {
AppTrayConveyLocation destinationInfo = destinationInfoList.getFirst();
TaskConveyTask taskConveyTask = new TaskConveyTask();
taskConveyTask.setPlcTaskId(appStockSingleTask.getPlcTaskId());
taskConveyTask.setTaskType(StockSingleTaskTypeEnum.BINDING_VEHICLE_NO.getCode().shortValue());
taskConveyTask.setOrigin((short)0);
taskConveyTask.setDestination((short)0);
taskConveyTask.setVehicleSize(appStockSingleTask.getVehicleSize().shortValue());
taskConveyTask.setWeight(appStockSingleTask.getWeight().intValue());
taskConveyTask.setVehicleNo(appStockSingleTask.getVehicleNo());
String writeTaskResult = PlcCommunicationFactory.getPlcCommunicationByPlcId(destinationInfo.getPlcId()).writeTaskConveyTask(appStockSingleTask.getDestination(), taskConveyTask);
if(AppStringUtils.isNotEmpty(writeTaskResult)) {
log.warn("托盘线:{} 写入绑定载具号任务失败,站台:{},载具号:{}。原因:{}", appStockSingleTask.getTaskId(), appStockSingleTask.getDestination(),appStockSingleTask.getVehicleNo(), writeTaskResult);
} else {
log.info("托盘线:{} 写入绑定载具号任务成功,站台:{},载具号:{}", appStockSingleTask.getTaskId(), appStockSingleTask.getDestination(),appStockSingleTask.getVehicleNo());
}
}
}
if(appStockSingleTask.getTaskType().equals(StockSingleTaskTypeEnum.STACKER_MOVE.getCode())) { // 堆垛机移库
// 移库更新库位状态
stackerLocationService.updateStatusAndVehicleNoById(appStockSingleTask.getOrigin(), StackerLocationStatusEnum.EMPTY.getCode(), "");
stackerLocationService.updateStatusAndVehicleNoById(appStockSingleTask.getDestination(), StackerLocationStatusEnum.OCCUPY.getCode(), appStockSingleTask.getVehicleNo());
}
return null;
}
/**
* 任务异常
* @param appStockSingleTask 开始的任务
* @param message 信息
* @return 执行结果
*/
@Override
public String taskError(AppStockSingleTask appStockSingleTask, String message) {
// 更新任务为异常
AppStockSingleTask stockSingleTaskUpdate = new AppStockSingleTask();
stockSingleTaskUpdate.setTaskId(appStockSingleTask.getTaskId());
stockSingleTaskUpdate.setTaskStatus(StockSingleTaskStatusEnum.ERROR.getCode());
stockSingleTaskUpdate.setStartTime(LocalDateTime.now());
stockSingleTaskUpdate.setTaskMsg(message);
boolean singleTaskUpdated = stockSingleTaskService.update(stockSingleTaskUpdate);
log.info("独立任务异常更新独立任务数据库行:{} 更新结果:{}", AppStringUtils.objectToString(appStockSingleTask), singleTaskUpdated);
return null;
}
/**
* 任务取消
* @param appStockSingleTask 开始的任务
* @param message 信息
* @return 执行结果
*/
@Override
public String taskCancel(AppStockSingleTask appStockSingleTask, String message) {
// 更新任务为取消
AppStockSingleTask stockSingleTaskUpdate = new AppStockSingleTask();
stockSingleTaskUpdate.setTaskId(appStockSingleTask.getTaskId());
stockSingleTaskUpdate.setTaskStatus(StockSingleTaskStatusEnum.CANCEL.getCode());
stockSingleTaskUpdate.setStartTime(LocalDateTime.now());
stockSingleTaskUpdate.setTaskMsg(message);
boolean singleTaskUpdated = stockSingleTaskService.update(stockSingleTaskUpdate);
log.info("独立任务取消更新独立任务数据库行:{} 更新结果:{}", AppStringUtils.objectToString(appStockSingleTask), singleTaskUpdated);
// 更新库位状态
if(appStockSingleTask.getTaskType().equals(StockSingleTaskTypeEnum.STACKER_IN.getCode())) {
// 入库更新库位状态
stackerLocationService.updateStatusAndVehicleNoById(appStockSingleTask.getDestination(), StackerLocationStatusEnum.EMPTY.getCode(), "");
}
if(appStockSingleTask.getTaskType().equals(StockSingleTaskTypeEnum.STACKER_OUT.getCode())) {
// 出库更新库位状态
stackerLocationService.updateStatusAndVehicleNoById(appStockSingleTask.getOrigin(), StackerLocationStatusEnum.OCCUPY.getCode(), appStockSingleTask.getVehicleNo());
}
if(appStockSingleTask.getTaskType().equals(StockSingleTaskTypeEnum.STACKER_MOVE.getCode())) {
// 移库更新库位状态
stackerLocationService.updateStatusAndVehicleNoById(appStockSingleTask.getOrigin(), StackerLocationStatusEnum.OCCUPY.getCode(), appStockSingleTask.getVehicleNo());
stackerLocationService.updateStatusAndVehicleNoById(appStockSingleTask.getDestination(), StackerLocationStatusEnum.EMPTY.getCode(), "");
}
return null;
}
/**
* 任务重复入库
* @param appStockSingleTask 开始的任务
* @param message 信息
* @return 执行结果
*/
@Override
public String taskDoubleIn(AppStockSingleTask appStockSingleTask, String message) {
// 更新任务为重复入库
AppStockSingleTask stockSingleTaskUpdate = new AppStockSingleTask();
stockSingleTaskUpdate.setTaskId(appStockSingleTask.getTaskId());
stockSingleTaskUpdate.setTaskStatus(StockSingleTaskStatusEnum.ERROR.getCode());
stockSingleTaskUpdate.setStartTime(LocalDateTime.now());
stockSingleTaskUpdate.setTaskMsg("[重复入库]" + message);
boolean singleTaskUpdated = stockSingleTaskService.update(stockSingleTaskUpdate);
log.info("独立任务重复入库更新独立任务数据库行:{} 更新结果:{}", AppStringUtils.objectToString(appStockSingleTask), singleTaskUpdated);
if(AppStringUtils.isEmpty(appStockSingleTask.getUpperTaskId())) {
return null;
}
// 查看主任务状态
AppStockComposeTask stockComposeTask = stockComposeTaskService.queryByTaskId(appStockSingleTask.getUpperTaskId());
if(stockComposeTask == null) {
log.info("独立任务重复入库检索组合任务数据服务异常,上位任务号:{}", appStockSingleTask.getUpperTaskId());
return null;
}
if(!Objects.equals(stockComposeTask.getTaskStatus(), StockComposeTaskStatusEnum.ERROR.getCode())) {
AppStockComposeTask stockComposeTaskUpdate = new AppStockComposeTask();
stockComposeTaskUpdate.setTaskId(stockComposeTask.getTaskId());
stockComposeTaskUpdate.setTaskStatus(StockComposeTaskStatusEnum.ERROR.getCode());
stockComposeTaskUpdate.setStepStatus(StockComposeTaskStepStatusEnum.ERROR.getCode());
stockComposeTaskUpdate.setStartTime(LocalDateTime.now());
stockComposeTaskUpdate.setTaskMsg("[重复入库]" + message);
boolean composeTaskUpdated = stockComposeTaskService.update(stockComposeTaskUpdate);
log.info("独立任务重复入库更新组合任务数据库行:{} 状态:{} 更新结果:{}", AppStringUtils.objectToString(stockComposeTask), StockComposeTaskStatusEnum.EXECUTING.getCode(), composeTaskUpdated);
composeTaskStatusReporter.taskDoubleIn(stockComposeTask, message);
}
return null;
}
/**
* 任务空出库
* @param appStockSingleTask 开始的任务
* @param message 信息
* @return 执行结果
*/
@Override
public String taskEmptyOut(AppStockSingleTask appStockSingleTask, String message) {
// 更新任务为空出库
AppStockSingleTask stockSingleTaskUpdate = new AppStockSingleTask();
stockSingleTaskUpdate.setTaskId(appStockSingleTask.getTaskId());
stockSingleTaskUpdate.setTaskStatus(StockSingleTaskStatusEnum.CANCEL.getCode());
stockSingleTaskUpdate.setStartTime(LocalDateTime.now());
stockSingleTaskUpdate.setTaskMsg("[空出库]" + message);
boolean singleTaskUpdated = stockSingleTaskService.update(stockSingleTaskUpdate);
log.info("独立任务空出库更新独立任务数据库行:{} 更新结果:{}", AppStringUtils.objectToString(appStockSingleTask), singleTaskUpdated);
if(AppStringUtils.isEmpty(appStockSingleTask.getUpperTaskId())) {
return null;
}
// 查看主任务状态
AppStockComposeTask stockComposeTask = stockComposeTaskService.queryByTaskId(appStockSingleTask.getUpperTaskId());
if(stockComposeTask == null) {
log.info("独立任务空出库检索组合任务数据服务异常,上位任务号:{}", appStockSingleTask.getUpperTaskId());
return null;
}
if(!Objects.equals(stockComposeTask.getTaskStatus(), StockComposeTaskStatusEnum.CANCEL.getCode())) {
AppStockComposeTask stockComposeTaskUpdate = new AppStockComposeTask();
stockComposeTaskUpdate.setTaskId(stockComposeTask.getTaskId());
stockComposeTaskUpdate.setTaskStatus(StockComposeTaskStatusEnum.CANCEL.getCode());
stockComposeTaskUpdate.setStepStatus(StockComposeTaskStepStatusEnum.ERROR.getCode());
stockComposeTaskUpdate.setStartTime(LocalDateTime.now());
stockComposeTaskUpdate.setTaskMsg("[空出库]" + message);
boolean composeTaskUpdated = stockComposeTaskService.update(stockComposeTaskUpdate);
log.info("独立任务空出库更新组合任务数据库行:{} 状态:{} 更新结果:{}", AppStringUtils.objectToString(stockComposeTask), StockComposeTaskStatusEnum.EXECUTING.getCode(), composeTaskUpdated);
composeTaskStatusReporter.taskEmptyOut(stockComposeTask, message);
}
return null;
}
/**
* 任务超时
* @param appStockSingleTask 开始的任务
* @param message 信息
* @return 执行结果
*/
@Override
public String taskTimeOut(AppStockSingleTask appStockSingleTask, String message) {
// 更新任务为任务超时
AppStockSingleTask stockSingleTaskUpdate = new AppStockSingleTask();
stockSingleTaskUpdate.setTaskId(appStockSingleTask.getTaskId());
stockSingleTaskUpdate.setTaskStatus(StockSingleTaskStatusEnum.TIMEOUT.getCode());
stockSingleTaskUpdate.setStartTime(LocalDateTime.now());
stockSingleTaskUpdate.setTaskMsg(message);
boolean singleTaskUpdated = stockSingleTaskService.update(stockSingleTaskUpdate);
log.info("独立任务超时更新独立任务数据库行:{} 更新结果:{}", AppStringUtils.objectToString(appStockSingleTask), singleTaskUpdated);
return null;
}
}

View File

@ -0,0 +1,25 @@
package org.wcs.business.stock.impl.StockTaskDataCheck;
import org.wcs.business.stock.StockComposeTaskManage;
import org.wcs.business.stock.intf.IStockTaskDataCheck;
import org.wcs.model.po.app.AppStockComposeTask;
import org.wcs.utils.SpringUtils;
/**
* 自动任务 的数据检查
*/
public class AutoStockTaskDataCheck implements IStockTaskDataCheck {
@Override
public String checkStockTaskData(AppStockComposeTask composeTask) {
StockComposeTaskManage stockComposeTaskManage = SpringUtils.getBean(StockComposeTaskManage.class);
boolean originExist = stockComposeTaskManage.checkLocationExist(composeTask.getOrigin());
if(!originExist) {
return "自动任务起点位置不能为空";
}
boolean destinationExist = stockComposeTaskManage.checkLocationExist(composeTask.getDestination());
if(!destinationExist) {
return "自动任务终点位置不能为空";
}
return null;
}
}

View File

@ -0,0 +1,21 @@
package org.wcs.business.stock.impl.StockTaskDataCheck;
import org.wcs.business.stock.StockComposeTaskManage;
import org.wcs.business.stock.intf.IStockTaskDataCheck;
import org.wcs.model.po.app.AppStockComposeTask;
import org.wcs.utils.SpringUtils;
/**
* 输送搬运任务数据检查
*/
public class ConveyStockTaskDataCheck implements IStockTaskDataCheck {
@Override
public String checkStockTaskData(AppStockComposeTask composeTask) {
StockComposeTaskManage stockComposeTaskManage = SpringUtils.getBean(StockComposeTaskManage.class);
boolean destinationExist = stockComposeTaskManage.checkLocationExist(composeTask.getDestination());
if(!destinationExist) {
return "搬运任务终点位置不能为空";
}
return null;
}
}

View File

@ -0,0 +1,21 @@
package org.wcs.business.stock.impl.StockTaskDataCheck;
import org.wcs.business.stock.StockComposeTaskManage;
import org.wcs.business.stock.intf.IStockTaskDataCheck;
import org.wcs.model.po.app.AppStockComposeTask;
import org.wcs.utils.SpringUtils;
/**
* 入库任务数据检查
*/
public class InStockTaskDataCheck implements IStockTaskDataCheck {
@Override
public String checkStockTaskData(AppStockComposeTask composeTask) {
StockComposeTaskManage stockComposeTaskManage = SpringUtils.getBean(StockComposeTaskManage.class);
boolean destinationExist = stockComposeTaskManage.checkLocationExist(composeTask.getDestination());
if(!destinationExist) {
return "入库任务终点位置不能为空";
}
return null;
}
}

View File

@ -0,0 +1,25 @@
package org.wcs.business.stock.impl.StockTaskDataCheck;
import org.wcs.business.stock.StockComposeTaskManage;
import org.wcs.business.stock.intf.IStockTaskDataCheck;
import org.wcs.model.po.app.AppStockComposeTask;
import org.wcs.utils.SpringUtils;
/**
* 移库任务数据检查
*/
public class MoveStockTaskDataCheck implements IStockTaskDataCheck {
@Override
public String checkStockTaskData(AppStockComposeTask composeTask) {
StockComposeTaskManage stockComposeTaskManage = SpringUtils.getBean(StockComposeTaskManage.class);
boolean originExist = stockComposeTaskManage.checkLocationExist(composeTask.getOrigin());
if(!originExist) {
return "移库任务起点位置不能为空";
}
boolean destinationExist = stockComposeTaskManage.checkLocationExist(composeTask.getDestination());
if(!destinationExist) {
return "移库任务终点位置不能为空";
}
return null;
}
}

View File

@ -0,0 +1,21 @@
package org.wcs.business.stock.impl.StockTaskDataCheck;
import org.wcs.business.stock.StockComposeTaskManage;
import org.wcs.business.stock.intf.IStockTaskDataCheck;
import org.wcs.model.po.app.AppStockComposeTask;
import org.wcs.utils.SpringUtils;
/**
* 出库任务数据检查
*/
public class OutStockTaskDataCheck implements IStockTaskDataCheck {
@Override
public String checkStockTaskData(AppStockComposeTask composeTask) {
StockComposeTaskManage stockComposeTaskManage = SpringUtils.getBean(StockComposeTaskManage.class);
boolean originExist = stockComposeTaskManage.checkLocationExist(composeTask.getOrigin());
if(!originExist) {
return "出库任务起点位置不能为空";
}
return null;
}
}

View File

@ -0,0 +1,14 @@
package org.wcs.business.stock.impl.StockTaskDataCheck;
import org.wcs.business.stock.intf.IStockTaskDataCheck;
import org.wcs.model.po.app.AppStockComposeTask;
/**
* 未知任务数据检查
*/
public class UnknownStockTaskDataCheck implements IStockTaskDataCheck {
@Override
public String checkStockTaskData(AppStockComposeTask composeTask) {
return "任务类型不支持";
}
}

View File

@ -0,0 +1,58 @@
package org.wcs.business.stock.intf;
import org.wcs.model.bo.tuple.Tuple2;
import org.wcs.model.po.app.AppStockComposeTask;
/**
* 组合式任务状态上报者
*/
public interface IComposeTaskStatusReporter {
/**
* 任务开始
* @param composeTask 任务信息
* @param message 消息
* @return <错误信息响应数据 >
*/
Tuple2<String, String> taskStart(AppStockComposeTask composeTask, String message);
/**
* 任务结束
* @param composeTask 任务信息
* @param message 消息
* @return <错误信息响应数据 >
*/
Tuple2<String, String> taskFinish(AppStockComposeTask composeTask, String message);
/**
* 任务异常
* @param composeTask 任务信息
* @param message 消息
* @return <错误信息响应数据 >
*/
Tuple2<String, String> taskError(AppStockComposeTask composeTask, String message);
/**
* 任务取消
* @param composeTask 任务信息
* @param message 消息
* @return <错误信息响应数据 >
*/
Tuple2<String, String> taskCancel(AppStockComposeTask composeTask, String message);
/**
* 任务重复入库
* @param composeTask 任务信息
* @param message 消息
* @return <错误信息响应数据 >
*/
Tuple2<String, String> taskDoubleIn(AppStockComposeTask composeTask, String message);
/**
* 任务空出
* @param composeTask 任务信息
* @param message 消息
* @return <错误信息响应数据 >
*/
Tuple2<String, String> taskEmptyOut(AppStockComposeTask composeTask, String message);
}

View File

@ -0,0 +1,26 @@
package org.wcs.business.stock.intf;
import org.wcs.model.po.app.AppStockScan;
import org.wcs.plugin.plc.model.ScanInfo;
public interface IScanMethod {
/* 这个所有方法只能是这两个参数,不得改变 */
/**
* 注册任务仅注册成功则继续执行失败则退回成功写1失败写2
* @param stockScan 扫码器信息
* @param scanInfo 扫码信息
*/
void loginStackerTask(AppStockScan stockScan, ScanInfo scanInfo);
/**
* 注册任务并根据返回值确定该进那个堆垛机
* @param stockScan 扫码器信息
* @param scanInfo 扫码信息
*/
void loginStackerTaskSeed(AppStockScan stockScan, ScanInfo scanInfo);
}

View File

@ -0,0 +1,68 @@
package org.wcs.business.stock.intf;
import org.wcs.model.po.app.AppStockSingleTask;
/**
* 独立任务执行事件
*/
public interface IStockSingleTaskExecuteEvent {
/**
* 任务开始
* @param appStockSingleTask 开始的任务
* @param message 信息
* @return 执行结果
*/
String taskStart(AppStockSingleTask appStockSingleTask, String message);
/**
* 任务完成
* @param appStockSingleTask 开始的任务
* @param message 信息
* @return 执行结果
*/
String taskFinish(AppStockSingleTask appStockSingleTask, String message);
/**
* 任务异常
* @param appStockSingleTask 开始的任务
* @param message 信息
* @return 执行结果
*/
String taskError(AppStockSingleTask appStockSingleTask, String message);
/**
* 任务取消
* @param appStockSingleTask 开始的任务
* @param message 信息
* @return 执行结果
*/
String taskCancel(AppStockSingleTask appStockSingleTask, String message);
/**
* 任务重复入库
* @param appStockSingleTask 开始的任务
* @param message 信息
* @return 执行结果
*/
String taskDoubleIn(AppStockSingleTask appStockSingleTask, String message);
/**
* 任务空出库
* @param appStockSingleTask 开始的任务
* @param message 信息
* @return 执行结果
*/
String taskEmptyOut(AppStockSingleTask appStockSingleTask, String message);
/**
* 任务超时
* @param appStockSingleTask 开始的任务
* @param message 信息
* @return 执行结果
*/
String taskTimeOut(AppStockSingleTask appStockSingleTask, String message);
}

View File

@ -0,0 +1,16 @@
package org.wcs.business.stock.intf;
import org.wcs.model.po.app.AppStockComposeTask;
/**
* 综合任务检查接口
*/
public interface IStockTaskDataCheck {
/**
* 检查仓储任务数据若有问题则返回异常信息没问题返回 null
* @param composeTask 任务
* @return 异常信息
*/
String checkStockTaskData(AppStockComposeTask composeTask);
}

View File

@ -0,0 +1,22 @@
package org.wcs.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 添加全局跨域允许 -- 无需配置 saToken 配置代管
*/
//@Configuration
public class CorsConfig implements WebMvcConfigurer {
// @Override
// public void addCorsMappings(CorsRegistry registry) {
// // 添加映射路径
// registry.addMapping("/**")
// .allowedOriginPatterns("*") // 允许哪些域的请求星号代表允许所有
// .allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE") // 允许的方法
// .allowedHeaders("*") // 允许的头部设置
// .allowCredentials(true) // 是否发送cookie
// .maxAge(168000); // 预检间隔时间
// }
}

View File

@ -0,0 +1,22 @@
package org.wcs.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig {
/**
* 添加分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 如果配置多个插件, 切记分页最后添加
// 如果有多数据源可以不配具体类型, 否则都建议配上具体的 DbType
return interceptor;
}
}

View File

@ -0,0 +1,60 @@
package org.wcs.config;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.router.SaHttpMethod;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
// 注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册 Sa-Token 拦截器校验规则为 StpUtil.checkLogin() 登录校验
registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
.addPathPatterns("/**")
.excludePathPatterns("/**/user/login", "/**/app/system/**", "/api/pub/**");
}
/**
* 注册 [Sa-Token 全局过滤器]
*/
@Bean
public SaServletFilter getSaServletFilter() {
return new SaServletFilter()
// 指定 [拦截路由] [放行路由]
.addInclude("/**")
// 认证函数: 每次请求执行
.setAuth(obj -> {
// SaManager.getLog().debug("----- 请求path={} 提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue());
})
// 异常处理函数每次认证函数发生异常时执行此函数
.setError(e -> {
return SaResult.error(e.getMessage());
})
// 前置函数在每次认证函数之前执行
.setBeforeAuth(obj -> {SaHolder.getResponse()
// ---------- 设置跨域响应头 ----------
// 允许指定域访问跨域资源
.setHeader("Access-Control-Allow-Origin", "*")
// 允许所有请求方式
.setHeader("Access-Control-Allow-Methods", "*")
// 允许的header参数
.setHeader("Access-Control-Allow-Headers", "*")
// 有效时间
.setHeader("Access-Control-Max-Age", "3600");
// 如果是预检请求则立即返回到前端
SaRouter.match(SaHttpMethod.OPTIONS)
// .free(r -> System.out.println("--------OPTIONS预检请求不做处理"))
.back();
});
}
}

View File

@ -0,0 +1,30 @@
package org.wcs.constant;
/**
* 系统常量
*/
public class ConstantData {
public static final String SYSTEM_NAME = "WCS";
/**
* 占位字符
*/
public static final String OCCUPY_CHAR = "-";
/**
* 读码失败的返回值
*/
public static final String NO_READ = "NoRead";
/**
* 堆垛机区域前缀
*/
public static final String STACKER_AREA_PREFIX = "STACKER_AREA_";
/**
* 输送线区域前缀
*/
public static final String CONVEY_AREA_PREFIX = "CONVEY_AREA_";
}

View File

@ -0,0 +1,23 @@
package org.wcs.constant.enums.business;
import lombok.Getter;
@Getter
public enum LocationTypeEnum {
UNKNOWN(0, "未知点位"), // 保底未知点位
STACKER(1, "堆垛机"), // 堆垛机 ---- 堆垛机站台也算
CONVEY(2, "输送线"), // 箱式线
TRAY_CONVEY(3, "托盘线"), // 托盘线
AGV(4, "AGV"), // AGV
SHUTTLE(5, "四项车"); // 四项车
private final Integer code;
private final String desc;
LocationTypeEnum(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
}

View File

@ -0,0 +1,20 @@
package org.wcs.constant.enums.business;
import lombok.Getter;
@Getter
public enum LxLedColor {
RED(0xFF, "红色"),
GREEN(0xFF00, "绿色"),
YELLOW(0xFFFF, "黄色");
private final int value;
private final String name;
private LxLedColor(int value, String name) {
this.value = value;
this.name = name;
}
}

View File

@ -0,0 +1,33 @@
package org.wcs.constant.enums.business;
import lombok.Getter;
@Getter
public enum SimpleTaskEnum {
UNKNOWN(0, "未知任务"),
STACKER(1, "堆垛机任务"),
CONVEY(2, "输送任务"),
TRAY_CONVEY(3, "托盘输送任务"),
AGV(4, "AGV任务"),
SHUTTLE(5, "穿梭车任务");
private final Integer taskType;
private final String taskName;
SimpleTaskEnum(Integer taskType, String taskName) {
this.taskType = taskType;
this.taskName = taskName;
}
public static SimpleTaskEnum getSimpleTaskEnum(LocationTypeEnum locationType) {
return switch (locationType) {
case LocationTypeEnum.STACKER -> STACKER;
case LocationTypeEnum.CONVEY -> CONVEY;
case LocationTypeEnum.TRAY_CONVEY -> TRAY_CONVEY;
case LocationTypeEnum.AGV -> AGV;
case LocationTypeEnum.SHUTTLE -> SHUTTLE;
default -> UNKNOWN;
};
}
}

View File

@ -0,0 +1,23 @@
package org.wcs.constant.enums.business;
import lombok.Getter;
/**
* SSE事件类型枚举
*/
@Getter
public enum SseEventTypeEnum {
RUNNING_INFO("RUNNING_INFO", "运行记录"),
SYSTEM_INFO("SYSTEM_INFO", "系统信息");
private final String code;
private final String desc;
SseEventTypeEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
}

View File

@ -0,0 +1,28 @@
package org.wcs.constant.enums.common;
import lombok.Getter;
@Getter
public enum OnOrOffEnum {
UNKNOWN(-1, "未知"),
TRUE(1, "启用"),
FALSE(0, "停用");
private final int code;
private final String msg;
OnOrOffEnum(int code, String msg) {
this.code = code;
this.msg = msg;
}
public static OnOrOffEnum getByCode(int code) {
for (OnOrOffEnum value : OnOrOffEnum.values()) {
if (value.code == code) {
return value;
}
}
return UNKNOWN;
}
}

View File

@ -0,0 +1,29 @@
package org.wcs.constant.enums.common;
import lombok.Getter;
@Getter
public enum TrueOrFalseEnum {
UNKNOWN(-1, "未知"),
TRUE(1, ""),
FALSE(0, "");
private final int code;
private final String msg;
TrueOrFalseEnum(int code, String msg) {
this.code = code;
this.msg = msg;
}
public static TrueOrFalseEnum getByCode(int code) {
for (TrueOrFalseEnum value : TrueOrFalseEnum.values()) {
if (value.code == code) {
return value;
}
}
return UNKNOWN;
}
}

View File

@ -0,0 +1,31 @@
package org.wcs.constant.enums.database;
import lombok.Getter;
/**
* 基础资料里面的设备类型枚举
*/
@Getter
public enum BaseErroorEquipmentTypeEnum {
STACKER(0, "堆垛机"),
TRAY_CONVEY(1, "托盘线"),
CONVEY(2, "箱式线"),
CAR(3, "小车");
private final Integer code;
private final String name;
BaseErroorEquipmentTypeEnum(Integer code, String name) {
this.code = code;
this.name = name;
}
public static String getName(Integer code) {
for (BaseErroorEquipmentTypeEnum value : BaseErroorEquipmentTypeEnum.values()) {
if (value.code.equals(code)) {
return value.name;
}
}
return null;
}
}

View File

@ -0,0 +1,33 @@
package org.wcs.constant.enums.database;
import lombok.Getter;
/**
* 报警等级枚举
*/
@Getter
public enum BaseErrorLevelEnum {
NORMAL(0, "普通"),
WARNING(1, "注意"),
EMERGENCY(2, "紧急"),
FATAL(3, "致命");
private final Integer code;
private final String name;
public static String getName(Integer code) {
for (BaseErrorLevelEnum value : BaseErrorLevelEnum.values()) {
if (value.code.equals(code)) {
return value.name;
}
}
return null;
}
BaseErrorLevelEnum(Integer code, String name) {
this.code = code;
this.name = name;
}
}

View File

@ -0,0 +1,32 @@
package org.wcs.constant.enums.database;
import lombok.Getter;
/**
* 报警类型枚举
*/
@Getter
public enum BaseErrorTypeEnum {
NET_ERROR(0, "网络异常"),
POWER_ERROR(3, "电源异常"),
EQUIPMENT_ERROR(2, "设备异常");
private final Integer code;
private final String name;
BaseErrorTypeEnum(Integer code, String name) {
this.code = code;
this.name = name;
}
public static String getName(Integer code) {
for (BaseErrorTypeEnum value : BaseErrorTypeEnum.values()) {
if (value.code.equals(code)) {
return value.name;
}
}
return null;
}
}

View File

@ -0,0 +1,21 @@
package org.wcs.constant.enums.database;
import lombok.Getter;
/**
* 输送线捡选站台类型枚举
*/
@Getter
public enum ConveyPickStandTypeEnum {
BOX(0, "箱式线"),
TRAY(1, "托盘线");
private final Integer code;
private final String message;
ConveyPickStandTypeEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
}

View File

@ -0,0 +1,30 @@
package org.wcs.constant.enums.database;
import lombok.Getter;
@Getter
public enum ConveyTaskStatusEnum {
CREATED(0, "创建"),
PRINTED(1, "已打印"),
RUNNING(2, "运行中"),
ARRIVE(3, "已到达"),
ERROR(4, "错误"),
TIME_OUT(5, "超时"),
CANCEL(6, "取消");
private final Integer code;
private final String msg;
ConveyTaskStatusEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public static String getMsgByCode(Integer code) {
for (ConveyTaskStatusEnum value : ConveyTaskStatusEnum.values()) {
if (value.getCode().equals(code)) {
return value.getMsg();
}
}
return null;
}
}

View File

@ -0,0 +1,46 @@
package org.wcs.constant.enums.database;
import lombok.Getter;
/**
* 箱式线任务类型枚举
*/
@Getter
public enum ConveyTaskTypeEnum {
PICK(1, "捡选任务"),
CHECK(2, "复核任务"),
SEND(3, "发货任务"),
PUT(4, "补货任务");
private final Integer code;
private final String msg;
ConveyTaskTypeEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public static String getMsgByCode(Integer code) {
for (ConveyTaskTypeEnum value : ConveyTaskTypeEnum.values()) {
if (value.code.equals(code)) {
return value.msg;
}
}
return null;
}
/**
* 判断是否存在
* @param code 任务类型
* @return 存在结果
*/
public static boolean isExist(Integer code) {
for (ConveyTaskTypeEnum value : ConveyTaskTypeEnum.values()) {
if (value.code.equals(code)) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,26 @@
package org.wcs.constant.enums.database;
import lombok.Getter;
/**
* 电子标签种类
*/
@Getter
public enum ETagTypeEnum {
NUMBER_BUTTON_3(1, "3位数字带按钮"),
NUMBER_BUTTON_7(2, "7位数字带按钮"),
SCAN(3, "扫描枪"),
LIGHT(4, "单色灯"),
NUMBER_ENGLISH_BUTTON_12(5, "12位数字英文带按钮"),
NUMBER_CHINESE_BUTTON_12(6, "12位数字中文带按钮");
private final Integer code;
private final String desc;
ETagTypeEnum(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
}

View File

@ -0,0 +1,23 @@
package org.wcs.constant.enums.database;
import lombok.Getter;
/**
* 设备类型枚举
*/
@Getter
public enum EquipmentTypeEnum {
STACKER(1, "堆垛机"),
CONVEY(2, "传送带");
private final Integer code;
private final String name;
EquipmentTypeEnum(Integer code, String name) {
this.code = code;
this.name = name;
}
}

View File

@ -0,0 +1,21 @@
package org.wcs.constant.enums.database;
import lombok.Getter;
/**
* 电子标签任务点亮模式枚举类
*/
@Getter
public enum EtagTaskLightModelEnum {
IMMEDIATELY(1, "立即点亮"),
WAITING(2, "等待点亮");
private final Integer code;
private final String message;
EtagTaskLightModelEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
}

View File

@ -0,0 +1,26 @@
package org.wcs.constant.enums.database;
import lombok.Getter;
/**
* 电子标签任务状态枚举类
*/
@Getter
public enum EtagTaskStatusEnum {
NOT_ACTIVATED(0, "未激活"), // 待处理
ACTIVATED(1, "已激活"), // 待点亮
LIGHTING(2, "正在点亮"),
CONFIRM(3, "已确认"),
ERROR(4, "异常");
private final Integer code;
private final String message;
EtagTaskStatusEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
}

View File

@ -0,0 +1,24 @@
package org.wcs.constant.enums.database;
import lombok.Getter;
/**
* 电子标签任务类型枚举类
*/
@Getter
public enum EtagTaskTypeEnum {
PICK(1, "拣货"),
PUT(2, "上架"),
CHECK(3, "盘点"),
CLEAN(4, "清点"),
OTHER(5, "其他");
private final Integer code;
private final String message;
EtagTaskTypeEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
}

View File

@ -0,0 +1,26 @@
package org.wcs.constant.enums.database;
import lombok.Getter;
@Getter
public enum PlcTypeEnum {
SIEMENS(1, "西门子");
private final Integer code;
private final String message;
PlcTypeEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
public static PlcTypeEnum getMessage(Integer code) {
for (PlcTypeEnum value : PlcTypeEnum.values()) {
if (value.code.equals(code)) {
return value;
}
}
return PlcTypeEnum.SIEMENS;
}
}

View File

@ -0,0 +1,35 @@
package org.wcs.constant.enums.database;
import lombok.Getter;
import lombok.Setter;
/**
* 堆垛机库位状态枚举
*/
@Getter
public enum StackerLocationStatusEnum {
UNKNOWN(-1, "未知"),
EMPTY(0, "空闲"),
IN_ING(1, "入库中"),
OUT_ING(2, "出库中"),
OCCUPY(3, "占用"),
FORBIDDEN(9, "禁用");
private final Integer code;
private final String message;
StackerLocationStatusEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
public static StackerLocationStatusEnum getMessageByCode(Integer code) {
for (StackerLocationStatusEnum value : StackerLocationStatusEnum.values()) {
if (value.code.equals(code)) {
return value;
}
}
return StackerLocationStatusEnum.UNKNOWN;
}
}

View File

@ -0,0 +1,20 @@
package org.wcs.constant.enums.database;
import lombok.Getter;
/**
* 入库站台的入库模式
*/
@Getter
public enum StackerStandInTypeEnum {
IMMEDIATELY(0, "立即入库"),
CHECK_CODE(1, "验证条码入库");
private final Integer code;
private final String name;
StackerStandInTypeEnum(Integer code, String name) {
this.code = code;
this.name = name;
}
}

View File

@ -0,0 +1,52 @@
package org.wcs.constant.enums.database;
import lombok.Getter;
import java.util.ArrayList;
import java.util.List;
/**
* 组合式任务表任务状态枚举
*/
@Getter
public enum StockComposeTaskStatusEnum {
UNKNOWN(-1, "未知"),
CREATE(0, "新建待执行"),
QUEUE(1, "排队中"),
EXECUTING(2, "执行中"),
COMPLETE(3, "已完成"),
CANCEL(4, "已取消"),
ERROR(5, "任务异常"),
TIMEOUT(6, "任务超时");
private final Integer code;
private final String msg;
StockComposeTaskStatusEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public static StockComposeTaskStatusEnum getByCode(Integer code) {
for (StockComposeTaskStatusEnum stockComposeTaskStatusEnum : StockComposeTaskStatusEnum.values()) {
if (stockComposeTaskStatusEnum.getCode().equals(code)) {
return stockComposeTaskStatusEnum;
}
}
return StockComposeTaskStatusEnum.UNKNOWN;
}
/**
* 获取所有非执行状态
*
* @return 未执行状态
*/
public static List<Integer> getAllNotExecuteStatus() {
return List.of(StockComposeTaskStatusEnum.CREATE.getCode(), StockComposeTaskStatusEnum.QUEUE.getCode());
}
}

View File

@ -0,0 +1,36 @@
package org.wcs.constant.enums.database;
import lombok.Getter;
/**
* 组合式任务步骤状态枚举
*/
@Getter
public enum StockComposeTaskStepStatusEnum {
UNKNOWN(-1, "未知"),
CREATE(0, "新建待执行"),
RUNNING(1, "执行中"),
COMPLETE(3, "已完成"),
CANCEL(4, "已取消"),
ERROR(5, "任务异常"),
LEVEL_ORIGIN(6, "离开起点"),
ARRIVE_DESTINATION(7, "到达终点");
private final Integer code;
private final String msg;
StockComposeTaskStepStatusEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public static StockComposeTaskStepStatusEnum getByCode(Integer code) {
for (StockComposeTaskStepStatusEnum value : StockComposeTaskStepStatusEnum.values()) {
if (value.getCode().equals(code)) {
return value;
}
}
return StockComposeTaskStepStatusEnum.UNKNOWN;
}
}

View File

@ -0,0 +1,52 @@
package org.wcs.constant.enums.database;
import lombok.Getter;
import org.wcs.business.stock.impl.StockTaskDataCheck.*;
import org.wcs.business.stock.intf.IStockTaskDataCheck;
import java.util.List;
@Getter
public enum StockComposeTaskTypeEnum {
UNKNOWN(-1, "未知", new UnknownStockTaskDataCheck()),
AUTO(0, "自动任务", new AutoStockTaskDataCheck()),
IN(1, "入库", new InStockTaskDataCheck()),
OUT(2, "出库", new OutStockTaskDataCheck()),
MOVE(9, "移库", new MoveStockTaskDataCheck()),
CONVEY(3, "输送搬运", new ConveyStockTaskDataCheck());
private final Integer code;
private final String msg;
private final IStockTaskDataCheck stockTaskDataCheck;
StockComposeTaskTypeEnum(Integer code, String msg, IStockTaskDataCheck stockTaskDataCheck) {
this.code = code;
this.msg = msg;
this.stockTaskDataCheck = stockTaskDataCheck;
}
public static StockComposeTaskTypeEnum getByCode(Integer code) {
for (StockComposeTaskTypeEnum value : StockComposeTaskTypeEnum.values()) {
if (value.code.equals(code)) {
return value;
}
}
return StockComposeTaskTypeEnum.UNKNOWN;
}
/**
* 是否自动计算路径
*
* @param code 组合任务类型
* @return true:
*/
public static boolean autoCalculateCourseType(Integer code) {
if (code == null) {
return false;
}
return List.of(IN, OUT, MOVE).contains(StockComposeTaskTypeEnum.getByCode(code));
}
}

View File

@ -0,0 +1,39 @@
package org.wcs.constant.enums.database;
import lombok.Getter;
import org.wcs.constant.enums.business.LocationTypeEnum;
/**
* 订单执行设备枚举
*/
@Getter
public enum StockExecuteEquipmentEnum {
UNKNOWN(0, "未知"),
STACKER(1, "堆垛机"),
CONVEYOR(2, "输送线"),
SHUTTLE(3, "穿梭车"),
STOCK_BOX(4, "货柜"),
RGV(5, "RGV"),
AGV(6, "AGV"),
TRAY(7, "托盘");
private final Integer code;
private final String name;
StockExecuteEquipmentEnum(Integer code, String name) {
this.code = code;
this.name = name;
}
public static StockExecuteEquipmentEnum getExecuteEquipment(LocationTypeEnum locationType) {
return switch (locationType) {
case STACKER -> STACKER;
case CONVEY -> CONVEYOR;
case AGV -> AGV;
case SHUTTLE -> SHUTTLE;
case TRAY_CONVEY -> TRAY;
default -> UNKNOWN;
};
}
}

View File

@ -0,0 +1,17 @@
package org.wcs.constant.enums.database;
import lombok.Getter;
@Getter
public enum StockScanTypeEnum {
PLC_SCAN(0, "plc转发"),
TCP_SCAN(1, "tcp直连");
private final Integer code;
private final String desc;
StockScanTypeEnum(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
}

View File

@ -0,0 +1,43 @@
package org.wcs.constant.enums.database;
import lombok.Getter;
import java.util.List;
@Getter
public enum StockSingleTaskStatusEnum {
UNKNOWN(-1, "未知"),
CREATE(0, "新建待执行"),
QUEUE(1, "排队中"),
EXECUTING(2, "执行中"),
COMPLETE(3, "已完成"),
CANCEL(4, "已取消"),
ERROR(5, "任务异常"),
TIMEOUT(6, "任务超时");
private final Integer code;
private final String msg;
StockSingleTaskStatusEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public static StockComposeTaskStatusEnum getByCode(int code) {
for (StockComposeTaskStatusEnum stockComposeTaskStatusEnum : StockComposeTaskStatusEnum.values()) {
if (stockComposeTaskStatusEnum.getCode().equals(code)) {
return stockComposeTaskStatusEnum;
}
}
return StockComposeTaskStatusEnum.UNKNOWN;
}
/**
* 获取未完成状态码
* @return 未完成状态码
*/
public static List<Integer> notFinishedCodes() {
return List.of(CREATE.getCode(), QUEUE.getCode(), EXECUTING.getCode(), TIMEOUT.getCode());
}
}

View File

@ -0,0 +1,123 @@
package org.wcs.constant.enums.database;
import lombok.Getter;
import org.wcs.constant.enums.business.SimpleTaskEnum;
import java.util.List;
/**
* 单项任务类型枚举
*/
@Getter
public enum StockSingleTaskTypeEnum {
STACKER_AUTO(-1, "堆垛机自动任务"),
UNKNOWN(0, "未知任务"),
STACKER_IN(1, "堆垛机入库"),
STACKER_OUT(2, "堆垛机出库"),
STACKER_MOVE(9, "堆垛机移库"),
CONVEY_IN(4, "输送入库"),
CONVEY_OUT(5, "输送出库"),
CONVEY_MOVE(6, "输送移动"),
CONVEY_PICK(7, "输送线捡选"),
CONVEY_CHECK(8, "输送线复核"),
AGV(11, "AGV任务"),
SHUTTLE(10, "穿梭车任务"),
BINDING_VEHICLE_NO(12, "绑定载具号任务"); // 这个一版时载具号搬运出来后将载具号绑定到输送机上
private final Integer code;
private final String desc;
StockSingleTaskTypeEnum(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
public static StockSingleTaskTypeEnum getByCode(Integer code) {
if (code == null) {
return UNKNOWN;
}
for (StockSingleTaskTypeEnum value : values()) {
if (value.code.equals(code)) {
return value;
}
}
return UNKNOWN;
}
/**
* 获取所有堆垛机任务类型
* @return 堆垛机任务类型
*/
public static List<Integer> getAllStackerTaskType() {
return List.of(STACKER_IN.getCode(), STACKER_OUT.getCode(), STACKER_MOVE.getCode());
}
/**
* 获取所有输送任务类型
* @return 输送任务类型
*/
public static List<Integer> getAllConveyTaskType() {
return List.of(CONVEY_IN.getCode(), CONVEY_OUT.getCode(), CONVEY_MOVE.getCode());
}
/**
* 根据简单任务类型获取堆垛机任务类型
* @param simpleTaskType 简单任务类型
* @return 堆垛机任务类型
*/
public static StockSingleTaskTypeEnum getBySimpleTaskType(SimpleTaskEnum simpleTaskType) {
return switch (simpleTaskType) {
case STACKER -> STACKER_AUTO;
case CONVEY, TRAY_CONVEY -> CONVEY_MOVE;
case AGV -> AGV;
case SHUTTLE -> SHUTTLE;
default -> UNKNOWN;
};
}
public static StockSingleTaskTypeEnum getByComposeTaskTypeAndExecuteMachine(StockComposeTaskTypeEnum composeTaskType, StockExecuteEquipmentEnum executeMachine) {
if(executeMachine == StockExecuteEquipmentEnum.STACKER && composeTaskType == StockComposeTaskTypeEnum.IN) {
return StockSingleTaskTypeEnum.STACKER_IN; // 入库
}
if(executeMachine == StockExecuteEquipmentEnum.STACKER && composeTaskType == StockComposeTaskTypeEnum.OUT) {
return StockSingleTaskTypeEnum.STACKER_OUT; // 出库
}
if(executeMachine == StockExecuteEquipmentEnum.STACKER && composeTaskType == StockComposeTaskTypeEnum.MOVE) {
return StockSingleTaskTypeEnum.STACKER_MOVE; // 移库
}
if(executeMachine == StockExecuteEquipmentEnum.TRAY && composeTaskType == StockComposeTaskTypeEnum.IN) {
return StockSingleTaskTypeEnum.CONVEY_IN;
}
if(executeMachine == StockExecuteEquipmentEnum.TRAY && composeTaskType == StockComposeTaskTypeEnum.OUT) {
return StockSingleTaskTypeEnum.CONVEY_OUT;
}
if(executeMachine == StockExecuteEquipmentEnum.TRAY && composeTaskType == StockComposeTaskTypeEnum.MOVE) {
return StockSingleTaskTypeEnum.CONVEY_MOVE;
}
return UNKNOWN;
}
/**
* 输送任务类型转换成PLC任务类型
* @param taskType 输送任务类型
* @return PLC任务类型
*/
public static short conveyTaskTypeToPlc(Integer taskType) {
StockSingleTaskTypeEnum taskTypeEnum = getByCode(taskType);
return switch (taskTypeEnum) {
case CONVEY_IN -> 1;
case CONVEY_OUT -> 2;
case CONVEY_MOVE -> 3;
default -> 0;
};
}
}

View File

@ -0,0 +1,31 @@
package org.wcs.constant.enums.database;
import lombok.Getter;
import org.wcs.constant.enums.common.OnOrOffEnum;
@Getter
public enum TrayConveyLocationTypeEnum {
UNKNOWN(-1, "未知"),
TRUE(1, "任务点"),
FALSE(0, "普通点");
private final int code;
private final String msg;
TrayConveyLocationTypeEnum(int code, String msg) {
this.code = code;
this.msg = msg;
}
public static TrayConveyLocationTypeEnum getByCode(int code) {
for (TrayConveyLocationTypeEnum value : TrayConveyLocationTypeEnum.values()) {
if (value.code == code) {
return value;
}
}
return UNKNOWN;
}
}

View File

@ -0,0 +1,23 @@
package org.wcs.constant.enums.database;
import lombok.Getter;
/**
* 用户级别枚举
*/
@Getter
public enum UserLevelEnum {
NORMAL(0, "普通用户"),
MIDDLE(1, "中级用户"),
ADMIN(3, "管理员"),
TOP(4, "顶级用户");
private final Integer level;
private final String name;
UserLevelEnum(Integer level, String name) {
this.level = level;
this.name = name;
}
}

View File

@ -0,0 +1,23 @@
package org.wcs.constant.enums.database;
import lombok.Getter;
/**
* 用户账户状态枚举
*/
@Getter
public enum UserStatusEnum {
CREATE(0, "创建"),
OK(1, "正常"),
DISABLE(2, "禁用"),
LOCK(3, "锁定");
private final Integer code;
private final String desc;
UserStatusEnum(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
}

View File

@ -0,0 +1,22 @@
package org.wcs.constant.enums.http;
import lombok.Getter;
/**
* 箱式线任务回传状态枚举
*/
@Getter
public enum ConveyCallBackStatusEnum {
START(1, "开始"),
FINISH(2, "完成"),
ERROR(3, "错误"),
CANCEL(4, "取消");
private final Integer code;
private final String message;
ConveyCallBackStatusEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
}

View File

@ -0,0 +1,25 @@
package org.wcs.constant.enums.http;
import lombok.Getter;
/**
* 仓库系统回调状态枚举
*/
@Getter
public enum StockCallBackStatusEnum {
START(1, "开始"),
FINISH(2, "完成"),
ERROR(3, "错误"),
CANCEL(4, "取消"),
DOUBLE_IN(5, "重复入库"),
EMPTY_OUT(6, "空出");
private final Integer code;
private final String message;
StockCallBackStatusEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
}

View File

@ -0,0 +1,26 @@
package org.wcs.constant.enums.plc;
import lombok.Getter;
/**
* 堆垛机控制方式枚举
*/
@Getter
public enum StackerControllerModelEnum {
OFF_LINE(0, "离线"),
SELF_LEARNING(1, "自学习"),
DEBUG(2, "调试模式"),
MANUAL(3, "手动模式"),
ALONE(4, "单机模式"),
AUTO(5, "自动模式");
private final Integer code;
private final String desc;
StackerControllerModelEnum(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
}

View File

@ -0,0 +1,34 @@
package org.wcs.constant.enums.plc;
import lombok.Getter;
/**
* 堆垛机设备状态枚举
*/
@Getter
public enum StackerDeviceStatusEnum {
OFF_LINE(0, "离线"),
FREE(1, "空闲"),
ACCEPT_TASK(2, "接收任务"),
GET_MOVE(3, "取货移动"),
GETTING(4, "取货中"),
GET_COMPLETE(5, "取货完成"),
SET_MOVE(6, "放货移动"),
SETTING(7, "放货中"),
SET_COMPLETE(8, "放货完成"),
TASK_COMPLETE(9, "任务完成"),
DELETE_TASK(10, "删除任务"),
CHECKING(11, "盘点中"),
APPLY_TASK(21, "申请任务");
private final Integer code;
private final String desc;
StackerDeviceStatusEnum(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
}

View File

@ -0,0 +1,32 @@
package org.wcs.constant.enums.plc;
import lombok.Getter;
/**
* 堆垛机任务类型枚举
*/
@Getter
public enum StackerTaskTypeEnum {
ERROR(-1, "异常搬出"),
IN(1, "入库"),
OUT(2, "出库"),
MOVE(3, "移库");
private final Integer code;
private final String desc;
StackerTaskTypeEnum(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
public static StackerTaskTypeEnum getByCode(Integer code) {
for (StackerTaskTypeEnum value : values()) {
if (value.code.equals(code)) {
return value;
}
}
return null;
}
}

View File

@ -0,0 +1,28 @@
package org.wcs.constant.enums.serve;
import lombok.Getter;
/**
* 服务的响应码枚举
*/
@Getter
public enum AppServeResponseCodeEnum {
SUCCESS(200, "成功"),
DATA_REPEAT(201, "数据重复"),
FAIL(400, "失败"),
UNAUTHORIZED(401, "未授权"),
FORBIDDEN(403, "禁止访问"),
NOT_FOUND(404, "未找到"),
INTERNAL_SERVER_ERROR(500, "服务器内部错误"),
SERVICE_UNAVAILABLE(503, "服务不可用");
private final int code;
private final String msg;
AppServeResponseCodeEnum(int code, String msg) {
this.code = code;
this.msg = msg;
}
}

View File

@ -0,0 +1,251 @@
package org.wcs.factory;
import org.wcs.constant.enums.serve.AppServeResponseCodeEnum;
import org.wcs.model.vo.serve.AppServeDataResponse;
import org.wcs.model.vo.serve.AppServeResponse;
/**
* 此服务响应工厂类
*/
public class AppServeResponseFactory {
/**
* 返回一个普通的失败响应
* @param msg 响应信息
* @return 响应
*/
public static AppServeResponse fail(String msg) {
AppServeResponse response = new AppServeResponse();
response.setCode(AppServeResponseCodeEnum.FAIL.getCode());
response.setMsg(msg);
return response;
}
/**
* 返回一个带数据的失败响应
* @param msg 响应信息
* @param data 数据
* @return 响应
* @param <T> 数据范型
*/
public static <T> AppServeDataResponse<T> fail(String msg, T data) {
AppServeDataResponse<T> response = new AppServeDataResponse<>();
response.setCode(AppServeResponseCodeEnum.FAIL.getCode());
response.setMsg(msg);
response.setData(data);
return response;
}
/**
* 返回一个成功的响应
* @return 响应
*/
public static AppServeResponse success() {
AppServeResponse response = new AppServeResponse();
response.setCode(AppServeResponseCodeEnum.SUCCESS.getCode());
response.setMsg(AppServeResponseCodeEnum.SUCCESS.getMsg());
return response;
}
/**
* 返回一个成功的响应
* @param msg 响应信息
* @return 响应
*/
public static AppServeResponse success(String msg) {
AppServeResponse response = new AppServeResponse();
response.setCode(AppServeResponseCodeEnum.SUCCESS.getCode());
response.setMsg(msg);
return response;
}
/**
* 返回一个成功的响应
* @param data 数据
* @return 响应
* @param <T> 数据范型
*/
public static <T> AppServeDataResponse<T> success(T data) {
AppServeDataResponse<T> response = new AppServeDataResponse<>();
response.setCode(AppServeResponseCodeEnum.SUCCESS.getCode());
response.setMsg(AppServeResponseCodeEnum.SUCCESS.getMsg());
response.setData(data);
return response;
}
/**
* 返回一个成功的响应
* @param msg 响应信息
* @param data 数据
* @return 响应
* @param <T> 数据范型
*/
public static <T> AppServeDataResponse<T> success(String msg, T data) {
AppServeDataResponse<T> response = new AppServeDataResponse<>();
response.setCode(AppServeResponseCodeEnum.SUCCESS.getCode());
response.setMsg(msg);
response.setData(data);
return response;
}
/**
* 返回一个数据重复的响应
* @param msg 响应信息
* @return 响应
*/
public static AppServeResponse dataRepeat(String msg) {
AppServeResponse response = new AppServeResponse();
response.setCode(AppServeResponseCodeEnum.DATA_REPEAT.getCode());
response.setMsg(msg);
return response;
}
/**
* 返回一个未授权响应
* @return 响应
*/
public static AppServeResponse unauthorized() {
AppServeResponse response = new AppServeResponse();
response.setCode(AppServeResponseCodeEnum.UNAUTHORIZED.getCode());
response.setMsg(AppServeResponseCodeEnum.UNAUTHORIZED.getMsg());
return response;
}
/**
* 返回一个未授权响应
* @param msg 响应信息
* @return 响应
*/
public static AppServeResponse unauthorized(String msg) {
AppServeResponse response = new AppServeResponse();
response.setCode(AppServeResponseCodeEnum.UNAUTHORIZED.getCode());
response.setMsg(msg);
return response;
}
/**
* 返回一个带数据的未授权响应
* @param msg 响应信息
* @param data 数据
* @return 结果
* @param <T> 范型
*/
public static <T> AppServeDataResponse<T> unauthorized(String msg, T data) {
AppServeDataResponse<T> response = new AppServeDataResponse<>();
response.setCode(AppServeResponseCodeEnum.UNAUTHORIZED.getCode());
response.setMsg(msg);
response.setData(data);
return response;
}
/**
* 返回一个禁止访问响应
* @return 响应
*/
public static AppServeResponse forbidden() {
AppServeResponse response = new AppServeResponse();
response.setCode(AppServeResponseCodeEnum.FORBIDDEN.getCode());
response.setMsg(AppServeResponseCodeEnum.FORBIDDEN.getMsg());
return response;
}
/**
* 返回一个禁止访问响应
* @param msg 响应信息
* @return 响应
*/
public static AppServeResponse forbidden(String msg) {
AppServeResponse response = new AppServeResponse();
response.setCode(AppServeResponseCodeEnum.FORBIDDEN.getCode());
response.setMsg(msg);
return response;
}
/**
* 返回一个未找到响应
* @return 响应
*/
public static AppServeResponse notFound() {
AppServeResponse response = new AppServeResponse();
response.setCode(AppServeResponseCodeEnum.NOT_FOUND.getCode());
response.setMsg(AppServeResponseCodeEnum.NOT_FOUND.getMsg());
return response;
}
/**
* 返回一个未找到响应
* @param msg 响应信息
* @return 响应
*/
public static AppServeResponse notFound(String msg) {
AppServeResponse response = new AppServeResponse();
response.setCode(AppServeResponseCodeEnum.NOT_FOUND.getCode());
response.setMsg(msg);
return response;
}
/**
* 返回一个服务器内部错误响应
* @return 响应
*/
public static AppServeResponse internalServerError() {
AppServeResponse response = new AppServeResponse();
response.setCode(AppServeResponseCodeEnum.INTERNAL_SERVER_ERROR.getCode());
response.setMsg(AppServeResponseCodeEnum.INTERNAL_SERVER_ERROR.getMsg());
return response;
}
/**
* 返回一个服务器内部错误响应
* @param msg 响应信息
* @param data 数据
* @return 响应信息
* @param <T> 范型
*/
public static <T> AppServeDataResponse<T> internalServerError(String msg, T data) {
AppServeDataResponse<T> response = new AppServeDataResponse<>();
response.setCode(AppServeResponseCodeEnum.INTERNAL_SERVER_ERROR.getCode());
response.setMsg(msg);
response.setData(data);
return response;
}
/**
* 返回一个服务器内部错误响应
* @param msg 响应信息
* @return 响应
*/
public static AppServeResponse internalServerError(String msg) {
AppServeResponse response = new AppServeResponse();
response.setCode(AppServeResponseCodeEnum.INTERNAL_SERVER_ERROR.getCode());
response.setMsg(msg);
return response;
}
/**
* 返回一个服务不可用响应
* @return 响应
*/
public static AppServeResponse serviceUnavailable() {
AppServeResponse response = new AppServeResponse();
response.setCode(AppServeResponseCodeEnum.SERVICE_UNAVAILABLE.getCode());
response.setMsg(AppServeResponseCodeEnum.SERVICE_UNAVAILABLE.getMsg());
return response;
}
/**
* 返回一个服务不可用响应
* @param msg 响应信息
* @return 响应
*/
public static AppServeResponse serviceUnavailable(String msg) {
AppServeResponse response = new AppServeResponse();
response.setCode(AppServeResponseCodeEnum.SERVICE_UNAVAILABLE.getCode());
response.setMsg(msg);
return response;
}
}

View File

@ -0,0 +1,125 @@
package org.wcs.factory;
import org.wcs.model.vo.pub.PubServeDataResponse;
import org.wcs.model.vo.pub.PubServeResponse;
/**
* 公用服务响应工厂类
*/
public class PubServeResponseFactory {
/**
* 返回一个普通的失败响应
* @param msg 响应信息
* @return 响应
*/
public static PubServeResponse fail(String msg) {
PubServeResponse response = new PubServeResponse();
response.setCode(999);
response.setMessage(msg);
return response;
}
/**
* 返回一个带数据的失败响应
* @param msg 响应信息
* @param data 数据
* @return 响应
* @param <T> 数据范型
*/
public static <T> PubServeDataResponse<T> fail(String msg, T data) {
PubServeDataResponse<T> response = new PubServeDataResponse<>();
response.setCode(999);
response.setMessage(msg);
response.setData(data);
return response;
}
/**
* 返回一个成功的响应
* @return 响应
*/
public static PubServeResponse success() {
PubServeResponse response = new PubServeResponse();
response.setCode(200);
response.setMessage("SUCCESS");
return response;
}
/**
* 返回一个成功的响应
* @param msg 响应信息
* @return 响应
*/
public static PubServeResponse success(String msg) {
PubServeResponse response = new PubServeResponse();
response.setCode(200);
response.setMessage(msg);
return response;
}
/**
* 返回一个成功的响应
* @param data 数据
* @return 响应
* @param <T> 数据范型
*/
public static <T> PubServeDataResponse<T> success(T data) {
PubServeDataResponse<T> response = new PubServeDataResponse<>();
response.setCode(200);
response.setMessage("SUCCESS");
response.setData(data);
return response;
}
/**
* 返回一个成功的响应
* @param msg 响应信息
* @param data 数据
* @return 响应
* @param <T> 数据范型
*/
public static <T> PubServeDataResponse<T> success(String msg, T data) {
PubServeDataResponse<T> response = new PubServeDataResponse<>();
response.setCode(200);
response.setMessage(msg);
response.setData(data);
return response;
}
/**
* 返回一个未授权响应
* @return 响应
*/
public static PubServeResponse unauthorized() {
PubServeResponse response = new PubServeResponse();
response.setCode(401);
response.setMessage("unauthorized");
return response;
}
/**
* 返回一个禁止访问响应
* @return 响应
*/
public static PubServeResponse forbidden() {
PubServeResponse response = new PubServeResponse();
response.setCode(403);
response.setMessage("Forbidden");
return response;
}
/**
* 响应系统错误
* @param msg 错误信息
* @return 响应
*/
public static PubServeResponse systemError(String msg) {
PubServeResponse response = new PubServeResponse();
response.setCode(500);
response.setMessage(msg);
return response;
}
}

View File

@ -0,0 +1,69 @@
package org.wcs.helper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.wcs.mapper.intf.AppBaseConfigService;
import org.wcs.model.po.app.AppBaseConfig;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 系统配置的帮助类
*/
@Component
@RequiredArgsConstructor
public class AppConfigHelper {
private final AppBaseConfigService baseConfigService;
private Map<String, String> configMap; // 存储配置的map
/**
* 加载配置
*/
public void loadConfig() {
List<AppBaseConfig> configs = baseConfigService.queryAll();
if(configs == null) {
return;
}
configMap = new HashMap<>();
for (AppBaseConfig config : configs) {
configMap.put(config.getConfigKey(), config.getConfigValue());
}
}
/**
* 获取配置
* @param key 配置键
* @return 配置值
*/
public String getConfig(String key) {
if(configMap == null) {
loadConfig();
}
return configMap.get(key);
}
/**
* 设置配置
* @param key 配置键
* @param value 配置值
* @return 设置结果
*/
public synchronized String SetConfig(String key, String value) {
AppBaseConfig config = new AppBaseConfig();
config.setConfigKey(key);
config.setConfigValue(value);
int update = baseConfigService.update(config);
if(update <= 0) {
return "更新 config 表 数据库操作失败";
}
configMap.put(key, value);
return null;
}
}

View File

@ -0,0 +1,39 @@
package org.wcs.helper;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
@RequiredArgsConstructor
public class HttpResponseHelper {
private final StringRedisTemplate stringRedisTemplate;
private static final String RECORD_ID_KEY = "WCS:HTTP:RESPONSE";
/**
* 获取响应结果
*
* @param recordId 记录ID
* @return 响应结果
*/
public String getResponse(String recordId) {
return stringRedisTemplate.opsForValue().get(RECORD_ID_KEY + ":" + recordId);
}
/**
* 设置响应结果
*
* @param recordId 响应结果
* @param response 响应结果
* @param timeout 超时时间
*/
public void setResponse(String recordId, String response, long timeout) {
stringRedisTemplate.opsForValue().set(RECORD_ID_KEY + ":" + recordId, response, timeout, TimeUnit.SECONDS);
}
}

View File

@ -0,0 +1,27 @@
package org.wcs.helper;
import org.wcs.model.bo.tuple.Tuple2;
import org.wcs.model.pojo.led.LedBaseInfo;
import java.util.ArrayList;
import java.util.List;
/**
* LED屏的操作帮助类
*/
public class LedHelper {
/**
* LED屏的通讯对象 <绑定的位置Ip地址>
*/
private static List<Tuple2<String, String>> ledLocationIpList = new ArrayList<>();
/**
* LED屏的通讯对象 <IP基础资料>
* 一般用于对外开放接口
*/
private static List<Tuple2<String, LedBaseInfo>> ledIpCommunicationList = new ArrayList<>();
}

View File

@ -0,0 +1,116 @@
package org.wcs.helper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.wcs.mapper.intf.AppConveyLocationService;
import org.wcs.mapper.intf.AppStackerLocationService;
import org.wcs.mapper.intf.AppStackerStandService;
import org.wcs.mapper.intf.AppTrayConveyLocationService;
import org.wcs.model.po.app.AppConveyLocation;
import org.wcs.model.po.app.AppStackerLocation;
import org.wcs.model.po.app.AppStackerStand;
import org.wcs.model.po.app.AppTrayConveyLocation;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 点位帮助类
*/
@Component
@RequiredArgsConstructor
public class LocationHelper {
private final AppStackerLocationService stackerLocationService;
private final AppStackerStandService stackerStandService;
private final AppTrayConveyLocationService trayConveyLocationService;
private final AppConveyLocationService conveyLocationService;
/**
* 点位映射 < 业务点位系统点位 >
*/
Map<String, String> btLocationMap;
/**
* 点位映射 < 系统点位业务点位 >
*/
Map<String, String> tbLocationMap;
/**
* 初始化数据
*/
public synchronized void init() {
List<AppStackerLocation> stackerLocations = stackerLocationService.queryStackerLocationAll();
if(stackerLocations == null) {
return;
}
List<AppStackerStand> stackerStands = stackerStandService.queryAll();
if(stackerStands == null) {
return;
}
List<AppTrayConveyLocation> trayConveyLocations = trayConveyLocationService.queryAll();
if(trayConveyLocations == null) {
return;
}
List<AppConveyLocation> conveyLocations = conveyLocationService.queryAll();
if(conveyLocations == null) {
return;
}
btLocationMap = new ConcurrentHashMap<>();
tbLocationMap = new ConcurrentHashMap<>();
stackerLocations.forEach(stackerLocation -> {
btLocationMap.put(stackerLocation.getBusinessLocation(), stackerLocation.getLocationId());
tbLocationMap.put(stackerLocation.getLocationId(), stackerLocation.getBusinessLocation());
});
stackerStands.forEach(stackerStand -> {
btLocationMap.put(stackerStand.getStandId(), stackerStand.getStandId());
tbLocationMap.put(stackerStand.getStandId(), stackerStand.getStandId());
});
trayConveyLocations.forEach(trayConveyLocation -> {
btLocationMap.put(trayConveyLocation.getBusinessLocationId(), trayConveyLocation.getLocationId());
tbLocationMap.put(trayConveyLocation.getLocationId(), trayConveyLocation.getBusinessLocationId());
});
conveyLocations.forEach(conveyLocation -> {
btLocationMap.put(conveyLocation.getBusinessLocationId(), conveyLocation.getLocationId());
tbLocationMap.put(conveyLocation.getLocationId(), conveyLocation.getBusinessLocationId());
});
}
/**
* 获取业务点位
* @param appLocationId 系统点位
* @return 业务点位
*/
public String getBusinessLocationId(String appLocationId) {
if(tbLocationMap == null) {
init();
}
if(tbLocationMap == null) {
return null;
}
return tbLocationMap.get(appLocationId);
}
/**
* 获取系统点位
* @param businessLocationId 业务点位
* @return 系统点位
*/
public String getAppLocationId(String businessLocationId) {
if(btLocationMap == null) {
init();
}
if(btLocationMap == null) {
return null;
}
return btLocationMap.get(businessLocationId);
}
}

View File

@ -0,0 +1,55 @@
package org.wcs.helper;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.wcs.constant.ConstantData;
import org.wcs.utils.AppStringUtils;
@Component
@RequiredArgsConstructor
public class PlcTaskIdHelper {
private final AppConfigHelper appConfigHelper;
private final StringRedisTemplate stringRedisTemplate;
private static final int MAX_COUNT = 100; // 最大任务ID数
private static final String KEY_TASK_ID = ConstantData.SYSTEM_NAME + ":config";
/**
* 获取新的任务ID
* @return 任务ID
*/
public Integer newTaskId()
{
String plcTaskIdRedisKet = KEY_TASK_ID + ":plcTaskId";
Long size = stringRedisTemplate.opsForList().size(plcTaskIdRedisKet);
if(size == null || size < MAX_COUNT - 80) {
String plcTaskId = appConfigHelper.getConfig("PLC_TASK_ID");
if (plcTaskId == null) {
return null;
}
if(!AppStringUtils.isNumber(plcTaskId)) {
return null;
}
int plcTaskIdInt = Integer.parseInt(plcTaskId);
if(plcTaskIdInt > 150000000) {
plcTaskIdInt = 100000;
}
int maxPlcTaskId = plcTaskIdInt + MAX_COUNT;
String setResult = appConfigHelper.SetConfig("PLC_TASK_ID", String.valueOf(maxPlcTaskId));
if(AppStringUtils.isNotEmpty(setResult)) {
return null; // 设置数据库失败
}
for(int i = plcTaskIdInt; i < plcTaskIdInt + MAX_COUNT; i++) {
stringRedisTemplate.opsForList().rightPush(plcTaskIdRedisKet, String.valueOf(i));
}
}
String plcTaskIdString = stringRedisTemplate.opsForList().leftPop(plcTaskIdRedisKet);
if(plcTaskIdString == null) {
return null; // 获取redis数据失败
}
return Integer.parseInt(plcTaskIdString);
}
}

View File

@ -0,0 +1,103 @@
package org.wcs.helper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import org.wcs.constant.enums.business.SseEventTypeEnum;
import java.util.Map;
/**
* SSE 帮助类
*/
@Slf4j
@Component
public class SseHelper {
private static Map<String, SseEmitter> emitters;
/**
* 设置 SSEEmitter
* @param key
* @param emitter SSEEmitter
*/
public synchronized void setEmitter(String key, SseEmitter emitter) {
if(emitters == null) {
emitters = new java.util.concurrent.ConcurrentHashMap<>();
}
if(emitters.containsKey(key)) {
SseEmitter sseEmitter = emitters.get(key);
try{
sseEmitter.complete();
emitters.remove(key);
log.info("已存在连接,已关闭,连接 Key: {}", key);
} catch (Exception ignored){
}
}
emitters.put(key, emitter);
log.info("已创建连接,连接 Key: {}", key);
}
/**
* 关闭所有 SSEEmitter
*/
public synchronized void closeAllEmitter() {
if(emitters == null) {
return;
}
for (Map.Entry<String, SseEmitter> entry : emitters.entrySet()) {
SseEmitter emitter = entry.getValue();
try {
emitter.complete();
emitters.remove(entry.getKey());
} catch (Exception ex) {
emitter.complete();
emitters.remove(entry.getKey());
log.info("已关闭连接,连接 Key: {},异常信息:{}", entry.getKey(), ex.getMessage());
}
}
}
/**
* 移除 SSEEmitter
* @param key
*/
public synchronized void removeEmitter(String key) {
if(emitters == null) {
return;
}
SseEmitter emitter = emitters.get(key);
if(emitter != null) {
emitters.remove(key);
emitter.complete();
log.info("已关闭连接,连接 Key: {}", key);
}
}
/**
* 发送事件给所有连接
* @param eventName 事件名称
* @param message 消息
*/
public void SendEventToAll(SseEventTypeEnum eventName, String message) {
if(emitters == null) {
return;
}
for (Map.Entry<String, SseEmitter> entry : emitters.entrySet()) {
SseEmitter emitter = entry.getValue();
try {
SseEmitter.SseEventBuilder event = SseEmitter.event()
.name(eventName.getCode())
.reconnectTime(1000)
.data(message, MediaType.APPLICATION_JSON);
emitter.send(event);
} catch (Exception ex) {
emitter.complete();
emitters.remove(entry.getKey());
log.info("已关闭连接,因为无法发送数据,连接 Key: {},异常信息:{}", entry.getKey(), ex.getMessage());
}
}
}
}

View File

@ -0,0 +1,9 @@
package org.wcs.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.wcs.model.po.app.AppBaseApiInfo;
@Mapper
public interface AppBaseApiInfoMapper extends BaseMapper<AppBaseApiInfo> {
}

View File

@ -0,0 +1,9 @@
package org.wcs.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.wcs.model.po.app.AppBaseConfig;
@Mapper
public interface AppBaseConfigMapper extends BaseMapper<AppBaseConfig> {
}

View File

@ -0,0 +1,9 @@
package org.wcs.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.wcs.model.po.app.AppBaseDb;
@Mapper
public interface AppBaseDbMapper extends BaseMapper<AppBaseDb> {
}

View File

@ -0,0 +1,9 @@
package org.wcs.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.wcs.model.po.app.AppBaseErrInfo;
@Mapper
public interface AppBaseErrInfoMapper extends BaseMapper<AppBaseErrInfo> {
}

View File

@ -0,0 +1,9 @@
package org.wcs.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.wcs.model.po.app.AppBaseGroupPermission;
@Mapper
public interface AppBaseGroupPermissionMapper extends BaseMapper<AppBaseGroupPermission> {
}

View File

@ -0,0 +1,12 @@
package org.wcs.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.wcs.model.po.app.AppBaseLed;
/**
* LED 屏基础资料
*/
@Mapper
public interface AppBaseLedMapper extends BaseMapper<AppBaseLed> {
}

Some files were not shown because too many files have changed in this diff Show More