7. Asset 服务
通过 client.asset() 访问。管理 PlantPulse 资产模型的核心 — 企业 > 站点 > 区域 > 线路 > 设备 五层树形结构。
ID 规则:区域
AREA_· 线路LINE_· 设备EQUIPMENT_(已有的ASSET_同样有效)。若asset_id为空,则由 Platform 发行。决定树层级的不是 ID,而是asset_type。详情请参阅 领域 ID 规则。
7.1 方法一览
| 方法 | 返回类型 | HTTP | 端点 |
|---|---|---|---|
create(AssetRequestV5) | AssetResponseV5 或 null | POST | /api/v5/asset |
update(asset_id, AssetRequestV5) | AssetResponseV5 或 null | PUT | /api/v5/asset/{id} |
delete(asset_id) | boolean | DELETE | /api/v5/asset/{id} |
get(asset_id) | AssetResponseV5 或 null | GET | /api/v5/asset/{id} |
list() | List<AssetResponseV5> | GET | /api/v5/asset |
exists(asset_id) | boolean | GET | /api/v5/asset/{id}/exists |
getByExternal(system, external_id) | AssetResponseV5 或 null | GET | /api/v5/asset/by-external/{system}/{external_id} |
patchProperties(asset_id, 요청) | AssetResponseV5 或 null | PATCH | /api/v5/asset/{id}/properties |
Asset 服务专注于简化的 CRUD。树级层级查询请使用 Path 服务 或客户端过滤(
list()后进行asset_type过滤)。
7.2 Asset Type 值
| asset_type | 含义 |
|---|---|
E | 企业 (Enterprise) — 每个安装一个,作为树根 |
S | 站点 (Site) |
A | 区域 (Area) |
L | 线路 (Line) |
M | 设备 (Equipment, Machine) |
通过 POST /api/v5/asset 创建的是 A · L · M。站点通过 Site 服务 创建。
GET /api/v5/asset 在不提供 asset_type 时返回区域·线路·设备,在提供 asset_type=E 或 S 时返回相应层级。
请勿将 E · S 视为设备。 层级是通过该值判断的,而非 ID 前缀。
7.3 AssetRequestV5 / AssetResponseV5
| 字段 | 类型 | 说明 |
|---|---|---|
asset_id | String | 资产 ID (PK)。为空时自动发行 |
asset_name | String | 显示名称 |
parent_asset_id | String | 父资产 ID — 直接上级层级 |
asset_order | String | 同级间的排序顺序 |
asset_type | String | A/L/M (响应中包含 E·S)。创建后不可更改 |
asset_class_code | String | 设备分类代码 (CNC, INJECTION 等)。必须是该层级的分类,否则为 422 |
site_id | String | 所属站点 |
description | String | 说明 (必填) |
operational_status_code | String | (响应) 运行状态 |
external_system / external_id | String | (响应) 其他系统调用此节点的编号 |
insert_date / update_date | String | (响应专用) 时间 |
单条查询包含该层级的特有属性,列表仅包含公共字段(operational_status_code · external_system · external_id)。
特有属性 — /api/v5/asset/{id}/properties
每个层级拥有其特有的属性。通过 GET 读取,通过 PATCH(或 PUT)修改。仅使用发送的键,null 表示删除,未发送的键保持不变。
| 层级 | 键 |
|---|---|
| 企业除外公共 | operational_status_code, external_system + external_id (两者同时存在或均不存在) |
站点 S | timezone, country, site_code, address, lat, lng |
区域 A | area_function_code(PRODUCTION · STORAGE · UTILITY), responsible_employee_id |
线路 L | line_kind_code(PRODUCTION_LINE · PROCESS_CELL · PRODUCTION_UNIT), design_rate(每小时,需 > 0) + design_rate_unit_of_measure(UN/ECE Rec 20, 两者同时存在或均不存在), responsible_employee_id |
设备 M | manufacturer, model, serial_number, installed_date, commissioned_date(安装日之后), warranty_end_date(yyyy-MM-dd), criticality_code(A · B · C), functional_location(站点内唯一) |
| 响应 | 含义 |
|---|---|
422 asset.invalid.<key> | 该层级不存在的键、未知代码、不完整配对、日期顺序违规 |
422 asset.invalid.asset_type | 尝试更改层级 — 层级不可更改 |
409 asset.duplicate.external_id | 其他节点使用了相同的外部编号 |
409 asset.duplicate.functional_location | 同站点的其他设备使用了相同的功能位置 |
响应中的 effective_responsible_employee_id 为实际负责人 — 若自身字段为空,则继承最近上级(线路 → 区域)的负责人,effective_responsible_from_asset_id 说明继承来源。
GET /api/v5/asset/{id} 响应中的 qr_payload 是打印在 QR 标签 上的地址。此调用不创建令牌 — 仅在控制台打开过标签且生成了令牌的设备中填充,若尚不存在或控制台地址为空则为 ""。
通过外部编号查找
GET /api/v5/asset/by-external/MAXIMO/BEDFORD%2F11430
GET /api/v5/asset/by-external?system=MAXIMO&external_id=BEDFORD/11430
在所有层级中查找。若不存在则返回 404 asset.not_found,若仅提供系统或编号其中之一则返回 422。
7.4 使用示例
创建 Area → Line → Equipment 树
import plantpulse.api.v5.dto.request.AssetRequestV5;
// 1) Area
AssetRequestV5 area = new AssetRequestV5();
area.setSite_id("SITE_DJ");
area.setParent_asset_id("SITE_DJ");
area.setAsset_name("조립구역");
area.setAsset_type("A");
client.asset().create(area);
// asset_id 를 비워 두면 플랫폼이 발급합니다 — 응답에서 AREA_000001 을 받습니다
// 2) Line
AssetRequestV5 line = new AssetRequestV5();
line.setSite_id("SITE_DJ");
line.setParent_asset_id("AREA_000001");
line.setAsset_name("1번 조립 라인");
line.setAsset_type("L");
client.asset().create(line);
// 3) Equipment (CNC 머신)
AssetRequestV5 cnc = new AssetRequestV5();
cnc.setSite_id("SITE_DJ");
cnc.setParent_asset_id("LINE_000001");
cnc.setAsset_name("CNC #1");
cnc.setAsset_type("M");
cnc.setAsset_class_code("CNC");
cnc.setDescription("DMG MORI NLX-2500");
client.asset().create(cnc);
单条查询
import plantpulse.api.v5.dto.response.AssetResponseV5;
AssetResponseV5 cnc = client.asset().get("EQUIPMENT_000001");
if (cnc != null) {
System.out.println(cnc.getAsset_name() + " (" + cnc.getAsset_type() + ")");
System.out.println("부모: " + cnc.getParent_asset_id());
}
完整列表 + 按类型过滤
服务器端过滤为 GET /api/v5/asset?asset_type=M · operational_status_code= · external_system=。Java 客户端的 list() 不接收过滤器,因此在接收后进行过滤。
import java.util.List;
import java.util.stream.Collectors;
List<AssetResponseV5> all = client.asset().list();
// Equipment만
List<AssetResponseV5> equipments = all.stream()
.filter(a -> "M".equals(a.getAsset_type()))
.collect(Collectors.toList());
// 대전공장의 Line만
List<AssetResponseV5> djLines = all.stream()
.filter(a -> "SITE_DJ".equals(a.getSite_id()))
.filter(a -> "L".equals(a.getAsset_type()))
.collect(Collectors.toList());
树形结构重组 (客户端)
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
// asset_id 인덱스
Map<String, AssetResponseV5> byId = new HashMap<>();
for (AssetResponseV5 a : client.asset().list()) {
byId.put(a.getAsset_id(), a);
}
// 부모-자식 매핑
Map<String, List<AssetResponseV5>> children = new HashMap<>();
for (AssetResponseV5 a : byId.values()) {
children
.computeIfAbsent(a.getParent_asset_id(), k -> new ArrayList<>())
.add(a);
}
// 사이트(SITE_DJ) 산하 트리 출력
printTree(children, "SITE_DJ", 0);
static void printTree(Map<String, List<AssetResponseV5>> children, String id, int depth) {
List<AssetResponseV5> kids = children.getOrDefault(id, List.of());
for (AssetResponseV5 c : kids) {
System.out.println(" ".repeat(depth) + c.getAsset_id() + " — " + c.getAsset_name());
printTree(children, c.getAsset_id(), depth + 1);
}
}
修改资产
AssetRequestV5 update = new AssetRequestV5();
update.setSite_id("SITE_DJ");
update.setParent_asset_id("LINE_000001");
update.setAsset_id("EQUIPMENT_000001");
update.setAsset_name("CNC #1 (보수 후)");
update.setAsset_type("M");
update.setAsset_class_code("CNC");
update.setDescription("2026-05-10 베어링 교체 완료");
client.asset().update("EQUIPMENT_000001", update);
删除资产
⚠️ 在删除资产前,必须先删除与其连接的所有标签(Tag)及子资产(Asset)。
boolean ok = client.asset().delete("EQUIPMENT_OLD");
7.5 应用场景
场景 — 站点设备列表及各标签数量
String siteId = "SITE_DJ";
List<AssetResponseV5> equipments = client.asset().list().stream()
.filter(a -> siteId.equals(a.getSite_id()))
.filter(a -> "M".equals(a.getAsset_type()))
.collect(Collectors.toList());
for (AssetResponseV5 eq : equipments) {
long tagCount = client.tag().countByAsset(eq.getAsset_id());
System.out.printf("%s — Tag %d개%n", eq.getAsset_name(), tagCount);
}
场景 — 线路单位 OEE 仪表板数据采集
// 1) Line 목록
List<AssetResponseV5> lines = client.asset().list().stream()
.filter(a -> "L".equals(a.getAsset_type()))
.filter(a -> "SITE_DJ".equals(a.getSite_id()))
.collect(Collectors.toList());
// 2) 각 Line 산하 Equipment 조회 → 자산 경로 → 작업지시 → KPI 집계
for (AssetResponseV5 line : lines) {
List<AssetResponseV5> machines = client.asset().list().stream()
.filter(a -> line.getAsset_id().equals(a.getParent_asset_id()))
.collect(Collectors.toList());
for (AssetResponseV5 m : machines) {
long now = System.currentTimeMillis();
// 현재 작업지시는 Order 서비스로 조회
// ...
}
}