1.同步大华平台设备 组织架构信息

koumen
shenzhidan 2026-08-01 11:20:41 +08:00
parent 81740090b4
commit 817f0de961
27 changed files with 1169 additions and 0 deletions

View File

@ -0,0 +1,381 @@
package com.zcloud.primeport.service;
import cn.hutool.json.JSONUtil;
import com.zcloud.primeport.domain.gateway.DaHuaResourceGateway;
import com.zcloud.primeport.domain.gateway.DaHuaResourceRepositoryGateway;
import com.zcloud.primeport.domain.model.CorpInfoSnapshotE;
import com.zcloud.primeport.domain.model.DaHuaDepartmentCorpMappingE;
import com.zcloud.primeport.domain.model.DaHuaDeviceE;
import com.zcloud.primeport.domain.model.DaHuaResourcePageE;
import com.zcloud.primeport.domain.model.DaHuaResourceSyncResultE;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.text.Normalizer;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
@Service
@RequiredArgsConstructor
public class DaHuaResourceSyncApplicationService {
private static final int MAX_PAGE_COUNT = 10000;
private static final int MAX_DETAIL_COUNT = 200;
private final DaHuaResourceGateway resourceGateway;
private final DaHuaResourceRepositoryGateway repositoryGateway;
@Transactional(rollbackFor = Exception.class)
public DaHuaResourceSyncResultE sync(Integer pageSize, String subsystem, boolean autoMatchByName) {
int actualPageSize = pageSize == null ? 200 : pageSize;
if (actualPageSize < 1 || actualPageSize > 1000) {
throw new IllegalArgumentException("pageSize必须在1到1000之间");
}
List<Map<String, Object>> departments = fetchAll(actualPageSize, subsystem, true);
List<Map<String, Object>> devices = fetchAll(actualPageSize, subsystem, false);
DaHuaResourceSyncResultE result = new DaHuaResourceSyncResultE();
result.setDepartmentTotal(departments.size());
syncDepartmentMappings(departments, autoMatchByName, result);
result.setDeviceTotal(devices.size());
syncDevices(devices, result);
deduplicateDetails(result);
return result;
}
private List<Map<String, Object>> fetchAll(int pageSize, String subsystem, boolean organization) {
List<Map<String, Object>> records = new ArrayList<>();
for (int pageNum = 1; pageNum <= MAX_PAGE_COUNT; pageNum++) {
DaHuaResourcePageE page = organization
? resourceGateway.pageOrganizations(pageNum, pageSize, subsystem)
: resourceGateway.pageDevices(pageNum, pageSize, subsystem);
List<Map<String, Object>> pageRecords = page == null || page.getRecords() == null
? new ArrayList<>() : page.getRecords();
records.addAll(pageRecords);
if (pageRecords.isEmpty() || pageRecords.size() < pageSize
|| (page.getTotal() != null && records.size() >= page.getTotal())) {
return records;
}
}
throw new RuntimeException("大华资源分页超过最大页数" + MAX_PAGE_COUNT);
}
private void syncDepartmentMappings(List<Map<String, Object>> departments, boolean autoMatchByName,
DaHuaResourceSyncResultE result) {
List<CorpInfoSnapshotE> corps = repositoryGateway.listActiveCorps();
Map<String, List<CorpInfoSnapshotE>> corpsByName = groupCorpsByNormalizedName(corps);
Map<Long, CorpInfoSnapshotE> corpsById = indexCorpsById(corps);
MappingIndex mappingIndex = new MappingIndex(repositoryGateway.listMappings());
LocalDateTime now = LocalDateTime.now();
for (Map<String, Object> department : departments) {
Long departmentId = firstLong(department, "id", "departmentId", "deptId");
String departmentName = firstString(department,
"name", "departmentName", "deptName", "orgName");
String orgCode = blankToNull(firstString(department, "orgCode"));
if (departmentId == null || isBlank(departmentName)) {
result.setDepartmentInvalid(result.getDepartmentInvalid() + 1);
continue;
}
DaHuaDepartmentCorpMappingE mappingByDepartment = mappingIndex.byDepartmentId.get(departmentId);
DaHuaDepartmentCorpMappingE mappingByOrg = orgCode == null ? null : mappingIndex.byOrgCode.get(orgCode);
if (mappingByDepartment != null && mappingByOrg != null
&& !mappingByDepartment.getId().equals(mappingByOrg.getId())) {
throw new IllegalStateException("大华组织数据冲突: departmentId=" + departmentId
+ "和orgCode=" + orgCode + "已绑定到不同企业");
}
DaHuaDepartmentCorpMappingE mapping = mappingByDepartment != null
? mappingByDepartment : mappingByOrg;
if (mapping != null) {
Long oldDepartmentId = mapping.getDahuaDepartmentId();
String oldOrgCode = mapping.getDahuaOrgCode();
mapping.setDahuaDepartmentId(departmentId);
mapping.setDahuaOrgCode(orgCode);
mapping.setDahuaDepartmentName(departmentName);
CorpInfoSnapshotE currentCorp = corpsById.get(mapping.getCorpId());
if (currentCorp != null) {
mapping.setCorpName(currentCorp.getCorpName());
}
mapping.setBindStatus(1);
mapping.setDeleteEnum("FALSE");
mapping.setLastSyncTime(now);
repositoryGateway.updateMapping(mapping);
mappingIndex.replace(mapping, oldDepartmentId, oldOrgCode);
result.setMappingUpdated(result.getMappingUpdated() + 1);
continue;
}
CorpInfoSnapshotE matchedCorp = autoMatchByName
? findUniqueCorp(corpsByName, departmentName) : null;
if (matchedCorp == null || mappingIndex.byCorpId.containsKey(matchedCorp.getId())) {
result.setDepartmentUnmatched(result.getDepartmentUnmatched() + 1);
addDetail(result.getUnmatchedDepartmentNames(), departmentName);
continue;
}
DaHuaDepartmentCorpMappingE created = new DaHuaDepartmentCorpMappingE();
created.setDahuaDepartmentId(departmentId);
created.setDahuaOrgCode(orgCode);
created.setDahuaDepartmentName(departmentName);
created.setCorpId(matchedCorp.getId());
created.setCorpName(matchedCorp.getCorpName());
created.setMatchType(1);
created.setBindStatus(1);
created.setLastSyncTime(now);
repositoryGateway.addMapping(created);
mappingIndex.add(created);
result.setMappingCreated(result.getMappingCreated() + 1);
}
}
private void syncDevices(List<Map<String, Object>> devices, DaHuaResourceSyncResultE result) {
MappingIndex mappingIndex = new MappingIndex(repositoryGateway.listMappings());
DeviceIndex deviceIndex = new DeviceIndex(repositoryGateway.listDevices());
LocalDateTime now = LocalDateTime.now();
for (Map<String, Object> source : devices) {
String deviceCode = blankToNull(firstString(source, "deviceCode", "code", "resourceCode"));
if (deviceCode == null) {
result.setDeviceInvalid(result.getDeviceInvalid() + 1);
continue;
}
Long departmentId = firstLong(source, "departmentId", "deptId");
String orgCode = blankToNull(firstString(source,
"ownerCode", "orgCode", "departmentCode", "deptCode"));
DaHuaDepartmentCorpMappingE mapping = departmentId == null
? null : mappingIndex.byDepartmentId.get(departmentId);
if (mapping == null && orgCode != null) {
mapping = mappingIndex.byOrgCode.get(orgCode);
}
DaHuaDeviceE device = deviceIndex.byDeviceCode.get(deviceCode);
boolean created = device == null;
String oldDeviceCode = null;
if (created) {
device = new DaHuaDeviceE();
} else {
oldDeviceCode = device.getDeviceCode();
}
device.setDeleteEnum("FALSE");
device.setDeviceCode(deviceCode);
device.setDeviceName(firstString(source, "deviceName", "name"));
device.setDeviceType(firstString(source, "deviceType", "type"));
device.setDeviceCategory(firstString(source, "deviceCategory", "category"));
device.setDeviceManufacturer(firstString(source, "deviceManufacturer", "manufacturer"));
device.setDeviceModel(firstString(source, "deviceModel", "model"));
device.setIsOnline(firstBooleanFlag(source, "isOnline", "onlineStatus", "status"));
device.setOfflineReason(firstString(source, "offlineReason"));
device.setSubSystem(firstString(source, "subSystem", "subsystem"));
device.setDeviceIp(firstString(source, "deviceIp", "ip"));
device.setDevicePort(firstInteger(source, "devicePort", "port"));
device.setDahuaDepartmentId(departmentId != null
? departmentId : (mapping == null ? null : mapping.getDahuaDepartmentId()));
device.setDahuaOrgCode(orgCode);
String orgName = firstString(source, "orgName", "departmentName", "deptName");
device.setDahuaOrgName(isBlank(orgName) && mapping != null
? mapping.getDahuaDepartmentName() : orgName);
device.setCorpId(mapping == null ? null : mapping.getCorpId());
device.setCorpName(mapping == null ? null : mapping.getCorpName());
device.setMatchStatus(mapping == null ? 0 : 1);
device.setRawData(JSONUtil.toJsonStr(source));
device.setLastSyncTime(now);
if (created) {
repositoryGateway.addDevice(device);
deviceIndex.add(device);
result.setDeviceCreated(result.getDeviceCreated() + 1);
} else {
repositoryGateway.updateDevice(device);
deviceIndex.replace(device, oldDeviceCode);
result.setDeviceUpdated(result.getDeviceUpdated() + 1);
}
if (mapping == null) {
result.setDeviceUnmatched(result.getDeviceUnmatched() + 1);
addDetail(result.getUnmatchedDeviceCodes(), deviceCode);
}
}
}
private Map<String, List<CorpInfoSnapshotE>> groupCorpsByNormalizedName(List<CorpInfoSnapshotE> corps) {
Map<String, List<CorpInfoSnapshotE>> result = new HashMap<>();
for (CorpInfoSnapshotE corp : corps) {
String normalized = normalizeName(corp.getCorpName());
if (normalized == null) {
continue;
}
result.computeIfAbsent(normalized, key -> new ArrayList<>()).add(corp);
}
return result;
}
private Map<Long, CorpInfoSnapshotE> indexCorpsById(List<CorpInfoSnapshotE> corps) {
Map<Long, CorpInfoSnapshotE> result = new HashMap<>();
for (CorpInfoSnapshotE corp : corps) {
result.put(corp.getId(), corp);
}
return result;
}
private CorpInfoSnapshotE findUniqueCorp(Map<String, List<CorpInfoSnapshotE>> corpsByName,
String departmentName) {
List<CorpInfoSnapshotE> matches = corpsByName.get(normalizeName(departmentName));
return matches != null && matches.size() == 1 ? matches.get(0) : null;
}
private String normalizeName(String value) {
if (isBlank(value)) {
return null;
}
return Normalizer.normalize(value, Normalizer.Form.NFKC)
.replaceAll("\\s+", "")
.toLowerCase(Locale.ROOT);
}
private Object firstValue(Map<String, Object> source, String... fields) {
for (String field : fields) {
Object value = source.get(field);
if (value != null) {
return value;
}
}
return null;
}
private String firstString(Map<String, Object> source, String... fields) {
Object value = firstValue(source, fields);
return value == null ? null : String.valueOf(value);
}
private Long firstLong(Map<String, Object> source, String... fields) {
Object value = firstValue(source, fields);
if (value == null || isBlank(String.valueOf(value))) {
return null;
}
try {
return Long.valueOf(String.valueOf(value));
} catch (NumberFormatException e) {
return null;
}
}
private Integer firstInteger(Map<String, Object> source, String... fields) {
Object value = firstValue(source, fields);
if (value == null || isBlank(String.valueOf(value))) {
return null;
}
try {
return Integer.valueOf(String.valueOf(value));
} catch (NumberFormatException e) {
return null;
}
}
private Integer firstBooleanFlag(Map<String, Object> source, String... fields) {
Object value = firstValue(source, fields);
if (value == null) {
return null;
}
if (value instanceof Boolean) {
return (Boolean) value ? 1 : 0;
}
String text = String.valueOf(value).trim();
if ("true".equalsIgnoreCase(text)) {
return 1;
}
if ("false".equalsIgnoreCase(text)) {
return 0;
}
try {
return Integer.valueOf(text);
} catch (NumberFormatException e) {
return null;
}
}
private String blankToNull(String value) {
return isBlank(value) ? null : value.trim();
}
private boolean isBlank(String value) {
return value == null || value.trim().isEmpty();
}
private void addDetail(List<String> target, String value) {
if (!isBlank(value) && target.size() < MAX_DETAIL_COUNT) {
target.add(value);
}
}
private void deduplicateDetails(DaHuaResourceSyncResultE result) {
result.setUnmatchedDepartmentNames(uniqueList(result.getUnmatchedDepartmentNames()));
result.setUnmatchedDeviceCodes(uniqueList(result.getUnmatchedDeviceCodes()));
}
private List<String> uniqueList(List<String> values) {
Set<String> unique = new LinkedHashSet<>(values);
return new ArrayList<>(unique);
}
private static class MappingIndex {
private final Map<Long, DaHuaDepartmentCorpMappingE> byDepartmentId = new HashMap<>();
private final Map<String, DaHuaDepartmentCorpMappingE> byOrgCode = new HashMap<>();
private final Map<Long, DaHuaDepartmentCorpMappingE> byCorpId = new HashMap<>();
private MappingIndex(List<DaHuaDepartmentCorpMappingE> mappings) {
for (DaHuaDepartmentCorpMappingE mapping : mappings) {
add(mapping);
}
}
private void add(DaHuaDepartmentCorpMappingE mapping) {
byDepartmentId.put(mapping.getDahuaDepartmentId(), mapping);
if (mapping.getDahuaOrgCode() != null && !mapping.getDahuaOrgCode().trim().isEmpty()) {
byOrgCode.put(mapping.getDahuaOrgCode(), mapping);
}
byCorpId.put(mapping.getCorpId(), mapping);
}
private void replace(DaHuaDepartmentCorpMappingE mapping, Long oldDepartmentId, String oldOrgCode) {
if (oldDepartmentId != null && !oldDepartmentId.equals(mapping.getDahuaDepartmentId())) {
byDepartmentId.remove(oldDepartmentId);
}
if (oldOrgCode != null && !oldOrgCode.equals(mapping.getDahuaOrgCode())) {
byOrgCode.remove(oldOrgCode);
}
add(mapping);
}
}
private static class DeviceIndex {
private final Map<String, DaHuaDeviceE> byDeviceCode = new HashMap<>();
private DeviceIndex(List<DaHuaDeviceE> devices) {
for (DaHuaDeviceE device : devices) {
add(device);
}
}
private void add(DaHuaDeviceE device) {
if (device.getDeviceCode() != null) {
byDeviceCode.put(device.getDeviceCode(), device);
}
}
private void replace(DaHuaDeviceE device, String oldDeviceCode) {
if (oldDeviceCode != null && !oldDeviceCode.equals(device.getDeviceCode())) {
byDeviceCode.remove(oldDeviceCode);
}
add(device);
}
}
}

