mirror of
https://gitee.com/pnoker/iot-dc3.git
synced 2026-08-28 22:21:16 +08:00
Merge pull request #164 from pnoker/chore/promote-develop-main-3
chore: promote develop → main (quality #160-163)
This commit is contained in:
+2
-2
@@ -28,7 +28,7 @@ import io.github.pnoker.common.agentic.tools.TenantTool;
|
||||
import io.github.pnoker.common.agentic.tools.UserTool;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
|
||||
import org.springframework.ai.chat.client.advisor.ToolCallAdvisor;
|
||||
import org.springframework.ai.chat.client.advisor.ToolCallingAdvisor;
|
||||
import org.springframework.ai.chat.client.advisor.api.Advisor;
|
||||
import org.springframework.ai.chat.memory.ChatMemory;
|
||||
import org.springframework.ai.chat.memory.ChatMemoryRepository;
|
||||
@@ -95,7 +95,7 @@ public class ChatClientConfig {
|
||||
|
||||
@Bean
|
||||
public Advisor agenticToolCallAdvisor(ToolCallingManager toolCallingManager) {
|
||||
return ToolCallAdvisor.builder()
|
||||
return ToolCallingAdvisor.builder()
|
||||
.toolCallingManager(toolCallingManager)
|
||||
.advisorOrder(Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER + 100)
|
||||
.build();
|
||||
|
||||
+3
-2
@@ -39,6 +39,7 @@ import io.github.pnoker.common.enums.EnableFlagEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import static io.github.pnoker.common.utils.LogSanitizer.sanitize;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Strings;
|
||||
import org.springframework.ai.anthropic.AnthropicChatModel;
|
||||
import org.springframework.ai.anthropic.AnthropicChatOptions;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
@@ -123,7 +124,7 @@ public class ChatClientFactory {
|
||||
}
|
||||
String fallback = StringUtils.trimToNull(fallbackModel);
|
||||
if (StringUtils.isNotBlank(fallback)) {
|
||||
if (StringUtils.isNotBlank(candidate) && !StringUtils.equals(candidate, fallback)) {
|
||||
if (StringUtils.isNotBlank(candidate) && !Strings.CS.equals(candidate, fallback)) {
|
||||
log.warn("Agentic requested model is not configured, falling back to Spring AI model, requestedModel={}, fallbackModel={}",
|
||||
sanitize(candidate), sanitize(fallback));
|
||||
}
|
||||
@@ -199,7 +200,7 @@ public class ChatClientFactory {
|
||||
if (Objects.nonNull(config)) {
|
||||
return Boolean.TRUE.equals(config.getToolCall());
|
||||
}
|
||||
return StringUtils.isNotBlank(fallbackModel) && StringUtils.equals(model, fallbackModel)
|
||||
return StringUtils.isNotBlank(fallbackModel) && Strings.CS.equals(model, fallbackModel)
|
||||
&& properties.isFallbackToolCallingEnabled();
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -83,7 +83,7 @@ public class AgenticPromptBuilder {
|
||||
if (!prepared.toolCallingEnabled()) {
|
||||
return promptSpec;
|
||||
}
|
||||
return promptSpec.toolCallbacks(toolCallbackProvider).advisors(toolCallAdvisor);
|
||||
return promptSpec.tools(toolCallbackProvider).advisors(toolCallAdvisor);
|
||||
}
|
||||
|
||||
private ChatClient.ChatClientRequestSpec applyRequestOptions(ChatClient.ChatClientRequestSpec promptSpec,
|
||||
|
||||
+2
-2
@@ -53,7 +53,7 @@ public class AgenticRunTrace {
|
||||
|
||||
public void recordPendingEvent(AgenticRunEvent event) {
|
||||
if (Objects.nonNull(event)) {
|
||||
pendingEvents.offer(event);
|
||||
pendingEvents.add(event);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ public class AgenticRunTrace {
|
||||
|
||||
public void recordPendingVisualization(AgenticVisualizationSpec visualization) {
|
||||
if (Objects.nonNull(visualization)) {
|
||||
pendingVisualizations.offer(visualization);
|
||||
pendingVisualizations.add(visualization);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -107,7 +107,7 @@ public class AgenticToolContextUtil {
|
||||
Object value = getContextValue(toolContext, AgenticConstant.ToolContextKey.VISUALIZATIONS);
|
||||
if (value instanceof Queue<?>) {
|
||||
Queue<AgenticVisualizationSpec> queue = (Queue<AgenticVisualizationSpec>) value;
|
||||
visualizations.stream().filter(Objects::nonNull).forEach(queue::offer);
|
||||
visualizations.stream().filter(Objects::nonNull).forEach(queue::add);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ public class AgenticToolContextUtil {
|
||||
}
|
||||
Object value = getContextValue(toolContext, AgenticConstant.ToolContextKey.RUN_EVENTS);
|
||||
if (value instanceof Queue<?>) {
|
||||
((Queue<AgenticRunEvent>) value).offer(event);
|
||||
((Queue<AgenticRunEvent>) value).add(event);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -38,7 +38,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.ai.chat.client.advisor.ToolCallAdvisor;
|
||||
import org.springframework.ai.chat.client.advisor.ToolCallingAdvisor;
|
||||
import org.springframework.ai.chat.client.advisor.api.Advisor;
|
||||
import org.springframework.ai.model.tool.ToolCallingManager;
|
||||
import org.springframework.ai.tool.ToolCallback;
|
||||
@@ -139,8 +139,8 @@ class ChatClientConfigTest {
|
||||
void agenticToolCallAdvisorRunsAfterMemoryAdvisor() {
|
||||
ChatClientConfig config = new ChatClientConfig();
|
||||
assertThat(config.agenticToolCallAdvisor(ToolCallingManager.builder().build()))
|
||||
.isInstanceOf(ToolCallAdvisor.class)
|
||||
.extracting(ToolCallAdvisor.class::cast)
|
||||
.isInstanceOf(ToolCallingAdvisor.class)
|
||||
.extracting(ToolCallingAdvisor.class::cast)
|
||||
.satisfies(advisor -> {
|
||||
assertThat(advisor.getName()).isEqualTo("Tool Calling Advisor");
|
||||
assertThat(advisor.getOrder()).isEqualTo(Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER + 100);
|
||||
|
||||
+3
-3
@@ -76,12 +76,12 @@ class AgenticPromptBuilderTest {
|
||||
|
||||
@Test
|
||||
void buildAttachesToolCallbacksAndExplicitToolCallAdvisorWhenToolCallingIsEnabled() {
|
||||
when(promptSpec.toolCallbacks(toolCallbackProvider)).thenReturn(promptSpec);
|
||||
when(promptSpec.tools(toolCallbackProvider)).thenReturn(promptSpec);
|
||||
when(promptSpec.advisors(toolCallAdvisor)).thenReturn(promptSpec);
|
||||
|
||||
promptBuilder.build(prepared(true));
|
||||
|
||||
verify(promptSpec).toolCallbacks(toolCallbackProvider);
|
||||
verify(promptSpec).tools(toolCallbackProvider);
|
||||
verify(promptSpec).advisors(toolCallAdvisor);
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ class AgenticPromptBuilderTest {
|
||||
|
||||
@Test
|
||||
void buildAdvertisesPlatformToolsWhenToolCallingIsEnabled() {
|
||||
when(promptSpec.toolCallbacks(toolCallbackProvider)).thenReturn(promptSpec);
|
||||
when(promptSpec.tools(toolCallbackProvider)).thenReturn(promptSpec);
|
||||
when(promptSpec.advisors(toolCallAdvisor)).thenReturn(promptSpec);
|
||||
|
||||
promptBuilder.build(prepared(true));
|
||||
|
||||
+3
@@ -1394,6 +1394,9 @@ public class OAuthMcpRuntimeServiceImpl implements OAuthMcpRuntimeService {
|
||||
}
|
||||
|
||||
private Map<String, Object> orderedMap(Object... values) {
|
||||
if (values.length % 2 != 0) {
|
||||
throw new IllegalArgumentException("orderedMap requires key-value pairs (even number of arguments)");
|
||||
}
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
for (int i = 0; i < values.length; i += 2) {
|
||||
map.put(String.valueOf(values[i]), values[i + 1]);
|
||||
|
||||
-2
@@ -164,7 +164,6 @@ public class OAuthController {
|
||||
/**
|
||||
* Exchange an authorization code (or supported grant) for access and refresh tokens.
|
||||
*
|
||||
* @param body form-encoded token request parameters (grant_type, code, redirect_uri, code_verifier, etc.)
|
||||
* @param authorizationHeader optional HTTP Basic client credentials
|
||||
* @return a 200 response carrying the token JSON; OAuth protocol errors map to the spec status
|
||||
*/
|
||||
@@ -191,7 +190,6 @@ public class OAuthController {
|
||||
/**
|
||||
* Revoke a previously issued access or refresh token.
|
||||
*
|
||||
* @param body form-encoded revocation parameters (token and token_type_hint)
|
||||
* @param authorizationHeader optional HTTP Basic client credentials
|
||||
* @return a 200 response confirming the revocation; OAuth protocol errors map to the spec status
|
||||
*/
|
||||
|
||||
+4
-4
@@ -111,12 +111,12 @@ public class McpOpenApiAggregator {
|
||||
if (!paths.isObject()) {
|
||||
continue;
|
||||
}
|
||||
paths.fields().forEachRemaining(pathEntry -> {
|
||||
paths.properties().forEach(pathEntry -> {
|
||||
JsonNode pathItem = pathEntry.getValue();
|
||||
if (!pathItem.isObject()) {
|
||||
return;
|
||||
}
|
||||
pathItem.fields().forEachRemaining(opEntry -> {
|
||||
pathItem.properties().forEach(opEntry -> {
|
||||
String method = opEntry.getKey().toUpperCase();
|
||||
JsonNode operation = opEntry.getValue();
|
||||
if (!operation.isObject()) {
|
||||
@@ -192,7 +192,7 @@ public class McpOpenApiAggregator {
|
||||
JsonNode resolved = resolveRefs(bodySchema, root, 0);
|
||||
JsonNode bodyProps = resolved.path("properties");
|
||||
if (bodyProps.isObject()) {
|
||||
bodyProps.fields().forEachRemaining(f -> properties.set(f.getKey(), f.getValue()));
|
||||
bodyProps.properties().forEach(f -> properties.set(f.getKey(), f.getValue()));
|
||||
resolved.path("required").forEach(required::add);
|
||||
}
|
||||
}
|
||||
@@ -251,7 +251,7 @@ public class McpOpenApiAggregator {
|
||||
return node;
|
||||
}
|
||||
ObjectNode copy = objectMapper.createObjectNode();
|
||||
node.fields().forEachRemaining(e -> copy.set(e.getKey(), resolveRefs(e.getValue(), root, depth + 1)));
|
||||
node.properties().forEach(e -> copy.set(e.getKey(), resolveRefs(e.getValue(), root, depth + 1)));
|
||||
return copy;
|
||||
}
|
||||
if (node.isArray()) {
|
||||
|
||||
+2
-1
@@ -241,7 +241,8 @@ public class GroupServiceImpl implements GroupService {
|
||||
}
|
||||
}
|
||||
|
||||
Byte parentLevel = Objects.isNull(parent.getGroupLevel()) ? 0 : parent.getGroupLevel();
|
||||
Byte groupLevel = parent.getGroupLevel();
|
||||
byte parentLevel = groupLevel == null ? 0 : groupLevel;
|
||||
entityBO.setGroupLevel((byte) (parentLevel + 1));
|
||||
}
|
||||
|
||||
|
||||
-2
@@ -80,13 +80,11 @@ public class AlarmRuleTriggerServiceImpl implements AlarmRuleTriggerService {
|
||||
return;
|
||||
}
|
||||
|
||||
List<PointValueBO> valid = new ArrayList<>();
|
||||
List<RuleFact> facts = new ArrayList<>();
|
||||
for (PointValueBO pointValue : pointValues) {
|
||||
if (Objects.isNull(pointValue) || !isValidId(pointValue.getTenantId()) || !isValidId(pointValue.getPointId())) {
|
||||
continue;
|
||||
}
|
||||
valid.add(pointValue);
|
||||
LocalDateTime ts = factTime(pointValue.getCreateTime());
|
||||
windowSampleBuffer.append(
|
||||
WindowSampleKey.of(pointValue.getTenantId(), AlarmTargetTypeEnum.POINT, pointValue.getPointId()),
|
||||
|
||||
+4
-3
@@ -20,6 +20,7 @@ package io.github.pnoker.common.data.biz.alarm;
|
||||
import io.github.pnoker.common.constant.common.BaseConstant;
|
||||
import io.github.pnoker.common.entity.ext.RuleExt;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Strings;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Objects;
|
||||
@@ -149,9 +150,9 @@ public final class ConditionEvaluator {
|
||||
String actual = Objects.toString(value, "");
|
||||
String expected = Objects.toString(condition.getExpected(), "");
|
||||
return switch (operator) {
|
||||
case "==", "eq" -> StringUtils.equals(actual, expected);
|
||||
case "!=", "ne" -> !StringUtils.equals(actual, expected);
|
||||
case "contains" -> StringUtils.contains(actual, expected);
|
||||
case "==", "eq" -> Strings.CS.equals(actual, expected);
|
||||
case "!=", "ne" -> !Strings.CS.equals(actual, expected);
|
||||
case "contains" -> Strings.CS.contains(actual, expected);
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
+3
-2
@@ -22,6 +22,7 @@ import io.github.pnoker.common.entity.ext.MessageExt;
|
||||
import io.github.pnoker.common.enums.NotifyChannelTypeEnum;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Strings;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -83,8 +84,8 @@ public class MessageRenderServiceImpl implements MessageRenderService {
|
||||
if (Objects.isNull(template) || StringUtils.isBlank(template.getChannelType())) {
|
||||
return false;
|
||||
}
|
||||
return StringUtils.equalsIgnoreCase(template.getChannelType(), channelTypeFlag.name())
|
||||
|| StringUtils.equalsIgnoreCase(template.getChannelType(), channelTypeFlag.getCode());
|
||||
return Strings.CI.equals(template.getChannelType(), channelTypeFlag.name())
|
||||
|| Strings.CI.equals(template.getChannelType(), channelTypeFlag.getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+5
-4
@@ -24,6 +24,7 @@ import io.github.pnoker.common.data.entity.bo.RuleStateBO;
|
||||
import io.github.pnoker.common.entity.ext.NotifyChannelBindExt;
|
||||
import io.github.pnoker.common.entity.ext.NotifyExt;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Strings;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.DateTimeException;
|
||||
@@ -68,7 +69,7 @@ public class NotifyPolicyEngineImpl implements NotifyPolicyEngine {
|
||||
}
|
||||
|
||||
private boolean isRecovery(RuleMatch match) {
|
||||
return StringUtils.equalsIgnoreCase(match.getMatchType(), AlarmConstant.MATCH_TYPE_RECOVERY);
|
||||
return Strings.CI.equals(match.getMatchType(), AlarmConstant.MATCH_TYPE_RECOVERY);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,7 +105,7 @@ public class NotifyPolicyEngineImpl implements NotifyPolicyEngine {
|
||||
return true;
|
||||
}
|
||||
for (String level : bindContent.getLevels()) {
|
||||
if (StringUtils.equalsIgnoreCase(level, match.getSeverity())) {
|
||||
if (Strings.CI.equals(level, match.getSeverity())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -222,8 +223,8 @@ public class NotifyPolicyEngineImpl implements NotifyPolicyEngine {
|
||||
return true;
|
||||
}
|
||||
for (String day : daysOfWeek) {
|
||||
if (StringUtils.equalsIgnoreCase(day, current.name())
|
||||
|| StringUtils.equalsIgnoreCase(day, current.getDisplayName(java.time.format.TextStyle.SHORT,
|
||||
if (Strings.CI.equals(day, current.name())
|
||||
|| Strings.CI.equals(day, current.getDisplayName(java.time.format.TextStyle.SHORT,
|
||||
Locale.ENGLISH))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
+2
-1
@@ -31,6 +31,7 @@ import io.github.pnoker.common.utils.JsonUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Strings;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -67,7 +68,7 @@ public class RuleAlarmPersistenceServiceImpl implements RuleAlarmPersistenceServ
|
||||
fact.setAlarmId(firingAlarmId);
|
||||
return;
|
||||
}
|
||||
if (!StringUtils.equalsIgnoreCase(AlarmConstant.MATCH_TYPE_FIRING, match.getMatchType())) {
|
||||
if (!Strings.CI.equals(AlarmConstant.MATCH_TYPE_FIRING, match.getMatchType())) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -47,6 +47,7 @@ import io.github.pnoker.common.enums.RuleStatusEnum;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Strings;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -257,7 +258,7 @@ public class RuleNotificationServiceImpl implements RuleNotificationService {
|
||||
RuleFact fact = match.getFact();
|
||||
String fingerprint = fingerprint(match, notify, variables);
|
||||
RuleStateBO state = loadState(rule, fact, fingerprint);
|
||||
boolean isRecovery = StringUtils.equalsIgnoreCase(match.getMatchType(), AlarmConstant.MATCH_TYPE_RECOVERY);
|
||||
boolean isRecovery = Strings.CI.equals(match.getMatchType(), AlarmConstant.MATCH_TYPE_RECOVERY);
|
||||
if (isRecovery && (Objects.isNull(state) || !RuleStatusEnum.FIRING.equals(state.getEntityStateFlag()))) {
|
||||
log.debug("Skip recovery state transition because no FIRING fingerprint exists, ruleId={}, entityId={}",
|
||||
rule.getId(), fact.getEntityId());
|
||||
|
||||
+1
-1
@@ -683,7 +683,7 @@ public class DashboardServiceImpl implements DashboardService {
|
||||
CoverageGapVO.Item it = new CoverageGapVO.Item();
|
||||
it.setPointId(r.getPointId());
|
||||
it.setProfileId(r.getProfileId());
|
||||
vo.getItems().add(it);
|
||||
vo.addItem(it);
|
||||
}
|
||||
// missingPoints = actual count; items may be capped. Use a second
|
||||
// query only if we hit the cap — otherwise items.size() is authoritative.
|
||||
|
||||
+1
-2
@@ -150,8 +150,7 @@ public class PointCommandServiceImpl implements PointCommandService, PointComman
|
||||
}
|
||||
checkDriverOnline(tenantId, driver.getId());
|
||||
|
||||
FacadePointBO point = pointFacade.getById(tenantId, entityBO.getPointId());
|
||||
pointCommandValidator.validateWriteValue(entityBO.getValue(), Objects.nonNull(point) ? point.getPointExt() : null);
|
||||
pointCommandValidator.validateWriteValue(entityBO.getValue());
|
||||
|
||||
String commandId = resolveCommandId(entityBO.getCommandId());
|
||||
LocalDateTime nowLocal = LocalDateTime.now();
|
||||
|
||||
+20
@@ -26,6 +26,7 @@ import lombok.ToString;
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -56,6 +57,25 @@ public class CoverageGapVO implements Serializable {
|
||||
@Schema(description = "capped list of offending point/profile ids")
|
||||
private List<Item> items = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Unmodifiable view of the offending items, so callers cannot mutate internal state
|
||||
* through the getter. Use {@link #addItem(Item)} to append.
|
||||
*
|
||||
* @return unmodifiable view of the items list
|
||||
*/
|
||||
public List<Item> getItems() {
|
||||
return Collections.unmodifiableList(items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append one offending item to the internal list.
|
||||
*
|
||||
* @param item item to append
|
||||
*/
|
||||
public void addItem(Item item) {
|
||||
items.add(item);
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
|
||||
+2
-4
@@ -17,7 +17,6 @@
|
||||
|
||||
package io.github.pnoker.common.data.validator;
|
||||
|
||||
import io.github.pnoker.common.entity.ext.PointExt;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -40,11 +39,10 @@ public class PointCommandValidator {
|
||||
/**
|
||||
* Validate a write value against the point's constraints.
|
||||
*
|
||||
* @param value raw value string to validate
|
||||
* @param pointExt point extension JSON (may contain constraints for future use)
|
||||
* @param value raw value string to validate
|
||||
* @throws IllegalArgumentException if the value fails validation
|
||||
*/
|
||||
public void validateWriteValue(String value, PointExt pointExt) {
|
||||
public void validateWriteValue(String value) {
|
||||
if (value == null || value.isBlank()) {
|
||||
throw new IllegalArgumentException("Write value must not be blank");
|
||||
}
|
||||
|
||||
-5
@@ -49,11 +49,6 @@ public class DataInitRunner implements ApplicationRunner {
|
||||
|
||||
private final ScheduleForDataService scheduleForDataService;
|
||||
|
||||
/**
|
||||
* Constructor for DataInitRunner
|
||||
*
|
||||
* @param scheduleForDataService Service for handling data scheduling operations
|
||||
*/
|
||||
/**
|
||||
* Executes the data initialization process when the application starts
|
||||
*
|
||||
|
||||
+1
-1
@@ -109,7 +109,7 @@ public class DriverInitRunner implements ApplicationRunner {
|
||||
}
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
if (attempt == REGISTER_MAX_ATTEMPTS) {
|
||||
if (attempt >= REGISTER_MAX_ATTEMPTS) {
|
||||
log.error("Driver register failed after {} attempts, giving up", attempt, e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
+5
-5
@@ -86,7 +86,7 @@ public final class DeviceMetadata extends AbstractMetadataCache<DeviceBO> {
|
||||
if (value == null) {
|
||||
// Manager has dropped this device; drop the orphan id so the Quartz read
|
||||
// scan stops attempting to read a record that no longer exists.
|
||||
if (driverMetadata.getDeviceIds().remove(id)) {
|
||||
if (driverMetadata.removeDeviceId(id)) {
|
||||
log.info("Drop orphan device id={} after upstream returned null", id);
|
||||
}
|
||||
}
|
||||
@@ -114,7 +114,7 @@ public final class DeviceMetadata extends AbstractMetadataCache<DeviceBO> {
|
||||
}
|
||||
|
||||
Map<Long, DriverAttributeConfigDTO> attributeConfigMap = device.getDriverAttributeConfigIdMap();
|
||||
if (MapUtils.isEmpty(attributeConfigMap)
|
||||
if (attributeConfigMap == null || attributeConfigMap.isEmpty()
|
||||
|| !attributeConfigMap.keySet().containsAll(attributeMap.keySet())) {
|
||||
log.warn("Driver config incomplete, deviceId={}, required={}, configured={}",
|
||||
deviceId, attributeMap.keySet(),
|
||||
@@ -200,7 +200,7 @@ public final class DeviceMetadata extends AbstractMetadataCache<DeviceBO> {
|
||||
}
|
||||
|
||||
Map<Long, PointAttributeConfigDTO> attributeConfigMap = pointAttributeConfigMap.get(pointId);
|
||||
if (MapUtils.isEmpty(attributeConfigMap)
|
||||
if (attributeConfigMap == null || attributeConfigMap.isEmpty()
|
||||
|| !attributeConfigMap.keySet().containsAll(attributeMap.keySet())) {
|
||||
log.warn("Point config incomplete, deviceId={}, pointId={}, required={}, configured={}",
|
||||
deviceId, pointId, attributeMap.keySet(),
|
||||
@@ -288,7 +288,7 @@ public final class DeviceMetadata extends AbstractMetadataCache<DeviceBO> {
|
||||
}
|
||||
|
||||
Map<Long, CommandAttributeConfigDTO> attributeConfigMap = commandAttributeConfigMap.get(commandId);
|
||||
if (MapUtils.isEmpty(attributeConfigMap)
|
||||
if (attributeConfigMap == null || attributeConfigMap.isEmpty()
|
||||
|| !attributeConfigMap.keySet().containsAll(attributeMap.keySet())) {
|
||||
log.warn("Command config incomplete, deviceId={}, commandId={}, required={}, configured={}",
|
||||
deviceId, commandId, attributeMap.keySet(),
|
||||
@@ -375,7 +375,7 @@ public final class DeviceMetadata extends AbstractMetadataCache<DeviceBO> {
|
||||
}
|
||||
|
||||
Map<Long, EventAttributeConfigDTO> attributeConfigMap = eventAttributeConfigMap.get(eventId);
|
||||
if (MapUtils.isEmpty(attributeConfigMap)
|
||||
if (attributeConfigMap == null || attributeConfigMap.isEmpty()
|
||||
|| !attributeConfigMap.keySet().containsAll(attributeMap.keySet())) {
|
||||
log.warn("Event config incomplete, deviceId={}, eventId={}, required={}, configured={}",
|
||||
deviceId, eventId, attributeMap.keySet(),
|
||||
|
||||
+34
@@ -28,6 +28,7 @@ import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
@@ -59,6 +60,39 @@ public final class DriverMetadata {
|
||||
* Identifiers of devices owned by the driver.
|
||||
*/
|
||||
private final Set<Long> deviceIds = ConcurrentHashMap.newKeySet();
|
||||
|
||||
/**
|
||||
* Unmodifiable view of the device ids so callers cannot mutate the internal set
|
||||
* through the getter. The underlying set is still live — reads observe the most
|
||||
* recent state. Use {@link #addDeviceId(Long)} / {@link #removeDeviceId(Long)} to
|
||||
* mutate, or {@link #setDeviceIds(Set)} to replace the contents in place.
|
||||
*
|
||||
* @return unmodifiable live view of the device ids
|
||||
*/
|
||||
public Set<Long> getDeviceIds() {
|
||||
return Collections.unmodifiableSet(deviceIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a device id to the live set.
|
||||
*
|
||||
* @param id device id to add
|
||||
* @return {@code true} if the set did not already contain the id
|
||||
*/
|
||||
public boolean addDeviceId(Long id) {
|
||||
return deviceIds.add(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a device id from the live set.
|
||||
*
|
||||
* @param id device id to remove
|
||||
* @return {@code true} if the set contained the id
|
||||
*/
|
||||
public boolean removeDeviceId(Long id) {
|
||||
return deviceIds.remove(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Driver attributes keyed by attribute identifier.
|
||||
*/
|
||||
|
||||
+6
-3
@@ -78,7 +78,10 @@ public class MetadataReceiver {
|
||||
if (Objects.isNull(entityDTO) || Objects.isNull(entityDTO.getId())
|
||||
|| Objects.isNull(entityDTO.getMetadataType())
|
||||
|| Objects.isNull(entityDTO.getOperateType())) {
|
||||
log.error("Invalid driver metadata: {}", entityDTO);
|
||||
log.error("Invalid driver metadata: id={}, type={}, operate={}",
|
||||
Objects.nonNull(entityDTO) ? entityDTO.getId() : null,
|
||||
Objects.nonNull(entityDTO) ? entityDTO.getMetadataType() : null,
|
||||
Objects.nonNull(entityDTO) ? entityDTO.getOperateType() : null);
|
||||
RabbitAckUtil.reject(channel, deliveryTag);
|
||||
return;
|
||||
}
|
||||
@@ -94,14 +97,14 @@ public class MetadataReceiver {
|
||||
// Add the id first so a refresh that races with a Quartz scan does
|
||||
// not bypass the just-loaded entry; loadCache below either fills
|
||||
// the cache or, on a null upstream, removes the orphan id again.
|
||||
driverMetadata.getDeviceIds().add(entityDTO.getId());
|
||||
driverMetadata.addDeviceId(entityDTO.getId());
|
||||
deviceMetadata.loadCache(entityDTO.getId());
|
||||
} else if (MetadataOperateTypeEnum.DELETE.equals(entityDTO.getOperateType())) {
|
||||
log.debug("Delete device: {}", entityDTO.getId());
|
||||
// Remove the id before invalidating the cache so a Quartz scan
|
||||
// hitting the cache between the two operations does not re-fetch
|
||||
// the doomed device through the loader.
|
||||
driverMetadata.getDeviceIds().remove(entityDTO.getId());
|
||||
driverMetadata.removeDeviceId(entityDTO.getId());
|
||||
deviceMetadata.removeCache(entityDTO.getId());
|
||||
}
|
||||
|
||||
|
||||
+10
-10
@@ -126,12 +126,12 @@ public final class TypedValueConverter {
|
||||
}
|
||||
case FLOAT -> {
|
||||
float value = roundedFloat(linearValue(multiple, rawValue, base, "Point", pointType.getCode()),
|
||||
rawValue, decimal, "Point", pointType.getCode());
|
||||
rawValue, decimal, "Point");
|
||||
yield new CalculatedPointValue(String.valueOf(value), (double) value);
|
||||
}
|
||||
case DOUBLE -> {
|
||||
double value = roundedDouble(linearValue(multiple, rawValue, base, "Point", pointType.getCode()),
|
||||
rawValue, decimal, "Point", pointType.getCode());
|
||||
rawValue, decimal, "Point");
|
||||
yield new CalculatedPointValue(String.valueOf(value), value);
|
||||
}
|
||||
case BOOLEAN -> {
|
||||
@@ -162,9 +162,9 @@ public final class TypedValueConverter {
|
||||
case SHORT -> (T) Short.valueOf(exactShort(decimal(value, type, label, typeCode), value, label, typeCode));
|
||||
case INT -> (T) Integer.valueOf(exactInt(decimal(value, type, label, typeCode), value, label, typeCode));
|
||||
case LONG -> (T) Long.valueOf(exactLong(decimal(value, type, label, typeCode), value, label, typeCode));
|
||||
case FLOAT -> (T) Float.valueOf(finiteFloat(decimal(value, type, label, typeCode), value, label, typeCode));
|
||||
case FLOAT -> (T) Float.valueOf(finiteFloat(decimal(value, type, label, typeCode), value, label));
|
||||
case DOUBLE ->
|
||||
(T) Double.valueOf(finiteDouble(decimal(value, type, label, typeCode), value, label, typeCode));
|
||||
(T) Double.valueOf(finiteDouble(decimal(value, type, label, typeCode), value, label));
|
||||
case BOOLEAN -> (T) Boolean.valueOf(strictBoolean(value, label, typeCode));
|
||||
};
|
||||
}
|
||||
@@ -252,15 +252,15 @@ public final class TypedValueConverter {
|
||||
return value.longValueExact();
|
||||
}
|
||||
|
||||
private static float roundedFloat(BigDecimal value, String rawValue, byte decimal, String label, String typeCode) {
|
||||
return ArithmeticUtil.round(finiteFloat(value, rawValue, label, typeCode), decimal);
|
||||
private static float roundedFloat(BigDecimal value, String rawValue, byte decimal, String label) {
|
||||
return ArithmeticUtil.round(finiteFloat(value, rawValue, label), decimal);
|
||||
}
|
||||
|
||||
private static double roundedDouble(BigDecimal value, String rawValue, byte decimal, String label, String typeCode) {
|
||||
return ArithmeticUtil.round(finiteDouble(value, rawValue, label, typeCode), decimal);
|
||||
private static double roundedDouble(BigDecimal value, String rawValue, byte decimal, String label) {
|
||||
return ArithmeticUtil.round(finiteDouble(value, rawValue, label), decimal);
|
||||
}
|
||||
|
||||
private static float finiteFloat(BigDecimal value, String rawValue, String label, String typeCode) {
|
||||
private static float finiteFloat(BigDecimal value, String rawValue, String label) {
|
||||
float result = value.floatValue();
|
||||
if (!Float.isFinite(result)) {
|
||||
throw new OutRangeException("{} value out of float range: {} ~ {}, current: {}", label, -Float.MAX_VALUE,
|
||||
@@ -269,7 +269,7 @@ public final class TypedValueConverter {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static double finiteDouble(BigDecimal value, String rawValue, String label, String typeCode) {
|
||||
private static double finiteDouble(BigDecimal value, String rawValue, String label) {
|
||||
double result = value.doubleValue();
|
||||
if (!Double.isFinite(result)) {
|
||||
throw new OutRangeException("{} value out of double range: {} ~ {}, current: {}", label, -Double.MAX_VALUE,
|
||||
|
||||
+1
-1
@@ -129,7 +129,7 @@ class MetadataReceiverTest {
|
||||
|
||||
@Test
|
||||
void deviceDeleteRemovesCacheAndDriverDeviceIds() throws Exception {
|
||||
driverMetadata.getDeviceIds().add(99L);
|
||||
driverMetadata.addDeviceId(99L);
|
||||
MetadataEventDTO dto = event(MetadataTypeEnum.DEVICE, MetadataOperateTypeEnum.DELETE, 99L);
|
||||
receiver.metadataReceive(channel, message, dto);
|
||||
verify(deviceMetadata).removeCache(99L);
|
||||
|
||||
+12
-2
@@ -33,13 +33,23 @@ import org.springframework.stereotype.Component;
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class AuthenticGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {
|
||||
public class AuthenticGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthenticGatewayFilterFactory.Config> {
|
||||
|
||||
private final AuthenticGatewayFilter authenticGatewayFilter;
|
||||
|
||||
@Override
|
||||
public GatewayFilter apply(Object config) {
|
||||
public GatewayFilter apply(Config config) {
|
||||
return authenticGatewayFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty configuration marker for the authentic gateway filter.
|
||||
* <p>
|
||||
* The filter has no tunable options; this class exists to give the factory a
|
||||
* dedicated, named config type instead of the overly generic {@code Object},
|
||||
* which produces a confusing method signature.
|
||||
*/
|
||||
public static class Config {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
@@ -77,6 +77,9 @@ public class McpGatewayController {
|
||||
private final McpGatewayProperties mcpGatewayProperties;
|
||||
|
||||
private static Map<String, Object> orderedMap(Object... values) {
|
||||
if (values.length % 2 != 0) {
|
||||
throw new IllegalArgumentException("orderedMap requires key-value pairs (even number of arguments)");
|
||||
}
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
for (int i = 0; i < values.length; i += 2) {
|
||||
map.put(String.valueOf(values[i]), values[i + 1]);
|
||||
|
||||
+2
-1
@@ -21,6 +21,7 @@ import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Strings;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
@@ -61,7 +62,7 @@ public class McpGatewayProperties {
|
||||
if (StringUtils.isBlank(baseUrl)) {
|
||||
throw new IllegalArgumentException("Unknown backend service: " + serviceName);
|
||||
}
|
||||
return StringUtils.removeEnd(baseUrl, "/");
|
||||
return Strings.CS.removeEnd(baseUrl, "/");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-6
@@ -44,12 +44,6 @@ public class ManagerInitRunner implements ApplicationRunner {
|
||||
|
||||
private final ScheduleForManagerService scheduleForManagerService;
|
||||
|
||||
/**
|
||||
* Constructs a new ManagerInitRunner with the required service dependency.
|
||||
*
|
||||
* @param scheduleForManagerService The service responsible for manager scheduling
|
||||
* operations
|
||||
*/
|
||||
/**
|
||||
* Executes the initialization process when the application starts. This method
|
||||
* initializes the schedule manager service to set up necessary scheduling
|
||||
|
||||
+17
-9
@@ -500,10 +500,10 @@ public class DashboardServiceImpl implements DashboardService {
|
||||
// Others never represents "nothing is happening".
|
||||
Map<Long, List<TopologyHiddenChildVO>> otherDevicesByDriver = new LinkedHashMap<>();
|
||||
for (TopologyDeviceRow r : filteredDevices) {
|
||||
Long deviceId = r.getId();
|
||||
long deviceId = r.getId();
|
||||
if (topDeviceIdSet.contains(deviceId))
|
||||
continue;
|
||||
Long drvId = r.getDriverId();
|
||||
long drvId = r.getDriverId();
|
||||
TopologyHiddenChildVO hidden = new TopologyHiddenChildVO();
|
||||
hidden.setId("device:" + deviceId);
|
||||
hidden.setName(r.getDeviceName());
|
||||
@@ -541,15 +541,15 @@ public class DashboardServiceImpl implements DashboardService {
|
||||
List<TopologyLinkVO> links = new ArrayList<>();
|
||||
|
||||
for (TopologyDriverRow r : topDrivers) {
|
||||
Long id = r.getId();
|
||||
long id = r.getId();
|
||||
nodes.add(node("driver:" + id, driverNameById.get(id), 1, "driver", null));
|
||||
}
|
||||
|
||||
// Driver → Device links. Edge weight in cardinality mode = 1 (one
|
||||
// relationship); in volume mode = that device's total pv count.
|
||||
for (TopologyDeviceRow r : topDevices) {
|
||||
Long id = r.getId();
|
||||
Long driverId = r.getDriverId();
|
||||
long id = r.getId();
|
||||
long driverId = r.getDriverId();
|
||||
String name = r.getDeviceName();
|
||||
nodes.add(node("device:" + id, name, 2, "device", null));
|
||||
long w = volumeMode ? nullZero(deviceWeight.get(id)) : 1L;
|
||||
@@ -563,8 +563,16 @@ public class DashboardServiceImpl implements DashboardService {
|
||||
if (volumeMode) {
|
||||
long sum = 0;
|
||||
for (TopologyHiddenChildVO c : children) {
|
||||
long did = Long.parseLong(c.getId().substring("device:".length()));
|
||||
sum += nullZero(deviceWeight.get(did));
|
||||
String cid = c.getId();
|
||||
if (cid == null || !cid.startsWith("device:")) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
long did = Long.parseLong(cid.substring("device:".length()));
|
||||
sum += nullZero(deviceWeight.get(did));
|
||||
} catch (NumberFormatException ignored) {
|
||||
// skip malformed device ids (shouldn't happen; ids are self-generated above)
|
||||
}
|
||||
}
|
||||
w = Math.max(1L, sum);
|
||||
} else {
|
||||
@@ -629,7 +637,7 @@ public class DashboardServiceImpl implements DashboardService {
|
||||
int keep = Math.min(TopologyLimits.TOP_POINTS_PER_PROFILE, allPoints.size());
|
||||
for (int i = 0; i < keep; i++) {
|
||||
TopologyPointRow r = allPoints.get(i);
|
||||
Long id = r.getId();
|
||||
long id = r.getId();
|
||||
String name = r.getPointName();
|
||||
nodes.add(node("point:" + id, name, 4, "point", null));
|
||||
long w = volumeMode ? nullZero(pointWeight.get(id)) : 1L;
|
||||
@@ -641,7 +649,7 @@ public class DashboardServiceImpl implements DashboardService {
|
||||
long sumW = 0;
|
||||
for (int i = keep; i < allPoints.size(); i++) {
|
||||
TopologyPointRow r = allPoints.get(i);
|
||||
Long id = r.getId();
|
||||
long id = r.getId();
|
||||
TopologyHiddenChildVO hidden = new TopologyHiddenChildVO();
|
||||
hidden.setId("point:" + id);
|
||||
hidden.setName(r.getPointName());
|
||||
|
||||
+2
-6
@@ -24,6 +24,7 @@ import io.github.pnoker.common.utils.MqttUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Strings;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -59,11 +60,6 @@ public class MqttConfig {
|
||||
|
||||
private final MqttProperties mqttProperties;
|
||||
|
||||
/**
|
||||
* Constructor for MQTT configuration
|
||||
*
|
||||
* @param mqttProperties MQTT configuration properties
|
||||
*/
|
||||
/**
|
||||
* MQTT inbound message channel bean
|
||||
*
|
||||
@@ -147,7 +143,7 @@ public class MqttConfig {
|
||||
|
||||
private String prefixedTopicName(String topicName) {
|
||||
String topicPrefix = mqttProperties.getTopicPrefix();
|
||||
if (StringUtils.isBlank(topicPrefix) || StringUtils.startsWith(topicName, topicPrefix)) {
|
||||
if (StringUtils.isBlank(topicPrefix) || Strings.CS.startsWith(topicName, topicPrefix)) {
|
||||
return topicName;
|
||||
}
|
||||
return topicPrefix + topicName;
|
||||
|
||||
-5
@@ -49,11 +49,6 @@ public class MqttInitRunner implements ApplicationRunner {
|
||||
private final MqttScheduleService mqttScheduleService;
|
||||
private final ObjectProvider<MqttReceiveService> mqttReceiveServiceProvider;
|
||||
|
||||
/**
|
||||
* Creates a new MQTT initialization runner with the specified MQTT schedule service.
|
||||
*
|
||||
* @param mqttScheduleService The MQTT schedule service to be initialized
|
||||
*/
|
||||
/**
|
||||
* Executes the MQTT initialization process when the application starts. This method
|
||||
* is called automatically by Spring Boot after the application context is loaded.
|
||||
|
||||
+2
@@ -19,6 +19,7 @@ package io.github.pnoker.common.mqtt.entity;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
|
||||
@@ -40,6 +41,7 @@ import java.util.UUID;
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@Slf4j
|
||||
public class MessageHeader implements Serializable {
|
||||
|
||||
|
||||
+21
@@ -27,6 +27,7 @@ import lombok.ToString;
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -61,4 +62,24 @@ public class Pages implements Serializable {
|
||||
@Schema(description = "Sort order items applied to the query results")
|
||||
private List<OrderItem> orders = new ArrayList<>(2);
|
||||
|
||||
/**
|
||||
* Unmodifiable view of the order items, so callers cannot mutate internal state
|
||||
* through the getter. Use {@link #addOrder(OrderItem)} to append, or
|
||||
* {@link #setOrders(List)} to replace.
|
||||
*
|
||||
* @return unmodifiable view of the order items
|
||||
*/
|
||||
public List<OrderItem> getOrders() {
|
||||
return Collections.unmodifiableList(orders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append one order item to the internal list.
|
||||
*
|
||||
* @param order order item to append
|
||||
*/
|
||||
public void addOrder(OrderItem order) {
|
||||
orders.add(order);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+23
-1
@@ -26,6 +26,10 @@ import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.FileAttribute;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.nio.file.attribute.PosixFilePermissions;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@@ -61,7 +65,7 @@ public class FileUtil {
|
||||
Path dir = Paths.get(FolderConstant.TEMP_FILE_PATH, safePathSegments(segments));
|
||||
if (Files.notExists(dir) || !Files.isDirectory(dir)) {
|
||||
try {
|
||||
Files.createDirectories(dir);
|
||||
createSecureTempDirectories(dir);
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to create temp directory: {}", dir, e);
|
||||
}
|
||||
@@ -69,6 +73,24 @@ public class FileUtil {
|
||||
return dir.toString() + SymbolConstant.SLASH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the temp directory chain with owner-only permissions (rwx------) to
|
||||
* prevent information disclosure on multi-user hosts. Falls back to platform
|
||||
* defaults on non-POSIX filesystems (e.g. Windows).
|
||||
*
|
||||
* @param dir the directory to create
|
||||
* @throws IOException if directory creation fails
|
||||
*/
|
||||
private static void createSecureTempDirectories(Path dir) throws IOException {
|
||||
FileAttribute<Set<PosixFilePermission>> ownerOnly =
|
||||
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwx------"));
|
||||
try {
|
||||
Files.createDirectories(dir, ownerOnly);
|
||||
} catch (UnsupportedOperationException ignored) {
|
||||
Files.createDirectories(dir);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a module-scoped temporary upload file path.
|
||||
*
|
||||
|
||||
+2
-1
@@ -25,6 +25,7 @@ import io.github.pnoker.common.entity.common.Pages;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
@@ -66,7 +67,7 @@ public class PageUtil {
|
||||
}
|
||||
page.setSize(pages.getSize());
|
||||
|
||||
List<OrderItem> orders = pages.getOrders();
|
||||
List<OrderItem> orders = new ArrayList<>(pages.getOrders());
|
||||
boolean anyMatch = orders.stream()
|
||||
.filter(order -> Objects.nonNull(order) && StringUtils.isNotEmpty(order.getColumn()))
|
||||
.anyMatch(order -> "create_time".equals(order.getColumn()));
|
||||
|
||||
+2
-1
@@ -21,6 +21,7 @@ import io.github.pnoker.common.constant.common.ExceptionConstant;
|
||||
import io.github.pnoker.common.enums.PasswordAlgorithmEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Strings;
|
||||
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
|
||||
@@ -82,7 +83,7 @@ public class PasswordUtil {
|
||||
* @return password algorithm
|
||||
*/
|
||||
public static PasswordAlgorithmEnum algorithmOfHash(String hash) {
|
||||
if (StringUtils.startsWith(hash, "$argon2")) {
|
||||
if (Strings.CS.startsWith(hash, "$argon2")) {
|
||||
return PasswordAlgorithmEnum.ARGON2ID;
|
||||
}
|
||||
return PasswordAlgorithmEnum.BCRYPT;
|
||||
|
||||
+3
-3
@@ -37,17 +37,17 @@ class PagesTest {
|
||||
Pages pages = new Pages();
|
||||
pages.setStartTime(100L);
|
||||
pages.setEndTime(200L);
|
||||
pages.getOrders().add(OrderItem.asc("name"));
|
||||
pages.addOrder(OrderItem.asc("name"));
|
||||
assertThat(pages.getStartTime()).isEqualTo(100L);
|
||||
assertThat(pages.getEndTime()).isEqualTo(200L);
|
||||
assertThat(pages.getOrders()).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void ordersListIsMutableInitially() {
|
||||
void addOrderAppendsToInternalList() {
|
||||
Pages pages = new Pages();
|
||||
assertThat(pages.getOrders()).isEmpty();
|
||||
pages.getOrders().add(OrderItem.desc("create_time"));
|
||||
pages.addOrder(OrderItem.desc("create_time"));
|
||||
assertThat(pages.getOrders()).extracting(OrderItem::getColumn).contains("create_time");
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -65,7 +65,7 @@ class PageUtilTest {
|
||||
@Test
|
||||
void pagePreservesUserOrdersAndAddsCreateTime() {
|
||||
Pages pages = new Pages();
|
||||
pages.getOrders().add(OrderItem.asc("name"));
|
||||
pages.addOrder(OrderItem.asc("name"));
|
||||
Page<Object> page = PageUtil.page(pages);
|
||||
assertThat(page.orders()).extracting(OrderItem::getColumn)
|
||||
.contains("name", "create_time");
|
||||
@@ -74,7 +74,7 @@ class PageUtilTest {
|
||||
@Test
|
||||
void pageDoesNotDuplicateExistingCreateTimeOrder() {
|
||||
Pages pages = new Pages();
|
||||
pages.getOrders().add(OrderItem.desc("create_time"));
|
||||
pages.addOrder(OrderItem.desc("create_time"));
|
||||
Page<Object> page = PageUtil.page(pages);
|
||||
long createTimeOrders = page.orders().stream()
|
||||
.filter(it -> "create_time".equals(it.getColumn()))
|
||||
|
||||
-10
@@ -54,16 +54,6 @@ public class ResourceRegistrar {
|
||||
|
||||
private final Environment environment;
|
||||
|
||||
/**
|
||||
* Create a registrar that scans the current service and submits the inventory through
|
||||
* the active facade implementation.
|
||||
*
|
||||
* @param scanner endpoint scanner for the local WebFlux mappings
|
||||
* @param facade transport-neutral resource registry facade
|
||||
* @param properties registrar runtime options
|
||||
* @param environment Spring environment used for service-name fallback
|
||||
*/
|
||||
|
||||
/**
|
||||
* Register scanned endpoints after the application is ready and all WebFlux handler
|
||||
* mappings have been built. Failures abort startup only when
|
||||
|
||||
-8
@@ -69,14 +69,6 @@ public class ApiEndpointScanner {
|
||||
|
||||
private final AntPathMatcher matcher = new AntPathMatcher();
|
||||
|
||||
/**
|
||||
* Create a scanner bound to the WebFlux handler mapping built by the current
|
||||
* application context.
|
||||
*
|
||||
* @param handlerMapping WebFlux request mapping registry
|
||||
* @param properties registrar scan configuration
|
||||
*/
|
||||
|
||||
private static String buildApiName(HandlerMethod handler, String httpMethod, boolean isSingleGet) {
|
||||
String protectedApiName = resolvePreAuthorizeApiName(handler);
|
||||
if (Objects.nonNull(protectedApiName)) {
|
||||
|
||||
-3
@@ -181,9 +181,6 @@ public abstract class AbstractJdbcDriverCustomService implements DriverCustomSer
|
||||
*/
|
||||
protected HikariDataSource getConnector(Long deviceId, Map<String, AttributeBO> driverConfig) {
|
||||
return connectMap.computeIfAbsent(deviceId, id -> {
|
||||
String host = getConfigValue(driverConfig, "host", "localhost");
|
||||
int port = getConfigIntValue(driverConfig, "port", getDefaultPort());
|
||||
String database = getRequiredConfig(driverConfig, "database");
|
||||
String username = getConfigValue(driverConfig, "username", "root");
|
||||
String password = getConfigValue(driverConfig, "password", "");
|
||||
int queryTimeout = getConfigIntValue(driverConfig, "queryTimeout", 30);
|
||||
|
||||
+3
-12
@@ -217,18 +217,9 @@ public class BacnetIpDriverCustomServiceImpl implements DriverCustomService {
|
||||
}
|
||||
if (upperName.startsWith("MULTI_STATE_") || upperName.startsWith("DEVICE")) {
|
||||
try {
|
||||
try {
|
||||
|
||||
return new UnsignedInteger(Integer.parseInt(value));
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
|
||||
return new UnsignedInteger(0);
|
||||
|
||||
}
|
||||
} catch (NumberFormatException ignored) {
|
||||
log.warn("BACnet write: multi-state/device value '{}' is not an integer, falling back to Real", value);
|
||||
return new Real(Float.parseFloat(value));
|
||||
return new UnsignedInteger(Integer.parseInt(value));
|
||||
} catch (NumberFormatException e) {
|
||||
return new UnsignedInteger(0);
|
||||
}
|
||||
}
|
||||
// Analog types and fallback
|
||||
|
||||
+1
-5
@@ -26,7 +26,6 @@ import io.github.pnoker.common.driver.entity.bo.DeviceBO;
|
||||
import io.github.pnoker.common.driver.entity.bo.PointBO;
|
||||
import io.github.pnoker.common.driver.metadata.DriverMetadata;
|
||||
import io.github.pnoker.common.driver.service.DriverCustomService;
|
||||
import io.github.pnoker.common.driver.service.DriverSenderService;
|
||||
import io.github.pnoker.common.entity.dto.MetadataEventDTO;
|
||||
import io.github.pnoker.common.enums.MetadataOperateTypeEnum;
|
||||
import io.github.pnoker.common.enums.MetadataTypeEnum;
|
||||
@@ -79,17 +78,14 @@ public class CanDriverCustomServiceImpl implements DriverCustomService {
|
||||
java.util.regex.Pattern.compile("^[A-Za-z0-9_]+$");
|
||||
|
||||
private final DriverMetadata driverMetadata;
|
||||
private final DriverSenderService driverSenderService;
|
||||
|
||||
@Value("${dc3.driver.code}")
|
||||
private String driverCode;
|
||||
|
||||
private Map<Long, Boolean> deviceMap;
|
||||
|
||||
public CanDriverCustomServiceImpl(DriverMetadata driverMetadata,
|
||||
DriverSenderService driverSenderService) {
|
||||
public CanDriverCustomServiceImpl(DriverMetadata driverMetadata) {
|
||||
this.driverMetadata = driverMetadata;
|
||||
this.driverSenderService = driverSenderService;
|
||||
}
|
||||
|
||||
private static void checkRequired(Map<String, AttributeBO> config, String code,
|
||||
|
||||
+1
-5
@@ -21,7 +21,6 @@ import io.github.pnoker.common.driver.entity.bean.ValidationReport;
|
||||
import io.github.pnoker.common.driver.entity.bo.AttributeBO;
|
||||
import io.github.pnoker.common.driver.entity.bo.PointBO;
|
||||
import io.github.pnoker.common.driver.metadata.DriverMetadata;
|
||||
import io.github.pnoker.common.driver.service.DriverSenderService;
|
||||
import io.github.pnoker.common.enums.AttributeTypeEnum;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -46,14 +45,11 @@ class CanDriverCustomServiceImplTest {
|
||||
@Mock
|
||||
private DriverMetadata driverMetadata;
|
||||
|
||||
@Mock
|
||||
private DriverSenderService driverSenderService;
|
||||
|
||||
private CanDriverCustomServiceImpl service;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
service = new CanDriverCustomServiceImpl(driverMetadata, driverSenderService);
|
||||
service = new CanDriverCustomServiceImpl(driverMetadata);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+21
-3
@@ -312,11 +312,23 @@ public class EthernetIpDriverCustomServiceImpl implements DriverCustomService {
|
||||
}
|
||||
case "SINT" -> {
|
||||
buf = ByteBuffer.allocate(1);
|
||||
buf.put(Byte.parseByte(value));
|
||||
byte b;
|
||||
try {
|
||||
b = Byte.parseByte(value);
|
||||
} catch (NumberFormatException e) {
|
||||
b = 0;
|
||||
}
|
||||
buf.put(b);
|
||||
}
|
||||
case "INT" -> {
|
||||
buf = ByteBuffer.allocate(2);
|
||||
buf.order(ByteOrder.LITTLE_ENDIAN).putShort(Short.parseShort(value));
|
||||
short s;
|
||||
try {
|
||||
s = Short.parseShort(value);
|
||||
} catch (NumberFormatException e) {
|
||||
s = 0;
|
||||
}
|
||||
buf.order(ByteOrder.LITTLE_ENDIAN).putShort(s);
|
||||
}
|
||||
case "DINT" -> {
|
||||
buf = ByteBuffer.allocate(4);
|
||||
@@ -332,7 +344,13 @@ public class EthernetIpDriverCustomServiceImpl implements DriverCustomService {
|
||||
}
|
||||
case "REAL" -> {
|
||||
buf = ByteBuffer.allocate(4);
|
||||
buf.order(ByteOrder.LITTLE_ENDIAN).putFloat(Float.parseFloat(value));
|
||||
float f;
|
||||
try {
|
||||
f = Float.parseFloat(value);
|
||||
} catch (NumberFormatException e) {
|
||||
f = 0f;
|
||||
}
|
||||
buf.order(ByteOrder.LITTLE_ENDIAN).putFloat(f);
|
||||
}
|
||||
default -> buf = ByteBuffer.wrap(value.getBytes(StandardCharsets.US_ASCII));
|
||||
}
|
||||
|
||||
+12
-2
@@ -444,7 +444,12 @@ public class FinsDriverCustomServiceImpl implements DriverCustomService {
|
||||
case "INT16":
|
||||
case "UINT16": {
|
||||
data = new byte[2];
|
||||
short s = Short.parseShort(value);
|
||||
short s;
|
||||
try {
|
||||
s = Short.parseShort(value);
|
||||
} catch (NumberFormatException e) {
|
||||
s = 0;
|
||||
}
|
||||
ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN).putShort(s);
|
||||
break;
|
||||
}
|
||||
@@ -467,7 +472,12 @@ public class FinsDriverCustomServiceImpl implements DriverCustomService {
|
||||
}
|
||||
case "FLOAT": {
|
||||
data = new byte[4];
|
||||
float f = Float.parseFloat(value);
|
||||
float f;
|
||||
try {
|
||||
f = Float.parseFloat(value);
|
||||
} catch (NumberFormatException e) {
|
||||
f = 0f;
|
||||
}
|
||||
ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN).putFloat(f);
|
||||
break;
|
||||
}
|
||||
|
||||
+3
-3
@@ -147,7 +147,7 @@ class MqttReceiveServiceImplTest {
|
||||
|
||||
@Test
|
||||
void receiveEventMessageMatchesConfiguredTopicAndReportsEvent() {
|
||||
driverMetadata.getDeviceIds().add(10L);
|
||||
driverMetadata.addDeviceId(10L);
|
||||
driverMetadata.setEventAttributeIdMap(Map.of(
|
||||
1L, eventAttribute(1L, "sourceTopic"),
|
||||
2L, eventAttribute(2L, "eventCodePath"),
|
||||
@@ -192,7 +192,7 @@ class MqttReceiveServiceImplTest {
|
||||
|
||||
@Test
|
||||
void eventMessageWithPointIdentityReportsBothEventAndPointValue() {
|
||||
driverMetadata.getDeviceIds().add(10L);
|
||||
driverMetadata.addDeviceId(10L);
|
||||
driverMetadata.setEventAttributeIdMap(Map.of(
|
||||
1L, eventAttribute(1L, "sourceTopic"),
|
||||
2L, eventAttribute(2L, "eventCodePath"),
|
||||
@@ -233,7 +233,7 @@ class MqttReceiveServiceImplTest {
|
||||
|
||||
@Test
|
||||
void eventReportFailureDoesNotDropPointValue() {
|
||||
driverMetadata.getDeviceIds().add(10L);
|
||||
driverMetadata.addDeviceId(10L);
|
||||
driverMetadata.setEventAttributeIdMap(Map.of(
|
||||
1L, eventAttribute(1L, "sourceTopic"),
|
||||
2L, eventAttribute(2L, "eventCodePath"),
|
||||
|
||||
+2
-3
@@ -175,8 +175,7 @@ public class Sl651DriverCustomServiceImpl implements DriverCustomService {
|
||||
Object listener = Proxy.newProxyInstance(listenerClass.getClassLoader(), new Class<?>[]{listenerClass},
|
||||
(proxy, method, args) -> {
|
||||
if ("onMessage".equals(method.getName()) && Objects.nonNull(args) && args.length == 3) {
|
||||
byte[] messageBytes = args[0] instanceof byte[] bytes ? bytes : new byte[0];
|
||||
handleSl651Message(messageBytes, args[1], args[2]);
|
||||
handleSl651Message(args[1], args[2]);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
@@ -268,7 +267,7 @@ public class Sl651DriverCustomServiceImpl implements DriverCustomService {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSl651Message(byte[] bytes, Object response, Object bodyResponses) {
|
||||
private void handleSl651Message(Object response, Object bodyResponses) {
|
||||
String stationAddr = bytesToHex(invokeBytes(response, "getRemoteStationAddress"));
|
||||
String funcCode = bytesToHex(invokeBytes(response, "getFunctionCode"));
|
||||
List<String> elements = extractBodyElements(bodyResponses);
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ class Sl651DriverCustomServiceImplTest {
|
||||
|
||||
@Test
|
||||
void forwardTelemetryMapsStationElementsToConfiguredPoints() {
|
||||
driverMetadata.getDeviceIds().add(10L);
|
||||
driverMetadata.addDeviceId(10L);
|
||||
DeviceBO device = new DeviceBO();
|
||||
device.setId(10L);
|
||||
device.setDeviceCode("01020304");
|
||||
|
||||
+8
-10
@@ -114,7 +114,7 @@ public class SnmpDriverCustomServiceImpl implements DriverCustomService {
|
||||
if (clientMap.containsKey(device.getId())) {
|
||||
return DeviceHealthState.online();
|
||||
}
|
||||
Snmp snmp = getConnector(device.getId(), driverConfig);
|
||||
Snmp snmp = getConnector(device.getId());
|
||||
return Objects.nonNull(snmp) ? DeviceHealthState.online() : DeviceHealthState.offline();
|
||||
}
|
||||
|
||||
@@ -148,10 +148,10 @@ public class SnmpDriverCustomServiceImpl implements DriverCustomService {
|
||||
@Override
|
||||
public ReadPointValue read(Map<String, AttributeBO> driverConfig, Map<String, AttributeBO> pointConfig,
|
||||
DeviceBO device, PointBO point) {
|
||||
Snmp snmp = getConnector(device.getId(), driverConfig);
|
||||
Snmp snmp = getConnector(device.getId());
|
||||
String oid = getConfigValue(pointConfig, "oid", "");
|
||||
try {
|
||||
CommunityTarget target = buildTarget(device.getId(), driverConfig);
|
||||
CommunityTarget target = buildTarget(driverConfig);
|
||||
|
||||
PDU pdu = new PDU();
|
||||
pdu.add(new VariableBinding(new OID(oid)));
|
||||
@@ -177,11 +177,11 @@ public class SnmpDriverCustomServiceImpl implements DriverCustomService {
|
||||
@Override
|
||||
public Boolean write(Map<String, AttributeBO> driverConfig, Map<String, AttributeBO> pointConfig,
|
||||
DeviceBO device, PointBO point, WritePointValue writePointValue) {
|
||||
Snmp snmp = getConnector(device.getId(), driverConfig);
|
||||
Snmp snmp = getConnector(device.getId());
|
||||
String oid = getConfigValue(pointConfig, "oid", "");
|
||||
String snmpType = getConfigValue(pointConfig, "snmpType", "OCTET_STRING");
|
||||
try {
|
||||
CommunityTarget target = buildTarget(device.getId(), driverConfig);
|
||||
CommunityTarget target = buildTarget(driverConfig);
|
||||
|
||||
PDU pdu = new PDU();
|
||||
Variable variable = createVariable(snmpType, writePointValue.getValue(String.class));
|
||||
@@ -205,11 +205,10 @@ public class SnmpDriverCustomServiceImpl implements DriverCustomService {
|
||||
/**
|
||||
* Get or create an SNMP client for the given device.
|
||||
*
|
||||
* @param deviceId unique device identifier
|
||||
* @param driverConfig driver configuration
|
||||
* @param deviceId unique device identifier
|
||||
* @return cached or newly created Snmp instance
|
||||
*/
|
||||
private Snmp getConnector(Long deviceId, Map<String, AttributeBO> driverConfig) {
|
||||
private Snmp getConnector(Long deviceId) {
|
||||
return clientMap.computeIfAbsent(deviceId, id -> {
|
||||
try {
|
||||
TransportMapping<org.snmp4j.smi.UdpAddress> transport = new DefaultUdpTransportMapping();
|
||||
@@ -228,11 +227,10 @@ public class SnmpDriverCustomServiceImpl implements DriverCustomService {
|
||||
/**
|
||||
* Build an SNMP community target from driver configuration.
|
||||
*
|
||||
* @param deviceId device identifier
|
||||
* @param driverConfig driver configuration
|
||||
* @return configured CommunityTarget
|
||||
*/
|
||||
private CommunityTarget buildTarget(Long deviceId, Map<String, AttributeBO> driverConfig) {
|
||||
private CommunityTarget buildTarget(Map<String, AttributeBO> driverConfig) {
|
||||
String host = getConfigValue(driverConfig, "host", "127.0.0.1");
|
||||
int port = getConfigIntValue(driverConfig, "port", 161);
|
||||
String version = getConfigValue(driverConfig, "version", "v2c");
|
||||
|
||||
@@ -48,5 +48,5 @@ export function isNull(val: unknown): boolean {
|
||||
if (val !== null && typeof val === 'object') {
|
||||
return Object.keys(val as object).length === 0;
|
||||
}
|
||||
return val === 'null' || val == null || val === 'undefined' || val === undefined || val === '';
|
||||
return val === 'null' || val == null || val === 'undefined' || val === '';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user