You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

13 KiB

主要展示(其余字段在前端暂时隐藏或注释掉)

  • 位置信息:标识、位置编号、位置别名、父级编号、是否标识、备注、创建人、创建时间、更新人、更新时间

  • 设备信息:标识、设备编号、设备名称、所在位置、设备地址、设备端口、读取频率、是否标识、备注、创建人、创建时间、更新人、更新时间

  • 读取记录:标识、设备编号、读取状态(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
      • decimalBigDecimal
      • bigintLong
      • doubleDouble
      • floatFloat
  1. 主子表删除规范
    • 主子表删除时,必须在同一事务中同时删除主表与子表记录,保证数据一致性。
  2. 接口合并与聚合
    • 前端调用接口应尽量合并:同一模块或同一功能点优先通过单个接口组装返回所需数据,减少多次请求。
  3. 前端 columns 配置规则
    • columnskey 必须从 0 开始,不间断完整自增。
    • 对比实际表格列与 columns 配置,第 0 列使用“序号”(index 列)而不是主键。
    • 将“项目ID”等技术列从 columns 中完全移除(而不是隐藏),去掉租户编号等无业务意义的字段,其他字段根据实际情况调整,确保 columns.key 连续从 0 自增且与表格实际列一一对应。
  4. 名称获取规范
    • 所有创建人名称、部门名称必须通过后端连表查询得到,而不是前端拼接或本地缓存获取。
  5. 业务编号规范
    • 业务编号应该唯一。
  6. 树形结构规范
    • 树形结构的parent_id和ancestors字段的递归逻辑需要完善若parent_id和ancestors为0则为顶级节点0是必须有的。
  7. 更新子节点的ancestors
    • 更新子节点的ancestors时需要更新所有子节点的ancestors而不是只更新当前节点的ancestors。
  8. 前端树形结构的新增或修改
    • 前端对话框第一行必须是先选择父节点!
  9. 特别注意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按小时023 点)返回折线图数据,用于前端折线图展示.
  • 统计维度:后端自动以当前系统日期作为“今日”,同时统计昨日同一小时的成功率,前端无需传任何查询参数.
  • 分表与时间范围:分别通过 RfidReadRecordTableHelper.getTableName(今日)RfidReadRecordTableHelper.getTableName(昨日) 生成当日与昨日分表(如 rfid_read_record_20251126),各自只统计 00:00:0023:59:59 之间的读取记录.
  • 成功率聚合 SQL在分表上按 DATE_FORMAT(record_time, '%H:00') 分组,统计每小时的总记录数 totalCount 与成功记录数 successCountread_status = '1')。
  • 成功率计算公式:successRate = ROUND(successCount * 100.0 / totalCount, 2),即“该小时成功条数 ÷ 总条数 × 100”保留两位小数.
  • Mapper 至少返回 timePointsuccessRate 字段Service 层分别将“今日”与“昨日”的统计结果转为 Map<timePoint, successRate>,再按 023 小时顺序构造 24 条数据:
    • successRate:今日该小时成功率;
    • yesterdaySuccessRate:昨日同一小时成功率;
    • 对于没有统计结果的小时,对应字段返回 null,保证时间轴完整.
  • 异常与容错当某日分表不存在或查询出错时Service 捕获异常记录告警日志并返回空列表或空 Map不影响整个看板接口的可用性.

看板模块

接口1实时统计顶部概览 + 告警列表)

请求方式:GET /rfid/dashboard/realtime
前端方法:getRealtimeStats(alarmLimit?: number)
后端链路:DashboardController#getRealtimeStatsIDashboardService#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#getLocationTreeIDashboardService#getLocationTree

请求参数Query

  • 无参数,直接调用即返回完整位置树。

返回实体与字段

  • 后端返回类型:List<DashboardVO.LocationTreeNode>
  • 前端 TS 类型:LocationTreeNode[]

字段说明:

  1. 位置基础信息

    • id: number:位置 ID。
    • locationCode: string:位置编号。
    • locationAlias: string:位置别名。
    • locationType: string:位置类型(1-车间2-工序3-工位/设备)。
    • parentId: number:父级位置 ID0null 表示顶级)。
  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 实时推送设备读取记录,格式示例:

{
    "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#getSuccessRateTrendsDashboardServiceImpl#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. 查询维度

    • 入参 typetoday(默认)、yesterday
    • 根据 type 计算目标日期 targetDate
      • today 或其它值 → LocalDate.now()(今日);
      • yesterdayLocalDate.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.