调整实时定位推送为直接转换WebSocket消息
parent
1c630fa960
commit
7bfab8159f
|
|
@ -0,0 +1,32 @@
|
||||||
|
package com.zcloud.personnel.positioning.plan;
|
||||||
|
|
||||||
|
import com.jjb.saas.framework.job.Job;
|
||||||
|
import com.jjb.saas.framework.job.annotation.JobRegister;
|
||||||
|
import com.xxl.job.core.biz.model.ReturnT;
|
||||||
|
import com.xxl.job.core.context.XxlJobHelper;
|
||||||
|
import com.xxl.job.core.handler.annotation.XxlJob;
|
||||||
|
import com.zcloud.personnel.positioning.command.query.FindsStaffMappingSyncService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class FindsStaffMappingSyncJob implements Job {
|
||||||
|
private final FindsStaffMappingSyncService syncService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@JobRegister(cron = "0 */5 * * * ?", jobDesc = "FindS人员映射同步", author = "系统", triggerStatus = 1)
|
||||||
|
@XxlJob("com.zcloud.personnel.positioning.plan.FindsStaffMappingSyncJob")
|
||||||
|
public ReturnT<String> execute(String param) {
|
||||||
|
try {
|
||||||
|
int count = syncService.syncAll();
|
||||||
|
String message = "FindS人员映射同步完成,本次处理 " + count + " 条";
|
||||||
|
XxlJobHelper.log(message);
|
||||||
|
return new ReturnT<>(ReturnT.SUCCESS_CODE, message);
|
||||||
|
} catch (Exception e) {
|
||||||
|
String message = "FindS人员映射同步失败:" + e.getMessage();
|
||||||
|
XxlJobHelper.log(message);
|
||||||
|
return new ReturnT<>(ReturnT.FAIL_CODE, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,287 @@
|
||||||
|
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.LinkedHashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes the FindS-to-local personnel mapping cache.
|
||||||
|
*
|
||||||
|
* <p>The cache contains identity and terminal snapshots only. Location coordinates are deliberately
|
||||||
|
* not persisted here and continue to come from FindS at query time.</p>
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class FindsStaffMappingSyncService {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(FindsStaffMappingSyncService.class);
|
||||||
|
private static final String STAFF_LIST_API = "finds.staff.list";
|
||||||
|
private static final String SOURCE_CONFIG_CODE = "FINDS";
|
||||||
|
private static final int PAGE_SIZE = 2000;
|
||||||
|
private static final int MAX_PAGE_COUNT = 50;
|
||||||
|
private static final int BATCH_CONCURRENCY = 4;
|
||||||
|
|
||||||
|
private final FindsOpenApiClient findsOpenApiClient;
|
||||||
|
private final PositionPersonLocalLookupService localLookupService;
|
||||||
|
private final JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
|
public int syncAll() {
|
||||||
|
// The schema uses second precision; keep the same precision for the stale-row cutoff.
|
||||||
|
LocalDateTime syncTime = LocalDateTime.now().withNano(0);
|
||||||
|
List<StaffSnapshot> staffRows = loadAllStaff();
|
||||||
|
Set<String> staffNos = new LinkedHashSet<>();
|
||||||
|
for (StaffSnapshot row : staffRows) {
|
||||||
|
staffNos.add(row.staffNo);
|
||||||
|
}
|
||||||
|
Map<String, PositionPersonLocalLookupService.LocalPerson> localPeople =
|
||||||
|
localLookupService.findUniquePeopleByPhones(staffNos);
|
||||||
|
for (StaffSnapshot row : staffRows) {
|
||||||
|
upsert(row, localPeople.get(row.staffNo), syncTime);
|
||||||
|
}
|
||||||
|
disableMissingRows(syncTime);
|
||||||
|
LOGGER.info("FindS staff mapping synchronization completed, total={}, matched={}",
|
||||||
|
staffRows.size(), countMatched(staffRows, localPeople));
|
||||||
|
return staffRows.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<StaffSnapshot> loadAllStaff() {
|
||||||
|
List<StaffSnapshot> result = new ArrayList<>();
|
||||||
|
JsonNode firstData = successData(
|
||||||
|
findsOpenApiClient.postFailFast(STAFF_LIST_API, pageRequest(1)));
|
||||||
|
int firstPageSize = appendRows(result, extractRows(firstData));
|
||||||
|
if (firstPageSize == 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
long total = totalValue(firstData, firstPageSize);
|
||||||
|
int pageCount = (int) Math.min((total + PAGE_SIZE - 1) / PAGE_SIZE, MAX_PAGE_COUNT);
|
||||||
|
if (pageCount <= 1) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
List<Map<String, Object>> requests = new ArrayList<>(pageCount - 1);
|
||||||
|
for (int pageNo = 2; pageNo <= pageCount; pageNo++) {
|
||||||
|
requests.add(pageRequest(pageNo));
|
||||||
|
}
|
||||||
|
for (JsonNode root : findsOpenApiClient.postFailFastBatch(
|
||||||
|
STAFF_LIST_API, requests, BATCH_CONCURRENCY)) {
|
||||||
|
appendRows(result, extractRows(successData(root)));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> pageRequest(int pageNo) {
|
||||||
|
Map<String, Object> request = new LinkedHashMap<>();
|
||||||
|
request.put("pageNo", pageNo);
|
||||||
|
request.put("pageSize", PAGE_SIZE);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int appendRows(List<StaffSnapshot> result, JsonNode rows) {
|
||||||
|
if (rows == null || !rows.isArray()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
for (JsonNode row : rows) {
|
||||||
|
StaffSnapshot snapshot = map(row);
|
||||||
|
if (snapshot != null) {
|
||||||
|
result.add(snapshot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rows.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
private StaffSnapshot map(JsonNode row) {
|
||||||
|
if (row == null || row.isNull()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
JsonNode terminalInfo = row.path("terminalInfo");
|
||||||
|
StaffSnapshot snapshot = new StaffSnapshot();
|
||||||
|
snapshot.staffNo = firstText(
|
||||||
|
text(firstPresent(row, "trackNo")),
|
||||||
|
text(firstPresent(terminalInfo, "trackNo")),
|
||||||
|
text(firstPresent(row, "workNo")),
|
||||||
|
text(firstPresent(row, "employeeNo")),
|
||||||
|
text(firstPresent(row, "staffNo")),
|
||||||
|
text(firstPresent(row, "code")),
|
||||||
|
text(firstPresent(terminalInfo, "staffNo")));
|
||||||
|
if (!StringUtils.hasText(snapshot.staffNo)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
snapshot.staffNoType = firstText(
|
||||||
|
text(firstPresent(row, "trackNo")),
|
||||||
|
text(firstPresent(terminalInfo, "trackNo"))) != null
|
||||||
|
? "TRACK_NO" : "STAFF_NO";
|
||||||
|
snapshot.findsTrackId = longValue(firstPresent(row, "trackId", "id"));
|
||||||
|
snapshot.idCardNo = firstText(
|
||||||
|
text(firstPresent(row, "idCardNo", "identityNo", "certificateNo", "cardNo")),
|
||||||
|
text(firstPresent(terminalInfo, "idCardNo")));
|
||||||
|
snapshot.staffName = firstText(text(firstPresent(row, "staffName", "name", "realName", "userName")));
|
||||||
|
snapshot.corpinfoId = firstText(text(firstPresent(row,
|
||||||
|
"corpinfoId", "companyId", "corpId", "enterpriseId")));
|
||||||
|
snapshot.corpinfoName = firstText(text(firstPresent(row,
|
||||||
|
"companyName", "corpName", "enterpriseName", "orgName")));
|
||||||
|
snapshot.departmentName = firstText(text(firstPresent(row,
|
||||||
|
"departmentName", "deptName", "orgDepartmentName")));
|
||||||
|
snapshot.terminalNo = firstText(
|
||||||
|
text(firstPresent(row, "terminalNo")),
|
||||||
|
text(firstPresent(terminalInfo, "terminalNo")),
|
||||||
|
text(firstPresent(row.path("terminal"), "terminalNo")),
|
||||||
|
text(firstPresent(row.path("bindTerminal"), "terminalNo")),
|
||||||
|
terminalNoFromList(row.path("takenTerminalList")));
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void upsert(StaffSnapshot row,
|
||||||
|
PositionPersonLocalLookupService.LocalPerson local,
|
||||||
|
LocalDateTime syncTime) {
|
||||||
|
String matchStatus = local == null ? "UNMATCHED" : "MATCHED";
|
||||||
|
String matchType = local == null ? null : "MOBILE";
|
||||||
|
if (local != null && StringUtils.hasText(row.idCardNo) && StringUtils.hasText(local.getIdCardNo())
|
||||||
|
&& !row.idCardNo.equals(local.getIdCardNo().trim())) {
|
||||||
|
matchStatus = "AMBIGUOUS";
|
||||||
|
matchType = null;
|
||||||
|
local = null;
|
||||||
|
}
|
||||||
|
String sql = "INSERT INTO personnel_match (source_system, source_config_code, finds_staff_no, "
|
||||||
|
+ "finds_staff_no_type, finds_track_id, finds_id_card_no, finds_staff_name, "
|
||||||
|
+ "finds_corpinfo_id, finds_corpinfo_name, finds_department_name, finds_terminal_no, "
|
||||||
|
+ "local_user_id, local_mobile_no, local_id_card_no, match_status, match_type, "
|
||||||
|
+ "last_seen_time, last_sync_time, sync_status, sync_error, delete_enum) "
|
||||||
|
+ "VALUES ('FINDS', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'SUCCESS', NULL, 'FALSE') "
|
||||||
|
+ "ON DUPLICATE KEY UPDATE finds_staff_no_type = VALUES(finds_staff_no_type), "
|
||||||
|
+ "finds_track_id = VALUES(finds_track_id), finds_id_card_no = VALUES(finds_id_card_no), "
|
||||||
|
+ "finds_staff_name = VALUES(finds_staff_name), finds_corpinfo_id = VALUES(finds_corpinfo_id), "
|
||||||
|
+ "finds_corpinfo_name = VALUES(finds_corpinfo_name), finds_department_name = VALUES(finds_department_name), "
|
||||||
|
+ "finds_terminal_no = VALUES(finds_terminal_no), local_user_id = VALUES(local_user_id), "
|
||||||
|
+ "local_mobile_no = VALUES(local_mobile_no), local_id_card_no = VALUES(local_id_card_no), "
|
||||||
|
+ "match_status = VALUES(match_status), match_type = VALUES(match_type), "
|
||||||
|
+ "last_seen_time = VALUES(last_seen_time), last_sync_time = VALUES(last_sync_time), "
|
||||||
|
+ "sync_status = 'SUCCESS', sync_error = NULL, delete_enum = 'FALSE', version = version + 1";
|
||||||
|
jdbcTemplate.update(sql, SOURCE_CONFIG_CODE, row.staffNo, row.staffNoType, row.findsTrackId,
|
||||||
|
row.idCardNo, row.staffName, row.corpinfoId, row.corpinfoName, row.departmentName,
|
||||||
|
row.terminalNo, local == null ? null : local.getUserId(),
|
||||||
|
local == null ? null : local.getMobileNo(), local == null ? null : local.getIdCardNo(),
|
||||||
|
matchStatus, matchType, syncTime, syncTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void disableMissingRows(LocalDateTime syncTime) {
|
||||||
|
jdbcTemplate.update("UPDATE personnel_match SET match_status = 'DISABLED', "
|
||||||
|
+ "sync_status = 'SUCCESS', last_sync_time = ?, version = version + 1 "
|
||||||
|
+ "WHERE source_config_code = ? AND (delete_enum IS NULL OR delete_enum = 'FALSE') "
|
||||||
|
+ "AND (last_seen_time IS NULL OR last_seen_time < ?)",
|
||||||
|
syncTime, SOURCE_CONFIG_CODE, syncTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int countMatched(List<StaffSnapshot> rows,
|
||||||
|
Map<String, PositionPersonLocalLookupService.LocalPerson> localPeople) {
|
||||||
|
int count = 0;
|
||||||
|
for (StaffSnapshot row : rows) {
|
||||||
|
if (localPeople.containsKey(row.staffNo)) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonNode successData(JsonNode root) {
|
||||||
|
int code = root == null ? -1 : root.path("code").asInt(-1);
|
||||||
|
if (code != 200 && code != 0) {
|
||||||
|
throw new BizException("FindS staff list 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 totalValue(JsonNode data, long defaultValue) {
|
||||||
|
Long total = longValue(firstPresent(data, "total", "totalCount", "count", "totalRecords"));
|
||||||
|
return total == null ? defaultValue : 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 terminalNoFromList(JsonNode terminals) {
|
||||||
|
if (terminals != null && terminals.isArray()) {
|
||||||
|
for (JsonNode terminal : terminals) {
|
||||||
|
String terminalNo = text(firstPresent(terminal, "terminalNo"));
|
||||||
|
if (StringUtils.hasText(terminalNo)) {
|
||||||
|
return terminalNo.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 StaffSnapshot {
|
||||||
|
private String staffNo;
|
||||||
|
private String staffNoType;
|
||||||
|
private Long findsTrackId;
|
||||||
|
private String idCardNo;
|
||||||
|
private String staffName;
|
||||||
|
private String corpinfoId;
|
||||||
|
private String corpinfoName;
|
||||||
|
private String departmentName;
|
||||||
|
private String terminalNo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package com.zcloud.personnel.positioning.command.query;
|
package com.zcloud.personnel.positioning.command.query;
|
||||||
|
|
||||||
|
import com.zcloud.personnel.positioning.dto.PositionPersonPageQry;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.dao.DataAccessException;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
@ -22,6 +24,90 @@ class PositionPersonLocalLookupService {
|
||||||
|
|
||||||
private final JdbcTemplate jdbcTemplate;
|
private final JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
|
boolean hasPersonnelMappings() {
|
||||||
|
try {
|
||||||
|
Integer count = jdbcTemplate.queryForObject(
|
||||||
|
"SELECT COUNT(1) FROM personnel_match "
|
||||||
|
+ "WHERE (delete_enum IS NULL OR delete_enum = 'FALSE')",
|
||||||
|
Integer.class);
|
||||||
|
return count != null && count > 0;
|
||||||
|
} catch (DataAccessException ignored) {
|
||||||
|
// Keep the pre-migration FindS fallback available during rolling deployment.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<PersonMapping> findMatchedPersonnel(PositionPersonPageQry query) {
|
||||||
|
StringBuilder sql = new StringBuilder(
|
||||||
|
"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, "
|
||||||
|
+ "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, "
|
||||||
|
+ "ci.corp_name AS corpinfo_name, ci.code AS credit_code, ci.port_area, "
|
||||||
|
+ "u.department_id, d.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 CAST(ci.corpinfo_id AS CHAR) = CAST(u.corpinfo_id AS CHAR) "
|
||||||
|
+ "LEFT JOIN department d ON d.id = u.department_id "
|
||||||
|
+ "AND (d.delete_enum IS NULL OR d.delete_enum = 'FALSE') "
|
||||||
|
+ "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')");
|
||||||
|
List<Object> args = new ArrayList<>();
|
||||||
|
if (query != null && query.getUserId() != null) {
|
||||||
|
sql.append(" AND u.id = ?");
|
||||||
|
args.add(query.getUserId());
|
||||||
|
}
|
||||||
|
if (query != null && StringUtils.hasText(query.getStaffName())) {
|
||||||
|
sql.append(" AND u.name LIKE ?");
|
||||||
|
args.add("%" + query.getStaffName().trim() + "%");
|
||||||
|
}
|
||||||
|
if (query != null && query.getCorpinfoId() != null) {
|
||||||
|
sql.append(" AND CAST(u.corpinfo_id AS CHAR) = ?");
|
||||||
|
args.add(String.valueOf(query.getCorpinfoId()));
|
||||||
|
}
|
||||||
|
if (query != null && StringUtils.hasText(query.getCorpinfoName())) {
|
||||||
|
sql.append(" AND ci.corp_name LIKE ?");
|
||||||
|
args.add("%" + query.getCorpinfoName().trim() + "%");
|
||||||
|
}
|
||||||
|
if (query != null && query.getDepartmentId() != null) {
|
||||||
|
sql.append(" AND u.department_id = ?");
|
||||||
|
args.add(query.getDepartmentId());
|
||||||
|
}
|
||||||
|
if (query != null && StringUtils.hasText(query.getDepartmentName())) {
|
||||||
|
sql.append(" AND d.name LIKE ?");
|
||||||
|
args.add("%" + query.getDepartmentName().trim() + "%");
|
||||||
|
}
|
||||||
|
sql.append(" ORDER BY u.id DESC, pm.finds_staff_no");
|
||||||
|
return jdbcTemplate.query(sql.toString(), args.toArray(), (rs, rowNum) -> {
|
||||||
|
LocalPerson local = new LocalPerson();
|
||||||
|
local.setUserId(nullableLong(rs.getLong("user_id"), rs.wasNull()));
|
||||||
|
local.setLocalStaffNo(rs.getString("local_staff_no"));
|
||||||
|
local.setStaffName(rs.getString("staff_name"));
|
||||||
|
local.setMobileNo(normalize(rs.getString("mobile_no")));
|
||||||
|
local.setIdCardNo(normalize(rs.getString("id_card_no")));
|
||||||
|
local.setCorpinfoId(rs.getString("corpinfo_id"));
|
||||||
|
local.setCorpinfoName(rs.getString("corpinfo_name"));
|
||||||
|
local.setCreditCode(rs.getString("credit_code"));
|
||||||
|
int portArea = rs.getInt("port_area");
|
||||||
|
local.setPortArea(rs.wasNull() ? null : portArea);
|
||||||
|
local.setDepartmentId(nullableLong(rs.getLong("department_id"), rs.wasNull()));
|
||||||
|
local.setDepartmentName(rs.getString("department_name"));
|
||||||
|
|
||||||
|
PersonMapping mapping = new PersonMapping();
|
||||||
|
mapping.setFindsStaffNo(normalize(rs.getString("finds_staff_no")));
|
||||||
|
mapping.setFindsStaffNoType(normalize(rs.getString("finds_staff_no_type")));
|
||||||
|
mapping.setFindsIdCardNo(normalize(rs.getString("finds_id_card_no")));
|
||||||
|
mapping.setFindsStaffName(rs.getString("finds_staff_name"));
|
||||||
|
mapping.setFindsCorpinfoId(rs.getString("finds_corpinfo_id"));
|
||||||
|
mapping.setFindsCorpinfoName(rs.getString("finds_corpinfo_name"));
|
||||||
|
mapping.setFindsDepartmentName(rs.getString("finds_department_name"));
|
||||||
|
mapping.setFindsTerminalNo(normalize(rs.getString("finds_terminal_no")));
|
||||||
|
mapping.setLocal(local);
|
||||||
|
return mapping;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, LocalPerson> findUniquePeopleByPhones(Set<String> phones) {
|
Map<String, LocalPerson> findUniquePeopleByPhones(Set<String> phones) {
|
||||||
Map<String, List<LocalPerson>> grouped = new LinkedHashMap<>();
|
Map<String, List<LocalPerson>> grouped = new LinkedHashMap<>();
|
||||||
for (List<String> batch : batches(phones)) {
|
for (List<String> batch : batches(phones)) {
|
||||||
|
|
@ -110,6 +196,34 @@ class PositionPersonLocalLookupService {
|
||||||
return unique;
|
return unique;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<String, String> findTerminalNosByIdCardNos(Set<String> idCardNos) {
|
||||||
|
Map<String, String> result = new LinkedHashMap<>();
|
||||||
|
for (List<String> batch : batches(idCardNos)) {
|
||||||
|
String placeholders = String.join(",", Collections.nCopies(batch.size(), "?"));
|
||||||
|
String sql = "SELECT COALESCE(NULLIF(TRIM(tb.id_card_no), ''), "
|
||||||
|
+ "NULLIF(TRIM(t.bind_id_card_no), '')) AS id_card_no, t.terminal_no "
|
||||||
|
+ "FROM terminal t LEFT JOIN terminal_bind tb ON tb.id = ("
|
||||||
|
+ "SELECT tb2.id FROM terminal_bind tb2 WHERE tb2.terminal_no = t.terminal_no "
|
||||||
|
+ "AND tb2.bind_status = 'BOUND' "
|
||||||
|
+ "AND (tb2.delete_enum IS NULL OR tb2.delete_enum = 'FALSE') "
|
||||||
|
+ "ORDER BY tb2.bind_time DESC, tb2.id DESC LIMIT 1) "
|
||||||
|
+ "WHERE (t.delete_enum IS NULL OR t.delete_enum = 'FALSE') "
|
||||||
|
+ "AND COALESCE(NULLIF(TRIM(tb.id_card_no), ''), "
|
||||||
|
+ "NULLIF(TRIM(t.bind_id_card_no), '')) IN (" + placeholders + ") "
|
||||||
|
+ "ORDER BY id_card_no, t.update_time DESC, t.id DESC";
|
||||||
|
jdbcTemplate.query(sql, batch.toArray(), (rs, rowNum) -> {
|
||||||
|
String idCardNo = normalize(rs.getString("id_card_no"));
|
||||||
|
String terminalNo = normalize(rs.getString("terminal_no"));
|
||||||
|
return new TerminalBinding(idCardNo, terminalNo);
|
||||||
|
}).forEach(binding -> {
|
||||||
|
if (StringUtils.hasText(binding.idCardNo) && StringUtils.hasText(binding.terminalNo)) {
|
||||||
|
result.putIfAbsent(binding.idCardNo, binding.terminalNo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, TerminalSnapshot> findTerminals(Set<String> terminalNos) {
|
Map<String, TerminalSnapshot> findTerminals(Set<String> terminalNos) {
|
||||||
Map<String, TerminalSnapshot> result = new LinkedHashMap<>();
|
Map<String, TerminalSnapshot> result = new LinkedHashMap<>();
|
||||||
for (List<String> batch : batches(terminalNos)) {
|
for (List<String> batch : batches(terminalNos)) {
|
||||||
|
|
@ -178,6 +292,19 @@ class PositionPersonLocalLookupService {
|
||||||
private String lastLocationName;
|
private String lastLocationName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
static class PersonMapping {
|
||||||
|
private String findsStaffNo;
|
||||||
|
private String findsStaffNoType;
|
||||||
|
private String findsIdCardNo;
|
||||||
|
private String findsStaffName;
|
||||||
|
private String findsCorpinfoId;
|
||||||
|
private String findsCorpinfoName;
|
||||||
|
private String findsDepartmentName;
|
||||||
|
private String findsTerminalNo;
|
||||||
|
private LocalPerson local;
|
||||||
|
}
|
||||||
|
|
||||||
private static class TerminalPerson {
|
private static class TerminalPerson {
|
||||||
private final String terminalNo;
|
private final String terminalNo;
|
||||||
private final LocalPerson person;
|
private final LocalPerson person;
|
||||||
|
|
@ -187,4 +314,14 @@ class PositionPersonLocalLookupService {
|
||||||
this.person = person;
|
this.person = person;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class TerminalBinding {
|
||||||
|
private final String idCardNo;
|
||||||
|
private final String terminalNo;
|
||||||
|
|
||||||
|
private TerminalBinding(String idCardNo, String terminalNo) {
|
||||||
|
this.idCardNo = idCardNo;
|
||||||
|
this.terminalNo = terminalNo;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,10 +30,10 @@ public class PositionPersonQueryExe {
|
||||||
static final String STATUS_OFFLINE = "OFFLINE";
|
static final String STATUS_OFFLINE = "OFFLINE";
|
||||||
|
|
||||||
private static final String STAFF_LIST_API = "finds.staff.list";
|
private static final String STAFF_LIST_API = "finds.staff.list";
|
||||||
private static final String STAFF_TERMINAL_QUERY_API = "finds.staff.taken.terminal.query";
|
|
||||||
private static final String POINT_LOCATE_API = "finds.point.locate";
|
private static final String POINT_LOCATE_API = "finds.point.locate";
|
||||||
private static final int FINDS_PAGE_SIZE = 200;
|
private static final int FINDS_PAGE_SIZE = 2000;
|
||||||
private static final int MAX_FINDS_PAGE_COUNT = 50;
|
private static final int MAX_FINDS_PAGE_COUNT = 50;
|
||||||
|
private static final int FINDS_BATCH_CONCURRENCY = 4;
|
||||||
private static final int LOCATE_BATCH_SIZE = 100;
|
private static final int LOCATE_BATCH_SIZE = 100;
|
||||||
|
|
||||||
private final FindsOpenApiClient findsOpenApiClient;
|
private final FindsOpenApiClient findsOpenApiClient;
|
||||||
|
|
@ -41,34 +41,26 @@ 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<FindsStaff> findsStaff = loadAllFindsStaff();
|
List<MatchedPerson> matched = loadMatchedPeople(query);
|
||||||
Set<String> phones = new LinkedHashSet<>();
|
Set<String> idCardNos = new LinkedHashSet<>();
|
||||||
for (FindsStaff staff : findsStaff) {
|
for (MatchedPerson person : matched) {
|
||||||
if (StringUtils.hasText(staff.staffNo)) {
|
if (StringUtils.hasText(person.local.getIdCardNo())) {
|
||||||
phones.add(staff.staffNo);
|
idCardNos.add(person.local.getIdCardNo());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Map<String, PositionPersonLocalLookupService.LocalPerson> localPeople =
|
|
||||||
localLookupService.findUniquePeopleByPhones(phones);
|
|
||||||
|
|
||||||
List<MatchedPerson> matched = new ArrayList<>();
|
Map<String, String> localTerminalNos = localLookupService.findTerminalNosByIdCardNos(idCardNos);
|
||||||
|
if (localTerminalNos == null) {
|
||||||
|
localTerminalNos = Collections.emptyMap();
|
||||||
|
}
|
||||||
Set<String> terminalNos = new LinkedHashSet<>();
|
Set<String> terminalNos = new LinkedHashSet<>();
|
||||||
Set<Long> matchedUserIds = new LinkedHashSet<>();
|
for (MatchedPerson person : matched) {
|
||||||
for (FindsStaff staff : findsStaff) {
|
if (!StringUtils.hasText(person.finds.terminalNo)) {
|
||||||
PositionPersonLocalLookupService.LocalPerson local = localPeople.get(staff.staffNo);
|
person.finds.terminalNo = localTerminalNos.get(normalize(person.local.getIdCardNo()));
|
||||||
if (local == null || !matchesLocalPerson(local, query)) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
if (local.getUserId() != null && !matchedUserIds.add(local.getUserId())) {
|
if (StringUtils.hasText(person.finds.terminalNo)) {
|
||||||
continue;
|
terminalNos.add(person.finds.terminalNo);
|
||||||
}
|
}
|
||||||
if (!StringUtils.hasText(staff.terminalNo)) {
|
|
||||||
staff.terminalNo = resolveTerminalNo(staff.staffNo);
|
|
||||||
}
|
|
||||||
if (StringUtils.hasText(staff.terminalNo)) {
|
|
||||||
terminalNos.add(staff.terminalNo);
|
|
||||||
}
|
|
||||||
matched.add(new MatchedPerson(staff, local));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, JsonNode> currentLocations = locateTerminals(terminalNos);
|
Map<String, JsonNode> currentLocations = locateTerminals(terminalNos);
|
||||||
|
|
@ -102,6 +94,54 @@ 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) {
|
||||||
|
if (localLookupService.hasPersonnelMappings()) {
|
||||||
|
return loadPersistedMatches(query);
|
||||||
|
}
|
||||||
|
List<FindsStaff> findsStaff = loadAllFindsStaff();
|
||||||
|
Set<String> phones = new LinkedHashSet<>();
|
||||||
|
for (FindsStaff staff : findsStaff) {
|
||||||
|
if (StringUtils.hasText(staff.staffNo)) {
|
||||||
|
phones.add(staff.staffNo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Map<String, PositionPersonLocalLookupService.LocalPerson> localPeople =
|
||||||
|
localLookupService.findUniquePeopleByPhones(phones);
|
||||||
|
|
||||||
|
List<MatchedPerson> matched = new ArrayList<>();
|
||||||
|
Set<Long> matchedUserIds = new LinkedHashSet<>();
|
||||||
|
for (FindsStaff staff : findsStaff) {
|
||||||
|
PositionPersonLocalLookupService.LocalPerson local = localPeople.get(staff.staffNo);
|
||||||
|
if (local == null || !matchesLocalPerson(local, query)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (local.getUserId() != null && !matchedUserIds.add(local.getUserId())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
matched.add(new MatchedPerson(staff, local));
|
||||||
|
}
|
||||||
|
return matched;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<MatchedPerson> loadPersistedMatches(PositionPersonPageQry query) {
|
||||||
|
List<MatchedPerson> result = new ArrayList<>();
|
||||||
|
Set<Long> matchedUserIds = new LinkedHashSet<>();
|
||||||
|
for (PositionPersonLocalLookupService.PersonMapping mapping
|
||||||
|
: localLookupService.findMatchedPersonnel(query)) {
|
||||||
|
PositionPersonLocalLookupService.LocalPerson local = mapping.getLocal();
|
||||||
|
if (local == null || (local.getUserId() != null && !matchedUserIds.add(local.getUserId()))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
FindsStaff staff = new FindsStaff();
|
||||||
|
staff.staffNo = mapping.getFindsStaffNo();
|
||||||
|
staff.sourceCompanyName = mapping.getFindsCorpinfoName();
|
||||||
|
staff.sourceDepartmentName = mapping.getFindsDepartmentName();
|
||||||
|
staff.terminalNo = mapping.getFindsTerminalNo();
|
||||||
|
result.add(new MatchedPerson(staff, local));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public PositionPersonCO queryByUserId(Long userId) {
|
public PositionPersonCO queryByUserId(Long userId) {
|
||||||
if (userId == null) {
|
if (userId == null) {
|
||||||
throw new BizException("本平台用户ID不能为空");
|
throw new BizException("本平台用户ID不能为空");
|
||||||
|
|
@ -119,17 +159,43 @@ public class PositionPersonQueryExe {
|
||||||
|
|
||||||
private List<FindsStaff> loadAllFindsStaff() {
|
private List<FindsStaff> loadAllFindsStaff() {
|
||||||
List<FindsStaff> result = new ArrayList<>();
|
List<FindsStaff> result = new ArrayList<>();
|
||||||
long total = Long.MAX_VALUE;
|
JsonNode firstRoot = findsOpenApiClient.postFailFast(STAFF_LIST_API, staffPageRequest(1));
|
||||||
int loaded = 0;
|
JsonNode firstData = assertSuccess(firstRoot, STAFF_LIST_API).path("data");
|
||||||
for (int pageNo = 1; pageNo <= MAX_FINDS_PAGE_COUNT && loaded < total; pageNo++) {
|
int firstPageSize = appendFindsStaff(result, extractRows(firstData));
|
||||||
|
if (firstPageSize == 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
long total = totalValue(firstData, firstPageSize);
|
||||||
|
int pageCount = (int) Math.min((total + FINDS_PAGE_SIZE - 1) / FINDS_PAGE_SIZE,
|
||||||
|
MAX_FINDS_PAGE_COUNT);
|
||||||
|
if (pageCount <= 1) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Map<String, Object>> requests = new ArrayList<>(pageCount - 1);
|
||||||
|
for (int pageNo = 2; pageNo <= pageCount; pageNo++) {
|
||||||
|
requests.add(staffPageRequest(pageNo));
|
||||||
|
}
|
||||||
|
List<JsonNode> roots = findsOpenApiClient.postFailFastBatch(
|
||||||
|
STAFF_LIST_API, requests, FINDS_BATCH_CONCURRENCY);
|
||||||
|
for (JsonNode root : roots) {
|
||||||
|
JsonNode data = assertSuccess(root, STAFF_LIST_API).path("data");
|
||||||
|
appendFindsStaff(result, extractRows(data));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> staffPageRequest(int pageNo) {
|
||||||
Map<String, Object> request = new LinkedHashMap<>();
|
Map<String, Object> request = new LinkedHashMap<>();
|
||||||
request.put("pageNo", pageNo);
|
request.put("pageNo", pageNo);
|
||||||
request.put("pageSize", FINDS_PAGE_SIZE);
|
request.put("pageSize", FINDS_PAGE_SIZE);
|
||||||
JsonNode root = findsOpenApiClient.post(STAFF_LIST_API, request);
|
return request;
|
||||||
JsonNode data = assertSuccess(root, STAFF_LIST_API).path("data");
|
}
|
||||||
JsonNode rows = extractRows(data);
|
|
||||||
|
private int appendFindsStaff(List<FindsStaff> result, JsonNode rows) {
|
||||||
if (rows == null || !rows.isArray() || rows.size() == 0) {
|
if (rows == null || !rows.isArray() || rows.size() == 0) {
|
||||||
break;
|
return 0;
|
||||||
}
|
}
|
||||||
for (JsonNode row : rows) {
|
for (JsonNode row : rows) {
|
||||||
FindsStaff staff = mapFindsStaff(row);
|
FindsStaff staff = mapFindsStaff(row);
|
||||||
|
|
@ -137,10 +203,7 @@ public class PositionPersonQueryExe {
|
||||||
result.add(staff);
|
result.add(staff);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
loaded += rows.size();
|
return rows.size();
|
||||||
total = totalValue(data, loaded);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private FindsStaff mapFindsStaff(JsonNode row) {
|
private FindsStaff mapFindsStaff(JsonNode row) {
|
||||||
|
|
@ -164,23 +227,6 @@ public class PositionPersonQueryExe {
|
||||||
return staff;
|
return staff;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String resolveTerminalNo(String staffNo) {
|
|
||||||
if (!StringUtils.hasText(staffNo)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Map<String, Object> request = Collections.singletonMap("staffNo", staffNo);
|
|
||||||
JsonNode root = findsOpenApiClient.post(STAFF_TERMINAL_QUERY_API, request);
|
|
||||||
JsonNode data = assertSuccess(root, STAFF_TERMINAL_QUERY_API).path("data");
|
|
||||||
JsonNode rows = extractRows(data);
|
|
||||||
if (rows == null || !rows.isArray() || rows.size() == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonNode first = rows.get(0);
|
|
||||||
return firstText(text(firstPresent(first, "terminalNo")),
|
|
||||||
text(firstPresent(first.path("bindTerminal"), "terminalNo")),
|
|
||||||
terminalNoFromList(first.path("takenTerminalList")));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, JsonNode> locateTerminals(Set<String> terminalNos) {
|
private Map<String, JsonNode> locateTerminals(Set<String> terminalNos) {
|
||||||
if (terminalNos == null || terminalNos.isEmpty()) {
|
if (terminalNos == null || terminalNos.isEmpty()) {
|
||||||
return Collections.emptyMap();
|
return Collections.emptyMap();
|
||||||
|
|
@ -202,7 +248,7 @@ public class PositionPersonQueryExe {
|
||||||
|
|
||||||
private void locateTerminalBatch(List<String> terminalNos, Map<String, JsonNode> result) {
|
private void locateTerminalBatch(List<String> terminalNos, Map<String, JsonNode> result) {
|
||||||
Map<String, Object> request = Collections.singletonMap("terminalNoList", new ArrayList<>(terminalNos));
|
Map<String, Object> request = Collections.singletonMap("terminalNoList", new ArrayList<>(terminalNos));
|
||||||
JsonNode root = findsOpenApiClient.post(POINT_LOCATE_API, request);
|
JsonNode root = findsOpenApiClient.postFailFast(POINT_LOCATE_API, request);
|
||||||
JsonNode data = assertSuccess(root, POINT_LOCATE_API).path("data");
|
JsonNode data = assertSuccess(root, POINT_LOCATE_API).path("data");
|
||||||
JsonNode rows = extractRows(data);
|
JsonNode rows = extractRows(data);
|
||||||
if (rows != null && rows.isArray()) {
|
if (rows != null && rows.isArray()) {
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,7 @@ import java.io.IOException;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
|
@ -23,48 +20,21 @@ public class RealtimePersonLocationMatcher {
|
||||||
private static final String[] ENVELOPE_FIELDS = {"data", "payload", "message", "body"};
|
private static final String[] ENVELOPE_FIELDS = {"data", "payload", "message", "body"};
|
||||||
|
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
private final PositionPersonLocalLookupService localLookupService;
|
|
||||||
|
|
||||||
public List<BiPersonLocationCO> match(byte[] payload) {
|
public List<BiPersonLocationCO> match(byte[] payload) {
|
||||||
JsonNode root = parse(payload);
|
JsonNode root = parse(payload);
|
||||||
List<LocationCandidate> candidates = new ArrayList<>();
|
List<LocationCandidate> candidates = new ArrayList<>();
|
||||||
Set<String> staffNos = new LinkedHashSet<>();
|
|
||||||
for (JsonNode row : extractRows(root)) {
|
for (JsonNode row : extractRows(root)) {
|
||||||
String staffNo = staffNo(row);
|
String staffNo = staffNo(row);
|
||||||
if (!StringUtils.hasText(staffNo)) {
|
if (!StringUtils.hasText(staffNo)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
candidates.add(new LocationCandidate(row, staffNo));
|
candidates.add(new LocationCandidate(row, staffNo, terminalNo(row)));
|
||||||
staffNos.add(staffNo);
|
|
||||||
}
|
}
|
||||||
if (staffNos.isEmpty()) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, PositionPersonLocalLookupService.LocalPerson> localPeople =
|
|
||||||
localLookupService.findUniquePeopleByPhones(staffNos);
|
|
||||||
if (localPeople.isEmpty()) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
Set<String> terminalNos = new LinkedHashSet<>();
|
|
||||||
for (LocationCandidate candidate : candidates) {
|
|
||||||
if (!localPeople.containsKey(candidate.staffNo)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
candidate.terminalNo = terminalNo(candidate.row);
|
|
||||||
if (StringUtils.hasText(candidate.terminalNo)) {
|
|
||||||
terminalNos.add(candidate.terminalNo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Map<String, PositionPersonLocalLookupService.TerminalSnapshot> terminals =
|
|
||||||
localLookupService.findTerminals(terminalNos);
|
|
||||||
|
|
||||||
List<BiPersonLocationCO> result = new ArrayList<>();
|
List<BiPersonLocationCO> result = new ArrayList<>();
|
||||||
for (LocationCandidate candidate : candidates) {
|
for (LocationCandidate candidate : candidates) {
|
||||||
PositionPersonLocalLookupService.LocalPerson local = localPeople.get(candidate.staffNo);
|
result.add(toClientObject(candidate));
|
||||||
if (local != null) {
|
|
||||||
result.add(toClientObject(candidate, local, terminals.get(candidate.terminalNo)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -167,29 +137,49 @@ public class RealtimePersonLocationMatcher {
|
||||||
directText(locationNode(row), "terminalNo"));
|
directText(locationNode(row), "terminalNo"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private BiPersonLocationCO toClientObject(LocationCandidate candidate,
|
private BiPersonLocationCO toClientObject(LocationCandidate candidate) {
|
||||||
PositionPersonLocalLookupService.LocalPerson local,
|
|
||||||
PositionPersonLocalLookupService.TerminalSnapshot terminal) {
|
|
||||||
JsonNode row = candidate.row;
|
JsonNode row = candidate.row;
|
||||||
JsonNode location = locationNode(row);
|
JsonNode location = locationNode(row);
|
||||||
|
JsonNode staff = firstObject(row, "staff", "person", "personInfo", "staffInfo");
|
||||||
|
String orgName = firstText(
|
||||||
|
directText(row, "orgName", "organizationName", "companyName", "corpName", "enterpriseName"),
|
||||||
|
directText(staff, "orgName", "organizationName", "companyName", "corpName", "enterpriseName"));
|
||||||
|
String orgCode = firstText(
|
||||||
|
directText(row, "orgCode", "companyCode", "corpCode", "enterpriseCode"),
|
||||||
|
directText(staff, "orgCode", "companyCode", "corpCode", "enterpriseCode"));
|
||||||
BiPersonLocationCO co = new BiPersonLocationCO();
|
BiPersonLocationCO co = new BiPersonLocationCO();
|
||||||
co.setUserId(local.getUserId());
|
co.setStaffName(firstText(
|
||||||
co.setStaffName(local.getStaffName());
|
directText(row, "staffName", "name", "realName", "userName"),
|
||||||
co.setLocalStaffNo(local.getLocalStaffNo());
|
directText(staff, "staffName", "name", "realName", "userName")));
|
||||||
co.setStaffNo(candidate.staffNo);
|
co.setStaffNo(candidate.staffNo);
|
||||||
co.setIdCardNo(local.getIdCardNo());
|
co.setIdCardNo(firstText(
|
||||||
co.setMobileNo(local.getMobileNo());
|
directText(row, "idCardNo", "identityNo", "certificateNo", "cardNo"),
|
||||||
co.setDepartmentId(local.getDepartmentId());
|
directText(staff, "idCardNo", "identityNo", "certificateNo", "cardNo")));
|
||||||
co.setDepartmentName(local.getDepartmentName());
|
co.setMobileNo(firstText(
|
||||||
co.setCompanyName(local.getCorpinfoName());
|
directText(row, "mobileNo", "phone", "phoneNo", "tel"),
|
||||||
co.setCorpinfoId(local.getCorpinfoId());
|
directText(staff, "mobileNo", "phone", "phoneNo", "tel")));
|
||||||
co.setCorpinfoName(local.getCorpinfoName());
|
co.setDepartmentName(firstText(
|
||||||
co.setCreditCode(local.getCreditCode());
|
directText(row, "departmentName", "deptName"),
|
||||||
co.setPortArea(local.getPortArea());
|
directText(child(row, "department"), "name", "departmentName"),
|
||||||
co.setOrgName(directText(row, "orgName", "companyName", "corpName", "enterpriseName"));
|
directText(child(staff, "department"), "name", "departmentName")));
|
||||||
co.setOrgCode(directText(row, "orgCode", "companyCode", "corpCode", "enterpriseCode"));
|
co.setCompanyName(firstText(
|
||||||
|
directText(row, "companyName", "corpName", "enterpriseName"),
|
||||||
|
directText(staff, "companyName", "corpName", "enterpriseName"),
|
||||||
|
orgName));
|
||||||
|
co.setCorpinfoId(firstText(
|
||||||
|
directText(row, "corpinfoId", "companyId", "corpId", "enterpriseId"),
|
||||||
|
directText(staff, "corpinfoId", "companyId", "corpId", "enterpriseId")));
|
||||||
|
co.setCorpinfoName(firstText(co.getCompanyName(), orgName));
|
||||||
|
co.setOrgName(orgName);
|
||||||
|
co.setOrgCode(orgCode);
|
||||||
|
co.setCreditCode(firstText(
|
||||||
|
directText(row, "creditCode", "socialCreditCode", "unifiedSocialCreditCode",
|
||||||
|
"unifiedCreditCode", "creditNo"),
|
||||||
|
directText(staff, "creditCode", "socialCreditCode", "unifiedSocialCreditCode",
|
||||||
|
"unifiedCreditCode", "creditNo")));
|
||||||
|
co.setPortArea(integerValue(firstValue(row, staff, "portArea", "portAreaCode")));
|
||||||
co.setTerminalNo(candidate.terminalNo);
|
co.setTerminalNo(candidate.terminalNo);
|
||||||
co.setPositionMode(positionMode(terminal));
|
co.setPositionMode(positionMode(row));
|
||||||
co.setLon(decimalValue(firstValue(location, row, "lon", "lng", "longitude")));
|
co.setLon(decimalValue(firstValue(location, row, "lon", "lng", "longitude")));
|
||||||
co.setLat(decimalValue(firstValue(location, row, "lat", "latitude")));
|
co.setLat(decimalValue(firstValue(location, row, "lat", "latitude")));
|
||||||
co.setAlt(decimalValue(firstValue(location, row, "alt", "altitude")));
|
co.setAlt(decimalValue(firstValue(location, row, "alt", "altitude")));
|
||||||
|
|
@ -200,8 +190,12 @@ public class RealtimePersonLocationMatcher {
|
||||||
co.setCurrentLocation(firstText(
|
co.setCurrentLocation(firstText(
|
||||||
directText(location, "fenceName", "areaName", "positionName", "location", "address"),
|
directText(location, "fenceName", "areaName", "positionName", "location", "address"),
|
||||||
directText(row, "fenceName", "areaName", "positionName", "locationName", "address")));
|
directText(row, "fenceName", "areaName", "positionName", "locationName", "address")));
|
||||||
co.setOnline(co.getLon() != null && co.getLat() != null);
|
Boolean online = booleanValue(firstValue(location, row, "online", "isOnline"));
|
||||||
co.setLocationRawJson(row.toString());
|
co.setOnline(online == null ? co.getLon() != null && co.getLat() != null : online);
|
||||||
|
co.setAlarmStatus(booleanValue(firstValue(location, row, "alarmStatus", "isAlarm")));
|
||||||
|
co.setAlarmCount(integerValue(firstValue(location, row, "alarmCount", "unclosedAlarmCount")));
|
||||||
|
co.setStaffRawJson(row.toString());
|
||||||
|
co.setLocationRawJson(location.toString());
|
||||||
return co;
|
return co;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -272,8 +266,11 @@ public class RealtimePersonLocationMatcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String positionMode(PositionPersonLocalLookupService.TerminalSnapshot terminal) {
|
private String positionMode(JsonNode row) {
|
||||||
String deviceType = terminal == null ? null : terminal.getDeviceType();
|
JsonNode terminal = firstObject(row, "terminalInfo", "terminal", "bindTerminal");
|
||||||
|
String deviceType = firstText(
|
||||||
|
directText(row, "positionMode", "deviceType", "terminalType"),
|
||||||
|
directText(terminal, "positionMode", "deviceType", "terminalType", "type"));
|
||||||
if ("CARD".equalsIgnoreCase(deviceType)) {
|
if ("CARD".equalsIgnoreCase(deviceType)) {
|
||||||
return "\u5b9a\u4f4d\u5361";
|
return "\u5b9a\u4f4d\u5361";
|
||||||
}
|
}
|
||||||
|
|
@ -286,6 +283,37 @@ public class RealtimePersonLocationMatcher {
|
||||||
return StringUtils.hasText(deviceType) ? deviceType : "\u5b9a\u4f4d\u7ec8\u7aef";
|
return StringUtils.hasText(deviceType) ? deviceType : "\u5b9a\u4f4d\u7ec8\u7aef";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Integer integerValue(JsonNode node) {
|
||||||
|
if (node == null || node.isNull()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return node.isNumber() ? node.asInt() : Integer.valueOf(node.asText());
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Boolean booleanValue(JsonNode node) {
|
||||||
|
if (node == null || node.isNull()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (node.isBoolean()) {
|
||||||
|
return node.asBoolean();
|
||||||
|
}
|
||||||
|
String value = node.asText();
|
||||||
|
if (!StringUtils.hasText(value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if ("1".equals(value) || "true".equalsIgnoreCase(value) || "yes".equalsIgnoreCase(value)) {
|
||||||
|
return Boolean.TRUE;
|
||||||
|
}
|
||||||
|
if ("0".equals(value) || "false".equalsIgnoreCase(value) || "no".equalsIgnoreCase(value)) {
|
||||||
|
return Boolean.FALSE;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private JsonNode child(JsonNode node, String fieldName) {
|
private JsonNode child(JsonNode node, String fieldName) {
|
||||||
return node == null || !node.isObject() ? null : node.get(fieldName);
|
return node == null || !node.isObject() ? null : node.get(fieldName);
|
||||||
}
|
}
|
||||||
|
|
@ -302,11 +330,12 @@ public class RealtimePersonLocationMatcher {
|
||||||
private static class LocationCandidate {
|
private static class LocationCandidate {
|
||||||
private final JsonNode row;
|
private final JsonNode row;
|
||||||
private final String staffNo;
|
private final String staffNo;
|
||||||
private String terminalNo;
|
private final String terminalNo;
|
||||||
|
|
||||||
private LocationCandidate(JsonNode row, String staffNo) {
|
private LocationCandidate(JsonNode row, String staffNo, String terminalNo) {
|
||||||
this.row = row;
|
this.row = row;
|
||||||
this.staffNo = staffNo;
|
this.staffNo = staffNo;
|
||||||
|
this.terminalNo = terminalNo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,10 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.jjb.saas.framework.auth.model.SSOUser;
|
import com.jjb.saas.framework.auth.model.SSOUser;
|
||||||
import com.jjb.saas.framework.auth.utils.AuthContext;
|
import com.jjb.saas.framework.auth.utils.AuthContext;
|
||||||
import com.zcloud.personnel.positioning.persistence.dataobject.OpenapiConfigDO;
|
import com.zcloud.personnel.positioning.persistence.dataobject.OpenapiConfigDO;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
|
@ -20,8 +23,14 @@ import java.net.HttpURLConnection;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FindS OpenAPI signed client.
|
* FindS OpenAPI signed client.
|
||||||
|
|
@ -30,12 +39,25 @@ import java.util.Map;
|
||||||
* @Date 2026-07-07
|
* @Date 2026-07-07
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
@AllArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class FindsOpenApiClient {
|
public class FindsOpenApiClient {
|
||||||
|
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 CONNECT_TIMEOUT_MS = 10000;
|
|
||||||
private static final int READ_TIMEOUT_MS = 60000;
|
@Value("${finds.openapi.connect-timeout-ms:10000}")
|
||||||
private static final int MAX_REQUEST_ATTEMPTS = 3;
|
private int connectTimeoutMs = 10000;
|
||||||
|
|
||||||
|
@Value("${finds.openapi.read-timeout-ms:60000}")
|
||||||
|
private int readTimeoutMs = 60000;
|
||||||
|
|
||||||
|
@Value("${finds.openapi.max-request-attempts:3}")
|
||||||
|
private int maxRequestAttempts = 3;
|
||||||
|
|
||||||
|
@Value("${finds.openapi.fail-fast-connect-timeout-ms:5000}")
|
||||||
|
private int failFastConnectTimeoutMs = 5000;
|
||||||
|
|
||||||
|
@Value("${finds.openapi.fail-fast-read-timeout-ms:15000}")
|
||||||
|
private int failFastReadTimeoutMs = 15000;
|
||||||
|
|
||||||
private final OpenapiConfigCache openapiConfigCache;
|
private final OpenapiConfigCache openapiConfigCache;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
|
|
@ -45,7 +67,60 @@ public class FindsOpenApiClient {
|
||||||
return post(config, apiName, request);
|
return post(config, apiName, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public JsonNode postFailFast(String apiName, Map<String, Object> request) {
|
||||||
|
OpenapiConfigDO config = loadEnabledConfig();
|
||||||
|
return post(config, apiName, request,
|
||||||
|
failFastConnectTimeoutMs, failFastReadTimeoutMs, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<JsonNode> postFailFastBatch(String apiName, List<Map<String, Object>> requests,
|
||||||
|
int maxConcurrency) {
|
||||||
|
if (requests == null || requests.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
OpenapiConfigDO config = loadEnabledConfig();
|
||||||
|
int concurrency = Math.max(1, Math.min(Math.min(maxConcurrency, 8), requests.size()));
|
||||||
|
if (concurrency == 1) {
|
||||||
|
List<JsonNode> result = new ArrayList<>(requests.size());
|
||||||
|
for (Map<String, Object> request : requests) {
|
||||||
|
result.add(post(config, apiName, request,
|
||||||
|
failFastConnectTimeoutMs, failFastReadTimeoutMs, 1));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExecutorService executor = Executors.newFixedThreadPool(concurrency);
|
||||||
|
try {
|
||||||
|
List<Future<JsonNode>> futures = new ArrayList<>(requests.size());
|
||||||
|
for (Map<String, Object> request : requests) {
|
||||||
|
futures.add(executor.submit(() -> post(config, apiName, request,
|
||||||
|
failFastConnectTimeoutMs, failFastReadTimeoutMs, 1)));
|
||||||
|
}
|
||||||
|
List<JsonNode> result = new ArrayList<>(requests.size());
|
||||||
|
for (Future<JsonNode> future : futures) {
|
||||||
|
result.add(future.get());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new BizException("FindS OpenAPI批量请求被中断");
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
Throwable cause = e.getCause();
|
||||||
|
if (cause instanceof RuntimeException) {
|
||||||
|
throw (RuntimeException) cause;
|
||||||
|
}
|
||||||
|
throw new BizException("FindS OpenAPI批量请求失败:" + cause.getMessage());
|
||||||
|
} finally {
|
||||||
|
executor.shutdownNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public JsonNode post(OpenapiConfigDO config, String apiName, Map<String, Object> request) {
|
public JsonNode post(OpenapiConfigDO config, String apiName, Map<String, Object> request) {
|
||||||
|
return post(config, apiName, request, connectTimeoutMs, readTimeoutMs, maxRequestAttempts);
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonNode post(OpenapiConfigDO config, String apiName, Map<String, Object> request,
|
||||||
|
int connectTimeout, int readTimeout, int requestAttempts) {
|
||||||
validateConfig(config);
|
validateConfig(config);
|
||||||
String requestJson = writeJson(request == null ? Collections.emptyMap() : request);
|
String requestJson = writeJson(request == null ? Collections.emptyMap() : request);
|
||||||
String timestamp = String.valueOf(System.currentTimeMillis());
|
String timestamp = String.valueOf(System.currentTimeMillis());
|
||||||
|
|
@ -55,7 +130,8 @@ public class FindsOpenApiClient {
|
||||||
String requestPath = buildApiPath(internalPrefix, apiName, config.getSik());
|
String requestPath = buildApiPath(internalPrefix, apiName, config.getSik());
|
||||||
String signature = sign(signPath, requestJson, config.getSisCipherText(), timestamp);
|
String signature = sign(signPath, requestJson, config.getSisCipherText(), timestamp);
|
||||||
String url = trimTrailingSlash(config.getHostUrl()) + requestPath + "?_sign=" + signature;
|
String url = trimTrailingSlash(config.getHostUrl()) + requestPath + "?_sign=" + signature;
|
||||||
String response = doPost(url, requestJson, timestamp);
|
String response = doPost(url, requestJson, timestamp, apiName,
|
||||||
|
connectTimeout, readTimeout, requestAttempts);
|
||||||
try {
|
try {
|
||||||
return objectMapper.readTree(response);
|
return objectMapper.readTree(response);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|
@ -105,16 +181,22 @@ public class FindsOpenApiClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String doPost(String url, String requestJson, String timestamp) {
|
private String doPost(String url, String requestJson, String timestamp, String apiName,
|
||||||
|
int configuredConnectTimeout, int configuredReadTimeout,
|
||||||
|
int configuredRequestAttempts) {
|
||||||
IOException lastException = null;
|
IOException lastException = null;
|
||||||
for (int attempt = 1; attempt <= MAX_REQUEST_ATTEMPTS; attempt++) {
|
int attempts = Math.max(configuredRequestAttempts, 1);
|
||||||
|
int connectTimeout = Math.max(configuredConnectTimeout, 1);
|
||||||
|
int readTimeout = Math.max(configuredReadTimeout, 1);
|
||||||
|
for (int attempt = 1; attempt <= attempts; attempt++) {
|
||||||
HttpURLConnection connection = null;
|
HttpURLConnection connection = null;
|
||||||
|
long startedAt = System.nanoTime();
|
||||||
try {
|
try {
|
||||||
byte[] body = ("request=" + URLEncoder.encode(requestJson, "UTF-8"))
|
byte[] body = ("request=" + URLEncoder.encode(requestJson, "UTF-8"))
|
||||||
.getBytes(StandardCharsets.UTF_8);
|
.getBytes(StandardCharsets.UTF_8);
|
||||||
connection = (HttpURLConnection) new URL(url).openConnection();
|
connection = (HttpURLConnection) new URL(url).openConnection();
|
||||||
connection.setConnectTimeout(CONNECT_TIMEOUT_MS);
|
connection.setConnectTimeout(connectTimeout);
|
||||||
connection.setReadTimeout(READ_TIMEOUT_MS);
|
connection.setReadTimeout(readTimeout);
|
||||||
connection.setRequestMethod("POST");
|
connection.setRequestMethod("POST");
|
||||||
connection.setDoOutput(true);
|
connection.setDoOutput(true);
|
||||||
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
|
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
|
||||||
|
|
@ -129,10 +211,14 @@ public class FindsOpenApiClient {
|
||||||
if (status != HttpURLConnection.HTTP_OK) {
|
if (status != HttpURLConnection.HTTP_OK) {
|
||||||
throw new BizException("FindS OpenAPI HTTP调用失败,status=" + status + ",body=" + response);
|
throw new BizException("FindS OpenAPI HTTP调用失败,status=" + status + ",body=" + response);
|
||||||
}
|
}
|
||||||
|
LOGGER.debug("FindS OpenAPI request succeeded, api={}, attempt={}, elapsedMs={}",
|
||||||
|
apiName, attempt, elapsedMillis(startedAt));
|
||||||
return response;
|
return response;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
lastException = e;
|
lastException = e;
|
||||||
if (attempt < MAX_REQUEST_ATTEMPTS) {
|
LOGGER.warn("FindS OpenAPI request failed, api={}, attempt={}/{}, elapsedMs={}, message={}",
|
||||||
|
apiName, attempt, attempts, elapsedMillis(startedAt), e.getMessage());
|
||||||
|
if (attempt < attempts) {
|
||||||
sleepBeforeRetry(attempt);
|
sleepBeforeRetry(attempt);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -145,6 +231,10 @@ public class FindsOpenApiClient {
|
||||||
? "unknown error" : lastException.getMessage()));
|
? "unknown error" : lastException.getMessage()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private long elapsedMillis(long startedAt) {
|
||||||
|
return (System.nanoTime() - startedAt) / 1_000_000L;
|
||||||
|
}
|
||||||
|
|
||||||
private void sleepBeforeRetry(int attempt) {
|
private void sleepBeforeRetry(int attempt) {
|
||||||
try {
|
try {
|
||||||
Thread.sleep(500L * attempt);
|
Thread.sleep(500L * attempt);
|
||||||
|
|
|
||||||
|
|
@ -72,18 +72,18 @@ public class LocationKafkaProbeListener {
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
List<BiPersonLocationCO> matchedLocations = locationMatcher.match(record.value());
|
List<BiPersonLocationCO> locations = locationMatcher.match(record.value());
|
||||||
int deliveries = 0;
|
int deliveries = 0;
|
||||||
for (BiPersonLocationCO location : matchedLocations) {
|
for (BiPersonLocationCO location : locations) {
|
||||||
String payload = serialize(location);
|
String payload = serialize(location);
|
||||||
for (RealtimeLocationPublisher publisher : publishers) {
|
for (RealtimeLocationPublisher publisher : publishers) {
|
||||||
deliveries += publisher.publish(payload);
|
deliveries += publisher.publish(payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
acknowledgment.acknowledge();
|
acknowledgment.acknowledge();
|
||||||
LOGGER.info("Kafka location message processed, topic={}, partition={}, offset={}, matched={}, "
|
LOGGER.info("Kafka location message processed, topic={}, partition={}, offset={}, converted={}, "
|
||||||
+ "websocketDeliveries={}",
|
+ "websocketDeliveries={}",
|
||||||
record.topic(), record.partition(), record.offset(), matchedLocations.size(), deliveries);
|
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={}",
|
||||||
|
|
@ -105,7 +105,7 @@ public class LocationKafkaProbeListener {
|
||||||
payload.remove(nullFields);
|
payload.remove(nullFields);
|
||||||
return objectMapper.writeValueAsString(payload);
|
return objectMapper.writeValueAsString(payload);
|
||||||
} catch (JsonProcessingException e) {
|
} catch (JsonProcessingException e) {
|
||||||
throw new IllegalStateException("Failed to serialize matched personnel location", e);
|
throw new IllegalStateException("Failed to serialize FindS personnel location", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -170,6 +170,51 @@ CREATE TABLE IF NOT EXISTS `terminal_bind` (
|
||||||
KEY `idx_tenant_org` (`tenant_id`, `org_id`)
|
KEY `idx_tenant_org` (`tenant_id`, `org_id`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='人员定位终端绑定记录';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='人员定位终端绑定记录';
|
||||||
|
|
||||||
|
-- FindS staff to local platform user mapping. This is a mapping cache, not a personnel master table.
|
||||||
|
CREATE TABLE IF NOT EXISTS `personnel_match` (
|
||||||
|
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'primary key',
|
||||||
|
`source_system` varchar(32) NOT NULL DEFAULT 'FINDS' COMMENT 'external source system',
|
||||||
|
`source_config_code` varchar(64) NOT NULL DEFAULT 'FINDS' COMMENT 'FindS config scope',
|
||||||
|
`finds_staff_no` varchar(64) NOT NULL COMMENT 'FindS business staff/track number',
|
||||||
|
`finds_staff_no_type` varchar(32) NOT NULL DEFAULT 'TRACK_NO' COMMENT 'TRACK_NO/WORK_NO/STAFF_NO',
|
||||||
|
`finds_track_id` bigint DEFAULT NULL COMMENT 'FindS track object id',
|
||||||
|
`finds_id_card_no` varchar(64) DEFAULT NULL COMMENT 'FindS id card number',
|
||||||
|
`finds_staff_name` varchar(128) DEFAULT NULL COMMENT 'FindS staff name snapshot',
|
||||||
|
`finds_corpinfo_id` varchar(64) DEFAULT NULL COMMENT 'FindS company id snapshot',
|
||||||
|
`finds_corpinfo_name` varchar(255) DEFAULT NULL COMMENT 'FindS company name snapshot',
|
||||||
|
`finds_department_name` varchar(255) DEFAULT NULL COMMENT 'FindS department name snapshot',
|
||||||
|
`finds_terminal_no` varchar(64) DEFAULT NULL COMMENT 'FindS terminal number snapshot',
|
||||||
|
`local_user_id` bigint DEFAULT NULL COMMENT 'matched local platform user id',
|
||||||
|
`local_mobile_no` varchar(64) DEFAULT NULL COMMENT 'local platform mobile snapshot used for matching',
|
||||||
|
`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_type` varchar(32) DEFAULT NULL COMMENT 'MOBILE/ID_CARD/MOBILE_AND_ID_CARD',
|
||||||
|
`last_seen_time` datetime DEFAULT NULL COMMENT 'last time observed from FindS',
|
||||||
|
`last_sync_time` datetime DEFAULT NULL COMMENT 'last mapping synchronization time',
|
||||||
|
`sync_status` varchar(32) NOT NULL DEFAULT 'SUCCESS' COMMENT 'SUCCESS/FAIL',
|
||||||
|
`sync_error` varchar(1000) DEFAULT NULL COMMENT 'last synchronization error',
|
||||||
|
`delete_enum` varchar(32) NOT NULL DEFAULT 'FALSE' COMMENT 'soft delete flag',
|
||||||
|
`remarks` varchar(500) DEFAULT NULL COMMENT 'remarks',
|
||||||
|
`tenant_id` bigint DEFAULT NULL COMMENT 'tenant id',
|
||||||
|
`org_id` bigint DEFAULT NULL COMMENT 'organization id',
|
||||||
|
`env` varchar(32) DEFAULT NULL COMMENT 'environment',
|
||||||
|
`version` int NOT NULL DEFAULT 0 COMMENT 'version',
|
||||||
|
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT 'created time',
|
||||||
|
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'updated time',
|
||||||
|
`create_id` bigint DEFAULT NULL COMMENT 'creator id',
|
||||||
|
`update_id` bigint DEFAULT NULL COMMENT 'updater id',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_personnel_match_source_staff_delete` (`source_config_code`, `finds_staff_no`, `delete_enum`),
|
||||||
|
KEY `idx_personnel_match_local_user` (`local_user_id`, `match_status`),
|
||||||
|
KEY `idx_personnel_match_local_mobile` (`local_mobile_no`),
|
||||||
|
KEY `idx_personnel_match_find_id_card` (`finds_id_card_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_scope_status` (`finds_corpinfo_id`, `match_status`),
|
||||||
|
KEY `idx_personnel_match_last_seen` (`last_seen_time`),
|
||||||
|
KEY `idx_personnel_match_tenant_org` (`tenant_id`, `org_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='FindS to local personnel mapping';
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `fence` (
|
CREATE TABLE IF NOT EXISTS `fence` (
|
||||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
|
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||||
`fence_code` varchar(64) NOT NULL COMMENT '围栏编码',
|
`fence_code` varchar(64) NOT NULL COMMENT '围栏编码',
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
-- Cache the cross-system personnel mapping used by the local-first personnel list.
|
||||||
|
-- The table stores identity/mapping snapshots only. Realtime coordinates remain in FindS/Redis.
|
||||||
|
CREATE TABLE IF NOT EXISTS `personnel_match` (
|
||||||
|
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'primary key',
|
||||||
|
`source_system` varchar(32) NOT NULL DEFAULT 'FINDS' COMMENT 'external source system',
|
||||||
|
`source_config_code` varchar(64) NOT NULL DEFAULT 'FINDS' COMMENT 'FindS config scope',
|
||||||
|
`finds_staff_no` varchar(64) NOT NULL COMMENT 'FindS business staff/track number',
|
||||||
|
`finds_staff_no_type` varchar(32) NOT NULL DEFAULT 'TRACK_NO' COMMENT 'TRACK_NO/WORK_NO/STAFF_NO',
|
||||||
|
`finds_track_id` bigint DEFAULT NULL COMMENT 'FindS track object id',
|
||||||
|
`finds_id_card_no` varchar(64) DEFAULT NULL COMMENT 'FindS id card number',
|
||||||
|
`finds_staff_name` varchar(128) DEFAULT NULL COMMENT 'FindS staff name snapshot',
|
||||||
|
`finds_corpinfo_id` varchar(64) DEFAULT NULL COMMENT 'FindS company id snapshot',
|
||||||
|
`finds_corpinfo_name` varchar(255) DEFAULT NULL COMMENT 'FindS company name snapshot',
|
||||||
|
`finds_department_name` varchar(255) DEFAULT NULL COMMENT 'FindS department name snapshot',
|
||||||
|
`finds_terminal_no` varchar(64) DEFAULT NULL COMMENT 'FindS terminal number snapshot',
|
||||||
|
`local_user_id` bigint DEFAULT NULL COMMENT 'matched local platform user id',
|
||||||
|
`local_mobile_no` varchar(64) DEFAULT NULL COMMENT 'local platform mobile snapshot used for matching',
|
||||||
|
`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_type` varchar(32) DEFAULT NULL COMMENT 'MOBILE/ID_CARD/MOBILE_AND_ID_CARD',
|
||||||
|
`last_seen_time` datetime DEFAULT NULL COMMENT 'last time observed from FindS',
|
||||||
|
`last_sync_time` datetime DEFAULT NULL COMMENT 'last mapping synchronization time',
|
||||||
|
`sync_status` varchar(32) NOT NULL DEFAULT 'SUCCESS' COMMENT 'SUCCESS/FAIL',
|
||||||
|
`sync_error` varchar(1000) DEFAULT NULL COMMENT 'last synchronization error',
|
||||||
|
`delete_enum` varchar(32) NOT NULL DEFAULT 'FALSE' COMMENT 'soft delete flag',
|
||||||
|
`remarks` varchar(500) DEFAULT NULL COMMENT 'remarks',
|
||||||
|
`tenant_id` bigint DEFAULT NULL COMMENT 'tenant id',
|
||||||
|
`org_id` bigint DEFAULT NULL COMMENT 'organization id',
|
||||||
|
`env` varchar(32) DEFAULT NULL COMMENT 'environment',
|
||||||
|
`version` int NOT NULL DEFAULT 0 COMMENT 'version',
|
||||||
|
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT 'created time',
|
||||||
|
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'updated time',
|
||||||
|
`create_id` bigint DEFAULT NULL COMMENT 'creator id',
|
||||||
|
`update_id` bigint DEFAULT NULL COMMENT 'updater id',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_personnel_match_source_staff_delete` (`source_config_code`, `finds_staff_no`, `delete_enum`),
|
||||||
|
KEY `idx_personnel_match_local_user` (`local_user_id`, `match_status`),
|
||||||
|
KEY `idx_personnel_match_local_mobile` (`local_mobile_no`),
|
||||||
|
KEY `idx_personnel_match_find_id_card` (`finds_id_card_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_scope_status` (`finds_corpinfo_id`, `match_status`),
|
||||||
|
KEY `idx_personnel_match_last_seen` (`last_seen_time`),
|
||||||
|
KEY `idx_personnel_match_tenant_org` (`tenant_id`, `org_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='FindS to local personnel mapping';
|
||||||
Loading…
Reference in New Issue