feat(): 通过人员定位系统统计封闭区域人员车辆数据
parent
5483a9efa5
commit
4b7358ab1d
|
|
@ -127,6 +127,12 @@ public class ClosedAreaController {
|
|||
return closedAreaService.count(closedAreaCountCmd);
|
||||
}
|
||||
|
||||
@ApiOperation("区域人员和车辆定位统计")
|
||||
@PostMapping("/positionCount")
|
||||
public MultiResponse positionCount(@RequestBody ClosedAreaCountCmd closedAreaCountCmd) {
|
||||
return closedAreaService.positionCount(closedAreaCountCmd);
|
||||
}
|
||||
|
||||
@ApiOperation("可视化大屏-封闭区域统计")
|
||||
@PostMapping("/corpStat")
|
||||
public SingleResponse<ClosedAreaCorpStatCO> corpStat( @RequestBody ClosedAreaCorpStatCmd cmd) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,105 @@
|
|||
package com.zcloud.primeport.command.query;
|
||||
|
||||
import com.alibaba.cola.exception.BizException;
|
||||
import com.zcloud.primeport.persistence.dataobject.ClosedAreaCountDO;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
class ClosedAreaMatcher {
|
||||
List<AreaPolygon> prepare(List<ClosedAreaCountDO> areas) {
|
||||
if (areas == null || areas.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<AreaPolygon> result = new ArrayList<>(areas.size());
|
||||
for (ClosedAreaCountDO area : areas) {
|
||||
if (StringUtils.hasText(area.getLocation())) {
|
||||
result.add(new AreaPolygon(area, parseLocation(area)));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
AreaPolygon match(BigDecimal lon, BigDecimal lat, List<AreaPolygon> areas) {
|
||||
if (lon == null || lat == null || areas == null) {
|
||||
return null;
|
||||
}
|
||||
for (AreaPolygon area : areas) {
|
||||
if (contains(lon, lat, area)) {
|
||||
return area;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean contains(BigDecimal lon, BigDecimal lat, AreaPolygon area) {
|
||||
if (area == null || area.points.size() < 3) {
|
||||
return false;
|
||||
}
|
||||
boolean inside = false;
|
||||
double x = lon.doubleValue();
|
||||
double y = lat.doubleValue();
|
||||
for (int i = 0, j = area.points.size() - 1; i < area.points.size(); j = i++) {
|
||||
double xi = area.points.get(i)[0].doubleValue();
|
||||
double yi = area.points.get(i)[1].doubleValue();
|
||||
double xj = area.points.get(j)[0].doubleValue();
|
||||
double yj = area.points.get(j)[1].doubleValue();
|
||||
if ((yi > y) != (yj > y)
|
||||
&& x < (xj - xi) * (y - yi) / (yj - yi) + xi) {
|
||||
inside = !inside;
|
||||
}
|
||||
}
|
||||
return inside;
|
||||
}
|
||||
|
||||
private List<BigDecimal[]> parseLocation(ClosedAreaCountDO area) {
|
||||
List<BigDecimal[]> points = new ArrayList<>();
|
||||
if (area == null || !StringUtils.hasText(area.getLocation())) {
|
||||
throw locationFormatException(area);
|
||||
}
|
||||
for (String coordinate : area.getLocation().split(";")) {
|
||||
if (!StringUtils.hasText(coordinate)) {
|
||||
continue;
|
||||
}
|
||||
String[] values = coordinate.trim().split(",");
|
||||
if (values.length != 2) {
|
||||
throw locationFormatException(area);
|
||||
}
|
||||
try {
|
||||
points.add(new BigDecimal[]{
|
||||
new BigDecimal(values[0].trim()),
|
||||
new BigDecimal(values[1].trim())
|
||||
});
|
||||
} catch (NumberFormatException e) {
|
||||
throw locationFormatException(area);
|
||||
}
|
||||
}
|
||||
if (points.size() < 3) {
|
||||
throw locationFormatException(area);
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
private BizException locationFormatException(ClosedAreaCountDO area) {
|
||||
return new BizException("closed area location format error, areaId=" + (area == null ? null : area.getId()));
|
||||
}
|
||||
|
||||
static class AreaPolygon {
|
||||
private final ClosedAreaCountDO area;
|
||||
private final List<BigDecimal[]> points;
|
||||
|
||||
private AreaPolygon(ClosedAreaCountDO area, List<BigDecimal[]> points) {
|
||||
this.area = area;
|
||||
this.points = points;
|
||||
}
|
||||
|
||||
ClosedAreaCountDO getArea() {
|
||||
return area;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.zcloud.primeport.command.query;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
class ClosedAreaPersonLocation {
|
||||
private Long userId;
|
||||
private String staffName;
|
||||
private Long corpinfoId;
|
||||
private String corpinfoName;
|
||||
private String terminalNo;
|
||||
private String locationStatus;
|
||||
private Long lastLocationTime;
|
||||
private String currentLocation;
|
||||
private BigDecimal lon;
|
||||
private BigDecimal lat;
|
||||
}
|
||||
|
|
@ -0,0 +1,320 @@
|
|||
package com.zcloud.primeport.command.query;
|
||||
|
||||
import com.alibaba.cola.exception.BizException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.zcloud.primeport.integration.finds.PrimePortFindsOpenApiClient;
|
||||
import com.zcloud.primeport.persistence.dataobject.ClosedAreaPersonLocationDO;
|
||||
import com.zcloud.primeport.persistence.dataobject.ClosedAreaTerminalBindingDO;
|
||||
import com.zcloud.primeport.persistence.mapper.ClosedAreaPersonLocationMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
class ClosedAreaPersonLocationProvider {
|
||||
private static final String POINT_LOCATE_API = "finds.point.locate";
|
||||
private static final String STATUS_ONLINE = "ONLINE";
|
||||
private static final int LOCATE_BATCH_SIZE = 100;
|
||||
private static final int FINDS_BATCH_CONCURRENCY = 4;
|
||||
|
||||
private final ClosedAreaPersonLocationMapper locationMapper;
|
||||
private final PrimePortFindsOpenApiClient findsOpenApiClient;
|
||||
|
||||
List<ClosedAreaPersonLocation> listOnlinePersons(Long corpinfoId) {
|
||||
if (corpinfoId == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<ClosedAreaPersonLocationDO> matched = locationMapper.listMatchedPersons(corpinfoId);
|
||||
if (matched == null || matched.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
fillMissingTerminalNos(matched);
|
||||
Map<String, ClosedAreaPersonLocationDO> terminals = loadTerminals(matched);
|
||||
Set<String> terminalNosToLocate = collectOnlineTerminalNos(matched, terminals);
|
||||
Map<String, JsonNode> currentLocations = locateTerminals(terminalNosToLocate);
|
||||
List<ClosedAreaPersonLocation> result = new ArrayList<>();
|
||||
Set<Long> userIds = new LinkedHashSet<>();
|
||||
for (ClosedAreaPersonLocationDO person : matched) {
|
||||
if (person.getUserId() != null && !userIds.add(person.getUserId())) {
|
||||
continue;
|
||||
}
|
||||
ClosedAreaPersonLocationDO terminal = terminals.get(person.getFindsTerminalNo());
|
||||
if (!isStoredOnline(person, terminal)) {
|
||||
continue;
|
||||
}
|
||||
JsonNode currentLocation = currentLocations.get(person.getFindsTerminalNo());
|
||||
if (!hasLocation(currentLocation)) {
|
||||
continue;
|
||||
}
|
||||
result.add(toLocation(person, terminal, currentLocation));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void fillMissingTerminalNos(List<ClosedAreaPersonLocationDO> matched) {
|
||||
Set<String> idCardNos = new LinkedHashSet<>();
|
||||
for (ClosedAreaPersonLocationDO person : matched) {
|
||||
if (!StringUtils.hasText(person.getFindsTerminalNo())
|
||||
&& StringUtils.hasText(person.getIdCardNo())) {
|
||||
idCardNos.add(person.getIdCardNo().trim());
|
||||
}
|
||||
}
|
||||
if (idCardNos.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Map<String, String> terminalNosByIdCardNo = new LinkedHashMap<>();
|
||||
for (List<String> batch : batches(idCardNos)) {
|
||||
for (ClosedAreaTerminalBindingDO binding : locationMapper.listTerminalBindingsByIdCardNos(batch)) {
|
||||
String idCardNo = firstText(binding.getBindingIdCardNo(), binding.getTerminalIdCardNo());
|
||||
if (StringUtils.hasText(idCardNo) && StringUtils.hasText(binding.getTerminalNo())) {
|
||||
terminalNosByIdCardNo.putIfAbsent(idCardNo.trim(), binding.getTerminalNo().trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
for (ClosedAreaPersonLocationDO person : matched) {
|
||||
if (!StringUtils.hasText(person.getFindsTerminalNo())) {
|
||||
person.setFindsTerminalNo(terminalNosByIdCardNo.get(normalize(person.getIdCardNo())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, ClosedAreaPersonLocationDO> loadTerminals(List<ClosedAreaPersonLocationDO> matched) {
|
||||
Set<String> terminalNos = new LinkedHashSet<>();
|
||||
Map<String, ClosedAreaPersonLocationDO> result = new LinkedHashMap<>();
|
||||
for (ClosedAreaPersonLocationDO person : matched) {
|
||||
String terminalNo = normalize(person.getFindsTerminalNo());
|
||||
if (!StringUtils.hasText(terminalNo)) {
|
||||
continue;
|
||||
}
|
||||
terminalNos.add(terminalNo);
|
||||
if (StringUtils.hasText(person.getTerminalNo())) {
|
||||
result.putIfAbsent(terminalNo, person);
|
||||
}
|
||||
}
|
||||
for (List<String> batch : batches(terminalNos)) {
|
||||
for (ClosedAreaPersonLocationDO terminal : locationMapper.listTerminals(batch)) {
|
||||
if (StringUtils.hasText(terminal.getTerminalNo())) {
|
||||
result.putIfAbsent(terminal.getTerminalNo(), terminal);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Set<String> collectOnlineTerminalNos(List<ClosedAreaPersonLocationDO> matched,
|
||||
Map<String, ClosedAreaPersonLocationDO> terminals) {
|
||||
Set<String> result = new LinkedHashSet<>();
|
||||
for (ClosedAreaPersonLocationDO person : matched) {
|
||||
String terminalNo = normalize(person.getFindsTerminalNo());
|
||||
if (StringUtils.hasText(terminalNo) && isStoredOnline(person, terminals.get(terminalNo))) {
|
||||
result.add(terminalNo);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<String, JsonNode> locateTerminals(Set<String> terminalNos) {
|
||||
if (terminalNos == null || terminalNos.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
List<List<String>> batches = batches(terminalNos);
|
||||
List<Map<String, Object>> requests = new ArrayList<>(batches.size());
|
||||
for (List<String> batch : batches) {
|
||||
requests.add(Collections.singletonMap("terminalNoList", batch));
|
||||
}
|
||||
List<JsonNode> roots = findsOpenApiClient.postFailFastBatch(
|
||||
POINT_LOCATE_API, requests, FINDS_BATCH_CONCURRENCY);
|
||||
Map<String, JsonNode> result = new LinkedHashMap<>();
|
||||
for (int index = 0; index < roots.size(); index++) {
|
||||
appendLocatedTerminals(batches.get(index), roots.get(index), result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void appendLocatedTerminals(List<String> terminalNos, JsonNode root, Map<String, JsonNode> result) {
|
||||
JsonNode data = assertSuccess(root).path("data");
|
||||
JsonNode rows = extractRows(data);
|
||||
if (rows == null || !rows.isArray()) {
|
||||
return;
|
||||
}
|
||||
for (JsonNode row : rows) {
|
||||
String terminalNo = text(firstPresent(row, "terminalNo"));
|
||||
if (!StringUtils.hasText(terminalNo) && terminalNos.size() == 1) {
|
||||
terminalNo = terminalNos.get(0);
|
||||
}
|
||||
if (StringUtils.hasText(terminalNo)) {
|
||||
result.put(terminalNo, row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private JsonNode assertSuccess(JsonNode root) {
|
||||
int code = root == null ? -1 : root.path("code").asInt(-1);
|
||||
if (code != 200 && code != 0) {
|
||||
String message = root == null ? "" : root.path("msg").asText("");
|
||||
throw new BizException("FindS point locate failed, code=" + code + ", message=" + message);
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
private ClosedAreaPersonLocation toLocation(ClosedAreaPersonLocationDO person,
|
||||
ClosedAreaPersonLocationDO terminal,
|
||||
JsonNode currentLocation) {
|
||||
ClosedAreaPersonLocation location = new ClosedAreaPersonLocation();
|
||||
location.setUserId(person.getUserId());
|
||||
location.setStaffName(firstText(person.getStaffName(), person.getFindsStaffName()));
|
||||
location.setCorpinfoId(person.getCorpinfoId());
|
||||
location.setCorpinfoName(person.getCorpinfoName());
|
||||
location.setTerminalNo(person.getFindsTerminalNo());
|
||||
location.setLocationStatus(STATUS_ONLINE);
|
||||
location.setCurrentLocation(locationName(currentLocation));
|
||||
location.setLastLocationTime(firstLong(locationTime(currentLocation), terminalLastLocationTime(terminal)));
|
||||
location.setLon(decimal(firstPresent(currentLocation, "lon", "lng", "longitude")));
|
||||
location.setLat(decimal(firstPresent(currentLocation, "lat", "latitude")));
|
||||
return location;
|
||||
}
|
||||
|
||||
private boolean isStoredOnline(ClosedAreaPersonLocationDO person, ClosedAreaPersonLocationDO terminal) {
|
||||
if (person != null && StringUtils.hasText(person.getLocationStatus())) {
|
||||
return STATUS_ONLINE.equalsIgnoreCase(person.getLocationStatus());
|
||||
}
|
||||
String deviceStatus = firstText(
|
||||
person == null ? null : person.getFindsDeviceStatus(),
|
||||
terminal == null ? null : terminal.getTerminalDeviceStatus());
|
||||
return STATUS_ONLINE.equalsIgnoreCase(deviceStatus)
|
||||
|| "STATIC".equalsIgnoreCase(deviceStatus)
|
||||
|| "MOVING".equalsIgnoreCase(deviceStatus);
|
||||
}
|
||||
|
||||
private boolean hasLocation(JsonNode location) {
|
||||
return decimal(firstPresent(location, "lon", "lng", "longitude")) != null
|
||||
&& decimal(firstPresent(location, "lat", "latitude")) != null;
|
||||
}
|
||||
|
||||
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 String locationName(JsonNode location) {
|
||||
return firstText(text(firstPresent(location, "fenceName")),
|
||||
text(firstPresent(location, "areaName")),
|
||||
text(firstPresent(location, "positionName")),
|
||||
text(firstPresent(location, "location")),
|
||||
text(firstPresent(location, "address")));
|
||||
}
|
||||
|
||||
private Long locationTime(JsonNode location) {
|
||||
JsonNode node = firstPresent(location, "gt", "time", "locateTime", "locationTime", "timestamp");
|
||||
if (node == null || node.isNull() || node.isMissingNode()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
long value = Long.parseLong(node.asText());
|
||||
return value < 100000000000L ? value * 1000L : value;
|
||||
} catch (NumberFormatException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Long terminalLastLocationTime(ClosedAreaPersonLocationDO terminal) {
|
||||
LocalDateTime value = terminal == null ? null : terminal.getTerminalLastLocationTime();
|
||||
return value == null ? null : value.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
||||
}
|
||||
|
||||
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() ? null : node.asText();
|
||||
}
|
||||
|
||||
private BigDecimal decimal(JsonNode node) {
|
||||
if (node == null || node.isNull() || node.isMissingNode()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new BigDecimal(node.asText());
|
||||
} catch (NumberFormatException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private List<List<String>> batches(Set<String> values) {
|
||||
List<String> normalized = new ArrayList<>();
|
||||
if (values != null) {
|
||||
for (String value : values) {
|
||||
String item = normalize(value);
|
||||
if (StringUtils.hasText(item)) {
|
||||
normalized.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
List<List<String>> result = new ArrayList<>();
|
||||
for (int from = 0; from < normalized.size(); from += LOCATE_BATCH_SIZE) {
|
||||
result.add(normalized.subList(from, Math.min(from + LOCATE_BATCH_SIZE, normalized.size())));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String normalize(String value) {
|
||||
return value == null ? null : value.trim();
|
||||
}
|
||||
|
||||
private String firstText(String... values) {
|
||||
if (values == null) {
|
||||
return null;
|
||||
}
|
||||
for (String value : values) {
|
||||
if (StringUtils.hasText(value)) {
|
||||
return value.trim();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Long firstLong(Long... values) {
|
||||
if (values == null) {
|
||||
return null;
|
||||
}
|
||||
for (Long value : values) {
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
package com.zcloud.primeport.command.query;
|
||||
|
||||
import com.alibaba.cola.dto.MultiResponse;
|
||||
import com.jjb.saas.framework.auth.utils.AuthContext;
|
||||
import com.zcloud.gbscommon.utils.PageQueryHelper;
|
||||
import com.zcloud.primeport.command.convertor.ClosedAreaCountCoConvertor;
|
||||
import com.zcloud.primeport.dto.ClosedAreaCountCmd;
|
||||
import com.zcloud.primeport.dto.clientobject.ClosedAreaCountCO;
|
||||
import com.zcloud.primeport.persistence.dataobject.ClosedAreaCountDO;
|
||||
import com.zcloud.primeport.persistence.repository.ClosedAreaRepository;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
public class ClosedAreaPositionCountExe {
|
||||
private final ClosedAreaRepository closedAreaRepository;
|
||||
private final ClosedAreaCountCoConvertor closedAreaCountCoConvertor;
|
||||
private final ClosedAreaPersonLocationProvider personLocationProvider;
|
||||
private final ClosedAreaMatcher closedAreaMatcher;
|
||||
|
||||
public MultiResponse<ClosedAreaCountCO> execute(ClosedAreaCountCmd cmd) {
|
||||
Map<String, Object> params = PageQueryHelper.toHashMap(cmd);
|
||||
List<ClosedAreaCountDO> rows = closedAreaRepository.listPositionCount(params);
|
||||
fillRealtimeCounts(rows, cmd);
|
||||
List<ClosedAreaCountCO> result = closedAreaCountCoConvertor.converDOsToCOs(rows);
|
||||
for (ClosedAreaCountCO item : result) {
|
||||
item.setChildren(treeChildren(item.getId(), rows));
|
||||
}
|
||||
for (ClosedAreaCountCO item : result) {
|
||||
sumChildrenCount(item);
|
||||
}
|
||||
return MultiResponse.of(result);
|
||||
}
|
||||
|
||||
private void fillRealtimeCounts(List<ClosedAreaCountDO> areas, ClosedAreaCountCmd cmd) {
|
||||
if (areas == null || areas.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Map<Long, List<ClosedAreaCountDO>> areasByCorpinfoId = groupAreasByCorpinfoId(areas, cmd);
|
||||
for (Map.Entry<Long, List<ClosedAreaCountDO>> entry : areasByCorpinfoId.entrySet()) {
|
||||
List<ClosedAreaMatcher.AreaPolygon> polygons = closedAreaMatcher.prepare(entry.getValue());
|
||||
if (polygons.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
List<ClosedAreaPersonLocation> persons = personLocationProvider.listOnlinePersons(entry.getKey());
|
||||
Map<Long, Integer> personCounts = countPersonsByArea(persons, polygons);
|
||||
for (ClosedAreaCountDO area : entry.getValue()) {
|
||||
area.setPersonCount(personCounts.getOrDefault(area.getId(), 0));
|
||||
area.setCarCount(0);
|
||||
}
|
||||
}
|
||||
for (ClosedAreaCountDO area : areas) {
|
||||
if (area.getPersonCount() == null) {
|
||||
area.setPersonCount(0);
|
||||
}
|
||||
area.setCarCount(0);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<Long, List<ClosedAreaCountDO>> groupAreasByCorpinfoId(List<ClosedAreaCountDO> areas,
|
||||
ClosedAreaCountCmd cmd) {
|
||||
Map<Long, List<ClosedAreaCountDO>> result = new LinkedHashMap<>();
|
||||
Long fallbackCorpinfoId = cmd == null ? null : cmd.getCorpId();
|
||||
for (ClosedAreaCountDO area : areas) {
|
||||
Long corpinfoId = area.getJurisdictionalCorpId() == null
|
||||
? fallbackCorpinfoId : area.getJurisdictionalCorpId();
|
||||
if (corpinfoId == null) {
|
||||
continue;
|
||||
}
|
||||
result.computeIfAbsent(corpinfoId, key -> new ArrayList<>()).add(area);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<Long, Integer> countPersonsByArea(List<ClosedAreaPersonLocation> persons,
|
||||
List<ClosedAreaMatcher.AreaPolygon> polygons) {
|
||||
if (persons == null || persons.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
Map<Long, Integer> result = new HashMap<>();
|
||||
for (ClosedAreaPersonLocation person : persons) {
|
||||
ClosedAreaMatcher.AreaPolygon matched = closedAreaMatcher.match(
|
||||
person.getLon(), person.getLat(), polygons);
|
||||
if (matched == null || matched.getArea().getId() == null) {
|
||||
continue;
|
||||
}
|
||||
Long areaId = matched.getArea().getId();
|
||||
result.put(areaId, result.getOrDefault(areaId, 0) + 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<ClosedAreaCountCO> treeChildren(Long parentId, List<ClosedAreaCountDO> rows) {
|
||||
List<ClosedAreaCountCO> children = new ArrayList<>();
|
||||
for (ClosedAreaCountDO row : rows) {
|
||||
if (parentId.equals(row.getParentId())) {
|
||||
children.add(closedAreaCountCoConvertor.converDOToCO(row));
|
||||
}
|
||||
}
|
||||
for (ClosedAreaCountCO child : children) {
|
||||
child.setChildren(treeChildren(child.getId(), rows));
|
||||
}
|
||||
return children.isEmpty() ? null : children;
|
||||
}
|
||||
|
||||
private void sumChildrenCount(ClosedAreaCountCO node) {
|
||||
List<ClosedAreaCountCO> children = node.getChildren();
|
||||
if (children == null || children.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
int personSum = 0;
|
||||
int carSum = 0;
|
||||
for (ClosedAreaCountCO child : children) {
|
||||
sumChildrenCount(child);
|
||||
personSum += child.getPersonCount() == null ? 0 : child.getPersonCount();
|
||||
carSum += child.getCarCount() == null ? 0 : child.getCarCount();
|
||||
}
|
||||
node.setPersonCount((node.getPersonCount() == null ? 0 : node.getPersonCount()) + personSum);
|
||||
node.setCarCount((node.getCarCount() == null ? 0 : node.getCarCount()) + carSum);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,221 @@
|
|||
package com.zcloud.primeport.integration.finds;
|
||||
|
||||
import com.alibaba.cola.exception.BizException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.jjb.saas.framework.auth.model.SSOUser;
|
||||
import com.jjb.saas.framework.auth.utils.AuthContext;
|
||||
import com.zcloud.primeport.persistence.dataobject.OpenapiConfigDO;
|
||||
import com.zcloud.primeport.persistence.mapper.ClosedAreaPersonLocationMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class PrimePortFindsOpenApiClient {
|
||||
private static final String SIK_PATH_SEGMENT = "sik";
|
||||
|
||||
@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 ClosedAreaPersonLocationMapper locationMapper;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
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));
|
||||
}
|
||||
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)));
|
||||
}
|
||||
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 batch request interrupted");
|
||||
} catch (ExecutionException e) {
|
||||
Throwable cause = e.getCause();
|
||||
if (cause instanceof RuntimeException) {
|
||||
throw (RuntimeException) cause;
|
||||
}
|
||||
throw new BizException("FindS OpenAPI batch request failed: " + cause.getMessage());
|
||||
} finally {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
private OpenapiConfigDO loadEnabledConfig() {
|
||||
SSOUser currentUser = AuthContext.getCurrentUser();
|
||||
Long companyId = currentUser == null ? null : currentUser.getCompanyId();
|
||||
OpenapiConfigDO config = locationMapper.findEnabledOpenapiConfig(companyId);
|
||||
if (config == null) {
|
||||
throw new BizException("FindS OpenAPI enabled config not found");
|
||||
}
|
||||
if (!StringUtils.hasText(config.getHostUrl())
|
||||
|| !StringUtils.hasText(config.getSik())
|
||||
|| !StringUtils.hasText(config.getSisCipherText())) {
|
||||
throw new BizException("FindS OpenAPI config is incomplete");
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
private JsonNode post(OpenapiConfigDO config, String apiName, Map<String, Object> request) {
|
||||
String requestJson = writeJson(request == null ? Collections.emptyMap() : request);
|
||||
String timestamp = String.valueOf(System.currentTimeMillis());
|
||||
String externalPrefix = normalizePrefix(defaultIfBlank(config.getExternalPrefix(), "/rest/"));
|
||||
String internalPrefix = normalizePrefix(defaultIfBlank(config.getInternalPrefix(), "/rest/"));
|
||||
String signPath = buildApiPath(externalPrefix, apiName, config.getSik());
|
||||
String requestPath = buildApiPath(internalPrefix, apiName, config.getSik());
|
||||
String signature = sign(signPath, requestJson, config.getSisCipherText(), timestamp);
|
||||
String url = trimTrailingSlash(config.getHostUrl()) + requestPath + "?_sign=" + signature;
|
||||
String response = doPost(url, requestJson, timestamp);
|
||||
try {
|
||||
return objectMapper.readTree(response);
|
||||
} catch (IOException e) {
|
||||
throw new BizException("FindS response is not valid JSON: " + apiName);
|
||||
}
|
||||
}
|
||||
|
||||
private String buildApiPath(String prefix, String apiName, String sik) {
|
||||
return prefix + apiName + "/" + SIK_PATH_SEGMENT + "/" + sik;
|
||||
}
|
||||
|
||||
private String sign(String path, String requestJson, String sis, String timestamp) {
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(new SecretKeySpec(sis.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
|
||||
mac.update(path.getBytes(StandardCharsets.UTF_8));
|
||||
mac.update("request".getBytes(StandardCharsets.UTF_8));
|
||||
mac.update(requestJson.getBytes(StandardCharsets.UTF_8));
|
||||
mac.update(timestamp.getBytes(StandardCharsets.UTF_8));
|
||||
return toUpperHex(mac.doFinal());
|
||||
} catch (Exception e) {
|
||||
throw new BizException("FindS OpenAPI sign failed");
|
||||
}
|
||||
}
|
||||
|
||||
private String doPost(String url, String requestJson, String timestamp) {
|
||||
HttpURLConnection connection = null;
|
||||
try {
|
||||
byte[] body = ("request=" + URLEncoder.encode(requestJson, "UTF-8"))
|
||||
.getBytes(StandardCharsets.UTF_8);
|
||||
connection = (HttpURLConnection) new URL(url).openConnection();
|
||||
connection.setConnectTimeout(Math.max(failFastConnectTimeoutMs, 1));
|
||||
connection.setReadTimeout(Math.max(failFastReadTimeoutMs, 1));
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setDoOutput(true);
|
||||
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
|
||||
connection.setRequestProperty("wz-acs-timestamp", timestamp);
|
||||
connection.setRequestProperty("Accept", "application/json");
|
||||
try (OutputStream outputStream = connection.getOutputStream()) {
|
||||
outputStream.write(body);
|
||||
}
|
||||
int status = connection.getResponseCode();
|
||||
String response = readResponse(status >= 400
|
||||
? connection.getErrorStream() : connection.getInputStream());
|
||||
if (status != HttpURLConnection.HTTP_OK) {
|
||||
throw new BizException("FindS OpenAPI HTTP failed, status=" + status + ", body=" + response);
|
||||
}
|
||||
return response;
|
||||
} catch (IOException e) {
|
||||
throw new BizException("FindS OpenAPI connection failed: " + e.getMessage());
|
||||
} finally {
|
||||
if (connection != null) {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String readResponse(InputStream inputStream) throws IOException {
|
||||
if (inputStream == null) {
|
||||
return "";
|
||||
}
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
byte[] buffer = new byte[4096];
|
||||
int len;
|
||||
while ((len = inputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, len);
|
||||
}
|
||||
return new String(outputStream.toByteArray(), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private String writeJson(Object value) {
|
||||
try {
|
||||
return objectMapper.writeValueAsString(value);
|
||||
} catch (IOException e) {
|
||||
throw new BizException("FindS request JSON convert failed");
|
||||
}
|
||||
}
|
||||
|
||||
private String toUpperHex(byte[] bytes) {
|
||||
char[] digits = "0123456789ABCDEF".toCharArray();
|
||||
char[] result = new char[bytes.length * 2];
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
result[i * 2] = digits[(bytes[i] & 0xF0) >> 4];
|
||||
result[i * 2 + 1] = digits[bytes[i] & 0x0F];
|
||||
}
|
||||
return new String(result);
|
||||
}
|
||||
|
||||
private String normalizePrefix(String prefix) {
|
||||
String value = prefix.trim();
|
||||
if (!value.startsWith("/")) {
|
||||
value = "/" + value;
|
||||
}
|
||||
if (!value.endsWith("/")) {
|
||||
value = value + "/";
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private String trimTrailingSlash(String value) {
|
||||
String result = value.trim();
|
||||
while (result.endsWith("/")) {
|
||||
result = result.substring(0, result.length() - 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String defaultIfBlank(String value, String defaultValue) {
|
||||
return StringUtils.hasText(value) ? value : defaultValue;
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import com.zcloud.primeport.command.ClosedAreaAddExe;
|
|||
import com.zcloud.primeport.command.ClosedAreaRemoveExe;
|
||||
import com.zcloud.primeport.command.ClosedAreaUpdateExe;
|
||||
import com.zcloud.primeport.command.query.ClosedAreaCountExe;
|
||||
import com.zcloud.primeport.command.query.ClosedAreaPositionCountExe;
|
||||
import com.zcloud.primeport.command.query.ClosedAreaQueryExe;
|
||||
import com.zcloud.primeport.dto.ClosedAreaAddCmd;
|
||||
import com.zcloud.primeport.dto.ClosedAreaCorpStatCmd;
|
||||
|
|
@ -38,6 +39,7 @@ public class ClosedAreaServiceImpl implements ClosedAreaServiceI {
|
|||
private final ClosedAreaRemoveExe closedAreaRemoveExe;
|
||||
private final ClosedAreaQueryExe closedAreaQueryExe;
|
||||
private final ClosedAreaCountExe closedAreaCountExe;
|
||||
private final ClosedAreaPositionCountExe closedAreaPositionCountExe;
|
||||
private final ClosedAreaRepository closedAreaRepository;
|
||||
|
||||
@Override
|
||||
|
|
@ -71,6 +73,11 @@ public class ClosedAreaServiceImpl implements ClosedAreaServiceI {
|
|||
return closedAreaCountExe.execute(cmd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultiResponse<ClosedAreaCountCO> positionCount(ClosedAreaCountCmd cmd) {
|
||||
return closedAreaPositionCountExe.execute(cmd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SingleResponse<ClosedAreaCorpStatCO> corpStat(ClosedAreaCorpStatCmd cmd) {
|
||||
return closedAreaCountExe.corpStat(cmd);
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ public interface ClosedAreaServiceI {
|
|||
|
||||
MultiResponse<ClosedAreaCountCO> count(ClosedAreaCountCmd cmd);
|
||||
|
||||
MultiResponse<ClosedAreaCountCO> positionCount(ClosedAreaCountCmd cmd);
|
||||
|
||||
SingleResponse<ClosedAreaCorpStatCO> corpStat(ClosedAreaCorpStatCmd cmd);
|
||||
|
||||
void remove(Long id);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
package com.zcloud.primeport.persistence.dataobject;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class ClosedAreaPersonLocationDO {
|
||||
private Long userId;
|
||||
private String localStaffNo;
|
||||
private String staffName;
|
||||
private String mobileNo;
|
||||
private String idCardNo;
|
||||
private Long corpinfoId;
|
||||
private String corpinfoName;
|
||||
private Long departmentId;
|
||||
private String departmentName;
|
||||
private String findsStaffNo;
|
||||
private String findsStaffName;
|
||||
private String findsTerminalNo;
|
||||
private String locationStatus;
|
||||
private String findsDeviceStatus;
|
||||
private Long locationAreaId;
|
||||
private String locationAreaName;
|
||||
private String terminalNo;
|
||||
private String terminalDeviceType;
|
||||
private String terminalDeviceStatus;
|
||||
private LocalDateTime terminalLastLocationTime;
|
||||
private String terminalLastLocationName;
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.zcloud.primeport.persistence.dataobject;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ClosedAreaTerminalBindingDO {
|
||||
private String bindingIdCardNo;
|
||||
private String terminalIdCardNo;
|
||||
private String terminalNo;
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.zcloud.primeport.persistence.dataobject;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.jjb.saas.framework.repository.basedo.BaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("openapi_config")
|
||||
public class OpenapiConfigDO extends BaseDO {
|
||||
private String configName;
|
||||
private String configCode;
|
||||
private Long corpinfoId;
|
||||
private String corpinfoName;
|
||||
private String hostUrl;
|
||||
private String externalPrefix;
|
||||
private String internalPrefix;
|
||||
private String sik;
|
||||
private String sisCipherText;
|
||||
private Integer enabled;
|
||||
}
|
||||
|
|
@ -22,14 +22,17 @@ import java.util.Map;
|
|||
* @Date 2026-03-19 10:27:51
|
||||
*/
|
||||
@Mapper
|
||||
@DataScopes(
|
||||
@DataScope(method = "listPage", menuPerms = "")
|
||||
)
|
||||
@DataScopes({
|
||||
@DataScope(method = "listPage", menuPerms = ""),
|
||||
@DataScope(method = "listPositionCount", menuPerms = "")
|
||||
})
|
||||
public interface ClosedAreaMapper extends BaseMapper<ClosedAreaDO> {
|
||||
|
||||
|
||||
List<ClosedAreaCountDO> listCount(@Param("params") Map<String,Object> params);
|
||||
|
||||
List<ClosedAreaCountDO> listPositionCount(@Param("params") Map<String,Object> params);
|
||||
|
||||
ClosedAreaCorpStatDO corpStat(@Param("params") Map<String, Object> params);
|
||||
|
||||
List<ClosedAreaDO> getCountByHgAuthArea();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
package com.zcloud.primeport.persistence.mapper;
|
||||
|
||||
import com.zcloud.primeport.persistence.dataobject.ClosedAreaPersonLocationDO;
|
||||
import com.zcloud.primeport.persistence.dataobject.ClosedAreaTerminalBindingDO;
|
||||
import com.zcloud.primeport.persistence.dataobject.OpenapiConfigDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ClosedAreaPersonLocationMapper {
|
||||
List<ClosedAreaPersonLocationDO> listMatchedPersons(@Param("corpinfoId") Long corpinfoId);
|
||||
|
||||
List<ClosedAreaTerminalBindingDO> listTerminalBindingsByIdCardNos(@Param("values") List<String> values);
|
||||
|
||||
List<ClosedAreaPersonLocationDO> listTerminals(@Param("values") List<String> values);
|
||||
|
||||
OpenapiConfigDO findEnabledOpenapiConfig(@Param("corpinfoId") Long corpinfoId);
|
||||
}
|
||||
|
|
@ -21,6 +21,8 @@ public interface ClosedAreaRepository extends BaseRepository<ClosedAreaDO> {
|
|||
|
||||
List<ClosedAreaCountDO> listCount(Map<String,Object> params);
|
||||
|
||||
List<ClosedAreaCountDO> listPositionCount(Map<String,Object> params);
|
||||
|
||||
ClosedAreaCorpStatDO corpStat(Map<String, Object> params);
|
||||
|
||||
List<ClosedAreaDO> getCountByHgAuthArea();
|
||||
|
|
|
|||
|
|
@ -45,6 +45,11 @@ public class ClosedAreaRepositoryImpl extends BaseRepositoryImpl<ClosedAreaMappe
|
|||
return closedAreaMapper.listCount(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ClosedAreaCountDO> listPositionCount(Map<String, Object> params) {
|
||||
return closedAreaMapper.listPositionCount(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClosedAreaCorpStatDO corpStat(Map<String, Object> params) {
|
||||
return closedAreaMapper.corpStat(params);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.zcloud.gbscommon.hkDevice.HKDeviceUtil;
|
|||
import com.zcloud.primeport.hk.config.HkAccessRecordProperties;
|
||||
import com.zcloud.primeport.persistence.dataobject.HkAccessRecordDO;
|
||||
import com.zcloud.primeport.persistence.repository.HkAccessRecordRepository;
|
||||
import jodd.util.Base64;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
|
@ -36,6 +37,7 @@ public class HkAccessRecordSyncXxlJob implements Job {
|
|||
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss+08:00");
|
||||
private static final DateTimeFormatter PARSE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
private static final int MAX_PAGES = 200;
|
||||
private static final String ID_CARD_PATTERN = "^(\\d{15}|\\d{17}[0-9Xx])$";
|
||||
|
||||
@Override
|
||||
@JobRegister(cron = "0 */5 * * * ?", jobDesc = "海康人员进出记录增量同步", triggerStatus = 1)
|
||||
|
|
@ -120,7 +122,8 @@ public class HkAccessRecordSyncXxlJob implements Job {
|
|||
record.setHkEventId(item.getStr("eventId"));
|
||||
record.setPersonId(item.getStr("personId"));
|
||||
record.setPersonName(item.getStr("personName"));
|
||||
record.setCertificateNo(item.getStr("certNo"));
|
||||
String certificateNo = encryptIdCardIfNecessary(item.getStr("certNo"));
|
||||
record.setCertificateNo(certificateNo);
|
||||
record.setOrgIndexCode(item.getStr("orgIndexCode"));
|
||||
record.setOrgPathName(item.getStr("orgName"));
|
||||
record.setDoorIndexCode(item.getStr("doorIndexCode"));
|
||||
|
|
@ -146,11 +149,38 @@ public class HkAccessRecordSyncXxlJob implements Job {
|
|||
}
|
||||
record.setRecordImageUrl(item.getStr("picUri"));
|
||||
record.setSourceType("PULL");
|
||||
item.set("certNo", certificateNo);
|
||||
record.setRawData(item.toString());
|
||||
record.setLastSyncTime(LocalDateTime.now());
|
||||
return record;
|
||||
}
|
||||
|
||||
private String encryptIdCardIfNecessary(String certNo) {
|
||||
if (certNo == null || certNo.trim().isEmpty()) {
|
||||
return certNo;
|
||||
}
|
||||
String trimmedCertNo = certNo.trim();
|
||||
if (isIdCard(trimmedCertNo)) {
|
||||
return Base64.encodeToString(trimmedCertNo);
|
||||
}
|
||||
if (isEncryptedIdCard(trimmedCertNo)) {
|
||||
return trimmedCertNo;
|
||||
}
|
||||
return certNo;
|
||||
}
|
||||
|
||||
private boolean isEncryptedIdCard(String certNo) {
|
||||
try {
|
||||
return isIdCard(Base64.decodeToString(certNo));
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isIdCard(String certNo) {
|
||||
return certNo != null && certNo.matches(ID_CARD_PATTERN);
|
||||
}
|
||||
|
||||
private LocalDateTime parseDateTime(String timeStr) {
|
||||
if (timeStr == null || timeStr.isEmpty()) return null;
|
||||
// 兼容 ISO 格式: 2026-08-12T10:30:00+08:00 和普通格式
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ CREATE TABLE IF NOT EXISTS `hk_access_record` (
|
|||
`person_id` varchar(128) DEFAULT NULL COMMENT '海康人员ID(personId)',
|
||||
`person_name` varchar(255) DEFAULT NULL COMMENT '人员姓名快照',
|
||||
`certificate_no` varchar(128) DEFAULT NULL COMMENT '证件号码(身份证)',
|
||||
`phone_no` varchar(128) DEFAULT NULL COMMENT '手机号',
|
||||
`org_index_code` varchar(128) DEFAULT NULL COMMENT '海康组织编码',
|
||||
`org_path_name` varchar(512) DEFAULT NULL COMMENT '海康组织路径名称',
|
||||
`door_index_code` varchar(128) DEFAULT NULL COMMENT '门禁点编码(indexCode)',
|
||||
|
|
|
|||
|
|
@ -108,6 +108,37 @@
|
|||
c.id
|
||||
</select>
|
||||
|
||||
<select id="listPositionCount" resultType="com.zcloud.primeport.persistence.dataobject.ClosedAreaCountDO">
|
||||
SELECT
|
||||
c.*,
|
||||
CASE WHEN child_cnt > 0 THEN 1 ELSE 2 END AS has_child,
|
||||
0 AS person_count,
|
||||
0 AS car_count
|
||||
FROM
|
||||
closed_area c
|
||||
LEFT JOIN (
|
||||
SELECT parent_id, COUNT(id) child_cnt
|
||||
FROM closed_area
|
||||
WHERE delete_enum = 'FALSE'
|
||||
GROUP BY parent_id
|
||||
) child ON c.id = child.parent_id
|
||||
WHERE
|
||||
c.delete_enum = 'FALSE'
|
||||
<if test="params.corpId != null">
|
||||
AND c.jurisdictional_corp_id = #{params.corpId}
|
||||
</if>
|
||||
<if test="params.parentId != null">
|
||||
AND c.parent_id = #{params.parentId}
|
||||
</if>
|
||||
<if test="params.closedAreaName != null and params.closedAreaName != ''">
|
||||
AND C.closed_area_name LIKE CONCAT('%', #{params.closedAreaName}, '%')
|
||||
</if>
|
||||
GROUP BY
|
||||
c.id
|
||||
ORDER BY
|
||||
c.id
|
||||
</select>
|
||||
|
||||
<select id="corpStat" resultType="com.zcloud.primeport.persistence.dataobject.ClosedAreaCorpStatDO">
|
||||
SELECT
|
||||
IFNULL(COUNT(DISTINCT ca.id), 0) AS closed_area_count,
|
||||
|
|
@ -177,18 +208,34 @@
|
|||
<if test="params.vehiclePlateNumber != null and params.vehiclePlateNumber != ''">
|
||||
AND hvar.plate_no LIKE CONCAT('%', #{params.vehiclePlateNumber}, '%')
|
||||
</if>
|
||||
<if test="(params.arrivalStartTime != null and params.arrivalStartTime != '') or (params.arrivalEndTime != null and params.arrivalEndTime != '') or (params.departureStartTime != null and params.departureStartTime != '') or (params.departureEndTime != null and params.departureEndTime != '')">
|
||||
AND (
|
||||
<trim prefixOverrides="OR">
|
||||
<if test="(params.arrivalStartTime != null and params.arrivalStartTime != '') or (params.arrivalEndTime != null and params.arrivalEndTime != '')">
|
||||
OR (
|
||||
hvar.enter_or_exit = 0
|
||||
<if test="params.arrivalStartTime != null and params.arrivalStartTime != ''">
|
||||
AND hvar.event_time >= CONCAT(#{params.arrivalStartTime}, ' 00:00:00')
|
||||
</if>
|
||||
<if test="params.arrivalEndTime != null and params.arrivalEndTime != ''">
|
||||
AND hvar.event_time <= CONCAT(#{params.arrivalEndTime}, ' 23:59:59')
|
||||
</if>
|
||||
)
|
||||
</if>
|
||||
<if test="(params.departureStartTime != null and params.departureStartTime != '') or (params.departureEndTime != null and params.departureEndTime != '')">
|
||||
OR (
|
||||
hvar.enter_or_exit = 1
|
||||
<if test="params.departureStartTime != null and params.departureStartTime != ''">
|
||||
AND hvar.event_time >= CONCAT(#{params.departureStartTime}, ' 00:00:00')
|
||||
</if>
|
||||
<if test="params.departureEndTime != null and params.departureEndTime != ''">
|
||||
AND hvar.event_time <= CONCAT(#{params.departureEndTime}, ' 23:59:59')
|
||||
</if>
|
||||
)
|
||||
</if>
|
||||
</trim>
|
||||
)
|
||||
</if>
|
||||
</sql>
|
||||
|
||||
<select id="listVehicleInOutRecord" resultType="com.zcloud.primeport.persistence.dataobject.HkVehicleAccessRecordDO">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,101 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="com.zcloud.primeport.persistence.mapper.ClosedAreaPersonLocationMapper">
|
||||
<select id="listMatchedPersons" resultType="com.zcloud.primeport.persistence.dataobject.ClosedAreaPersonLocationDO">
|
||||
SELECT pm.finds_staff_no,
|
||||
pm.finds_staff_name,
|
||||
pm.finds_terminal_no,
|
||||
pm.location_status,
|
||||
pm.finds_device_status,
|
||||
pm.location_area_id,
|
||||
pm.location_area_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,
|
||||
ci.id AS corpinfo_id,
|
||||
ci.corp_name AS corpinfo_name,
|
||||
u.department_id,
|
||||
d.name AS department_name,
|
||||
t.terminal_no,
|
||||
t.device_type AS terminal_device_type,
|
||||
t.device_status AS terminal_device_status,
|
||||
t.last_location_time AS terminal_last_location_time,
|
||||
t.last_location_name AS terminal_last_location_name
|
||||
FROM personnel_match pm
|
||||
JOIN user_scope_v u ON u.id = pm.local_user_id
|
||||
LEFT JOIN corp_info ci ON ci.id = u.corpinfo_id
|
||||
LEFT JOIN department d ON d.id = u.department_id
|
||||
AND (d.delete_enum IS NULL OR d.delete_enum = 'FALSE')
|
||||
LEFT JOIN terminal t ON t.terminal_no = pm.finds_terminal_no
|
||||
AND (t.delete_enum IS NULL OR t.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')
|
||||
<if test="corpinfoId != null">
|
||||
AND ci.id = #{corpinfoId}
|
||||
</if>
|
||||
ORDER BY u.id DESC, pm.finds_staff_no
|
||||
</select>
|
||||
|
||||
<select id="listTerminalBindingsByIdCardNos"
|
||||
resultType="com.zcloud.primeport.persistence.dataobject.ClosedAreaTerminalBindingDO">
|
||||
SELECT tb.id_card_no AS binding_id_card_no,
|
||||
t.bind_id_card_no AS terminal_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')
|
||||
<choose>
|
||||
<when test="values != null and values.size() > 0">
|
||||
AND (
|
||||
tb.id_card_no IN
|
||||
<foreach collection="values" item="value" open="(" separator="," close=")">
|
||||
#{value}
|
||||
</foreach>
|
||||
OR (
|
||||
(tb.id_card_no IS NULL OR tb.id_card_no = '')
|
||||
AND t.bind_id_card_no IN
|
||||
<foreach collection="values" item="value" open="(" separator="," close=")">
|
||||
#{value}
|
||||
</foreach>
|
||||
)
|
||||
)
|
||||
</when>
|
||||
<otherwise>
|
||||
AND 1 = 0
|
||||
</otherwise>
|
||||
</choose>
|
||||
ORDER BY binding_id_card_no, terminal_id_card_no, t.update_time DESC, t.id DESC
|
||||
</select>
|
||||
|
||||
<select id="listTerminals" resultType="com.zcloud.primeport.persistence.dataobject.ClosedAreaPersonLocationDO">
|
||||
SELECT terminal_no,
|
||||
device_type AS terminal_device_type,
|
||||
device_status AS terminal_device_status,
|
||||
last_location_time AS terminal_last_location_time,
|
||||
last_location_name AS terminal_last_location_name
|
||||
FROM terminal
|
||||
WHERE (delete_enum IS NULL OR delete_enum = 'FALSE')
|
||||
AND terminal_no IN
|
||||
<foreach collection="values" item="value" open="(" separator="," close=")">#{value}</foreach>
|
||||
ORDER BY update_time DESC, id DESC
|
||||
</select>
|
||||
|
||||
<select id="findEnabledOpenapiConfig" resultType="com.zcloud.primeport.persistence.dataobject.OpenapiConfigDO">
|
||||
SELECT *
|
||||
FROM openapi_config
|
||||
WHERE enabled = 1
|
||||
AND (delete_enum IS NULL OR delete_enum = 'FALSE')
|
||||
AND (corpinfo_id = #{corpinfoId} OR corpinfo_id IS NULL)
|
||||
ORDER BY CASE WHEN corpinfo_id = #{corpinfoId} THEN 0 ELSE 1 END, id DESC
|
||||
LIMIT 1
|
||||
</select>
|
||||
</mapper>
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
id, hk_event_id, person_id, person_name, certificate_no, phone_no,
|
||||
org_index_code, org_path_name,
|
||||
door_index_code, door_name, door_region_index_code,
|
||||
enter_or_exit, open_result, open_type, card_no,
|
||||
enter_or_exit, card_no,
|
||||
event_time, hk_create_time, record_image_url,
|
||||
source_type, raw_data, last_sync_time,
|
||||
delete_enum, create_time, update_time, env, version
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
#{record.certificateNo}, #{record.phoneNo},
|
||||
#{record.orgIndexCode}, #{record.orgPathName},
|
||||
#{record.doorIndexCode}, #{record.doorName}, #{record.doorRegionIndexCode},
|
||||
#{record.enterOrExit}, #{record.openResult}, #{record.openType}, #{record.cardNo},
|
||||
#{record.enterOrExit}, #{record.cardNo},
|
||||
#{record.eventTime}, #{record.hkCreateTime}, #{record.recordImageUrl},
|
||||
#{record.sourceType}, #{record.rawData}, #{record.lastSyncTime},
|
||||
#{record.deleteEnum}, #{record.createTime}, #{record.updateTime}, #{record.env}, #{record.version}
|
||||
|
|
@ -42,8 +42,6 @@
|
|||
door_name = COALESCE(VALUES(door_name), door_name),
|
||||
door_region_index_code = COALESCE(VALUES(door_region_index_code), door_region_index_code),
|
||||
enter_or_exit = COALESCE(VALUES(enter_or_exit), enter_or_exit),
|
||||
open_result = COALESCE(VALUES(open_result), open_result),
|
||||
open_type = COALESCE(VALUES(open_type), open_type),
|
||||
card_no = COALESCE(VALUES(card_no), card_no),
|
||||
event_time = COALESCE(VALUES(event_time), event_time),
|
||||
hk_create_time = COALESCE(VALUES(hk_create_time), hk_create_time),
|
||||
|
|
|
|||
Loading…
Reference in New Issue