View File

@ -0,0 +1,117 @@
package com.zcloud.primeport.service;
import com.zcloud.primeport.domain.gateway.DaHuaResourceGateway;
import com.zcloud.primeport.domain.gateway.DaHuaResourceRepositoryGateway;
import com.zcloud.primeport.domain.model.CorpInfoSnapshotE;
import com.zcloud.primeport.domain.model.DaHuaDepartmentCorpMappingE;
import com.zcloud.primeport.domain.model.DaHuaDeviceE;
import com.zcloud.primeport.domain.model.DaHuaResourcePageE;
import com.zcloud.primeport.domain.model.DaHuaResourceSyncResultE;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class DaHuaResourceSyncApplicationServiceTest {
@Mock
private DaHuaResourceGateway resourceGateway;
@Mock
private DaHuaResourceRepositoryGateway repositoryGateway;
private DaHuaResourceSyncApplicationService applicationService;
@BeforeEach
void setUp() {
applicationService = new DaHuaResourceSyncApplicationService(resourceGateway, repositoryGateway);
}
@Test
void shouldOrchestratePagedResourcesAndPersistMatches() {
when(resourceGateway.pageOrganizations(1, 1, "access"))
.thenReturn(page(Collections.singletonList(department(10L, "测试企业", "ORG-10")), 2L));
when(resourceGateway.pageOrganizations(2, 1, "access"))
.thenReturn(page(Collections.singletonList(department(11L, "未匹配企业", "ORG-11")), 2L));
when(resourceGateway.pageDevices(1, 1, "access"))
.thenReturn(page(Collections.singletonList(device("DEV-1", 10L, "ORG-10")), 1L));
CorpInfoSnapshotE corp = new CorpInfoSnapshotE();
corp.setId(100L);
corp.setCorpName(" 测试企业 ");
when(repositoryGateway.listActiveCorps()).thenReturn(Collections.singletonList(corp));
List<DaHuaDepartmentCorpMappingE> mappings = new ArrayList<>();
when(repositoryGateway.listMappings()).thenAnswer(invocation -> new ArrayList<>(mappings));
when(repositoryGateway.listDevices()).thenReturn(Collections.emptyList());
doAnswer(invocation -> {
DaHuaDepartmentCorpMappingE mapping = invocation.getArgument(0);
mapping.setId(1000L);
mappings.add(mapping);
return null;
}).when(repositoryGateway).addMapping(any(DaHuaDepartmentCorpMappingE.class));
DaHuaResourceSyncResultE result = applicationService.sync(1, "access", true);
assertEquals(2, result.getDepartmentTotal());
assertEquals(1, result.getMappingCreated());
assertEquals(1, result.getDepartmentUnmatched());
assertEquals(Collections.singletonList("未匹配企业"), result.getUnmatchedDepartmentNames());
assertEquals(1, result.getDeviceTotal());
assertEquals(1, result.getDeviceCreated());
assertEquals(0, result.getDeviceUnmatched());
ArgumentCaptor<DaHuaDeviceE> deviceCaptor = ArgumentCaptor.forClass(DaHuaDeviceE.class);
verify(repositoryGateway).addDevice(deviceCaptor.capture());
assertEquals(100L, deviceCaptor.getValue().getCorpId());
assertEquals("测试企业", deviceCaptor.getValue().getCorpName().trim());
verify(resourceGateway).pageOrganizations(2, 1, "access");
verify(resourceGateway).pageDevices(1, 1, "access");
}
@Test
void shouldRejectInvalidPageSizeBeforeCallingGateway() {
assertThrows(IllegalArgumentException.class, () -> applicationService.sync(0, null, true));
verify(resourceGateway, never()).pageOrganizations(any(), any(), any());
}
private DaHuaResourcePageE page(List<Map<String, Object>> records, Long total) {
DaHuaResourcePageE page = new DaHuaResourcePageE();
page.setRecords(records);
page.setTotal(total);
return page;
}
private Map<String, Object> department(Long id, String name, String orgCode) {
Map<String, Object> value = new HashMap<>();
value.put("id", id);
value.put("name", name);
value.put("orgCode", orgCode);
return value;
}
private Map<String, Object> device(String code, Long departmentId, String orgCode) {
Map<String, Object> value = new HashMap<>();
value.put("deviceCode", code);
value.put("deviceName", "门禁一号");
value.put("departmentId", departmentId);
value.put("ownerCode", orgCode);
return value;
}
}

