提交 e358cfcb 作者: 方治民

feat: 测试定位系统真实定位数据、jaskson 序列化配置调整、swagger 配置仅在 prod 下不生效、gitlab-ci 更新

上级 0aa2b74a
# 变量
variables:
# 本地镜像地址,用于拉取镜像以及发布
REGISTRY_REMOTE: localhost:18500
# 容器名称
CONTAINER_NAME: kshg-api
# 对外访问端口
EXPOSE_PORT: 18181
# Pipelines 步骤 # Pipelines 步骤
stages: stages:
- build - build
- test - test
- deploy - deploy
# 缓存配置
cache:
paths:
- .gradle/wrapper
- .gradle/caches
# 编译项目 # 编译项目
build-job: build-job:
stage: build stage: build
image: java:8 # 缓存配置
cache:
paths:
- /root/.gradle/cache/
- /root/.gradle/.tmp/
- /root/.m2/
image: $REGISTRY_REMOTE/jdk-17
only: only:
- beta
- preview
- tags - tags
# 使用 CI Runner,在 GitLab-Runner 中注册好的 Runner # 使用 CI Runner,在 GitLab-Runner 中注册好的 Runner
tags: tags:
- CI - YR-CI
before_script: before_script:
- chmod +x ./gradlew - chmod +x ./gradlew
script: script:
- ./gradlew assemble - ./gradlew app:assemble -Dskip-hooks
artifacts: artifacts:
# 配置构建结果过期时间 # 配置构建结果过期时间
expire_in: 1 week expire_in: 1 day
# 保留目录 # 保留目录
paths: paths:
- build/libs/*.jar - app/build/libs/*.jar
# 发布,在本地构建镜像并推送到发布环境的镜像库 # 发布,在本地构建镜像并推送到发布环境的镜像库
deploy-job: deploy-job:
stage: deploy stage: deploy
image: docker:latest image: $REGISTRY_REMOTE/docker
# 部署依赖编译 # 部署依赖编译
dependencies: dependencies:
- build-job - build-job
only: only:
- tags - beta
# 使用 CD Runner,在 GitLab-Runner 中注册好的 Runner(此处配置成使用宿主环境构建) # 使用 CD Runner,在 GitLab-Runner 中注册好的 Runner(此处配置成使用宿主环境构建)
tags: tags:
- CD - YR-CD
script: script:
# 基于 Dockerfile 构建镜像 # 基于 Dockerfile 构建镜像
- docker build -t $TAG . - docker build -t $TAG .
# 登录到发布环境的私服 # 尝试删除上一个容器
- docker login -u $REGISTRY_REMOTE_USER -p $REGISTRY_REMOTE_PASSWORD https://$REGISTRY_REMOTE - id=$(docker ps -aqf name=$CONTAINER_NAME) && [ "$id" ] && docker rm -f $id || true
# 将刚刚构建的镜像推送到私服 # 在本地运行构建好的镜像
- docker push $TAG - docker run -d --name $CONTAINER_NAME -p $EXPOSE_PORT:8181 -p 9999:9999 $TAG
variables: variables:
# 读取 GitLab CI/CD 配置的 Secret variables
REGISTRY_REMOTE: $REGISTRY_REMOTE
REGISTRY_REMOTE_USER: $REGISTRY_REMOTE_USER
REGISTRY_REMOTE_PASSWORD: $REGISTRY_REMOTE_PASSWORD
# 设置镜像 tag,使用 git tag 标识作为镜像 tag # 设置镜像 tag,使用 git tag 标识作为镜像 tag
TAG: ${REGISTRY_REMOTE}/kshg/kshg-api:${CI_BUILD_REF_NAME} TAG: $REGISTRY_REMOTE/basic/$CONTAINER_NAME:$CI_BUILD_REF_NAME
# 指定基础镜像,在其上进行定制 # 指定基础镜像,在其上进行定制
FROM java:8 FROM localhost:18500/jdk-17
# 维护者信息 # 维护者信息
MAINTAINER ifzm <fangzhimin@yiring.com> MAINTAINER ifzm <fangzhimin@yiring.com>
...@@ -14,10 +14,10 @@ COPY app/build/libs/app-0.0.1-SNAPSHOT.jar app.jar ...@@ -14,10 +14,10 @@ COPY app/build/libs/app-0.0.1-SNAPSHOT.jar app.jar
# bash方式执行,使 app.jar 可访问 # bash方式执行,使 app.jar 可访问
# RUN新建立一层,在其上执行这些命令,执行结束后, commit 这一层的修改,构成新的镜像。 # RUN新建立一层,在其上执行这些命令,执行结束后, commit 这一层的修改,构成新的镜像。
RUN bash -c "touch /app.jar" # RUN bash -c "touch /app.jar"
# 声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务 # 声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务
EXPOSE 8181 EXPOSE 8181 9999
# 指定容器启动程序及参数 <ENTRYPOINT> "<CMD>" # 指定容器启动程序及参数 <ENTRYPOINT> "<CMD>"
ENTRYPOINT ["java", "-jar", "app.jar", "--spring.profiles.active=prod"] ENTRYPOINT ["java", "-jar", "app.jar", "--spring.profiles.active=beta"]
...@@ -38,3 +38,4 @@ ...@@ -38,3 +38,4 @@
> 引用 > 引用
1. [JTS](https://github.com/locationtech/jts) 1. [JTS](https://github.com/locationtech/jts)
2. [GeoTools](http://docs.geotools.org/) 2. [GeoTools](http://docs.geotools.org/)
3. [PostGIS](https://blog.csdn.net/qq_27816785/article/details/124540160)
...@@ -6,6 +6,7 @@ import com.yiring.common.annotation.FieldMapping; ...@@ -6,6 +6,7 @@ import com.yiring.common.annotation.FieldMapping;
import com.yiring.common.domain.BasicEntity; import com.yiring.common.domain.BasicEntity;
import java.io.Serial; import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import java.time.LocalDateTime;
import javax.persistence.*; import javax.persistence.*;
import lombok.*; import lombok.*;
import lombok.experimental.FieldDefaults; import lombok.experimental.FieldDefaults;
...@@ -107,6 +108,9 @@ public class LocationTag extends BasicEntity implements Serializable { ...@@ -107,6 +108,9 @@ public class LocationTag extends BasicEntity implements Serializable {
@Comment("是否在厂外") @Comment("是否在厂外")
Boolean out; Boolean out;
@Comment("运动状态变更时间")
LocalDateTime silentStatusUpdateTime;
@SuppressWarnings({ "unused" }) @SuppressWarnings({ "unused" })
public enum Type { public enum Type {
BTT01("蓝牙人员定位卡"), BTT01("蓝牙人员定位卡"),
......
...@@ -104,11 +104,11 @@ public class MockPositionMessageJob { ...@@ -104,11 +104,11 @@ public class MockPositionMessageJob {
} }
private String mockTag() { private String mockTag() {
return "BTT33333331"; return "BTT34070736";
} }
private Long mockAreaId() { private Long mockAreaId() {
return 10019L; return 1L;
} }
private JSONObject mockPositionMessage(JSONObject extra) { private JSONObject mockPositionMessage(JSONObject extra) {
...@@ -128,6 +128,7 @@ public class MockPositionMessageJob { ...@@ -128,6 +128,7 @@ public class MockPositionMessageJob {
params.put("volt", 3650); params.put("volt", 3650);
params.put("voltUnit", "mV"); params.put("voltUnit", "mV");
params.put("floor", 1); params.put("floor", 1);
params.put("out", false);
params.putAll(extra); params.putAll(extra);
JSONObject body = new JSONObject(); JSONObject body = new JSONObject();
......
...@@ -12,11 +12,11 @@ import com.yiring.app.domain.log.ZyRealtimeLog; ...@@ -12,11 +12,11 @@ import com.yiring.app.domain.log.ZyRealtimeLog;
import com.yiring.app.domain.log.ZyRealtimeLogRepository; import com.yiring.app.domain.log.ZyRealtimeLogRepository;
import com.yiring.app.param.key.KeyAlarmAddParam; import com.yiring.app.param.key.KeyAlarmAddParam;
import com.yiring.app.service.message.PositionMessageService; import com.yiring.app.service.message.PositionMessageService;
import com.yiring.app.stomp.message.PositionMessage;
import com.yiring.app.util.GeoUtils; import com.yiring.app.util.GeoUtils;
import com.yiring.auth.domain.dept.Department; import com.yiring.auth.domain.dept.Department;
import com.yiring.auth.domain.user.User; import com.yiring.auth.domain.user.User;
import com.yiring.auth.domain.user.UserRepository; import com.yiring.auth.domain.user.UserRepository;
import com.yiring.common.annotation.Times;
import java.time.Instant; import java.time.Instant;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneId; import java.time.ZoneId;
...@@ -79,16 +79,19 @@ public class PositionMessageServiceImpl implements PositionMessageService { ...@@ -79,16 +79,19 @@ public class PositionMessageServiceImpl implements PositionMessageService {
@Resource @Resource
UserRepository userRepository; UserRepository userRepository;
@Times("Message Consume")
@Override @Override
public void consume(String message) { public void consume(String message) {
// 将消息转换成 JSON 格式 // 将消息转换成 JSON 格式
JSONObject info = JSON.parseObject(message); JSONObject info = JSON.parseObject(message);
log.info("Receiver Message: {}", info); log.debug("Receiver Message: {}", info);
// 解构消息内容 // 解构消息内容
JSONObject data = info.getJSONObject("params"); JSONObject data = info.getJSONObject("params");
String method = info.getString("method"); String method = info.getString("method");
if (method == null) {
log.warn("Unknown Message: {}", info);
return;
}
// 记录日志 // 记录日志
ZyRealtimeLog realtimeLog = ZyRealtimeLog.builder().method(method).raw(info).build(); ZyRealtimeLog realtimeLog = ZyRealtimeLog.builder().method(method).raw(info).build();
...@@ -118,13 +121,6 @@ public class PositionMessageServiceImpl implements PositionMessageService { ...@@ -118,13 +121,6 @@ public class PositionMessageServiceImpl implements PositionMessageService {
// TODO // TODO
log.info("Position Message: {}", data); log.info("Position Message: {}", data);
// 包装消息
// TODO
// 1. 解析消息内容,进行围栏、出入标识判断等处理,将定位记录录入数据库
// 2. 创建一条需要进行消息推送的记录
// 3. 将记录推送的消息推送模块
// 4. 检查是否触发围栏告警,记录告警数据,并推送消息
// 查询定位标签 // 查询定位标签
String tagId = data.getString("tagId"); String tagId = data.getString("tagId");
Example<LocationTag> example = Example.of(LocationTag.builder().code(tagId).build()); Example<LocationTag> example = Example.of(LocationTag.builder().code(tagId).build());
...@@ -174,98 +170,158 @@ public class PositionMessageServiceImpl implements PositionMessageService { ...@@ -174,98 +170,158 @@ public class PositionMessageServiceImpl implements PositionMessageService {
locationLog.setPoint(point); locationLog.setPoint(point);
// 定位信标 // 定位信标
Set<String> beaconCodes = Arrays String beacons = data.getString("beacons");
.stream(data.getString("beacons").split(",")) if (beacons != null) {
.map(beacon -> beacon.replaceAll("\\(.*\\)", "")) Set<String> beaconCodes = Arrays
.stream(beacons.split(","))
.map(beacon -> beacon.replaceAll("\\(.*\\)", ""))
.collect(Collectors.toSet());
locationLog.setBeacons(new JSONArray().fluentAddAll(beaconCodes));
}
// 查询所有围栏信息
List<LocationFence> fences = locationFenceRepository.findAll();
// 找出当前定位在哪些围栏内(同楼层,经纬度范围内)
Set<Long> fenceIds = fences
.stream()
.filter(fence -> fence.getMapId().equals(locationLog.getAreaId()) && fence.getGeometry().contains(point))
.map(LocationFence::getId)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
locationLog.setBeacons(new JSONArray().fluentAddAll(beaconCodes)); locationLog.setFences(new JSONArray().fluentAddAll(fenceIds));
// 计算出入标记(围栏、区域) // 查询出所有的区域
List<LocationTurnover> turnovers = new ArrayList<>(); List<District> districts = districtRepository.findAll();
// 找出当前定位在哪些围栏内(同楼层,经纬度范围内)
Set<Long> districtIds = districts
.stream()
.filter(district -> point.within(district.getGeometry()))
.map(District::getId)
.collect(Collectors.toSet());
locationLog.setDistricts(new JSONArray().fluentAddAll(districtIds));
// 查询定位在围栏内的围栏信息(同时根据地图区域查询,用来区分不同楼层的围栏) // 找出区域与围栏配置的最大防抖时间
List<LocationFence> fences = locationFenceRepository.findByGeometryContains(locationLog.getAreaId(), point); Optional<LocationFence> maxThresholdFence = fences
Set<Long> fenceIds = fences.stream().map(LocationFence::getId).collect(Collectors.toSet()); .stream()
locationLog.setFences(new JSONArray().fluentAddAll(fenceIds)); .max(Comparator.comparing(LocationFence::getThreshold));
// 计算围栏进出 Optional<District> maxThresholdDistrict = districts
for (LocationFence fence : fences) { .stream()
// 查询当前围栏的防抖时间内的所有定位记录 .max(Comparator.comparing(District::getDebouncingDuration));
List<LocationLog> logs = findByTagTimeIdAndThreshold(id, fence.getThreshold()); Optional<Integer> maxThreshold = Stream
List<JSONArray> list = logs.stream().map(LocationLog::getFences).toList(); .of(
if (list.isEmpty()) { maxThresholdFence.map(LocationFence::getThreshold).orElse(0),
continue; maxThresholdDistrict.map(District::getDebouncingDuration).orElse(0)
)
.max(Integer::compareTo);
if (maxThreshold.get() > 0) {
// 查询最大防抖时间段内的定位日志记录
List<LocationLog> logs = findByTagTimeIdAndThreshold(id, maxThreshold.get());
// 计算出入标记(围栏、区域)
List<LocationTurnover> turnovers = new ArrayList<>();
// 统计所有围栏
turnovers.addAll(statisticalTurnovers(fences, id, logs));
// 统计所有区域
turnovers.addAll(statisticalTurnovers(districts, id, logs));
// 写入围栏/区域进出记录
if (!turnovers.isEmpty()) {
locationTurnoverRepository.saveAll(turnovers);
} }
}
// 检查是否进入区域 // 写入定位数据
Boolean isEnter = checkEnter(list, fence.getId()); locationLogRepository.saveAndFlush(locationLog);
if (isEnter != null) {
// 检查是否为重复进入/退出
Optional<LocationTurnover> enter = findRepeatTurnoverRecord(
fence.getId(),
LocationTurnover.Type.FENCE,
id.getTag(),
isEnter
);
if (enter.isEmpty()) {
// 尝试将上一次记录标记为非最新记录
trySetPrevTurnoverExpired(fence.getId(), LocationTurnover.Type.FENCE, id.getTag());
// 当前标签进入/离开围栏的动作标记为最新记录 // 更新定位标签卡状态信息
LocationTurnover turnover = LocationTurnover if (!tag.getSilent().equals(locationLog.getSilent())) {
.builder() // 如果运动状态发生变更,记录状态变更时间
.enter(isEnter) tag.setSilentStatusUpdateTime(id.getTime());
.type(LocationTurnover.Type.FENCE) }
.sourceId(fence.getId()) tag.setSilent(locationLog.getSilent());
.tag(id.getTag()) tag.setOut(locationLog.getOut());
.time(id.getTime()) tag.setPoint(locationLog.getPoint());
.user(id.getTag().getUser()) tag.setVolt(locationLog.getVolt());
.isLatest(true) tag.setVoltUnit(locationLog.getVoltUnit());
.build(); locationTagRepository.save(tag);
turnovers.add(turnover);
// 更新围栏内的标签 // 更新围栏内的标签数据
fence.setTags(resetInTags(tag, isEnter, fence.getTags())); locationFenceRepository.saveAll(fences);
// 有人员进出围栏,需要检查是否触发围栏报警规则 // 更新区域内的标签数据
// TODO: 通过定时任务调度异步实现,提高定位消息消费能力 districtRepository.saveAll(districts);
// 1. 判断是否触发围栏报警规则,触发则记录报警记录,同时记录报警记录触发所需推送的消息
// 2. 同时进行 WebSocket 消息推送 // WebSocket 推送定位消息
} // 消息内容需要确定 TODO
PositionMessage message = PositionMessage
.builder()
.time(id.getTime())
.tagId(id.getTag().getId())
.tagCode(id.getTag().getCode())
.point(locationLog.getPoint())
.build();
simpMessagingTemplate.convertAndSend("/topic/position", message);
}
/**
* 统计进出记录
* @param areas 区域/围栏列表
* @param id 定位日志主键
* @param logs 定位日志列表
* @return 进出记录列表
*/
public <E> List<LocationTurnover> statisticalTurnovers(List<E> areas, TagTimeId id, List<LocationLog> logs) {
List<LocationTurnover> turnovers = new ArrayList<>();
// 遍历所有区域/围栏
for (Object area : areas) {
// 收集不同表共同关键信息
LocationTurnover.Type type;
Long sourceId;
int threshold;
if (area instanceof LocationFence fence) {
sourceId = fence.getId();
threshold = fence.getThreshold();
type = LocationTurnover.Type.FENCE;
} else if (area instanceof District district) {
sourceId = district.getId();
threshold = district.getDebouncingDuration();
type = LocationTurnover.Type.DISTRICT;
} else {
continue;
} }
}
// 查询定位在区域内的区域信息 // 获取需要过滤的时间段
List<District> districts = districtRepository.findByGeometryContains(point); LocalDateTime startTime = id.getTime().minusSeconds(threshold);
Set<Long> districtIds = districts.stream().map(District::getId).collect(Collectors.toSet()); LocalDateTime endTime = id.getTime();
locationLog.setDistricts(new JSONArray().fluentAddAll(districtIds));
// 计算区域进出 // 过滤出围栏防抖时间段内的定位日志记录
for (District district : districts) { Set<LocationLog> collect = logs
// 查询当前区域的防抖时间内的所有定位记录 .stream()
List<LocationLog> logs = findByTagTimeIdAndThreshold(id, district.getDebouncingDuration()); .filter(item -> item.getId().getTime().isBefore(endTime) && item.getId().getTime().isAfter(startTime))
List<JSONArray> list = logs.stream().map(LocationLog::getDistricts).toList(); .collect(Collectors.toSet());
if (list.isEmpty()) { // 找出定位记录中围栏集合
LocationTurnover.Type finalType = type;
List<JSONArray> includes = collect
.stream()
.map(item -> LocationTurnover.Type.FENCE.equals(finalType) ? item.getFences() : item.getDistricts())
.toList();
if (includes.isEmpty()) {
continue; continue;
} }
// 检查是否进入区域 // 检查是否进入区域
Boolean isEnter = checkEnter(list, district.getId()); Boolean isEnter = checkEnter(includes, sourceId);
if (isEnter != null) { if (isEnter != null) {
// 检查是否为重复进入/退出 // 检查是否为重复进入/退出(一定是先有进入记录才会有离开记录)
Optional<LocationTurnover> enter = findRepeatTurnoverRecord( Optional<LocationTurnover> repeat = findRepeatTurnoverRecord(sourceId, type, id.getTag(), isEnter);
district.getId(), if (repeat.isEmpty()) {
LocationTurnover.Type.DISTRICT,
id.getTag(),
isEnter
);
if (enter.isEmpty()) {
// 尝试将上一次记录标记为非最新记录 // 尝试将上一次记录标记为非最新记录
trySetPrevTurnoverExpired(district.getId(), LocationTurnover.Type.DISTRICT, id.getTag()); trySetPrevTurnoverExpired(sourceId, type, id.getTag());
// 当前标签进入/离开围栏的动作标记为最新记录 // 记录当前标签进入/离开围栏/区域的动作,标记为最新记录
LocationTurnover turnover = LocationTurnover LocationTurnover turnover = LocationTurnover
.builder() .builder()
.enter(isEnter) .enter(isEnter)
.type(LocationTurnover.Type.DISTRICT) .type(type)
.sourceId(district.getId()) .sourceId(sourceId)
.tag(id.getTag()) .tag(id.getTag())
.time(id.getTime()) .time(id.getTime())
.user(id.getTag().getUser()) .user(id.getTag().getUser())
...@@ -273,41 +329,18 @@ public class PositionMessageServiceImpl implements PositionMessageService { ...@@ -273,41 +329,18 @@ public class PositionMessageServiceImpl implements PositionMessageService {
.build(); .build();
turnovers.add(turnover); turnovers.add(turnover);
// 更新区域内的标签 // 更新范围内的标签信息
district.setTags(resetInTags(tag, isEnter, district.getTags())); if (area instanceof LocationFence fence) {
fence.setTags(resetInTags(id.getTag(), isEnter, fence.getTags()));
} else {
District district = (District) area;
district.setTags(resetInTags(id.getTag(), isEnter, district.getTags()));
}
} }
} }
} }
// 写入定位数据 return turnovers;
locationLogRepository.saveAndFlush(locationLog);
// 更新定位标签卡状态信息
tag.setOut(locationLog.getOut());
tag.setPoint(locationLog.getPoint());
tag.setVolt(locationLog.getVolt());
tag.setVoltUnit(locationLog.getVoltUnit());
tag.setSilent(locationLog.getSilent());
locationTagRepository.save(tag);
// 更新围栏内的标签数据
locationFenceRepository.saveAll(fences);
// 更新区域内的标签数据
districtRepository.saveAll(districts);
// 写入围栏/区域进出记录
locationTurnoverRepository.saveAll(turnovers);
// WebSocket 推送定位消息
// 消息内容需要确定 TODO
JSONObject message = new JSONObject();
message.put("type", "location");
message.put("time", id.getTime());
message.put("tagId", id.getTag().getId());
message.put("tagCode", id.getTag().getCode());
message.put("entityId", locationLog.getPoint());
message.put("point", locationLog.getPoint());
simpMessagingTemplate.convertAndSend("/topic/position", message.toJSONString());
} }
/** /**
...@@ -334,8 +367,8 @@ public class PositionMessageServiceImpl implements PositionMessageService { ...@@ -334,8 +367,8 @@ public class PositionMessageServiceImpl implements PositionMessageService {
* @return true 进入,false 退出,null 未发生变化 * @return true 进入,false 退出,null 未发生变化
*/ */
public Boolean checkEnter(List<JSONArray> array, Long id) { public Boolean checkEnter(List<JSONArray> array, Long id) {
long count = array.stream().filter(ids -> ids != null && ids.contains(id)).count();
Boolean isEnter = null; Boolean isEnter = null;
long count = array.stream().filter(ids -> ids != null && ids.contains(id)).count();
if (count == array.size()) { if (count == array.size()) {
isEnter = true; isEnter = true;
} else if (count == 0) { } else if (count == 0) {
...@@ -371,14 +404,20 @@ public class PositionMessageServiceImpl implements PositionMessageService { ...@@ -371,14 +404,20 @@ public class PositionMessageServiceImpl implements PositionMessageService {
pageable pageable
); );
if (page.getTotalElements() > 0) { long elements = page.getTotalElements();
if (elements == 0 && Boolean.FALSE.equals(enter)) {
// 如果标签在目标区域没有过进入记录,但是确判断为离开,这时需拟一个记录,避免记录一个没有进入确有离开记录的情况
return Optional.of(new LocationTurnover());
}
if (elements > 0) {
// 检查是否重复进入 // 检查是否重复进入
Stream<LocationTurnover> stream = page.get(); if (enter == null || page.get().anyMatch(turnover -> turnover.getEnter() == enter)) {
if (enter == null || stream.anyMatch(turnover -> turnover.getEnter() == enter)) { return page.stream().findFirst();
return stream.findFirst();
} }
} }
// 需要记录
return Optional.empty(); return Optional.empty();
} }
...@@ -397,6 +436,13 @@ public class PositionMessageServiceImpl implements PositionMessageService { ...@@ -397,6 +436,13 @@ public class PositionMessageServiceImpl implements PositionMessageService {
} }
} }
/**
* 重置标签集合
* @param tag 标签
* @param enter 进入/离开
* @param tags 标签集合
* @return 重置后的标签集合
*/
public Set<LocationTag> resetInTags(LocationTag tag, Boolean enter, Set<LocationTag> tags) { public Set<LocationTag> resetInTags(LocationTag tag, Boolean enter, Set<LocationTag> tags) {
if (Boolean.TRUE.equals(enter)) { if (Boolean.TRUE.equals(enter)) {
tags.add(tag); tags.add(tag);
......
/* (C) 2022 YiRing, Inc. */
package com.yiring.app.stomp.message;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
import lombok.*;
import lombok.experimental.FieldDefaults;
import org.locationtech.jts.geom.Point;
/**
* @author Jim
* @version 0.1
* 2022/5/16 19:32
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class PositionMessage implements Serializable {
@Serial
private static final long serialVersionUID = -1806101353088274819L;
@Builder.Default
String type = "position";
LocalDateTime time;
Long tagId;
String tagCode;
Long entityId;
Point point;
}
...@@ -13,6 +13,7 @@ import java.util.Arrays; ...@@ -13,6 +13,7 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import javax.validation.Valid; import javax.validation.Valid;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.geolatte.geom.jts.JTS;
import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.Polygon; import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.io.ParseException; import org.locationtech.jts.io.ParseException;
...@@ -61,12 +62,12 @@ public class ExampleController { ...@@ -61,12 +62,12 @@ public class ExampleController {
} }
@GetMapping("test2") @GetMapping("test2")
public Result<Geometry> test2() throws ParseException { public Result<org.geolatte.geom.Polygon<?>> test2() throws ParseException {
WKTReader reader = new WKTReader(); WKTReader reader = new WKTReader();
Geometry geometry = reader.read( Geometry geometry = reader.read(
"POLYGON((114.13726247683384 22.57453153296995,114.13726253585672 22.57362062876488,114.1379932868094 22.57336379439826,114.13860672275516 22.573820711775532,114.13726247683384 22.57453153296995))" "POLYGON((114.13726247683384 22.57453153296995,114.13726253585672 22.57362062876488,114.1379932868094 22.57336379439826,114.13860672275516 22.573820711775532,114.13726247683384 22.57453153296995))"
); );
Polygon polygon = (Polygon) geometry; Polygon polygon = (Polygon) geometry;
return Result.ok(polygon); return Result.ok(JTS.from(polygon));
} }
} }
# 环境变量
env:
host: 192.168.0.156
spring:
servlet:
multipart:
enabled: true
max-file-size: 50MB
max-request-size: 100MB
datasource:
url: jdbc:postgresql://${env.host}:5432/kshg_app_beta
username: admin
password: 123456
jpa:
database-platform: com.yiring.app.config.dialect.PostgresDialect
open-in-view: true
hibernate:
ddl-auto: update
show-sql: false
properties:
hibernate:
format_sql: true
types.print.banner: false
redis:
database: 10
host: ${env.host}
rabbitmq:
host: ${env.host}
port: 5672
username: admin
password: 123456
virtual-host: beta
data:
redis:
repositories:
enabled: false
# 处理多网卡选举
cloud:
inetutils:
preferred-networks: 192.168.0
# knife4j(swagger)
knife4j:
enable: true
basic:
enable: false
username: admin
password: 123456
setting:
enableOpenApi: false
enableDebug: true
# minio
minio:
access-key: minioadmin
secret-key: minioadmin
end-point: "http://${env.host}:18100"
bucket: kshg
domain: ${minio.endpoint}/${minio.bucket}
# 任务调度
xxl:
job:
### 调度中心部署跟地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册;
admin-addresses: http://${env.host}:8080/xxl-job-admin
### 执行器通讯TOKEN [选填]:非空时启用;
access-token: local
### 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
executor-app-name: kshg-app-beta
### 执行器注册 [选填]:优先使用该配置作为注册地址,为空时使用内嵌服务 ”IP:PORT“ 作为注册地址。从而更灵活的支持容器类型执行器动态IP和动态映射端口问题。
executor-address:
### 执行器IP [选填]:默认为空表示自动获取IP,多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯实用;地址信息用于 "执行器注册" 和 "调度中心请求并触发任务";
executor-ip:
### 执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口;
executor-port:
### 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径;
executor-log-path: /data/applogs/xxl-job/jobhandler
### 执行器日志文件保存天数 [选填] : 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能;
executor-log-retention-days: 30
# 日志
logging:
level:
# sql bind parameter
# org.hibernate.type.descriptor.sql.BasicBinder: trace
org.hibernate.type.descriptor.sql.BasicBinder: error
# 真源定位系统相关配置
zy-config:
host: project.yz-online.com
# RabbitMQ 订阅配置
rabbitmq:
mock: false
enabled: false
host: 123.60.56.5
port: 5672
username: tenant
password: tenant
virtual-host: /
queue-name: tenant_msg_${zy-config.open.client-secret}_${zy-config.open.client-id}
# 开放接口信息配置
open:
api: https://nl.yz-cloud.com/positionApi
client-id: sc22030527
grant-type: client_credentials
client-secret: C18422B9
tenant: sc22030527
# 代理接口信息配置
proxy:
api: https://nl.yz-cloud.com
tenant: sc22030527
# 应用平台账户信息
client:
username: client
password: client@yiring.com
# 管理后台账户信息
manage:
username: manage
password: manage@yiring.com
feign:
httpclient:
enabled: false
okhttp:
enabled: true
...@@ -17,7 +17,7 @@ spring: ...@@ -17,7 +17,7 @@ spring:
open-in-view: true open-in-view: true
hibernate: hibernate:
ddl-auto: update ddl-auto: update
show-sql: true show-sql: false
properties: properties:
hibernate: hibernate:
format_sql: true format_sql: true
...@@ -83,40 +83,41 @@ xxl: ...@@ -83,40 +83,41 @@ xxl:
logging: logging:
level: level:
# sql bind parameter # sql bind parameter
org.hibernate.type.descriptor.sql.BasicBinder: trace # org.hibernate.type.descriptor.sql.BasicBinder: trace
# org.hibernate.type.descriptor.sql.BasicBinder: error org.hibernate.type.descriptor.sql.BasicBinder: error
# 真源定位系统相关配置 # 真源定位系统相关配置
zy-config: zy-config:
host: project.yz-online.com host: project.yz-online.com
# RabbitMQ 订阅配置 # RabbitMQ 订阅配置
rabbitmq: rabbitmq:
mock: false
enabled: false enabled: false
host: ${zy-config.host} host: 123.60.56.5
port: 672 port: 5672
username: admin username: tenant
password: admin password: tenant
virtual-host: / virtual-host: /
queue-name: tenant_msg_${zy-config.open.client-secret}_${zy-config.open.client-id}_mock queue-name: tenant_msg_${zy-config.open.client-secret}_${zy-config.open.client-id}
# 开放接口信息配置 # 开放接口信息配置
open: open:
api: http://${zy-config.host}:789/positionApi api: https://nl.yz-cloud.com/positionApi
client-id: sc21080400 client-id: sc22030527
grant-type: client_credentials grant-type: client_credentials
client-secret: 12A14FDC client-secret: C18422B9
tenant: sc21080400 tenant: sc22030527
# 代理接口信息配置 # 代理接口信息配置
proxy: proxy:
api: https://nl.yz-cloud.com api: https://nl.yz-cloud.com
tenant: ts00000006 tenant: sc22030527
# 应用平台账户信息 # 应用平台账户信息
client: client:
username: test1234 username: client
password: 123456 password: client@yiring.com
# 管理后台账户信息 # 管理后台账户信息
manage: manage:
username: test123 username: manage
password: test123 password: manage@yiring.com
feign: feign:
httpclient: httpclient:
......
...@@ -5,7 +5,7 @@ server: ...@@ -5,7 +5,7 @@ server:
spring: spring:
application: application:
name: "kshg-api" name: "kshg-api-beta"
profiles: profiles:
include: auth, conf-patch include: auth, conf-patch
active: dev active: dev
......
...@@ -14,15 +14,16 @@ dependencies { ...@@ -14,15 +14,16 @@ dependencies {
implementation fileTree(dir: project.rootDir.getPath() + '\\libs', includes: ['*jar']) implementation fileTree(dir: project.rootDir.getPath() + '\\libs', includes: ['*jar'])
// JTS 几何对象操作库
implementation "org.locationtech.jts:jts-core:${jtsVersion}"
// https://mvnrepository.com/artifact/org.n52.jackson/jackson-datatype-jts/1.2.10 // https://mvnrepository.com/artifact/org.n52.jackson/jackson-datatype-jts/1.2.10
implementation("org.n52.jackson:jackson-datatype-jts:1.2.10") { implementation("org.n52.jackson:jackson-datatype-jts:1.2.10") {
exclude group: 'com.fasterxml.jackson.core', module: 'jackson-databind' exclude group: 'com.fasterxml.jackson.core'
exclude group: 'com.fasterxml.jackson.core', module: 'jackson-annotations' exclude group: 'org.locationtech.jts'
exclude group: 'com.fasterxml.jackson.core', module: 'jackson-core' }
exclude group: 'org.locationtech.jts', module: 'jts-core'
// JTS 几何对象操作库
implementation "org.geolatte:geolatte-geom:${geolatteVersion}"
implementation("org.geolatte:geolatte-geojson:${geolatteVersion}") {
exclude group: 'com.fasterxml.jackson.core'
} }
} }
...@@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; ...@@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import javax.annotation.Resource; import javax.annotation.Resource;
import org.geolatte.geom.json.GeolatteGeomModule;
import org.n52.jackson.datatype.jts.JtsModule; import org.n52.jackson.datatype.jts.JtsModule;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
...@@ -35,6 +36,7 @@ public class JacksonConfig { ...@@ -35,6 +36,7 @@ public class JacksonConfig {
mapper.registerModule(javaTimeModule); mapper.registerModule(javaTimeModule);
// JTS Geometry support // JTS Geometry support
mapper.registerModule(new JtsModule()); mapper.registerModule(new JtsModule());
mapper.registerModule(new GeolatteGeomModule());
// feat: add AdminServerModule // feat: add AdminServerModule
return mapper; return mapper;
} }
......
/* (C) 2022 YiRing, Inc. */
package com.yiring.common.core.redis;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
/**
* redis缓存 工具类
*
* @author tzl
* 2022/4/13 15:36
*/
@Component
public class RedisCache {
@Autowired
public RedisTemplate redisTemplate;
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout) {
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit) {
return Boolean.TRUE.equals(redisTemplate.expire(key, timeout, unit));
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key) {
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key) {
return Boolean.TRUE.equals(redisTemplate.delete(key));
}
/**
* 删除集合对象
*
* @param collection 多个对象
* @return
*/
public long deleteObject(final Collection collection) {
return redisTemplate.delete(collection);
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> long setCacheList(final String key, final List<T> dataList) {
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public <T> List<T> getCacheList(final String key) {
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
for (T t : dataSet) {
setOperation.add(t);
}
return setOperation;
}
/**
* 获得缓存的set
*
* @param key String
* @return
*/
public <T> Set<T> getCacheSet(final String key) {
return redisTemplate.opsForSet().members(key);
}
/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 获得缓存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(final String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public <T> T getCacheMapValue(final String key, final String hKey) {
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 删除Hash中的数据
*
* @param key
* @param hKey
*/
public void delCacheMapValue(final String key, final String hKey) {
HashOperations hashOperations = redisTemplate.opsForHash();
hashOperations.delete(key, hKey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<String> keys(final String pattern) {
return redisTemplate.keys(pattern);
}
}
...@@ -40,7 +40,7 @@ import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc; ...@@ -40,7 +40,7 @@ import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
*/ */
@Slf4j @Slf4j
@Profile(value = { "default", "dev", "mock", "test", "preview" }) @Profile("!prod")
@EnableSwagger2WebMvc @EnableSwagger2WebMvc
@Configuration @Configuration
@Import(BeanValidatorPluginsConfiguration.class) @Import(BeanValidatorPluginsConfiguration.class)
......
...@@ -10,9 +10,9 @@ buildscript { ...@@ -10,9 +10,9 @@ buildscript {
// https://mvnrepository.com/artifact/io.swagger/swagger-annotations // https://mvnrepository.com/artifact/io.swagger/swagger-annotations
swaggerAnnotationsVersion = '1.6.6' swaggerAnnotationsVersion = '1.6.6'
// https://mvnrepository.com/artifact/cn.dev33/sa-token-spring-boot-starter // https://mvnrepository.com/artifact/cn.dev33/sa-token-spring-boot-starter
saTokenVersion = '1.29.1.trial' saTokenVersion = '1.30.0'
// https://mvnrepository.com/artifact/cn.hutool/hutool-all // https://mvnrepository.com/artifact/cn.hutool/hutool-all
hutoolVersion = '5.8.0.M3' hutoolVersion = '5.8.0'
// https://mvnrepository.com/artifact/com.alibaba/fastjson // https://mvnrepository.com/artifact/com.alibaba/fastjson
fastJsonVersion = '1.2.80' fastJsonVersion = '1.2.80'
// https://mvnrepository.com/artifact/com.xuxueli/xxl-job-core // https://mvnrepository.com/artifact/com.xuxueli/xxl-job-core
...@@ -20,7 +20,7 @@ buildscript { ...@@ -20,7 +20,7 @@ buildscript {
// https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp // https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp
okhttpVersion = '4.9.3' okhttpVersion = '4.9.3'
// https://mvnrepository.com/artifact/io.minio/minio // https://mvnrepository.com/artifact/io.minio/minio
minioVersion = '8.3.8' minioVersion = '8.4.1'
// https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter // https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter
mybatisPlusVersion = '3.5.1' mybatisPlusVersion = '3.5.1'
// https://mvnrepository.com/artifact/org.hibernate/hibernate-spatial // https://mvnrepository.com/artifact/org.hibernate/hibernate-spatial
...@@ -33,6 +33,8 @@ buildscript { ...@@ -33,6 +33,8 @@ buildscript {
myexcelVersion = '4.1.1' myexcelVersion = '4.1.1'
// https://mvnrepository.com/artifact/io.github.openfeign/feign-okhttp // https://mvnrepository.com/artifact/io.github.openfeign/feign-okhttp
feignOkhttpVersion= '11.8' feignOkhttpVersion= '11.8'
// https://mvnrepository.com/artifact/org.geolatte/geolatte-geom
geolatteVersion = '1.8.2'
} }
} }
...@@ -133,13 +135,25 @@ subprojects { ...@@ -133,13 +135,25 @@ subprojects {
} }
} }
// GitHook pre-commit (spotless, spotbugs) task hooks() {
def hook = new File("$rootProject.projectDir/.git/hooks/pre-commit") try {
hook.text = """#!/bin/bash // GitHook pre-commit (spotless, spotbugs)
#set -x def hook = new File("$rootProject.projectDir/.git/hooks/pre-commit")
hook.text = """#!/bin/bash
./gradlew spotlessCheck #set -x
./gradlew spotlessCheck
RESULT=\$?
exit \$RESULT
"""
} catch (ignored) {}
}
RESULT=\$? gradle.getTaskGraph().whenReady {
exit \$RESULT def skipHooks = gradle.startParameter.getSystemPropertiesArgs().containsKey('skip-hooks')
""" printf("skip-hooks: %s", skipHooks)
if (!skipHooks) {
hooks
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论