mirror of
https://github.com/hpd840321/starRiverProperty.git
synced 2026-06-09 08:20:31 +08:00
chore: 工作区反编译与 Maven/文档/脚本同步到发布分支
- artifacts/decompiled 树与相关源码变更 - maven-cw-elevator-application 业务 docs 与 package-info - scripts 下 formatter 校验与辅助脚本 - 其他子工程/接口与发布线一并纳入版本控制 Made-with: Cursor Former-commit-id: e102e8cab64e575bcd23c9a66a598aa1892bb492
This commit is contained in:
+1
-2
@@ -79,8 +79,7 @@ public class FeignThreadLocalUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 在有界线程池等子线程中调用 Feign 前,必须为当前线程设置与 {@code context} 一致的请求头 ThreadLocal;
|
||||
* 调用结束后恢复/清理,避免池化线程泄漏或串扰。
|
||||
* 在有界线程池等子线程中调用 Feign 前,必须为当前线程设置与 {@code context} 一致的请求头 ThreadLocal; 调用结束后恢复/清理,避免池化线程泄漏或串扰。
|
||||
*/
|
||||
public static <T> T callWithContext(CloudwalkCallContext context, Callable<T> action) throws Exception {
|
||||
Map<String, String> previous = get();
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@
|
||||
<foreach collection="zoneIds" item="id" open="(" separator="," close=")">
|
||||
#{id,jdbcType=VARCHAR}
|
||||
</foreach>
|
||||
</select>
|
||||
</select>
|
||||
|
||||
|
||||
<update id="updateOld" parameterType="cn.cloudwalk.elevator.codeElevatorArea.dto.AcsElevatorCodeDTO">
|
||||
|
||||
+2
-2
@@ -74,8 +74,8 @@ public class ImageRuleRefListResult extends CloudwalkBaseTimes implements Serial
|
||||
if ((this$includeLabels == null) ? (other$includeLabels != null)
|
||||
: !this$includeLabels.equals(other$includeLabels))
|
||||
return false;
|
||||
Object this$includeOrganizations = getIncludeOrganizations(), other$includeOrganizations =
|
||||
other.getIncludeOrganizations();
|
||||
Object this$includeOrganizations = getIncludeOrganizations(),
|
||||
other$includeOrganizations = other.getIncludeOrganizations();
|
||||
if ((this$includeOrganizations == null) ? (other$includeOrganizations != null)
|
||||
: !this$includeOrganizations.equals(other$includeOrganizations))
|
||||
return false;
|
||||
|
||||
+14
-14
@@ -1,14 +1,14 @@
|
||||
package cn.cloudwalk.elevator.person.dao;
|
||||
|
||||
import cn.cloudwalk.elevator.person.dto.TenantVisitorFloorPolicyDto;
|
||||
|
||||
public interface TenantVisitorFloorPolicyDao {
|
||||
|
||||
/**
|
||||
* 查询租户级启用中的 INTERSECT_ALLOWLIST 策略(building_id 为空)。
|
||||
*
|
||||
* @param businessId 机构 ID
|
||||
* @return 无配置时 null
|
||||
*/
|
||||
TenantVisitorFloorPolicyDto selectEnabledTenantDefault(String businessId);
|
||||
}
|
||||
package cn.cloudwalk.elevator.person.dao;
|
||||
|
||||
import cn.cloudwalk.elevator.person.dto.TenantVisitorFloorPolicyDto;
|
||||
|
||||
public interface TenantVisitorFloorPolicyDao {
|
||||
|
||||
/**
|
||||
* 查询租户级启用中的 INTERSECT_ALLOWLIST 策略(building_id 为空)。
|
||||
*
|
||||
* @param businessId 机构 ID
|
||||
* @return 无配置时 null
|
||||
*/
|
||||
TenantVisitorFloorPolicyDto selectEnabledTenantDefault(String businessId);
|
||||
}
|
||||
|
||||
+71
-71
@@ -1,71 +1,71 @@
|
||||
package cn.cloudwalk.elevator.person.dto;
|
||||
|
||||
/**
|
||||
* 租户访客楼层策略(表 tenant_visitor_floor_policy 行映射)。
|
||||
*/
|
||||
public class TenantVisitorFloorPolicyDto {
|
||||
|
||||
private String id;
|
||||
private String businessId;
|
||||
private String policyType;
|
||||
private String allowZoneIds;
|
||||
private String buildingId;
|
||||
private Integer enabled;
|
||||
private Long policyVersion;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getBusinessId() {
|
||||
return businessId;
|
||||
}
|
||||
|
||||
public void setBusinessId(String businessId) {
|
||||
this.businessId = businessId;
|
||||
}
|
||||
|
||||
public String getPolicyType() {
|
||||
return policyType;
|
||||
}
|
||||
|
||||
public void setPolicyType(String policyType) {
|
||||
this.policyType = policyType;
|
||||
}
|
||||
|
||||
public String getAllowZoneIds() {
|
||||
return allowZoneIds;
|
||||
}
|
||||
|
||||
public void setAllowZoneIds(String allowZoneIds) {
|
||||
this.allowZoneIds = allowZoneIds;
|
||||
}
|
||||
|
||||
public String getBuildingId() {
|
||||
return buildingId;
|
||||
}
|
||||
|
||||
public void setBuildingId(String buildingId) {
|
||||
this.buildingId = buildingId;
|
||||
}
|
||||
|
||||
public Integer getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(Integer enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public Long getPolicyVersion() {
|
||||
return policyVersion;
|
||||
}
|
||||
|
||||
public void setPolicyVersion(Long policyVersion) {
|
||||
this.policyVersion = policyVersion;
|
||||
}
|
||||
}
|
||||
package cn.cloudwalk.elevator.person.dto;
|
||||
|
||||
/**
|
||||
* 租户访客楼层策略(表 tenant_visitor_floor_policy 行映射)。
|
||||
*/
|
||||
public class TenantVisitorFloorPolicyDto {
|
||||
|
||||
private String id;
|
||||
private String businessId;
|
||||
private String policyType;
|
||||
private String allowZoneIds;
|
||||
private String buildingId;
|
||||
private Integer enabled;
|
||||
private Long policyVersion;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getBusinessId() {
|
||||
return businessId;
|
||||
}
|
||||
|
||||
public void setBusinessId(String businessId) {
|
||||
this.businessId = businessId;
|
||||
}
|
||||
|
||||
public String getPolicyType() {
|
||||
return policyType;
|
||||
}
|
||||
|
||||
public void setPolicyType(String policyType) {
|
||||
this.policyType = policyType;
|
||||
}
|
||||
|
||||
public String getAllowZoneIds() {
|
||||
return allowZoneIds;
|
||||
}
|
||||
|
||||
public void setAllowZoneIds(String allowZoneIds) {
|
||||
this.allowZoneIds = allowZoneIds;
|
||||
}
|
||||
|
||||
public String getBuildingId() {
|
||||
return buildingId;
|
||||
}
|
||||
|
||||
public void setBuildingId(String buildingId) {
|
||||
this.buildingId = buildingId;
|
||||
}
|
||||
|
||||
public Integer getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(Integer enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public Long getPolicyVersion() {
|
||||
return policyVersion;
|
||||
}
|
||||
|
||||
public void setPolicyVersion(Long policyVersion) {
|
||||
this.policyVersion = policyVersion;
|
||||
}
|
||||
}
|
||||
|
||||
+19
-19
@@ -1,19 +1,19 @@
|
||||
package cn.cloudwalk.elevator.person.impl;
|
||||
|
||||
import cn.cloudwalk.elevator.person.dao.TenantVisitorFloorPolicyDao;
|
||||
import cn.cloudwalk.elevator.person.dto.TenantVisitorFloorPolicyDto;
|
||||
import cn.cloudwalk.elevator.person.mapper.TenantVisitorFloorPolicyMapper;
|
||||
import javax.annotation.Resource;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public class TenantVisitorFloorPolicyDaoImpl implements TenantVisitorFloorPolicyDao {
|
||||
|
||||
@Resource
|
||||
private TenantVisitorFloorPolicyMapper tenantVisitorFloorPolicyMapper;
|
||||
|
||||
@Override
|
||||
public TenantVisitorFloorPolicyDto selectEnabledTenantDefault(String businessId) {
|
||||
return this.tenantVisitorFloorPolicyMapper.selectEnabledTenantDefault(businessId);
|
||||
}
|
||||
}
|
||||
package cn.cloudwalk.elevator.person.impl;
|
||||
|
||||
import cn.cloudwalk.elevator.person.dao.TenantVisitorFloorPolicyDao;
|
||||
import cn.cloudwalk.elevator.person.dto.TenantVisitorFloorPolicyDto;
|
||||
import cn.cloudwalk.elevator.person.mapper.TenantVisitorFloorPolicyMapper;
|
||||
import javax.annotation.Resource;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public class TenantVisitorFloorPolicyDaoImpl implements TenantVisitorFloorPolicyDao {
|
||||
|
||||
@Resource
|
||||
private TenantVisitorFloorPolicyMapper tenantVisitorFloorPolicyMapper;
|
||||
|
||||
@Override
|
||||
public TenantVisitorFloorPolicyDto selectEnabledTenantDefault(String businessId) {
|
||||
return this.tenantVisitorFloorPolicyMapper.selectEnabledTenantDefault(businessId);
|
||||
}
|
||||
}
|
||||
|
||||
+12
-12
@@ -1,12 +1,12 @@
|
||||
package cn.cloudwalk.elevator.person.mapper;
|
||||
|
||||
import cn.cloudwalk.elevator.person.dto.TenantVisitorFloorPolicyDto;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
public interface TenantVisitorFloorPolicyMapper {
|
||||
|
||||
/**
|
||||
* 租户级默认策略:building_id 为空,启用,INTERSECT_ALLOWLIST。
|
||||
*/
|
||||
TenantVisitorFloorPolicyDto selectEnabledTenantDefault(@Param("businessId") String businessId);
|
||||
}
|
||||
package cn.cloudwalk.elevator.person.mapper;
|
||||
|
||||
import cn.cloudwalk.elevator.person.dto.TenantVisitorFloorPolicyDto;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
public interface TenantVisitorFloorPolicyMapper {
|
||||
|
||||
/**
|
||||
* 租户级默认策略:building_id 为空,启用,INTERSECT_ALLOWLIST。
|
||||
*/
|
||||
TenantVisitorFloorPolicyDto selectEnabledTenantDefault(@Param("businessId") String businessId);
|
||||
}
|
||||
|
||||
+22
-22
@@ -1,22 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="cn.cloudwalk.elevator.person.mapper.TenantVisitorFloorPolicyMapper">
|
||||
|
||||
<select id="selectEnabledTenantDefault" resultType="cn.cloudwalk.elevator.person.dto.TenantVisitorFloorPolicyDto">
|
||||
SELECT id,
|
||||
business_id AS businessId,
|
||||
policy_type AS policyType,
|
||||
allow_zone_ids AS allowZoneIds,
|
||||
building_id AS buildingId,
|
||||
enabled AS enabled,
|
||||
policy_version AS policyVersion
|
||||
FROM tenant_visitor_floor_policy
|
||||
WHERE business_id = #{businessId,jdbcType=VARCHAR}
|
||||
AND enabled = 1
|
||||
AND policy_type = 'INTERSECT_ALLOWLIST'
|
||||
AND (building_id IS NULL OR building_id = '')
|
||||
ORDER BY updated_at DESC, policy_version DESC
|
||||
LIMIT 1
|
||||
</select>
|
||||
</mapper>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="cn.cloudwalk.elevator.person.mapper.TenantVisitorFloorPolicyMapper">
|
||||
|
||||
<select id="selectEnabledTenantDefault" resultType="cn.cloudwalk.elevator.person.dto.TenantVisitorFloorPolicyDto">
|
||||
SELECT id,
|
||||
business_id AS businessId,
|
||||
policy_type AS policyType,
|
||||
allow_zone_ids AS allowZoneIds,
|
||||
building_id AS buildingId,
|
||||
enabled AS enabled,
|
||||
policy_version AS policyVersion
|
||||
FROM tenant_visitor_floor_policy
|
||||
WHERE business_id = #{businessId,jdbcType=VARCHAR}
|
||||
AND enabled = 1
|
||||
AND policy_type = 'INTERSECT_ALLOWLIST'
|
||||
AND (building_id IS NULL OR building_id = '')
|
||||
ORDER BY updated_at DESC, policy_version DESC
|
||||
LIMIT 1
|
||||
</select>
|
||||
</mapper>
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
# 00 总览:模块定位与领域结构
|
||||
|
||||
## 1. 模块在系统中的位置
|
||||
|
||||
`cw-elevator-application-service` 是电梯应用的 **业务编排与领域服务实现** 层:
|
||||
|
||||
- **向上** 被 `cw-elevator-application-web`(HTTP 入口)等调用。
|
||||
- **向下** 依赖 `cw-elevator-application-data`(DAO/MyBatis)与多类 **OpenFeign 客户端**(CWOS 设备/人员/图库/系统设置、访客标准服务、第三方 MQTT 等)。
|
||||
- **横切** 使用 Spring 事件(`CloudwalkEventManager`)、缓存(`@Cacheable`)、异步(`@Async`)等。
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph Web["cw-elevator-application-web"]
|
||||
C[Controllers]
|
||||
end
|
||||
subgraph Svc["cw-elevator-application-service 本文档范围"]
|
||||
D[device]
|
||||
P[passrule / person]
|
||||
R[record]
|
||||
Z[zone / code]
|
||||
M[mqtt / export / ...]
|
||||
end
|
||||
subgraph Data["cw-elevator-application-data"]
|
||||
DAO[DAO / Mapper]
|
||||
end
|
||||
subgraph Ext["外部服务 Feign / HTTP"]
|
||||
W[CWOS intelligent-*]
|
||||
V[访客 ninca-crk-std]
|
||||
Q[MQTT 第三方]
|
||||
end
|
||||
C --> Svc
|
||||
Svc --> DAO
|
||||
Svc --> W
|
||||
Svc --> V
|
||||
Svc --> Q
|
||||
```
|
||||
|
||||
## 2. 领域全景(思维导图)
|
||||
|
||||
```mermaid
|
||||
mindmap
|
||||
root((elevator service))
|
||||
设备 device
|
||||
本应用电梯设备 CRUD
|
||||
与平台设备联动分页
|
||||
重组绑定 楼层/人员/条件
|
||||
异步任务 进度
|
||||
通行与人员
|
||||
规则 passrule
|
||||
图库规则 image 规则
|
||||
人员 acs 与 规则 person
|
||||
记录 record
|
||||
通行记录
|
||||
识别记录
|
||||
分析统计
|
||||
区域与编码
|
||||
区域树 zone
|
||||
电梯码 codeElevatorArea
|
||||
集成
|
||||
mqtt 推送
|
||||
导出 export
|
||||
下载 downloadcenter
|
||||
文件 storage
|
||||
```
|
||||
|
||||
## 3. 核心服务接口与包对应关系
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class AcsElevatorDeviceService
|
||||
class AcsDeviceTaskService
|
||||
class AcsPassRuleService
|
||||
class ImageRuleRefService
|
||||
class AcsPersonService
|
||||
class PersonRuleService
|
||||
class AcsElevatorRecordService
|
||||
class AcsRecogRecordService
|
||||
class ZoneService
|
||||
class AcsElevatorCodeService
|
||||
class MqttService
|
||||
AcsElevatorDeviceService : +设备与绑定编排
|
||||
AcsDeviceTaskService : +updateFloors 任务推进
|
||||
AcsPassRuleService : +规则 CRUD 与图库 list
|
||||
ImageRuleRefService : +图库视角规则
|
||||
AcsPersonService : +人员 page 等
|
||||
PersonRuleService : +规则内人员/访客
|
||||
AcsElevatorRecordService : +通行记录
|
||||
AcsRecogRecordService : +识别记录 add
|
||||
ZoneService : +区域树
|
||||
AcsElevatorCodeService : +电梯码
|
||||
MqttService : +sendInfoToOne
|
||||
```
|
||||
|
||||
## 4. 与外部系统协作(逻辑视图)
|
||||
|
||||
| 外部能力 | 典型用途 |
|
||||
|----------|----------|
|
||||
| `intelligent` 设备/人员/图库/系统设置 | 设备主数据、人员同步、图库、区域树 |
|
||||
| `ninca-crk-std`(IP 配置 + RestTemplate 或 Feign) | 访客 **three 线** 与 **Feign `VisitorFeignClient`** 不同路径 |
|
||||
| `cloudwalk-device-thirdparty` / MQTT | 发布主题推送识别摘要 |
|
||||
| DaVinci 文件分片 | `AcsFileStorageService` 大文件 |
|
||||
| 下载中心 | 异步任务结果取回 |
|
||||
|
||||
## 5. 读文档建议
|
||||
|
||||
1. 先读 [01-device-and-task.md](01-device-and-task.md) 理解「设备—楼层—任务」主路径。
|
||||
2. 再读 [02-passrule-and-person.md](02-passrule-and-person.md) 理解规则与人员两条下发线。
|
||||
3. 记录、MQTT 与事件见 [03-record-recognition.md](03-record-recognition.md) 与 [04-mqtt-visitor-event.md](04-mqtt-visitor-event.md)。
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
# 01 设备与设备任务(`device`)
|
||||
|
||||
## 1.1 业务目标
|
||||
|
||||
- 维护 **本应用视角** 的电梯设备与平台设备(`DeviceService`)的关联数据。
|
||||
- 支持 **重组/绑定**:按区域、标签、组织等条件选择楼层,将 **人员** 或 **规则** 批量下发到设备侧(经远程服务)。
|
||||
- 通过 **设备任务**(`AcsDeviceTaskService` / `AcsDeviceTaskServiceImpl`)在楼层维度 **有界并行** 执行远程步骤,并 **按完成顺序** 推进任务上的 `bindDevices` 等进度(与走查约定 §9 一致)。
|
||||
|
||||
## 1.2 主接口 `AcsElevatorDeviceService`
|
||||
|
||||
| 方法 | 行为概要 |
|
||||
|------|----------|
|
||||
| `add` / `edit` / `delete` | 电梯设备增删改,写本库并协调平台设备信息 |
|
||||
| `get` / `getById` / `getFo` | 分页列表、单条、表单选项数据 |
|
||||
| `getBuildingId` / `getBusinessId` | 从查询条件解析楼宇或租户 |
|
||||
| `devicePage` | 走 **平台** 设备分页,与 `Acs` 表关联展示 |
|
||||
| `listUnbindFloors` / `listFloors` / `listCondition` / `listConditionByLabelIds` | 绑定时待选楼层、已绑楼层、条件筛选(标签等) |
|
||||
| `bindingFloors` / `bindingPerson` | 启动绑定:创建/更新设备任务,异步 `updateFloors` |
|
||||
| `getTask` / `setTaskStop` | 查询任务进度、停止任务 |
|
||||
|
||||
**实现类**:`device/impl/AcsElevatorDeviceServiceImpl.java`。
|
||||
|
||||
## 1.3 设备任务 `AcsDeviceTaskService`
|
||||
|
||||
| 方法 | 行为概要 |
|
||||
|------|----------|
|
||||
| `updateFloors` | 在 **增加楼层 / 删除楼层** 两路上,将远程调用按 **每批 `UPDATE_FLOORS_FLOOR_PARALLEL` 个楼层** 提交到 `elevatorRemoteBoundedExecutor`;`FeignThreadLocalUtil` 在子任务中恢复租户上下文;每步成功再 `advanceBindProgressOne` 更新 `bindDevices`。 |
|
||||
|
||||
**实现类**:`device/impl/AcsDeviceTaskServiceImpl.java`。
|
||||
|
||||
## 1.4 用例级视图(操作者:管理员 / 系统)
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph 电梯设备管理
|
||||
A[增删改电梯设备]
|
||||
B[分页查询/详情]
|
||||
end
|
||||
subgraph 重组绑定
|
||||
C[选楼层/人员或规则]
|
||||
D[启动 bindingFloors/bindingPerson]
|
||||
E[异步 updateFloors]
|
||||
end
|
||||
A --> B
|
||||
C --> D --> E
|
||||
```
|
||||
|
||||
## 1.5 时序:启动楼层绑定后异步推进(概念)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Web as Web/Controller
|
||||
participant Dev as AcsElevatorDeviceServiceImpl
|
||||
participant Task as AcsDeviceTaskService
|
||||
participant Pool as 有界线程池
|
||||
participant Remote as 人员/规则 Feign
|
||||
Web->>Dev: bindingFloors(参数)
|
||||
Dev->>Task: 异步 updateFloors(...)
|
||||
loop 每批楼层
|
||||
Task->>Pool: invokeAll(子任务)
|
||||
Pool->>Remote: add/delete 单楼层步骤
|
||||
Remote-->>Task: 成功
|
||||
Task->>Task: advanceBindProgressOne(taskId)
|
||||
end
|
||||
```
|
||||
|
||||
## 1.6 设备设置子域 `device/setting`
|
||||
|
||||
- **`AcsDeviceSettingService`**:如体温相关设置查询(`getTemperatureSetting`)。
|
||||
- **`AcsDeviceImageStoreAppBindService`**:设备与图库应用绑定/解绑等(实现见 `setting/impl`)。
|
||||
|
||||
## 1.7 与邻域关系
|
||||
|
||||
- **规则/人员**:`updateFloors` 内调 `PersonRuleService`、`ImageRuleRefService` 等,取决于绑定参数是 `personId` 还是标签/组织。
|
||||
- **data 层**:`AcsDeviceTaskDao`、设备相关 DAO 在 **data 模块**。
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
# 02 通行规则与人员(`passrule` / `person`)
|
||||
|
||||
## 2.1 业务目标
|
||||
|
||||
- **通行规则**:按 **区域/楼层/图库/标签/组织** 等维护通行策略,与平台图库、设备绑定任务配合。
|
||||
- **图库规则引用**(`ImageRuleRefService`):从 **图库/图片 ID** 视角查规则列表、只增规则、人员列表、分页等,与 `AcsPassRuleService` 的「规则主 CRUD」互补。
|
||||
- **人员**:`AcsPersonService` 面向业务侧人员维护;`PersonRuleService` 在 **规则上下文** 下做人员/访客的增删改查及 **与图库人员** 的关联(如 `personDetail`)。
|
||||
|
||||
## 2.2 `AcsPassRuleService` 方法表
|
||||
|
||||
| 方法 | 概要 |
|
||||
|------|------|
|
||||
| `listFloor` | 规则关联楼层列表 |
|
||||
| `getIsDefaultByZoneId` | 区域下是否默认规则等标识 |
|
||||
| `add` / `update` / `delete` / `detail` / `page` / `list` | 标准 CRUD 与详情分页 |
|
||||
| `listByImageId` | 按图库/图片查规则 DTO 列表,供设备或绑定流使用 |
|
||||
|
||||
## 2.3 `ImageRuleRefService` 方法表
|
||||
|
||||
| 方法 | 概要 |
|
||||
|------|------|
|
||||
| `page` | 图库规则分页(与规则查询参数共用部分字段) |
|
||||
| `listFloor` | 图库侧楼层规则列表 |
|
||||
| `listByPersonInfo` | 按人维度列规则/楼层 |
|
||||
| `listByPersonList` | 批量人列表与规则关系 |
|
||||
| `detail` | 单条图库规则详情 |
|
||||
| `addOnlyRule` | **仅** 建规则(设备任务中「非人员」线可能走此路) |
|
||||
| `update` / `delete` | 更新/删除,与 `AcsPassRuleService` 分工依实现类而定 |
|
||||
|
||||
**实现基类提示**:`passrule/impl/AbstractAcsPassService.java` 可抽取与远程/DAO 的共性。
|
||||
|
||||
## 2.4 `AcsPersonService` 与 `PersonRuleService`
|
||||
|
||||
| 接口 | 特点 |
|
||||
|------|------|
|
||||
| `AcsPersonService` | `add` / `edit` / `delete` / `page` / `timeDetail` / `pageByApp` |
|
||||
| `PersonRuleService` | 在规则域增加 `addVisitor`、`personDetail(图库人员)` 等 |
|
||||
|
||||
## 2.5 用例图(简)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Admin((管理员))
|
||||
Admin --> P1[维护通行规则]
|
||||
Admin --> P2[按图查规则/楼层]
|
||||
Admin --> P3[维护人员/访客]
|
||||
P1 --> S1[AcsPassRuleService]
|
||||
P2 --> S2[ImageRuleRefService]
|
||||
P3 --> S3[PersonRuleService / AcsPersonService]
|
||||
```
|
||||
|
||||
## 2.6 时序:仅创建规则(与设备任务线关联)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Task as AcsDeviceTaskServiceImpl
|
||||
participant IMG as ImageRuleRefService
|
||||
Task->>IMG: addOnlyRule(区域/标签/组织)
|
||||
IMG-->>Task: CloudwalkResult
|
||||
Note over Task: 成功后再推进 bind 进度
|
||||
```
|
||||
|
||||
## 2.7 与设备任务关系(概念)
|
||||
|
||||
- **人员绑定**:`updateFloors` 中若带 `personId`,走 `PersonRuleService.add/delete` 按 **楼层/区域** 维度。
|
||||
- **非人员**(标签/组织):建/删 **规则名** 与 `ImageRuleRef` DAO,并调 `imageRuleRefService` 的删除或 `addOnlyRule`。
|
||||
|
||||
更多细节以 `AcsPassRuleServiceImpl.java`、`AcsDeviceTaskServiceImpl.java` 源码为准。
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
# 03 通行记录与识别记录(`record`)
|
||||
|
||||
## 3.1 业务目标
|
||||
|
||||
- **电梯通行/开门记录**(`AcsElevatorRecordService`):分页查询、**新增**(落库、访客判断、发域事件)、修改状态、统计、Redis 缓存键等。
|
||||
- **人员识别记录**(`AcsRecogRecordService`):单接口 `add`,与图库/识别流对接。
|
||||
- **图库文件**(`PersonFileService`):`upload` 小图片字节上传,给记录新增时 **Base64 解码后** 调图库。
|
||||
- **`SendRecordTimeService`**:接口体为空,**无方法**,可视为占位或历史契约保留。
|
||||
|
||||
## 3.2 `AcsElevatorRecordService` 方法表
|
||||
|
||||
| 方法 | 概要 |
|
||||
|------|------|
|
||||
| `openRecord` | 分页查开门记录明细,**一年** 窗口校验;组装区域/片区/人员展示字段 |
|
||||
| `add` | 见 [04-mqtt-visitor-event.md](04-mqtt-visitor-event.md) 的访客与事件说明 |
|
||||
| `modify` | 按条件改开门记录 **状态**(如从 INIT 更新) |
|
||||
| `createCache` | 分布式任务锁下 **Redis 缓存** 初始化 |
|
||||
| `pageInfo` | 分页查询请求维表信息类结果 |
|
||||
| `analyseCycle` / `analyseCount` | 按周期/条件的开门统计、排行等 |
|
||||
|
||||
**实现类**:`record/impl/AcsElevatorRecordServiceImpl.java`。
|
||||
|
||||
## 3.3 识别子域
|
||||
|
||||
- **`AcsRecogRecordService#add`**:`AcsRecogRecordServiceImpl` 中增加识别记录(实现细节见同文件)。
|
||||
|
||||
## 3.4 流程图:`openRecord` 主路径
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[入参+分页] --> B{时间跨度>1年?}
|
||||
B -- 是 --> X[失败返回]
|
||||
B -- 否 --> C[DAO 分页]
|
||||
C --> D[去重取片区/区域id]
|
||||
D --> E[批量查设备片区名]
|
||||
E --> F[取区域树缓存/区域父名]
|
||||
F --> G[批量查人员底库照]
|
||||
G --> H[组装 AcsElevatorRecordResult 分页返回]
|
||||
```
|
||||
|
||||
## 3.5 时序:`add` 落库前访客与人员(节选)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant S as AcsElevatorRecordServiceImpl
|
||||
participant HTTP as ninca-crk-std HTTP
|
||||
participant P as PersonService
|
||||
participant DAO as AcsElevatorRecordDao
|
||||
participant EVT as CloudwalkEventManager
|
||||
S->>HTTP: POST three/visitor/record/query
|
||||
HTTP-->>S: 是否访客+被访人
|
||||
S->>P: detail(识别脸 id)
|
||||
P-->>S: 工号/组织
|
||||
S->>DAO: add(DTO)
|
||||
S->>EVT: publish(VisitorRecordPushEvent)
|
||||
```
|
||||
|
||||
## 3.6 领域事件
|
||||
|
||||
- 类型:`record/result/VisitorRecordPushEvent.java`
|
||||
- 主题 `getTopic()` 固定为 `VISITOR_RECORD_TOPIC`(供订阅方区分)。
|
||||
|
||||
与 MQTT 的关系见 [04-mqtt-visitor-event.md](04-mqtt-visitor-event.md)。
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
# 04 MQTT、访客与域事件
|
||||
|
||||
## 4.1 双通道:访客查询
|
||||
|
||||
| 方式 | 路径/Bean | 使用场景(本模块) |
|
||||
|------|-----------|---------------------|
|
||||
| **HTTP + RestTemplate** | `combineAuthClientURI("intelligent/three/visitor/record/query")` | `AcsElevatorRecordServiceImpl#add` 反查是否访客 |
|
||||
| **Feign** | `VisitorFeignClient` → `/intelligent/visitor/record/query` | 定义在 `visitor/client`,**本模块内无直接注入调用** |
|
||||
|
||||
两者 **不是** 同一路径;业务上均面向标准访客中心,但 **「three」** 与 **「intelligent」** 为产品/版本差异,部署时需与 `ninca-crk-std` 实际路由一致。
|
||||
|
||||
## 4.2 `MqttService` 与 `MqttServiceImpl`
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| `sendInfoToOne` | `@Async`:先 **睡眠约 10s** 等待识别记录落库,再查识别流水,拼 `AcsElevatorRecordMqttParam`,向 topic `{businessId}+_elevator_record` 发 JSON |
|
||||
| 远程 | `MqttFeignClient#publish` → 设备第三方 MQTT 服务 |
|
||||
|
||||
**调用关系**:本模块中 **`AcsElevatorRecordServiceImpl` 注入了 `MqttService` 但当前未调用**;若产品要求「保存记录后推送大屏」,可在 **监听 `VisitorRecordPushEvent` 的处理器** 或 **在 `add` 成功后** 显式调用 `sendInfoToOne`(需评估与异步睡眠设计是否一致)。
|
||||
|
||||
## 4.3 时序:MQTT 推送(当显式调用 `sendInfoToOne` 时)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Caller as 业务/监听器
|
||||
participant M as MqttServiceImpl
|
||||
participant DAO as AcsRecogRecordDao
|
||||
participant F as MqttFeignClient
|
||||
Caller->>M: sendInfoToOne(DTO)
|
||||
Note over M: sleep 10s
|
||||
M->>DAO: page(识别流水)
|
||||
DAO-->>M: 人名/标签
|
||||
M->>F: publish(topic, JSON)
|
||||
F-->>M: CloudwalkResult
|
||||
```
|
||||
|
||||
## 4.4 状态机:从「仅事件」到「+ MQTT」(部署选项)
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> RecordSaved: add DAO 成功
|
||||
RecordSaved --> EventPublished: VisitorRecordPushEvent
|
||||
EventPublished --> MqttOptional: 可选
|
||||
MqttOptional --> MqttPush: 若接线 sendInfoToOne
|
||||
MqttOptional --> NoMqtt: 当前默认
|
||||
```
|
||||
|
||||
## 4.5 域事件与 MQTT 解耦说明
|
||||
|
||||
- `VisitorRecordPushEvent` 经 `CloudwalkEventManager` 发布,**不保证** 与 MQTT 同一步骤执行。
|
||||
- 下游可独立订阅 **事件** 与 **MQTT topic**,避免强耦合。
|
||||
@@ -0,0 +1,45 @@
|
||||
# 05 区域与电梯区域编码(`zone` / `codeElevatorArea`)
|
||||
|
||||
## 5.1 `ZoneService`
|
||||
|
||||
| 方法 | 概要 |
|
||||
|------|------|
|
||||
| `tree` | 入参 `ZoneNextTreeParam`,调 **平台区域/系统设置** 能力,组装 **下一级树** 等,返回 `List<ZoneTreeResult>`(实现中常经 Feign + 工具类 `ZoneTreeCollectors`) |
|
||||
| `page` | 区域维度的分页列表 `ZoneResult` |
|
||||
|
||||
**实现类**:`zone/impl/ZoneServiceImpl.java`。
|
||||
|
||||
## 5.2 `AcsElevatorCodeService`(`codeElevatorArea`)
|
||||
|
||||
| 方法 | 概要 |
|
||||
|------|------|
|
||||
| `insertNew` / `updateOld` | 电梯与区域绑定的 **编码** 数据维护 |
|
||||
| `get` / `getFirstByParentId` | 单条/按父级取首条 |
|
||||
| `mapByZoneIds` | **批量** 按区域 id 查电梯编码,供树形接口 **一次** 拉取,避免 N+1(见接口内 Javadoc) |
|
||||
|
||||
## 5.3 时序:区域树 + 树上网格批量取码(概念)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Web as 上层
|
||||
participant Z as ZoneService
|
||||
participant Sy as 平台区域服务
|
||||
participant C as AcsElevatorCodeService
|
||||
Web->>Z: tree(参数)
|
||||
Z->>Sy: Feign 取区域
|
||||
Sy-->>Z: AreaTree
|
||||
Z-->>Web: ZoneTreeResult
|
||||
Web->>C: mapByZoneIds(多 zoneId)
|
||||
C-->>Web: Map 区域id→编码
|
||||
```
|
||||
|
||||
## 5.4 用例简图
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
U[运营/系统]
|
||||
U --> T[浏览区域树选楼层]
|
||||
U --> M[维护电梯-区域码]
|
||||
T --> ZoneService
|
||||
M --> AcsElevatorCodeService
|
||||
```
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
# 06 导出、下载与存储(`export` / `downloadcenter` / `storage`)
|
||||
|
||||
## 6.1 异步导出 `export`
|
||||
|
||||
- 抽象基类:`AcsAbstractExportAsyncService` —— 泛型封装 **分页拉取** → 转 Excel 行 DTO → 与下载中心协作。
|
||||
- **实现示例**:`ElevatorDeviceExportService`
|
||||
- 注入 `AcsElevatorDeviceService#get` 取设备分页。
|
||||
- 在 `queryPage` 中将 DTO 转为 `ElevatorDeviceRecordExcelResult`,并翻译 **在线/禁用** 等展示中文。
|
||||
|
||||
**典型时序**(概念):
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User as 用户/任务
|
||||
participant E as ElevatorDeviceExportService
|
||||
participant D as AcsElevatorDeviceService
|
||||
participant DC as 下载中心
|
||||
User->>E: 触发导出任务
|
||||
loop 分页
|
||||
E->>D: get(查询+分页)
|
||||
D-->>E: 行数据
|
||||
end
|
||||
E->>DC: 完成文件/回写状态
|
||||
```
|
||||
|
||||
## 6.2 下载中心 `AcsDownloadCenterService`
|
||||
|
||||
| 方法 | 概要 |
|
||||
|------|------|
|
||||
| `createDownload` | 创建下载任务/令牌 |
|
||||
| `finishDownload` | 任务完成回执 |
|
||||
| `queryDownloadStatus` | 轮询或查询状态 |
|
||||
|
||||
**实现类**:`downloadcenter/impl/AcsDownloadCenterServiceImpl.java`(以源码为准)。
|
||||
|
||||
## 6.3 分片文件 `AcsFileStorageService`(`storage`)
|
||||
|
||||
| 方法 | 概要 |
|
||||
|------|------|
|
||||
| `filePartInit` / `filePartAppend` / `filePartFinish` | DaVinci/门户 **分片上传** 生命周期 |
|
||||
| `getFileBase64` | 按 id 回读为 Base64 |
|
||||
|
||||
## 6.4 ER/依赖(导出子域)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Export[导出任务] --> Abs[AcsAbstractExportAsyncService]
|
||||
Abs --> Svc[具体领域 Service 如 设备]
|
||||
Abs --> Download[AcsDownloadCenterService]
|
||||
Storage[AcsFileStorageService] -.大文件.-> 门户
|
||||
```
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
# 07 横切与公共(`common` / `cacheable` / 基类)
|
||||
|
||||
## 7.1 `AcsApplicationService`
|
||||
|
||||
- 方法 `getApplicationId`:按业务/租户等解析 **应用 ID**(`AcsApplicationServiceImpl` 实现,细节见类)。
|
||||
|
||||
## 7.2 `AcsAreaTreeCacheableService`
|
||||
|
||||
- 包装 `SysettingAreaService#tree`。
|
||||
- `@Cacheable`:`ACS_AreaTreeCache`,key 与 `CacheOverrideConfig` 中 **租户前缀** 拼出,减少区域树 **重复远程调用**。
|
||||
|
||||
## 7.3 基类
|
||||
|
||||
| 类 | 作用 |
|
||||
|----|------|
|
||||
| `AbstractCloudwalkService` | 与 Cloudwalk 框架通用基能力(见父类/模块) |
|
||||
| `AbstractAcsDeviceService` | 设备域公用:区域树展平、构造带 `FeignThreadLocalUtil` 的 `CloudwalkCallContext` 等(见 `common/AbstractAcsDeviceService`) |
|
||||
| `AbstractAcsPassService` | 通行/规则子域抽取(见 `passrule/impl`) |
|
||||
|
||||
## 7.4 空接口
|
||||
|
||||
- `SendRecordTimeService`:无方法;若需扩展 **发送记录时间** 类能力,可在此增方法并由实现类承载。
|
||||
|
||||
## 7.5 包级 `package-info`
|
||||
|
||||
各子包在源码中已逐步补充 `package-info.java`(`device`/`record`/`mqtt`/`visitor` 等),可与本 `docs` 互参。
|
||||
|
||||
## 7.6 多维度总览表(维度矩阵)
|
||||
|
||||
| 维度 | 读者关注点 | 建议文档 |
|
||||
|------|------------|----------|
|
||||
| 业务价值 | 电梯设备、规则、人员、记录 | 01–03 |
|
||||
| 集成 | 访客、MQTT、Feign | 04, 00 |
|
||||
| 可运维 | 缓存、异步、任务进度 | 01, 07, 04 |
|
||||
| 可观测 | 事件、topic、锁 | 03, 04, 03-锁 |
|
||||
|
||||
**读文档优先级(示意,非绝对)**
|
||||
|
||||
| | 远程依赖多 | 本地/缓存多 |
|
||||
|--|------------|-------------|
|
||||
| **写路径多** | 设备任务、规则+人员 | (较少) |
|
||||
| **读路径多** | 记录 openRecord | 区域树缓存、电梯码 map |
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
# 访客与电梯业务:注册/登记与派梯授权完整说明
|
||||
|
||||
> 本文档针对 **本仓库 `cw-elevator-application`(以 service 层为逻辑落点)** 中与「访客」相关的 **完整可追踪路径** 做说明,并区分行业语义下常被混用的 **「访客登记/注册」** 与 **「在电梯域为访客授派梯权」**。
|
||||
|
||||
## 1. 概念与边界
|
||||
|
||||
| 概念 | 通常含义 | 在本项目中的落点 |
|
||||
|------|----------|------------------|
|
||||
| **访客主数据登记/注册** | 在**标准访客/一卡通**中录入:姓名、证号、访期、人脸、被访人、访客单等 | **不在**电梯应用内实现完整登记 UI;主数据以 **人员 ID(`personId`)** 或 **标准访客服务** 的档案形式存在。电梯服务通过 `PersonService`、图库、`ninca-crk-std` 的 HTTP/ Feign 与**既有档案**对接。 |
|
||||
| **电梯侧「访客派梯授权」** | 在已存在**访客人员 ID** 的前提下,为指定**楼层**写入通行规则、绑定图库、设访期,使闸机/电梯能识别其通行 | **本应用显式能力**:`PersonRuleService#addVisitor` → HTTP `POST /elevator/person/add/visitor`(见下文)。 |
|
||||
| **通行中「是否访客」打标** | 识别到人脸后,写通行记录时判断是否访客、回填被访人 | `AcsElevatorRecordServiceImpl#add` 中调 **`intelligent/three/visitor/record/query`**,不创建新访客档案。 |
|
||||
|
||||
**结论**:若将「访客注册」理解为**在平台完整新建访客档案**,其主流程在 **外部标准服务**;本仓文档的「电梯侧部分」是 **(1) 授权** 与 **(2) 业务记录打标** 的完整、可追溯描述。
|
||||
|
||||
---
|
||||
|
||||
## 2. 总览图:访客相关双主线
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph 标准域["标准访客/人员/图库(多在外部或 CWOS 组件)"]
|
||||
VReg[访客档案登记/维护]
|
||||
P[平台人员 personId]
|
||||
G[图库 imageStoreId]
|
||||
end
|
||||
subgraph 电梯应用
|
||||
A["POST /elevator/person/add/visitor\naddVisitor 派梯授权"]
|
||||
B["AcsElevatorRecordService#add\nthree 线反查访客+被访人"]
|
||||
end
|
||||
VReg -.->|产生 visitor 对应人员ID| P
|
||||
P --> A
|
||||
A --> G
|
||||
B -->|只读反查| VReg
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 主线 A:访客派梯授权(核心业务逻辑)
|
||||
|
||||
### 3.1 对外入口
|
||||
|
||||
| 项 | 值 |
|
||||
|----|-----|
|
||||
| HTTP 方法/路径 | `POST` **`/elevator/person/add/visitor`** |
|
||||
| Web | `AcsPersonController#addVisitor`(`person/controller/AcsPersonController.java`) |
|
||||
| 入参体 | `AcsPersonAddVisitorForm` → 复制为 `AcsPersonAddVisitorParam` |
|
||||
| 服务 | `PersonRuleService#addVisitor` |
|
||||
| 实现 | `PersonRuleServiceImpl#addVisitor`(`person/impl/PersonRuleServiceImpl.java`) |
|
||||
|
||||
**入参字段(`AcsPersonAddVisitorParam`)**:
|
||||
|
||||
| 字段 | 含义 |
|
||||
|------|------|
|
||||
| `visitorId` | 访客在**平台人员体系**中的人员主键(非电梯单独造号) |
|
||||
| `personId` | **被访人**人员主键,用于在**未传 floorIds 时**拉取被访人可通行楼层 |
|
||||
| `begVisitorTime` / `endVisitorTime` | 图库绑定**有效期**(见 `ImageStorePersonBindParam`) |
|
||||
| `floorIds` | 可选。若**非空**,则跳过被访人楼层与租户策略的自动推算,**直接使用**该列表作为要授权的楼层 id 集合 |
|
||||
|
||||
### 3.2 处理步骤(与源码顺序一致)
|
||||
|
||||
1. **若调用方未传 `floorIds`**
|
||||
- 用 `PersonService#detail` 取 **被访人** `PersonResult`。
|
||||
- 失败或无人 → 返回 `CloudwalkResult.fail`(`76260531` 等,以返回码为准)。
|
||||
- 取被访人 **`floorList`** 作为候选楼层;**为空** → 失败 `76260531`。
|
||||
2. **租户访客楼层策略(可选收窄)**
|
||||
- 从 `TenantVisitorFloorPolicyDao#selectEnabledTenantDefault(businessId)` 读**启用**的默认策略行(`TenantVisitorFloorPolicyDto`)。
|
||||
- 若 `enabled==1` 且 `allowZoneIds`(JSON 字符串数组)解析出非空列表:
|
||||
将 **被访人 `floorList`** 与 **策略允许 zone id 集合** 做**有序交集**(`intersectPreserveHostOrder`:保留被访人原顺序、只留落在 allow 中的楼层)。
|
||||
- 交完若**为空** → 失败 **`76260532`**(可配置文案,表示与租户策略无交集)。
|
||||
3. **若调用方已传 `floorIds`**
|
||||
- 以上「被访人楼层 + 策略求交」**整段不执行**;**直接使用**传入的 `floorIds` 作为 `effectiveFloors` 后续逻辑已合并进 `param.setFloorIds` 分支,最终以 `param.getFloorIds()` 非空为继续条件;若仍为空则 `76260531`。
|
||||
4. **解析首楼层所属楼栋 → 取图库 ID**
|
||||
- 用 `zoneService.page` 以 **第一个 `floorId`** 查 `ZoneResult`,再 `deviceImageStoreDao.getByBuildingId(首楼层 parentId)` 得 **`imageStoreId`**(与楼栋绑定的图库)。
|
||||
5. **每层写入「人员挂默认规则」的规则引用行**
|
||||
- 对每个 `floorId`:查 `imageRuleRefDao.getDefaultByZoneId(floorId)` 得 **该楼层默认规则**。
|
||||
- 拼 `ImageRuleRefAddDto` 列表:新建 UUID、`businessId`、`personId=visitorId`、挂 `parentRule`、`name`/`zoneId`/`zoneName` 自默认规则、`personDelete=0` 等。
|
||||
- `imageRuleRefDao.insertList(insertList)` 批量落库(电梯侧**规则与人员**关系,供通行策略使用)。
|
||||
6. **图库人员绑定与分组同步**
|
||||
- `ImageStorePersonBindParam`:`imageStoreId`、`personIds=[visitorId]`、长期 null 作长期、起止时间用 `begVisitorTime`/`endVisitorTime`。
|
||||
- `imageStorePersonService.batchBind` —— 将访客**绑定**到本楼栋图库并带有效期。
|
||||
- 再 `updateGroupPersonRef`:对同一 `visitorId` + `imageStoreId` 做**组人员引用**更新(与通行业务的图库组一致)。
|
||||
|
||||
7. 任一步远程失败:返回 Feign/平台返回的 code/message 或 `76260530` 等包装异常(见 `catch`)。
|
||||
|
||||
### 3.3 活动图(无 floorIds,有租户策略时)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([收到 add/visitor]) --> F{floorIds 已传?}
|
||||
F -- 是 --> G[直接使用 floorIds]
|
||||
F -- 否 --> D[被访人 detail]
|
||||
D --> H{有 floorList?}
|
||||
H -- 否 --> E1[失败 76260531]
|
||||
H -- 是 --> T{租户策略 enabled=1 且 allow 非空?}
|
||||
T -- 否 --> G
|
||||
T -- 是 --> I[与 allowZoneIds 求交]
|
||||
I --> J{交后非空?}
|
||||
J -- 否 --> E2[失败 76260532]
|
||||
J -- 是 --> G
|
||||
G --> K[首 floor → 楼栋 → imageStoreId]
|
||||
K --> L[每楼层: 写 ImageRuleRef + 默认父规则]
|
||||
L --> M[图库 batchBind 访客+访期]
|
||||
M --> N[updateGroupPersonRef]
|
||||
N --> Ok([成功])
|
||||
```
|
||||
|
||||
### 3.4 时序图
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
participant C as 前端/调用方
|
||||
participant API as AcsPersonController
|
||||
participant PR as PersonRuleServiceImpl
|
||||
participant PS as PersonService(CWOS)
|
||||
participant DB as ImageRuleRefDao/租户策略表
|
||||
participant Z as ZoneService
|
||||
participant D as DeviceImageStoreDao
|
||||
participant IS as ImageStorePersonService
|
||||
C->>API: POST /elevator/person/add/visitor
|
||||
API->>PR: addVisitor(param, context)
|
||||
opt 未带 floorIds
|
||||
PR->>PS: detail(被访人)
|
||||
PS-->>PR: floorList
|
||||
PR->>DB: 读租户策略 + 求交
|
||||
end
|
||||
PR->>Z: page(查首层 zone)
|
||||
Z-->>PR: 楼栋信息
|
||||
PR->>D: getByBuildingId(楼栋)
|
||||
D-->>PR: imageStoreId
|
||||
loop 每层
|
||||
PR->>DB: getDefaultByZoneId 规则
|
||||
PR->>DB: insertList 人员规则引用
|
||||
end
|
||||
PR->>IS: batchBind(访期+访客+图库)
|
||||
IS-->>PR: 成功
|
||||
PR->>IS: updateGroupPersonRef
|
||||
PR-->>API: CloudwalkResult
|
||||
API-->>C: Boolean
|
||||
```
|
||||
|
||||
### 3.5 策略与数据表(只读理解)
|
||||
|
||||
- **`tenant_visitor_floor_policy`**:租户级默认策略;`allow_zone_ids` 为 **JSON 数组字符串**,表示允许作为访客派梯的 **区域/楼层 id**;`enabled=1` 时参与**交集收紧**(见 DTO 注释与 DAO)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 主线 B:通行记录落库时「访客身份」的认定(非注册)
|
||||
|
||||
**场景**:设备侧上报识别结果 **`add` 一条电梯通行/开门记录**。
|
||||
|
||||
**实现**:`AcsElevatorRecordServiceImpl#add` 中,在**写本库**之前:
|
||||
|
||||
1. 组装 `VisitorRecordQueryParam`:`visitorId = addDTO.getRecognitionFaceId()`(**识别脸/访客侧 id**,与产品约定有关)、`businessId` 为租户;请求头 `businessId` 同。
|
||||
2. `RestTemplateUtil.post` → **`http://{ninca-crk-std}/intelligent/three/visitor/record/query`**
|
||||
3. 若返回列表**非空**:`isVisitor=1`,并从首条 `VisitorResult` 取 `personId` 写入 **被访人** `interviewee`。
|
||||
4. 此接口**不**在电梯服务内**新建**访客主档,仅**查询**与打标;与 `VisitorFeignClient` 的 ` /intelligent/visitor/record/query` **路径不同**(见 [04-mqtt-visitor-event.md](04-mqtt-visitor-event.md))。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant R as AcsElevatorRecordServiceImpl
|
||||
participant HTTP as ninca-crk-std three/query
|
||||
participant DAO as 电梯记录 DAO
|
||||
R->>HTTP: visitorId+tenant
|
||||
HTTP-->>R: List 访客档案或空
|
||||
R->>DAO: add(含 isVisitor, interviewee)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 与图库/通行「访客标签」的其它关系
|
||||
|
||||
- **MQTT 侧**:`MqttServiceImpl` 在识别流水中若 `personLabelIds` **含 `"1"`**,在 MQTT JSON 中置 `isVisitor=true`(**标签**语义,与上一节**档案访客**是不同判断维度)。见 `MqttServiceImpl` 中 `VISITOR_LABEL_CODE`。
|
||||
- **域事件**:`VisitorRecordPushEvent` 在记录 `add` 成功后发布,**主题**为 `VISITOR_RECORD_TOPIC`;不替代访客主数据登记。
|
||||
|
||||
---
|
||||
|
||||
## 6. 错误与日志索引(addVisitor 相关,便于排障)
|
||||
|
||||
| 场景 | 码/信息方向 |
|
||||
|------|-------------|
|
||||
| 被访人查不到/无 floorList/最终 floor 为空 | `76260531`(及 message 中 `getMessage` 文案) |
|
||||
| 与租户 `allowZoneIds` 交后无楼层 | `76260532` |
|
||||
| 其它未预期异常 | `76260530` 包装为 `ServiceException` |
|
||||
| 图库 `batchBind` 失败 | 透传 `bindResult` 的 code/message |
|
||||
|
||||
**日志关键词**:`根据被访人添加访客派梯权限`;`租户访客楼层策略求交`;`访客添加派梯权限`;`远程调用绑定人员图库`。
|
||||
|
||||
---
|
||||
|
||||
## 7. 本文档在仓库中的位置
|
||||
|
||||
- 与 [INDEX.md](INDEX.md) 其它分册互补:本文件专门 **深挖访客在电梯服务中的业务闭环**;部署与接口清单仍以运行环境及标准访客系统文档为准。
|
||||
|
||||
---
|
||||
|
||||
## 8. 关键源码索引
|
||||
|
||||
| 说明 | 路径(相对于 `maven-cw-elevator-application/…` 下模块) |
|
||||
|------|--------------------------------------------------------|
|
||||
| 入口 Controller | `cw-elevator-application-web/.../person/controller/AcsPersonController.java` |
|
||||
| 派梯实现 | `cw-elevator-application-service/.../person/impl/PersonRuleServiceImpl.java` `addVisitor` |
|
||||
| 入参 | `.../person/param/AcsPersonAddVisitorParam.java` |
|
||||
| 策略 DTO/DAO | `cw-elevator-application-data/.../person/dto/TenantVisitorFloorPolicyDto.java`、`TenantVisitorFloorPolicyDao` |
|
||||
| 记录打标 | `.../record/impl/AcsElevatorRecordServiceImpl.java` `add` |
|
||||
|
||||
最后更新以当前工作区代码为准;若你方将「访客注册」**唯一定义**为 **HTTP `/elevator/person/add/visitor`** 这一条,可忽略第 4 节,仅将第 3 节作为上线评审主材料。
|
||||
@@ -0,0 +1,55 @@
|
||||
# `cw-elevator-application-service` 业务逻辑文档总索引
|
||||
|
||||
本目录以 **本 Maven 模块源码根**(`src/main/java/cn/cloudwalk/elevator`)为范围,对电梯应用 **业务编排层** 作多维度说明:分域业务、接口级能力、用例/时序/流程等图(Mermaid)及与外部系统的协作关系。
|
||||
|
||||
| 元数据 | 说明 |
|
||||
|--------|------|
|
||||
| 模块路径 | `maven-cw-elevator-application/cw-elevator-application-service` |
|
||||
| 文档根 | 本目录 `.../cw-elevator-application-service/docs/` |
|
||||
| 源码包根 | `cn.cloudwalk.elevator` |
|
||||
| 产出形态 | Markdown + Mermaid(可用支持 Mermaid 的 IDE、Git 站点或 [mermaid.live](https://mermaid.live) 渲染) |
|
||||
|
||||
## 与仓库级文档的关系
|
||||
|
||||
- 全仓约定、走查、接口不变等说明见仓库根下 **`../../../docs/`**(相对本文件)。
|
||||
- 本目录专注 **本 service 模块内** 类职责与业务流程梳理,不替代对外 API 合同文档。
|
||||
|
||||
## 分册导航
|
||||
|
||||
| 文档 | 内容摘要 |
|
||||
|------|----------|
|
||||
| [00-overview.md](00-overview.md) | 模块定位、包结构、领域全景、组件依赖图 |
|
||||
| [01-device-and-task.md](01-device-and-task.md) | 设备 CRUD、绑定楼层/人员、设备任务与并行推进 |
|
||||
| [02-passrule-and-person.md](02-passrule-and-person.md) | 通行规则、图库规则引用、人员规则、人员管理 |
|
||||
| [03-record-recognition.md](03-record-recognition.md) | 电梯通行记录、识别记录、图库文件、分析统计 |
|
||||
| [04-mqtt-visitor-event.md](04-mqtt-visitor-event.md) | MQTT 推送、访客查询差异、域事件 `VisitorRecordPushEvent` |
|
||||
| [05-zone-code.md](05-zone-code.md) | 区域树/分页、电梯区域编码 |
|
||||
| [06-export-download-storage.md](06-export-download-storage.md) | 异步导出、下载中心、分片存储 |
|
||||
| [07-cross-cutting.md](07-cross-cutting.md) | 缓存、公共应用 ID、基类、空接口等横切项 |
|
||||
| [08-visitor-registration-and-elevator-auth.md](08-visitor-registration-and-elevator-auth.md) | **访客:登记/授权边界、派梯 `add/visitor` 全链路、记录打标、时序/活动图** |
|
||||
|
||||
## 包 → 主入口(速查)
|
||||
|
||||
| 包路径 | 主要 `*Service` 接口 / 类 |
|
||||
|--------|---------------------------|
|
||||
| `device` | `AcsElevatorDeviceService`、`AcsDeviceTaskService` |
|
||||
| `device/setting` | `AcsDeviceSettingService`、`AcsDeviceImageStoreAppBindService` |
|
||||
| `passrule` | `AcsPassRuleService`、`ImageRuleRefService` |
|
||||
| `person` | `AcsPersonService`、`PersonRuleService` |
|
||||
| `record` | `AcsElevatorRecordService`、`AcsRecogRecordService`、`PersonFileService`、`SendRecordTimeService`(空) |
|
||||
| `zone` | `ZoneService` |
|
||||
| `codeElevatorArea` | `AcsElevatorCodeService` |
|
||||
| `mqtt` | `MqttService` |
|
||||
| `export` | `AcsAbstractExportAsyncService` 子类如 `ElevatorDeviceExportService` |
|
||||
| `downloadcenter` | `AcsDownloadCenterService` |
|
||||
| `storage` | `AcsFileStorageService` |
|
||||
| `common` | `AcsApplicationService` |
|
||||
| `cacheable` | `AcsAreaTreeCacheableService`(非 interface,为 `@Service` 包装) |
|
||||
|
||||
## 图例说明
|
||||
|
||||
- **用例图**:采用 Mermaid `flowchart` / `C4` 简图表达参与者与用例分箱;需要标准 UML 用例图时可自工具导出。
|
||||
- **时序图**:`sequenceDiagram` 表示一次调用链。
|
||||
- **活动/状态**:`flowchart TB` 或 `stateDiagram-v2` 表示分支与状态。
|
||||
|
||||
最后更新与源码版本以当前工作区 `cw-elevator-application-service` 反编译/还原代码为准;若与线上运行版本不一致,以发布制品为准。
|
||||
+44
-44
@@ -1,44 +1,44 @@
|
||||
package cn.cloudwalk.client.davinci.portal.file.param.part;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/** 分片追加参数(与存储层 append 调用约定一致)。 */
|
||||
public class FilePartAppendParam<T> implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private String filePath;
|
||||
private Integer partNumber;
|
||||
private String uploadId;
|
||||
private T content;
|
||||
|
||||
public String getFilePath() {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
public void setFilePath(String filePath) {
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
public Integer getPartNumber() {
|
||||
return partNumber;
|
||||
}
|
||||
|
||||
public void setPartNumber(Integer partNumber) {
|
||||
this.partNumber = partNumber;
|
||||
}
|
||||
|
||||
public String getUploadId() {
|
||||
return uploadId;
|
||||
}
|
||||
|
||||
public void setUploadId(String uploadId) {
|
||||
this.uploadId = uploadId;
|
||||
}
|
||||
|
||||
public T getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(T content) {
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
package cn.cloudwalk.client.davinci.portal.file.param.part;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/** 分片追加参数(与存储层 append 调用约定一致)。 */
|
||||
public class FilePartAppendParam<T> implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private String filePath;
|
||||
private Integer partNumber;
|
||||
private String uploadId;
|
||||
private T content;
|
||||
|
||||
public String getFilePath() {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
public void setFilePath(String filePath) {
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
public Integer getPartNumber() {
|
||||
return partNumber;
|
||||
}
|
||||
|
||||
public void setPartNumber(Integer partNumber) {
|
||||
this.partNumber = partNumber;
|
||||
}
|
||||
|
||||
public String getUploadId() {
|
||||
return uploadId;
|
||||
}
|
||||
|
||||
public void setUploadId(String uploadId) {
|
||||
this.uploadId = uploadId;
|
||||
}
|
||||
|
||||
public T getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(T content) {
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
|
||||
+44
-44
@@ -1,44 +1,44 @@
|
||||
package cn.cloudwalk.client.davinci.portal.file.param.part;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/** 分片结束参数(与 PartFinishDTO 字段对齐)。 */
|
||||
public class FilePartFinishParam implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private String uploadId;
|
||||
private Long fileSize;
|
||||
private String filePath;
|
||||
private Integer returnType;
|
||||
|
||||
public String getUploadId() {
|
||||
return uploadId;
|
||||
}
|
||||
|
||||
public void setUploadId(String uploadId) {
|
||||
this.uploadId = uploadId;
|
||||
}
|
||||
|
||||
public Long getFileSize() {
|
||||
return fileSize;
|
||||
}
|
||||
|
||||
public void setFileSize(Long fileSize) {
|
||||
this.fileSize = fileSize;
|
||||
}
|
||||
|
||||
public String getFilePath() {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
public void setFilePath(String filePath) {
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
public Integer getReturnType() {
|
||||
return returnType;
|
||||
}
|
||||
|
||||
public void setReturnType(Integer returnType) {
|
||||
this.returnType = returnType;
|
||||
}
|
||||
}
|
||||
package cn.cloudwalk.client.davinci.portal.file.param.part;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/** 分片结束参数(与 PartFinishDTO 字段对齐)。 */
|
||||
public class FilePartFinishParam implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private String uploadId;
|
||||
private Long fileSize;
|
||||
private String filePath;
|
||||
private Integer returnType;
|
||||
|
||||
public String getUploadId() {
|
||||
return uploadId;
|
||||
}
|
||||
|
||||
public void setUploadId(String uploadId) {
|
||||
this.uploadId = uploadId;
|
||||
}
|
||||
|
||||
public Long getFileSize() {
|
||||
return fileSize;
|
||||
}
|
||||
|
||||
public void setFileSize(Long fileSize) {
|
||||
this.fileSize = fileSize;
|
||||
}
|
||||
|
||||
public String getFilePath() {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
public void setFilePath(String filePath) {
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
public Integer getReturnType() {
|
||||
return returnType;
|
||||
}
|
||||
|
||||
public void setReturnType(Integer returnType) {
|
||||
this.returnType = returnType;
|
||||
}
|
||||
}
|
||||
|
||||
+17
-17
@@ -1,17 +1,17 @@
|
||||
package cn.cloudwalk.client.davinci.portal.file.param.part;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/** 分片上传初始化参数(与 PartInitDTO 字段对齐,供 BeanCopy 与业务层使用)。 */
|
||||
public class FilePartInitParam implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private String fileName;
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public void setFileName(String fileName) {
|
||||
this.fileName = fileName;
|
||||
}
|
||||
}
|
||||
package cn.cloudwalk.client.davinci.portal.file.param.part;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/** 分片上传初始化参数(与 PartInitDTO 字段对齐,供 BeanCopy 与业务层使用)。 */
|
||||
public class FilePartInitParam implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private String fileName;
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public void setFileName(String fileName) {
|
||||
this.fileName = fileName;
|
||||
}
|
||||
}
|
||||
|
||||
+26
-26
@@ -1,26 +1,26 @@
|
||||
package cn.cloudwalk.client.davinci.portal.file.result;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/** 分片初始化/追加返回(与 PartInitResultDTO 字段对齐)。 */
|
||||
public class FilePartResult implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private String filePath;
|
||||
private String uploadId;
|
||||
|
||||
public String getFilePath() {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
public void setFilePath(String filePath) {
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
public String getUploadId() {
|
||||
return uploadId;
|
||||
}
|
||||
|
||||
public void setUploadId(String uploadId) {
|
||||
this.uploadId = uploadId;
|
||||
}
|
||||
}
|
||||
package cn.cloudwalk.client.davinci.portal.file.result;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/** 分片初始化/追加返回(与 PartInitResultDTO 字段对齐)。 */
|
||||
public class FilePartResult implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private String filePath;
|
||||
private String uploadId;
|
||||
|
||||
public String getFilePath() {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
public void setFilePath(String filePath) {
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
public String getUploadId() {
|
||||
return uploadId;
|
||||
}
|
||||
|
||||
public void setUploadId(String uploadId) {
|
||||
this.uploadId = uploadId;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -16,8 +16,7 @@ public interface AcsElevatorCodeService {
|
||||
AcsElevatorCodeResultDTO getFirstByParentId(String paramString) throws ServiceException;
|
||||
|
||||
/**
|
||||
* 按区域 ID 批量查询电梯编码,供树形接口一次拉取,避免循环内逐条查询。
|
||||
* 不改变 {@link #get} 语义;入参去重由调用方控制。
|
||||
* 按区域 ID 批量查询电梯编码,供树形接口一次拉取,避免循环内逐条查询。 不改变 {@link #get} 语义;入参去重由调用方控制。
|
||||
*/
|
||||
Map<String, AcsElevatorCodeResultDTO> mapByZoneIds(List<String> zoneIds) throws ServiceException;
|
||||
}
|
||||
|
||||
+3
@@ -10,6 +10,9 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 设备相关服务的抽象基类:提供区域树扁平化、以及为远程调用组装的 {@link CloudwalkCallContext}(含 Feign 线程本地传参)。
|
||||
*/
|
||||
public class AbstractAcsDeviceService extends AbstractCloudwalkService {
|
||||
protected void getAreaMap(List<AreaTreeResult> areaTreeResultList, Map<String, String> areaMap) {
|
||||
for (AreaTreeResult areaTree : areaTreeResultList) {
|
||||
|
||||
+27
-27
@@ -1,27 +1,27 @@
|
||||
package cn.cloudwalk.elevator.common;
|
||||
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
@Configuration
|
||||
public class ElevatorRemoteIoExecutorConfig {
|
||||
@Autowired
|
||||
private ElevatorRemoteIoPoolProperties elevatorRemoteIoPoolProperties;
|
||||
|
||||
@Bean(name = {"elevatorRemoteBoundedExecutor"})
|
||||
public ThreadPoolTaskExecutor elevatorRemoteBoundedExecutor() {
|
||||
ThreadPoolTaskExecutor ex = new ThreadPoolTaskExecutor();
|
||||
ex.setCorePoolSize(this.elevatorRemoteIoPoolProperties.getCorePoolSize());
|
||||
ex.setMaxPoolSize(this.elevatorRemoteIoPoolProperties.getMaxPoolSize());
|
||||
ex.setQueueCapacity(this.elevatorRemoteIoPoolProperties.getQueueCapacity());
|
||||
ex.setKeepAliveSeconds(this.elevatorRemoteIoPoolProperties.getKeepAliveSeconds());
|
||||
ex.setAllowCoreThreadTimeOut(this.elevatorRemoteIoPoolProperties.isAllowCoreThreadTimeOut());
|
||||
ex.setThreadNamePrefix("elevator-remote-io-");
|
||||
ex.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
ex.initialize();
|
||||
return ex;
|
||||
}
|
||||
}
|
||||
package cn.cloudwalk.elevator.common;
|
||||
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
@Configuration
|
||||
public class ElevatorRemoteIoExecutorConfig {
|
||||
@Autowired
|
||||
private ElevatorRemoteIoPoolProperties elevatorRemoteIoPoolProperties;
|
||||
|
||||
@Bean(name = {"elevatorRemoteBoundedExecutor"})
|
||||
public ThreadPoolTaskExecutor elevatorRemoteBoundedExecutor() {
|
||||
ThreadPoolTaskExecutor ex = new ThreadPoolTaskExecutor();
|
||||
ex.setCorePoolSize(this.elevatorRemoteIoPoolProperties.getCorePoolSize());
|
||||
ex.setMaxPoolSize(this.elevatorRemoteIoPoolProperties.getMaxPoolSize());
|
||||
ex.setQueueCapacity(this.elevatorRemoteIoPoolProperties.getQueueCapacity());
|
||||
ex.setKeepAliveSeconds(this.elevatorRemoteIoPoolProperties.getKeepAliveSeconds());
|
||||
ex.setAllowCoreThreadTimeOut(this.elevatorRemoteIoPoolProperties.isAllowCoreThreadTimeOut());
|
||||
ex.setThreadNamePrefix("elevator-remote-io-");
|
||||
ex.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
ex.initialize();
|
||||
return ex;
|
||||
}
|
||||
}
|
||||
|
||||
+55
-55
@@ -1,55 +1,55 @@
|
||||
package cn.cloudwalk.elevator.common;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "ninca.elevator.remote-io.pool")
|
||||
public class ElevatorRemoteIoPoolProperties {
|
||||
/** 约定 §3.2 / §3.3 / §3.4:有界并行建议 4~8,默认 6 */
|
||||
private int corePoolSize = 6;
|
||||
private int maxPoolSize = 6;
|
||||
private int queueCapacity = 512;
|
||||
private int keepAliveSeconds = 60;
|
||||
private boolean allowCoreThreadTimeOut = true;
|
||||
|
||||
public int getCorePoolSize() {
|
||||
return this.corePoolSize;
|
||||
}
|
||||
|
||||
public void setCorePoolSize(int corePoolSize) {
|
||||
this.corePoolSize = corePoolSize;
|
||||
}
|
||||
|
||||
public int getMaxPoolSize() {
|
||||
return this.maxPoolSize;
|
||||
}
|
||||
|
||||
public void setMaxPoolSize(int maxPoolSize) {
|
||||
this.maxPoolSize = maxPoolSize;
|
||||
}
|
||||
|
||||
public int getQueueCapacity() {
|
||||
return this.queueCapacity;
|
||||
}
|
||||
|
||||
public void setQueueCapacity(int queueCapacity) {
|
||||
this.queueCapacity = queueCapacity;
|
||||
}
|
||||
|
||||
public int getKeepAliveSeconds() {
|
||||
return this.keepAliveSeconds;
|
||||
}
|
||||
|
||||
public void setKeepAliveSeconds(int keepAliveSeconds) {
|
||||
this.keepAliveSeconds = keepAliveSeconds;
|
||||
}
|
||||
|
||||
public boolean isAllowCoreThreadTimeOut() {
|
||||
return this.allowCoreThreadTimeOut;
|
||||
}
|
||||
|
||||
public void setAllowCoreThreadTimeOut(boolean allowCoreThreadTimeOut) {
|
||||
this.allowCoreThreadTimeOut = allowCoreThreadTimeOut;
|
||||
}
|
||||
}
|
||||
package cn.cloudwalk.elevator.common;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "ninca.elevator.remote-io.pool")
|
||||
public class ElevatorRemoteIoPoolProperties {
|
||||
/** 约定 §3.2 / §3.3 / §3.4:有界并行建议 4~8,默认 6 */
|
||||
private int corePoolSize = 6;
|
||||
private int maxPoolSize = 6;
|
||||
private int queueCapacity = 512;
|
||||
private int keepAliveSeconds = 60;
|
||||
private boolean allowCoreThreadTimeOut = true;
|
||||
|
||||
public int getCorePoolSize() {
|
||||
return this.corePoolSize;
|
||||
}
|
||||
|
||||
public void setCorePoolSize(int corePoolSize) {
|
||||
this.corePoolSize = corePoolSize;
|
||||
}
|
||||
|
||||
public int getMaxPoolSize() {
|
||||
return this.maxPoolSize;
|
||||
}
|
||||
|
||||
public void setMaxPoolSize(int maxPoolSize) {
|
||||
this.maxPoolSize = maxPoolSize;
|
||||
}
|
||||
|
||||
public int getQueueCapacity() {
|
||||
return this.queueCapacity;
|
||||
}
|
||||
|
||||
public void setQueueCapacity(int queueCapacity) {
|
||||
this.queueCapacity = queueCapacity;
|
||||
}
|
||||
|
||||
public int getKeepAliveSeconds() {
|
||||
return this.keepAliveSeconds;
|
||||
}
|
||||
|
||||
public void setKeepAliveSeconds(int keepAliveSeconds) {
|
||||
this.keepAliveSeconds = keepAliveSeconds;
|
||||
}
|
||||
|
||||
public boolean isAllowCoreThreadTimeOut() {
|
||||
return this.allowCoreThreadTimeOut;
|
||||
}
|
||||
|
||||
public void setAllowCoreThreadTimeOut(boolean allowCoreThreadTimeOut) {
|
||||
this.allowCoreThreadTimeOut = allowCoreThreadTimeOut;
|
||||
}
|
||||
}
|
||||
|
||||
+211
-83
@@ -1,6 +1,7 @@
|
||||
package cn.cloudwalk.elevator.device.impl;
|
||||
|
||||
import cn.cloudwalk.cloud.context.CloudwalkCallContext;
|
||||
import cn.cloudwalk.cloud.exception.DataAccessException;
|
||||
import cn.cloudwalk.cloud.exception.ServiceException;
|
||||
import cn.cloudwalk.cloud.result.CloudwalkResult;
|
||||
import cn.cloudwalk.elevator.common.AbstractAcsDeviceService;
|
||||
@@ -14,23 +15,37 @@ import cn.cloudwalk.elevator.passrule.dto.AcsPassRuleDeleteDto;
|
||||
import cn.cloudwalk.elevator.passrule.dto.AcsPassRuleImageResultDto;
|
||||
import cn.cloudwalk.elevator.passrule.param.AcsPassRuleDeleteParam;
|
||||
import cn.cloudwalk.elevator.passrule.param.AcsPassRuleNewParam;
|
||||
import cn.cloudwalk.elevator.config.FeignThreadLocalUtil;
|
||||
import cn.cloudwalk.elevator.passrule.service.ImageRuleRefService;
|
||||
import cn.cloudwalk.elevator.person.param.AcsPersonAddParam;
|
||||
import cn.cloudwalk.elevator.person.param.AcsPersonDeleteParam;
|
||||
import cn.cloudwalk.elevator.person.service.PersonRuleService;
|
||||
import cn.cloudwalk.elevator.util.CollectionUtils;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import javax.annotation.Resource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* 设备异步任务:推进绑定进度、按楼层增删人员/规则;{@code updateFloors} 在楼层维度使用有界线程池并行远程调用,并按 Future 完成顺序推进
|
||||
* {@code bindDevices},与走查约定 §9 一致。
|
||||
*/
|
||||
@Service
|
||||
public class AcsDeviceTaskServiceImpl extends AbstractAcsDeviceService implements AcsDeviceTaskService {
|
||||
/** 单次并发执行的楼层数上限,与 {@code elevatorRemoteBoundedExecutor} 池容量配合,避免对下游突发压测。 */
|
||||
private static final int UPDATE_FLOORS_FLOOR_PARALLEL = 6;
|
||||
|
||||
@Autowired
|
||||
private PersonRuleService personRuleService;
|
||||
@Autowired
|
||||
@@ -39,107 +54,220 @@ public class AcsDeviceTaskServiceImpl extends AbstractAcsDeviceService implement
|
||||
private AcsDeviceTaskDao acsDeviceTaskDao;
|
||||
@Resource
|
||||
private ImageRuleRefDao imageRuleRefDao;
|
||||
@Autowired
|
||||
@Qualifier("elevatorRemoteBoundedExecutor")
|
||||
private ThreadPoolTaskExecutor elevatorRemoteBoundedExecutor;
|
||||
|
||||
@Async("updateFloorsExecutor")
|
||||
public void updateFloors(AcsRestructureBindingParam param, List<AcsPassRuleImageResultDto> addFloors,
|
||||
List<String> delFloorIds, CloudwalkCallContext context) throws ServiceException {
|
||||
try {
|
||||
if (!CollectionUtils.isEmpty(addFloors)) {
|
||||
for (AcsPassRuleImageResultDto addFloor : addFloors) {
|
||||
AcsDeviceTaskDTO task = this.acsDeviceTaskDao.getById(param.getTaskId());
|
||||
if (task == null) {
|
||||
this.logger.error("updateFloors 任务不存在 taskId={}", param.getTaskId());
|
||||
throw new ServiceException("设备任务不存在");
|
||||
}
|
||||
if (task.getIsStop().intValue() == 0) {
|
||||
if (!ObjectUtils.isEmpty(param.getPersonId())) {
|
||||
AcsPersonAddParam addParam = new AcsPersonAddParam();
|
||||
addParam.setPersonIds(Collections.singletonList(param.getPersonId()));
|
||||
addParam.setParentId(param.getParentId());
|
||||
addParam.setZoneId(addFloor.getZoneId());
|
||||
addParam.setZoneName(addFloor.getZoneName());
|
||||
CloudwalkResult<Boolean> addResult = this.personRuleService.add(addParam, context);
|
||||
requireTaskStepSuccess(addResult, "personRuleService.add");
|
||||
} else {
|
||||
AcsPassRuleNewParam ruleParam = new AcsPassRuleNewParam();
|
||||
ruleParam.setParentId(param.getParentId());
|
||||
ruleParam.setZoneId(addFloor.getZoneId());
|
||||
ruleParam.setZoneName(addFloor.getZoneName());
|
||||
if (!ObjectUtils.isEmpty(param.getLabelId())) {
|
||||
ruleParam.setIncludeLabels(Collections.singletonList(param.getLabelId()));
|
||||
ruleParam.setRuleName(addFloor.getZoneName() + param.getLabelName());
|
||||
}
|
||||
if (!ObjectUtils.isEmpty(param.getOrgId())) {
|
||||
ruleParam.setIncludeOrganizations(Collections.singletonList(param.getOrgId()));
|
||||
ruleParam.setRuleName(addFloor.getZoneName() + param.getOrgName());
|
||||
}
|
||||
CloudwalkResult<Boolean> addRuleResult =
|
||||
this.imageRuleRefService.addOnlyRule(ruleParam, context);
|
||||
requireTaskStepSuccess(addRuleResult, "imageRuleRefService.addOnlyRule");
|
||||
}
|
||||
AcsDeviceTaskAddDto addDto = new AcsDeviceTaskAddDto();
|
||||
addDto.setId(task.getId());
|
||||
addDto.setBindDevices(Integer.valueOf(task.getBindDevices().intValue() + 1));
|
||||
this.acsDeviceTaskDao.updateBingDevices(addDto);
|
||||
}
|
||||
}
|
||||
runAddFloorsInBoundedParallel(param, addFloors, context);
|
||||
}
|
||||
if (!CollectionUtils.isEmpty(delFloorIds)) {
|
||||
List<AcsPassRuleImageResultDto> ruleList = this.imageRuleRefDao.listZoneInfoByIds(delFloorIds);
|
||||
Map<String, String> ruleMap = new HashMap<>();
|
||||
ruleList.forEach(rule -> ruleMap.put(rule.getZoneId(), rule.getZoneName()));
|
||||
for (String delFloorId : delFloorIds) {
|
||||
AcsDeviceTaskDTO task = this.acsDeviceTaskDao.getById(param.getTaskId());
|
||||
if (task == null) {
|
||||
this.logger.error("updateFloors 任务不存在 taskId={}", param.getTaskId());
|
||||
throw new ServiceException("设备任务不存在");
|
||||
}
|
||||
if (task.getIsStop().intValue() == 0) {
|
||||
if (!ObjectUtils.isEmpty(param.getPersonId())) {
|
||||
AcsPersonDeleteParam delParam = new AcsPersonDeleteParam();
|
||||
delParam.setParentId(param.getParentId());
|
||||
delParam.setZoneId(delFloorId);
|
||||
delParam.setPersonIds(Collections.singletonList(param.getPersonId()));
|
||||
CloudwalkResult<Boolean> delResult = this.personRuleService.delete(delParam, context);
|
||||
requireTaskStepSuccess(delResult, "personRuleService.delete");
|
||||
} else {
|
||||
String ruleName = "";
|
||||
if (!ObjectUtils.isEmpty(param.getLabelName())) {
|
||||
ruleName = (String)ruleMap.get(delFloorId) + param.getLabelName();
|
||||
}
|
||||
if (!ObjectUtils.isEmpty(param.getOrgName())) {
|
||||
ruleName = (String)ruleMap.get(delFloorId) + param.getOrgName();
|
||||
}
|
||||
String ruleId = this.imageRuleRefDao.getByRuleName(ruleName, delFloorId);
|
||||
if (!ObjectUtils.isEmpty(ruleId)) {
|
||||
AcsPassRuleDeleteParam deleteParam = new AcsPassRuleDeleteParam();
|
||||
deleteParam.setIds(Collections.singletonList(ruleId));
|
||||
deleteParam.setZoneId(delFloorId);
|
||||
deleteParam.setParentId(param.getParentId());
|
||||
CloudwalkResult<Boolean> delRuleResult =
|
||||
this.imageRuleRefService.delete(deleteParam, context);
|
||||
requireTaskStepSuccess(delRuleResult, "imageRuleRefService.delete");
|
||||
} else {
|
||||
AcsPassRuleDeleteDto dto = new AcsPassRuleDeleteDto();
|
||||
dto.setZoneId(delFloorId);
|
||||
dto.setLabelId(param.getLabelId());
|
||||
dto.setOrgId(param.getOrgId());
|
||||
this.imageRuleRefDao.deleteByOrgAndLabel(dto);
|
||||
}
|
||||
}
|
||||
AcsDeviceTaskAddDto addDto = new AcsDeviceTaskAddDto();
|
||||
addDto.setId(task.getId());
|
||||
addDto.setBindDevices(Integer.valueOf(task.getBindDevices().intValue() + 1));
|
||||
this.acsDeviceTaskDao.updateBingDevices(addDto);
|
||||
}
|
||||
}
|
||||
runDelFloorsInBoundedParallel(param, delFloorIds, ruleMap, context);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
this.logger.error("处理设备任务失败,失败原因:{}", e);
|
||||
if (e instanceof ServiceException) {
|
||||
throw (ServiceException)e;
|
||||
}
|
||||
throw new ServiceException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 约定 §3.5:楼层级有界并行发起远程调用;本方法内按原列表顺序 {@code get()} Future,
|
||||
* 与串行时一致地「每成功一层 → 重读任务行并 BIND_DEVICES+1」。
|
||||
*/
|
||||
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)));
|
||||
}
|
||||
List<Future<Integer>> futures;
|
||||
try {
|
||||
futures = this.elevatorRemoteBoundedExecutor.getThreadPoolExecutor().invokeAll(batch);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new ServiceException("76260540", "updateFloors 被中断");
|
||||
}
|
||||
for (Future<Integer> f : futures) {
|
||||
int inc;
|
||||
try {
|
||||
inc = f.get();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new ServiceException("76260540", "updateFloors 被中断");
|
||||
} catch (ExecutionException e) {
|
||||
Throwable c = e.getCause();
|
||||
if (c instanceof ServiceException) {
|
||||
throw (ServiceException)c;
|
||||
}
|
||||
throw new ServiceException(c != null ? c.getMessage() : e.getMessage());
|
||||
}
|
||||
if (inc > 0) {
|
||||
advanceBindProgressOne(param.getTaskId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void runDelFloorsInBoundedParallel(AcsRestructureBindingParam param, List<String> delFloorIds,
|
||||
Map<String, String> ruleMap, CloudwalkCallContext context) throws ServiceException {
|
||||
for (int i = 0; i < delFloorIds.size(); i += UPDATE_FLOORS_FLOOR_PARALLEL) {
|
||||
int end = Math.min(i + UPDATE_FLOORS_FLOOR_PARALLEL, delFloorIds.size());
|
||||
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)));
|
||||
}
|
||||
List<Future<Integer>> futures;
|
||||
try {
|
||||
futures = this.elevatorRemoteBoundedExecutor.getThreadPoolExecutor().invokeAll(batch);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new ServiceException("76260540", "updateFloors 被中断");
|
||||
}
|
||||
for (Future<Integer> f : futures) {
|
||||
int inc;
|
||||
try {
|
||||
inc = f.get();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new ServiceException("76260540", "updateFloors 被中断");
|
||||
} catch (ExecutionException e) {
|
||||
Throwable c = e.getCause();
|
||||
if (c instanceof ServiceException) {
|
||||
throw (ServiceException)c;
|
||||
}
|
||||
throw new ServiceException(c != null ? c.getMessage() : e.getMessage());
|
||||
}
|
||||
if (inc > 0) {
|
||||
advanceBindProgressOne(param.getTaskId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void advanceBindProgressOne(String taskId) throws ServiceException {
|
||||
AcsDeviceTaskDTO task = this.acsDeviceTaskDao.getById(taskId);
|
||||
if (task == null) {
|
||||
this.logger.error("updateFloors 任务不存在 taskId={}", taskId);
|
||||
throw new ServiceException("设备任务不存在");
|
||||
}
|
||||
AcsDeviceTaskAddDto addDto = new AcsDeviceTaskAddDto();
|
||||
addDto.setId(task.getId());
|
||||
addDto.setBindDevices(Integer.valueOf(task.getBindDevices().intValue() + 1));
|
||||
this.acsDeviceTaskDao.updateBingDevices(addDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 1 本层已执行远程步骤且应推进 bind 计数;0 任务已停止跳过
|
||||
*/
|
||||
private int addOneFloorStep(AcsPassRuleImageResultDto addFloor, AcsRestructureBindingParam param,
|
||||
CloudwalkCallContext context) throws ServiceException {
|
||||
AcsDeviceTaskDTO task = this.acsDeviceTaskDao.getById(param.getTaskId());
|
||||
if (task == null) {
|
||||
this.logger.error("updateFloors 任务不存在 taskId={}", param.getTaskId());
|
||||
throw new ServiceException("设备任务不存在");
|
||||
}
|
||||
if (task.getIsStop().intValue() != 0) {
|
||||
return 0;
|
||||
}
|
||||
if (!ObjectUtils.isEmpty(param.getPersonId())) {
|
||||
AcsPersonAddParam addParam = new AcsPersonAddParam();
|
||||
addParam.setPersonIds(Collections.singletonList(param.getPersonId()));
|
||||
addParam.setParentId(param.getParentId());
|
||||
addParam.setZoneId(addFloor.getZoneId());
|
||||
addParam.setZoneName(addFloor.getZoneName());
|
||||
CloudwalkResult<Boolean> addResult = this.personRuleService.add(addParam, context);
|
||||
requireTaskStepSuccess(addResult, "personRuleService.add");
|
||||
} else {
|
||||
AcsPassRuleNewParam ruleParam = new AcsPassRuleNewParam();
|
||||
ruleParam.setParentId(param.getParentId());
|
||||
ruleParam.setZoneId(addFloor.getZoneId());
|
||||
ruleParam.setZoneName(addFloor.getZoneName());
|
||||
if (!ObjectUtils.isEmpty(param.getLabelId())) {
|
||||
ruleParam.setIncludeLabels(Collections.singletonList(param.getLabelId()));
|
||||
ruleParam.setRuleName(addFloor.getZoneName() + param.getLabelName());
|
||||
}
|
||||
if (!ObjectUtils.isEmpty(param.getOrgId())) {
|
||||
ruleParam.setIncludeOrganizations(Collections.singletonList(param.getOrgId()));
|
||||
ruleParam.setRuleName(addFloor.getZoneName() + param.getOrgName());
|
||||
}
|
||||
CloudwalkResult<Boolean> addRuleResult = this.imageRuleRefService.addOnlyRule(ruleParam, context);
|
||||
requireTaskStepSuccess(addRuleResult, "imageRuleRefService.addOnlyRule");
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
private int delOneFloorStep(String delFloorId, AcsRestructureBindingParam param, Map<String, String> ruleMap,
|
||||
CloudwalkCallContext context) throws ServiceException {
|
||||
AcsDeviceTaskDTO task = this.acsDeviceTaskDao.getById(param.getTaskId());
|
||||
if (task == null) {
|
||||
this.logger.error("updateFloors 任务不存在 taskId={}", param.getTaskId());
|
||||
throw new ServiceException("设备任务不存在");
|
||||
}
|
||||
if (task.getIsStop().intValue() != 0) {
|
||||
return 0;
|
||||
}
|
||||
if (!ObjectUtils.isEmpty(param.getPersonId())) {
|
||||
AcsPersonDeleteParam delParam = new AcsPersonDeleteParam();
|
||||
delParam.setParentId(param.getParentId());
|
||||
delParam.setZoneId(delFloorId);
|
||||
delParam.setPersonIds(Collections.singletonList(param.getPersonId()));
|
||||
CloudwalkResult<Boolean> delResult = this.personRuleService.delete(delParam, context);
|
||||
requireTaskStepSuccess(delResult, "personRuleService.delete");
|
||||
} else {
|
||||
String baseName = ruleMap.getOrDefault(delFloorId, "");
|
||||
String ruleName = "";
|
||||
if (!ObjectUtils.isEmpty(param.getLabelName())) {
|
||||
ruleName = baseName + param.getLabelName();
|
||||
}
|
||||
if (!ObjectUtils.isEmpty(param.getOrgName())) {
|
||||
ruleName = baseName + param.getOrgName();
|
||||
}
|
||||
String ruleId;
|
||||
try {
|
||||
ruleId = this.imageRuleRefDao.getByRuleName(ruleName, delFloorId);
|
||||
} catch (DataAccessException e) {
|
||||
this.logger.error("updateFloors getByRuleName 失败 delFloorId={} {}", delFloorId, e.getMessage());
|
||||
throw new ServiceException("76260540", e.getMessage());
|
||||
}
|
||||
if (!ObjectUtils.isEmpty(ruleId)) {
|
||||
AcsPassRuleDeleteParam deleteParam = new AcsPassRuleDeleteParam();
|
||||
deleteParam.setIds(Collections.singletonList(ruleId));
|
||||
deleteParam.setZoneId(delFloorId);
|
||||
deleteParam.setParentId(param.getParentId());
|
||||
CloudwalkResult<Boolean> delRuleResult = this.imageRuleRefService.delete(deleteParam, context);
|
||||
requireTaskStepSuccess(delRuleResult, "imageRuleRefService.delete");
|
||||
} else {
|
||||
AcsPassRuleDeleteDto dto = new AcsPassRuleDeleteDto();
|
||||
dto.setZoneId(delFloorId);
|
||||
dto.setLabelId(param.getLabelId());
|
||||
dto.setOrgId(param.getOrgId());
|
||||
try {
|
||||
this.imageRuleRefDao.deleteByOrgAndLabel(dto);
|
||||
} catch (DataAccessException e) {
|
||||
this.logger.error("updateFloors deleteByOrgAndLabel 失败 delFloorId={} {}", delFloorId, e.getMessage());
|
||||
throw new ServiceException("76260540", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 约定 §2.2:异步任务内对业务服务返回的 {@link CloudwalkResult} 须校验成功后再推进进度(避免失败仍递增 bindDevices)。
|
||||
*/
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* 设备域服务层:电梯设备查询/编辑、设备任务(含楼层变更)、设备侧设置与图库应用绑定等编排。
|
||||
* <p>
|
||||
* 同包名在 data 模块中承担 DAO/Mapper;此处仅放接口、入参出参与 {@code impl} 实现,表结构见 data 包说明。
|
||||
*/
|
||||
package cn.cloudwalk.elevator.device;
|
||||
+9
@@ -8,9 +8,18 @@ import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
/**
|
||||
* 设备第三方 MQTT 发布入口:向指定 topic 推送 JSON 载荷(由 {@code /mqtt/publish} 落到底层 broker)。
|
||||
*/
|
||||
@FeignClient(name = "${feign.mqtt.name:cloudwalk-device-thirdparty}", path = "/mqtt",
|
||||
fallback = MqttFeignClientFallback.class)
|
||||
public interface MqttFeignClient {
|
||||
/**
|
||||
* 发布一条 MQTT 消息。
|
||||
*
|
||||
* @param paramMqttSendMessageParam topic 与 body(通常为业务侧 JSON 字符串)
|
||||
* @return 是否投递成功,由下游服务定义成功语义
|
||||
*/
|
||||
@RequestMapping(value = {"/publish"}, method = {RequestMethod.POST})
|
||||
CloudwalkResult<Boolean> publish(MqttSendMessageParam paramMqttSendMessageParam) throws ServiceException;
|
||||
}
|
||||
|
||||
+8
@@ -6,8 +6,16 @@ import cn.cloudwalk.elevator.mqtt.client.MqttFeignClient;
|
||||
import cn.cloudwalk.elevator.mqtt.param.MqttSendMessageParam;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* {@link MqttFeignClient} 熔断/降级:调用失败时抛出运行时异常,促使上层按失败处理(不伪造成功投递)。
|
||||
*/
|
||||
@Component
|
||||
public class MqttFeignClientFallback implements MqttFeignClient {
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @implSpec 不返回降级业务结果,直接抛错以暴露下游不可用
|
||||
*/
|
||||
@Override
|
||||
public CloudwalkResult<Boolean> publish(MqttSendMessageParam param) throws ServiceException {
|
||||
throw new RuntimeException("mqtt发送数据失败");
|
||||
}
|
||||
|
||||
+14
-2
@@ -23,10 +23,16 @@ import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* {@link MqttService} 实现:先短暂休眠以便识别明细入库,再按通行流水查人名、访客标签,向 {@code businessId + ELEVATOR_RECORD_SUFFIX} topic 推送 JSON。
|
||||
*/
|
||||
@Component
|
||||
public class MqttServiceImpl extends AbstractAcsDeviceService implements MqttService {
|
||||
/** 人员标签中表示「访客」的编码,与识别记录里 {@code personLabelIds} 包含关系判断一致。 */
|
||||
private static final String VISITOR_LABEL_CODE = "1";
|
||||
/** 与 {@link #VISITOR_LABEL_CODE} 对应的展示名,当前实现未参与逻辑,仅作文档对齐。 */
|
||||
private static final String VISITOR_LABEL_NAME = "访客";
|
||||
/** MQTT topic 后缀,与 {@code businessId} 拼接为完整 topic。 */
|
||||
private static final String ELEVATOR_RECORD_SUFFIX = "_elevator_record";
|
||||
@Qualifier("cn.cloudwalk.elevator.mqtt.client.MqttFeignClient")
|
||||
@Resource
|
||||
@@ -34,6 +40,12 @@ public class MqttServiceImpl extends AbstractAcsDeviceService implements MqttSer
|
||||
@Resource
|
||||
private AcsRecogRecordDao acsRecogRecordDao;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* 异常在方法内记录日志后吞掉(除中断转 {@link ServiceException}),避免异步线程因下游偶发错误反复未捕获终止。
|
||||
*/
|
||||
@Override
|
||||
@Async
|
||||
public void sendInfoToOne(AcsElevatorRecordAddDTO addDTO) throws ServiceException {
|
||||
this.logger.info("防止人员识别记录未入库即开始推送消息,休眠10秒");
|
||||
@@ -56,11 +68,11 @@ public class MqttServiceImpl extends AbstractAcsDeviceService implements MqttSer
|
||||
acsElevatorRecordMqttParam.setOpenDoorId(addDTO.getId());
|
||||
acsElevatorRecordMqttParam.setPersonName(acsRecogRecordResultDTO.getPersonName());
|
||||
if (StringUtils.isNotBlank(acsRecogRecordResultDTO.getPersonLabelIds())
|
||||
&& acsRecogRecordResultDTO.getPersonLabelIds().contains("1")) {
|
||||
&& acsRecogRecordResultDTO.getPersonLabelIds().contains(VISITOR_LABEL_CODE)) {
|
||||
acsElevatorRecordMqttParam.setIsVisitor(Boolean.TRUE);
|
||||
}
|
||||
CloudwalkResult<Boolean> publish = this.mqttFeignClient
|
||||
.publish(MqttSendMessageParam.builder().topic(addDTO.getBusinessId() + "_elevator_record")
|
||||
.publish(MqttSendMessageParam.builder().topic(addDTO.getBusinessId() + ELEVATOR_RECORD_SUFFIX)
|
||||
.data(JSON.toJSONString(acsElevatorRecordMqttParam)).build());
|
||||
if (publish.isSuccess()) {
|
||||
this.logger.info("推送数据成功!!!,数据,{}", JSON.toJSONString(acsElevatorRecordMqttParam));
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* 电梯识别记录 MQTT 推送:经 {@code cloudwalk-device-thirdparty} 等下游将消息发布到业务 topic,供大屏/第三方订阅。
|
||||
* <p>
|
||||
* 与 {@code record} 域协作:在人员识别记录落库后异步组装载荷并调用发布接口。
|
||||
*/
|
||||
package cn.cloudwalk.elevator.mqtt;
|
||||
+3
@@ -1,5 +1,8 @@
|
||||
package cn.cloudwalk.elevator.mqtt.param;
|
||||
|
||||
/**
|
||||
* 推送到 MQTT 的电梯记录载荷:在 {@link cn.cloudwalk.elevator.record.dto.AcsElevatorRecordAddDTO} 基础上补全人名、开门流水 id、是否访客等,序列化为 {@code data} 字段内容。
|
||||
*/
|
||||
public class AcsElevatorRecordMqttParam {
|
||||
private String openDoorId;
|
||||
private String openDoorType;
|
||||
|
||||
+3
@@ -2,6 +2,9 @@ package cn.cloudwalk.elevator.mqtt.param;
|
||||
|
||||
import java.beans.ConstructorProperties;
|
||||
|
||||
/**
|
||||
* Feign 发布请求体:MQTT topic 与一条 JSON 字符串形式的业务数据。
|
||||
*/
|
||||
public class MqttSendMessageParam {
|
||||
private String topic;
|
||||
private String data;
|
||||
|
||||
+11
@@ -3,6 +3,17 @@ package cn.cloudwalk.elevator.mqtt.service;
|
||||
import cn.cloudwalk.cloud.exception.ServiceException;
|
||||
import cn.cloudwalk.elevator.record.dto.AcsElevatorRecordAddDTO;
|
||||
|
||||
/**
|
||||
* 将单条电梯通行/识别结果异步推送到 MQTT(供订阅方如大屏展示),与落库存在时序上的缓冲(实现内会短暂休眠后查库补全人名等)。
|
||||
*/
|
||||
public interface MqttService {
|
||||
/**
|
||||
* 按一条待写入或已关联的识别记录,组装业务 topic 与 JSON 后发起 {@link cn.cloudwalk.elevator.mqtt.client.MqttFeignClient#publish}。
|
||||
* <p>
|
||||
* 异步方法:调用方不应依赖其完成时刻做强一致逻辑。
|
||||
*
|
||||
* @param paramAcsElevatorRecordAddDTO 电梯识别记录主数据(需含 businessId、recognition 关联键等,供拼 topic 与反查人员)
|
||||
* @throws ServiceException 睡眠被中断等不可恢复情况
|
||||
*/
|
||||
void sendInfoToOne(AcsElevatorRecordAddDTO paramAcsElevatorRecordAddDTO) throws ServiceException;
|
||||
}
|
||||
|
||||
+7
@@ -0,0 +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;
|
||||
+3
-4
@@ -126,7 +126,7 @@ public class AcsPassRuleServiceImpl extends AbstractAcsPassService implements Ac
|
||||
}
|
||||
int floorCount = passRuleResults.size();
|
||||
long[] personTotals = new long[floorCount];
|
||||
for (int i = 0; i < floorCount; ) {
|
||||
for (int i = 0; i < floorCount;) {
|
||||
int end = Math.min(i + REMOTE_IO_PARALLEL, floorCount);
|
||||
List<Callable<Void>> batch = new ArrayList<>();
|
||||
for (int j = i; j < end; j++) {
|
||||
@@ -237,7 +237,7 @@ public class AcsPassRuleServiceImpl extends AbstractAcsPassService implements Ac
|
||||
this.acsDeviceImageStoreAppBindService.bindAppImageStoreDevice(appBindParam, context);
|
||||
if (!CollectionUtils.isEmpty(deviceList)) {
|
||||
final String newImageStoreId = (String)imageStoreId.getData();
|
||||
for (int i = 0; i < deviceList.size(); ) {
|
||||
for (int i = 0; i < deviceList.size();) {
|
||||
int end = Math.min(i + REMOTE_IO_PARALLEL, deviceList.size());
|
||||
List<Callable<Void>> bindBatch = new ArrayList<>();
|
||||
for (int j = i; j < end; j++) {
|
||||
@@ -556,8 +556,7 @@ public class AcsPassRuleServiceImpl extends AbstractAcsPassService implements Ac
|
||||
ImageStoreDelParam delParam = new ImageStoreDelParam();
|
||||
delParam.setId(imageStoreIdValue);
|
||||
delParam.setBusinessId(context.getCompany().getCompanyId());
|
||||
this.logger.info("回滚删除图库开始,delParam={},context={}", JSONObject.toJSON(delParam),
|
||||
JSONObject.toJSON(context));
|
||||
this.logger.info("回滚删除图库开始,delParam={},context={}", JSONObject.toJSON(delParam), JSONObject.toJSON(context));
|
||||
CloudwalkResult<Boolean> deleteResult = this.imageStoreService.delete(delParam, context);
|
||||
this.logger.info("删除图库:图库id={},结果:{}", imageStoreIdValue, deleteResult.getMessage());
|
||||
}
|
||||
|
||||
+2
-2
@@ -678,8 +678,8 @@ public class ImageRuleRefServiceImpl extends AbstractAcsPassService implements I
|
||||
}
|
||||
|
||||
/**
|
||||
* 一次 {@link LabelService#getAll} 建 id→详情索引,避免规则详情/分页组装时对 {@link LabelService#detail} 的 N 次远程调用。
|
||||
* 若某 id 不在全量列表中(数据不同步),回退单次 detail。
|
||||
* 一次 {@link LabelService#getAll} 建 id→详情索引,避免规则详情/分页组装时对 {@link LabelService#detail} 的 N 次远程调用。 若某 id
|
||||
* 不在全量列表中(数据不同步),回退单次 detail。
|
||||
*/
|
||||
private Map<String, LabelDetailResult> loadLabelDetailMap(CloudwalkCallContext context) throws ServiceException {
|
||||
CloudwalkResult<List<LabelDetailResult>> all = this.labelService.getAll(new LabelQueryParam(), context);
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* 通行/人员规则与图库规则引用:规则增删、与区域/标签/组织维度的组合,及与设备任务的协作。
|
||||
* <p>
|
||||
* 与 {@code person} 包在“按人下发”和“按规则下发”两种路径上常共同出现在设备任务流中。
|
||||
*/
|
||||
package cn.cloudwalk.elevator.passrule;
|
||||
+1
-2
@@ -208,8 +208,7 @@ public class AcsPersonServiceImpl extends AbstractAcsPassService implements AcsP
|
||||
Throwable c = e.getCause();
|
||||
if (c instanceof ServiceException) {
|
||||
ServiceException se = (ServiceException)c;
|
||||
return CloudwalkResult.fail(
|
||||
se.getCode() != null ? se.getCode() : "76260407",
|
||||
return CloudwalkResult.fail(se.getCode() != null ? se.getCode() : "76260407",
|
||||
getMessage("76260407") + " " + se.getMessage());
|
||||
}
|
||||
return CloudwalkResult.fail("76260407",
|
||||
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* 人员与人员-规则服务:人员增删、与区域/父级人员关系、及与设备侧同步相关的编排。
|
||||
*/
|
||||
package cn.cloudwalk.elevator.person;
|
||||
+2
-2
@@ -44,8 +44,8 @@ public class AcsPersonAddVisitorParam implements Serializable {
|
||||
return false;
|
||||
}
|
||||
return Objects.equals(visitorId, other.visitorId) && Objects.equals(personId, other.personId)
|
||||
&& Objects.equals(begVisitorTime, other.begVisitorTime) && Objects.equals(endVisitorTime, other.endVisitorTime)
|
||||
&& Objects.equals(floorIds, other.floorIds);
|
||||
&& Objects.equals(begVisitorTime, other.begVisitorTime)
|
||||
&& Objects.equals(endVisitorTime, other.endVisitorTime) && Objects.equals(floorIds, other.floorIds);
|
||||
}
|
||||
|
||||
protected boolean canEqual(Object other) {
|
||||
|
||||
+38
@@ -86,8 +86,23 @@ import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
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。
|
||||
* <p>
|
||||
* {@link MqttService} 为识别结果 MQTT 推送能力,本类已注入但<strong>未直接调用</strong>;若需与入库联动,可在监听
|
||||
* {@link cn.cloudwalk.elevator.record.result.VisitorRecordPushEvent} 的处理器中显式
|
||||
* {@link cn.cloudwalk.elevator.mqtt.service.MqttService#sendInfoToOne}(或其它入口接线)。
|
||||
*/
|
||||
@Service
|
||||
public class AcsElevatorRecordServiceImpl extends AbstractAcsDeviceService implements AcsElevatorRecordService {
|
||||
/**
|
||||
* 标准访客/三方服务所在主机(IP 或 host:port),与 {@link #combineAuthClientURI} 拼成完整 URL。
|
||||
*/
|
||||
@Value("${ninca-crk-std.ip}")
|
||||
private String nincaCrkStd;
|
||||
@Autowired
|
||||
@@ -104,6 +119,9 @@ public class AcsElevatorRecordServiceImpl extends AbstractAcsDeviceService imple
|
||||
private DeviceDistrictService deviceDistrictService;
|
||||
@Resource
|
||||
private AcsAreaTreeCacheableService acsAreaTreeCacheableService;
|
||||
/**
|
||||
* 识别记录 MQTT 异步推送服务;本类当前不调用,保留供与 {@link #sendRecordEvent} 或外部监听协同接入。
|
||||
*/
|
||||
@Resource
|
||||
private MqttService mqttService;
|
||||
@Resource
|
||||
@@ -118,6 +136,9 @@ public class AcsElevatorRecordServiceImpl extends AbstractAcsDeviceService imple
|
||||
protected static final int CACHE_EXPIRE_TIME = 8;
|
||||
private static final String ELEVATOR_RECORD_SUFFIX = "elevator_record";
|
||||
|
||||
/**
|
||||
* 分页查询开门记录明细:组装区域/片区/人员等展示字段,时间跨度超过一年直接拒绝。
|
||||
*/
|
||||
@CloudwalkParamsValidate(argsIndexs = {0, 1})
|
||||
public CloudwalkResult<CloudwalkPageAble<AcsElevatorRecordResult>> openRecord(AcsElevatorRecordDetailParam param,
|
||||
CloudwalkPageInfo pageInfo, CloudwalkCallContext cloudwalkContext) throws ServiceException {
|
||||
@@ -210,6 +231,7 @@ public class AcsElevatorRecordServiceImpl extends AbstractAcsDeviceService imple
|
||||
}
|
||||
}
|
||||
|
||||
/** 从记录列表中按片区或区域去重后收集 id,供批量拉取名称。 */
|
||||
private List<String> acsElevatorRecordListDupRemove(List<AcsElevatorRecordDetailQueryResultDTO> list, String flag) {
|
||||
List<String> tempList = new ArrayList<>();
|
||||
if (flag.equals("district")) {
|
||||
@@ -227,6 +249,16 @@ public class AcsElevatorRecordServiceImpl extends AbstractAcsDeviceService imple
|
||||
return tempList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增单条电梯通行/识别结果:写库并发布 {@link cn.cloudwalk.elevator.record.result.VisitorRecordPushEvent} 域事件。
|
||||
* <p>
|
||||
* 在持久化前通过 {@link cn.cloudwalk.elevator.util.RestTemplateUtil#post} 调访客「three」线查询,若命中则置访客并回填被访人
|
||||
* {@code interviewee};再拉人员详情补工号/组织。此处未调用 {@link cn.cloudwalk.elevator.mqtt.service.MqttService}。
|
||||
*
|
||||
* @param param 设备侧识别结果、图片、操作者等
|
||||
* @param context 租户与调用方上下文
|
||||
* @return 落库与事件是否成功
|
||||
*/
|
||||
public CloudwalkResult<Boolean> add(AcsElevatorRecordAddParam param, CloudwalkCallContext context)
|
||||
throws ServiceException {
|
||||
try {
|
||||
@@ -393,6 +425,9 @@ public class AcsElevatorRecordServiceImpl extends AbstractAcsDeviceService imple
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将入库后的记录复制为域事件并发布,供图库/订阅方等异步入队处理(可能间接触发其它推送,与 MQTT 无强绑定)。
|
||||
*/
|
||||
private void sendRecordEvent(AcsElevatorRecordAddDTO addDTO) {
|
||||
VisitorRecordPushEvent event =
|
||||
(VisitorRecordPushEvent)BeanCopyUtils.copyProperties(addDTO, VisitorRecordPushEvent.class);
|
||||
@@ -401,6 +436,9 @@ public class AcsElevatorRecordServiceImpl extends AbstractAcsDeviceService imple
|
||||
this.cloudwalkEventManager.publish((BaseEvent)event);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造访问 {@code ninca-crk-std} 的绝对 URI,{@code api} 为不含前导斜杠的 path 段(如 {@code intelligent/three/...})。
|
||||
*/
|
||||
private URI combineAuthClientURI(String api, @Nullable MultiValueMap<String, String> params) {
|
||||
return UriComponentsBuilder.fromUriString("http://" + this.nincaCrkStd).path(api).queryParams(params).build()
|
||||
.toUri();
|
||||
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* 电梯通行/识别记录业务:查询、落库、统计,并与访客中心(HTTP「three」线或 Feign「intelligent」线)、域事件、可选 MQTT 推送协同。
|
||||
*/
|
||||
package cn.cloudwalk.elevator.record;
|
||||
+4
@@ -2,6 +2,10 @@ package cn.cloudwalk.elevator.record.result;
|
||||
|
||||
import cn.cloudwalk.cwos.client.event.event.CustomEvent;
|
||||
|
||||
/**
|
||||
* 通行记录入库后发布的域事件,{@link #getTopic()} 固定为 {@code VISITOR_RECORD_TOPIC};监听方可据此做下游同步,与
|
||||
* {@link cn.cloudwalk.elevator.mqtt.service.MqttService} 无强制耦合。
|
||||
*/
|
||||
public class VisitorRecordPushEvent extends CustomEvent {
|
||||
private String businessId;
|
||||
private String deviceId;
|
||||
|
||||
+9
@@ -9,9 +9,18 @@ import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
/**
|
||||
* 标准访客服务:按访客主键分页查询其档案与到访/识别记录列表(路径 {@code /intelligent/visitor/record/query})。
|
||||
*/
|
||||
@FeignClient(name = "${feign.ninca-crk-std.name:ninca-crk-std}", path = "/intelligent/visitor/record",
|
||||
fallback = VisitorFeignClientFallback.class)
|
||||
public interface VisitorFeignClient {
|
||||
/**
|
||||
* 查询某租户下指定访客的详细资料及关联识别记录。
|
||||
*
|
||||
* @param paramVisitorRecordQueryParam 访客 id、业务租户 id 与分页参数
|
||||
* @return 聚合 {@link cn.cloudwalk.elevator.visitor.result.VisitorQueryResult}
|
||||
*/
|
||||
@RequestMapping(value = {"/query"}, method = {RequestMethod.POST})
|
||||
CloudwalkResult<VisitorQueryResult> query(VisitorRecordQueryParam paramVisitorRecordQueryParam)
|
||||
throws ServiceException;
|
||||
|
||||
+5
@@ -7,8 +7,13 @@ import cn.cloudwalk.elevator.visitor.param.VisitorRecordQueryParam;
|
||||
import cn.cloudwalk.elevator.visitor.result.VisitorQueryResult;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* {@link VisitorFeignClient} 熔断/降级:查询失败时抛错,不返回空壳数据,避免业务误判「无记录」与「服务不可用」。
|
||||
*/
|
||||
@Component
|
||||
public class VisitorFeignClientFallback implements VisitorFeignClient {
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public CloudwalkResult<VisitorQueryResult> query(VisitorRecordQueryParam param) throws ServiceException {
|
||||
throw new RuntimeException("查询识别记录详情失败");
|
||||
}
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* 访客主数据与识别记录查询:通过 Feign 调用 {@code ninca-crk-std} 等标准访客服务,为电梯业务侧提供访客档案与到访记录。
|
||||
* <p>
|
||||
* 本包内为入参/出参模型与客户端;业务组装与调用方在 record 等域中完成。
|
||||
*/
|
||||
package cn.cloudwalk.elevator.visitor;
|
||||
+3
@@ -3,6 +3,9 @@ package cn.cloudwalk.elevator.visitor.param;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 按主键批量拉取访客(或软删过滤)的查询条件,供与标准访客服务其它接口对接时复用。
|
||||
*/
|
||||
public class VisitorGetsParam implements Serializable {
|
||||
private static final long serialVersionUID = 3454014008721633675L;
|
||||
private List<String> ids;
|
||||
|
||||
+3
@@ -1,5 +1,8 @@
|
||||
package cn.cloudwalk.elevator.visitor.param;
|
||||
|
||||
/**
|
||||
* 轻量入参模型,仅含访客 id,字段可与 {@link VisitorRecordQueryParam#visitorId} 对应后再补全租户、分页等。
|
||||
*/
|
||||
public class VisitorRecordQueryForm {
|
||||
private String visitorId;
|
||||
|
||||
|
||||
+3
@@ -3,6 +3,9 @@ package cn.cloudwalk.elevator.visitor.param;
|
||||
import cn.cloudwalk.cloud.page.CloudwalkPageInfo;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 访客记录分页查询条件:必含业务租户与访客主键,继承公共分页信息。
|
||||
*/
|
||||
public class VisitorRecordQueryParam extends CloudwalkPageInfo implements Serializable {
|
||||
private static final long serialVersionUID = -5418825126025402170L;
|
||||
private String visitorId;
|
||||
|
||||
+3
@@ -3,6 +3,9 @@ package cn.cloudwalk.elevator.visitor.result;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 访客查询接口的聚合出参:一份访客档案 + 多页识别记录集合。
|
||||
*/
|
||||
public class VisitorQueryResult implements Serializable {
|
||||
private static final long serialVersionUID = 5547248296251558353L;
|
||||
private VisitorResult visitorInfo;
|
||||
|
||||
+3
@@ -3,6 +3,9 @@ package cn.cloudwalk.elevator.visitor.result;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 单条访客识别/签到记录:设备与区域信息、比分数、以及电梯业务扩展字段(如原因、起止楼层、派梯号等)。
|
||||
*/
|
||||
public class VisitorRecordResult implements Serializable {
|
||||
private static final long serialVersionUID = -7671207718927334038L;
|
||||
private String id;
|
||||
|
||||
+3
@@ -4,6 +4,9 @@ import cn.cloudwalk.cloud.annotation.SensitiveField;
|
||||
import cn.cloudwalk.cloud.enums.SensitiveType;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 访客主数据:含证件号(脱敏标注)、访期、登记人脸/展示图、与平台人员关系等;字段与标准访客中心模型对齐。
|
||||
*/
|
||||
public class VisitorResult implements Serializable {
|
||||
private static final long serialVersionUID = -6835803928819332341L;
|
||||
private String id;
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* 区域(园区/楼栋/楼层等)树与下一级树查询,供设备、通行与前端级联选择使用。
|
||||
* <p>
|
||||
* 多通过 Feign 拉取平台区域数据并在本包内做组装与 {@code result} 封装。
|
||||
*/
|
||||
package cn.cloudwalk.elevator.zone;
|
||||
+3
@@ -36,6 +36,9 @@ import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 设备侧聚合网关:设备/电梯码/区域树/记录等 {@code /device/v2/} 接口,薄控制器,具体逻辑见各 {@code *Service}。
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping({"/device/v2/"})
|
||||
public class AcsElevatorDeviceGetWayController extends AbstractCloudwalkController {
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* 设备与通行相关 HTTP 入口:设备网关查询、电梯码、通行记录等 {@code /device/v2/} 下接口。
|
||||
* <p>
|
||||
* 负责表单/JSON 与业务 {@code param} 的转换,业务规则与远程协作放在 service 层。
|
||||
*/
|
||||
package cn.cloudwalk.elevator.handler.device;
|
||||
+2
-2
@@ -44,8 +44,8 @@ public class AcsPersonAddVisitorForm implements Serializable {
|
||||
return false;
|
||||
}
|
||||
return Objects.equals(visitorId, other.visitorId) && Objects.equals(personId, other.personId)
|
||||
&& Objects.equals(begVisitorTime, other.begVisitorTime) && Objects.equals(endVisitorTime, other.endVisitorTime)
|
||||
&& Objects.equals(floorIds, other.floorIds);
|
||||
&& Objects.equals(begVisitorTime, other.begVisitorTime)
|
||||
&& Objects.equals(endVisitorTime, other.endVisitorTime) && Objects.equals(floorIds, other.floorIds);
|
||||
}
|
||||
|
||||
protected boolean canEqual(Object other) {
|
||||
|
||||
+25
-25
@@ -1,25 +1,25 @@
|
||||
package cn.cloudwalk.elevator.zone.util;
|
||||
|
||||
import cn.cloudwalk.elevator.util.StringUtils;
|
||||
import cn.cloudwalk.elevator.zone.result.ZoneTreeResult;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/** 区域树遍历辅助,供批量查询电梯编码等场景复用。 */
|
||||
public final class ZoneTreeCollectors {
|
||||
|
||||
private ZoneTreeCollectors() {}
|
||||
|
||||
/** 深度优先收集树上各节点 {@link ZoneTreeResult#getId()}(去重由调用方 {@link Set} 保证)。 */
|
||||
public static void collectNodeIds(List<ZoneTreeResult> nodes, Set<String> out) {
|
||||
if (nodes == null || out == null) {
|
||||
return;
|
||||
}
|
||||
for (ZoneTreeResult n : nodes) {
|
||||
if (StringUtils.isNotBlank(n.getId())) {
|
||||
out.add(n.getId());
|
||||
}
|
||||
collectNodeIds(n.getChildren(), out);
|
||||
}
|
||||
}
|
||||
}
|
||||
package cn.cloudwalk.elevator.zone.util;
|
||||
|
||||
import cn.cloudwalk.elevator.util.StringUtils;
|
||||
import cn.cloudwalk.elevator.zone.result.ZoneTreeResult;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/** 区域树遍历辅助,供批量查询电梯编码等场景复用。 */
|
||||
public final class ZoneTreeCollectors {
|
||||
|
||||
private ZoneTreeCollectors() {}
|
||||
|
||||
/** 深度优先收集树上各节点 {@link ZoneTreeResult#getId()}(去重由调用方 {@link Set} 保证)。 */
|
||||
public static void collectNodeIds(List<ZoneTreeResult> nodes, Set<String> out) {
|
||||
if (nodes == null || out == null) {
|
||||
return;
|
||||
}
|
||||
for (ZoneTreeResult n : nodes) {
|
||||
if (StringUtils.isNotBlank(n.getId())) {
|
||||
out.add(n.getId());
|
||||
}
|
||||
collectNodeIds(n.getChildren(), out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user