View File

@ -0,0 +1,25 @@
package com.zcloud.primeport.dto;
import com.alibaba.cola.dto.Command;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
@Data
@EqualsAndHashCode(callSuper = true)
public class DaHuaResourceSyncCmd extends Command {
@Min(1)
@Max(1000)
@ApiModelProperty(value = "大华分页大小默认200")
private Integer pageSize = 200;
@ApiModelProperty(value = "大华子系统编码门禁默认evo-accesscontrol")
private String subsystem = "evo-accesscontrol";
@ApiModelProperty(value = "首次同步时是否按企业名称自动匹配默认true")
private Boolean autoMatchByName = true;
}

View File

@ -0,0 +1,37 @@
package com.zcloud.primeport.dto.clientobject;
import com.alibaba.cola.dto.ClientObject;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class DaHuaResourceSyncResultCO extends ClientObject {
@ApiModelProperty("大华部门总数")
private Integer departmentTotal = 0;
@ApiModelProperty("新增部门企业映射数")
private Integer mappingCreated = 0;
@ApiModelProperty("更新部门企业映射数")
private Integer mappingUpdated = 0;
@ApiModelProperty("未匹配部门数")
private Integer departmentUnmatched = 0;
@ApiModelProperty("无效部门数据数")
private Integer departmentInvalid = 0;
@ApiModelProperty("大华设备总数")
private Integer deviceTotal = 0;
@ApiModelProperty("新增设备数")
private Integer deviceCreated = 0;
@ApiModelProperty("更新设备数")
private Integer deviceUpdated = 0;
@ApiModelProperty("未匹配企业设备数")
private Integer deviceUnmatched = 0;
@ApiModelProperty("无效设备数据数")
private Integer deviceInvalid = 0;
@ApiModelProperty("未匹配的大华部门名称")
private List<String> unmatchedDepartmentNames = new ArrayList<>();
@ApiModelProperty("未匹配企业的大华设备编码")
private List<String> unmatchedDeviceCodes = new ArrayList<>();
}

