|
|
|
|
|
# 主要展示(其余字段在前端暂时隐藏或注释掉)
|
|
|
|
|
|
- 位置信息:标识、位置编号、位置别名、父级编号、是否标识、备注、创建人、创建时间、更新人、更新时间
|
|
|
|
|
|
- 设备信息:标识、设备编号、设备名称、所在位置、设备地址、设备端口、读取频率、是否标识、备注、创建人、创建时间、更新人、更新时间
|
|
|
|
|
|
- 读取记录:标识、设备编号、读取状态(1-成功;0-失败)、条码信息、记录时间
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- **代码注意事项**
|
|
|
|
|
|
1. ** 连表查询规范**
|
|
|
|
|
|
使用 `mybatis-plus`做连表查询时:
|
|
|
|
|
|
- `MybatisPlus`自带方法不支持连表查询,需要在mapper.xml中编写sql语句.
|
|
|
|
|
|
- 在实体类和 `VO` 中添加连表字段,实体类字段需加 `@TableField(exist = false)` 注解。
|
|
|
|
|
|
2. **types 同步维护**
|
|
|
|
|
|
连表查询得到的字段,需要同步补充到前端 `types.ts` 中,保证类型定义完整。
|
|
|
|
|
|
3. **接口前缀规范**
|
|
|
|
|
|
参考同模块下已有接口前缀,统一后台 `@RequestMapping` 前缀。
|
|
|
|
|
|
4. **前端列表 / 表单字段规范**
|
|
|
|
|
|
- 只展示业务字段,技术字段仅用于内部关联(连表查询得到名称字段),若无关联则隐藏技术字段(前端在 `columns` 中设为 `false`)
|
|
|
|
|
|
- 业务字段不显示多余的说明文字,例如字典字段的括号以及括号的字典数据
|
|
|
|
|
|
- 列表不直接展示主键,使用“序号”列(`index`)替代。
|
|
|
|
|
|
- 注释掉不合理的搜索条件,对话框中注释掉不合理的输入字段。
|
|
|
|
|
|
- 数字字段使用数字输入框;
|
|
|
|
|
|
- 新增对话框时,前端激活标识 `isMarked` 默认为 `1`。
|
|
|
|
|
|
- 需要绑定其他表字段时,通过下拉框选择,表单字段使用 `get${ClassName}List` 接口获取不分页的全部数据。
|
|
|
|
|
|
5. **主子表 List 字段**
|
|
|
|
|
|
主子表结构时,需要在主表实体中将子表作为 `List` 类型字段,便于提交新增/修改、返回数据和删除时统一处理,保证事务一致性。
|
|
|
|
|
|
6. **字典使用规范**
|
|
|
|
|
|
- 后端只返回字典键值。
|
|
|
|
|
|
- 前端列表通过 `dict-tag` 组件展示字典文本。
|
|
|
|
|
|
- 前端需要显式声明字典,例如:
|
|
|
|
|
|
`const { project_category, change_type, project_phases } = toRefs<any>(proxy?.useDict('project_category', 'change_type', 'project_phases'));`
|
|
|
|
|
|
7. **实体字段类型规范**
|
|
|
|
|
|
- 数据库 `int` → Java `Integer`
|
|
|
|
|
|
- `decimal` → `BigDecimal`
|
|
|
|
|
|
- `bigint` → `Long`
|
|
|
|
|
|
- `double` → `Double`
|
|
|
|
|
|
- `float` → `Float`
|
|
|
|
|
|
11. **主子表删除规范**
|
|
|
|
|
|
- 主子表删除时,必须在同一事务中同时删除主表与子表记录,保证数据一致性。
|
|
|
|
|
|
12. **接口合并与聚合**
|
|
|
|
|
|
- 前端调用接口应尽量合并:同一模块或同一功能点优先通过单个接口组装返回所需数据,减少多次请求。
|
|
|
|
|
|
13. **前端 columns 配置规则**
|
|
|
|
|
|
- `columns` 的 `key` 必须从 `0` 开始,不间断完整自增。
|
|
|
|
|
|
- 对比实际表格列与 `columns` 配置,第 `0` 列使用“序号”(`index` 列)而不是主键。
|
|
|
|
|
|
- 将“项目ID”等技术列从 `columns` 中完全移除(而不是隐藏),去掉租户编号等无业务意义的字段,其他字段根据实际情况调整,确保 `columns.key` 连续从 `0` 自增且与表格实际列一一对应。
|
|
|
|
|
|
14. **名称获取规范**
|
|
|
|
|
|
- 所有创建人名称、部门名称必须通过后端连表查询得到,而不是前端拼接或本地缓存获取。
|
|
|
|
|
|
15. **业务编号规范**
|
|
|
|
|
|
- 业务编号应该唯一。
|
|
|
|
|
|
16. **树形结构规范**
|
|
|
|
|
|
- 树形结构的parent_id和ancestors字段的递归逻辑需要完善,若parent_id和ancestors为0则为顶级节点,0是必须有的。
|
|
|
|
|
|
17. **更新子节点的ancestors**
|
|
|
|
|
|
- 更新子节点的ancestors时,需要更新所有子节点的ancestors,而不是只更新当前节点的ancestors。
|
|
|
|
|
|
18. **前端树形结构的新增或修改**
|
|
|
|
|
|
- 前端对话框第一行必须是先选择父节点!
|
|
|
|
|
|
19. **特别注意:rfid_read_record分表**
|
|
|
|
|
|
- rfid_read_record按照日期分表,例如rfid_read_record_20251126,可参考rfid_read_record_20251126.sql文件,数据库类型是MySQL
|
|
|
|
|
|
- 前后端代码可以参考ShardingQuery.md文件(其他项目TiDb数据库的总结文档)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 业务逻辑
|
|
|
|
|
|
# 位置树与设备
|
|
|
|
|
|
- 位置树分为车间/机台/工位,共三级
|
|
|
|
|
|
- 设备绑定在工位上
|
|
|
|
|
|
- IP地址 deviceAddress 唯一校验
|
|
|
|
|
|
- 端口号devicePort应该都是20108,创建的时候先默认这个,如果有需要再修改
|
|
|
|
|
|
## 设备记录
|
|
|
|
|
|
### 分页查询
|
|
|
|
|
|
- 分页查询逻辑参考rfid-middleware\ruoyi-modules\hw-rfid\ShardingQuery.md文档
|
|
|
|
|
|
### 条码信息显示空格符号的空格处理
|
|
|
|
|
|
- 202020202020205357303034 设备返回的这个 20 转成 ascii 是个空格
|
|
|
|
|
|
- 条码信息原始值可能包含空格(0x20) 和 NULL 字符(0x00) 等不可见字符,例如设备返回 `202020202020205357303034`,其中 `20` 转成 ASCII 是空格。
|
|
|
|
|
|
- Mapper 层在 `RfidReadRecordMapper.xml` 中对 `barcode` 使用 `TRIM(t.barcode) as barcode` 去掉前后空格,但对 NULL/控制字符无效(SQL 的 `TRIM()` 只能去除标准空格 ASCII 32)。
|
|
|
|
|
|
- Service 层在 `RfidReadRecordServiceImpl` 中通过 `cleanBarcodeList / cleanBarcode`,使用正则 `Pattern.compile("[\\s\\x00-\\x1F\\x7F]+")` 清理所有不可见字符,只保留业务字符,最终前端看到的条码为无空格的纯业务值(例如 `QNC006`)。
|
|
|
|
|
|
### 设备记录成功率逻辑
|
|
|
|
|
|
- 成功率统计接口:`GET /rfid/dashboard/successRate`,按小时(0–23 点)返回折线图数据,用于前端折线图展示.
|
|
|
|
|
|
- 统计维度:后端自动以当前系统日期作为“今日”,**同时统计昨日同一小时的成功率**,前端无需传任何查询参数.
|
|
|
|
|
|
- 分表与时间范围:分别通过 `RfidReadRecordTableHelper.getTableName(今日)` 与 `RfidReadRecordTableHelper.getTableName(昨日)` 生成当日与昨日分表(如 `rfid_read_record_20251126`),各自只统计 `00:00:00` 至 `23:59:59` 之间的读取记录.
|
|
|
|
|
|
- 成功率聚合 SQL:在分表上按 `DATE_FORMAT(record_time, '%H:00')` 分组,统计每小时的总记录数 `totalCount` 与成功记录数 `successCount`(`read_status = '1'`)。
|
|
|
|
|
|
- 成功率计算公式:`successRate = ROUND(successCount * 100.0 / totalCount, 2)`,即“该小时成功条数 ÷ 总条数 × 100”,保留两位小数.
|
|
|
|
|
|
- Mapper 至少返回 `timePoint`、`successRate` 字段,Service 层分别将“今日”与“昨日”的统计结果转为 `Map<timePoint, successRate>`,再按 0–23 小时顺序构造 24 条数据:
|
|
|
|
|
|
- `successRate`:今日该小时成功率;
|
|
|
|
|
|
- `yesterdaySuccessRate`:昨日同一小时成功率;
|
|
|
|
|
|
- 对于没有统计结果的小时,对应字段返回 `null`,保证时间轴完整.
|
|
|
|
|
|
- 异常与容错:当某日分表不存在或查询出错时,Service 捕获异常记录告警日志并返回空列表或空 Map,不影响整个看板接口的可用性.
|
|
|
|
|
|
|
|
|
|
|
|
## 看板模块
|
|
|
|
|
|
|
|
|
|
|
|
### 接口1:实时统计(顶部概览 + 告警列表)
|
|
|
|
|
|
|
|
|
|
|
|
> 请求方式:`GET /rfid/dashboard/realtime`
|
|
|
|
|
|
> 前端方法:`getRealtimeStats(alarmLimit?: number)`
|
|
|
|
|
|
> 后端链路:`DashboardController#getRealtimeStats` → `IDashboardService#getRealtimeStats`
|
|
|
|
|
|
|
|
|
|
|
|
**请求参数(Query)**
|
|
|
|
|
|
|
|
|
|
|
|
- `alarmLimit?: number`
|
|
|
|
|
|
告警列表限制数量,可选。
|
|
|
|
|
|
- **不传:返回全部告警记录**,供前端滚动显示。
|
|
|
|
|
|
|
|
|
|
|
|
**返回实体与字段**
|
|
|
|
|
|
|
|
|
|
|
|
- 后端返回类型:`DashboardVO.RealtimeStats`
|
|
|
|
|
|
- 前端 TS 类型:`RealtimeStats`
|
|
|
|
|
|
|
|
|
|
|
|
字段说明:
|
|
|
|
|
|
|
|
|
|
|
|
1. `overview: StatisticsOverview` —— 顶部统计概览
|
|
|
|
|
|
- `deviceTotal: number`:设备总数(仅统计 `is_marked = 1` 的设备)。
|
|
|
|
|
|
- `onlineCount: number`:在线设备数量(`online_status = 1`)。
|
|
|
|
|
|
- `offlineCount: number`:离线设备数量(`online_status = 0`)。
|
|
|
|
|
|
- `alarmCount: number`:告警设备数量(`alarm_status = 1`)。
|
|
|
|
|
|
|
|
|
|
|
|
2. `alarmStats: AlarmStatVO[]` —— 告警统计列表
|
|
|
|
|
|
- `alarmTime: string`:告警时间(`MM-dd HH:mm` 格式)。
|
|
|
|
|
|
- `deviceName: string`:设备名称。
|
|
|
|
|
|
- `location: string`:所在位置名称。
|
|
|
|
|
|
- `alarmLevel: string`:告警级别。
|
|
|
|
|
|
- `alarmAction: string`:告警行为/建议动作。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### 接口2:位置树(含设备信息)
|
|
|
|
|
|
|
|
|
|
|
|
> 请求方式:`GET /rfid/dashboard/deviceStatus`
|
|
|
|
|
|
> 前端方法:`getLocationTree()`
|
|
|
|
|
|
> 后端链路:`DashboardController#getLocationTree` → `IDashboardService#getLocationTree`
|
|
|
|
|
|
|
|
|
|
|
|
**请求参数(Query)**
|
|
|
|
|
|
|
|
|
|
|
|
- 无参数,直接调用即返回完整位置树。
|
|
|
|
|
|
|
|
|
|
|
|
**返回实体与字段**
|
|
|
|
|
|
|
|
|
|
|
|
- 后端返回类型:`List<DashboardVO.LocationTreeNode>`
|
|
|
|
|
|
- 前端 TS 类型:`LocationTreeNode[]`
|
|
|
|
|
|
|
|
|
|
|
|
字段说明:
|
|
|
|
|
|
|
|
|
|
|
|
1. 位置基础信息
|
|
|
|
|
|
- `id: number`:位置 ID。
|
|
|
|
|
|
- `locationCode: string`:位置编号。
|
|
|
|
|
|
- `locationAlias: string`:位置别名。
|
|
|
|
|
|
- `locationType: string`:位置类型(`1-车间;2-工序;3-工位/设备`)。
|
|
|
|
|
|
- `parentId: number`:父级位置 ID(`0` 或 `null` 表示顶级)。
|
|
|
|
|
|
|
|
|
|
|
|
2. 设备信息(仅当 `locationType = 3` 时有值)
|
|
|
|
|
|
- `deviceId: number`:设备 ID,**前端用于匹配 WebSocket 数据**。
|
|
|
|
|
|
- `deviceCode: string`:设备编号。
|
|
|
|
|
|
- `deviceName: string`:设备名称。
|
|
|
|
|
|
- `onlineStatus: string`:在线状态(`1-在线;0-离线`)。
|
|
|
|
|
|
- `alarmStatus: string`:告警状态(`0-正常;1-告警`)。
|
|
|
|
|
|
|
|
|
|
|
|
3. 子节点
|
|
|
|
|
|
- `children: LocationTreeNode[]`:子位置/设备列表。
|
|
|
|
|
|
|
|
|
|
|
|
**WebSocket 数据匹配说明**
|
|
|
|
|
|
|
|
|
|
|
|
C# 服务会通过 WebSocket 实时推送设备读取记录,格式示例:
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"objid": 1993656942031147008,
|
|
|
|
|
|
"deviceId": 1,
|
|
|
|
|
|
"readStatus": "1",
|
|
|
|
|
|
"epcStr": "SW004",
|
|
|
|
|
|
"alarmFlag": "0",
|
|
|
|
|
|
"alarmLevel": "",
|
|
|
|
|
|
"alarmType": "",
|
|
|
|
|
|
"alarmAction": "",
|
|
|
|
|
|
"recordTime": "2025-11-26T20:23:49.695356+08:00"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
前端通过 `deviceId` 字段匹配位置树中的设备节点,实现实时数据展示。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### 接口3:设备记录的成功率(按小时统计)
|
|
|
|
|
|
|
|
|
|
|
|
> 请求方式:`GET /rfid/dashboard/successRate`
|
|
|
|
|
|
> 前端方法:`getSuccessRateTrends(type?: string)`
|
|
|
|
|
|
> 后端链路:`DashboardController#getSuccessRateTrends` → `DashboardServiceImpl#getSuccessRateTrends`
|
|
|
|
|
|
|
|
|
|
|
|
**请求参数(Query)**
|
|
|
|
|
|
|
|
|
|
|
|
- `type?: string`
|
|
|
|
|
|
- 前端不传参,后端直接默认当天,返回
|
|
|
|
|
|
|
|
|
|
|
|
**返回实体与字段**
|
|
|
|
|
|
|
|
|
|
|
|
- 后端返回类型:`List<DashboardVO.SuccessRateTrend>`
|
|
|
|
|
|
- 前端 TS 类型:`SuccessRateTrend[]`
|
|
|
|
|
|
|
|
|
|
|
|
字段说明:
|
|
|
|
|
|
|
|
|
|
|
|
1. `timePoint: string`
|
|
|
|
|
|
- 时间点,格式为 `"HH:00"`,例如 `"09:00"`,从 `"00:00"` 到 `"23:00"` 共 24 个。
|
|
|
|
|
|
|
|
|
|
|
|
2. `successRate: number | null`
|
|
|
|
|
|
- 该小时的读取成功率(百分比,如 `98.5`)。
|
|
|
|
|
|
- 如果某个小时没有任何读取记录,则为 `null`。
|
|
|
|
|
|
|
|
|
|
|
|
**后端处理要点(概要版)**
|
|
|
|
|
|
|
|
|
|
|
|
1. **查询维度**
|
|
|
|
|
|
- 入参 `type`:`today`(默认)、`yesterday`。
|
|
|
|
|
|
- 根据 `type` 计算目标日期 `targetDate`:
|
|
|
|
|
|
- `today` 或其它值 → `LocalDate.now()`(今日);
|
|
|
|
|
|
- `yesterday` → `LocalDate.now().minusDays(1)`(昨日)。
|
|
|
|
|
|
|
|
|
|
|
|
2. **分表与时间范围**
|
|
|
|
|
|
- 使用 `RfidReadRecordTableHelper.getTableName(targetDate)` 生成当日分表名,例如 `rfid_read_record_20251126`。
|
|
|
|
|
|
- 计算当日时间范围字符串:
|
|
|
|
|
|
- 起始时间 `startTime = targetDate.atStartOfDay()`(`yyyy-MM-dd 00:00:00`);
|
|
|
|
|
|
- 结束时间 `endTime = targetDate.atTime(23, 59, 59)`(`yyyy-MM-dd 23:59:59`)。
|
|
|
|
|
|
|
|
|
|
|
|
3. **按小时统计成功率(Mapper 层)**
|
|
|
|
|
|
- 调用 `readRecordMapper.selectSuccessRateByHour(tableName, startTime, endTime)` 进行聚合统计。
|
|
|
|
|
|
- SQL 约定返回字段:
|
|
|
|
|
|
- `timePoint`:小时点,格式为 `"HH:00"`,如 `"08:00"`;
|
|
|
|
|
|
- `successRate`:该小时的读取成功率(类型可能为 `Double` / `BigDecimal` 等)。
|
|
|
|
|
|
- 成功率的具体计算逻辑由 Mapper SQL 维护(如成功条数 / 总条数),Service 层只消费结果,不参与公式计算。
|
|
|
|
|
|
|
|
|
|
|
|
4. **补全 0–23 小时完整时间轴**
|
|
|
|
|
|
- 将 Mapper 返回结果转换为 `Map<String, Double>`;
|
|
|
|
|
|
- 循环 `hour = 0..23` 构建 24 个时间点;
|
|
|
|
|
|
- 对没有统计结果的小时,`successRate` 返回为 `null`。
|
|
|
|
|
|
|
|
|
|
|
|
5. **前端展示约定**
|
|
|
|
|
|
- 折线图 X 轴:直接使用 `timePoint`(`"HH:00"`)。
|
|
|
|
|
|
- 折线图 Y 轴:使用 `successRate` 数值。
|
|
|
|
|
|
- 当前端拿到 `successRate = null` 的点时,可根据实际需求选择:
|
|
|
|
|
|
- 展示为空值(不连线 / 断点);或
|
|
|
|
|
|
- 按 0 处理(展示为 0%)。
|
|
|
|
|
|
- 由于后端已保证 24 个小时点完整返回,前端无需再做补全或排序逻辑。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### 原有接口(聚合 / 辅助)
|
|
|
|
|
|
|
|
|
|
|
|
1. `GET /rfid/dashboard/data`
|
|
|
|
|
|
- 前端方法:`getDashboardData(locationId?: number)` / `getDashboardStats(locationId?: number)`(兼容老名称)。
|
|
|
|
|
|
- 参数:`locationId?: number`,位置 ID,可选。
|
|
|
|
|
|
- 返回:`DashboardVO`(统计概览 + 设备状态列表 + 成功率趋势 + 告警统计)。
|
|
|
|
|
|
|
|
|
|
|
|
2. `GET /rfid/dashboard/overview`
|
|
|
|
|
|
- 前端方法:`getOverview()`。
|
|
|
|
|
|
- 参数:无。
|
|
|
|
|
|
- 返回:`StatisticsOverview`,字段含义同上。
|
|
|
|
|
|
|
|
|
|
|
|
3. `GET /rfid/dashboard/alarmStats`
|
|
|
|
|
|
- 前端方法:`getAlarmStats(limit?: number)`。
|
|
|
|
|
|
- 参数:`limit?: number`,限制返回条数,默认 10。
|
|
|
|
|
|
- 返回:`AlarmStatVO[]`,字段含义同实时统计中的 `alarmStats`.
|