1、优化

master
shenzhidan 2026-08-21 14:25:15 +08:00
parent a43e9b0bbf
commit 340626e83b
13 changed files with 334 additions and 129 deletions

View File

@ -5,6 +5,7 @@ import com.zcloud.personnel.positioning.integration.realtime.RealtimeLocationMes
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.TextMessage;
@ -87,6 +88,15 @@ public class PersonLocationWebSocketHandler extends TextWebSocketHandler impleme
return sessions.size(); return sessions.size();
} }
@Scheduled(fixedDelayString = "${personnel-positioning.websocket.location.session-cleanup-ms:30000}")
public void cleanupClosedSessions() {
for (Map.Entry<String, SubscriptionSession> entry : sessions.entrySet()) {
if (!entry.getValue().session.isOpen()) {
sessions.remove(entry.getKey(), entry.getValue());
}
}
}
private void removeAndClose(String sessionId) { private void removeAndClose(String sessionId) {
SubscriptionSession subscriptionSession = sessions.remove(sessionId); SubscriptionSession subscriptionSession = sessions.remove(sessionId);
WebSocketSession session = subscriptionSession == null ? null : subscriptionSession.session; WebSocketSession session = subscriptionSession == null ? null : subscriptionSession.session;

View File

@ -35,6 +35,9 @@ public class BiOverviewCache {
if (local != null && local.expireAt > now) { if (local != null && local.expireAt > now) {
return local.value; return local.value;
} }
if (local != null) {
localCache.remove(key, local);
}
if (stringRedisTemplate == null) { if (stringRedisTemplate == null) {
return null; return null;
} }

View File

@ -229,23 +229,27 @@ public class PersonnelPositioningBiQueryExe {
public PageResponse<BiAreaFenceCO> listCompanyFence(BiCompanyFenceQry qry) { public PageResponse<BiAreaFenceCO> listCompanyFence(BiCompanyFenceQry qry) {
BiCompanyFenceQry query = qry == null ? new BiCompanyFenceQry() : qry; BiCompanyFenceQry query = qry == null ? new BiCompanyFenceQry() : qry;
List<BiAreaFenceCO> matched = new ArrayList<>();
for (BiAreaFenceCO fence : listAllAreaFence()) {
if (matchesCompanyFence(fence, query)) {
matched.add(fence);
}
}
int pageNo = Math.max(query.getPageIndex(), 1); int pageNo = Math.max(query.getPageIndex(), 1);
int pageSize = Math.max(query.getPageSize(), 1); int pageSize = Math.max(query.getPageSize(), 1);
int fromIndex = Math.min((pageNo - 1) * pageSize, matched.size()); QueryWrapper<FenceDO> wrapper = companyFenceQuery(query);
int toIndex = Math.min(fromIndex + pageSize, matched.size()); long total = fenceRepository.count(wrapper);
return PageResponse.of(matched.subList(fromIndex, toIndex), matched.size(), pageSize, pageNo); wrapper.orderByDesc("update_time", "id");
wrapper.last("LIMIT " + pageSize + " OFFSET " + ((pageNo - 1) * pageSize));
return PageResponse.of(mapLocalFences(fenceRepository.list(wrapper)),
safeLongToInt(total), pageSize, pageNo);
} }
public PageResponse<BiPersonLocationCO> listPersonLocation(BiPersonLocationQry qry) { public PageResponse<BiPersonLocationCO> listPersonLocation(BiPersonLocationQry qry) {
BiPersonLocationQry query = qry == null ? new BiPersonLocationQry() : qry; BiPersonLocationQry query = qry == null ? new BiPersonLocationQry() : qry;
int pageNo = Math.max(query.getPageIndex(), 1); int pageNo = Math.max(query.getPageIndex(), 1);
int pageSize = Math.max(query.getPageSize(), 1); int pageSize = Math.max(query.getPageSize(), 1);
if (query.getCorpinfoId() != null && localLookupService.hasPersonnelMappings()) {
PageResponse<PositionPersonLocalLookupService.PersonMapping> mappingPage =
localLookupService.findMatchedPersonnelPage(query);
List<BiPersonLocationCO> pageRows = loadPersistedPersonLocations(
query, mappingPage.getData());
return PageResponse.of(pageRows, mappingPage.getTotalCount(), pageSize, pageNo);
}
List<BiPersonLocationCO> rows = loadMatchedPersonLocations(query); List<BiPersonLocationCO> rows = loadMatchedPersonLocations(query);
int fromIndex = Math.min((pageNo - 1) * pageSize, rows.size()); int fromIndex = Math.min((pageNo - 1) * pageSize, rows.size());
int toIndex = Math.min(fromIndex + pageSize, rows.size()); int toIndex = Math.min(fromIndex + pageSize, rows.size());
@ -526,11 +530,19 @@ public class PersonnelPositioningBiQueryExe {
localQuery.setCorpinfoName(query.getCompanyName()); localQuery.setCorpinfoName(query.getCompanyName());
localQuery.setStaffName(query.getStaffName()); localQuery.setStaffName(query.getStaffName());
return loadPersistedPersonLocations(query, localLookupService.findMatchedPersonnel(localQuery));
}
private List<BiPersonLocationCO> loadPersistedPersonLocations(
BiPersonLocationQry query,
List<PositionPersonLocalLookupService.PersonMapping> mappings) {
if (mappings == null || mappings.isEmpty()) {
return Collections.emptyList();
}
List<MatchedStaffSnapshot> matchedStaff = new ArrayList<>(); List<MatchedStaffSnapshot> matchedStaff = new ArrayList<>();
Set<Long> matchedUserIds = new HashSet<>(); Set<Long> matchedUserIds = new HashSet<>();
Set<String> terminalNos = new LinkedHashSet<>(); Set<String> terminalNos = new LinkedHashSet<>();
for (PositionPersonLocalLookupService.PersonMapping mapping for (PositionPersonLocalLookupService.PersonMapping mapping : mappings) {
: localLookupService.findMatchedPersonnel(localQuery)) {
PositionPersonLocalLookupService.LocalPerson local = mapping.getLocal(); PositionPersonLocalLookupService.LocalPerson local = mapping.getLocal();
if (local == null || (local.getUserId() != null && !matchedUserIds.add(local.getUserId()))) { if (local == null || (local.getUserId() != null && !matchedUserIds.add(local.getUserId()))) {
continue; continue;
@ -654,6 +666,29 @@ public class PersonnelPositioningBiQueryExe {
return wrapper; return wrapper;
} }
private QueryWrapper<FenceDO> companyFenceQuery(BiCompanyFenceQry query) {
QueryWrapper<FenceDO> wrapper = new QueryWrapper<>();
wrapper.and(value -> value.isNull("delete_enum").or().eq("delete_enum", "FALSE"));
if (query.getId() != null) {
wrapper.and(value -> value.eq("id", query.getId()).or().eq("finds_fence_id", query.getId()));
}
if (StringUtils.hasText(query.getNameLike())) {
wrapper.like("fence_name", query.getNameLike());
}
if (query.getCorpinfoId() != null) {
wrapper.eq("corpinfo_id", query.getCorpinfoId());
}
if (StringUtils.hasText(query.getCompanyName())) {
String companyName = query.getCompanyName();
wrapper.and(value -> value.eq("corpinfo_name", companyName).or().eq("org_name", companyName));
}
if (StringUtils.hasText(query.getOrgName())) {
String orgName = query.getOrgName();
wrapper.and(value -> value.eq("corpinfo_name", orgName).or().eq("org_name", orgName));
}
return wrapper;
}
private List<BiAreaFenceCO> mapLocalFences(List<FenceDO> fences) { private List<BiAreaFenceCO> mapLocalFences(List<FenceDO> fences) {
List<BiAreaFenceCO> result = new ArrayList<>(); List<BiAreaFenceCO> result = new ArrayList<>();
if (fences == null || fences.isEmpty()) { if (fences == null || fences.isEmpty()) {

View File

@ -1,6 +1,8 @@
package com.zcloud.personnel.positioning.command.query; package com.zcloud.personnel.positioning.command.query;
import com.alibaba.cola.dto.PageResponse;
import com.zcloud.personnel.positioning.dto.PositionPersonPageQry; import com.zcloud.personnel.positioning.dto.PositionPersonPageQry;
import com.zcloud.personnel.positioning.dto.BiPersonLocationQry;
import com.zcloud.personnel.positioning.persistence.dataobject.PersonnelLocalPersonDO; import com.zcloud.personnel.positioning.persistence.dataobject.PersonnelLocalPersonDO;
import com.zcloud.personnel.positioning.persistence.dataobject.PersonnelLocationAreaUpdateDO; import com.zcloud.personnel.positioning.persistence.dataobject.PersonnelLocationAreaUpdateDO;
import com.zcloud.personnel.positioning.persistence.dataobject.PersonnelMappingSnapshotDO; import com.zcloud.personnel.positioning.persistence.dataobject.PersonnelMappingSnapshotDO;
@ -42,6 +44,36 @@ class PositionPersonLocalLookupService {
return findMatchedPersonnel(query, null); return findMatchedPersonnel(query, null);
} }
PageResponse<PersonMapping> findMatchedPersonnelPage(PositionPersonPageQry query) {
Map<String, Object> params = matchedPersonnelParams(query, null);
return findMatchedPersonnelPage(params, query.getPageIndex(), query.getPageSize());
}
PageResponse<PersonMapping> findMatchedPersonnelPage(BiPersonLocationQry query) {
Map<String, Object> params = new HashMap<>();
params.put("staffName", normalize(query.getStaffName()));
params.put("corpinfoId", query.getCorpinfoId());
params.put("biCompanyName", normalize(query.getCompanyName()));
params.put("staffNo", normalize(query.getStaffNo()));
params.put("idCardNo", normalize(query.getIdCardNo()));
params.put("mobileNo", normalize(query.getMobileNo()));
params.put("terminalNo", normalize(query.getTerminalNo()));
params.put("sourceOrgName", normalize(query.getOrgName()));
params.put("orgCode", normalize(query.getOrgCode()));
params.put("creditCode", normalize(query.getCreditCode()));
return findMatchedPersonnelPage(params, query.getPageIndex(), query.getPageSize());
}
private PageResponse<PersonMapping> findMatchedPersonnelPage(Map<String, Object> params,
int pageIndex,
int pageSize) {
params.put("pageIndex", Math.max(pageIndex, 1));
params.put("pageSize", Math.max(pageSize, 1));
PageResponse<PersonnelMappingSnapshotDO> page = personnelMatchRepository.listMatchedPage(params);
return PageResponse.of(toPersonMappings(page.getData()), page.getTotalCount(),
page.getPageSize(), page.getPageIndex());
}
List<PersonMapping> findMatchedPersonnelByCorpinfoIds(Set<Long> corpinfoIds) { List<PersonMapping> findMatchedPersonnelByCorpinfoIds(Set<Long> corpinfoIds) {
List<PersonMapping> result = new ArrayList<>(); List<PersonMapping> result = new ArrayList<>();
for (Set<Long> batch : idBatches(corpinfoIds)) { for (Set<Long> batch : idBatches(corpinfoIds)) {
@ -52,6 +84,12 @@ class PositionPersonLocalLookupService {
private List<PersonMapping> findMatchedPersonnel(PositionPersonPageQry query, private List<PersonMapping> findMatchedPersonnel(PositionPersonPageQry query,
Set<Long> corpinfoIds) { Set<Long> corpinfoIds) {
return toPersonMappings(personnelMatchRepository.listMatched(
matchedPersonnelParams(query, corpinfoIds)));
}
private Map<String, Object> matchedPersonnelParams(PositionPersonPageQry query,
Set<Long> corpinfoIds) {
Map<String, Object> params = new HashMap<>(); Map<String, Object> params = new HashMap<>();
if (query != null) { if (query != null) {
params.put("userId", query.getUserId()); params.put("userId", query.getUserId());
@ -60,12 +98,18 @@ class PositionPersonLocalLookupService {
params.put("corpinfoName", normalize(query.getCorpinfoName())); params.put("corpinfoName", normalize(query.getCorpinfoName()));
params.put("departmentId", query.getDepartmentId()); params.put("departmentId", query.getDepartmentId());
params.put("departmentName", normalize(query.getDepartmentName())); params.put("departmentName", normalize(query.getDepartmentName()));
params.put("locationStatus", upper(query.getLocationStatus()));
params.put("positionSource", upper(query.getPositionSource()));
} }
if (corpinfoIds != null && !corpinfoIds.isEmpty()) { if (corpinfoIds != null && !corpinfoIds.isEmpty()) {
params.put("corpinfoIds", corpinfoIds); params.put("corpinfoIds", corpinfoIds);
} }
return params;
}
private List<PersonMapping> toPersonMappings(List<PersonnelMappingSnapshotDO> rows) {
List<PersonMapping> result = new ArrayList<>(); List<PersonMapping> result = new ArrayList<>();
for (PersonnelMappingSnapshotDO row : personnelMatchRepository.listMatched(params)) { for (PersonnelMappingSnapshotDO row : rows) {
PersonMapping mapping = new PersonMapping(); PersonMapping mapping = new PersonMapping();
mapping.setFindsStaffNo(normalize(row.getFindsStaffNo())); mapping.setFindsStaffNo(normalize(row.getFindsStaffNo()));
mapping.setFindsStaffNoType(normalize(row.getFindsStaffNoType())); mapping.setFindsStaffNoType(normalize(row.getFindsStaffNoType()));
@ -86,6 +130,11 @@ class PositionPersonLocalLookupService {
return result; return result;
} }
private String upper(String value) {
String normalized = normalize(value);
return normalized == null ? null : normalized.toUpperCase(java.util.Locale.ROOT);
}
/** /**
* *
*/ */

View File

@ -47,6 +47,14 @@ public class PositionPersonQueryExe {
public PageResponse<PositionPersonCO> list(PositionPersonPageQry qry) { public PageResponse<PositionPersonCO> list(PositionPersonPageQry qry) {
PositionPersonPageQry query = qry == null ? new PositionPersonPageQry() : qry; PositionPersonPageQry query = qry == null ? new PositionPersonPageQry() : qry;
if (localLookupService.hasPersonnelMappings()) {
PageResponse<PositionPersonLocalLookupService.PersonMapping> mappingPage =
localLookupService.findMatchedPersonnelPage(query);
List<PositionPersonCO> pageRows = buildRows(
query, true, loadPersistedMatches(mappingPage.getData()), true);
return PageResponse.of(pageRows, mappingPage.getTotalCount(),
mappingPage.getPageSize(), mappingPage.getPageIndex());
}
List<PositionPersonCO> rows = listRows(query); List<PositionPersonCO> rows = listRows(query);
int pageIndex = Math.max(query.getPageIndex(), 1); int pageIndex = Math.max(query.getPageIndex(), 1);
int pageSize = Math.max(query.getPageSize(), 1); int pageSize = Math.max(query.getPageSize(), 1);
@ -260,12 +268,11 @@ public class PositionPersonQueryExe {
for (int pageNo = 2; pageNo <= pageCount; pageNo++) { for (int pageNo = 2; pageNo <= pageCount; pageNo++) {
requests.add(staffPageRequest(pageNo)); requests.add(staffPageRequest(pageNo));
} }
List<JsonNode> roots = findsOpenApiClient.postFailFastBatch( findsOpenApiClient.consumeFailFastBatch(
STAFF_LIST_API, requests, FINDS_BATCH_CONCURRENCY); STAFF_LIST_API, requests, FINDS_BATCH_CONCURRENCY, (index, root) -> {
for (JsonNode root : roots) { JsonNode data = assertSuccess(root, STAFF_LIST_API).path("data");
JsonNode data = assertSuccess(root, STAFF_LIST_API).path("data"); appendFindsStaff(result, extractRows(data));
appendFindsStaff(result, extractRows(data)); });
}
return result; return result;
} }
@ -330,12 +337,10 @@ public class PositionPersonQueryExe {
for (List<String> terminalBatch : batches) { for (List<String> terminalBatch : batches) {
requests.add(Collections.singletonMap("terminalNoList", terminalBatch)); requests.add(Collections.singletonMap("terminalNoList", terminalBatch));
} }
List<JsonNode> roots = findsOpenApiClient.postFailFastBatch(
POINT_LOCATE_API, requests, FINDS_BATCH_CONCURRENCY);
Map<String, JsonNode> result = new LinkedHashMap<>(); Map<String, JsonNode> result = new LinkedHashMap<>();
for (int index = 0; index < roots.size(); index++) { findsOpenApiClient.consumeFailFastBatch(
appendLocatedTerminals(batches.get(index), roots.get(index), result); POINT_LOCATE_API, requests, FINDS_BATCH_CONCURRENCY,
} (index, root) -> appendLocatedTerminals(batches.get(index), root, result));
return result; return result;
} }

View File

@ -195,8 +195,6 @@ public class RealtimePersonLocationMatcher {
co.setOnline(online == null ? Boolean.FALSE : online); co.setOnline(online == null ? Boolean.FALSE : online);
co.setAlarmStatus(booleanValue(firstValue(location, row, "alarmStatus", "isAlarm"))); co.setAlarmStatus(booleanValue(firstValue(location, row, "alarmStatus", "isAlarm")));
co.setAlarmCount(integerValue(firstValue(location, row, "alarmCount", "unclosedAlarmCount"))); co.setAlarmCount(integerValue(firstValue(location, row, "alarmCount", "unclosedAlarmCount")));
co.setStaffRawJson(row.toString());
co.setLocationRawJson(location.toString());
return co; return co;
} }

View File

@ -27,10 +27,12 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.function.BiConsumer;
/** /**
* FindS OpenAPI signed client. * FindS OpenAPI signed client.
@ -43,6 +45,7 @@ import java.util.concurrent.Future;
public class FindsOpenApiClient { public class FindsOpenApiClient {
private static final Logger LOGGER = LoggerFactory.getLogger(FindsOpenApiClient.class); private static final Logger LOGGER = LoggerFactory.getLogger(FindsOpenApiClient.class);
private static final String SIK_PATH_SEGMENT = "sik"; private static final String SIK_PATH_SEGMENT = "sik";
private static final int MAX_RESPONSE_BYTES = 16 * 1024 * 1024;
@Value("${finds.openapi.connect-timeout-ms:10000}") @Value("${finds.openapi.connect-timeout-ms:10000}")
private int connectTimeoutMs = 10000; private int connectTimeoutMs = 10000;
@ -73,34 +76,38 @@ public class FindsOpenApiClient {
failFastConnectTimeoutMs, failFastReadTimeoutMs, 1); failFastConnectTimeoutMs, failFastReadTimeoutMs, 1);
} }
public List<JsonNode> postFailFastBatch(String apiName, List<Map<String, Object>> requests, public void consumeFailFastBatch(String apiName, List<Map<String, Object>> requests,
int maxConcurrency) { int maxConcurrency,
BiConsumer<Integer, JsonNode> responseConsumer) {
if (requests == null || requests.isEmpty()) { if (requests == null || requests.isEmpty()) {
return Collections.emptyList(); return;
} }
Objects.requireNonNull(responseConsumer, "responseConsumer");
OpenapiConfigDO config = loadEnabledConfig(); OpenapiConfigDO config = loadEnabledConfig();
int concurrency = Math.max(1, Math.min(Math.min(maxConcurrency, 8), requests.size())); int concurrency = Math.max(1, Math.min(Math.min(maxConcurrency, 8), requests.size()));
if (concurrency == 1) { if (concurrency == 1) {
List<JsonNode> result = new ArrayList<>(requests.size()); for (int index = 0; index < requests.size(); index++) {
for (Map<String, Object> request : requests) { responseConsumer.accept(index, post(config, apiName, requests.get(index),
result.add(post(config, apiName, request,
failFastConnectTimeoutMs, failFastReadTimeoutMs, 1)); failFastConnectTimeoutMs, failFastReadTimeoutMs, 1));
} }
return result; return;
} }
ExecutorService executor = Executors.newFixedThreadPool(concurrency); ExecutorService executor = Executors.newFixedThreadPool(concurrency);
try { try {
List<Future<JsonNode>> futures = new ArrayList<>(requests.size()); // 每个窗口完成后立即交给调用方处理,避免所有分页响应同时滞留在 Future 中。
for (Map<String, Object> request : requests) { for (int from = 0; from < requests.size(); from += concurrency) {
futures.add(executor.submit(() -> post(config, apiName, request, int to = Math.min(from + concurrency, requests.size());
failFastConnectTimeoutMs, failFastReadTimeoutMs, 1))); List<Future<JsonNode>> futures = new ArrayList<>(to - from);
for (int index = from; index < to; index++) {
Map<String, Object> request = requests.get(index);
futures.add(executor.submit(() -> post(config, apiName, request,
failFastConnectTimeoutMs, failFastReadTimeoutMs, 1)));
}
for (int offset = 0; offset < futures.size(); offset++) {
responseConsumer.accept(from + offset, futures.get(offset).get());
}
} }
List<JsonNode> result = new ArrayList<>(requests.size());
for (Future<JsonNode> future : futures) {
result.add(future.get());
}
return result;
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
throw new BizException("FindS OpenAPI批量请求被中断"); throw new BizException("FindS OpenAPI批量请求被中断");
@ -247,13 +254,18 @@ public class FindsOpenApiClient {
if (inputStream == null) { if (inputStream == null) {
return ""; return "";
} }
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(8192);
byte[] buffer = new byte[4096]; byte[] buffer = new byte[4096];
int total = 0;
int len; int len;
while ((len = inputStream.read(buffer)) != -1) { while ((len = inputStream.read(buffer)) != -1) {
if (len > MAX_RESPONSE_BYTES - total) {
throw new IOException("FindS OpenAPI response exceeds 16 MiB safety limit");
}
outputStream.write(buffer, 0, len); outputStream.write(buffer, 0, len);
total += len;
} }
return new String(outputStream.toByteArray(), StandardCharsets.UTF_8); return outputStream.toString(StandardCharsets.UTF_8.name());
} }
private String writeJson(Object value) { private String writeJson(Object value) {

View File

@ -96,6 +96,9 @@ public class OpenapiConfigCache {
if (local != null && local.expireAt > now) { if (local != null && local.expireAt > now) {
return local.config; return local.config;
} }
if (local != null) {
localCache.remove(key, local);
}
OpenapiConfigDO redisConfig = readRedis(key); OpenapiConfigDO redisConfig = readRedis(key);
if (redisConfig != null) { if (redisConfig != null) {
putLocal(key, redisConfig); putLocal(key, redisConfig);

View File

@ -12,9 +12,8 @@ import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap; import java.util.HashMap;
import java.util.LinkedHashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -83,8 +82,7 @@ public class RealtimePersonRoutingCache {
return current; return current;
} }
try { try {
Map<String, PersonRoute> routes = buildRoutes( Map<String, PersonRoute> routes = buildRoutes();
personnelMatchRepository.listRealtimePersonRoutes(SOURCE_CONFIG_CODE));
snapshot = new CacheSnapshot(Collections.unmodifiableMap(routes), now + ttlMillis); snapshot = new CacheSnapshot(Collections.unmodifiableMap(routes), now + ttlMillis);
LOGGER.info("Realtime WebSocket person-routing cache refreshed, size={}", routes.size()); LOGGER.info("Realtime WebSocket person-routing cache refreshed, size={}", routes.size());
} catch (RuntimeException e) { } catch (RuntimeException e) {
@ -97,49 +95,10 @@ public class RealtimePersonRoutingCache {
} }
} }
private Map<String, PersonRoute> buildRoutes(List<RealtimePersonRoutingDO> rows) { private Map<String, PersonRoute> buildRoutes() {
Map<String, PersonRoute> persistedRoutes = new LinkedHashMap<>(); RouteAccumulator accumulator = new RouteAccumulator();
UniqueRouteIndex usernames = new UniqueRouteIndex(); personnelMatchRepository.scanRealtimePersonRoutes(SOURCE_CONFIG_CODE, accumulator::add);
UniqueRouteIndex phones = new UniqueRouteIndex(); return accumulator.toRoutes();
if (rows != null) {
for (RealtimePersonRoutingDO row : rows) {
if (row == null) {
continue;
}
PersonRoute route = toRoute(row);
if (row.isPersistedMatch()) {
String staffNo = normalize(row.getFindsStaffNo());
if (staffNo != null) {
persistedRoutes.put(staffNo, route);
}
continue;
}
usernames.add(row.getLocalStaffNo(), route);
phones.add(row.getMobileNo(), route);
}
}
Map<String, PersonRoute> routes = new LinkedHashMap<>(persistedRoutes);
Set<String> fallbackKeys = new LinkedHashSet<>();
fallbackKeys.addAll(usernames.keys());
fallbackKeys.addAll(phones.keys());
for (String key : fallbackKeys) {
if (routes.containsKey(key)) {
continue;
}
IndexResult username = usernames.resolve(key);
if (username.present) {
if (!username.ambiguous) {
routes.put(key, username.route);
}
continue;
}
IndexResult phone = phones.resolve(key);
if (phone.present && !phone.ambiguous) {
routes.put(key, phone.route);
}
}
return routes;
} }
private PersonRoute toRoute(RealtimePersonRoutingDO row) { private PersonRoute toRoute(RealtimePersonRoutingDO row) {
@ -179,8 +138,8 @@ public class RealtimePersonRoutingCache {
} }
private static final class UniqueRouteIndex { private static final class UniqueRouteIndex {
private final Map<String, PersonRoute> unique = new LinkedHashMap<>(); private final Map<String, PersonRoute> unique = new HashMap<>();
private final Set<String> ambiguous = new LinkedHashSet<>(); private final Set<String> ambiguous = new HashSet<>();
private void add(String rawKey, PersonRoute route) { private void add(String rawKey, PersonRoute route) {
String key = normalize(rawKey); String key = normalize(rawKey);
@ -194,18 +153,18 @@ public class RealtimePersonRoutingCache {
} }
} }
private Set<String> keys() { private boolean contains(String key) {
Set<String> keys = new LinkedHashSet<>(unique.keySet()); return unique.containsKey(key) || ambiguous.contains(key);
keys.addAll(ambiguous);
return keys;
} }
private IndexResult resolve(String key) { private void appendUniqueRoutes(Map<String, PersonRoute> routes,
if (ambiguous.contains(key)) { UniqueRouteIndex preferredIndex) {
return IndexResult.ambiguous(); for (Map.Entry<String, PersonRoute> entry : unique.entrySet()) {
if (preferredIndex != null && preferredIndex.contains(entry.getKey())) {
continue;
}
routes.putIfAbsent(entry.getKey(), entry.getValue());
} }
PersonRoute route = unique.get(key);
return route == null ? IndexResult.absent() : IndexResult.unique(route);
} }
private boolean sameUser(PersonRoute first, PersonRoute second) { private boolean sameUser(PersonRoute first, PersonRoute second) {
@ -213,20 +172,33 @@ public class RealtimePersonRoutingCache {
} }
} }
private static final class IndexResult { private final class RouteAccumulator {
private final boolean present; private final Map<String, PersonRoute> routes = new HashMap<>();
private final boolean ambiguous; private final UniqueRouteIndex usernames = new UniqueRouteIndex();
private final PersonRoute route; private final UniqueRouteIndex phones = new UniqueRouteIndex();
private IndexResult(boolean present, boolean ambiguous, PersonRoute route) { private void add(RealtimePersonRoutingDO row) {
this.present = present; if (row == null) {
this.ambiguous = ambiguous; return;
this.route = route; }
PersonRoute route = toRoute(row);
if (row.isPersistedMatch()) {
String staffNo = normalize(row.getFindsStaffNo());
if (staffNo != null) {
routes.put(staffNo, route);
}
return;
}
usernames.add(row.getLocalStaffNo(), route);
phones.add(row.getMobileNo(), route);
} }
private static IndexResult absent() { return new IndexResult(false, false, null); } private Map<String, PersonRoute> toRoutes() {
private static IndexResult ambiguous() { return new IndexResult(true, true, null); } usernames.appendUniqueRoutes(routes, null);
private static IndexResult unique(PersonRoute route) { return new IndexResult(true, false, route); } // 用户名存在但有歧义时也不能回退到同值手机号,保持原有匹配优先级。
phones.appendUniqueRoutes(routes, usernames);
return routes;
}
} }
private static final class CacheSnapshot { private static final class CacheSnapshot {

View File

@ -7,8 +7,10 @@ import com.zcloud.personnel.positioning.persistence.dataobject.RealtimePersonRou
import com.zcloud.personnel.positioning.persistence.dataobject.TerminalBindingSnapshotDO; import com.zcloud.personnel.positioning.persistence.dataobject.TerminalBindingSnapshotDO;
import com.zcloud.personnel.positioning.persistence.dataobject.TerminalLocationSnapshotDO; import com.zcloud.personnel.positioning.persistence.dataobject.TerminalLocationSnapshotDO;
import com.zcloud.personnel.positioning.persistence.dataobject.CorpInfoSnapshotDO; import com.zcloud.personnel.positioning.persistence.dataobject.CorpInfoSnapshotDO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.session.ResultHandler;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
@ -51,11 +53,16 @@ public interface PersonnelMatchMapper {
@Param("updates") List<PersonnelLocationAreaUpdateDO> updates, @Param("updates") List<PersonnelLocationAreaUpdateDO> updates,
@Param("syncTime") LocalDateTime syncTime); @Param("syncTime") LocalDateTime syncTime);
List<RealtimePersonRoutingDO> listRealtimePersonRoutes( void scanRealtimePersonRoutes(
@Param("sourceConfigCode") String sourceConfigCode); @Param("sourceConfigCode") String sourceConfigCode,
ResultHandler<RealtimePersonRoutingDO> resultHandler);
List<PersonnelMappingSnapshotDO> listMatched(@Param("params") Map<String, Object> params); List<PersonnelMappingSnapshotDO> listMatched(@Param("params") Map<String, Object> params);
IPage<PersonnelMappingSnapshotDO> listMatchedPage(
IPage<PersonnelMappingSnapshotDO> page,
@Param("params") Map<String, Object> params);
List<PersonnelLocalPersonDO> listPeopleByPhones(@Param("values") List<String> values); List<PersonnelLocalPersonDO> listPeopleByPhones(@Param("values") List<String> values);
List<PersonnelLocalPersonDO> listAllPeople(); List<PersonnelLocalPersonDO> listAllPeople();

View File

@ -1,5 +1,6 @@
package com.zcloud.personnel.positioning.persistence.repository; package com.zcloud.personnel.positioning.persistence.repository;
import com.alibaba.cola.dto.PageResponse;
import com.zcloud.personnel.positioning.persistence.dataobject.PersonnelLocalPersonDO; import com.zcloud.personnel.positioning.persistence.dataobject.PersonnelLocalPersonDO;
import com.zcloud.personnel.positioning.persistence.dataobject.PersonnelLocationAreaUpdateDO; import com.zcloud.personnel.positioning.persistence.dataobject.PersonnelLocationAreaUpdateDO;
import com.zcloud.personnel.positioning.persistence.dataobject.PersonnelMappingSnapshotDO; import com.zcloud.personnel.positioning.persistence.dataobject.PersonnelMappingSnapshotDO;
@ -11,6 +12,7 @@ import com.zcloud.personnel.positioning.persistence.dataobject.CorpInfoSnapshotD
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer;
public interface PersonnelMatchRepository { public interface PersonnelMatchRepository {
long countActive(String sourceConfigCode); long countActive(String sourceConfigCode);
@ -36,10 +38,12 @@ public interface PersonnelMatchRepository {
List<PersonnelLocationAreaUpdateDO> updates, List<PersonnelLocationAreaUpdateDO> updates,
LocalDateTime syncTime); LocalDateTime syncTime);
List<RealtimePersonRoutingDO> listRealtimePersonRoutes(String sourceConfigCode); void scanRealtimePersonRoutes(String sourceConfigCode, Consumer<RealtimePersonRoutingDO> consumer);
List<PersonnelMappingSnapshotDO> listMatched(Map<String, Object> params); List<PersonnelMappingSnapshotDO> listMatched(Map<String, Object> params);
PageResponse<PersonnelMappingSnapshotDO> listMatchedPage(Map<String, Object> params);
List<PersonnelLocalPersonDO> listPeopleByPhones(List<String> values); List<PersonnelLocalPersonDO> listPeopleByPhones(List<String> values);
List<PersonnelLocalPersonDO> listAllPeople(); List<PersonnelLocalPersonDO> listAllPeople();

View File

@ -1,5 +1,9 @@
package com.zcloud.personnel.positioning.persistence.repository.impl; package com.zcloud.personnel.positioning.persistence.repository.impl;
import com.alibaba.cola.dto.PageResponse;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.jjb.saas.framework.repository.common.PageHelper;
import com.zcloud.gbscommon.utils.Query;
import com.zcloud.personnel.positioning.persistence.dataobject.PersonnelLocalPersonDO; import com.zcloud.personnel.positioning.persistence.dataobject.PersonnelLocalPersonDO;
import com.zcloud.personnel.positioning.persistence.dataobject.PersonnelLocationAreaUpdateDO; import com.zcloud.personnel.positioning.persistence.dataobject.PersonnelLocationAreaUpdateDO;
import com.zcloud.personnel.positioning.persistence.dataobject.PersonnelMappingSnapshotDO; import com.zcloud.personnel.positioning.persistence.dataobject.PersonnelMappingSnapshotDO;
@ -18,6 +22,8 @@ import java.time.LocalDateTime;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
@Repository @Repository
@RequiredArgsConstructor @RequiredArgsConstructor
@ -63,12 +69,21 @@ public class PersonnelMatchRepositoryImpl implements PersonnelMatchRepository {
} }
return affected; return affected;
} }
@Override public List<RealtimePersonRoutingDO> listRealtimePersonRoutes(String code) { @Override
return personnelMatchMapper.listRealtimePersonRoutes(code); public void scanRealtimePersonRoutes(String code, Consumer<RealtimePersonRoutingDO> consumer) {
Objects.requireNonNull(consumer, "consumer");
// ResultHandler 在 Mapper 调用期间逐行消费,避免先将全量人员结果装入 List。
personnelMatchMapper.scanRealtimePersonRoutes(
code, context -> consumer.accept(context.getResultObject()));
} }
@Override public List<PersonnelMappingSnapshotDO> listMatched(Map<String, Object> params) { @Override public List<PersonnelMappingSnapshotDO> listMatched(Map<String, Object> params) {
return personnelMatchMapper.listMatched(params); return personnelMatchMapper.listMatched(params);
} }
@Override public PageResponse<PersonnelMappingSnapshotDO> listMatchedPage(Map<String, Object> params) {
IPage<PersonnelMappingSnapshotDO> page = new Query<PersonnelMappingSnapshotDO>().getPage(params);
IPage<PersonnelMappingSnapshotDO> result = personnelMatchMapper.listMatchedPage(page, params);
return PageHelper.pageToResponse(result, result.getRecords());
}
@Override public List<PersonnelLocalPersonDO> listPeopleByPhones(List<String> values) { @Override public List<PersonnelLocalPersonDO> listPeopleByPhones(List<String> values) {
return personnelMatchMapper.listPeopleByPhones(values); return personnelMatchMapper.listPeopleByPhones(values);
} }

View File

@ -113,19 +113,18 @@
</foreach> </foreach>
</update> </update>
<select id="listRealtimePersonRoutes" <select id="scanRealtimePersonRoutes"
fetchSize="-2147483648"
resultSetType="FORWARD_ONLY"
resultType="com.zcloud.personnel.positioning.persistence.dataobject.RealtimePersonRoutingDO"> resultType="com.zcloud.personnel.positioning.persistence.dataobject.RealtimePersonRoutingDO">
SELECT pm.finds_staff_no, u.id AS user_id, u.username AS local_staff_no, SELECT pm.finds_staff_no, u.id AS user_id, u.username AS local_staff_no,
u.name AS staff_name, TRIM(u.phone) AS mobile_no, u.user_id_card AS id_card_no, u.name AS staff_name, TRIM(u.phone) AS mobile_no,
ci.id AS corpinfo_id, ci.corp_name AS corpinfo_name, ci.code AS credit_code, ci.id AS corpinfo_id, ci.corp_name AS corpinfo_name, ci.port_area AS port_area,
ci.port_area AS port_area, u.department_id, d.dept_name AS department_name,
1 AS persisted_match 1 AS persisted_match
FROM personnel_match pm FROM personnel_match pm
INNER JOIN user_scope_v u ON u.id = pm.local_user_id INNER JOIN user_scope_v u ON u.id = pm.local_user_id
LEFT JOIN corp_info ci ON ci.id = u.corpinfo_id LEFT JOIN corp_info ci ON ci.id = u.corpinfo_id
AND (ci.delete_enum IS NULL OR ci.delete_enum = 'FALSE') AND (ci.delete_enum IS NULL OR ci.delete_enum = 'FALSE')
LEFT JOIN department d ON d.id = u.department_id
AND (d.delete_enum IS NULL OR d.delete_enum = 'FALSE')
WHERE pm.source_config_code = #{sourceConfigCode} WHERE pm.source_config_code = #{sourceConfigCode}
AND pm.match_status = 'MATCHED' AND pm.match_status = 'MATCHED'
AND (pm.delete_enum IS NULL OR pm.delete_enum = 'FALSE') AND (pm.delete_enum IS NULL OR pm.delete_enum = 'FALSE')
@ -133,15 +132,12 @@
AND pm.finds_staff_no IS NOT NULL AND TRIM(pm.finds_staff_no) &lt;&gt; '' AND pm.finds_staff_no IS NOT NULL AND TRIM(pm.finds_staff_no) &lt;&gt; ''
UNION ALL UNION ALL
SELECT NULL AS finds_staff_no, u.id AS user_id, u.username AS local_staff_no, SELECT NULL AS finds_staff_no, u.id AS user_id, u.username AS local_staff_no,
u.name AS staff_name, TRIM(u.phone) AS mobile_no, u.user_id_card AS id_card_no, u.name AS staff_name, TRIM(u.phone) AS mobile_no,
ci.id AS corpinfo_id, ci.corp_name AS corpinfo_name, ci.code AS credit_code, ci.id AS corpinfo_id, ci.corp_name AS corpinfo_name, ci.port_area AS port_area,
ci.port_area AS port_area, u.department_id, d.dept_name AS department_name,
0 AS persisted_match 0 AS persisted_match
FROM user_scope_v u FROM user_scope_v u
LEFT JOIN corp_info ci ON ci.id = u.corpinfo_id LEFT JOIN corp_info ci ON ci.id = u.corpinfo_id
AND (ci.delete_enum IS NULL OR ci.delete_enum = 'FALSE') AND (ci.delete_enum IS NULL OR ci.delete_enum = 'FALSE')
LEFT JOIN department d ON d.id = u.department_id
AND (d.delete_enum IS NULL OR d.delete_enum = 'FALSE')
WHERE (u.delete_enum IS NULL OR u.delete_enum = 'FALSE') WHERE (u.delete_enum IS NULL OR u.delete_enum = 'FALSE')
</select> </select>
@ -255,6 +251,102 @@
ORDER BY u.id DESC, pm.finds_staff_no ORDER BY u.id DESC, pm.finds_staff_no
</select> </select>
<select id="listMatchedPage"
resultType="com.zcloud.personnel.positioning.persistence.dataobject.PersonnelMappingSnapshotDO">
SELECT pm.finds_staff_no, pm.finds_staff_no_type, pm.finds_id_card_no,
pm.finds_staff_name, pm.finds_corpinfo_id, pm.finds_corpinfo_name,
pm.finds_department_name, pm.finds_terminal_no, pm.location_status,
pm.finds_device_status, pm.location_area_id, pm.location_area_name,
pm.location_area_time, u.id AS user_id, u.username AS local_staff_no,
u.name AS staff_name, TRIM(u.phone) AS mobile_no, u.user_id_card AS id_card_no,
ci.id AS corpinfo_id, ci.corp_name AS corpinfo_name, ci.code AS credit_code,
u.department_id, d.dept_name AS department_name
FROM personnel_match pm
JOIN user_scope_v u ON u.id = pm.local_user_id
LEFT JOIN corp_info ci ON ci.id = u.corpinfo_id
LEFT JOIN department d ON d.id = u.department_id
AND (d.delete_enum IS NULL OR d.delete_enum = 'FALSE')
<if test="(params.locationStatus != null and params.locationStatus != '')
or (params.positionSource != null and params.positionSource != '')">
LEFT JOIN terminal t ON t.id = (
SELECT t_pick.id
FROM terminal t_pick
WHERE t_pick.terminal_no = pm.finds_terminal_no
AND (t_pick.delete_enum IS NULL OR t_pick.delete_enum = 'FALSE')
ORDER BY t_pick.update_time DESC, t_pick.id DESC
LIMIT 1
)
</if>
WHERE pm.match_status = 'MATCHED'
AND (pm.delete_enum IS NULL OR pm.delete_enum = 'FALSE')
AND (u.delete_enum IS NULL OR u.delete_enum = 'FALSE')
AND pm.id = (
SELECT pm_pick.id
FROM personnel_match pm_pick
WHERE pm_pick.local_user_id = pm.local_user_id
AND pm_pick.match_status = 'MATCHED'
AND (pm_pick.delete_enum IS NULL OR pm_pick.delete_enum = 'FALSE')
ORDER BY pm_pick.finds_staff_no, pm_pick.id
LIMIT 1
)
<if test="params.userId != null">AND u.id = #{params.userId}</if>
<if test="params.staffName != null and params.staffName != ''">AND u.name LIKE CONCAT('%', #{params.staffName}, '%')</if>
<if test="params.corpinfoId != null">AND ci.id = #{params.corpinfoId}</if>
<if test="params.corpinfoName != null and params.corpinfoName != ''">AND ci.corp_name LIKE CONCAT('%', #{params.corpinfoName}, '%')</if>
<if test="params.biCompanyName != null and params.biCompanyName != ''">
AND COALESCE(NULLIF(ci.corp_name, ''), NULLIF(pm.finds_department_name, ''),
pm.finds_corpinfo_name, '') LIKE CONCAT('%', #{params.biCompanyName}, '%')
</if>
<if test="params.departmentId != null">AND u.department_id = #{params.departmentId}</if>
<if test="params.departmentName != null and params.departmentName != ''">AND d.dept_name LIKE CONCAT('%', #{params.departmentName}, '%')</if>
<if test="params.staffNo != null and params.staffNo != ''">AND pm.finds_staff_no = #{params.staffNo}</if>
<if test="params.idCardNo != null and params.idCardNo != ''">AND u.user_id_card = #{params.idCardNo}</if>
<if test="params.mobileNo != null and params.mobileNo != ''">AND TRIM(u.phone) = #{params.mobileNo}</if>
<if test="params.sourceOrgName != null and params.sourceOrgName != ''">
AND COALESCE(NULLIF(pm.finds_department_name, ''), pm.finds_corpinfo_name, '')
LIKE CONCAT('%', #{params.sourceOrgName}, '%')
</if>
<if test="params.creditCode != null and params.creditCode != ''">AND ci.code = #{params.creditCode}</if>
<if test="params.orgCode != null and params.orgCode != ''">AND 1 = 0</if>
<if test="params.terminalNo != null and params.terminalNo != ''">
AND (
pm.finds_terminal_no = #{params.terminalNo}
OR ((pm.finds_terminal_no IS NULL OR pm.finds_terminal_no = '') AND EXISTS (
SELECT 1
FROM terminal t_filter
LEFT JOIN terminal_bind tb_filter ON tb_filter.id = (
SELECT tb_pick.id
FROM terminal_bind tb_pick
WHERE tb_pick.terminal_no = t_filter.terminal_no
AND tb_pick.bind_status = 'BOUND'
AND (tb_pick.delete_enum IS NULL OR tb_pick.delete_enum = 'FALSE')
ORDER BY tb_pick.bind_time DESC, tb_pick.id DESC
LIMIT 1
)
WHERE t_filter.terminal_no = #{params.terminalNo}
AND (t_filter.delete_enum IS NULL OR t_filter.delete_enum = 'FALSE')
AND COALESCE(NULLIF(tb_filter.id_card_no, ''), NULLIF(t_filter.bind_id_card_no, ''))
= u.user_id_card
))
)
</if>
<if test="params.locationStatus != null and params.locationStatus != ''">
AND (CASE
WHEN UPPER(COALESCE(NULLIF(pm.location_status, ''), NULLIF(pm.finds_device_status, ''), t.device_status, ''))
IN ('ONLINE', 'STATIC', 'MOVING') THEN 'ONLINE'
ELSE 'OFFLINE'
END) = #{params.locationStatus}
</if>
<if test="params.positionSource != null and params.positionSource != ''">
AND (CASE
WHEN UPPER(COALESCE(t.device_type, '')) = 'CARD' THEN 'CARD'
WHEN UPPER(COALESCE(t.device_type, '')) = 'APP' THEN 'APP'
ELSE 'FINDS'
END) = #{params.positionSource}
</if>
ORDER BY u.id DESC, pm.finds_staff_no
</select>
<select id="listMatchedPersonCompanies" <select id="listMatchedPersonCompanies"
resultType="com.zcloud.personnel.positioning.persistence.dataobject.CorpInfoSnapshotDO"> resultType="com.zcloud.personnel.positioning.persistence.dataobject.CorpInfoSnapshotDO">
SELECT DISTINCT ci.id AS corpinfo_id, ci.corp_name SELECT DISTINCT ci.id AS corpinfo_id, ci.corp_name