realtime.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. """安全监控及重大设备实时列表:保留原始字段并显式返回分页信息。"""
  2. import asyncio
  3. import json
  4. import re
  5. from pydantic import BaseModel, ConfigDict, Field
  6. import config
  7. from extensions.main_fan import gateway_queries
  8. SENSOR_PATH = '/aqjk/node/getNodeRealtimeDetails'
  9. EQUIPMENT_PATH = '/zdsb/sbnode/selectSbNodeList'
  10. class RealtimePage(BaseModel):
  11. model_config = ConfigDict(extra='forbid', str_strip_whitespace=True)
  12. dept_id: str = Field(min_length=1, max_length=64, pattern=r'^[A-Za-z0-9_-]+$')
  13. page_num: int = Field(ge=1)
  14. page_size: int = Field(ge=1)
  15. def _page(dept_id, page_num, page_size):
  16. page = RealtimePage(dept_id=dept_id, page_num=page_num, page_size=page_size)
  17. maximum = config.get_int('MAIN_FAN_REALTIME_MAX_PAGE_SIZE', 1000)
  18. if maximum <= 0 or page.page_size > maximum:
  19. raise ValueError(f'实时列表每页最多 {maximum} 条,请减小 page_size 并分页查询')
  20. return page
  21. def _list_data(body, page):
  22. if not isinstance(body, (dict, list)):
  23. raise ValueError('实时列表未返回 JSON 对象或数组,请核对上游接口')
  24. container = body.get('data') if isinstance(body, dict) and 'data' in body else body
  25. rows, pointer = None, None
  26. if isinstance(body, dict) and 'rows' in body:
  27. rows, pointer, container = body['rows'], '/payload/rows', body
  28. elif isinstance(container, dict) and 'rows' in container:
  29. rows, pointer = container['rows'], '/payload/data/rows'
  30. elif isinstance(container, list) and all(isinstance(row, dict) for row in container):
  31. rows = container
  32. pointer = '/payload/data' if isinstance(body, dict) else '/payload'
  33. if pointer is not None and (not isinstance(rows, list) or not all(isinstance(row, dict) for row in rows)):
  34. raise ValueError('实时列表 rows 必须是对象数组,不能把无效列表当成0条记录')
  35. total = container.get('total') if isinstance(container, dict) else None
  36. if total is None and isinstance(body, dict):
  37. total = body.get('total')
  38. total = int(str(total)) if re.fullmatch(r'\d+', str(total)) else None
  39. count = len(rows) if rows is not None else None
  40. has_more = ((page.page_num - 1) * page.page_size + count < total
  41. if total is not None and count is not None else None)
  42. next_page = (page.page_num + 1 if count and (has_more is True or
  43. (has_more is None and count >= page.page_size)) else None)
  44. preview = rows[:3] if rows is not None else []
  45. preview_omitted = len(json.dumps(preview, ensure_ascii=False, allow_nan=False)) > 16000
  46. summary = {'page_num': page.page_num, 'page_size': page.page_size, 'row_count': count,
  47. 'total': total, 'has_more': has_more, 'next_page': next_page,
  48. 'rows_pointer': pointer, 'list_recognized': pointer is not None,
  49. 'preview': [] if preview_omitted else preview, 'preview_omitted': preview_omitted}
  50. if rows is None:
  51. summary['next_action'] = '完整响应已保存,列表结构尚未识别;请读取 data_path 的 payload 核对字段,不能按0条记录处理。'
  52. elif count == 0 and has_more:
  53. summary['next_action'] = '本页为空但 total 表明仍有记录,请核对页码与上游分页状态,不能视为完整空列表。'
  54. return {'payload': body}, summary
  55. async def _query(identifier, page, params, user, session, *, equipment):
  56. path = EQUIPMENT_PATH if equipment else SENSOR_PATH
  57. workflow, request, body = await gateway_queries.fetch(identifier, user, session, path, params)
  58. data, summary = await asyncio.to_thread(_list_data, body, page)
  59. if equipment:
  60. summary['dev_of_system'] = '30'
  61. return await asyncio.to_thread(gateway_queries.archive, workflow, user, session, request, body, data, summary,
  62. kind='equipment_realtime' if equipment else 'sensor_realtime',
  63. collection='equipment_queries' if equipment else 'sensor_queries',
  64. operation='查询重大设备实时列表' if equipment else '查询实时传感器列表', filename='realtime.json')
  65. async def query_equipment(identifier, dept_id, page_num, page_size, user, session):
  66. page = _page(dept_id, page_num, page_size)
  67. params = {'pageNum': str(page.page_num), 'pageSize': str(page.page_size),
  68. 'deptId': page.dept_id, 'devOfSystem': '30'}
  69. return await _query(identifier, page, params, user, session, equipment=True)
  70. async def query_sensors(identifier, dept_id, page_num, page_size, sensor_type_list, user, session):
  71. page = _page(dept_id, page_num, page_size)
  72. if sensor_type_list is not None:
  73. if not isinstance(sensor_type_list, list) or len(sensor_type_list) > 128:
  74. raise ValueError('sensor_type_list 须为最多128项的编码数组')
  75. if any(not isinstance(value, str) or len(value) > 128 or
  76. not re.fullmatch(r'[A-Za-z0-9_-]+(?:,[A-Za-z0-9_-]+)*', value) for value in sensor_type_list):
  77. raise ValueError('传感器类型须为字符串编码,可保留接口示例中的逗号组合,不能猜类型编码')
  78. # 保留网关约定的键值对数组与索引参数;不擅自拆分上游可能有分组含义的逗号项。
  79. params = [['pageSize', page.page_size], ['pageNum', page.page_num], ['deptId', page.dept_id]]
  80. params.extend([f'sensorTypeList[{index}]', value] for index, value in enumerate(sensor_type_list or []))
  81. return await _query(identifier, page, params, user, session, equipment=False)