优化人员同步与实时定位处理
parent
de78e0c26a
commit
8bbe0e7932
|
|
@ -11,11 +11,12 @@ import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refreshes the persisted online/offline state for FindS personnel.
|
* Refreshes the persisted online/offline state for FindS personnel.
|
||||||
|
|
@ -33,7 +34,6 @@ public class FindsStaffLocationStatusSyncService {
|
||||||
private static final int STAFF_COUNT_PAGE_SIZE = 1;
|
private static final int STAFF_COUNT_PAGE_SIZE = 1;
|
||||||
private static final int TERMINAL_PAGE_SIZE = 2000;
|
private static final int TERMINAL_PAGE_SIZE = 2000;
|
||||||
private static final int MAX_TERMINAL_PAGE_COUNT = 100;
|
private static final int MAX_TERMINAL_PAGE_COUNT = 100;
|
||||||
private static final int BATCH_CONCURRENCY = 4;
|
|
||||||
private static final int FULL_SYNC_INTERVAL_HOURS = 24;
|
private static final int FULL_SYNC_INTERVAL_HOURS = 24;
|
||||||
private static final List<String> ONLINE_DEVICE_STATUSES =
|
private static final List<String> ONLINE_DEVICE_STATUSES =
|
||||||
Arrays.asList("STATIC", "MOVING");
|
Arrays.asList("STATIC", "MOVING");
|
||||||
|
|
@ -51,15 +51,14 @@ public class FindsStaffLocationStatusSyncService {
|
||||||
|| fullSyncDue(syncTime);
|
|| fullSyncDue(syncTime);
|
||||||
int fullSyncCount = fullSync ? mappingSyncService.syncAll() : 0;
|
int fullSyncCount = fullSync ? mappingSyncService.syncAll() : 0;
|
||||||
|
|
||||||
Map<String, OnlineTerminal> onlineTerminals = loadOnlineTerminals();
|
StatusSyncCounter statusCounter = synchronizeOnlineTerminals(syncTime);
|
||||||
int onlineMappings = updateLocationStatuses(onlineTerminals, syncTime);
|
personnelMatchRepository.markStaleOffline(SOURCE_CONFIG_CODE, syncTime);
|
||||||
int onlineStaffTerminals = countStaffTerminals(onlineTerminals);
|
|
||||||
LOGGER.info("FindS personnel location status synchronization completed, remoteStaffCount={}, "
|
LOGGER.info("FindS personnel location status synchronization completed, remoteStaffCount={}, "
|
||||||
+ "localStaffCount={}, fullSync={}, fullSyncCount={}, onlineTerminals={}, onlineMappings={}",
|
+ "localStaffCount={}, fullSync={}, fullSyncCount={}, onlineTerminals={}, onlineMappings={}",
|
||||||
remoteStaffCount, localStaffCount, fullSync, fullSyncCount,
|
remoteStaffCount, localStaffCount, fullSync, fullSyncCount,
|
||||||
onlineStaffTerminals, onlineMappings);
|
statusCounter.onlineStaffTerminals, statusCounter.onlineMappings);
|
||||||
return new SyncSummary(remoteStaffCount, localStaffCount, fullSync, fullSyncCount,
|
return new SyncSummary(remoteStaffCount, localStaffCount, fullSync, fullSyncCount,
|
||||||
onlineStaffTerminals, onlineMappings);
|
statusCounter.onlineStaffTerminals, statusCounter.onlineMappings);
|
||||||
}
|
}
|
||||||
|
|
||||||
private long queryRemoteStaffCount() {
|
private long queryRemoteStaffCount() {
|
||||||
|
|
@ -85,15 +84,17 @@ public class FindsStaffLocationStatusSyncService {
|
||||||
|| lastSyncTime.isBefore(syncTime.minusHours(FULL_SYNC_INTERVAL_HOURS));
|
|| lastSyncTime.isBefore(syncTime.minusHours(FULL_SYNC_INTERVAL_HOURS));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, OnlineTerminal> loadOnlineTerminals() {
|
private StatusSyncCounter synchronizeOnlineTerminals(LocalDateTime syncTime) {
|
||||||
Map<String, OnlineTerminal> result = new LinkedHashMap<>();
|
StatusSyncCounter counter = new StatusSyncCounter();
|
||||||
for (String status : ONLINE_DEVICE_STATUSES) {
|
for (String status : ONLINE_DEVICE_STATUSES) {
|
||||||
loadOnlineTerminals(status, result);
|
synchronizeOnlineTerminals(status, syncTime, counter);
|
||||||
}
|
}
|
||||||
return result;
|
return counter;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadOnlineTerminals(String deviceStatus, Map<String, OnlineTerminal> result) {
|
private void synchronizeOnlineTerminals(String deviceStatus,
|
||||||
|
LocalDateTime syncTime,
|
||||||
|
StatusSyncCounter counter) {
|
||||||
JsonNode firstData = successData(findsOpenApiClient.postFailFast(
|
JsonNode firstData = successData(findsOpenApiClient.postFailFast(
|
||||||
TERMINAL_LIST_API, terminalPageRequest(deviceStatus, 1)));
|
TERMINAL_LIST_API, terminalPageRequest(deviceStatus, 1)));
|
||||||
JsonNode firstRows = extractRows(firstData);
|
JsonNode firstRows = extractRows(firstData);
|
||||||
|
|
@ -101,7 +102,7 @@ public class FindsStaffLocationStatusSyncService {
|
||||||
if (total > 0 && (firstRows == null || firstRows.size() == 0)) {
|
if (total > 0 && (firstRows == null || firstRows.size() == 0)) {
|
||||||
throw new BizException("FindS terminal status page is empty while total is " + total);
|
throw new BizException("FindS terminal status page is empty while total is " + total);
|
||||||
}
|
}
|
||||||
appendOnlineTerminals(result, firstRows, deviceStatus);
|
synchronizeTerminalPage(firstRows, deviceStatus, syncTime, counter);
|
||||||
|
|
||||||
int effectivePageSize = firstRows == null || firstRows.size() == 0
|
int effectivePageSize = firstRows == null || firstRows.size() == 0
|
||||||
? TERMINAL_PAGE_SIZE : firstRows.size();
|
? TERMINAL_PAGE_SIZE : firstRows.size();
|
||||||
|
|
@ -109,20 +110,13 @@ public class FindsStaffLocationStatusSyncService {
|
||||||
if (pageCount > MAX_TERMINAL_PAGE_COUNT) {
|
if (pageCount > MAX_TERMINAL_PAGE_COUNT) {
|
||||||
throw new BizException("FindS online terminal pages exceed the configured safety limit: " + pageCount);
|
throw new BizException("FindS online terminal pages exceed the configured safety limit: " + pageCount);
|
||||||
}
|
}
|
||||||
if (pageCount <= 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Map<String, Object>> requests = new ArrayList<>(pageCount - 1);
|
|
||||||
for (int pageNo = 2; pageNo <= pageCount; pageNo++) {
|
|
||||||
requests.add(terminalPageRequest(deviceStatus, pageNo));
|
|
||||||
}
|
|
||||||
long receivedCount = firstRows == null ? 0 : firstRows.size();
|
long receivedCount = firstRows == null ? 0 : firstRows.size();
|
||||||
for (JsonNode root : findsOpenApiClient.postFailFastBatch(
|
for (int pageNo = 2; pageNo <= pageCount; pageNo++) {
|
||||||
TERMINAL_LIST_API, requests, BATCH_CONCURRENCY)) {
|
JsonNode data = successData(findsOpenApiClient.postFailFast(
|
||||||
JsonNode rows = extractRows(successData(root));
|
TERMINAL_LIST_API, terminalPageRequest(deviceStatus, pageNo)));
|
||||||
|
JsonNode rows = extractRows(data);
|
||||||
receivedCount += rows == null ? 0 : rows.size();
|
receivedCount += rows == null ? 0 : rows.size();
|
||||||
appendOnlineTerminals(result, rows, deviceStatus);
|
synchronizeTerminalPage(rows, deviceStatus, syncTime, counter);
|
||||||
}
|
}
|
||||||
if (receivedCount < total) {
|
if (receivedCount < total) {
|
||||||
throw new BizException("FindS online terminal result is incomplete: received="
|
throw new BizException("FindS online terminal result is incomplete: received="
|
||||||
|
|
@ -138,9 +132,10 @@ public class FindsStaffLocationStatusSyncService {
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void appendOnlineTerminals(Map<String, OnlineTerminal> result,
|
private void synchronizeTerminalPage(JsonNode rows,
|
||||||
JsonNode rows,
|
String requestedStatus,
|
||||||
String requestedStatus) {
|
LocalDateTime syncTime,
|
||||||
|
StatusSyncCounter counter) {
|
||||||
if (rows == null || !rows.isArray()) {
|
if (rows == null || !rows.isArray()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -150,6 +145,9 @@ public class FindsStaffLocationStatusSyncService {
|
||||||
if (!StringUtils.hasText(terminalNo)) {
|
if (!StringUtils.hasText(terminalNo)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (!counter.seenTerminalNos.add(terminalNo)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
String trackType = firstText(
|
String trackType = firstText(
|
||||||
text(firstPresent(row, "trackType")),
|
text(firstPresent(row, "trackType")),
|
||||||
text(firstPresent(row.path("track"), "trackType")));
|
text(firstPresent(row.path("track"), "trackType")));
|
||||||
|
|
@ -159,35 +157,16 @@ public class FindsStaffLocationStatusSyncService {
|
||||||
boolean staffTrack = "STAFF".equalsIgnoreCase(trackType) && StringUtils.hasText(trackNo);
|
boolean staffTrack = "STAFF".equalsIgnoreCase(trackType) && StringUtils.hasText(trackNo);
|
||||||
String actualStatus = firstText(
|
String actualStatus = firstText(
|
||||||
text(firstPresent(row, "deviceStatus", "status", "onlineStatus")), requestedStatus);
|
text(firstPresent(row, "deviceStatus", "status", "onlineStatus")), requestedStatus);
|
||||||
result.put(terminalNo, new OnlineTerminal(
|
String staffNo = staffTrack ? trackNo.trim() : null;
|
||||||
terminalNo, actualStatus, staffTrack ? trackNo.trim() : null));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int updateLocationStatuses(Map<String, OnlineTerminal> onlineTerminals,
|
|
||||||
LocalDateTime syncTime) {
|
|
||||||
int matched = 0;
|
|
||||||
for (OnlineTerminal terminal : onlineTerminals.values()) {
|
|
||||||
personnelMatchRepository.refreshTerminalBinding(
|
personnelMatchRepository.refreshTerminalBinding(
|
||||||
SOURCE_CONFIG_CODE, terminal.terminalNo, terminal.staffNo, syncTime);
|
SOURCE_CONFIG_CODE, terminalNo, staffNo, syncTime);
|
||||||
if (!StringUtils.hasText(terminal.staffNo)) {
|
if (!StringUtils.hasText(staffNo)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
matched += personnelMatchRepository.updateOnline(
|
counter.onlineStaffTerminals++;
|
||||||
SOURCE_CONFIG_CODE, terminal.terminalNo, terminal.deviceStatus, syncTime);
|
counter.onlineMappings += personnelMatchRepository.updateOnline(
|
||||||
|
SOURCE_CONFIG_CODE, terminalNo, actualStatus, syncTime);
|
||||||
}
|
}
|
||||||
personnelMatchRepository.markStaleOffline(SOURCE_CONFIG_CODE, syncTime);
|
|
||||||
return matched;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int countStaffTerminals(Map<String, OnlineTerminal> onlineTerminals) {
|
|
||||||
int count = 0;
|
|
||||||
for (OnlineTerminal terminal : onlineTerminals.values()) {
|
|
||||||
if (StringUtils.hasText(terminal.staffNo)) {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private JsonNode successData(JsonNode root) {
|
private JsonNode successData(JsonNode root) {
|
||||||
|
|
@ -260,16 +239,10 @@ public class FindsStaffLocationStatusSyncService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class OnlineTerminal {
|
private static class StatusSyncCounter {
|
||||||
private final String terminalNo;
|
private final Set<String> seenTerminalNos = new HashSet<>();
|
||||||
private final String deviceStatus;
|
private int onlineStaffTerminals;
|
||||||
private final String staffNo;
|
private int onlineMappings;
|
||||||
|
|
||||||
private OnlineTerminal(String terminalNo, String deviceStatus, String staffNo) {
|
|
||||||
this.terminalNo = terminalNo;
|
|
||||||
this.deviceStatus = deviceStatus;
|
|
||||||
this.staffNo = staffNo;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class SyncSummary {
|
public static class SyncSummary {
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,6 @@ public class FindsStaffMappingSyncService {
|
||||||
private static final String SOURCE_CONFIG_CODE = "FINDS";
|
private static final String SOURCE_CONFIG_CODE = "FINDS";
|
||||||
private static final int PAGE_SIZE = 2000;
|
private static final int PAGE_SIZE = 2000;
|
||||||
private static final int MAX_PAGE_COUNT = 50;
|
private static final int MAX_PAGE_COUNT = 50;
|
||||||
private static final int BATCH_CONCURRENCY = 4;
|
|
||||||
|
|
||||||
private final FindsOpenApiClient findsOpenApiClient;
|
private final FindsOpenApiClient findsOpenApiClient;
|
||||||
private final PositionPersonLocalLookupService localLookupService;
|
private final PositionPersonLocalLookupService localLookupService;
|
||||||
|
|
@ -51,62 +50,62 @@ public class FindsStaffMappingSyncService {
|
||||||
/** 拉取 FindS 完整组织路径并单独刷新企业映射,不执行人员映射落库。 */
|
/** 拉取 FindS 完整组织路径并单独刷新企业映射,不执行人员映射落库。 */
|
||||||
public FindsCorpMappingService.PathSyncSummary syncOrganizations() {
|
public FindsCorpMappingService.PathSyncSummary syncOrganizations() {
|
||||||
LocalDateTime syncTime = LocalDateTime.now().withNano(0);
|
LocalDateTime syncTime = LocalDateTime.now().withNano(0);
|
||||||
return synchronizeOrganizations(loadAllStaff(), syncTime);
|
Set<List<String>> orgPaths = collectOrganizationPaths();
|
||||||
|
FindsCorpMappingService.PathSyncSummary summary = synchronizeOrganizationPaths(orgPaths, syncTime);
|
||||||
|
if (orgPaths.isEmpty()) {
|
||||||
|
forEachStaffPage(rows -> resolveOrganizations(rows));
|
||||||
|
}
|
||||||
|
return summary;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int syncAll() {
|
public int syncAll() {
|
||||||
// The schema uses second precision; keep the same precision for the stale-row cutoff.
|
// 数据库时间精度为秒,保持同一精度才能准确识别本次未出现的旧数据。
|
||||||
LocalDateTime syncTime = LocalDateTime.now().withNano(0);
|
LocalDateTime syncTime = LocalDateTime.now().withNano(0);
|
||||||
List<StaffSnapshot> staffRows = loadAllStaff();
|
Set<List<String>> orgPaths = collectOrganizationPaths();
|
||||||
FindsCorpMappingService.PathSyncSummary orgSummary =
|
FindsCorpMappingService.PathSyncSummary orgSummary = synchronizeOrganizationPaths(orgPaths, syncTime);
|
||||||
synchronizeOrganizations(staffRows, syncTime);
|
|
||||||
PersonIndexes localPeople = new PersonIndexes(localLookupService.findAllPeople());
|
PersonIndexes localPeople = new PersonIndexes(localLookupService.findAllPeople());
|
||||||
int matchedCount = 0;
|
SyncCounter counter = new SyncCounter();
|
||||||
for (StaffSnapshot row : staffRows) {
|
forEachStaffPage(rows -> syncStaffPage(
|
||||||
LocalMatch localMatch = localPeople.match(row);
|
rows, localPeople, syncTime, orgPaths.isEmpty(), counter));
|
||||||
upsert(row, localMatch, syncTime);
|
|
||||||
if (localMatch.person != null) {
|
|
||||||
matchedCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
disableMissingRows(syncTime);
|
disableMissingRows(syncTime);
|
||||||
realtimePersonRoutingCache.refreshNow();
|
realtimePersonRoutingCache.refreshNow();
|
||||||
LOGGER.info("FindS staff mapping synchronization completed, total={}, matched={}, "
|
LOGGER.info("FindS staff mapping synchronization completed, total={}, matched={}, "
|
||||||
+ "orgPaths={}, orgNodes={}, orgMatched={}, orgAmbiguous={}, orgUnmatched={}",
|
+ "orgPaths={}, orgNodes={}, orgMatched={}, orgAmbiguous={}, orgUnmatched={}",
|
||||||
staffRows.size(), matchedCount, orgSummary.getDistinctPathCount(),
|
counter.total, counter.matched, orgSummary.getDistinctPathCount(),
|
||||||
orgSummary.getDistinctNodeCount(), orgSummary.getMatchedCount(),
|
orgSummary.getDistinctNodeCount(), orgSummary.getMatchedCount(),
|
||||||
orgSummary.getAmbiguousCount(), orgSummary.getUnmatchedCount());
|
orgSummary.getAmbiguousCount(), orgSummary.getUnmatchedCount());
|
||||||
return staffRows.size();
|
return counter.total;
|
||||||
}
|
}
|
||||||
|
|
||||||
private FindsCorpMappingService.PathSyncSummary synchronizeOrganizations(
|
private Set<List<String>> collectOrganizationPaths() {
|
||||||
List<StaffSnapshot> staffRows, LocalDateTime syncTime) {
|
|
||||||
Set<List<String>> orgPaths = new LinkedHashSet<>();
|
Set<List<String>> orgPaths = new LinkedHashSet<>();
|
||||||
for (StaffSnapshot row : staffRows) {
|
forEachStaffPage(rows -> {
|
||||||
if (row.orgNamePath != null && !row.orgNamePath.isEmpty()) {
|
for (JsonNode row : rows) {
|
||||||
orgPaths.add(row.orgNamePath);
|
List<String> path = orgNamePath(row.path("orgNamePath"));
|
||||||
|
if (!path.isEmpty()) {
|
||||||
|
orgPaths.add(path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
return orgPaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
private FindsCorpMappingService.PathSyncSummary synchronizeOrganizationPaths(
|
||||||
|
Set<List<String>> orgPaths, LocalDateTime syncTime) {
|
||||||
FindsCorpMappingService.PathSyncSummary orgSummary =
|
FindsCorpMappingService.PathSyncSummary orgSummary =
|
||||||
findsCorpMappingService.synchronizePaths(orgPaths, syncTime);
|
findsCorpMappingService.synchronizePaths(orgPaths, syncTime);
|
||||||
if (orgSummary == null) {
|
if (orgSummary == null) {
|
||||||
orgSummary = FindsCorpMappingService.PathSyncSummary.empty();
|
orgSummary = FindsCorpMappingService.PathSyncSummary.empty();
|
||||||
}
|
}
|
||||||
lastOrgPathSummary = orgSummary;
|
lastOrgPathSummary = orgSummary;
|
||||||
if (orgPaths.isEmpty()) {
|
|
||||||
for (StaffSnapshot row : staffRows) {
|
|
||||||
findsCorpMappingService.resolve(SOURCE_CONFIG_CODE, row.findsCorpinfoId,
|
|
||||||
row.corpinfoName, row.departmentName, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return orgSummary;
|
return orgSummary;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<StaffSnapshot> loadAllStaff() {
|
private void forEachStaffPage(StaffPageConsumer consumer) {
|
||||||
List<StaffSnapshot> result = new ArrayList<>();
|
|
||||||
JsonNode firstData = successData(
|
JsonNode firstData = successData(
|
||||||
findsOpenApiClient.postFailFast(STAFF_LIST_API, pageRequest(1)));
|
findsOpenApiClient.postFailFast(STAFF_LIST_API, pageRequest(1)));
|
||||||
int firstPageSize = appendRows(result, extractRows(firstData));
|
JsonNode firstRows = extractRows(firstData);
|
||||||
|
int firstPageSize = rowCount(firstRows);
|
||||||
long total = requiredTotal(firstData);
|
long total = requiredTotal(firstData);
|
||||||
if (total > 0 && firstPageSize == 0) {
|
if (total > 0 && firstPageSize == 0) {
|
||||||
throw new BizException("FindS staff page is empty while total is " + total);
|
throw new BizException("FindS staff page is empty while total is " + total);
|
||||||
|
|
@ -115,23 +114,19 @@ public class FindsStaffMappingSyncService {
|
||||||
if (pageCount > MAX_PAGE_COUNT) {
|
if (pageCount > MAX_PAGE_COUNT) {
|
||||||
throw new BizException("FindS staff pages exceed the configured safety limit: " + pageCount);
|
throw new BizException("FindS staff pages exceed the configured safety limit: " + pageCount);
|
||||||
}
|
}
|
||||||
if (pageCount <= 1) {
|
consumer.accept(firstRows);
|
||||||
return result;
|
|
||||||
}
|
|
||||||
List<Map<String, Object>> requests = new ArrayList<>(pageCount - 1);
|
|
||||||
for (int pageNo = 2; pageNo <= pageCount; pageNo++) {
|
|
||||||
requests.add(pageRequest(pageNo));
|
|
||||||
}
|
|
||||||
long receivedCount = firstPageSize;
|
long receivedCount = firstPageSize;
|
||||||
for (JsonNode root : findsOpenApiClient.postFailFastBatch(
|
for (int pageNo = 2; pageNo <= pageCount; pageNo++) {
|
||||||
STAFF_LIST_API, requests, BATCH_CONCURRENCY)) {
|
JsonNode data = successData(
|
||||||
receivedCount += appendRows(result, extractRows(successData(root)));
|
findsOpenApiClient.postFailFast(STAFF_LIST_API, pageRequest(pageNo)));
|
||||||
|
JsonNode rows = extractRows(data);
|
||||||
|
receivedCount += rowCount(rows);
|
||||||
|
consumer.accept(rows);
|
||||||
}
|
}
|
||||||
if (receivedCount < total) {
|
if (receivedCount < total) {
|
||||||
throw new BizException("FindS staff result is incomplete: received="
|
throw new BizException("FindS staff result is incomplete: received="
|
||||||
+ receivedCount + ", total=" + total);
|
+ receivedCount + ", total=" + total);
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> pageRequest(int pageNo) {
|
private Map<String, Object> pageRequest(int pageNo) {
|
||||||
|
|
@ -141,17 +136,47 @@ public class FindsStaffMappingSyncService {
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int appendRows(List<StaffSnapshot> result, JsonNode rows) {
|
private int rowCount(JsonNode rows) {
|
||||||
|
return rows != null && rows.isArray() ? rows.size() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resolveOrganizations(JsonNode rows) {
|
||||||
if (rows == null || !rows.isArray()) {
|
if (rows == null || !rows.isArray()) {
|
||||||
return 0;
|
return;
|
||||||
}
|
}
|
||||||
for (JsonNode row : rows) {
|
for (JsonNode row : rows) {
|
||||||
StaffSnapshot snapshot = map(row);
|
StaffSnapshot snapshot = map(row);
|
||||||
if (snapshot != null) {
|
if (snapshot != null) {
|
||||||
result.add(snapshot);
|
findsCorpMappingService.resolve(SOURCE_CONFIG_CODE, snapshot.findsCorpinfoId,
|
||||||
|
snapshot.corpinfoName, snapshot.departmentName, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void syncStaffPage(JsonNode rows,
|
||||||
|
PersonIndexes localPeople,
|
||||||
|
LocalDateTime syncTime,
|
||||||
|
boolean resolveOrganization,
|
||||||
|
SyncCounter counter) {
|
||||||
|
if (rows == null || !rows.isArray()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (JsonNode row : rows) {
|
||||||
|
StaffSnapshot snapshot = map(row);
|
||||||
|
if (snapshot == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (resolveOrganization) {
|
||||||
|
findsCorpMappingService.resolve(SOURCE_CONFIG_CODE, snapshot.findsCorpinfoId,
|
||||||
|
snapshot.corpinfoName, snapshot.departmentName, null);
|
||||||
|
}
|
||||||
|
LocalMatch localMatch = localPeople.match(snapshot);
|
||||||
|
upsert(snapshot, localMatch, syncTime);
|
||||||
|
counter.total++;
|
||||||
|
if (localMatch.person != null) {
|
||||||
|
counter.matched++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return rows.size();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private StaffSnapshot map(JsonNode row) {
|
private StaffSnapshot map(JsonNode row) {
|
||||||
|
|
@ -355,6 +380,16 @@ public class FindsStaffMappingSyncService {
|
||||||
private String terminalNo;
|
private String terminalNo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
private interface StaffPageConsumer {
|
||||||
|
void accept(JsonNode rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class SyncCounter {
|
||||||
|
private int total;
|
||||||
|
private int matched;
|
||||||
|
}
|
||||||
|
|
||||||
private static class PersonIndexes {
|
private static class PersonIndexes {
|
||||||
private final UniqueIndex idCards = new UniqueIndex();
|
private final UniqueIndex idCards = new UniqueIndex();
|
||||||
private final UniqueIndex usernames = new UniqueIndex();
|
private final UniqueIndex usernames = new UniqueIndex();
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,9 @@ import com.zcloud.personnel.positioning.integration.realtime.RealtimeLocationMes
|
||||||
import com.zcloud.personnel.positioning.integration.realtime.RealtimePersonRoutingCache;
|
import com.zcloud.personnel.positioning.integration.realtime.RealtimePersonRoutingCache;
|
||||||
import com.zcloud.personnel.positioning.integration.realtime.RealtimePersonRoutingCache.PersonRoute;
|
import com.zcloud.personnel.positioning.integration.realtime.RealtimePersonRoutingCache.PersonRoute;
|
||||||
import org.apache.kafka.clients.consumer.ConsumerRecord;
|
import org.apache.kafka.clients.consumer.ConsumerRecord;
|
||||||
import org.apache.kafka.common.header.Header;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.ObjectProvider;
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.kafka.annotation.KafkaListener;
|
import org.springframework.kafka.annotation.KafkaListener;
|
||||||
import org.springframework.kafka.support.Acknowledgment;
|
import org.springframework.kafka.support.Acknowledgment;
|
||||||
|
|
@ -27,7 +25,6 @@ import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.StringJoiner;
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@ConditionalOnProperty(
|
@ConditionalOnProperty(
|
||||||
|
|
@ -38,19 +35,16 @@ import java.util.StringJoiner;
|
||||||
public class LocationKafkaProbeListener {
|
public class LocationKafkaProbeListener {
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(LocationKafkaProbeListener.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(LocationKafkaProbeListener.class);
|
||||||
|
|
||||||
private final int maxLogBytes;
|
|
||||||
private final RealtimePersonLocationMatcher locationMatcher;
|
private final RealtimePersonLocationMatcher locationMatcher;
|
||||||
private final RealtimePersonRoutingCache personRoutingCache;
|
private final RealtimePersonRoutingCache personRoutingCache;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
private final ObjectProvider<RealtimeLocationPublisher> publishers;
|
private final ObjectProvider<RealtimeLocationPublisher> publishers;
|
||||||
|
|
||||||
public LocationKafkaProbeListener(
|
public LocationKafkaProbeListener(
|
||||||
@Value("${personnel-positioning.kafka.location.max-log-bytes:16384}") int maxLogBytes,
|
|
||||||
RealtimePersonLocationMatcher locationMatcher,
|
RealtimePersonLocationMatcher locationMatcher,
|
||||||
RealtimePersonRoutingCache personRoutingCache,
|
RealtimePersonRoutingCache personRoutingCache,
|
||||||
ObjectMapper objectMapper,
|
ObjectMapper objectMapper,
|
||||||
ObjectProvider<RealtimeLocationPublisher> publishers) {
|
ObjectProvider<RealtimeLocationPublisher> publishers) {
|
||||||
this.maxLogBytes = Math.max(1, maxLogBytes);
|
|
||||||
this.locationMatcher = locationMatcher;
|
this.locationMatcher = locationMatcher;
|
||||||
this.personRoutingCache = personRoutingCache;
|
this.personRoutingCache = personRoutingCache;
|
||||||
this.objectMapper = objectMapper;
|
this.objectMapper = objectMapper;
|
||||||
|
|
@ -63,21 +57,6 @@ public class LocationKafkaProbeListener {
|
||||||
groupId = "${personnel-positioning.kafka.location.group-id:personnel-position-location-probe}"
|
groupId = "${personnel-positioning.kafka.location.group-id:personnel-position-location-probe}"
|
||||||
)
|
)
|
||||||
public void onMessage(ConsumerRecord<byte[], byte[]> record, Acknowledgment acknowledgment) {
|
public void onMessage(ConsumerRecord<byte[], byte[]> record, Acknowledgment acknowledgment) {
|
||||||
LOGGER.info(
|
|
||||||
"收到Kafka定位原始消息,topic={}, partition={}, offset={}, timestamp={}, "
|
|
||||||
+ "timestampType={}, keyBytes={}, valueBytes={}, key={}, headers={}, value={}",
|
|
||||||
record.topic(),
|
|
||||||
record.partition(),
|
|
||||||
record.offset(),
|
|
||||||
record.timestamp(),
|
|
||||||
record.timestampType(),
|
|
||||||
length(record.key()),
|
|
||||||
length(record.value()),
|
|
||||||
LocationKafkaPayloadPreview.render(record.key(), maxLogBytes),
|
|
||||||
renderHeaders(record),
|
|
||||||
LocationKafkaPayloadPreview.render(record.value(), maxLogBytes)
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
List<BiPersonLocationCO> locations = locationMatcher.match(record.value());
|
List<BiPersonLocationCO> locations = locationMatcher.match(record.value());
|
||||||
int deliveries = 0;
|
int deliveries = 0;
|
||||||
|
|
@ -91,9 +70,6 @@ public class LocationKafkaProbeListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
acknowledgment.acknowledge();
|
acknowledgment.acknowledge();
|
||||||
LOGGER.info("Kafka location message processed, topic={}, partition={}, offset={}, converted={}, "
|
|
||||||
+ "websocketDeliveries={}",
|
|
||||||
record.topic(), record.partition(), record.offset(), locations.size(), deliveries);
|
|
||||||
} catch (RealtimeLocationPayloadException e) {
|
} catch (RealtimeLocationPayloadException e) {
|
||||||
acknowledgment.acknowledge();
|
acknowledgment.acknowledge();
|
||||||
LOGGER.warn("Ignored invalid Kafka location message, topic={}, partition={}, offset={}, reason={}",
|
LOGGER.warn("Ignored invalid Kafka location message, topic={}, partition={}, offset={}, reason={}",
|
||||||
|
|
@ -140,15 +116,4 @@ public class LocationKafkaProbeListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int length(byte[] value) {
|
|
||||||
return value == null ? 0 : value.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String renderHeaders(ConsumerRecord<byte[], byte[]> record) {
|
|
||||||
StringJoiner joiner = new StringJoiner(", ", "[", "]");
|
|
||||||
for (Header header : record.headers()) {
|
|
||||||
joiner.add(header.key() + "=" + LocationKafkaPayloadPreview.render(header.value(), maxLogBytes));
|
|
||||||
}
|
|
||||||
return joiner.toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
-- 保存实时定位点匹配出的当前区域;不保存实时经纬度和轨迹点。
|
||||||
|
SET @ddl = IF(
|
||||||
|
(SELECT COUNT(1) FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'personnel_match' AND COLUMN_NAME = 'location_area_id') = 0,
|
||||||
|
'ALTER TABLE `personnel_match` ADD COLUMN `location_area_id` bigint DEFAULT NULL COMMENT ''当前定位区域id'' AFTER `location_status_time`',
|
||||||
|
'DO 0'
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @ddl;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
SET @ddl = IF(
|
||||||
|
(SELECT COUNT(1) FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'personnel_match' AND COLUMN_NAME = 'location_area_name') = 0,
|
||||||
|
'ALTER TABLE `personnel_match` ADD COLUMN `location_area_name` varchar(255) DEFAULT NULL COMMENT ''当前定位区域名称'' AFTER `location_area_id`',
|
||||||
|
'DO 0'
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @ddl;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
SET @ddl = IF(
|
||||||
|
(SELECT COUNT(1) FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'personnel_match' AND COLUMN_NAME = 'location_area_time') = 0,
|
||||||
|
'ALTER TABLE `personnel_match` ADD COLUMN `location_area_time` datetime DEFAULT NULL COMMENT ''定位区域状态更新时间'' AFTER `location_area_name`',
|
||||||
|
'DO 0'
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @ddl;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
SET @ddl = IF(
|
||||||
|
(SELECT COUNT(1) FROM information_schema.STATISTICS
|
||||||
|
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'personnel_match' AND INDEX_NAME = 'idx_personnel_match_location_area') = 0,
|
||||||
|
'ALTER TABLE `personnel_match` ADD KEY `idx_personnel_match_location_area` (`location_area_id`, `location_status`)',
|
||||||
|
'DO 0'
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @ddl;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
-- 组织名称标准化匹配类型超过 32 个字符,扩容以保证人员全量同步可以继续执行。
|
||||||
|
ALTER TABLE `org_match`
|
||||||
|
MODIFY COLUMN `match_type` varchar(64) DEFAULT NULL COMMENT '组织匹配方式';
|
||||||
Loading…
Reference in New Issue