diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..1f8ec3d Binary files /dev/null and b/.DS_Store differ diff --git a/data-center-business-controller/src/main/java/com/techsor/datacenter/business/controller/CommonController.java b/data-center-business-controller/src/main/java/com/techsor/datacenter/business/controller/CommonController.java index 09e8360..51e80ab 100644 --- a/data-center-business-controller/src/main/java/com/techsor/datacenter/business/controller/CommonController.java +++ b/data-center-business-controller/src/main/java/com/techsor/datacenter/business/controller/CommonController.java @@ -31,6 +31,7 @@ import com.techsor.datacenter.business.dto.common.DatacenterV1QueryParams; import com.techsor.datacenter.business.dto.common.GetFileTemporaryParams; import com.techsor.datacenter.business.dto.common.QueryBuilding; import com.techsor.datacenter.business.dto.common.QueryDataFor2D3D; +import com.techsor.datacenter.business.dto.data.QueryDeviceDataParams; import com.techsor.datacenter.business.dto.device.ApiAlarmDeviceSearchParams; import com.techsor.datacenter.business.dto.device.ApiRiliDeviceSearchParams; import com.techsor.datacenter.business.dto.device.DeviceSearchParams; @@ -185,6 +186,14 @@ public class CommonController{ return commonService.queryDeviceInfo(Apikey, deviceSearchParams); } + @Operation(summary = "Query device data by device IDs and time range") + @RequestMapping(value = "/queryDeviceData", method = RequestMethod.POST) + public SimpleDataResponse queryDeviceData( + @RequestBody QueryDeviceDataParams queryDeviceDataParams, + @Parameter(name = "Apikey", description = "API key", required = false, schema = @Schema(defaultValue = "RjV5ZTZBeFArekMxM2hlekFjWnlmZz09")) @RequestHeader(required = true) String Apikey) { + return commonService.queryDeviceData(Apikey, queryDeviceDataParams); + } + /** * Query asset information by class. * diff --git a/data-center-business-model/src/main/java/com/techsor/datacenter/business/dto/data/QueryDeviceDataParams.java b/data-center-business-model/src/main/java/com/techsor/datacenter/business/dto/data/QueryDeviceDataParams.java new file mode 100644 index 0000000..4ad12ee --- /dev/null +++ b/data-center-business-model/src/main/java/com/techsor/datacenter/business/dto/data/QueryDeviceDataParams.java @@ -0,0 +1,19 @@ +package com.techsor.datacenter.business.dto.data; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Data +public class QueryDeviceDataParams { + + @Schema(description = "Device ID list", example = "[\"FFFF0001\",\"FFFF0002\"]", required = true) + private List deviceIds; + + @Schema(description = "Start time, Unix milliseconds timestamp", example = "1709870240425", required = true) + private Long startTime; + + @Schema(description = "End time, Unix milliseconds timestamp", example = "1709889240425", required = true) + private Long endTime; +} \ No newline at end of file diff --git a/data-center-business-model/src/main/java/com/techsor/datacenter/business/vo/data/DeviceDataVO.java b/data-center-business-model/src/main/java/com/techsor/datacenter/business/vo/data/DeviceDataVO.java new file mode 100644 index 0000000..36d8200 --- /dev/null +++ b/data-center-business-model/src/main/java/com/techsor/datacenter/business/vo/data/DeviceDataVO.java @@ -0,0 +1,41 @@ +package com.techsor.datacenter.business.vo.data; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +public class DeviceDataVO { + + @Schema(description = "Device ID", example = "FFFF0001") + private String deviceId; + + @Schema(description = "Device name", example = "温度传感器") + private String deviceName; + + @Schema(description = "Raw data JSON", example = "{\"temp\":25.5}") + private String rawData; + + @Schema(description = "Device data values, comma-separated", example = "25.5") + private String dataValue; + + @Schema(description = "Receive timestamp (milliseconds)", example = "1709870240425") + private Long receiveTs; + + @Schema(description = "Status", example = "UP") + private String status; + + @Schema(description = "Alert level", example = "1") + private String alertLevel; + + @Schema(description = "Alert content", example = "温度异常") + private String alertContent; + + @Schema(description = "Building info", example = "东京建物日本桥ビル") + private String buildingInfo; + + @Schema(description = "Floor info", example = "3F") + private String floorInfo; + + @Schema(description = "Space info", example = "会議室A") + private String spaceInfo; +} \ No newline at end of file diff --git a/data-center-business-service/src/main/java/com/techsor/datacenter/business/service/CommonService.java b/data-center-business-service/src/main/java/com/techsor/datacenter/business/service/CommonService.java index 6dc56ff..c23a720 100644 --- a/data-center-business-service/src/main/java/com/techsor/datacenter/business/service/CommonService.java +++ b/data-center-business-service/src/main/java/com/techsor/datacenter/business/service/CommonService.java @@ -16,6 +16,7 @@ import com.techsor.datacenter.business.dto.common.DatacenterV1QueryParams; import com.techsor.datacenter.business.dto.common.GetFileTemporaryParams; import com.techsor.datacenter.business.dto.common.QueryBuilding; import com.techsor.datacenter.business.dto.common.QueryDataFor2D3D; +import com.techsor.datacenter.business.dto.data.QueryDeviceDataParams; import com.techsor.datacenter.business.dto.device.ApiAlarmDeviceSearchParams; import com.techsor.datacenter.business.dto.device.ApiRiliDeviceSearchParams; import com.techsor.datacenter.business.dto.device.DeviceSearchParams; @@ -88,4 +89,6 @@ public interface CommonService { SimpleDataResponse querySpaceInfoList(String authorization, ApiSpaceSearchParams searchParams); + SimpleDataResponse queryDeviceData(String apikey, QueryDeviceDataParams params); + } diff --git a/data-center-business-service/src/main/java/com/techsor/datacenter/business/service/impl/CommonServiceImpl.java b/data-center-business-service/src/main/java/com/techsor/datacenter/business/service/impl/CommonServiceImpl.java index 28034fd..d1671a7 100644 --- a/data-center-business-service/src/main/java/com/techsor/datacenter/business/service/impl/CommonServiceImpl.java +++ b/data-center-business-service/src/main/java/com/techsor/datacenter/business/service/impl/CommonServiceImpl.java @@ -7,6 +7,7 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.text.MessageFormat; +import java.time.ZoneId; import java.time.Duration; import java.time.LocalDate; import java.time.format.DateTimeFormatter; @@ -78,6 +79,7 @@ import com.techsor.datacenter.business.dto.common.DatacenterV1QueryParams; import com.techsor.datacenter.business.dto.common.GetFileTemporaryParams; import com.techsor.datacenter.business.dto.common.QueryBuilding; import com.techsor.datacenter.business.dto.common.QueryDataFor2D3D; +import com.techsor.datacenter.business.dto.data.QueryDeviceDataParams; import com.techsor.datacenter.business.dto.device.ApiAlarmDeviceSearchParams; import com.techsor.datacenter.business.dto.device.ApiRiliDeviceSearchParams; import com.techsor.datacenter.business.dto.device.DeviceSearchParams; @@ -103,6 +105,7 @@ import com.techsor.datacenter.business.vo.device.ApiAlarmDeviceInfoVO; import com.techsor.datacenter.business.vo.device.ApiCancelAlarmDeviceInfoVO; import com.techsor.datacenter.business.vo.device.ApiRiliDeviceInfoVO; import com.techsor.datacenter.business.vo.device.DeviceVO; +import com.techsor.datacenter.business.vo.data.DeviceDataVO; import com.techsor.datacenter.business.vo.space.ApiSpaceWithAssetVO; import com.techsor.datacenter.business.vo.version.VersionVO; @@ -1977,4 +1980,123 @@ public class CommonServiceImpl implements CommonService { return SimpleDataResponse.success(deviceInfoVOs); } + + @Override + public SimpleDataResponse queryDeviceData(String apikey, QueryDeviceDataParams params) { + try { + if (CollectionUtils.isEmpty(params.getDeviceIds())) { + return SimpleDataResponse.fail(ResponseCode.MSG_ERROR, "[deviceIds] is required"); + } + if (null == params.getStartTime()) { + return SimpleDataResponse.fail(ResponseCode.MSG_ERROR, "[startTime] is required"); + } + if (null == params.getEndTime()) { + return SimpleDataResponse.fail(ResponseCode.MSG_ERROR, "[endTime] is required"); + } + + long startTime = params.getStartTime(); + long endTime = params.getEndTime(); + + if (startTime < 10000000000L) { + startTime = startTime * 1000; + } + if (endTime < 10000000000L) { + endTime = endTime * 1000; + } + + if (startTime > endTime) { + return SimpleDataResponse.fail(ResponseCode.MSG_ERROR, "startTime must be before endTime"); + } + + if (endTime - startTime > 7 * 24 * 60 * 60 * 1000L) { + return SimpleDataResponse.fail(ResponseCode.MSG_ERROR, "Time range must not exceed 7 days"); + } + + Map paramMap = new HashMap<>(); + paramMap.put("apikey", apikey); + ApikeyInfo2 apikeyInfo = commonOpt.getAuroraInfoByApikey(paramMap); + + if (null == apikeyInfo) { + return SimpleDataResponse.fail(ResponseCode.MSG_ERROR, "Apikey is invalid"); + } + + apikeyInfo.setAuroraPassword(DESUtil.decrypt(apikeyInfo.getAuroraPassword(), Constants.DES_SALT)); + apikeyInfo.setAuroraUsername(DESUtil.decrypt(apikeyInfo.getAuroraUsername(), Constants.DES_SALT)); + + List dateList = DateUtils.getDatesBetween(startTime, endTime, ZoneId.of("Asia/Tokyo"), "yyyy_MM_dd"); + + List resultList = new ArrayList<>(); + + Class.forName("com.mysql.cj.jdbc.Driver"); + try (Connection conn = DriverManager.getConnection( + MessageFormat.format(Constants.AURORA_URL_FORMAT, apikeyInfo.getAuroraUrl()), + apikeyInfo.getAuroraUsername(), apikeyInfo.getAuroraPassword())) { + + StringBuilder inClause = new StringBuilder(); + for (int i = 0; i < params.getDeviceIds().size(); i++) { + inClause.append("?"); + if (i < params.getDeviceIds().size() - 1) { + inClause.append(","); + } + } + + for (String dateStr : dateList) { + String table = "rawData_" + dateStr; + String sql = "select * from " + table + + " where deviceId in (" + inClause + ") and ? <= receive_ts and receive_ts <= ?" + + " order by receive_ts desc, hashId desc"; + + logger.info("queryDeviceData sql:{}", sql); + + try (PreparedStatement preparedStatement = conn.prepareStatement(sql)) { + int idx = 1; + for (String deviceId : params.getDeviceIds()) { + preparedStatement.setString(idx++, deviceId); + } + preparedStatement.setLong(idx++, startTime); + preparedStatement.setLong(idx, endTime); + + ResultSet result = preparedStatement.executeQuery(); + + while (result.next()) { + DeviceDataVO vo = new DeviceDataVO(); + vo.setDeviceId(result.getString("deviceId")); + vo.setDeviceName(result.getString("deviceTypeName")); + vo.setRawData(result.getString("rawData")); + + String rawDataStr = result.getString("rawData"); + if (StringUtils.isNotBlank(rawDataStr)) { + try { + JSONObject rawDataObj = JSON.parseObject(rawDataStr); + List valueList = new ArrayList<>(); + for (String key : rawDataObj.keySet()) { + valueList.add(rawDataObj.get(key)); + } + vo.setDataValue(StringUtils.join(valueList, ",")); + } catch (Exception ignored) { + } + } + + vo.setReceiveTs(result.getLong("receive_ts")); + vo.setStatus(result.getString("status")); + vo.setAlertLevel(result.getString("alertLevel")); + vo.setAlertContent(result.getString("alertContent")); + vo.setBuildingInfo(result.getString("buildingInfo")); + vo.setFloorInfo(result.getString("floorInfo")); + vo.setSpaceInfo(result.getString("spaceInfo")); + resultList.add(vo); + } + } + } + } + + return SimpleDataResponse.success(resultList); + + } catch (Exception e) { + logger.error("queryDeviceData error", e); + return SimpleDataResponse.fail(ResponseCode.SERVER_ERROR, ResponseCode.SERVER_ERROR_MSG); + } + } + + } diff --git a/document/.DS_Store b/document/.DS_Store new file mode 100644 index 0000000..e87a7d0 Binary files /dev/null and b/document/.DS_Store differ diff --git a/document/openapis/query-device-data-openapi.json b/document/openapis/query-device-data-openapi.json new file mode 100644 index 0000000..4d6392a --- /dev/null +++ b/document/openapis/query-device-data-openapi.json @@ -0,0 +1,236 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Device Data Query API", + "description": "通过设备ID列表和时间范围查询设备数据的接口文档", + "version": "1.0.0" + }, + "servers": [ + { + "url": "https://api.example.com", + "description": "Production Server" + } + ], + "paths": { + "/common/queryDeviceData": { + "post": { + "summary": "Query device data by device IDs and time range", + "description": "根据设备ID列表和时间范围查询Aurora中的设备原始数据,支持跨天查询但时间跨度不能超过7天", + "tags": ["CommonController"], + "parameters": [ + { + "name": "Apikey", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "API key for authentication, provided by user" + }, + { + "name": "Referer", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "Request source domain, must match server's accessControlAllowOrigin config (e.g. https://bilplartform-stg.ifmservice.jp)" + } + ], + "requestBody": { + "description": "Query parameters including device IDs and time range", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/QueryDeviceDataParams" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimpleDataResponse_List_DeviceDataVO" + } + } + } + }, + "400": { + "description": "Bad request - validation error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimpleDataResponse" + } + } + } + }, + "401": { + "description": "Unauthorized - invalid API key", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimpleDataResponse" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimpleDataResponse" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "QueryDeviceDataParams": { + "type": "object", + "required": ["deviceIds", "startTime", "endTime"], + "properties": { + "deviceIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Device ID list", + "example": ["FFFF0001", "FFFF0002"] + }, + "startTime": { + "type": "integer", + "format": "int64", + "description": "Start time, Unix milliseconds timestamp", + "example": 1709870240425 + }, + "endTime": { + "type": "integer", + "format": "int64", + "description": "End time, Unix milliseconds timestamp (must not exceed 7 days from startTime)", + "example": 1709889240425 + } + } + }, + "DeviceDataVO": { + "type": "object", + "properties": { + "deviceId": { + "type": "string", + "description": "Device ID", + "example": "FFFF0001" + }, + "deviceName": { + "type": "string", + "description": "Device name", + "example": "温度传感器" + }, + "rawData": { + "type": "string", + "description": "Raw data JSON", + "example": "{\"temp\":25.5}" + }, + "dataValue": { + "type": "string", + "description": "Device data values, comma-separated", + "example": "25.5" + }, + "receiveTs": { + "type": "integer", + "format": "int64", + "description": "Receive timestamp (milliseconds)", + "example": 1709870240425 + }, + "status": { + "type": "string", + "description": "Status", + "example": "UP" + }, + "alertLevel": { + "type": "string", + "description": "Alert level", + "example": "1" + }, + "alertContent": { + "type": "string", + "description": "Alert content", + "example": "温度异常" + }, + "buildingInfo": { + "type": "string", + "description": "Building info", + "example": "东京建物日本桥ビル" + }, + "floorInfo": { + "type": "string", + "description": "Floor info", + "example": "3F" + }, + "spaceInfo": { + "type": "string", + "description": "Space info", + "example": "会議室A" + } + } + }, + "SimpleDataResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "description": "Status code, 0 or 200 indicates success", + "example": 0 + }, + "msg": { + "type": "string", + "description": "Response message", + "example": "success" + }, + "data": { + "description": "Response data (null on error)" + }, + "errorMap": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Detailed error information", + "example": { + "deviceIds": "Device ID list is required" + } + } + } + }, + "SimpleDataResponse_List_DeviceDataVO": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "description": "Status code, 0 or 200 indicates success", + "example": 0 + }, + "msg": { + "type": "string", + "description": "Response message", + "example": "success" + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DeviceDataVO" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/document/skills/.DS_Store b/document/skills/.DS_Store new file mode 100644 index 0000000..17eb37c Binary files /dev/null and b/document/skills/.DS_Store differ diff --git a/document/skills/eaon_query-device-data.zip b/document/skills/eaon_query-device-data.zip new file mode 100644 index 0000000..8e36cdf Binary files /dev/null and b/document/skills/eaon_query-device-data.zip differ diff --git a/document/skills/query-device-data/SKILL.md b/document/skills/query-device-data/SKILL.md new file mode 100644 index 0000000..eaf1014 --- /dev/null +++ b/document/skills/query-device-data/SKILL.md @@ -0,0 +1,239 @@ +--- +name: "query-device-data" +description: "Queries device raw data from Aurora by device IDs and time range via /common/queryDeviceData API. Invoke when user needs to retrieve device telemetry/historical data with device ID list and time range." +--- + +# Query Device Data + +通过设备ID列表和时间范围查询 Aurora 中的设备原始数据。 + +## 触发条件 + +当用户需要按设备ID列表和时间范围查询设备原始数据时调用此 skill。 + +用户可能用自然语言表达,例如: +- "查询设备A和设备B昨天的数据" +- "查一下FT0001最近3天的数据" +- "看看FFFF0001今天的数据" +- "获取设备A和C在2024年1月1日到1月5日的数据" + +## API 信息 + +| 项目 | 内容 | +|------|------| +| 接口路径 | `POST /common/queryDeviceData` | +| 认证方式 | Header 传入 `Apikey` | +| Referer 校验 | Header 需传入 `Referer`(值为服务端域名),否则被过滤器拦截返回 403 | +| 时间范围限制 | 最多7天 | +| 数据源 | Aurora 数据库 `rawData_yyyy_MM_dd` 分表 | + +## 自然语言转 API 参数 + +### 1. 时间范围解析 + +用户可能会用各种方式描述时间,需要根据当前时间(`Today's date`)计算出对应的毫秒时间戳。 + +| 自然语言表达 | 转换为 API 参数 | +|:-------------|:----------------| +| "最近 X 天 / 近 X 天 / 过去 X 天" | `startTime` = 当前日期往前推 X 天 的 00:00:00(时区 Asia/Tokyo),`endTime` = 当前时间的 23:59:59 | +| "昨天" | `startTime` = 昨天 00:00:00,`endTime` = 昨天 23:59:59 | +| "今天" | `startTime` = 今天 00:00:00,`endTime` = 当前时间 | +| "今天 x 点到 y 点" | `startTime` = 今天 x:00:00,`endTime` = 今天 y:59:59 | +| "2024年1月1日到2024年1月5日" | `startTime` = 2024-01-01 00:00:00,`endTime` = 2024-01-05 23:59:59 | +| "X月Y日到X月Y日" | 根据当前年份推算,`startTime` = X月Y日 00:00:00,`endTime` = X月Y日 23:59:59 | +| "上个月" | `startTime` = 上个月1号 00:00:00,`endTime` = 上个月最后一天 23:59:59 | +| "最近X小时" | `startTime` = 当前时间往前推X小时,`endTime` = 当前时间 | + +**注意**: +- 所有时间戳需要转换为 **毫秒**(13位数字) +- 时区统一使用 `Asia/Tokyo` +- `startTime` 和 `endTime` 之间的跨度不能超过 **7 天** + +### 2. 设备ID解析 + +请求参数要求传入 `deviceIds`(设备ID列表),用户可能会用不同方式描述设备: + +| 自然语言表达 | 转换方式 | +|:-------------|:---------| +| "设备A"、"设备B" | 如果用户提及设备名/编号,先根据上下文尝试确定是否为已知设备ID | +| "FFFF0001"、"FT0001" | 直接作为设备ID使用 | +| "所有设备"、"全部设备" | **不支持**,需要用户提供具体的设备ID列表 | +| "传感器A"、"温度计1" | 如果已知设备ID和名称的映射关系,转换为对应设备ID | + +如果用户只说了设备名称但没有提供具体ID,需要向用户确认具体的设备ID。 + +### 3. 请求头解析 + +- `Apikey` 由用户提供,**不设置默认值** +- 如果用户没有提供 Apikey,需要向用户询问具体的 Apikey 值 + +## 自然语言处理流程 + +当收到自然语言请求时,按以下步骤执行: + +``` +1. 提取时间描述 → 计算 startTime/endTime(毫秒时间戳) +2. 提取设备描述 → 构建设备ID列表 deviceIds +3. 检查参数完整性:如有缺漏,向用户询问补齐 +4. 检查 Apikey:如用户未提供,向用户询问 +5. 检查 Referer:如用户未提供,向用户询问 +6. 构建请求体 JSON 并设置请求头(Apikey、Referer) +7. 调用 POST /common/queryDeviceData +8. 返回结果给用户 +``` + +### 参数不完整时的处理 + +**所有参数不设默认值**,缺失时需向用户询问。 + +| 缺失参数 | 询问示例 | +|:---------|:---------| +| 未提供 `deviceIds` | "请问要查询哪些设备的ID?请提供设备ID列表。" | +| 未提供时间范围 | "请问要查询什么时间范围的数据?例如:最近3天、昨天、或指定日期区间。" | +| 仅提供开始/结束时间之一 | 补齐另一个时间点,例如:用户只说"从2024年1月1日开始",则询问"请问结束时间是什么时候?" | +| 未提供 `Apikey` | "请提供用于认证的 Apikey。" | +| 未提供 `Referer` | "请提供请求来源域名(Referer),例如服务的域名地址。" | + +### 处理后响应模板 + +调用接口后将结果以自然语言呈现给用户: +- 查询到数据时:告知用户共查询到多少条数据,以及设备的主要信息摘要 +- 未查询到数据时:告知用户在指定时间范围内没有找到对应设备的数据 +- 接口报错时:将错误信息展示给用户 + +## 请求参数 + +### Header + +| 参数名 | 必填 | 说明 | +|--------|:----:|------| +| `Apikey` | 是 | API key,用于认证,由用户提供 | +| `Referer` | 是 | 请求来源域名,需与服务端配置的 `accessControlAllowOrigin` 匹配(例如 `https://bilplartform-stg.ifmservice.jp`),否则返回 403 | + +### Request Body (JSON) + +| 字段 | 类型 | 必填 | 说明 | +|------|:----:|:----:|------| +| `deviceIds` | `List` | 是 | 设备ID列表,由用户提供 | +| `startTime` | `Long` | 是 | 开始时间戳(毫秒,也支持秒级自动转换),由用户提供或根据用户描述计算 | +| `endTime` | `Long` | 是 | 结束时间戳(毫秒,也支持秒级自动转换),与 startTime 跨度不能超过7天,由用户提供或根据用户描述计算 | + +## 返回数据 + +返回 `SimpleDataResponse>`,其中 `data` 为设备数据列表: + +| 字段 | 类型 | 说明 | +|------|:----:|------| +| `deviceId` | String | 设备ID | +| `deviceName` | String | 设备名称 | +| `rawData` | String | 原始数据 JSON | +| `dataValue` | String | 从 rawData 解析出的数值,逗号拼接 | +| `receiveTs` | Long | 接收时间戳(毫秒) | +| `status` | String | 状态 | +| `alertLevel` | String | 告警级别 | +| `alertContent` | String | 告警内容 | +| `buildingInfo` | String | 楼宇信息 | +| `floorInfo` | String | 楼层信息 | +| `spaceInfo` | String | 空间信息 | + +## 调用示例 + +### 请求 + +``` +POST /common/queryDeviceData +Apikey: <用户提供的Apikey> +Referer: <服务端域名,例如 https://bilplartform-stg.ifmservice.jp> +``` + +```json +{ + "deviceIds": ["FFFF0001", "FFFF0002"], + "startTime": 1709870240425, + "endTime": 1709889240425 +} +``` + +### 成功响应 + +```json +{ + "code": 0, + "msg": "success", + "data": [ + { + "deviceId": "FFFF0001", + "deviceName": "温度传感器", + "rawData": "{\"temp\":25.5}", + "dataValue": "25.5", + "receiveTs": 1709870240425, + "status": "UP", + "alertLevel": null, + "alertContent": null, + "buildingInfo": "东京建物日本桥ビル", + "floorInfo": "3F", + "spaceInfo": "会議室A" + } + ] +} +``` + +### 错误响应示例 + +```json +{ + "code": 20001, + "msg": "[deviceIds] is required", + "data": null +} +``` + +## 自然语言用例映射 + +### 用例1:查询某设备最近N天的数据 + +> 用户说:**"查一下设备FFFF0001最近3天的数据"** + +``` +步骤: +1. 假设 Today's date = 2026-06-01 +2. 计算 startTime = 2026-05-30 00:00:00 JST → 毫秒时间戳 +3. 计算 endTime = 2026-06-01 23:59:59 JST → 毫秒时间戳 +4. deviceIds = ["FFFF0001"] +5. 调用接口 +``` + +### 用例2:查询多个设备特定日期的数据 + +> 用户说:**"查询FFFF0001和FT0002在2024年1月1日的数据"** + +``` +步骤: +1. 计算 startTime = 2024-01-01 00:00:00 JST → 毫秒时间戳 +2. 计算 endTime = 2024-01-01 23:59:59 JST → 毫秒时间戳 +3. deviceIds = ["FFFF0001", "FT0002"] +4. 调用接口 +``` + +### 用例3:查询设备今天的数据 + +> 用户说:**"看看FT0001今天的监测数据"** + +``` +步骤: +1. 假设 Today's date = 2026-06-01 +2. 计算 startTime = 2026-06-01 00:00:00 JST → 毫秒时间戳 +3. 计算 endTime = 当前时间 JST → 毫秒时间戳 +4. deviceIds = ["FT0001"] +5. 调用接口 +``` + +## 注意事项 + +1. `startTime` 和 `endTime` 支持毫秒和秒级时间戳(< 10000000000L 时自动按秒处理) +2. 时间跨度不能超过 **7 天**,否则接口会返回错误 +3. 跨天查询时自动查询多张 `rawData_yyyy_MM_dd` 分表并合并结果 +4. 使用 Apikey 认证,与 `/common/queryDeviceInfo` 相同的方式 +5. 所有时间计算都基于 `Asia/Tokyo` 时区 +6. Apikey 和所有请求参数均**不设置默认值**,缺失时需向用户询问补齐 +7. **Referer 头必传**:请求必须携带 `Referer` 头,否则被 `CrosXssFilter` 过滤器拦截返回 403。Referer 值需与服务端配置的 `accessControlAllowOrigin` 匹配(通常为服务域名本身,如 `https://bilplartform-stg.ifmservice.jp`) \ No newline at end of file