View File

@ -0,0 +1,10 @@
package com.zcloud.primeport.domain.gateway;
import com.zcloud.primeport.domain.model.DaHuaResourcePageE;
public interface DaHuaResourceGateway {
DaHuaResourcePageE pageOrganizations(Integer pageNum, Integer pageSize, String subsystem);
DaHuaResourcePageE pageDevices(Integer pageNum, Integer pageSize, String subsystem);
}

View File

@ -0,0 +1,24 @@
package com.zcloud.primeport.domain.gateway;
import com.zcloud.primeport.domain.model.CorpInfoSnapshotE;
import com.zcloud.primeport.domain.model.DaHuaDepartmentCorpMappingE;
import com.zcloud.primeport.domain.model.DaHuaDeviceE;
import java.util.List;
public interface DaHuaResourceRepositoryGateway {
List<CorpInfoSnapshotE> listActiveCorps();
List<DaHuaDepartmentCorpMappingE> listMappings();
void addMapping(DaHuaDepartmentCorpMappingE mapping);
void updateMapping(DaHuaDepartmentCorpMappingE mapping);
List<DaHuaDeviceE> listDevices();
void addDevice(DaHuaDeviceE device);
void updateDevice(DaHuaDeviceE device);
}

View File

@ -0,0 +1,10 @@
package com.zcloud.primeport.domain.model;
import lombok.Data;
@Data
public class CorpInfoSnapshotE {
private Long id;
private String corpName;
}

