mirror of
https://gitee.com/pnoker/iot-dc3.git
synced 2026-09-01 15:33:10 +08:00
feat: add custom command call API, event report API with RabbitMQ and gRPC
- Add dc3_command_record and dc3_event_record DTOs, DOs, mappers, managers - Implement CommandRecordService: call flow with validation, RabbitMQ dispatch, result/dead receivers, full lifecycle (PENDING→SENT→SUCCESS/FAILED/TIMEOUT/DEAD) - Implement EventReportService: report flow with validation and persistence - Add REST controllers: /command_record (call/get/list), /event_report (report/get/list) - Add RabbitMQ exchanges/queues/bindings for command dispatch, result, dead letter - Add event exchange for driver→center event reporting - Add gRPC CommandRecordApi and EventReportApi with proto definitions and servers - Fix PointCommandValidator parameter type (String→PointExt)
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package api.common.data;
|
||||
|
||||
import "api/common/r.proto";
|
||||
import "api/common/page.proto";
|
||||
|
||||
option java_package = "io.github.pnoker.api.center.data";
|
||||
option java_outer_classname = "CommandRecordProto";
|
||||
option objc_class_prefix = "Data";
|
||||
option java_multiple_files = true;
|
||||
|
||||
service CommandRecordApi {
|
||||
rpc Call (GrpcCommandCallVO) returns (GrpcRString);
|
||||
rpc GetByRecordId (GrpcStringQuery) returns (GrpcRCommandRecordDTO);
|
||||
rpc List (GrpcCommandRecordQuery) returns (GrpcRPageCommandRecordDTO);
|
||||
}
|
||||
|
||||
message GrpcCommandCallVO {
|
||||
int64 device_id = 1;
|
||||
int64 command_id = 2;
|
||||
map<string, string> param_values = 3;
|
||||
int64 tenant_id = 4;
|
||||
}
|
||||
|
||||
message GrpcCommandRecordDTO {
|
||||
int64 id = 1;
|
||||
string record_id = 2;
|
||||
int64 tenant_id = 3;
|
||||
int64 device_id = 4;
|
||||
int64 command_id = 5;
|
||||
string command_code = 6;
|
||||
map<string, string> param_values = 7;
|
||||
map<string, string> result_values = 8;
|
||||
string status = 9;
|
||||
string error_code = 10;
|
||||
string error_message = 11;
|
||||
string source = 12;
|
||||
int64 source_user_id = 13;
|
||||
int64 occurred_at = 14;
|
||||
int64 sent_at = 15;
|
||||
int64 finished_at = 16;
|
||||
int32 schema_version = 17;
|
||||
int64 create_time = 18;
|
||||
int64 update_time = 19;
|
||||
}
|
||||
|
||||
message GrpcCommandRecordQuery {
|
||||
int64 device_id = 1;
|
||||
int64 command_id = 2;
|
||||
string status = 3;
|
||||
int64 tenant_id = 4;
|
||||
GrpcPage page = 5;
|
||||
}
|
||||
|
||||
message GrpcRCommandRecordDTO {
|
||||
GrpcR result = 1;
|
||||
GrpcCommandRecordDTO data = 2;
|
||||
}
|
||||
|
||||
message GrpcPageCommandRecordDTO {
|
||||
GrpcPage page = 1;
|
||||
repeated GrpcCommandRecordDTO data = 2;
|
||||
}
|
||||
|
||||
message GrpcRPageCommandRecordDTO {
|
||||
GrpcR result = 1;
|
||||
GrpcPageCommandRecordDTO data = 2;
|
||||
}
|
||||
|
||||
message GrpcStringQuery {
|
||||
string value = 1;
|
||||
}
|
||||
|
||||
message GrpcRString {
|
||||
GrpcR result = 1;
|
||||
string data = 2;
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package api.common.data;
|
||||
|
||||
import "api/common/r.proto";
|
||||
import "api/common/page.proto";
|
||||
import "api/center/data/command_record.proto";
|
||||
|
||||
option java_package = "io.github.pnoker.api.center.data";
|
||||
option java_outer_classname = "EventReportProto";
|
||||
option objc_class_prefix = "Data";
|
||||
option java_multiple_files = true;
|
||||
|
||||
service EventReportApi {
|
||||
rpc Report (GrpcEventReportVO) returns (GrpcRString);
|
||||
rpc GetByRecordId (GrpcStringQuery) returns (GrpcREventRecordDTO);
|
||||
rpc List (GrpcEventRecordQuery) returns (GrpcRPageEventRecordDTO);
|
||||
}
|
||||
|
||||
message GrpcEventReportVO {
|
||||
int64 device_id = 1;
|
||||
int64 event_id = 2;
|
||||
map<string, string> param_values = 3;
|
||||
string message = 4;
|
||||
int64 tenant_id = 5;
|
||||
}
|
||||
|
||||
message GrpcEventRecordDTO {
|
||||
int64 id = 1;
|
||||
string record_id = 2;
|
||||
int64 tenant_id = 3;
|
||||
int64 device_id = 4;
|
||||
int64 event_id = 5;
|
||||
string event_code = 6;
|
||||
int32 event_type_flag = 7;
|
||||
int32 event_level_flag = 8;
|
||||
map<string, string> param_values = 9;
|
||||
string message = 10;
|
||||
int64 occur_time = 11;
|
||||
int64 receive_time = 12;
|
||||
int32 acknowledge_flag = 13;
|
||||
int32 schema_version = 14;
|
||||
int64 create_time = 15;
|
||||
int64 update_time = 16;
|
||||
}
|
||||
|
||||
message GrpcEventRecordQuery {
|
||||
int64 device_id = 1;
|
||||
int64 event_id = 2;
|
||||
int32 event_type_flag = 3;
|
||||
int64 tenant_id = 4;
|
||||
GrpcPage page = 5;
|
||||
}
|
||||
|
||||
message GrpcREventRecordDTO {
|
||||
GrpcR result = 1;
|
||||
GrpcEventRecordDTO data = 2;
|
||||
}
|
||||
|
||||
message GrpcPageEventRecordDTO {
|
||||
GrpcPage page = 1;
|
||||
repeated GrpcEventRecordDTO data = 2;
|
||||
}
|
||||
|
||||
message GrpcRPageEventRecordDTO {
|
||||
GrpcR result = 1;
|
||||
GrpcPageEventRecordDTO data = 2;
|
||||
}
|
||||
+15
@@ -106,6 +106,21 @@ public class RabbitConstant {
|
||||
// Point Command Result
|
||||
public static String TOPIC_EXCHANGE_POINT_COMMAND_RESULT = "dc3.e.point_command_result";
|
||||
public static String QUEUE_POINT_COMMAND_RESULT = "dc3.q.point_command_result";
|
||||
// Custom Command
|
||||
public static String TOPIC_EXCHANGE_COMMAND = "dc3.e.command";
|
||||
public static String QUEUE_COMMAND_PREFIX = "dc3.q.command.";
|
||||
public static String ROUTING_COMMAND_PREFIX = "dc3.r.command.";
|
||||
// Custom Command Result
|
||||
public static String TOPIC_EXCHANGE_COMMAND_RESULT = "dc3.e.command_result";
|
||||
public static String QUEUE_COMMAND_RESULT = "dc3.q.command_result";
|
||||
public static String ROUTING_COMMAND_RESULT = "dc3.r.command_result";
|
||||
// Custom Command Dead Letter
|
||||
public static String TOPIC_EXCHANGE_COMMAND_DEAD = "dc3.e.command_dead";
|
||||
public static String QUEUE_COMMAND_DEAD = "dc3.q.command_dead";
|
||||
// Event Report
|
||||
public static String TOPIC_EXCHANGE_EVENT = "dc3.e.event";
|
||||
public static String QUEUE_EVENT_PREFIX = "dc3.q.event.";
|
||||
public static String ROUTING_EVENT_PREFIX = "dc3.r.event.";
|
||||
|
||||
private RabbitConstant() {
|
||||
throw new IllegalStateException(BaseConstant.UTILITY_CLASS);
|
||||
|
||||
+4
@@ -54,6 +54,10 @@ public class DataConstant {
|
||||
|
||||
public static final String MESSAGE_URL_PREFIX = "/message";
|
||||
|
||||
public static final String COMMAND_RECORD_URL_PREFIX = "/command_record";
|
||||
|
||||
public static final String EVENT_REPORT_URL_PREFIX = "/event_report";
|
||||
|
||||
public static final String DRIVER_STATUS_URL_PREFIX = "/driver/status";
|
||||
|
||||
public static final String DRIVER_EVENT_URL_PREFIX = "/driver/event";
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.github.pnoker.common.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.EnumValue;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Enumeration of custom command record sources.
|
||||
*
|
||||
* @author pnoker
|
||||
* @version 2026.5.23
|
||||
* @since 2026.5.23
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum CommandRecordSourceEnum {
|
||||
|
||||
/**
|
||||
* HTTP API
|
||||
*/
|
||||
HTTP((byte) 0, "http", "HTTP API"),
|
||||
|
||||
/**
|
||||
* gRPC API
|
||||
*/
|
||||
GRPC((byte) 1, "grpc", "gRPC API"),
|
||||
|
||||
/**
|
||||
* Agentic center
|
||||
*/
|
||||
AGENTIC((byte) 2, "agentic", "Agentic center"),
|
||||
;
|
||||
|
||||
@EnumValue
|
||||
private final Byte index;
|
||||
|
||||
private final String code;
|
||||
|
||||
private final String remark;
|
||||
|
||||
public static CommandRecordSourceEnum ofIndex(Byte index) {
|
||||
Optional<CommandRecordSourceEnum> any = Arrays.stream(CommandRecordSourceEnum.values())
|
||||
.filter(type -> type.getIndex().equals(index))
|
||||
.findFirst();
|
||||
return any.orElse(null);
|
||||
}
|
||||
|
||||
public static CommandRecordSourceEnum ofCode(String code) {
|
||||
Optional<CommandRecordSourceEnum> any = Arrays.stream(CommandRecordSourceEnum.values())
|
||||
.filter(type -> type.getCode().equals(code))
|
||||
.findFirst();
|
||||
return any.orElse(null);
|
||||
}
|
||||
|
||||
public static CommandRecordSourceEnum ofName(String name) {
|
||||
try {
|
||||
return valueOf(name);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.github.pnoker.common.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.EnumValue;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Enumeration of event record acknowledge flags.
|
||||
*
|
||||
* @author pnoker
|
||||
* @version 2026.5.23
|
||||
* @since 2026.5.23
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum EventRecordAcknowledgeFlagEnum {
|
||||
|
||||
/**
|
||||
* Not acknowledged
|
||||
*/
|
||||
NO((byte) 0, "no", "Not acknowledged"),
|
||||
|
||||
/**
|
||||
* Acknowledged
|
||||
*/
|
||||
YES((byte) 1, "yes", "Acknowledged"),
|
||||
;
|
||||
|
||||
@EnumValue
|
||||
private final Byte index;
|
||||
|
||||
private final String code;
|
||||
|
||||
private final String remark;
|
||||
|
||||
public static EventRecordAcknowledgeFlagEnum ofIndex(Byte index) {
|
||||
Optional<EventRecordAcknowledgeFlagEnum> any = Arrays.stream(EventRecordAcknowledgeFlagEnum.values())
|
||||
.filter(type -> type.getIndex().equals(index))
|
||||
.findFirst();
|
||||
return any.orElse(null);
|
||||
}
|
||||
|
||||
public static EventRecordAcknowledgeFlagEnum ofCode(String code) {
|
||||
Optional<EventRecordAcknowledgeFlagEnum> any = Arrays.stream(EventRecordAcknowledgeFlagEnum.values())
|
||||
.filter(type -> type.getCode().equals(code))
|
||||
.findFirst();
|
||||
return any.orElse(null);
|
||||
}
|
||||
|
||||
public static EventRecordAcknowledgeFlagEnum ofName(String name) {
|
||||
try {
|
||||
return valueOf(name);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+46
@@ -267,4 +267,50 @@ public class DataTopicConfig {
|
||||
.with(RabbitConstant.ROUTING_POINT_COMMAND_RESULT + ".*");
|
||||
}
|
||||
|
||||
// ===== Custom command dead letter ========================================
|
||||
|
||||
@Bean
|
||||
Queue commandDeadQueue() {
|
||||
return QueueBuilder.durable(RabbitConstant.QUEUE_COMMAND_DEAD).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
Binding commandDeadBinding(Queue commandDeadQueue, TopicExchange commandDeadExchange) {
|
||||
return BindingBuilder.bind(commandDeadQueue)
|
||||
.to(commandDeadExchange)
|
||||
.with("#");
|
||||
}
|
||||
|
||||
// ===== Custom command result =============================================
|
||||
|
||||
@Bean
|
||||
Queue commandResultQueue() {
|
||||
return QueueBuilder.durable(RabbitConstant.QUEUE_COMMAND_RESULT)
|
||||
.ttl(60000)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
Binding commandResultBinding(Queue commandResultQueue, TopicExchange commandResultExchange) {
|
||||
return BindingBuilder.bind(commandResultQueue)
|
||||
.to(commandResultExchange)
|
||||
.with(RabbitConstant.ROUTING_COMMAND_RESULT + ".*");
|
||||
}
|
||||
|
||||
// ===== Event report ======================================================
|
||||
|
||||
@Bean
|
||||
Queue eventReportQueue() {
|
||||
return QueueBuilder.durable(RabbitConstant.QUEUE_EVENT_PREFIX + "report")
|
||||
.ttl(60000)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
Binding eventReportBinding(Queue eventReportQueue, TopicExchange eventExchange) {
|
||||
return BindingBuilder.bind(eventReportQueue)
|
||||
.to(eventExchange)
|
||||
.with(RabbitConstant.ROUTING_EVENT_PREFIX + "*");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.github.pnoker.common.data.biz;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import io.github.pnoker.common.data.entity.model.CommandRecordDO;
|
||||
import io.github.pnoker.common.data.entity.vo.CommandCallVO;
|
||||
import io.github.pnoker.common.data.entity.vo.CommandRecordQueryVO;
|
||||
|
||||
/**
|
||||
* Business service for custom command call operations.
|
||||
*
|
||||
* @author pnoker
|
||||
* @version 2026.5.23
|
||||
* @since 2026.5.23
|
||||
*/
|
||||
public interface CommandRecordService {
|
||||
|
||||
String call(Long tenantId, CommandCallVO entityVO);
|
||||
|
||||
CommandRecordDO getByRecordId(String recordId);
|
||||
|
||||
Page<CommandRecordDO> list(Long tenantId, CommandRecordQueryVO queryVO);
|
||||
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.github.pnoker.common.data.biz;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import io.github.pnoker.common.data.entity.model.EventRecordDO;
|
||||
import io.github.pnoker.common.data.entity.vo.EventRecordQueryVO;
|
||||
import io.github.pnoker.common.data.entity.vo.EventReportVO;
|
||||
|
||||
/**
|
||||
* Business service for event report operations.
|
||||
*
|
||||
* @author pnoker
|
||||
* @version 2026.5.23
|
||||
* @since 2026.5.23
|
||||
*/
|
||||
public interface EventReportService {
|
||||
|
||||
String report(Long tenantId, EventReportVO entityVO);
|
||||
|
||||
EventRecordDO getByRecordId(String recordId);
|
||||
|
||||
Page<EventRecordDO> list(Long tenantId, EventRecordQueryVO queryVO);
|
||||
|
||||
}
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.github.pnoker.common.data.biz.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import io.github.pnoker.common.constant.common.ExceptionConstant;
|
||||
import io.github.pnoker.common.constant.driver.RabbitConstant;
|
||||
import io.github.pnoker.common.data.biz.CommandRecordService;
|
||||
import io.github.pnoker.common.data.dal.CommandRecordManager;
|
||||
import io.github.pnoker.common.data.entity.model.CommandRecordDO;
|
||||
import io.github.pnoker.common.data.entity.model.EntityStateDO;
|
||||
import io.github.pnoker.common.data.entity.vo.CommandCallVO;
|
||||
import io.github.pnoker.common.data.entity.vo.CommandRecordQueryVO;
|
||||
import io.github.pnoker.common.data.mapper.EntityStateMapper;
|
||||
import io.github.pnoker.common.entity.dto.CommandCallDTO;
|
||||
import io.github.pnoker.common.enums.CommandRecordSourceEnum;
|
||||
import io.github.pnoker.common.enums.EnableFlagEnum;
|
||||
import io.github.pnoker.common.enums.EntityStatusEnum;
|
||||
import io.github.pnoker.common.enums.EntityTypeFlagEnum;
|
||||
import io.github.pnoker.common.enums.PointCommandStatusEnum;
|
||||
import io.github.pnoker.common.exception.NotFoundException;
|
||||
import io.github.pnoker.common.exception.ServiceException;
|
||||
import io.github.pnoker.common.exception.UnAuthorizedException;
|
||||
import io.github.pnoker.common.facade.api.CommandFacade;
|
||||
import io.github.pnoker.common.facade.api.DeviceFacade;
|
||||
import io.github.pnoker.common.facade.api.DriverFacade;
|
||||
import io.github.pnoker.common.facade.entity.bo.FacadeCommandBO;
|
||||
import io.github.pnoker.common.facade.entity.bo.FacadeDeviceBO;
|
||||
import io.github.pnoker.common.facade.entity.bo.FacadeDriverBO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.amqp.rabbit.connection.CorrelationData;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Business service implementation for custom command call operations.
|
||||
*
|
||||
* @author pnoker
|
||||
* @version 2026.5.23
|
||||
* @since 2026.5.23
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CommandRecordServiceImpl implements CommandRecordService {
|
||||
|
||||
private final DeviceFacade deviceFacade;
|
||||
|
||||
private final DriverFacade driverFacade;
|
||||
|
||||
private final CommandFacade commandFacade;
|
||||
|
||||
private final RabbitTemplate rabbitTemplate;
|
||||
|
||||
private final CommandRecordManager commandRecordManager;
|
||||
|
||||
private final EntityStateMapper entityStateMapper;
|
||||
|
||||
@Override
|
||||
public String call(Long tenantId, CommandCallVO entityVO) {
|
||||
validateCommandScope(tenantId, entityVO.getDeviceId(), entityVO.getCommandId());
|
||||
|
||||
FacadeDriverBO driver = driverFacade.getByDeviceId(tenantId, entityVO.getDeviceId());
|
||||
if (Objects.isNull(driver)) {
|
||||
throw new ServiceException("No driver registered for this device");
|
||||
}
|
||||
checkDriverOnline(tenantId, driver.getId());
|
||||
|
||||
FacadeCommandBO command = commandFacade.getById(tenantId, entityVO.getCommandId());
|
||||
|
||||
String recordId = UUID.randomUUID().toString();
|
||||
LocalDateTime nowLocal = LocalDateTime.now();
|
||||
|
||||
CommandRecordDO recordDO = new CommandRecordDO();
|
||||
recordDO.setRecordId(recordId);
|
||||
recordDO.setTenantId(tenantId);
|
||||
recordDO.setDeviceId(entityVO.getDeviceId());
|
||||
recordDO.setCommandId(entityVO.getCommandId());
|
||||
recordDO.setCommandCode(command.getCommandCode());
|
||||
recordDO.setParamValues(Objects.isNull(entityVO.getParamValues()) ? null : entityVO.getParamValues().toString());
|
||||
recordDO.setStatus(PointCommandStatusEnum.PENDING.getCode());
|
||||
recordDO.setSource(CommandRecordSourceEnum.HTTP.getCode());
|
||||
recordDO.setOccurredAt(nowLocal);
|
||||
recordDO.setExpireAt(nowLocal.plusSeconds(30));
|
||||
recordDO.setSchemaVersion((short) 1);
|
||||
commandRecordManager.save(recordDO);
|
||||
|
||||
publishCommand(CommandCallDTO.builder()
|
||||
.recordId(recordId)
|
||||
.tenantId(tenantId)
|
||||
.deviceId(entityVO.getDeviceId())
|
||||
.commandId(entityVO.getCommandId())
|
||||
.commandCode(command.getCommandCode())
|
||||
.paramValues(entityVO.getParamValues())
|
||||
.source(CommandRecordSourceEnum.HTTP.getCode())
|
||||
.occurredAt(java.time.Instant.now())
|
||||
.expireAt(java.time.Instant.now().plusSeconds(30))
|
||||
.schemaVersion(1)
|
||||
.build(), driver.getServiceName(), recordId);
|
||||
|
||||
recordDO.setStatus(PointCommandStatusEnum.SENT.getCode());
|
||||
recordDO.setSentAt(LocalDateTime.now());
|
||||
commandRecordManager.updateById(recordDO);
|
||||
|
||||
return recordId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandRecordDO getByRecordId(String recordId) {
|
||||
return commandRecordManager.lambdaQuery()
|
||||
.eq(CommandRecordDO::getRecordId, recordId)
|
||||
.one();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<CommandRecordDO> list(Long tenantId, CommandRecordQueryVO queryVO) {
|
||||
LambdaQueryWrapper<CommandRecordDO> wrapper = new LambdaQueryWrapper<CommandRecordDO>()
|
||||
.eq(CommandRecordDO::getTenantId, tenantId)
|
||||
.eq(Objects.nonNull(queryVO.getDeviceId()), CommandRecordDO::getDeviceId, queryVO.getDeviceId())
|
||||
.eq(Objects.nonNull(queryVO.getCommandId()), CommandRecordDO::getCommandId, queryVO.getCommandId())
|
||||
.eq(Objects.nonNull(queryVO.getStatus()), CommandRecordDO::getStatus, queryVO.getStatus())
|
||||
.orderByDesc(CommandRecordDO::getOccurredAt);
|
||||
return commandRecordManager.page(queryVO.toPage(), wrapper);
|
||||
}
|
||||
|
||||
private void checkDriverOnline(Long tenantId, Long driverId) {
|
||||
EntityStateDO driverState = entityStateMapper.selectOne(
|
||||
new LambdaQueryWrapper<EntityStateDO>()
|
||||
.eq(EntityStateDO::getTenantId, tenantId)
|
||||
.eq(EntityStateDO::getEntityTypeFlag, EntityTypeFlagEnum.DRIVER.getIndex())
|
||||
.eq(EntityStateDO::getEntityId, driverId));
|
||||
if (Objects.isNull(driverState) || !EntityStatusEnum.ONLINE.getIndex().equals(driverState.getStateFlag())) {
|
||||
throw new ServiceException("Driver is offline");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateCommandScope(Long tenantId, Long deviceId, Long commandId) {
|
||||
FacadeDeviceBO device = deviceFacade.getById(tenantId, deviceId);
|
||||
if (Objects.isNull(device)) {
|
||||
throw new NotFoundException("Device does not exist");
|
||||
}
|
||||
if (EnableFlagEnum.DISABLE.equals(device.getEnableFlag())) {
|
||||
throw new ServiceException("Device is disabled");
|
||||
}
|
||||
|
||||
FacadeCommandBO command = commandFacade.getById(tenantId, commandId);
|
||||
if (Objects.isNull(command)) {
|
||||
throw new NotFoundException("Command does not exist");
|
||||
}
|
||||
if (EnableFlagEnum.DISABLE.equals(command.getEnableFlag())) {
|
||||
throw new ServiceException("Command is disabled");
|
||||
}
|
||||
if (Objects.isNull(device.getProfileId()) || !Objects.equals(device.getProfileId(), command.getProfileId())) {
|
||||
throw new UnAuthorizedException(ExceptionConstant.NO_AVAILABLE_AUTH);
|
||||
}
|
||||
}
|
||||
|
||||
private void publishCommand(CommandCallDTO dto, String serviceName, String recordId) {
|
||||
CorrelationData correlationData = new CorrelationData(recordId);
|
||||
rabbitTemplate.convertAndSend(RabbitConstant.TOPIC_EXCHANGE_COMMAND,
|
||||
RabbitConstant.ROUTING_COMMAND_PREFIX + serviceName, dto, correlationData);
|
||||
}
|
||||
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.github.pnoker.common.data.biz.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import io.github.pnoker.common.constant.common.ExceptionConstant;
|
||||
import io.github.pnoker.common.data.biz.EventReportService;
|
||||
import io.github.pnoker.common.data.dal.EventRecordManager;
|
||||
import io.github.pnoker.common.data.entity.model.EventRecordDO;
|
||||
import io.github.pnoker.common.data.entity.vo.EventRecordQueryVO;
|
||||
import io.github.pnoker.common.data.entity.vo.EventReportVO;
|
||||
import io.github.pnoker.common.enums.EnableFlagEnum;
|
||||
import io.github.pnoker.common.enums.EventRecordAcknowledgeFlagEnum;
|
||||
import io.github.pnoker.common.exception.NotFoundException;
|
||||
import io.github.pnoker.common.exception.ServiceException;
|
||||
import io.github.pnoker.common.exception.UnAuthorizedException;
|
||||
import io.github.pnoker.common.facade.api.DeviceFacade;
|
||||
import io.github.pnoker.common.facade.api.EventFacade;
|
||||
import io.github.pnoker.common.facade.entity.bo.FacadeDeviceBO;
|
||||
import io.github.pnoker.common.facade.entity.bo.FacadeEventBO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Business service implementation for event report operations.
|
||||
*
|
||||
* @author pnoker
|
||||
* @version 2026.5.23
|
||||
* @since 2026.5.23
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class EventReportServiceImpl implements EventReportService {
|
||||
|
||||
private final DeviceFacade deviceFacade;
|
||||
|
||||
private final EventFacade eventFacade;
|
||||
|
||||
private final EventRecordManager eventRecordManager;
|
||||
|
||||
@Override
|
||||
public String report(Long tenantId, EventReportVO entityVO) {
|
||||
validateEventScope(tenantId, entityVO.getDeviceId(), entityVO.getEventId());
|
||||
|
||||
FacadeEventBO event = eventFacade.getById(tenantId, entityVO.getEventId());
|
||||
|
||||
String recordId = UUID.randomUUID().toString();
|
||||
LocalDateTime nowLocal = LocalDateTime.now();
|
||||
|
||||
EventRecordDO recordDO = new EventRecordDO();
|
||||
recordDO.setRecordId(recordId);
|
||||
recordDO.setTenantId(tenantId);
|
||||
recordDO.setDeviceId(entityVO.getDeviceId());
|
||||
recordDO.setEventId(entityVO.getEventId());
|
||||
recordDO.setEventCode(event.getEventCode());
|
||||
recordDO.setEventTypeFlag(event.getEventTypeFlag().getIndex());
|
||||
recordDO.setEventLevelFlag(event.getEventLevelFlag().getIndex());
|
||||
recordDO.setParamValues(Objects.isNull(entityVO.getParamValues()) ? null : entityVO.getParamValues().toString());
|
||||
recordDO.setMessage(entityVO.getMessage());
|
||||
recordDO.setOccurTime(nowLocal);
|
||||
recordDO.setReceiveTime(nowLocal);
|
||||
recordDO.setAcknowledgeFlag(EventRecordAcknowledgeFlagEnum.NO.getIndex());
|
||||
recordDO.setSchemaVersion((short) 1);
|
||||
eventRecordManager.save(recordDO);
|
||||
|
||||
return recordId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventRecordDO getByRecordId(String recordId) {
|
||||
return eventRecordManager.lambdaQuery()
|
||||
.eq(EventRecordDO::getRecordId, recordId)
|
||||
.one();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<EventRecordDO> list(Long tenantId, EventRecordQueryVO queryVO) {
|
||||
LambdaQueryWrapper<EventRecordDO> wrapper = new LambdaQueryWrapper<EventRecordDO>()
|
||||
.eq(EventRecordDO::getTenantId, tenantId)
|
||||
.eq(Objects.nonNull(queryVO.getDeviceId()), EventRecordDO::getDeviceId, queryVO.getDeviceId())
|
||||
.eq(Objects.nonNull(queryVO.getEventId()), EventRecordDO::getEventId, queryVO.getEventId())
|
||||
.eq(Objects.nonNull(queryVO.getEventTypeFlag()), EventRecordDO::getEventTypeFlag, queryVO.getEventTypeFlag())
|
||||
.orderByDesc(EventRecordDO::getOccurTime);
|
||||
return eventRecordManager.page(queryVO.toPage(), wrapper);
|
||||
}
|
||||
|
||||
private void validateEventScope(Long tenantId, Long deviceId, Long eventId) {
|
||||
FacadeDeviceBO device = deviceFacade.getById(tenantId, deviceId);
|
||||
if (Objects.isNull(device)) {
|
||||
throw new NotFoundException("Device does not exist");
|
||||
}
|
||||
if (EnableFlagEnum.DISABLE.equals(device.getEnableFlag())) {
|
||||
throw new ServiceException("Device is disabled");
|
||||
}
|
||||
|
||||
FacadeEventBO event = eventFacade.getById(tenantId, eventId);
|
||||
if (Objects.isNull(event)) {
|
||||
throw new NotFoundException("Event does not exist");
|
||||
}
|
||||
if (EnableFlagEnum.DISABLE.equals(event.getEnableFlag())) {
|
||||
throw new ServiceException("Event is disabled");
|
||||
}
|
||||
if (Objects.isNull(device.getProfileId()) || !Objects.equals(device.getProfileId(), event.getProfileId())) {
|
||||
throw new UnAuthorizedException(ExceptionConstant.NO_AVAILABLE_AUTH);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.github.pnoker.common.data.controller;
|
||||
|
||||
import io.github.pnoker.common.base.BaseController;
|
||||
import io.github.pnoker.common.constant.service.DataConstant;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import io.github.pnoker.common.data.biz.CommandRecordService;
|
||||
import io.github.pnoker.common.data.entity.model.CommandRecordDO;
|
||||
import io.github.pnoker.common.data.entity.vo.CommandCallVO;
|
||||
import io.github.pnoker.common.data.entity.vo.CommandRecordQueryVO;
|
||||
import io.github.pnoker.common.entity.R;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* REST controller for custom command call management.
|
||||
*
|
||||
* @author pnoker
|
||||
* @version 2026.5.23
|
||||
* @since 2026.5.23
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping(DataConstant.COMMAND_RECORD_URL_PREFIX)
|
||||
@RequiredArgsConstructor
|
||||
public class CommandRecordController implements BaseController {
|
||||
|
||||
private final CommandRecordService commandRecordService;
|
||||
|
||||
@PostMapping("/call")
|
||||
public Mono<R<String>> call(@Validated @RequestBody CommandCallVO entityVO) {
|
||||
return getTenantId().flatMap(tenantId -> async(() -> {
|
||||
String recordId = commandRecordService.call(tenantId, entityVO);
|
||||
return R.ok(recordId);
|
||||
}));
|
||||
}
|
||||
|
||||
@GetMapping("/{recordId}")
|
||||
public Mono<R<CommandRecordDO>> getByRecordId(@NotBlank @PathVariable String recordId) {
|
||||
return async(() -> R.ok(commandRecordService.getByRecordId(recordId)));
|
||||
}
|
||||
|
||||
@PostMapping("/list")
|
||||
public Mono<R<Page<CommandRecordDO>>> list(@RequestBody(required = false) CommandRecordQueryVO queryVO) {
|
||||
return getTenantId().flatMap(tenantId -> async(() -> {
|
||||
CommandRecordQueryVO query = Objects.isNull(queryVO) ? new CommandRecordQueryVO() : queryVO;
|
||||
return R.ok(commandRecordService.list(tenantId, query));
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.github.pnoker.common.data.controller;
|
||||
|
||||
import io.github.pnoker.common.base.BaseController;
|
||||
import io.github.pnoker.common.constant.service.DataConstant;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import io.github.pnoker.common.data.biz.EventReportService;
|
||||
import io.github.pnoker.common.data.entity.model.EventRecordDO;
|
||||
import io.github.pnoker.common.data.entity.vo.EventRecordQueryVO;
|
||||
import io.github.pnoker.common.data.entity.vo.EventReportVO;
|
||||
import io.github.pnoker.common.entity.R;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* REST controller for event report management.
|
||||
*
|
||||
* @author pnoker
|
||||
* @version 2026.5.23
|
||||
* @since 2026.5.23
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping(DataConstant.EVENT_REPORT_URL_PREFIX)
|
||||
@RequiredArgsConstructor
|
||||
public class EventReportController implements BaseController {
|
||||
|
||||
private final EventReportService eventReportService;
|
||||
|
||||
@PostMapping("/report")
|
||||
public Mono<R<String>> report(@Validated @RequestBody EventReportVO entityVO) {
|
||||
return getTenantId().flatMap(tenantId -> async(() -> {
|
||||
String recordId = eventReportService.report(tenantId, entityVO);
|
||||
return R.ok(recordId);
|
||||
}));
|
||||
}
|
||||
|
||||
@GetMapping("/{recordId}")
|
||||
public Mono<R<EventRecordDO>> getByRecordId(@NotBlank @PathVariable String recordId) {
|
||||
return async(() -> R.ok(eventReportService.getByRecordId(recordId)));
|
||||
}
|
||||
|
||||
@PostMapping("/list")
|
||||
public Mono<R<Page<EventRecordDO>>> list(@RequestBody(required = false) EventRecordQueryVO queryVO) {
|
||||
return getTenantId().flatMap(tenantId -> async(() -> {
|
||||
EventRecordQueryVO query = Objects.isNull(queryVO) ? new EventRecordQueryVO() : queryVO;
|
||||
return R.ok(eventReportService.list(tenantId, query));
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.github.pnoker.common.data.dal;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import io.github.pnoker.common.data.entity.model.CommandRecordDO;
|
||||
|
||||
/**
|
||||
* Manager interface for dc3_command_record table.
|
||||
*
|
||||
* @author pnoker
|
||||
* @version 2026.5.23
|
||||
* @since 2026.5.23
|
||||
*/
|
||||
public interface CommandRecordManager extends IService<CommandRecordDO> {
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.github.pnoker.common.data.dal;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import io.github.pnoker.common.data.entity.model.EventRecordDO;
|
||||
|
||||
/**
|
||||
* Manager interface for dc3_event_record table.
|
||||
*
|
||||
* @author pnoker
|
||||
* @version 2026.5.23
|
||||
* @since 2026.5.23
|
||||
*/
|
||||
public interface EventRecordManager extends IService<EventRecordDO> {
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.github.pnoker.common.data.dal.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import io.github.pnoker.common.data.dal.CommandRecordManager;
|
||||
import io.github.pnoker.common.data.entity.model.CommandRecordDO;
|
||||
import io.github.pnoker.common.data.mapper.CommandRecordMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Manager implementation for dc3_command_record table.
|
||||
*
|
||||
* @author pnoker
|
||||
* @version 2026.5.23
|
||||
* @since 2026.5.23
|
||||
*/
|
||||
@Service
|
||||
public class CommandRecordManagerImpl extends ServiceImpl<CommandRecordMapper, CommandRecordDO> implements CommandRecordManager {
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.github.pnoker.common.data.dal.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import io.github.pnoker.common.data.dal.EventRecordManager;
|
||||
import io.github.pnoker.common.data.entity.model.EventRecordDO;
|
||||
import io.github.pnoker.common.data.mapper.EventRecordMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Manager implementation for dc3_event_record table.
|
||||
*
|
||||
* @author pnoker
|
||||
* @version 2026.5.23
|
||||
* @since 2026.5.23
|
||||
*/
|
||||
@Service
|
||||
public class EventRecordManagerImpl extends ServiceImpl<EventRecordMapper, EventRecordDO> implements EventRecordManager {
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.github.pnoker.common.data.entity.model;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Persistence object for the dc3_command_record table.
|
||||
*
|
||||
* @author pnoker
|
||||
* @version 2026.5.23
|
||||
* @since 2026.5.23
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@TableName(value = "dc3_command_record")
|
||||
public class CommandRecordDO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
@TableField("record_id")
|
||||
private String recordId;
|
||||
|
||||
@TableField("tenant_id")
|
||||
private Long tenantId;
|
||||
|
||||
@TableField("device_id")
|
||||
private Long deviceId;
|
||||
|
||||
@TableField("command_id")
|
||||
private Long commandId;
|
||||
|
||||
@TableField("command_code")
|
||||
private String commandCode;
|
||||
|
||||
@TableField("param_values")
|
||||
private String paramValues;
|
||||
|
||||
@TableField("result_values")
|
||||
private String resultValues;
|
||||
|
||||
@TableField("status")
|
||||
private String status;
|
||||
|
||||
@TableField("error_code")
|
||||
private String errorCode;
|
||||
|
||||
@TableField("error_message")
|
||||
private String errorMessage;
|
||||
|
||||
@TableField("source")
|
||||
private String source;
|
||||
|
||||
@TableField("source_user_id")
|
||||
private Long sourceUserId;
|
||||
|
||||
@TableField("occurred_at")
|
||||
private LocalDateTime occurredAt;
|
||||
|
||||
@TableField("sent_at")
|
||||
private LocalDateTime sentAt;
|
||||
|
||||
@TableField("finished_at")
|
||||
private LocalDateTime finishedAt;
|
||||
|
||||
@TableField("expire_at")
|
||||
private LocalDateTime expireAt;
|
||||
|
||||
@TableField("schema_version")
|
||||
private Short schemaVersion;
|
||||
|
||||
@TableField("create_time")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@TableField("update_time")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.github.pnoker.common.data.entity.model;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Persistence object for the dc3_event_record table.
|
||||
*
|
||||
* @author pnoker
|
||||
* @version 2026.5.23
|
||||
* @since 2026.5.23
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@TableName(value = "dc3_event_record")
|
||||
public class EventRecordDO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
@TableField("record_id")
|
||||
private String recordId;
|
||||
|
||||
@TableField("tenant_id")
|
||||
private Long tenantId;
|
||||
|
||||
@TableField("device_id")
|
||||
private Long deviceId;
|
||||
|
||||
@TableField("event_id")
|
||||
private Long eventId;
|
||||
|
||||
@TableField("event_code")
|
||||
private String eventCode;
|
||||
|
||||
@TableField("event_type_flag")
|
||||
private Byte eventTypeFlag;
|
||||
|
||||
@TableField("event_level_flag")
|
||||
private Byte eventLevelFlag;
|
||||
|
||||
@TableField("param_values")
|
||||
private String paramValues;
|
||||
|
||||
@TableField("message")
|
||||
private String message;
|
||||
|
||||
@TableField("occur_time")
|
||||
private LocalDateTime occurTime;
|
||||
|
||||
@TableField("receive_time")
|
||||
private LocalDateTime receiveTime;
|
||||
|
||||
@TableField("acknowledge_flag")
|
||||
private Byte acknowledgeFlag;
|
||||
|
||||
@TableField("schema_version")
|
||||
private Short schemaVersion;
|
||||
|
||||
@TableField("create_time")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@TableField("update_time")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.github.pnoker.common.data.entity.vo;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* VO for submitting a custom command call.
|
||||
*
|
||||
* @author pnoker
|
||||
* @version 2026.5.23
|
||||
* @since 2026.5.23
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class CommandCallVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@NotNull
|
||||
private Long deviceId;
|
||||
|
||||
@NotNull
|
||||
private Long commandId;
|
||||
|
||||
private Map<String, String> paramValues;
|
||||
|
||||
private String commandId_;
|
||||
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.github.pnoker.common.data.entity.vo;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import io.github.pnoker.common.data.entity.model.CommandRecordDO;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* VO for querying command records with pagination and filters.
|
||||
*
|
||||
* @author pnoker
|
||||
* @version 2026.5.23
|
||||
* @since 2026.5.23
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class CommandRecordQueryVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long deviceId;
|
||||
|
||||
private Long commandId;
|
||||
|
||||
private String status;
|
||||
|
||||
private Integer page = 1;
|
||||
|
||||
private Integer size = 20;
|
||||
|
||||
public <T> Page<T> toPage() {
|
||||
return new Page<>(page, size);
|
||||
}
|
||||
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.github.pnoker.common.data.entity.vo;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import io.github.pnoker.common.data.entity.model.EventRecordDO;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* VO for querying event records with pagination and filters.
|
||||
*
|
||||
* @author pnoker
|
||||
* @version 2026.5.23
|
||||
* @since 2026.5.23
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class EventRecordQueryVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long deviceId;
|
||||
|
||||
private Long eventId;
|
||||
|
||||
private Byte eventTypeFlag;
|
||||
|
||||
private Integer page = 1;
|
||||
|
||||
private Integer size = 20;
|
||||
|
||||
public <T> Page<T> toPage() {
|
||||
return new Page<>(page, size);
|
||||
}
|
||||
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.github.pnoker.common.data.entity.vo;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* VO for reporting an event from a device or external system.
|
||||
*
|
||||
* @author pnoker
|
||||
* @version 2026.5.23
|
||||
* @since 2026.5.23
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class EventReportVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@NotNull
|
||||
private Long deviceId;
|
||||
|
||||
@NotNull
|
||||
private Long eventId;
|
||||
|
||||
private Map<String, String> paramValues;
|
||||
|
||||
private String message;
|
||||
|
||||
}
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package io.github.pnoker.common.data.grpc.server;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import io.github.pnoker.api.center.data.CommandRecordApiGrpc;
|
||||
import io.github.pnoker.api.center.data.GrpcCommandCallVO;
|
||||
import io.github.pnoker.api.center.data.GrpcCommandRecordDTO;
|
||||
import io.github.pnoker.api.center.data.GrpcCommandRecordQuery;
|
||||
import io.github.pnoker.api.center.data.GrpcPageCommandRecordDTO;
|
||||
import io.github.pnoker.api.center.data.GrpcRCommandRecordDTO;
|
||||
import io.github.pnoker.api.center.data.GrpcRPageCommandRecordDTO;
|
||||
import io.github.pnoker.api.center.data.GrpcRString;
|
||||
import io.github.pnoker.api.center.data.GrpcStringQuery;
|
||||
import io.github.pnoker.api.common.GrpcPage;
|
||||
import io.github.pnoker.api.common.GrpcR;
|
||||
import io.github.pnoker.common.data.biz.CommandRecordService;
|
||||
import io.github.pnoker.common.data.entity.model.CommandRecordDO;
|
||||
import io.github.pnoker.common.data.entity.vo.CommandCallVO;
|
||||
import io.github.pnoker.common.data.entity.vo.CommandRecordQueryVO;
|
||||
import io.github.pnoker.common.enums.ResponseEnum;
|
||||
import io.grpc.stub.StreamObserver;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* gRPC server implementation for the CommandRecord service.
|
||||
*
|
||||
* @author pnoker
|
||||
* @version 2026.5.23
|
||||
* @since 2026.5.23
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CommandRecordServer extends CommandRecordApiGrpc.CommandRecordApiImplBase {
|
||||
|
||||
private final CommandRecordService commandRecordService;
|
||||
|
||||
@Override
|
||||
public void call(GrpcCommandCallVO request, StreamObserver<GrpcRString> responseObserver) {
|
||||
try {
|
||||
CommandCallVO vo = new CommandCallVO();
|
||||
vo.setDeviceId(request.getDeviceId());
|
||||
vo.setCommandId(request.getCommandId());
|
||||
vo.setParamValues(request.getParamValuesMap());
|
||||
String recordId = commandRecordService.call(request.getTenantId(), vo);
|
||||
|
||||
responseObserver.onNext(GrpcRString.newBuilder()
|
||||
.setResult(GrpcR.newBuilder()
|
||||
.setOk(true)
|
||||
.setCode(ResponseEnum.OK.getCode())
|
||||
.setMessage(ResponseEnum.OK.getText())
|
||||
.build())
|
||||
.setData(recordId)
|
||||
.build());
|
||||
responseObserver.onCompleted();
|
||||
} catch (Exception e) {
|
||||
log.error("CommandRecordServer.call failed", e);
|
||||
responseObserver.onNext(GrpcRString.newBuilder()
|
||||
.setResult(GrpcR.newBuilder()
|
||||
.setOk(false)
|
||||
.setCode(ResponseEnum.FAILURE.getCode())
|
||||
.setMessage(e.getMessage())
|
||||
.build())
|
||||
.build());
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getByRecordId(GrpcStringQuery request, StreamObserver<GrpcRCommandRecordDTO> responseObserver) {
|
||||
try {
|
||||
CommandRecordDO recordDO = commandRecordService.getByRecordId(request.getValue());
|
||||
GrpcRCommandRecordDTO.Builder response = GrpcRCommandRecordDTO.newBuilder();
|
||||
|
||||
if (Objects.nonNull(recordDO)) {
|
||||
response.setResult(GrpcR.newBuilder()
|
||||
.setOk(true)
|
||||
.setCode(ResponseEnum.OK.getCode())
|
||||
.setMessage(ResponseEnum.OK.getText())
|
||||
.build());
|
||||
response.setData(toGrpcDTO(recordDO));
|
||||
} else {
|
||||
response.setResult(GrpcR.newBuilder()
|
||||
.setOk(false)
|
||||
.setCode(ResponseEnum.NO_RESOURCE.getCode())
|
||||
.setMessage(ResponseEnum.NO_RESOURCE.getText())
|
||||
.build());
|
||||
}
|
||||
responseObserver.onNext(response.build());
|
||||
responseObserver.onCompleted();
|
||||
} catch (Exception e) {
|
||||
log.error("CommandRecordServer.getByRecordId failed", e);
|
||||
responseObserver.onNext(GrpcRCommandRecordDTO.newBuilder()
|
||||
.setResult(GrpcR.newBuilder()
|
||||
.setOk(false)
|
||||
.setCode(ResponseEnum.FAILURE.getCode())
|
||||
.setMessage(e.getMessage())
|
||||
.build())
|
||||
.build());
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void list(GrpcCommandRecordQuery request, StreamObserver<GrpcRPageCommandRecordDTO> responseObserver) {
|
||||
try {
|
||||
CommandRecordQueryVO queryVO = new CommandRecordQueryVO();
|
||||
queryVO.setDeviceId(request.getDeviceId() != 0 ? request.getDeviceId() : null);
|
||||
queryVO.setCommandId(request.getCommandId() != 0 ? request.getCommandId() : null);
|
||||
queryVO.setStatus(Objects.isNull(request.getStatus()) || request.getStatus().isEmpty() ? null : request.getStatus());
|
||||
queryVO.setPage(Math.toIntExact(request.getPage().getCurrent()));
|
||||
queryVO.setSize(Math.toIntExact(request.getPage().getSize()));
|
||||
|
||||
Page<CommandRecordDO> page = commandRecordService.list(request.getTenantId(), queryVO);
|
||||
|
||||
GrpcPageCommandRecordDTO.Builder pageDataBuilder = GrpcPageCommandRecordDTO.newBuilder()
|
||||
.setPage(GrpcPage.newBuilder()
|
||||
.setCurrent(page.getCurrent())
|
||||
.setSize(page.getSize())
|
||||
.setTotal(page.getTotal())
|
||||
.setPages(page.getPages())
|
||||
.build());
|
||||
page.getRecords().forEach(record -> pageDataBuilder.addData(toGrpcDTO(record)));
|
||||
|
||||
responseObserver.onNext(GrpcRPageCommandRecordDTO.newBuilder()
|
||||
.setResult(GrpcR.newBuilder()
|
||||
.setOk(true)
|
||||
.setCode(ResponseEnum.OK.getCode())
|
||||
.setMessage(ResponseEnum.OK.getText())
|
||||
.build())
|
||||
.setData(pageDataBuilder.build())
|
||||
.build());
|
||||
responseObserver.onCompleted();
|
||||
} catch (Exception e) {
|
||||
log.error("CommandRecordServer.list failed", e);
|
||||
responseObserver.onNext(GrpcRPageCommandRecordDTO.newBuilder()
|
||||
.setResult(GrpcR.newBuilder()
|
||||
.setOk(false)
|
||||
.setCode(ResponseEnum.FAILURE.getCode())
|
||||
.setMessage(e.getMessage())
|
||||
.build())
|
||||
.build());
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
private GrpcCommandRecordDTO toGrpcDTO(CommandRecordDO recordDO) {
|
||||
return GrpcCommandRecordDTO.newBuilder()
|
||||
.setId(Objects.nonNull(recordDO.getId()) ? recordDO.getId() : 0)
|
||||
.setRecordId(Objects.nonNull(recordDO.getRecordId()) ? recordDO.getRecordId() : "")
|
||||
.setTenantId(Objects.nonNull(recordDO.getTenantId()) ? recordDO.getTenantId() : 0)
|
||||
.setDeviceId(Objects.nonNull(recordDO.getDeviceId()) ? recordDO.getDeviceId() : 0)
|
||||
.setCommandId(Objects.nonNull(recordDO.getCommandId()) ? recordDO.getCommandId() : 0)
|
||||
.setCommandCode(Objects.nonNull(recordDO.getCommandCode()) ? recordDO.getCommandCode() : "")
|
||||
.setStatus(Objects.nonNull(recordDO.getStatus()) ? recordDO.getStatus() : "")
|
||||
.setErrorCode(Objects.nonNull(recordDO.getErrorCode()) ? recordDO.getErrorCode() : "")
|
||||
.setErrorMessage(Objects.nonNull(recordDO.getErrorMessage()) ? recordDO.getErrorMessage() : "")
|
||||
.setSource(Objects.nonNull(recordDO.getSource()) ? recordDO.getSource() : "")
|
||||
.setSourceUserId(Objects.nonNull(recordDO.getSourceUserId()) ? recordDO.getSourceUserId() : 0)
|
||||
.setSchemaVersion(Objects.nonNull(recordDO.getSchemaVersion()) ? recordDO.getSchemaVersion() : 0)
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package io.github.pnoker.common.data.grpc.server;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import io.github.pnoker.api.center.data.EventReportApiGrpc;
|
||||
import io.github.pnoker.api.center.data.GrpcEventRecordDTO;
|
||||
import io.github.pnoker.api.center.data.GrpcEventRecordQuery;
|
||||
import io.github.pnoker.api.center.data.GrpcEventReportVO;
|
||||
import io.github.pnoker.api.center.data.GrpcPageEventRecordDTO;
|
||||
import io.github.pnoker.api.center.data.GrpcREventRecordDTO;
|
||||
import io.github.pnoker.api.center.data.GrpcRPageEventRecordDTO;
|
||||
import io.github.pnoker.api.center.data.GrpcRString;
|
||||
import io.github.pnoker.api.center.data.GrpcStringQuery;
|
||||
import io.github.pnoker.api.common.GrpcPage;
|
||||
import io.github.pnoker.api.common.GrpcR;
|
||||
import io.github.pnoker.common.data.biz.EventReportService;
|
||||
import io.github.pnoker.common.data.entity.model.EventRecordDO;
|
||||
import io.github.pnoker.common.data.entity.vo.EventRecordQueryVO;
|
||||
import io.github.pnoker.common.data.entity.vo.EventReportVO;
|
||||
import io.github.pnoker.common.enums.ResponseEnum;
|
||||
import io.grpc.stub.StreamObserver;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* gRPC server implementation for the EventReport service.
|
||||
*
|
||||
* @author pnoker
|
||||
* @version 2026.5.23
|
||||
* @since 2026.5.23
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class EventReportServer extends EventReportApiGrpc.EventReportApiImplBase {
|
||||
|
||||
private final EventReportService eventReportService;
|
||||
|
||||
@Override
|
||||
public void report(GrpcEventReportVO request, StreamObserver<GrpcRString> responseObserver) {
|
||||
try {
|
||||
EventReportVO vo = new EventReportVO();
|
||||
vo.setDeviceId(request.getDeviceId());
|
||||
vo.setEventId(request.getEventId());
|
||||
vo.setParamValues(request.getParamValuesMap());
|
||||
vo.setMessage(request.getMessage());
|
||||
String recordId = eventReportService.report(request.getTenantId(), vo);
|
||||
|
||||
responseObserver.onNext(GrpcRString.newBuilder()
|
||||
.setResult(GrpcR.newBuilder()
|
||||
.setOk(true)
|
||||
.setCode(ResponseEnum.OK.getCode())
|
||||
.setMessage(ResponseEnum.OK.getText())
|
||||
.build())
|
||||
.setData(recordId)
|
||||
.build());
|
||||
responseObserver.onCompleted();
|
||||
} catch (Exception e) {
|
||||
log.error("EventReportServer.report failed", e);
|
||||
responseObserver.onNext(GrpcRString.newBuilder()
|
||||
.setResult(GrpcR.newBuilder()
|
||||
.setOk(false)
|
||||
.setCode(ResponseEnum.FAILURE.getCode())
|
||||
.setMessage(e.getMessage())
|
||||
.build())
|
||||
.build());
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getByRecordId(GrpcStringQuery request, StreamObserver<GrpcREventRecordDTO> responseObserver) {
|
||||
try {
|
||||
EventRecordDO recordDO = eventReportService.getByRecordId(request.getValue());
|
||||
GrpcREventRecordDTO.Builder response = GrpcREventRecordDTO.newBuilder();
|
||||
|
||||
if (Objects.nonNull(recordDO)) {
|
||||
response.setResult(GrpcR.newBuilder()
|
||||
.setOk(true)
|
||||
.setCode(ResponseEnum.OK.getCode())
|
||||
.setMessage(ResponseEnum.OK.getText())
|
||||
.build());
|
||||
response.setData(toGrpcDTO(recordDO));
|
||||
} else {
|
||||
response.setResult(GrpcR.newBuilder()
|
||||
.setOk(false)
|
||||
.setCode(ResponseEnum.NO_RESOURCE.getCode())
|
||||
.setMessage(ResponseEnum.NO_RESOURCE.getText())
|
||||
.build());
|
||||
}
|
||||
responseObserver.onNext(response.build());
|
||||
responseObserver.onCompleted();
|
||||
} catch (Exception e) {
|
||||
log.error("EventReportServer.getByRecordId failed", e);
|
||||
responseObserver.onNext(GrpcREventRecordDTO.newBuilder()
|
||||
.setResult(GrpcR.newBuilder()
|
||||
.setOk(false)
|
||||
.setCode(ResponseEnum.FAILURE.getCode())
|
||||
.setMessage(e.getMessage())
|
||||
.build())
|
||||
.build());
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void list(GrpcEventRecordQuery request, StreamObserver<GrpcRPageEventRecordDTO> responseObserver) {
|
||||
try {
|
||||
EventRecordQueryVO queryVO = new EventRecordQueryVO();
|
||||
queryVO.setDeviceId(request.getDeviceId() != 0 ? request.getDeviceId() : null);
|
||||
queryVO.setEventId(request.getEventId() != 0 ? request.getEventId() : null);
|
||||
if (request.getEventTypeFlag() != 0) {
|
||||
queryVO.setEventTypeFlag((byte) request.getEventTypeFlag());
|
||||
}
|
||||
queryVO.setPage(Math.toIntExact(request.getPage().getCurrent()));
|
||||
queryVO.setSize(Math.toIntExact(request.getPage().getSize()));
|
||||
|
||||
Page<EventRecordDO> page = eventReportService.list(request.getTenantId(), queryVO);
|
||||
|
||||
GrpcPageEventRecordDTO.Builder pageDataBuilder = GrpcPageEventRecordDTO.newBuilder()
|
||||
.setPage(GrpcPage.newBuilder()
|
||||
.setCurrent(page.getCurrent())
|
||||
.setSize(page.getSize())
|
||||
.setTotal(page.getTotal())
|
||||
.setPages(page.getPages())
|
||||
.build());
|
||||
page.getRecords().forEach(record -> pageDataBuilder.addData(toGrpcDTO(record)));
|
||||
|
||||
responseObserver.onNext(GrpcRPageEventRecordDTO.newBuilder()
|
||||
.setResult(GrpcR.newBuilder()
|
||||
.setOk(true)
|
||||
.setCode(ResponseEnum.OK.getCode())
|
||||
.setMessage(ResponseEnum.OK.getText())
|
||||
.build())
|
||||
.setData(pageDataBuilder.build())
|
||||
.build());
|
||||
responseObserver.onCompleted();
|
||||
} catch (Exception e) {
|
||||
log.error("EventReportServer.list failed", e);
|
||||
responseObserver.onNext(GrpcRPageEventRecordDTO.newBuilder()
|
||||
.setResult(GrpcR.newBuilder()
|
||||
.setOk(false)
|
||||
.setCode(ResponseEnum.FAILURE.getCode())
|
||||
.setMessage(e.getMessage())
|
||||
.build())
|
||||
.build());
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
private GrpcEventRecordDTO toGrpcDTO(EventRecordDO recordDO) {
|
||||
return GrpcEventRecordDTO.newBuilder()
|
||||
.setId(Objects.nonNull(recordDO.getId()) ? recordDO.getId() : 0)
|
||||
.setRecordId(Objects.nonNull(recordDO.getRecordId()) ? recordDO.getRecordId() : "")
|
||||
.setTenantId(Objects.nonNull(recordDO.getTenantId()) ? recordDO.getTenantId() : 0)
|
||||
.setDeviceId(Objects.nonNull(recordDO.getDeviceId()) ? recordDO.getDeviceId() : 0)
|
||||
.setEventId(Objects.nonNull(recordDO.getEventId()) ? recordDO.getEventId() : 0)
|
||||
.setEventCode(Objects.nonNull(recordDO.getEventCode()) ? recordDO.getEventCode() : "")
|
||||
.setEventTypeFlag(Objects.nonNull(recordDO.getEventTypeFlag()) ? recordDO.getEventTypeFlag() : 0)
|
||||
.setEventLevelFlag(Objects.nonNull(recordDO.getEventLevelFlag()) ? recordDO.getEventLevelFlag() : 0)
|
||||
.setMessage(Objects.nonNull(recordDO.getMessage()) ? recordDO.getMessage() : "")
|
||||
.setAcknowledgeFlag(Objects.nonNull(recordDO.getAcknowledgeFlag()) ? recordDO.getAcknowledgeFlag() : 0)
|
||||
.setSchemaVersion(Objects.nonNull(recordDO.getSchemaVersion()) ? recordDO.getSchemaVersion() : 0)
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.github.pnoker.common.data.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import io.github.pnoker.common.data.entity.model.CommandRecordDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* MyBatis-Plus mapper for dc3_command_record table.
|
||||
*
|
||||
* @author pnoker
|
||||
* @version 2026.5.23
|
||||
* @since 2026.5.23
|
||||
*/
|
||||
@Mapper
|
||||
public interface CommandRecordMapper extends BaseMapper<CommandRecordDO> {
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.github.pnoker.common.data.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import io.github.pnoker.common.data.entity.model.EventRecordDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* MyBatis-Plus mapper for dc3_event_record table.
|
||||
*
|
||||
* @author pnoker
|
||||
* @version 2026.5.23
|
||||
* @since 2026.5.23
|
||||
*/
|
||||
@Mapper
|
||||
public interface EventRecordMapper extends BaseMapper<EventRecordDO> {
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.github.pnoker.common.data.rabbit;
|
||||
|
||||
import com.rabbitmq.client.Channel;
|
||||
import io.github.pnoker.common.data.dal.CommandRecordManager;
|
||||
import io.github.pnoker.common.data.entity.model.CommandRecordDO;
|
||||
import io.github.pnoker.common.enums.PointCommandStatusEnum;
|
||||
import io.github.pnoker.common.utils.RabbitAckUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* RabbitMQ receiver for custom command messages rejected into the dead letter exchange.
|
||||
*
|
||||
* @author pnoker
|
||||
* @version 2026.5.23
|
||||
* @since 2026.5.23
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class CommandDeadReceiver {
|
||||
|
||||
private final CommandRecordManager commandRecordManager;
|
||||
|
||||
@RabbitHandler
|
||||
@RabbitListener(queues = "#{commandDeadQueue.name}")
|
||||
public void onDeadLetter(Channel channel, Message message) {
|
||||
long deliveryTag = message.getMessageProperties().getDeliveryTag();
|
||||
try {
|
||||
String correlationId = message.getMessageProperties().getCorrelationId();
|
||||
if (Objects.nonNull(correlationId)) {
|
||||
CommandRecordDO recordDO = commandRecordManager.lambdaQuery()
|
||||
.eq(CommandRecordDO::getRecordId, correlationId)
|
||||
.one();
|
||||
if (Objects.nonNull(recordDO)) {
|
||||
recordDO.setStatus(PointCommandStatusEnum.DEAD.getCode());
|
||||
recordDO.setErrorCode("DLX");
|
||||
recordDO.setErrorMessage("Message rejected to dead letter queue");
|
||||
recordDO.setFinishedAt(LocalDateTime.now());
|
||||
commandRecordManager.updateById(recordDO);
|
||||
log.info("Marked dead command record: recordId={}", correlationId);
|
||||
}
|
||||
}
|
||||
RabbitAckUtil.ack(channel, deliveryTag);
|
||||
} catch (Exception e) {
|
||||
log.error("Command dead letter processing failed", e);
|
||||
RabbitAckUtil.nack(channel, deliveryTag, true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.github.pnoker.common.data.rabbit;
|
||||
|
||||
import com.rabbitmq.client.Channel;
|
||||
import io.github.pnoker.common.data.dal.CommandRecordManager;
|
||||
import io.github.pnoker.common.data.entity.model.CommandRecordDO;
|
||||
import io.github.pnoker.common.entity.dto.CommandCallResultDTO;
|
||||
import io.github.pnoker.common.utils.RabbitAckUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* RabbitMQ receiver for custom command call result receipts sent by drivers.
|
||||
*
|
||||
* @author pnoker
|
||||
* @version 2026.5.23
|
||||
* @since 2026.5.23
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class CommandResultReceiver {
|
||||
|
||||
private final CommandRecordManager commandRecordManager;
|
||||
|
||||
@RabbitHandler
|
||||
@RabbitListener(queues = "#{commandResultQueue.name}")
|
||||
public void onResult(Channel channel, Message message, CommandCallResultDTO resultDTO) {
|
||||
long deliveryTag = message.getMessageProperties().getDeliveryTag();
|
||||
try {
|
||||
if (Objects.isNull(resultDTO) || Objects.isNull(resultDTO.recordId())) {
|
||||
RabbitAckUtil.reject(channel, deliveryTag);
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("Receive command result: recordId={}, status={}", resultDTO.recordId(), resultDTO.status());
|
||||
|
||||
CommandRecordDO recordDO = commandRecordManager.lambdaQuery()
|
||||
.eq(CommandRecordDO::getRecordId, resultDTO.recordId())
|
||||
.one();
|
||||
|
||||
if (Objects.nonNull(recordDO)) {
|
||||
recordDO.setStatus(resultDTO.status());
|
||||
recordDO.setErrorCode(resultDTO.errorCode());
|
||||
recordDO.setErrorMessage(resultDTO.errorMessage());
|
||||
if (Objects.nonNull(resultDTO.resultValues())) {
|
||||
recordDO.setResultValues(resultDTO.resultValues().toString());
|
||||
}
|
||||
if (Objects.nonNull(resultDTO.finishedAt())) {
|
||||
recordDO.setFinishedAt(LocalDateTime.ofInstant(resultDTO.finishedAt(), ZoneId.systemDefault()));
|
||||
} else {
|
||||
recordDO.setFinishedAt(LocalDateTime.now());
|
||||
}
|
||||
commandRecordManager.updateById(recordDO);
|
||||
log.info("Updated command record status: recordId={}, status={}", resultDTO.recordId(), resultDTO.status());
|
||||
} else {
|
||||
log.warn("Command record not found for result: recordId={}", resultDTO.recordId());
|
||||
}
|
||||
|
||||
RabbitAckUtil.ack(channel, deliveryTag);
|
||||
} catch (Exception e) {
|
||||
log.error("Command result processing failed, deliveryTag={}", deliveryTag, e);
|
||||
RabbitAckUtil.nack(channel, deliveryTag, true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+2
-1
@@ -17,6 +17,7 @@
|
||||
|
||||
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;
|
||||
|
||||
@@ -43,7 +44,7 @@ public class PointCommandValidator {
|
||||
* @param pointExt point extension JSON (may contain constraints for future use)
|
||||
* @throws IllegalArgumentException if the value fails validation
|
||||
*/
|
||||
public void validateWriteValue(String value, String pointExt) {
|
||||
public void validateWriteValue(String value, PointExt pointExt) {
|
||||
if (value == null || value.isBlank()) {
|
||||
throw new IllegalArgumentException("Write value must not be blank");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright 2016-present the IoT DC3 original author or authors.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="io.github.pnoker.common.data.mapper.CommandRecordMapper">
|
||||
</mapper>
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright 2016-present the IoT DC3 original author or authors.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="io.github.pnoker.common.data.mapper.EventRecordMapper">
|
||||
</mapper>
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.github.pnoker.common.entity.dto;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Custom command call dispatch DTO sent via RabbitMQ to the driver.
|
||||
*
|
||||
* @author pnoker
|
||||
* @version 2026.5.23
|
||||
* @since 2026.5.23
|
||||
*/
|
||||
public record CommandCallDTO(
|
||||
String recordId,
|
||||
Long tenantId,
|
||||
Long deviceId,
|
||||
Long commandId,
|
||||
String commandCode,
|
||||
Map<String, String> paramValues,
|
||||
String source,
|
||||
Long sourceUserId,
|
||||
Instant occurredAt,
|
||||
Instant expireAt,
|
||||
int schemaVersion
|
||||
) {
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private String recordId;
|
||||
private Long tenantId;
|
||||
private Long deviceId;
|
||||
private Long commandId;
|
||||
private String commandCode;
|
||||
private Map<String, String> paramValues;
|
||||
private String source;
|
||||
private Long sourceUserId;
|
||||
private Instant occurredAt;
|
||||
private Instant expireAt;
|
||||
private int schemaVersion;
|
||||
|
||||
public Builder recordId(String recordId) { this.recordId = recordId; return this; }
|
||||
public Builder tenantId(Long tenantId) { this.tenantId = tenantId; return this; }
|
||||
public Builder deviceId(Long deviceId) { this.deviceId = deviceId; return this; }
|
||||
public Builder commandId(Long commandId) { this.commandId = commandId; return this; }
|
||||
public Builder commandCode(String commandCode) { this.commandCode = commandCode; return this; }
|
||||
public Builder paramValues(Map<String, String> paramValues) { this.paramValues = paramValues; return this; }
|
||||
public Builder source(String source) { this.source = source; return this; }
|
||||
public Builder sourceUserId(Long sourceUserId) { this.sourceUserId = sourceUserId; return this; }
|
||||
public Builder occurredAt(Instant occurredAt) { this.occurredAt = occurredAt; return this; }
|
||||
public Builder expireAt(Instant expireAt) { this.expireAt = expireAt; return this; }
|
||||
public Builder schemaVersion(int schemaVersion) { this.schemaVersion = schemaVersion; return this; }
|
||||
|
||||
public CommandCallDTO build() {
|
||||
return new CommandCallDTO(recordId, tenantId, deviceId, commandId, commandCode,
|
||||
paramValues, source, sourceUserId, occurredAt, expireAt, schemaVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.github.pnoker.common.entity.dto;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Result receipt sent by the driver after executing a custom command.
|
||||
*
|
||||
* @author pnoker
|
||||
* @version 2026.5.23
|
||||
* @since 2026.5.23
|
||||
*/
|
||||
public record CommandCallResultDTO(
|
||||
String recordId,
|
||||
Long tenantId,
|
||||
String status,
|
||||
Map<String, String> resultValues,
|
||||
String errorCode,
|
||||
String errorMessage,
|
||||
Instant finishedAt,
|
||||
int schemaVersion
|
||||
) {
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private String recordId;
|
||||
private Long tenantId;
|
||||
private String status;
|
||||
private Map<String, String> resultValues;
|
||||
private String errorCode;
|
||||
private String errorMessage;
|
||||
private Instant finishedAt;
|
||||
private int schemaVersion;
|
||||
|
||||
public Builder recordId(String recordId) { this.recordId = recordId; return this; }
|
||||
public Builder tenantId(Long tenantId) { this.tenantId = tenantId; return this; }
|
||||
public Builder status(String status) { this.status = status; return this; }
|
||||
public Builder resultValues(Map<String, String> resultValues) { this.resultValues = resultValues; return this; }
|
||||
public Builder errorCode(String errorCode) { this.errorCode = errorCode; return this; }
|
||||
public Builder errorMessage(String errorMessage) { this.errorMessage = errorMessage; return this; }
|
||||
public Builder finishedAt(Instant finishedAt) { this.finishedAt = finishedAt; return this; }
|
||||
public Builder schemaVersion(int schemaVersion) { this.schemaVersion = schemaVersion; return this; }
|
||||
|
||||
public CommandCallResultDTO build() {
|
||||
return new CommandCallResultDTO(recordId, tenantId, status, resultValues,
|
||||
errorCode, errorMessage, finishedAt, schemaVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.github.pnoker.common.entity.dto;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Event report DTO sent from driver to data center via RabbitMQ.
|
||||
*
|
||||
* @author pnoker
|
||||
* @version 2026.5.23
|
||||
* @since 2026.5.23
|
||||
*/
|
||||
public record EventReportDTO(
|
||||
String recordId,
|
||||
Long tenantId,
|
||||
Long deviceId,
|
||||
Long eventId,
|
||||
String eventCode,
|
||||
Byte eventTypeFlag,
|
||||
Byte eventLevelFlag,
|
||||
Map<String, String> paramValues,
|
||||
String message,
|
||||
Instant occurTime,
|
||||
int schemaVersion
|
||||
) {
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private String recordId;
|
||||
private Long tenantId;
|
||||
private Long deviceId;
|
||||
private Long eventId;
|
||||
private String eventCode;
|
||||
private Byte eventTypeFlag;
|
||||
private Byte eventLevelFlag;
|
||||
private Map<String, String> paramValues;
|
||||
private String message;
|
||||
private Instant occurTime;
|
||||
private int schemaVersion;
|
||||
|
||||
public Builder recordId(String recordId) { this.recordId = recordId; return this; }
|
||||
public Builder tenantId(Long tenantId) { this.tenantId = tenantId; return this; }
|
||||
public Builder deviceId(Long deviceId) { this.deviceId = deviceId; return this; }
|
||||
public Builder eventId(Long eventId) { this.eventId = eventId; return this; }
|
||||
public Builder eventCode(String eventCode) { this.eventCode = eventCode; return this; }
|
||||
public Builder eventTypeFlag(Byte eventTypeFlag) { this.eventTypeFlag = eventTypeFlag; return this; }
|
||||
public Builder eventLevelFlag(Byte eventLevelFlag) { this.eventLevelFlag = eventLevelFlag; return this; }
|
||||
public Builder paramValues(Map<String, String> paramValues) { this.paramValues = paramValues; return this; }
|
||||
public Builder message(String message) { this.message = message; return this; }
|
||||
public Builder occurTime(Instant occurTime) { this.occurTime = occurTime; return this; }
|
||||
public Builder schemaVersion(int schemaVersion) { this.schemaVersion = schemaVersion; return this; }
|
||||
|
||||
public EventReportDTO build() {
|
||||
return new EventReportDTO(recordId, tenantId, deviceId, eventId, eventCode,
|
||||
eventTypeFlag, eventLevelFlag, paramValues, message, occurTime, schemaVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -123,4 +123,24 @@ public class ExchangeConfig {
|
||||
return new TopicExchange(RabbitConstant.TOPIC_EXCHANGE_STATE_TIMEOUT_CHECK, true, false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
TopicExchange commandExchange() {
|
||||
return new TopicExchange(RabbitConstant.TOPIC_EXCHANGE_COMMAND, true, false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
TopicExchange commandResultExchange() {
|
||||
return new TopicExchange(RabbitConstant.TOPIC_EXCHANGE_COMMAND_RESULT, true, false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
TopicExchange commandDeadExchange() {
|
||||
return new TopicExchange(RabbitConstant.TOPIC_EXCHANGE_COMMAND_DEAD, true, false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
TopicExchange eventExchange() {
|
||||
return new TopicExchange(RabbitConstant.TOPIC_EXCHANGE_EVENT, true, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user