优化人员在线状态增量同步
parent
7bfab8159f
commit
5d9927b6e4
|
|
@ -5,26 +5,30 @@ import com.jjb.saas.framework.job.annotation.JobRegister;
|
||||||
import com.xxl.job.core.biz.model.ReturnT;
|
import com.xxl.job.core.biz.model.ReturnT;
|
||||||
import com.xxl.job.core.context.XxlJobHelper;
|
import com.xxl.job.core.context.XxlJobHelper;
|
||||||
import com.xxl.job.core.handler.annotation.XxlJob;
|
import com.xxl.job.core.handler.annotation.XxlJob;
|
||||||
import com.zcloud.personnel.positioning.command.query.FindsStaffMappingSyncService;
|
import com.zcloud.personnel.positioning.command.query.FindsStaffLocationStatusSyncService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class FindsStaffMappingSyncJob implements Job {
|
public class FindsStaffMappingSyncJob implements Job {
|
||||||
private final FindsStaffMappingSyncService syncService;
|
private final FindsStaffLocationStatusSyncService locationStatusSyncService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@JobRegister(cron = "0 */5 * * * ?", jobDesc = "FindS人员映射同步", author = "系统", triggerStatus = 1)
|
@JobRegister(cron = "0 */5 * * * ?", jobDesc = "FindS人员映射和定位状态同步", author = "系统", triggerStatus = 1)
|
||||||
@XxlJob("com.zcloud.personnel.positioning.plan.FindsStaffMappingSyncJob")
|
@XxlJob("com.zcloud.personnel.positioning.plan.FindsStaffMappingSyncJob")
|
||||||
public ReturnT<String> execute(String param) {
|
public ReturnT<String> execute(String param) {
|
||||||
try {
|
try {
|
||||||
int count = syncService.syncAll();
|
FindsStaffLocationStatusSyncService.SyncSummary summary = locationStatusSyncService.synchronize();
|
||||||
String message = "FindS人员映射同步完成,本次处理 " + count + " 条";
|
String message = "FindS人员映射和定位状态同步完成,人员总数="
|
||||||
|
+ summary.getRemoteStaffCount()
|
||||||
|
+ ",全量同步=" + summary.isFullSync()
|
||||||
|
+ ",本次补充=" + summary.getFullSyncCount()
|
||||||
|
+ ",在线终端=" + summary.getOnlineTerminalCount();
|
||||||
XxlJobHelper.log(message);
|
XxlJobHelper.log(message);
|
||||||
return new ReturnT<>(ReturnT.SUCCESS_CODE, message);
|
return new ReturnT<>(ReturnT.SUCCESS_CODE, message);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
String message = "FindS人员映射同步失败:" + e.getMessage();
|
String message = "FindS人员映射和定位状态同步失败:" + e.getMessage();
|
||||||
XxlJobHelper.log(message);
|
XxlJobHelper.log(message);
|
||||||
return new ReturnT<>(ReturnT.FAIL_CODE, message);
|
return new ReturnT<>(ReturnT.FAIL_CODE, message);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,324 @@
|
||||||
|
package com.zcloud.personnel.positioning.command.query;
|
||||||
|
|
||||||
|
import com.alibaba.cola.exception.BizException;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.zcloud.personnel.positioning.integration.finds.FindsOpenApiClient;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes the persisted online/offline state for FindS personnel.
|
||||||
|
*
|
||||||
|
* <p>Only device status is stored. Coordinates remain outside MySQL.</p>
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class FindsStaffLocationStatusSyncService {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(FindsStaffLocationStatusSyncService.class);
|
||||||
|
|
||||||
|
private static final String STAFF_LIST_API = "finds.staff.list";
|
||||||
|
private static final String TERMINAL_LIST_API = "finds.terminal.list";
|
||||||
|
private static final String SOURCE_CONFIG_CODE = "FINDS";
|
||||||
|
private static final int STAFF_COUNT_PAGE_SIZE = 1;
|
||||||
|
private static final int TERMINAL_PAGE_SIZE = 2000;
|
||||||
|
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 List<String> ONLINE_DEVICE_STATUSES =
|
||||||
|
Arrays.asList("STATIC", "MOVING");
|
||||||
|
|
||||||
|
private final FindsOpenApiClient findsOpenApiClient;
|
||||||
|
private final FindsStaffMappingSyncService mappingSyncService;
|
||||||
|
private final JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
|
public SyncSummary synchronize() {
|
||||||
|
LocalDateTime syncTime = LocalDateTime.now().withNano(0);
|
||||||
|
long remoteStaffCount = queryRemoteStaffCount();
|
||||||
|
long localStaffCount = queryLocalStaffCount();
|
||||||
|
boolean fullSync = localStaffCount == 0
|
||||||
|
|| remoteStaffCount != localStaffCount
|
||||||
|
|| fullSyncDue(syncTime);
|
||||||
|
int fullSyncCount = fullSync ? mappingSyncService.syncAll() : 0;
|
||||||
|
|
||||||
|
Map<String, OnlineTerminal> onlineTerminals = loadOnlineTerminals();
|
||||||
|
int onlineMappings = updateLocationStatuses(onlineTerminals, syncTime);
|
||||||
|
LOGGER.info("FindS personnel location status synchronization completed, remoteStaffCount={}, "
|
||||||
|
+ "localStaffCount={}, fullSync={}, fullSyncCount={}, onlineTerminals={}, onlineMappings={}",
|
||||||
|
remoteStaffCount, localStaffCount, fullSync, fullSyncCount,
|
||||||
|
onlineTerminals.size(), onlineMappings);
|
||||||
|
return new SyncSummary(remoteStaffCount, localStaffCount, fullSync, fullSyncCount,
|
||||||
|
onlineTerminals.size(), onlineMappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private long queryRemoteStaffCount() {
|
||||||
|
JsonNode data = successData(findsOpenApiClient.postFailFast(STAFF_LIST_API,
|
||||||
|
staffCountRequest()));
|
||||||
|
return requiredTotal(data, "FindS staff count response");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> staffCountRequest() {
|
||||||
|
Map<String, Object> request = new LinkedHashMap<>();
|
||||||
|
request.put("pageNo", 1);
|
||||||
|
request.put("pageSize", STAFF_COUNT_PAGE_SIZE);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long queryLocalStaffCount() {
|
||||||
|
Long count = jdbcTemplate.queryForObject(
|
||||||
|
"SELECT COUNT(1) FROM personnel_match "
|
||||||
|
+ "WHERE source_config_code = ? "
|
||||||
|
+ "AND (delete_enum IS NULL OR delete_enum = 'FALSE') "
|
||||||
|
+ "AND match_status <> 'DISABLED'",
|
||||||
|
Long.class, SOURCE_CONFIG_CODE);
|
||||||
|
return count == null ? 0 : count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean fullSyncDue(LocalDateTime syncTime) {
|
||||||
|
LocalDateTime lastSyncTime = jdbcTemplate.queryForObject(
|
||||||
|
"SELECT MAX(last_sync_time) FROM personnel_match "
|
||||||
|
+ "WHERE source_config_code = ? "
|
||||||
|
+ "AND (delete_enum IS NULL OR delete_enum = 'FALSE') "
|
||||||
|
+ "AND match_status <> 'DISABLED'",
|
||||||
|
LocalDateTime.class, SOURCE_CONFIG_CODE);
|
||||||
|
return lastSyncTime == null
|
||||||
|
|| lastSyncTime.isBefore(syncTime.minusHours(FULL_SYNC_INTERVAL_HOURS));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, OnlineTerminal> loadOnlineTerminals() {
|
||||||
|
Map<String, OnlineTerminal> result = new LinkedHashMap<>();
|
||||||
|
for (String status : ONLINE_DEVICE_STATUSES) {
|
||||||
|
loadOnlineTerminals(status, result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadOnlineTerminals(String deviceStatus, Map<String, OnlineTerminal> result) {
|
||||||
|
JsonNode firstData = successData(findsOpenApiClient.postFailFast(
|
||||||
|
TERMINAL_LIST_API, terminalPageRequest(deviceStatus, 1)));
|
||||||
|
JsonNode firstRows = extractRows(firstData);
|
||||||
|
long total = requiredTotal(firstData, "FindS " + deviceStatus + " terminal response");
|
||||||
|
if (total > 0 && (firstRows == null || firstRows.size() == 0)) {
|
||||||
|
throw new BizException("FindS terminal status page is empty while total is " + total);
|
||||||
|
}
|
||||||
|
appendOnlineTerminals(result, firstRows, deviceStatus);
|
||||||
|
|
||||||
|
int effectivePageSize = firstRows == null || firstRows.size() == 0
|
||||||
|
? TERMINAL_PAGE_SIZE : firstRows.size();
|
||||||
|
int pageCount = (int) ((total + effectivePageSize - 1) / effectivePageSize);
|
||||||
|
if (pageCount > MAX_TERMINAL_PAGE_COUNT) {
|
||||||
|
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();
|
||||||
|
for (JsonNode root : findsOpenApiClient.postFailFastBatch(
|
||||||
|
TERMINAL_LIST_API, requests, BATCH_CONCURRENCY)) {
|
||||||
|
JsonNode rows = extractRows(successData(root));
|
||||||
|
receivedCount += rows == null ? 0 : rows.size();
|
||||||
|
appendOnlineTerminals(result, rows, deviceStatus);
|
||||||
|
}
|
||||||
|
if (receivedCount < total) {
|
||||||
|
throw new BizException("FindS online terminal result is incomplete: received="
|
||||||
|
+ receivedCount + ", total=" + total);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> terminalPageRequest(String deviceStatus, int pageNo) {
|
||||||
|
Map<String, Object> request = new LinkedHashMap<>();
|
||||||
|
request.put("deviceStatus", deviceStatus);
|
||||||
|
request.put("pageNo", pageNo);
|
||||||
|
request.put("pageSize", TERMINAL_PAGE_SIZE);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendOnlineTerminals(Map<String, OnlineTerminal> result,
|
||||||
|
JsonNode rows,
|
||||||
|
String requestedStatus) {
|
||||||
|
if (rows == null || !rows.isArray()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (JsonNode row : rows) {
|
||||||
|
String terminalNo = firstText(
|
||||||
|
text(firstPresent(row, "terminalNo", "no", "terminalCode", "imei")));
|
||||||
|
if (!StringUtils.hasText(terminalNo)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String trackType = firstText(
|
||||||
|
text(firstPresent(row, "trackType")),
|
||||||
|
text(firstPresent(row.path("track"), "trackType")));
|
||||||
|
if (StringUtils.hasText(trackType) && !"STAFF".equalsIgnoreCase(trackType)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String actualStatus = firstText(
|
||||||
|
text(firstPresent(row, "deviceStatus", "status", "onlineStatus")), requestedStatus);
|
||||||
|
result.put(terminalNo, new OnlineTerminal(terminalNo, actualStatus));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int updateLocationStatuses(Map<String, OnlineTerminal> onlineTerminals,
|
||||||
|
LocalDateTime syncTime) {
|
||||||
|
String updateOnlineSql = "UPDATE personnel_match SET location_status = 'ONLINE', "
|
||||||
|
+ "finds_device_status = ?, location_status_time = ?, version = version + 1 "
|
||||||
|
+ "WHERE source_config_code = ? AND (delete_enum IS NULL OR delete_enum = 'FALSE') "
|
||||||
|
+ "AND finds_terminal_no = ?";
|
||||||
|
List<Object[]> args = new ArrayList<>(onlineTerminals.size());
|
||||||
|
for (OnlineTerminal terminal : onlineTerminals.values()) {
|
||||||
|
args.add(new Object[]{terminal.deviceStatus, syncTime, SOURCE_CONFIG_CODE, terminal.terminalNo});
|
||||||
|
}
|
||||||
|
int matched = 0;
|
||||||
|
if (!args.isEmpty()) {
|
||||||
|
int[] updated = jdbcTemplate.batchUpdate(updateOnlineSql, args);
|
||||||
|
for (int count : updated) {
|
||||||
|
matched += count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jdbcTemplate.update("UPDATE personnel_match SET location_status = 'OFFLINE', "
|
||||||
|
+ "finds_device_status = 'OFFLINE', location_status_time = ?, version = version + 1 "
|
||||||
|
+ "WHERE source_config_code = ? AND (delete_enum IS NULL OR delete_enum = 'FALSE') "
|
||||||
|
+ "AND location_status = 'ONLINE' "
|
||||||
|
+ "AND (location_status_time IS NULL OR location_status_time < ?)",
|
||||||
|
syncTime, SOURCE_CONFIG_CODE, syncTime);
|
||||||
|
return matched;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonNode successData(JsonNode root) {
|
||||||
|
int code = root == null ? -1 : root.path("code").asInt(-1);
|
||||||
|
if (code != 200 && code != 0) {
|
||||||
|
throw new BizException("FindS personnel status synchronization failed, code=" + code);
|
||||||
|
}
|
||||||
|
return root.path("data");
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonNode extractRows(JsonNode data) {
|
||||||
|
if (data == null || data.isNull() || data.isMissingNode()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (data.isArray()) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
for (String field : new String[]{"data", "rows", "list", "records"}) {
|
||||||
|
JsonNode value = data.get(field);
|
||||||
|
if (value != null && value.isArray()) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long requiredTotal(JsonNode data, String responseName) {
|
||||||
|
Long total = longValue(firstPresent(data, "total", "totalCount", "count", "totalRecords"));
|
||||||
|
if (total == null || total < 0) {
|
||||||
|
throw new BizException(responseName + " is missing a valid total");
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonNode firstPresent(JsonNode node, String... fields) {
|
||||||
|
if (node == null || node.isNull() || node.isMissingNode()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (String field : fields) {
|
||||||
|
JsonNode value = node.get(field);
|
||||||
|
if (value != null && !value.isNull() && !value.isMissingNode()) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String text(JsonNode node) {
|
||||||
|
return node == null || node.isNull() || node.isMissingNode() || !node.isValueNode()
|
||||||
|
? null : node.asText();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String firstText(String... values) {
|
||||||
|
for (String value : values) {
|
||||||
|
if (StringUtils.hasText(value)) {
|
||||||
|
return value.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long longValue(JsonNode node) {
|
||||||
|
if (node == null || node.isNull() || node.isMissingNode()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return node.isNumber() ? node.asLong() : Long.valueOf(node.asText());
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class OnlineTerminal {
|
||||||
|
private final String terminalNo;
|
||||||
|
private final String deviceStatus;
|
||||||
|
|
||||||
|
private OnlineTerminal(String terminalNo, String deviceStatus) {
|
||||||
|
this.terminalNo = terminalNo;
|
||||||
|
this.deviceStatus = deviceStatus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SyncSummary {
|
||||||
|
private final long remoteStaffCount;
|
||||||
|
private final long localStaffCount;
|
||||||
|
private final boolean fullSync;
|
||||||
|
private final int fullSyncCount;
|
||||||
|
private final int onlineTerminalCount;
|
||||||
|
private final int onlineMappingCount;
|
||||||
|
|
||||||
|
private SyncSummary(long remoteStaffCount, long localStaffCount, boolean fullSync,
|
||||||
|
int fullSyncCount, int onlineTerminalCount, int onlineMappingCount) {
|
||||||
|
this.remoteStaffCount = remoteStaffCount;
|
||||||
|
this.localStaffCount = localStaffCount;
|
||||||
|
this.fullSync = fullSync;
|
||||||
|
this.fullSyncCount = fullSyncCount;
|
||||||
|
this.onlineTerminalCount = onlineTerminalCount;
|
||||||
|
this.onlineMappingCount = onlineMappingCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRemoteStaffCount() {
|
||||||
|
return remoteStaffCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLocalStaffCount() {
|
||||||
|
return localStaffCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFullSync() {
|
||||||
|
return fullSync;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFullSyncCount() {
|
||||||
|
return fullSyncCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getOnlineTerminalCount() {
|
||||||
|
return onlineTerminalCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getOnlineMappingCount() {
|
||||||
|
return onlineMappingCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -62,11 +62,14 @@ public class FindsStaffMappingSyncService {
|
||||||
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));
|
int firstPageSize = appendRows(result, extractRows(firstData));
|
||||||
if (firstPageSize == 0) {
|
long total = requiredTotal(firstData);
|
||||||
return result;
|
if (total > 0 && firstPageSize == 0) {
|
||||||
|
throw new BizException("FindS staff page is empty while total is " + total);
|
||||||
|
}
|
||||||
|
int pageCount = (int) ((total + PAGE_SIZE - 1) / PAGE_SIZE);
|
||||||
|
if (pageCount > MAX_PAGE_COUNT) {
|
||||||
|
throw new BizException("FindS staff pages exceed the configured safety limit: " + pageCount);
|
||||||
}
|
}
|
||||||
long total = totalValue(firstData, firstPageSize);
|
|
||||||
int pageCount = (int) Math.min((total + PAGE_SIZE - 1) / PAGE_SIZE, MAX_PAGE_COUNT);
|
|
||||||
if (pageCount <= 1) {
|
if (pageCount <= 1) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -74,9 +77,14 @@ public class FindsStaffMappingSyncService {
|
||||||
for (int pageNo = 2; pageNo <= pageCount; pageNo++) {
|
for (int pageNo = 2; pageNo <= pageCount; pageNo++) {
|
||||||
requests.add(pageRequest(pageNo));
|
requests.add(pageRequest(pageNo));
|
||||||
}
|
}
|
||||||
|
long receivedCount = firstPageSize;
|
||||||
for (JsonNode root : findsOpenApiClient.postFailFastBatch(
|
for (JsonNode root : findsOpenApiClient.postFailFastBatch(
|
||||||
STAFF_LIST_API, requests, BATCH_CONCURRENCY)) {
|
STAFF_LIST_API, requests, BATCH_CONCURRENCY)) {
|
||||||
appendRows(result, extractRows(successData(root)));
|
receivedCount += appendRows(result, extractRows(successData(root)));
|
||||||
|
}
|
||||||
|
if (receivedCount < total) {
|
||||||
|
throw new BizException("FindS staff result is incomplete: received="
|
||||||
|
+ receivedCount + ", total=" + total);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -218,9 +226,12 @@ public class FindsStaffMappingSyncService {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private long totalValue(JsonNode data, long defaultValue) {
|
private long requiredTotal(JsonNode data) {
|
||||||
Long total = longValue(firstPresent(data, "total", "totalCount", "count", "totalRecords"));
|
Long total = longValue(firstPresent(data, "total", "totalCount", "count", "totalRecords"));
|
||||||
return total == null ? defaultValue : total;
|
if (total == null || total < 0) {
|
||||||
|
throw new BizException("FindS staff response is missing a valid total");
|
||||||
|
}
|
||||||
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
private JsonNode firstPresent(JsonNode node, String... fields) {
|
private JsonNode firstPresent(JsonNode node, String... fields) {
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,8 @@ class PositionPersonLocalLookupService {
|
||||||
StringBuilder sql = new StringBuilder(
|
StringBuilder sql = new StringBuilder(
|
||||||
"SELECT pm.finds_staff_no, pm.finds_staff_no_type, pm.finds_id_card_no, "
|
"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_staff_name, pm.finds_corpinfo_id, pm.finds_corpinfo_name, "
|
||||||
+ "pm.finds_department_name, pm.finds_terminal_no, "
|
+ "pm.finds_department_name, pm.finds_terminal_no, pm.location_status, "
|
||||||
|
+ "pm.finds_device_status, "
|
||||||
+ "u.id AS user_id, u.username AS local_staff_no, u.name AS staff_name, "
|
+ "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.corpinfo_id, "
|
+ "TRIM(u.phone) AS mobile_no, u.user_id_card AS id_card_no, u.corpinfo_id, "
|
||||||
+ "ci.corp_name AS corpinfo_name, ci.code AS credit_code, ci.port_area, "
|
+ "ci.corp_name AS corpinfo_name, ci.code AS credit_code, ci.port_area, "
|
||||||
|
|
@ -103,6 +104,8 @@ class PositionPersonLocalLookupService {
|
||||||
mapping.setFindsCorpinfoName(rs.getString("finds_corpinfo_name"));
|
mapping.setFindsCorpinfoName(rs.getString("finds_corpinfo_name"));
|
||||||
mapping.setFindsDepartmentName(rs.getString("finds_department_name"));
|
mapping.setFindsDepartmentName(rs.getString("finds_department_name"));
|
||||||
mapping.setFindsTerminalNo(normalize(rs.getString("finds_terminal_no")));
|
mapping.setFindsTerminalNo(normalize(rs.getString("finds_terminal_no")));
|
||||||
|
mapping.setLocationStatus(normalize(rs.getString("location_status")));
|
||||||
|
mapping.setFindsDeviceStatus(normalize(rs.getString("finds_device_status")));
|
||||||
mapping.setLocal(local);
|
mapping.setLocal(local);
|
||||||
return mapping;
|
return mapping;
|
||||||
});
|
});
|
||||||
|
|
@ -228,13 +231,14 @@ class PositionPersonLocalLookupService {
|
||||||
Map<String, TerminalSnapshot> result = new LinkedHashMap<>();
|
Map<String, TerminalSnapshot> result = new LinkedHashMap<>();
|
||||||
for (List<String> batch : batches(terminalNos)) {
|
for (List<String> batch : batches(terminalNos)) {
|
||||||
String placeholders = String.join(",", Collections.nCopies(batch.size(), "?"));
|
String placeholders = String.join(",", Collections.nCopies(batch.size(), "?"));
|
||||||
String sql = "SELECT terminal_no, device_type, last_location_time, last_location_name "
|
String sql = "SELECT terminal_no, device_type, device_status, last_location_time, last_location_name "
|
||||||
+ "FROM terminal WHERE (delete_enum IS NULL OR delete_enum = 'FALSE') "
|
+ "FROM terminal WHERE (delete_enum IS NULL OR delete_enum = 'FALSE') "
|
||||||
+ "AND terminal_no IN (" + placeholders + ") ORDER BY update_time DESC, id DESC";
|
+ "AND terminal_no IN (" + placeholders + ") ORDER BY update_time DESC, id DESC";
|
||||||
jdbcTemplate.query(sql, batch.toArray(), (rs, rowNum) -> {
|
jdbcTemplate.query(sql, batch.toArray(), (rs, rowNum) -> {
|
||||||
TerminalSnapshot terminal = new TerminalSnapshot();
|
TerminalSnapshot terminal = new TerminalSnapshot();
|
||||||
terminal.setTerminalNo(rs.getString("terminal_no"));
|
terminal.setTerminalNo(rs.getString("terminal_no"));
|
||||||
terminal.setDeviceType(rs.getString("device_type"));
|
terminal.setDeviceType(rs.getString("device_type"));
|
||||||
|
terminal.setDeviceStatus(normalize(rs.getString("device_status")));
|
||||||
Timestamp time = rs.getTimestamp("last_location_time");
|
Timestamp time = rs.getTimestamp("last_location_time");
|
||||||
terminal.setLastLocationTime(time == null ? null : time.getTime());
|
terminal.setLastLocationTime(time == null ? null : time.getTime());
|
||||||
terminal.setLastLocationName(rs.getString("last_location_name"));
|
terminal.setLastLocationName(rs.getString("last_location_name"));
|
||||||
|
|
@ -288,6 +292,7 @@ class PositionPersonLocalLookupService {
|
||||||
static class TerminalSnapshot {
|
static class TerminalSnapshot {
|
||||||
private String terminalNo;
|
private String terminalNo;
|
||||||
private String deviceType;
|
private String deviceType;
|
||||||
|
private String deviceStatus;
|
||||||
private Long lastLocationTime;
|
private Long lastLocationTime;
|
||||||
private String lastLocationName;
|
private String lastLocationName;
|
||||||
}
|
}
|
||||||
|
|
@ -302,6 +307,8 @@ class PositionPersonLocalLookupService {
|
||||||
private String findsCorpinfoName;
|
private String findsCorpinfoName;
|
||||||
private String findsDepartmentName;
|
private String findsDepartmentName;
|
||||||
private String findsTerminalNo;
|
private String findsTerminalNo;
|
||||||
|
private String locationStatus;
|
||||||
|
private String findsDeviceStatus;
|
||||||
private LocalPerson local;
|
private LocalPerson local;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,8 @@ 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;
|
||||||
List<MatchedPerson> matched = loadMatchedPeople(query);
|
boolean persistedMappings = localLookupService.hasPersonnelMappings();
|
||||||
|
List<MatchedPerson> matched = loadMatchedPeople(query, persistedMappings);
|
||||||
Set<String> idCardNos = new LinkedHashSet<>();
|
Set<String> idCardNos = new LinkedHashSet<>();
|
||||||
for (MatchedPerson person : matched) {
|
for (MatchedPerson person : matched) {
|
||||||
if (StringUtils.hasText(person.local.getIdCardNo())) {
|
if (StringUtils.hasText(person.local.getIdCardNo())) {
|
||||||
|
|
@ -63,7 +64,8 @@ public class PositionPersonQueryExe {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, JsonNode> currentLocations = locateTerminals(terminalNos);
|
Map<String, JsonNode> currentLocations = persistedMappings
|
||||||
|
? Collections.emptyMap() : locateTerminals(terminalNos);
|
||||||
Map<String, PositionPersonLocalLookupService.TerminalSnapshot> localTerminals =
|
Map<String, PositionPersonLocalLookupService.TerminalSnapshot> localTerminals =
|
||||||
localLookupService.findTerminals(terminalNos);
|
localLookupService.findTerminals(terminalNos);
|
||||||
List<PositionPersonCO> rows = new ArrayList<>();
|
List<PositionPersonCO> rows = new ArrayList<>();
|
||||||
|
|
@ -71,8 +73,9 @@ public class PositionPersonQueryExe {
|
||||||
JsonNode currentLocation = currentLocations.get(person.finds.terminalNo);
|
JsonNode currentLocation = currentLocations.get(person.finds.terminalNo);
|
||||||
PositionPersonLocalLookupService.TerminalSnapshot terminal =
|
PositionPersonLocalLookupService.TerminalSnapshot terminal =
|
||||||
localTerminals.get(person.finds.terminalNo);
|
localTerminals.get(person.finds.terminalNo);
|
||||||
boolean online = hasLocation(currentLocation);
|
boolean online = persistedMappings
|
||||||
boolean locatedBefore = online || hasHistoricalLocation(person.finds.lastLocation)
|
? isStoredOnline(person.finds, terminal) : hasLocation(currentLocation);
|
||||||
|
boolean locatedBefore = persistedMappings || online || hasHistoricalLocation(person.finds.lastLocation)
|
||||||
|| person.finds.lastLocationTime != null
|
|| person.finds.lastLocationTime != null
|
||||||
|| hasHistoricalLocation(terminal);
|
|| hasHistoricalLocation(terminal);
|
||||||
if (!locatedBefore) {
|
if (!locatedBefore) {
|
||||||
|
|
@ -94,8 +97,8 @@ public class PositionPersonQueryExe {
|
||||||
return PageResponse.of(rows.subList(fromIndex, toIndex), rows.size(), pageSize, pageIndex);
|
return PageResponse.of(rows.subList(fromIndex, toIndex), rows.size(), pageSize, pageIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<MatchedPerson> loadMatchedPeople(PositionPersonPageQry query) {
|
private List<MatchedPerson> loadMatchedPeople(PositionPersonPageQry query, boolean persistedMappings) {
|
||||||
if (localLookupService.hasPersonnelMappings()) {
|
if (persistedMappings) {
|
||||||
return loadPersistedMatches(query);
|
return loadPersistedMatches(query);
|
||||||
}
|
}
|
||||||
List<FindsStaff> findsStaff = loadAllFindsStaff();
|
List<FindsStaff> findsStaff = loadAllFindsStaff();
|
||||||
|
|
@ -137,6 +140,8 @@ public class PositionPersonQueryExe {
|
||||||
staff.sourceCompanyName = mapping.getFindsCorpinfoName();
|
staff.sourceCompanyName = mapping.getFindsCorpinfoName();
|
||||||
staff.sourceDepartmentName = mapping.getFindsDepartmentName();
|
staff.sourceDepartmentName = mapping.getFindsDepartmentName();
|
||||||
staff.terminalNo = mapping.getFindsTerminalNo();
|
staff.terminalNo = mapping.getFindsTerminalNo();
|
||||||
|
staff.locationStatus = mapping.getLocationStatus();
|
||||||
|
staff.deviceStatus = mapping.getFindsDeviceStatus();
|
||||||
result.add(new MatchedPerson(staff, local));
|
result.add(new MatchedPerson(staff, local));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -343,6 +348,19 @@ public class PositionPersonQueryExe {
|
||||||
|| StringUtils.hasText(terminal.getLastLocationName()));
|
|| StringUtils.hasText(terminal.getLastLocationName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isStoredOnline(FindsStaff staff,
|
||||||
|
PositionPersonLocalLookupService.TerminalSnapshot terminal) {
|
||||||
|
if (staff != null && StringUtils.hasText(staff.locationStatus)) {
|
||||||
|
return STATUS_ONLINE.equalsIgnoreCase(staff.locationStatus);
|
||||||
|
}
|
||||||
|
String deviceStatus = firstText(
|
||||||
|
staff == null ? null : staff.deviceStatus,
|
||||||
|
terminal == null ? null : terminal.getDeviceStatus());
|
||||||
|
return STATUS_ONLINE.equalsIgnoreCase(deviceStatus)
|
||||||
|
|| "STATIC".equalsIgnoreCase(deviceStatus)
|
||||||
|
|| "MOVING".equalsIgnoreCase(deviceStatus);
|
||||||
|
}
|
||||||
|
|
||||||
private String positionSource(PositionPersonLocalLookupService.TerminalSnapshot terminal) {
|
private String positionSource(PositionPersonLocalLookupService.TerminalSnapshot terminal) {
|
||||||
String type = terminal == null ? null : normalize(terminal.getDeviceType());
|
String type = terminal == null ? null : normalize(terminal.getDeviceType());
|
||||||
if ("CARD".equalsIgnoreCase(type)) {
|
if ("CARD".equalsIgnoreCase(type)) {
|
||||||
|
|
@ -542,6 +560,8 @@ public class PositionPersonQueryExe {
|
||||||
private String sourceCompanyName;
|
private String sourceCompanyName;
|
||||||
private String sourceDepartmentName;
|
private String sourceDepartmentName;
|
||||||
private String terminalNo;
|
private String terminalNo;
|
||||||
|
private String locationStatus;
|
||||||
|
private String deviceStatus;
|
||||||
private Long lastLocationTime;
|
private Long lastLocationTime;
|
||||||
private JsonNode lastLocation;
|
private JsonNode lastLocation;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
package com.zcloud.personnel.positioning.command.query;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.zcloud.personnel.positioning.integration.finds.FindsOpenApiClient;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyMap;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
class FindsStaffLocationStatusSyncServiceTest {
|
||||||
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
private FindsOpenApiClient findsOpenApiClient;
|
||||||
|
private FindsStaffMappingSyncService mappingSyncService;
|
||||||
|
private JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
findsOpenApiClient = mock(FindsOpenApiClient.class);
|
||||||
|
mappingSyncService = mock(FindsStaffMappingSyncService.class);
|
||||||
|
jdbcTemplate = mock(JdbcTemplate.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void unchangedStaffCountSkipsFullSyncAndRefreshesStatus() throws Exception {
|
||||||
|
stubCount(2L);
|
||||||
|
stubTerminalStatusPages();
|
||||||
|
|
||||||
|
FindsStaffLocationStatusSyncService service = new FindsStaffLocationStatusSyncService(
|
||||||
|
findsOpenApiClient, mappingSyncService, jdbcTemplate);
|
||||||
|
FindsStaffLocationStatusSyncService.SyncSummary summary = service.synchronize();
|
||||||
|
|
||||||
|
assertFalse(summary.isFullSync());
|
||||||
|
verify(mappingSyncService, never()).syncAll();
|
||||||
|
verify(jdbcTemplate).update(anyString(), any(), eq("FINDS"), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void changedStaffCountRunsFullSyncBeforeStatusRefresh() throws Exception {
|
||||||
|
stubCount(2L);
|
||||||
|
when(jdbcTemplate.queryForObject(anyString(), eq(Long.class), eq("FINDS"))).thenReturn(1L);
|
||||||
|
stubTerminalStatusPages();
|
||||||
|
when(mappingSyncService.syncAll()).thenReturn(2);
|
||||||
|
|
||||||
|
FindsStaffLocationStatusSyncService service = new FindsStaffLocationStatusSyncService(
|
||||||
|
findsOpenApiClient, mappingSyncService, jdbcTemplate);
|
||||||
|
FindsStaffLocationStatusSyncService.SyncSummary summary = service.synchronize();
|
||||||
|
|
||||||
|
assertTrue(summary.isFullSync());
|
||||||
|
verify(mappingSyncService).syncAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void failedOnlineStatusPageDoesNotMarkExistingRowsOffline() throws Exception {
|
||||||
|
stubCount(2L);
|
||||||
|
when(findsOpenApiClient.postFailFast(eq("finds.terminal.list"), anyMap()))
|
||||||
|
.thenReturn(json("{\"code\":500,\"data\":{\"total\":0,\"data\":[]}}"));
|
||||||
|
|
||||||
|
FindsStaffLocationStatusSyncService service = new FindsStaffLocationStatusSyncService(
|
||||||
|
findsOpenApiClient, mappingSyncService, jdbcTemplate);
|
||||||
|
|
||||||
|
try {
|
||||||
|
service.synchronize();
|
||||||
|
} catch (RuntimeException expected) {
|
||||||
|
// The status cycle must fail before any database status update.
|
||||||
|
}
|
||||||
|
verify(jdbcTemplate, never()).batchUpdate(anyString(), any(java.util.List.class));
|
||||||
|
verify(jdbcTemplate, never()).update(anyString(), any(), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void missingRemoteStaffTotalStopsBeforeDatabaseStatusUpdate() throws Exception {
|
||||||
|
when(findsOpenApiClient.postFailFast(eq("finds.staff.list"), anyMap()))
|
||||||
|
.thenReturn(json("{\"code\":200,\"data\":{\"data\":[]}}"));
|
||||||
|
|
||||||
|
FindsStaffLocationStatusSyncService service = new FindsStaffLocationStatusSyncService(
|
||||||
|
findsOpenApiClient, mappingSyncService, jdbcTemplate);
|
||||||
|
|
||||||
|
assertThrows(RuntimeException.class, service::synchronize);
|
||||||
|
verify(mappingSyncService, never()).syncAll();
|
||||||
|
verify(jdbcTemplate, never()).batchUpdate(anyString(), any(java.util.List.class));
|
||||||
|
verify(jdbcTemplate, never()).update(anyString(), any(), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stubCount(long remoteCount) throws Exception {
|
||||||
|
when(findsOpenApiClient.postFailFast(eq("finds.staff.list"), anyMap()))
|
||||||
|
.thenReturn(json("{\"code\":200,\"data\":{\"total\":" + remoteCount
|
||||||
|
+ ",\"data\":[]}}"));
|
||||||
|
when(jdbcTemplate.queryForObject(anyString(), eq(Long.class), eq("FINDS")))
|
||||||
|
.thenReturn(remoteCount);
|
||||||
|
when(jdbcTemplate.queryForObject(anyString(), eq(LocalDateTime.class), eq("FINDS")))
|
||||||
|
.thenReturn(LocalDateTime.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stubTerminalStatusPages() throws Exception {
|
||||||
|
when(findsOpenApiClient.postFailFast(eq("finds.terminal.list"), anyMap()))
|
||||||
|
.thenReturn(json("{\"code\":200,\"data\":{\"total\":0,\"data\":[]}}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonNode json(String value) throws Exception {
|
||||||
|
return objectMapper.readTree(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
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.clientobject.PositionPersonCO;
|
||||||
|
import com.zcloud.personnel.positioning.integration.finds.FindsOpenApiClient;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyMap;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anySet;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
class PositionPersonStoredStatusTest {
|
||||||
|
private FindsOpenApiClient findsOpenApiClient;
|
||||||
|
private PositionPersonLocalLookupService localLookupService;
|
||||||
|
private PositionPersonQueryExe queryExe;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
findsOpenApiClient = mock(FindsOpenApiClient.class);
|
||||||
|
localLookupService = mock(PositionPersonLocalLookupService.class);
|
||||||
|
queryExe = new PositionPersonQueryExe(findsOpenApiClient, localLookupService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void persistedStatusIsUsedWithoutCallingFindsLocate() {
|
||||||
|
PositionPersonLocalLookupService.LocalPerson local = new PositionPersonLocalLookupService.LocalPerson();
|
||||||
|
local.setUserId(1L);
|
||||||
|
local.setStaffName("Stored User");
|
||||||
|
local.setMobileNo("13800000001");
|
||||||
|
|
||||||
|
PositionPersonLocalLookupService.PersonMapping mapping =
|
||||||
|
new PositionPersonLocalLookupService.PersonMapping();
|
||||||
|
mapping.setFindsStaffNo("13800000001");
|
||||||
|
mapping.setFindsTerminalNo("T-1");
|
||||||
|
mapping.setLocationStatus("ONLINE");
|
||||||
|
mapping.setLocal(local);
|
||||||
|
|
||||||
|
when(localLookupService.hasPersonnelMappings()).thenReturn(true);
|
||||||
|
when(localLookupService.findMatchedPersonnel(any(PositionPersonPageQry.class)))
|
||||||
|
.thenReturn(Collections.singletonList(mapping));
|
||||||
|
when(localLookupService.findTerminalNosByIdCardNos(anySet()))
|
||||||
|
.thenReturn(Collections.emptyMap());
|
||||||
|
when(localLookupService.findTerminals(eq(Collections.singleton("T-1"))))
|
||||||
|
.thenReturn(Collections.emptyMap());
|
||||||
|
|
||||||
|
PositionPersonPageQry qry = new PositionPersonPageQry();
|
||||||
|
qry.setPageIndex(1);
|
||||||
|
qry.setPageSize(20);
|
||||||
|
PageResponse<PositionPersonCO> response = queryExe.list(qry);
|
||||||
|
|
||||||
|
assertEquals(1, response.getTotalCount());
|
||||||
|
assertEquals("ONLINE", response.getData().get(0).getLocationStatus());
|
||||||
|
verify(findsOpenApiClient, never()).postFailFast(eq("finds.point.locate"), anyMap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -189,6 +189,9 @@ CREATE TABLE IF NOT EXISTS `personnel_match` (
|
||||||
`local_id_card_no` varchar(64) DEFAULT NULL COMMENT 'matched local id card snapshot',
|
`local_id_card_no` varchar(64) DEFAULT NULL COMMENT 'matched local id card snapshot',
|
||||||
`match_status` varchar(32) NOT NULL DEFAULT 'UNMATCHED' COMMENT 'MATCHED/UNMATCHED/AMBIGUOUS/DISABLED',
|
`match_status` varchar(32) NOT NULL DEFAULT 'UNMATCHED' COMMENT 'MATCHED/UNMATCHED/AMBIGUOUS/DISABLED',
|
||||||
`match_type` varchar(32) DEFAULT NULL COMMENT 'MOBILE/ID_CARD/MOBILE_AND_ID_CARD',
|
`match_type` varchar(32) DEFAULT NULL COMMENT 'MOBILE/ID_CARD/MOBILE_AND_ID_CARD',
|
||||||
|
`location_status` varchar(32) NOT NULL DEFAULT 'OFFLINE' COMMENT 'ONLINE/OFFLINE',
|
||||||
|
`finds_device_status` varchar(32) DEFAULT NULL COMMENT 'FindS STATIC/MOVING/OFFLINE/UN_LOCATION/NONE',
|
||||||
|
`location_status_time` datetime DEFAULT NULL COMMENT 'location status sync time',
|
||||||
`last_seen_time` datetime DEFAULT NULL COMMENT 'last time observed from FindS',
|
`last_seen_time` datetime DEFAULT NULL COMMENT 'last time observed from FindS',
|
||||||
`last_sync_time` datetime DEFAULT NULL COMMENT 'last mapping synchronization time',
|
`last_sync_time` datetime DEFAULT NULL COMMENT 'last mapping synchronization time',
|
||||||
`sync_status` varchar(32) NOT NULL DEFAULT 'SUCCESS' COMMENT 'SUCCESS/FAIL',
|
`sync_status` varchar(32) NOT NULL DEFAULT 'SUCCESS' COMMENT 'SUCCESS/FAIL',
|
||||||
|
|
@ -211,6 +214,7 @@ CREATE TABLE IF NOT EXISTS `personnel_match` (
|
||||||
KEY `idx_personnel_match_terminal_no` (`finds_terminal_no`),
|
KEY `idx_personnel_match_terminal_no` (`finds_terminal_no`),
|
||||||
KEY `idx_personnel_match_local_id_card` (`local_id_card_no`),
|
KEY `idx_personnel_match_local_id_card` (`local_id_card_no`),
|
||||||
KEY `idx_personnel_match_scope_status` (`finds_corpinfo_id`, `match_status`),
|
KEY `idx_personnel_match_scope_status` (`finds_corpinfo_id`, `match_status`),
|
||||||
|
KEY `idx_personnel_match_location_status` (`location_status`, `match_status`),
|
||||||
KEY `idx_personnel_match_last_seen` (`last_seen_time`),
|
KEY `idx_personnel_match_last_seen` (`last_seen_time`),
|
||||||
KEY `idx_personnel_match_tenant_org` (`tenant_id`, `org_id`)
|
KEY `idx_personnel_match_tenant_org` (`tenant_id`, `org_id`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='FindS to local personnel mapping';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='FindS to local personnel mapping';
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
-- Store the latest online/offline state without persisting realtime coordinates.
|
||||||
|
SET @ddl = IF(
|
||||||
|
(SELECT COUNT(1) FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'personnel_match' AND COLUMN_NAME = 'location_status') = 0,
|
||||||
|
'ALTER TABLE `personnel_match` ADD COLUMN `location_status` varchar(32) NOT NULL DEFAULT ''OFFLINE'' COMMENT ''ONLINE/OFFLINE'' AFTER `match_type`',
|
||||||
|
'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 = 'finds_device_status') = 0,
|
||||||
|
'ALTER TABLE `personnel_match` ADD COLUMN `finds_device_status` varchar(32) DEFAULT NULL COMMENT ''FindS device status'' AFTER `location_status`',
|
||||||
|
'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_status_time') = 0,
|
||||||
|
'ALTER TABLE `personnel_match` ADD COLUMN `location_status_time` datetime DEFAULT NULL COMMENT ''location status sync time'' AFTER `finds_device_status`',
|
||||||
|
'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_status') = 0,
|
||||||
|
'ALTER TABLE `personnel_match` ADD KEY `idx_personnel_match_location_status` (`location_status`, `match_status`)',
|
||||||
|
'DO 0'
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @ddl;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
Loading…
Reference in New Issue