View File

@ -0,0 +1,20 @@
package com.zcloud.primeport.domain.model;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class DaHuaDepartmentCorpMappingE {
private Long id;
private Long dahuaDepartmentId;
private String dahuaOrgCode;
private String dahuaDepartmentName;
private Long corpId;
private String corpName;
private Integer matchType;
private Integer bindStatus;
private String deleteEnum;
private LocalDateTime lastSyncTime;
}

View File

@ -0,0 +1,31 @@
package com.zcloud.primeport.domain.model;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class DaHuaDeviceE {
private Long id;
private String deviceCode;
private String deviceName;
private String deviceType;
private String deviceCategory;
private String deviceManufacturer;
private String deviceModel;
private Integer isOnline;
private String offlineReason;
private String subSystem;
private String deviceIp;
private Integer devicePort;
private Long dahuaDepartmentId;
private String dahuaOrgCode;
private String dahuaOrgName;
private Long corpId;
private String corpName;
private Integer matchStatus;
private String rawData;
private String deleteEnum;
private LocalDateTime lastSyncTime;
}

View File

@ -0,0 +1,14 @@
package com.zcloud.primeport.domain.model;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Data
public class DaHuaResourcePageE {
private List<Map<String, Object>> records = new ArrayList<>();
private Long total;
}

View File

@ -0,0 +1,23 @@
package com.zcloud.primeport.domain.model;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class DaHuaResourceSyncResultE {
private Integer departmentTotal = 0;
private Integer mappingCreated = 0;
private Integer mappingUpdated = 0;
private Integer departmentUnmatched = 0;
private Integer departmentInvalid = 0;
private Integer deviceTotal = 0;
private Integer deviceCreated = 0;
private Integer deviceUpdated = 0;
private Integer deviceUnmatched = 0;
private Integer deviceInvalid = 0;
private List<String> unmatchedDepartmentNames = new ArrayList<>();
private List<String> unmatchedDeviceCodes = new ArrayList<>();
}

View File

@ -0,0 +1,43 @@
package com.zcloud.primeport.dahua.api;
import com.dahuatech.icc.exception.ClientException;
import com.dahuatech.icc.oauth.model.v202010.GeneralResponse;
import com.zcloud.primeport.dahua.client.DaHuaApiClient;
import com.zcloud.primeport.dahua.config.DaHuaResourceProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
public class DaHuaResourceApi {
@Autowired(required = false)
private DaHuaApiClient daHuaApiClient;
@Autowired
private DaHuaResourceProperties properties;
public boolean isConfigured() {
return daHuaApiClient != null;
}
public GeneralResponse pageOrganizations(Map<String, Object> body) throws ClientException {
return postPage(properties.getOrganizationPagePath(), body);
}
public GeneralResponse pageDevices(Map<String, Object> body) throws ClientException {
return postPage(properties.getDevicePagePath(), body);
}
private GeneralResponse postPage(String path, Map<String, Object> body) throws ClientException {
if (daHuaApiClient == null) {
return null;
}
String token = daHuaApiClient.getToken();
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "bearer " + token);
return daHuaApiClient.postJson(path, body, headers);
}
}

