feat(rfid): 支持读取记录按日期分表查询和操作

- 在Service接口中新增queryDate参数,用于按日期定位具体分表查询
- 列表查询根据beginRecordTime和endRecordTime自动路由多个分表
- 新增、修改操作根据recordTime自动确定目标分表
- 删除接口增加queryDate参数支持精确分表定位并校验有效性
- 文档详细说明分表设计、前后端调用链与业务逻辑使用方式
- 前端实现分页查询、选中行修改删除传递queryDate支持分表访问
- 添加分表工具类封装表名映射、表存在检测及缓存机制
main
zangch@mesnac.com 1 month ago
parent 47b7112c50
commit c6913eb294

@ -56,3 +56,185 @@
- rfid_read_record按照日期分表例如rfid_read_record_20251126可参考rfid_read_record_20251126.sql文件数据库类型是MySQL - rfid_read_record按照日期分表例如rfid_read_record_20251126可参考rfid_read_record_20251126.sql文件数据库类型是MySQL
- 前后端代码可以参考ShardingQuery.md文件其他项目TiDb数据库的总结文档 - 前后端代码可以参考ShardingQuery.md文件其他项目TiDb数据库的总结文档
# 业务逻辑
## 设备记录成功率逻辑
-
- 成功率统计接口:`GET /rfid/dashboard/successRate`按小时023 点)返回折线图数据,用于前端折线图展示.
- 统计维度:后端自动以当前系统日期作为“今日”,**同时统计昨日同一小时的成功率**,前端无需传任何查询参数.
- 分表与时间范围:分别通过 `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>`,再按 023 小时顺序构造 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. **补全 023 小时完整时间轴**
- 将 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`.

File diff suppressed because it is too large Load Diff

@ -6,10 +6,14 @@ import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.PageQuery;
import java.util.Collection; import java.util.Collection;
import java.util.Date;
import java.util.List; import java.util.List;
/** /**
* Service * Service
* <p>
* rfid_read_record_yyyyMMdd
* </p>
* *
* @author zch * @author zch
* @date 2025-11-25 * @date 2025-11-25
@ -19,13 +23,17 @@ public interface IRfidReadRecordService {
/** /**
* *
* *
* @param id * @param id
* @param queryDate
* @return * @return
*/ */
RfidReadRecordVo queryById(Long id); RfidReadRecordVo queryById(Long id, Date queryDate);
/** /**
* *
* <p>
* bo beginRecordTime endRecordTime
* </p>
* *
* @param bo * @param bo
* @param pageQuery * @param pageQuery
@ -43,6 +51,9 @@ public interface IRfidReadRecordService {
/** /**
* *
* <p>
* recordTime
* </p>
* *
* @param bo * @param bo
* @return * @return
@ -51,6 +62,9 @@ public interface IRfidReadRecordService {
/** /**
* *
* <p>
* recordTime
* </p>
* *
* @param bo * @param bo
* @return * @return
@ -60,9 +74,10 @@ public interface IRfidReadRecordService {
/** /**
* *
* *
* @param ids * @param ids
* @param isValid * @param queryDate
* @param isValid
* @return * @return
*/ */
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid); Boolean deleteWithValidByIds(Collection<Long> ids, Date queryDate, Boolean isValid);
} }

Loading…
Cancel
Save