feat: add service config templates and extraction script

Former-commit-id: 1de24b7eb79676d1aba9d799a58c5a753290cf52
This commit is contained in:
反编译工作区
2026-05-01 19:38:01 +08:00
parent 3175b7074b
commit 8b15445328
2433 changed files with 8322164 additions and 1604 deletions
@@ -43,6 +43,102 @@
<dependency>
<groupId>cn.cloudwalk</groupId>
<artifactId>cwos-java-sdk-resource</artifactId>
<exclusions>
<exclusion>
<groupId>cn.cloudwalk</groupId>
<artifactId>cwos-portal-interface</artifactId>
</exclusion>
<!-- 部分私服 POM 仍传递 stub,双保险 -->
<exclusion>
<groupId>cn.cloudwalk</groupId>
<artifactId>cwos-device-pkg-stub</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 自行声明 portal,排除 V2 引入的 stub / validator 6.x / validation starter,贴近 V1 lib -->
<dependency>
<groupId>cn.cloudwalk</groupId>
<artifactId>cwos-portal-interface</artifactId>
<exclusions>
<exclusion>
<groupId>cn.cloudwalk</groupId>
<artifactId>cwos-device-pkg-stub</artifactId>
</exclusion>
<exclusion>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- V1 fat-jar 中出现的扩展链(历史 starter 传递) -->
<dependency>
<groupId>cn.cloudwalk.cloud</groupId>
<artifactId>cloudwalk-device-manager-common</artifactId>
</dependency>
<dependency>
<groupId>cn.cloudwalk.cloud</groupId>
<artifactId>cloudwalk-device-manager-interface</artifactId>
</dependency>
<dependency>
<groupId>cn.cloudwalk.cloud</groupId>
<artifactId>cwos-common-aks-interface</artifactId>
</dependency>
<dependency>
<groupId>cn.cloudwalk.cloud</groupId>
<artifactId>cwos-device-authentication-interface</artifactId>
</dependency>
<dependency>
<groupId>cn.cloudwalk</groupId>
<artifactId>cloudwalk-device-sdk-protocol-entity</artifactId>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
</dependency>
<!-- V1 fat-jar 中与本业务相邻出现的扩展库(补齐后与 baseline multiset 更接近) -->
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-afterburner</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okio</groupId>
<artifactId>okio</artifactId>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
<dependency>
<groupId>net.sf.opencsv</groupId>
<artifactId>opencsv</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
@@ -81,6 +177,27 @@
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- V1 fat-jar 中独立出现的 Netflix / Consul starters(与 OpenFeign 并存) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
@@ -1,15 +1,15 @@
package cn.cloudwalk.elevator;
import cn.cloudwalk.cloud.context.CloudwalkSessionContextHolder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/** 未扫描 {@code cn.cloudwalk.web} 时,等价于 LocaleConfiguration 中的 SessionHolder Bean。 */
@Configuration
public class CloudwalkSessionHolderConfiguration {
@Bean
public CloudwalkSessionContextHolder cloudwalkSessionContextHolder() {
return new CloudwalkSessionContextHolder();
}
}
package cn.cloudwalk.elevator;
import cn.cloudwalk.cloud.context.CloudwalkSessionContextHolder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/** 未扫描 {@code cn.cloudwalk.web} 时,等价于 LocaleConfiguration 中的 SessionHolder Bean。 */
@Configuration
public class CloudwalkSessionHolderConfiguration {
@Bean
public CloudwalkSessionContextHolder cloudwalkSessionContextHolder() {
return new CloudwalkSessionContextHolder();
}
}
@@ -38,8 +38,7 @@ import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
/**
* 设备异步任务:推进绑定进度、按楼层增删人员/规则;{@code updateFloors} 在楼层维度使用有界线程池并行远程调用,并按 Future 完成顺序推进
* {@code bindDevices},与走查约定 §9 一致。
* 设备异步任务:推进绑定进度、按楼层增删人员/规则;{@code updateFloors} 在楼层维度使用有界线程池并行远程调用,并按 Future 完成顺序推进 {@code bindDevices},与走查约定 §9 一致。
*/
@Service
public class AcsDeviceTaskServiceImpl extends AbstractAcsDeviceService implements AcsDeviceTaskService {
@@ -81,18 +80,17 @@ public class AcsDeviceTaskServiceImpl extends AbstractAcsDeviceService implement
}
/**
* 约定 §3.5:楼层级有界并行发起远程调用;本方法内按原列表顺序 {@code get()} Future
* 与串行时一致地「每成功一层 → 重读任务行并 BIND_DEVICES+1」。
* 约定 §3.5:楼层级有界并行发起远程调用;本方法内按原列表顺序 {@code get()} Future 与串行时一致地「每成功一层 → 重读任务行并 BIND_DEVICES+1」。
*/
private void runAddFloorsInBoundedParallel(AcsRestructureBindingParam param, List<AcsPassRuleImageResultDto> addFloors,
CloudwalkCallContext context) throws ServiceException {
private void runAddFloorsInBoundedParallel(AcsRestructureBindingParam param,
List<AcsPassRuleImageResultDto> addFloors, CloudwalkCallContext context) throws ServiceException {
for (int i = 0; i < addFloors.size(); i += UPDATE_FLOORS_FLOOR_PARALLEL) {
int end = Math.min(i + UPDATE_FLOORS_FLOOR_PARALLEL, addFloors.size());
List<Callable<Integer>> batch = new ArrayList<>();
for (int j = i; j < end; j++) {
final AcsPassRuleImageResultDto addFloor = addFloors.get(j);
batch.add(
() -> FeignThreadLocalUtil.callWithContext(context, () -> addOneFloorStep(addFloor, param, context)));
batch.add(() -> FeignThreadLocalUtil.callWithContext(context,
() -> addOneFloorStep(addFloor, param, context)));
}
List<Future<Integer>> futures;
try {
@@ -129,8 +127,8 @@ public class AcsDeviceTaskServiceImpl extends AbstractAcsDeviceService implement
List<Callable<Integer>> batch = new ArrayList<>();
for (int j = i; j < end; j++) {
final String delFloorId = delFloorIds.get(j);
batch.add(
() -> FeignThreadLocalUtil.callWithContext(context, () -> delOneFloorStep(delFloorId, param, ruleMap, context)));
batch.add(() -> FeignThreadLocalUtil.callWithContext(context,
() -> delOneFloorStep(delFloorId, param, ruleMap, context)));
}
List<Future<Integer>> futures;
try {
@@ -260,7 +258,8 @@ public class AcsDeviceTaskServiceImpl extends AbstractAcsDeviceService implement
try {
this.imageRuleRefDao.deleteByOrgAndLabel(dto);
} catch (DataAccessException e) {
this.logger.error("updateFloors deleteByOrgAndLabel 失败 delFloorId={} {}", delFloorId, e.getMessage());
this.logger.error("updateFloors deleteByOrgAndLabel 失败 delFloorId={} {}", delFloorId,
e.getMessage());
throw new ServiceException("76260540", e.getMessage());
}
}
@@ -411,8 +411,7 @@ public class AcsElevatorDeviceServiceImpl extends AbstractAcsPassService impleme
ArrayList<String> deviceIds = new ArrayList<>();
HashMap<String, DeviceResult> mapDevice = new HashMap<>();
if (!CollectionUtils.isEmpty(deviceList)) {
deviceList.forEach(
device -> deviceIds.add(((AcsElevatorDeviceResultDTO)device).getDeviceId()));
deviceList.forEach(device -> deviceIds.add(((AcsElevatorDeviceResultDTO)device).getDeviceId()));
DeviceQueryParam queryParam = new DeviceQueryParam();
queryParam.setBusinessId(context.getCompany().getCompanyId());
queryParam.setIds(deviceIds);
@@ -441,8 +440,8 @@ public class AcsElevatorDeviceServiceImpl extends AbstractAcsPassService impleme
DeviceResult deviceResult;
if (!floor2.getZoneId()
.equals(((AcsElevatorDeviceResultDTO)deviceList.get(i)).getCurrentFloorId())
|| ObjectUtils.isEmpty((deviceResult = mapDevice
.get(((AcsElevatorDeviceResultDTO)deviceList.get(i)).getDeviceId())))) {
|| ObjectUtils.isEmpty((deviceResult =
mapDevice.get(((AcsElevatorDeviceResultDTO)deviceList.get(i)).getDeviceId())))) {
continue;
}
if ("2".equals(deviceResult.getOnlineStatus())) {
@@ -518,8 +517,7 @@ public class AcsElevatorDeviceServiceImpl extends AbstractAcsPassService impleme
List deviceList = this.acsElevatorDeviceDao.listByZoneIds(listDto);
if (!CollectionUtils.isEmpty(deviceList)) {
ArrayList<String> deviceIds = new ArrayList<>();
deviceList.forEach(
device -> deviceIds.add(((AcsElevatorDeviceResultDTO)device).getDeviceId()));
deviceList.forEach(device -> deviceIds.add(((AcsElevatorDeviceResultDTO)device).getDeviceId()));
HashMap<String, DeviceResult> mapDevice = new HashMap<>();
DeviceQueryParam queryParam = new DeviceQueryParam();
queryParam.setBusinessId(context.getCompany().getCompanyId());
@@ -549,8 +547,7 @@ public class AcsElevatorDeviceServiceImpl extends AbstractAcsPassService impleme
}
DeviceResult deviceResult =
mapDevice.get(((AcsElevatorDeviceResultDTO)deviceList.get(i)).getDeviceId());
result.setParentId(
((AcsElevatorDeviceResultDTO)deviceList.get(i)).getCurrentBuildingId());
result.setParentId(((AcsElevatorDeviceResultDTO)deviceList.get(i)).getCurrentBuildingId());
if (ObjectUtils.isEmpty(deviceResult)) {
continue;
}
@@ -1,6 +1,6 @@
/**
* 设备域服务层:电梯设备查询/编辑、设备任务(含楼层变更)、设备侧设置与图库应用绑定等编排。
* <p>
* 同包名在 data 模块中承担 DAO/Mapper;此处仅放接口、入参出参与 {@code impl} 实现,表结构见 data 包说明。
*/
package cn.cloudwalk.elevator.device;
/**
* 设备域服务层:电梯设备查询/编辑、设备任务(含楼层变更)、设备侧设置与图库应用绑定等编排。
* <p>
* 同包名在 data 模块中承担 DAO/Mapper;此处仅放接口、入参出参与 {@code impl} 实现,表结构见 data 包说明。
*/
package cn.cloudwalk.elevator.device;
@@ -13,6 +13,7 @@ import org.springframework.stereotype.Component;
public class MqttFeignClientFallback implements MqttFeignClient {
/**
* {@inheritDoc}
*
* @implSpec 不返回降级业务结果,直接抛错以暴露下游不可用
*/
@Override
@@ -1,6 +1,6 @@
/**
* 电梯识别记录 MQTT 推送:经 {@code cloudwalk-device-thirdparty} 等下游将消息发布到业务 topic,供大屏/第三方订阅。
* <p>
* 与 {@code record} 域协作:在人员识别记录落库后异步组装载荷并调用发布接口。
*/
package cn.cloudwalk.elevator.mqtt;
/**
* 电梯识别记录 MQTT 推送:经 {@code cloudwalk-device-thirdparty} 等下游将消息发布到业务 topic,供大屏/第三方订阅。
* <p>
* 与 {@code record} 域协作:在人员识别记录落库后异步组装载荷并调用发布接口。
*/
package cn.cloudwalk.elevator.mqtt;
@@ -1,7 +1,8 @@
package cn.cloudwalk.elevator.mqtt.param;
/**
* 推送到 MQTT 的电梯记录载荷:在 {@link cn.cloudwalk.elevator.record.dto.AcsElevatorRecordAddDTO} 基础上补全人名、开门流水 id、是否访客等,序列化为 {@code data} 字段内容。
* 推送到 MQTT 的电梯记录载荷:在 {@link cn.cloudwalk.elevator.record.dto.AcsElevatorRecordAddDTO} 基础上补全人名、开门流水 id、是否访客等,序列化为
* {@code data} 字段内容。
*/
public class AcsElevatorRecordMqttParam {
private String openDoorId;
@@ -1,7 +1,7 @@
/**
* 电梯应用业务编排层({@code cw-elevator-application-service}):领域服务接口与实现、远程调用、异步与任务推进。
* <p>
* 与 {@code cw-elevator-application-data} 的持久化、{@code cw-elevator-application-web} 的 HTTP 入口分工协作;
* 本模块内可依赖对外 Feign 契约(如 {@code intelligent-cwoscomponent-interface}),但不应向上依赖 Web 层。
*/
package cn.cloudwalk.elevator;
/**
* 电梯应用业务编排层({@code cw-elevator-application-service}):领域服务接口与实现、远程调用、异步与任务推进。
* <p>
* 与 {@code cw-elevator-application-data} 的持久化、{@code cw-elevator-application-web} 的 HTTP 入口分工协作; 本模块内可依赖对外 Feign 契约(如
* {@code intelligent-cwoscomponent-interface}),但不应向上依赖 Web 层。
*/
package cn.cloudwalk.elevator;
@@ -1,6 +1,6 @@
/**
* 通行/人员规则与图库规则引用:规则增删、与区域/标签/组织维度的组合,及与设备任务的协作。
* <p>
* 与 {@code person} 包在“按人下发”和“按规则下发”两种路径上常共同出现在设备任务流中。
*/
package cn.cloudwalk.elevator.passrule;
/**
* 通行/人员规则与图库规则引用:规则增删、与区域/标签/组织维度的组合,及与设备任务的协作。
* <p>
* 与 {@code person} 包在“按人下发”和“按规则下发”两种路径上常共同出现在设备任务流中。
*/
package cn.cloudwalk.elevator.passrule;
@@ -195,24 +195,24 @@ public class PersonRuleServiceImpl extends AbstractAcsPassService implements Per
if (CollectionUtils.isEmpty(hostFloors)) {
return CloudwalkResult.fail("76260531", getMessage("76260531"));
}
List<String> effectiveFloors = hostFloors;
TenantVisitorFloorPolicyDto policy =
this.tenantVisitorFloorPolicyDao.selectEnabledTenantDefault(context.getCompany().getCompanyId());
if (policy != null && policy.getEnabled() != null && policy.getEnabled().intValue() == 1) {
List<String> allow = parseAllowZoneIds(policy.getAllowZoneIds());
if (!CollectionUtils.isEmpty(allow)) {
Set<String> allowSet = new HashSet<>(allow);
List<String> intersected = intersectPreserveHostOrder(hostFloors, allowSet);
if (intersected.isEmpty()) {
return CloudwalkResult.fail("76260532", getMessage("76260532"));
}
effectiveFloors = intersected;
this.logger.info(
"租户访客楼层策略求交 businessId={} personId={} visitorId={} policyId={} policyVersion={} effectiveSize={}",
context.getCompany().getCompanyId(), param.getPersonId(), param.getVisitorId(),
policy.getId(), policy.getPolicyVersion(), Integer.valueOf(intersected.size()));
List<String> effectiveFloors;
TenantVisitorFloorPolicyDto policy = findPolicyByOrgIds(personResult.getOrganizationIds());
if (policy != null) {
effectiveFloors = resolveEffectiveFloors(callerProvidedFloors ? param.getFloorIds() : hostFloors,
hostFloors, policy, param.getPersonId());
} else {
effectiveFloors = hostFloors;
Set<String> hostSet = new HashSet<>(hostFloors);
List<String> outliers =
param.getFloorIds().stream().filter(f -> !hostSet.contains(f)).collect(Collectors.toList());
if (!outliers.isEmpty()) {
this.logger.warn("UC-02 传入非被访人授权楼层 businessId={} personId={} outliers={}",
context.getCompany().getCompanyId(), param.getPersonId(), outliers);
}
}
if (CollectionUtils.isEmpty(effectiveFloors)) {
return CloudwalkResult.fail("76260531", getMessage("76260531"));
}
param.setFloorIds(effectiveFloors);
}
if (CollectionUtils.isEmpty(param.getFloorIds())) {
@@ -274,6 +274,47 @@ public class PersonRuleServiceImpl extends AbstractAcsPassService implements Per
return CloudwalkResult.success(Boolean.valueOf(true));
}
private TenantVisitorFloorPolicyDto findPolicyByOrgIds(List<String> orgIds) {
if (CollectionUtils.isEmpty(orgIds)) {
return null;
}
for (String orgId : orgIds) {
TenantVisitorFloorPolicyDto p = this.tenantVisitorFloorPolicyDao.selectEnabledByOrgId(orgId);
if (p != null && p.getEnabled() != null && p.getEnabled().intValue() == 1) {
List<String> allow = parseAllowZoneIds(p.getAllowZoneIds());
if (!CollectionUtils.isEmpty(allow)) {
return p;
}
}
}
return null;
}
private List<String> resolveEffectiveFloors(List<String> fallbackFloorsUnused, List<String> hostFloors,
TenantVisitorFloorPolicyDto policy, String personId) throws ServiceException {
String rawJson = policy.getAllowZoneIds();
List<String> allow = parseAllowZoneIds(rawJson);
if (CollectionUtils.isEmpty(allow)) {
if (!StringUtils.isBlank(rawJson)) {
this.logger.error("allow_zone_ids JSON 无效或为空,策略失效 orgId={} policyId={} raw={}", policy.getOrgId(),
policy.getId(), rawJson);
}
return fallbackFloorsUnused;
}
Set<String> hostSet = new HashSet<>(hostFloors);
List<String> unknownAllow = allow.stream().filter(a -> !hostSet.contains(a)).collect(Collectors.toList());
if (!unknownAllow.isEmpty()) {
this.logger.error(
"策略配置错误:allow 包含不在被访人 floorList 中的 zoneId"
+ "orgId={} policyId={} personId={} unknownAllow={} hostFloors={}",
policy.getOrgId(), policy.getId(), personId, unknownAllow, hostFloors);
throw new ServiceException("76260533", "策略配置了被访人无权访问的楼层,请联系管理员");
}
this.logger.info("策略生效 orgId={} policyId={} v={} allowSize={} hostSize={}", policy.getOrgId(), policy.getId(),
policy.getPolicyVersion(), Integer.valueOf(allow.size()), Integer.valueOf(hostFloors.size()));
return allow;
}
/**
* 解析 allow_zone_ids JSON;无效或空则返回空列表(等同未配置有效策略)。
*/
@@ -288,14 +329,12 @@ public class PersonRuleServiceImpl extends AbstractAcsPassService implements Per
}
return list.stream().filter(Objects::nonNull).filter(s -> !s.isEmpty()).collect(Collectors.toList());
} catch (Exception e) {
this.logger.warn("allow_zone_ids JSON 无效,按无策略处理: {}", e.getMessage());
this.logger.error("allow_zone_ids JSON 无效,按无策略处理: {}", e.getMessage());
return Collections.emptyList();
}
}
private static List<String> intersectPreserveHostOrder(List<String> hostFloors, Set<String> allowSet) {
return hostFloors.stream().filter(allowSet::contains).collect(Collectors.toList());
}
// intersectPreserveHostOrder removed in favor of org-policy-based resolution
public CloudwalkResult<Boolean> edit(AcsPersonEditParam param, CloudwalkCallContext context)
throws ServiceException {
@@ -1,4 +1,4 @@
/**
* 人员与人员-规则服务:人员增删、与区域/父级人员关系、及与设备侧同步相关的编排。
*/
package cn.cloudwalk.elevator.person;
/**
* 人员与人员-规则服务:人员增删、与区域/父级人员关系、及与设备侧同步相关的编排。
*/
package cn.cloudwalk.elevator.person;
@@ -91,8 +91,8 @@ import org.springframework.web.util.UriComponentsBuilder;
* <p>
* 新增记录时通过 {@link cn.cloudwalk.elevator.util.RestTemplateUtil} 请求 {@code ninca-crk-std} 的
* {@code intelligent/three/visitor/record/query},用识别 id 反查是否访客及被访人;该路径与
* {@link cn.cloudwalk.elevator.visitor.client.VisitorFeignClient} 所映射的
* {@code /intelligent/visitor/record/query} 为两套接口,本处固定走「three」版 HTTP。
* {@link cn.cloudwalk.elevator.visitor.client.VisitorFeignClient} 所映射的 {@code /intelligent/visitor/record/query}
* 为两套接口,本处固定走「three」版 HTTP。
* <p>
* {@link MqttService} 为识别结果 MQTT 推送能力,本类已注入但<strong>未直接调用</strong>;若需与入库联动,可在监听
* {@link cn.cloudwalk.elevator.record.result.VisitorRecordPushEvent} 的处理器中显式
@@ -1,4 +1,4 @@
/**
* 电梯通行/识别记录业务:查询、落库、统计,并与访客中心(HTTP「three」线或 Feign「intelligent」线)、域事件、可选 MQTT 推送协同。
*/
package cn.cloudwalk.elevator.record;
/**
* 电梯通行/识别记录业务:查询、落库、统计,并与访客中心(HTTP「three」线或 Feign「intelligent」线)、域事件、可选 MQTT 推送协同。
*/
package cn.cloudwalk.elevator.record;
@@ -1,6 +1,6 @@
/**
* 访客主数据与识别记录查询:通过 Feign 调用 {@code ninca-crk-std} 等标准访客服务,为电梯业务侧提供访客档案与到访记录。
* <p>
* 本包内为入参/出参模型与客户端;业务组装与调用方在 record 等域中完成。
*/
package cn.cloudwalk.elevator.visitor;
/**
* 访客主数据与识别记录查询:通过 Feign 调用 {@code ninca-crk-std} 等标准访客服务,为电梯业务侧提供访客档案与到访记录。
* <p>
* 本包内为入参/出参模型与客户端;业务组装与调用方在 record 等域中完成。
*/
package cn.cloudwalk.elevator.visitor;
@@ -1,6 +1,6 @@
/**
* 区域(园区/楼栋/楼层等)树与下一级树查询,供设备、通行与前端级联选择使用。
* <p>
* 多通过 Feign 拉取平台区域数据并在本包内做组装与 {@code result} 封装。
*/
package cn.cloudwalk.elevator.zone;
/**
* 区域(园区/楼栋/楼层等)树与下一级树查询,供设备、通行与前端级联选择使用。
* <p>
* 多通过 Feign 拉取平台区域数据并在本包内做组装与 {@code result} 封装。
*/
package cn.cloudwalk.elevator.zone;