View File

@ -0,0 +1,12 @@
package com.zcloud.primeport.dahua.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "dahua.resource")
public class DaHuaResourceProperties {
private String organizationPagePath = "/evo-apigw/evo-brm/1.2.0/organization/subsystem/page";
private String devicePagePath = "/evo-apigw/evo-brm/1.2.0/device/subsystem/page";
}

View File

@ -0,0 +1,154 @@
package com.zcloud.primeport.gatewayimpl;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.dahuatech.icc.exception.ClientException;
import com.dahuatech.icc.oauth.model.v202010.GeneralResponse;
import com.zcloud.primeport.dahua.api.DaHuaResourceApi;
import com.zcloud.primeport.domain.gateway.DaHuaResourceGateway;
import com.zcloud.primeport.domain.model.DaHuaResourcePageE;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
@RequiredArgsConstructor
public class DaHuaResourceGatewayImpl implements DaHuaResourceGateway {
private final DaHuaResourceApi daHuaResourceApi;
@Override
public DaHuaResourcePageE pageOrganizations(Integer pageNum, Integer pageSize, String subsystem) {
return requestPage(pageNum, pageSize, subsystem, true);
}
@Override
public DaHuaResourcePageE pageDevices(Integer pageNum, Integer pageSize, String subsystem) {
return requestPage(pageNum, pageSize, subsystem, false);
}
private DaHuaResourcePageE requestPage(Integer pageNum, Integer pageSize, String subsystem,
boolean organization) {
if (!daHuaResourceApi.isConfigured()) {
throw new RuntimeException("大华服务未配置");
}
Map<String, Object> body = new HashMap<>();
body.put("pageNum", pageNum);
body.put("pageSize", pageSize);
if (!isBlank(subsystem)) {
body.put("subsystem", subsystem.trim());
}
try {
GeneralResponse response = organization
? daHuaResourceApi.pageOrganizations(body)
: daHuaResourceApi.pageDevices(body);
Object data = unwrapData(response, organization ? "部门" : "设备");
return parsePage(data);
} catch (ClientException e) {
throw new RuntimeException("调用大华" + (organization ? "部门" : "设备") + "分页接口失败", e);
}
}
private Object unwrapData(GeneralResponse response, String resourceName) {
if (response == null) {
throw new RuntimeException("大华" + resourceName + "分页接口返回为空");
}
JSONObject outer = JSONUtil.parseObj(JSONUtil.toJsonStr(response));
String code = outer.getStr("code", response.getCode());
String errMsg = outer.getStr("errMsg", "");
Object data = outer.get("data");
JSONObject result = toJsonObject(outer.get("result"));
if (result != null) {
code = result.getStr("code", code);
errMsg = result.getStr("errMsg", errMsg);
if (result.containsKey("data")) {
data = result.get("data");
}
}
if (!"0".equals(code)) {
throw new RuntimeException("大华" + resourceName + "分页接口失败: code=" + code
+ ", errMsg=" + errMsg);
}
return data;
}
private DaHuaResourcePageE parsePage(Object data) {
DaHuaResourcePageE page = new DaHuaResourcePageE();
if (data == null) {
return page;
}
if (data instanceof JSONArray || data instanceof List) {
page.setRecords(toRecordList(data));
return page;
}
JSONObject object = toJsonObject(data);
if (object == null) {
return page;
}
page.setRecords(toRecordList(firstValue(object, "pageData", "records", "list", "dataList")));
page.setTotal(firstLong(object, "totalRows", "total", "totalCount", "recordCount"));
return page;
}
private List<Map<String, Object>> toRecordList(Object value) {
List<Map<String, Object>> records = new ArrayList<>();
if (value == null) {
return records;
}
JSONArray array;
try {
array = value instanceof JSONArray ? (JSONArray) value : JSONUtil.parseArray(value);
} catch (Exception e) {
return records;
}
for (Object item : array) {
JSONObject object = toJsonObject(item);
if (object != null) {
records.add(new HashMap<>(object));
}
}
return records;
}
private JSONObject toJsonObject(Object value) {
if (value == null) {
return null;
}
try {
return value instanceof JSONObject ? (JSONObject) value : JSONUtil.parseObj(value);
} catch (Exception e) {
return null;
}
}
private Object firstValue(Map<String, Object> source, String... fields) {
for (String field : fields) {
Object value = source.get(field);
if (value != null) {
return value;
}
}
return null;
}
private Long firstLong(Map<String, Object> source, String... fields) {
Object value = firstValue(source, fields);
if (value == null || isBlank(String.valueOf(value))) {
return null;
}
try {
return Long.valueOf(String.valueOf(value));
} catch (NumberFormatException e) {
return null;
}
}
private boolean isBlank(String value) {
return value == null || value.trim().isEmpty();
}
}

