跳到主要内容

7. Asset 服务

通过 client.asset() 访问。管理 PlantPulse 资产模型的核心 — 企业 > 站点 > 区域 > 线路 > 设备 五层树形结构。

ID 规则:区域 AREA_ · 线路 LINE_ · 设备 EQUIPMENT_(已有的 ASSET_ 同样有效)。若 asset_id 为空,则由 Platform 发行。决定树层级的不是 ID,而是 asset_type。详情请参阅 领域 ID 规则。

7.1 方法一览​

方法返回类型HTTP端点
create(AssetRequestV5)AssetResponseV5 或 nullPOST/api/v5/asset
update(asset_id, AssetRequestV5)AssetResponseV5 或 nullPUT/api/v5/asset/{id}
delete(asset_id)booleanDELETE/api/v5/asset/{id}
get(asset_id)AssetResponseV5 或 nullGET/api/v5/asset/{id}
list()List<AssetResponseV5>GET/api/v5/asset
exists(asset_id)booleanGET/api/v5/asset/{id}/exists
getByExternal(system, external_id)AssetResponseV5 或 nullGET/api/v5/asset/by-external/{system}/{external_id}
patchProperties(asset_id, 요청)AssetResponseV5 或 nullPATCH/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_idString资产 ID (PK)。为空时自动发行
asset_nameString显示名称
parent_asset_idString父资产 ID — 直接上级层级
asset_orderString同级间的排序顺序
asset_typeStringA/L/M (响应中包含 E·S)。创建后不可更改
asset_class_codeString设备分类代码 (CNC, INJECTION 等)。必须是该层级的分类,否则为 422
site_idString所属站点
descriptionString说明 (必填)
operational_status_codeString(响应) 运行状态
external_system / external_idString(响应) 其他系统调用此节点的编号
insert_date / update_dateString(响应专用) 时间

单条查询包含该层级的特有属性,列表仅包含公共字段(operational_status_code · external_system · external_id)。

特有属性 — /api/v5/asset/{id}/properties​

每个层级拥有其特有的属性。通过 GET 读取,通过 PATCH(或 PUT)修改。仅使用发送的键,null 表示删除,未发送的键保持不变。

层级键
企业除外公共operational_status_code, external_system + external_id (两者同时存在或均不存在)
站点 Stimezone, country, site_code, address, lat, lng
区域 Aarea_function_code(PRODUCTION · STORAGE · UTILITY), responsible_employee_id
线路 Lline_kind_code(PRODUCTION_LINE · PROCESS_CELL · PRODUCTION_UNIT), design_rate(每小时,需 > 0) + design_rate_unit_of_measure(UN/ECE Rec 20, 两者同时存在或均不存在), responsible_employee_id
设备 Mmanufacturer, 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 서비스로 조회
// ...
}
}

下一步​