View File

@ -0,0 +1,109 @@
package com.zcloud.primeport.gatewayimpl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
import com.jjb.saas.framework.repository.basedo.BaseDO;
import com.zcloud.primeport.domain.gateway.DaHuaResourceRepositoryGateway;
import com.zcloud.primeport.domain.model.CorpInfoSnapshotE;
import com.zcloud.primeport.domain.model.DaHuaDepartmentCorpMappingE;
import com.zcloud.primeport.domain.model.DaHuaDeviceE;
import com.zcloud.primeport.persistence.dataobject.CorpInfoSnapshotDO;
import com.zcloud.primeport.persistence.dataobject.DaHuaDepartmentCorpMappingDO;
import com.zcloud.primeport.persistence.dataobject.DaHuaDeviceDO;
import com.zcloud.primeport.persistence.repository.CorpInfoSnapshotRepository;
import com.zcloud.primeport.persistence.repository.DaHuaDepartmentCorpMappingRepository;
import com.zcloud.primeport.persistence.repository.DaHuaDeviceRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Service
@RequiredArgsConstructor
public class DaHuaResourceRepositoryGatewayImpl implements DaHuaResourceRepositoryGateway {
private final CorpInfoSnapshotRepository corpInfoSnapshotRepository;
private final DaHuaDepartmentCorpMappingRepository mappingRepository;
private final DaHuaDeviceRepository deviceRepository;
private final DefaultIdentifierGenerator idGenerator = new DefaultIdentifierGenerator();
@Override
public List<CorpInfoSnapshotE> listActiveCorps() {
LambdaQueryWrapper<CorpInfoSnapshotDO> query = new LambdaQueryWrapper<>();
query.eq(CorpInfoSnapshotDO::getDeleteEnum, "FALSE")
.select(CorpInfoSnapshotDO::getId, CorpInfoSnapshotDO::getCorpName);
return convertList(corpInfoSnapshotRepository.list(query), CorpInfoSnapshotE.class);
}
@Override
public List<DaHuaDepartmentCorpMappingE> listMappings() {
return convertList(mappingRepository.list(new LambdaQueryWrapper<>()),
DaHuaDepartmentCorpMappingE.class);
}
@Override
public void addMapping(DaHuaDepartmentCorpMappingE mapping) {
DaHuaDepartmentCorpMappingDO data = copy(mapping, DaHuaDepartmentCorpMappingDO.class);
initializeBase(data);
mappingRepository.save(data);
mapping.setId(data.getId());
}
@Override
public void updateMapping(DaHuaDepartmentCorpMappingE mapping) {
DaHuaDepartmentCorpMappingDO data = copy(mapping, DaHuaDepartmentCorpMappingDO.class);
data.setUpdateTime(LocalDateTime.now());
mappingRepository.updateById(data);
}
@Override
public List<DaHuaDeviceE> listDevices() {
return convertList(deviceRepository.list(new LambdaQueryWrapper<>()), DaHuaDeviceE.class);
}
@Override
public void addDevice(DaHuaDeviceE device) {
DaHuaDeviceDO data = copy(device, DaHuaDeviceDO.class);
initializeBase(data);
deviceRepository.save(data);
device.setId(data.getId());
}
@Override
public void updateDevice(DaHuaDeviceE device) {
DaHuaDeviceDO data = copy(device, DaHuaDeviceDO.class);
data.setUpdateTime(LocalDateTime.now());
deviceRepository.updateById(data);
}
private void initializeBase(BaseDO target) {
LocalDateTime now = LocalDateTime.now();
target.setId(idGenerator.nextId(target).longValue());
target.setDeleteEnum("FALSE");
target.setEnv("PROD");
target.setVersion(0);
target.setCreateTime(now);
target.setUpdateTime(now);
}
private <S, T> List<T> convertList(List<S> source, Class<T> targetType) {
List<T> result = new ArrayList<>();
for (S item : source) {
result.add(copy(item, targetType));
}
return result;
}
private <T> T copy(Object source, Class<T> targetType) {
try {
T target = targetType.newInstance();
BeanUtils.copyProperties(source, target);
return target;
} catch (InstantiationException | IllegalAccessException e) {
throw new IllegalStateException("大华资源持久化对象转换失败", e);
}
}
}

View File

@ -0,0 +1,14 @@
package com.zcloud.primeport.persistence.dataobject;
import com.baomidou.mybatisplus.annotation.TableName;
import com.jjb.saas.framework.repository.basedo.BaseDO;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@TableName("corp_info")
@EqualsAndHashCode(callSuper = true)
public class CorpInfoSnapshotDO extends BaseDO {
private String corpName;
}

View File

@ -0,0 +1,23 @@
package com.zcloud.primeport.persistence.dataobject;
import com.baomidou.mybatisplus.annotation.TableName;
import com.jjb.saas.framework.repository.basedo.BaseDO;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
@Data
@TableName("dahua_department_corp_mapping")
@EqualsAndHashCode(callSuper = true)
public class DaHuaDepartmentCorpMappingDO extends BaseDO {
private Long dahuaDepartmentId;
private String dahuaOrgCode;
private String dahuaDepartmentName;
private Long corpId;
private String corpName;
private Integer matchType;
private Integer bindStatus;
private LocalDateTime lastSyncTime;
}

View File

@ -0,0 +1,34 @@
package com.zcloud.primeport.persistence.dataobject;
import com.baomidou.mybatisplus.annotation.TableName;
import com.jjb.saas.framework.repository.basedo.BaseDO;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
@Data
@TableName("dahua_device")
@EqualsAndHashCode(callSuper = true)
public class DaHuaDeviceDO extends BaseDO {
private String deviceCode;
private String deviceName;
private String deviceType;
private String deviceCategory;
private String deviceManufacturer;
private String deviceModel;
private Integer isOnline;
private String offlineReason;
private String subSystem;
private String deviceIp;
private Integer devicePort;
private Long dahuaDepartmentId;
private String dahuaOrgCode;
private String dahuaOrgName;
private Long corpId;
private String corpName;
private Integer matchStatus;
private String rawData;
private LocalDateTime lastSyncTime;
}

View File

@ -0,0 +1,9 @@
package com.zcloud.primeport.persistence.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zcloud.primeport.persistence.dataobject.CorpInfoSnapshotDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface CorpInfoSnapshotMapper extends BaseMapper<CorpInfoSnapshotDO> {
}

View File

@ -0,0 +1,9 @@
package com.zcloud.primeport.persistence.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zcloud.primeport.persistence.dataobject.DaHuaDepartmentCorpMappingDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface DaHuaDepartmentCorpMappingMapper extends BaseMapper<DaHuaDepartmentCorpMappingDO> {
}

View File

@ -0,0 +1,9 @@
package com.zcloud.primeport.persistence.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zcloud.primeport.persistence.dataobject.DaHuaDeviceDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface DaHuaDeviceMapper extends BaseMapper<DaHuaDeviceDO> {
}

View File

@ -0,0 +1,7 @@
package com.zcloud.primeport.persistence.repository;
import com.jjb.saas.framework.repository.repo.BaseRepository;
import com.zcloud.primeport.persistence.dataobject.CorpInfoSnapshotDO;
public interface CorpInfoSnapshotRepository extends BaseRepository<CorpInfoSnapshotDO> {
}

View File

@ -0,0 +1,8 @@
package com.zcloud.primeport.persistence.repository;
import com.jjb.saas.framework.repository.repo.BaseRepository;
import com.zcloud.primeport.persistence.dataobject.DaHuaDepartmentCorpMappingDO;
public interface DaHuaDepartmentCorpMappingRepository
extends BaseRepository<DaHuaDepartmentCorpMappingDO> {
}

View File

@ -0,0 +1,7 @@
package com.zcloud.primeport.persistence.repository;
import com.jjb.saas.framework.repository.repo.BaseRepository;
import com.zcloud.primeport.persistence.dataobject.DaHuaDeviceDO;
public interface DaHuaDeviceRepository extends BaseRepository<DaHuaDeviceDO> {
}

View File

@ -0,0 +1,13 @@
package com.zcloud.primeport.persistence.repository.impl;
import com.jjb.saas.framework.repository.repo.impl.BaseRepositoryImpl;
import com.zcloud.primeport.persistence.dataobject.CorpInfoSnapshotDO;
import com.zcloud.primeport.persistence.mapper.CorpInfoSnapshotMapper;
import com.zcloud.primeport.persistence.repository.CorpInfoSnapshotRepository;
import org.springframework.stereotype.Service;
@Service
public class CorpInfoSnapshotRepositoryImpl
extends BaseRepositoryImpl<CorpInfoSnapshotMapper, CorpInfoSnapshotDO>
implements CorpInfoSnapshotRepository {
}

View File

@ -0,0 +1,13 @@
package com.zcloud.primeport.persistence.repository.impl;
import com.jjb.saas.framework.repository.repo.impl.BaseRepositoryImpl;
import com.zcloud.primeport.persistence.dataobject.DaHuaDepartmentCorpMappingDO;
import com.zcloud.primeport.persistence.mapper.DaHuaDepartmentCorpMappingMapper;
import com.zcloud.primeport.persistence.repository.DaHuaDepartmentCorpMappingRepository;
import org.springframework.stereotype.Service;
@Service
public class DaHuaDepartmentCorpMappingRepositoryImpl
extends BaseRepositoryImpl<DaHuaDepartmentCorpMappingMapper, DaHuaDepartmentCorpMappingDO>
implements DaHuaDepartmentCorpMappingRepository {
}

View File

@ -0,0 +1,13 @@
package com.zcloud.primeport.persistence.repository.impl;
import com.jjb.saas.framework.repository.repo.impl.BaseRepositoryImpl;
import com.zcloud.primeport.persistence.dataobject.DaHuaDeviceDO;
import com.zcloud.primeport.persistence.mapper.DaHuaDeviceMapper;
import com.zcloud.primeport.persistence.repository.DaHuaDeviceRepository;
import org.springframework.stereotype.Service;
@Service
public class DaHuaDeviceRepositoryImpl
extends BaseRepositoryImpl<DaHuaDeviceMapper, DaHuaDeviceDO>
implements DaHuaDeviceRepository {
}