mirror of
https://gitee.com/yudaocode/yudao-boot-mini.git
synced 2026-03-22 05:27:15 +08:00
【同步】最新精简版本!
This commit is contained in:
@@ -1,46 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.gemini;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* 谷歌 Gemini {@link ChatModel} 实现类,基于 Google AI Studio 提供的 <a href="https://ai.google.dev/gemini-api/docs/openai">OpenAI 兼容方案</a>
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class GeminiChatModel implements ChatModel {
|
||||
|
||||
public static final String BASE_URL = "https://generativelanguage.googleapis.com/v1beta/openai/";
|
||||
public static final String COMPLETE_PATH = "/chat/completions";
|
||||
|
||||
public static final String MODEL_DEFAULT = "gemini-2.5-flash";
|
||||
|
||||
/**
|
||||
* 兼容 OpenAI 接口,进行复用
|
||||
*/
|
||||
private final OpenAiChatModel openAiChatModel;
|
||||
|
||||
@Override
|
||||
public ChatResponse call(Prompt prompt) {
|
||||
return openAiChatModel.call(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ChatResponse> stream(Prompt prompt) {
|
||||
return openAiChatModel.stream(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatOptions getDefaultOptions() {
|
||||
return openAiChatModel.getDefaultOptions();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.webserch;
|
||||
|
||||
/**
|
||||
* 网络搜索客户端接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface AiWebSearchClient {
|
||||
|
||||
/**
|
||||
* 网页搜索
|
||||
*
|
||||
* @param request 搜索请求
|
||||
* @return 搜索结果
|
||||
*/
|
||||
AiWebSearchResponse search(AiWebSearchRequest request);
|
||||
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.webserch;
|
||||
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class AiWebSearchRequest {
|
||||
|
||||
/**
|
||||
* 用户的搜索词
|
||||
*/
|
||||
@NotEmpty(message = "搜索词不能为空")
|
||||
private String query;
|
||||
|
||||
/**
|
||||
* 是否显示文本摘要
|
||||
*
|
||||
* true - 显示
|
||||
* false - 不显示(默认)
|
||||
*/
|
||||
private Boolean summary;
|
||||
|
||||
/**
|
||||
* 返回结果的条数
|
||||
*/
|
||||
@NotNull(message = "返回结果条数不能为空")
|
||||
@Min(message = "返回结果条数最小为 1", value = 1)
|
||||
@Max(message = "返回结果条数最大为 50", value = 50)
|
||||
private Integer count;
|
||||
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.webserch;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class AiWebSearchResponse {
|
||||
|
||||
/**
|
||||
* 总数(总共匹配的网页数)
|
||||
*/
|
||||
private Long total;
|
||||
|
||||
/**
|
||||
* 数据列表
|
||||
*/
|
||||
private List<WebPage> lists;
|
||||
|
||||
/**
|
||||
* 网页对象
|
||||
*/
|
||||
@Data
|
||||
public static class WebPage {
|
||||
|
||||
/**
|
||||
* 名称
|
||||
*
|
||||
* 例如说:搜狐网
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 图标
|
||||
*/
|
||||
private String icon;
|
||||
|
||||
/**
|
||||
* 标题
|
||||
*
|
||||
* 例如说:186页|阿里巴巴:2024年环境、社会和治理(ESG)报告
|
||||
*/
|
||||
private String title;
|
||||
/**
|
||||
* URL
|
||||
*
|
||||
* 例如说:https://m.sohu.com/a/815036254_121819701/?pvid=000115_3w_a
|
||||
*/
|
||||
@SuppressWarnings("JavadocLinkAsPlainText")
|
||||
private String url;
|
||||
|
||||
/**
|
||||
* 内容的简短描述
|
||||
*/
|
||||
private String snippet;
|
||||
/**
|
||||
* 内容的文本摘要
|
||||
*/
|
||||
private String summary;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.webserch.bocha;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchClient;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchRequest;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchResponse;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
|
||||
/**
|
||||
* 博查 {@link AiWebSearchClient} 实现类
|
||||
*
|
||||
* @see <a href="https://open.bochaai.com/overview">博查 AI 开放平台</a>
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public class AiBoChaWebSearchClient implements AiWebSearchClient {
|
||||
|
||||
public static final String BASE_URL = "https://api.bochaai.com";
|
||||
private static final String AUTHORIZATION_HEADER = "Authorization";
|
||||
private static final String BEARER_PREFIX = "Bearer ";
|
||||
|
||||
private final WebClient webClient;
|
||||
|
||||
private final Predicate<HttpStatusCode> STATUS_PREDICATE = status -> !status.is2xxSuccessful();
|
||||
|
||||
private final Function<Object, Function<ClientResponse, Mono<? extends Throwable>>> EXCEPTION_FUNCTION =
|
||||
reqParam -> response -> response.bodyToMono(String.class).handle((responseBody, sink) -> {
|
||||
log.error("[AiBoChaWebSearchClient] 调用失败!请求参数:[{}],响应数据: [{}]", reqParam, responseBody);
|
||||
sink.error(new IllegalStateException("[AiBoChaWebSearchClient] 调用失败!"));
|
||||
});
|
||||
|
||||
public AiBoChaWebSearchClient(String apiKey) {
|
||||
this.webClient = WebClient.builder()
|
||||
.baseUrl(BASE_URL)
|
||||
.defaultHeaders((headers) -> {
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.add(AUTHORIZATION_HEADER, BEARER_PREFIX + apiKey);
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AiWebSearchResponse search(AiWebSearchRequest request) {
|
||||
// 转换请求参数
|
||||
WebSearchRequest webSearchRequest = new WebSearchRequest(
|
||||
request.getQuery(),
|
||||
request.getSummary(),
|
||||
request.getCount()
|
||||
);
|
||||
// 调用博查 API
|
||||
CommonResult<WebSearchResponse> response = this.webClient.post()
|
||||
.uri("/v1/web-search")
|
||||
.bodyValue(webSearchRequest)
|
||||
.retrieve()
|
||||
.onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(webSearchRequest))
|
||||
.bodyToMono(new ParameterizedTypeReference<CommonResult<WebSearchResponse>>() {})
|
||||
.block();
|
||||
if (response == null) {
|
||||
throw new IllegalStateException("[search][搜索结果为空]");
|
||||
}
|
||||
if (response.getData() == null) {
|
||||
throw new IllegalStateException(String.format("[search][搜索失败,code = %s, msg = %s]",
|
||||
response.getCode(), response.getMsg()));
|
||||
}
|
||||
WebSearchResponse data = response.getData();
|
||||
|
||||
// 转换结果
|
||||
AiWebSearchResponse result = new AiWebSearchResponse();
|
||||
if (data.webPages() == null || CollUtil.isEmpty(data.webPages().value())) {
|
||||
return result.setTotal(0L).setLists(List.of());
|
||||
}
|
||||
return result.setTotal(data.webPages().totalEstimatedMatches())
|
||||
.setLists(convertList(data.webPages().value(), page -> new AiWebSearchResponse.WebPage()
|
||||
.setName(page.siteName()).setIcon(page.siteIcon())
|
||||
.setTitle(page.name()).setUrl(page.url())
|
||||
.setSnippet(page.snippet()).setSummary(page.summary())));
|
||||
}
|
||||
|
||||
/**
|
||||
* 网页搜索请求参数
|
||||
*/
|
||||
@JsonInclude(value = JsonInclude.Include.NON_NULL)
|
||||
public record WebSearchRequest(
|
||||
String query,
|
||||
Boolean summary,
|
||||
Integer count
|
||||
) {
|
||||
public WebSearchRequest {
|
||||
Assert.notBlank(query, "query 不能为空");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 网页搜索响应
|
||||
*/
|
||||
@JsonInclude(value = JsonInclude.Include.NON_NULL)
|
||||
public record WebSearchResponse(
|
||||
WebSearchWebPages webPages
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 网页搜索结果
|
||||
*/
|
||||
@JsonInclude(value = JsonInclude.Include.NON_NULL)
|
||||
public record WebSearchWebPages(
|
||||
String webSearchUrl,
|
||||
Long totalEstimatedMatches,
|
||||
List<WebPageValue> value,
|
||||
Boolean someResultsRemoved
|
||||
) {
|
||||
|
||||
/**
|
||||
* 网页结果值
|
||||
*/
|
||||
@JsonInclude(value = JsonInclude.Include.NON_NULL)
|
||||
public record WebPageValue(
|
||||
String id,
|
||||
String name,
|
||||
String url,
|
||||
String displayUrl,
|
||||
String snippet,
|
||||
String summary,
|
||||
String siteName,
|
||||
String siteIcon,
|
||||
String datePublished,
|
||||
String dateLastCrawled,
|
||||
String cachedPageUrl,
|
||||
String language,
|
||||
Boolean isFamilyFriendly,
|
||||
Boolean isNavigational
|
||||
) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
/**
|
||||
* 占位
|
||||
*/
|
||||
package cn.iocoder.yudao.module.ai.framework.security.core;
|
||||
@@ -1,4 +0,0 @@
|
||||
/**
|
||||
* 参考 <a href="https://docs.spring.io/spring-ai/reference/api/tools.html#_methods_as_tools">Tool Calling —— Methods as Tools</a>
|
||||
*/
|
||||
package cn.iocoder.yudao.module.ai.tool.function;
|
||||
@@ -1,19 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.tool.method;
|
||||
|
||||
/**
|
||||
* 来自 Spring AI 官方文档
|
||||
*
|
||||
* Represents a person with basic information.
|
||||
* This is an immutable record.
|
||||
*/
|
||||
public record Person(
|
||||
int id,
|
||||
String firstName,
|
||||
String lastName,
|
||||
String email,
|
||||
String sex,
|
||||
String ipAddress,
|
||||
String jobTitle,
|
||||
int age
|
||||
) {
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.tool.method;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 来自 Spring AI 官方文档
|
||||
*
|
||||
* Service interface for managing Person data.
|
||||
* Defines the contract for CRUD operations and search/filter functionalities.
|
||||
*/
|
||||
public interface PersonService {
|
||||
|
||||
/**
|
||||
* Creates a new Person record.
|
||||
* Assigns a unique ID to the person and stores it.
|
||||
*
|
||||
* @param personData The data for the new person (ID field is ignored). Must not be null.
|
||||
* @return The created Person record, including the generated ID.
|
||||
*/
|
||||
Person createPerson(Person personData);
|
||||
|
||||
/**
|
||||
* Retrieves a Person by their unique ID.
|
||||
*
|
||||
* @param id The ID of the person to retrieve.
|
||||
* @return An Optional containing the found Person, or an empty Optional if not found.
|
||||
*/
|
||||
Optional<Person> getPersonById(int id);
|
||||
|
||||
/**
|
||||
* Retrieves all Person records currently stored.
|
||||
*
|
||||
* @return An unmodifiable List containing all Persons. Returns an empty list if none exist.
|
||||
*/
|
||||
List<Person> getAllPersons();
|
||||
|
||||
/**
|
||||
* Updates an existing Person record identified by ID.
|
||||
* Replaces the existing data with the provided data, keeping the original ID.
|
||||
*
|
||||
* @param id The ID of the person to update.
|
||||
* @param updatedPersonData The new data for the person (ID field is ignored). Must not be null.
|
||||
* @return true if the person was found and updated, false otherwise.
|
||||
*/
|
||||
boolean updatePerson(int id, Person updatedPersonData);
|
||||
|
||||
/**
|
||||
* Deletes a Person record identified by ID.
|
||||
*
|
||||
* @param id The ID of the person to delete.
|
||||
* @return true if the person was found and deleted, false otherwise.
|
||||
*/
|
||||
boolean deletePerson(int id);
|
||||
|
||||
/**
|
||||
* Searches for Persons whose job title contains the given query string (case-insensitive).
|
||||
*
|
||||
* @param jobTitleQuery The string to search for within job titles. Can be null or blank.
|
||||
* @return An unmodifiable List of matching Persons. Returns an empty list if no matches or query is invalid.
|
||||
*/
|
||||
List<Person> searchByJobTitle(String jobTitleQuery);
|
||||
|
||||
/**
|
||||
* Filters Persons by their exact sex (case-insensitive).
|
||||
*
|
||||
* @param sex The sex to filter by (e.g., "Male", "Female"). Can be null or blank.
|
||||
* @return An unmodifiable List of matching Persons. Returns an empty list if no matches or filter is invalid.
|
||||
*/
|
||||
List<Person> filterBySex(String sex);
|
||||
|
||||
/**
|
||||
* Filters Persons by their exact age.
|
||||
*
|
||||
* @param age The age to filter by.
|
||||
* @return An unmodifiable List of matching Persons. Returns an empty list if no matches.
|
||||
*/
|
||||
List<Person> filterByAge(int age);
|
||||
|
||||
}
|
||||
@@ -1,336 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.tool.method;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.tool.annotation.Tool;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 来自 Spring AI 官方文档
|
||||
*
|
||||
* Implementation of the PersonService interface using an in-memory data store.
|
||||
* Manages a collection of Person objects loaded from embedded CSV data.
|
||||
* This class is thread-safe due to the use of ConcurrentHashMap and AtomicInteger.
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class PersonServiceImpl implements PersonService {
|
||||
|
||||
private final Map<Integer, Person> personStore = new ConcurrentHashMap<>();
|
||||
|
||||
private AtomicInteger idGenerator;
|
||||
|
||||
/**
|
||||
* Embedded CSV data for initial population
|
||||
*/
|
||||
private static final String CSV_DATA = """
|
||||
Id,FirstName,LastName,Email,Sex,IpAddress,JobTitle,Age
|
||||
1,Fons,Tollfree,ftollfree0@senate.gov,Male,55.1 Tollfree Lane,Research Associate,31
|
||||
2,Emlynne,Tabourier,etabourier1@networksolutions.com,Female,18 Tabourier Way,Associate Professor,38
|
||||
3,Shae,Johncey,sjohncey2@yellowpages.com,Male,1 Johncey Circle,Structural Analysis Engineer,30
|
||||
4,Sebastien,Bradly,sbradly3@mapquest.com,Male,2 Bradly Hill,Chief Executive Officer,40
|
||||
5,Harriott,Kitteringham,hkitteringham4@typepad.com,Female,3 Kitteringham Drive,VP Sales,47
|
||||
6,Anallise,Parradine,aparradine5@miibeian.gov.cn,Female,4 Parradine Street,Analog Circuit Design manager,44
|
||||
7,Gorden,Kirkbright,gkirkbright6@reuters.com,Male,5 Kirkbright Plaza,Senior Editor,40
|
||||
8,Veradis,Ledwitch,vledwitch7@google.com.au,Female,6 Ledwitch Avenue,Computer Systems Analyst IV,44
|
||||
9,Agnesse,Penhalurick,apenhalurick8@google.it,Female,7 Penhalurick Terrace,Automation Specialist IV,41
|
||||
10,Bibby,Hutable,bhutable9@craigslist.org,Female,8 Hutable Place,Account Representative I,43
|
||||
11,Karoly,Lightoller,klightollera@rakuten.co.jp,Female,9 Lightoller Parkway,Senior Developer,46
|
||||
12,Cristine,Durrad,cdurradb@aol.com,Female,10 Durrad Center,Senior Developer,48
|
||||
13,Aggy,Napier,anapierc@hostgator.com,Female,11 Napier Court,VP Product Management,44
|
||||
14,Prisca,Caddens,pcaddensd@vinaora.com,Female,12 Caddens Alley,Business Systems Development Analyst,41
|
||||
15,Khalil,McKernan,kmckernane@google.fr,Male,13 McKernan Pass,Engineer IV,44
|
||||
16,Lorry,MacTrusty,lmactrustyf@eventbrite.com,Male,14 MacTrusty Junction,Design Engineer,42
|
||||
17,Casandra,Worsell,cworsellg@goo.gl,Female,15 Worsell Point,Systems Administrator IV,45
|
||||
18,Ulrikaumeko,Haveline,uhavelineh@usgs.gov,Female,16 Haveline Trail,Financial Advisor,42
|
||||
19,Shurlocke,Albany,salbanyi@artisteer.com,Male,17 Albany Plaza,Software Test Engineer III,46
|
||||
20,Myrilla,Brimilcombe,mbrimilcombej@accuweather.com,Female,18 Brimilcombe Road,Programmer Analyst I,48
|
||||
21,Carlina,Scimonelli,cscimonellik@va.gov,Female,19 Scimonelli Pass,Help Desk Technician,45
|
||||
22,Tina,Goullee,tgoulleel@miibeian.gov.cn,Female,20 Goullee Crossing,Accountant IV,43
|
||||
23,Adriaens,Storek,astorekm@devhub.com,Female,21 Storek Avenue,Recruiting Manager,40
|
||||
24,Tedra,Giraudot,tgiraudotn@wiley.com,Female,22 Giraudot Terrace,Speech Pathologist,47
|
||||
25,Josiah,Soares,jsoareso@google.nl,Male,23 Soares Street,Tax Accountant,45
|
||||
26,Kayle,Gaukrodge,kgaukrodgep@wikispaces.com,Female,24 Gaukrodge Parkway,Accountant II,43
|
||||
27,Ardys,Chuter,achuterq@ustream.tv,Female,25 Chuter Drive,Engineer IV,41
|
||||
28,Francyne,Baudinet,fbaudinetr@newyorker.com,Female,26 Baudinet Center,VP Accounting,48
|
||||
29,Gerick,Bullan,gbullans@seesaa.net,Male,27 Bullan Way,Senior Financial Analyst,43
|
||||
30,Northrup,Grivori,ngrivorit@unc.edu,Male,28 Grivori Plaza,Systems Administrator I,45
|
||||
31,Town,Duguid,tduguidu@squarespace.com,Male,29 Duguid Pass,Safety Technician IV,46
|
||||
32,Pierette,Kopisch,pkopischv@google.com.br,Female,30 Kopisch Lane,Director of Sales,41
|
||||
33,Jacquenetta,Le Prevost,jleprevostw@netlog.com,Female,31 Le Prevost Trail,Senior Developer,47
|
||||
34,Garvy,Rusted,grustedx@aboutads.info,Male,32 Rusted Junction,Senior Developer,42
|
||||
35,Clarice,Aysh,cayshy@merriam-webster.com,Female,33 Aysh Avenue,VP Quality Control,40
|
||||
36,Tracie,Fedorski,tfedorskiz@bloglines.com,Male,34 Fedorski Terrace,Design Engineer,44
|
||||
37,Noelyn,Matushenko,nmatushenko10@globo.com,Female,35 Matushenko Place,VP Sales,48
|
||||
38,Rudiger,Klaesson,rklaesson11@usnews.com,Male,36 Klaesson Road,Database Administrator IV,43
|
||||
39,Mirella,Syddie,msyddie12@geocities.jp,Female,37 Syddie Circle,Geological Engineer,46
|
||||
40,Donalt,O'Lunny,dolunny13@elpais.com,Male,38 O'Lunny Center,Analog Circuit Design manager,41
|
||||
41,Guntar,Deniskevich,gdeniskevich14@google.com.hk,Male,39 Deniskevich Way,Structural Engineer,47
|
||||
42,Hort,Shufflebotham,hshufflebotham15@about.me,Male,40 Shufflebotham Court,Structural Analysis Engineer,45
|
||||
43,Dominique,Thickett,dthickett16@slashdot.org,Male,41 Thickett Crossing,Safety Technician I,42
|
||||
44,Zebulen,Piscopello,zpiscopello17@umich.edu,Male,42 Piscopello Parkway,Web Developer II,40
|
||||
45,Mellicent,Mac Giany,mmacgiany18@state.tx.us,Female,43 Mac Giany Pass,Assistant Manager,44
|
||||
46,Merle,Bounds,mbounds19@amazon.co.jp,Female,44 Bounds Alley,Systems Administrator III,41
|
||||
47,Madelle,Farbrace,mfarbrace1a@xinhuanet.com,Female,45 Farbrace Terrace,Quality Engineer,48
|
||||
48,Galvin,O'Sheeryne,gosheeryne1b@addtoany.com,Male,46 O'Sheeryne Way,Environmental Specialist,43
|
||||
49,Guillemette,Bootherstone,gbootherstone1c@nationalgeographic.com,Female,47 Bootherstone Plaza,Professor,46
|
||||
50,Letti,Aylmore,laylmore1d@vinaora.com,Female,48 Aylmore Circle,Automation Specialist I,40
|
||||
51,Nonie,Rivalland,nrivalland1e@weather.com,Female,49 Rivalland Avenue,Software Test Engineer IV,45
|
||||
52,Jacquelynn,Halfacre,jhalfacre1f@surveymonkey.com,Female,50 Halfacre Pass,Geologist II,42
|
||||
53,Anderea,MacKibbon,amackibbon1g@weibo.com,Female,51 MacKibbon Parkway,Automation Specialist II,47
|
||||
54,Wash,Klimko,wklimko1h@slashdot.org,Male,52 Klimko Alley,Database Administrator I,40
|
||||
55,Flori,Kynett,fkynett1i@auda.org.au,Female,53 Kynett Trail,Quality Control Specialist,46
|
||||
56,Libbey,Penswick,lpenswick1j@google.co.uk,Female,54 Penswick Point,VP Accounting,43
|
||||
57,Silvanus,Skellorne,sskellorne1k@booking.com,Male,55 Skellorne Drive,Account Executive,48
|
||||
58,Carmine,Mateos,cmateos1l@plala.or.jp,Male,56 Mateos Terrace,Systems Administrator I,41
|
||||
59,Sheffie,Blazewicz,sblazewicz1m@google.com.au,Male,57 Blazewicz Center,VP Sales,44
|
||||
60,Leanor,Worsnop,lworsnop1n@uol.com.br,Female,58 Worsnop Plaza,Systems Administrator III,45
|
||||
61,Caspar,Pamment,cpamment1o@google.co.jp,Male,59 Pamment Court,Senior Financial Analyst,42
|
||||
62,Justinian,Pentycost,jpentycost1p@sciencedaily.com,Male,60 Pentycost Way,Senior Quality Engineer,47
|
||||
63,Gerianne,Jarnell,gjarnell1q@bing.com,Female,61 Jarnell Avenue,Help Desk Operator,40
|
||||
64,Boycie,Zanetto,bzanetto1r@about.com,Male,62 Zanetto Place,Quality Engineer,46
|
||||
65,Camilla,Mac Giany,cmacgiany1s@state.gov,Female,63 Mac Giany Parkway,Senior Cost Accountant,43
|
||||
66,Hadlee,Piscopiello,hpiscopiello1t@artisteer.com,Male,64 Piscopiello Street,Account Representative III,48
|
||||
67,Bobbie,Penvarden,bpenvarden1u@google.cn,Male,65 Penvarden Lane,Help Desk Operator,41
|
||||
68,Ali,Gowlett,agowlett1v@parallels.com,Male,66 Gowlett Pass,VP Marketing,44
|
||||
69,Olivette,Acome,oacome1w@qq.com,Female,67 Acome Hill,VP Product Management,45
|
||||
70,Jehanna,Brotherheed,jbrotherheed1x@google.nl,Female,68 Brotherheed Junction,Database Administrator III,42
|
||||
71,Morgan,Berthomieu,mberthomieu1y@artisteer.com,Male,69 Berthomieu Alley,Systems Administrator II,47
|
||||
72,Linzy,Shilladay,lshilladay1z@icq.com,Female,70 Shilladay Trail,Research Assistant IV,40
|
||||
73,Faydra,Brimner,fbrimner20@mozilla.org,Female,71 Brimner Road,Senior Editor,46
|
||||
74,Gwenore,Oxlee,goxlee21@devhub.com,Female,72 Oxlee Terrace,Systems Administrator II,43
|
||||
75,Evangelin,Beinke,ebeinke22@mozilla.com,Female,73 Beinke Circle,Accountant I,48
|
||||
76,Missy,Cockling,mcockling23@si.edu,Female,74 Cockling Way,Software Engineer I,41
|
||||
77,Suzanne,Klimschak,sklimschak24@etsy.com,Female,75 Klimschak Plaza,Tax Accountant,44
|
||||
78,Candide,Goricke,cgoricke25@weebly.com,Female,76 Goricke Pass,Sales Associate,45
|
||||
79,Gerome,Pinsent,gpinsent26@google.com.au,Male,77 Pinsent Junction,Software Consultant,42
|
||||
80,Lezley,Mac Giany,lmacgiany27@scribd.com,Male,78 Mac Giany Alley,Operator,47
|
||||
81,Tobiah,Durn,tdurn28@state.tx.us,Male,79 Durn Court,VP Sales,40
|
||||
82,Sherlocke,Cockshoot,scockshoot29@yelp.com,Male,80 Cockshoot Street,Senior Financial Analyst,46
|
||||
83,Myrle,Speenden,mspeenden2a@utexas.edu,Female,81 Speenden Center,Senior Developer,43
|
||||
84,Isidore,Gorries,igorries2b@flavors.me,Male,82 Gorries Parkway,Sales Representative,48
|
||||
85,Isac,Kitchingman,ikitchingman2c@businessinsider.com,Male,83 Kitchingman Drive,VP Accounting,41
|
||||
86,Benedetta,Purrier,bpurrier2d@admin.ch,Female,84 Purrier Trail,VP Accounting,44
|
||||
87,Tera,Fitchell,tfitchell2e@fotki.com,Female,85 Fitchell Place,Software Engineer IV,45
|
||||
88,Abbe,Pamment,apamment2f@about.com,Male,86 Pamment Avenue,VP Sales,42
|
||||
89,Jandy,Gommowe,jgommowe2g@angelfire.com,Female,87 Gommowe Road,Financial Analyst,47
|
||||
90,Karena,Fussey,kfussey2h@google.com.au,Female,88 Fussey Point,Assistant Professor,40
|
||||
91,Gaspar,Pammenter,gpammenter2i@google.com.br,Male,89 Pammenter Hill,Help Desk Operator,46
|
||||
92,Stanwood,Mac Giany,smacgiany2j@prlog.org,Male,90 Mac Giany Terrace,Research Associate,43
|
||||
93,Byrom,Beedell,bbeedell2k@google.co.jp,Male,91 Beedell Way,VP Sales,48
|
||||
94,Annabella,Rowbottom,arowbottom2l@google.com.au,Female,92 Rowbottom Plaza,Help Desk Operator,41
|
||||
95,Rodolphe,Debell,rdebell2m@imageshack.us,Male,93 Debell Pass,Design Engineer,44
|
||||
96,Tyne,Gommey,tgommey2n@joomla.org,Female,94 Gommey Junction,VP Marketing,45
|
||||
97,Christoper,Pincked,cpincked2o@icq.com,Male,95 Pincked Alley,Human Resources Manager,42
|
||||
98,Kore,Le Prevost,kleprevost2p@tripadvisor.com,Female,96 Le Prevost Street,VP Quality Control,47
|
||||
99,Ceciley,Petrolli,cpetrolli2q@oaic.gov.au,Female,97 Petrolli Court,Senior Developer,40
|
||||
100,Elspeth,Mac Giany,emacgiany2r@icio.us,Female,98 Mac Giany Parkway,Internal Auditor,46
|
||||
""";
|
||||
|
||||
/**
|
||||
* Initializes the service after dependency injection by loading data from the CSV string.
|
||||
* Uses @PostConstruct to ensure this runs after the bean is created.
|
||||
*/
|
||||
@PostConstruct
|
||||
private void initializeData() {
|
||||
log.info("Initializing PersonService data store...");
|
||||
int maxId = loadDataFromCsv();
|
||||
idGenerator = new AtomicInteger(maxId);
|
||||
log.info("PersonService initialized with {} records. Next ID: {}", personStore.size(), idGenerator.get() + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the embedded CSV data and populates the in-memory store.
|
||||
* Calculates the maximum ID found in the data to initialize the ID generator.
|
||||
*
|
||||
* @return The maximum ID found in the loaded CSV data.
|
||||
*/
|
||||
private int loadDataFromCsv() {
|
||||
final AtomicInteger currentMaxId = new AtomicInteger(0);
|
||||
// Clear existing data before loading (important for tests or re-initialization scenarios)
|
||||
personStore.clear();
|
||||
try (Stream<String> lines = CSV_DATA.lines().skip(1)) { // Skip header row
|
||||
lines.forEach(line -> {
|
||||
try {
|
||||
// Split carefully, handling potential commas within quoted fields if necessary (simple split here)
|
||||
String[] fields = line.split(",", 8); // Limit split to handle potential commas in job title
|
||||
if (fields.length == 8) {
|
||||
int id = Integer.parseInt(fields[0].trim());
|
||||
String firstName = fields[1].trim();
|
||||
String lastName = fields[2].trim();
|
||||
String email = fields[3].trim();
|
||||
String sex = fields[4].trim();
|
||||
String ipAddress = fields[5].trim();
|
||||
String jobTitle = fields[6].trim();
|
||||
int age = Integer.parseInt(fields[7].trim());
|
||||
|
||||
Person person = new Person(id, firstName, lastName, email, sex, ipAddress, jobTitle, age);
|
||||
personStore.put(id, person);
|
||||
currentMaxId.updateAndGet(max -> Math.max(max, id));
|
||||
} else {
|
||||
log.warn("Skipping malformed CSV line (expected 8 fields, found {}): {}", fields.length, line);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("Skipping line due to parsing error (ID or Age): {} - Error: {}", line, e.getMessage());
|
||||
} catch (Exception e) {
|
||||
log.error("Skipping line due to unexpected error: {} - Error: {}", line, e.getMessage(), e);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.error("Fatal error reading embedded CSV data: {}", e.getMessage(), e);
|
||||
// In a real application, might throw a specific initialization exception
|
||||
}
|
||||
return currentMaxId.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Tool(
|
||||
name = "ps_create_person",
|
||||
description = "Create a new person record in the in-memory store."
|
||||
)
|
||||
public Person createPerson(Person personData) {
|
||||
if (personData == null) {
|
||||
throw new IllegalArgumentException("Person data cannot be null");
|
||||
}
|
||||
int newId = idGenerator.incrementAndGet();
|
||||
// Create a new Person record using data from the input, but with the generated ID
|
||||
Person newPerson = new Person(
|
||||
newId,
|
||||
personData.firstName(),
|
||||
personData.lastName(),
|
||||
personData.email(),
|
||||
personData.sex(),
|
||||
personData.ipAddress(),
|
||||
personData.jobTitle(),
|
||||
personData.age()
|
||||
);
|
||||
personStore.put(newId, newPerson);
|
||||
log.debug("Created person: {}", newPerson);
|
||||
return newPerson;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Tool(
|
||||
name = "ps_get_person_by_id",
|
||||
description = "Retrieve a person record by ID from the in-memory store."
|
||||
)
|
||||
public Optional<Person> getPersonById(int id) {
|
||||
Person person = personStore.get(id);
|
||||
log.debug("Retrieved person by ID {}: {}", id, person);
|
||||
return Optional.ofNullable(person);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Tool(
|
||||
name = "ps_get_all_persons",
|
||||
description = "Retrieve all person records from the in-memory store."
|
||||
)
|
||||
public List<Person> getAllPersons() {
|
||||
// Return an unmodifiable view of the values
|
||||
List<Person> allPersons = personStore.values().stream().toList();
|
||||
log.debug("Retrieved all persons (count: {})", allPersons.size());
|
||||
return allPersons;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Tool(
|
||||
name = "ps_update_person",
|
||||
description = "Update an existing person record by ID in the in-memory store."
|
||||
)
|
||||
public boolean updatePerson(int id, Person updatedPersonData) {
|
||||
if (updatedPersonData == null) {
|
||||
throw new IllegalArgumentException("Updated person data cannot be null");
|
||||
}
|
||||
// Use computeIfPresent for atomic update if the key exists
|
||||
Person result = personStore.computeIfPresent(id, (key, existingPerson) ->
|
||||
// Create a new Person record with the original ID but updated data
|
||||
new Person(
|
||||
id, // Keep original ID
|
||||
updatedPersonData.firstName(),
|
||||
updatedPersonData.lastName(),
|
||||
updatedPersonData.email(),
|
||||
updatedPersonData.sex(),
|
||||
updatedPersonData.ipAddress(),
|
||||
updatedPersonData.jobTitle(),
|
||||
updatedPersonData.age()
|
||||
)
|
||||
);
|
||||
boolean updated = result != null;
|
||||
log.debug("Update attempt for ID {}: {}", id, updated ? "Successful" : "Failed (Not Found)");
|
||||
if(updated) log.trace("Updated person data for ID {}: {}", id, result);
|
||||
return updated;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Tool(
|
||||
name = "ps_delete_person",
|
||||
description = "Delete a person record by ID from the in-memory store."
|
||||
)
|
||||
public boolean deletePerson(int id) {
|
||||
boolean removed = personStore.remove(id) != null;
|
||||
log.debug("Delete attempt for ID {}: {}", id, removed ? "Successful" : "Failed (Not Found)");
|
||||
return removed;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Tool(
|
||||
name = "ps_search_by_job_title",
|
||||
description = "Search for persons by job title in the in-memory store."
|
||||
)
|
||||
public List<Person> searchByJobTitle(String jobTitleQuery) {
|
||||
if (jobTitleQuery == null || jobTitleQuery.isBlank()) {
|
||||
log.debug("Search by job title skipped due to blank query.");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
String lowerCaseQuery = jobTitleQuery.toLowerCase();
|
||||
List<Person> results = personStore.values().stream()
|
||||
.filter(person -> person.jobTitle() != null && person.jobTitle().toLowerCase().contains(lowerCaseQuery))
|
||||
.collect(Collectors.toList());
|
||||
log.debug("Search by job title '{}' found {} results.", jobTitleQuery, results.size());
|
||||
return Collections.unmodifiableList(results);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Tool(
|
||||
name = "ps_filter_by_sex",
|
||||
description = "Filters Persons by sex (case-insensitive)."
|
||||
)
|
||||
public List<Person> filterBySex(String sex) {
|
||||
if (sex == null || sex.isBlank()) {
|
||||
log.debug("Filter by sex skipped due to blank filter.");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<Person> results = personStore.values().stream()
|
||||
.filter(person -> person.sex() != null && person.sex().equalsIgnoreCase(sex))
|
||||
.collect(Collectors.toList());
|
||||
log.debug("Filter by sex '{}' found {} results.", sex, results.size());
|
||||
return Collections.unmodifiableList(results);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Tool(
|
||||
name = "ps_filter_by_age",
|
||||
description = "Filters Persons by age."
|
||||
)
|
||||
public List<Person> filterByAge(int age) {
|
||||
if (age < 0) {
|
||||
log.debug("Filter by age skipped due to negative age: {}", age);
|
||||
return Collections.emptyList(); // Or throw IllegalArgumentException based on requirements
|
||||
}
|
||||
List<Person> results = personStore.values().stream()
|
||||
.filter(person -> person.age() == age)
|
||||
.collect(Collectors.toList());
|
||||
log.debug("Filter by age {} found {} results.", age, results.size());
|
||||
return Collections.unmodifiableList(results);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
/**
|
||||
* 参考 <a href="https://docs.spring.io/spring-ai/reference/api/tools.html#_methods_as_tools">Tool Calling —— Methods as Tools</a>
|
||||
*/
|
||||
package cn.iocoder.yudao.module.ai.tool.method;
|
||||
@@ -1,37 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.util;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.tika.Tika;
|
||||
|
||||
/**
|
||||
* 文件类型 Utils
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public class FileTypeUtils {
|
||||
|
||||
private static final Tika TIKA = new Tika();
|
||||
|
||||
/**
|
||||
* 已知文件名,获取文件类型,在某些情况下比通过字节数组准确,例如使用 jar 文件时,通过名字更为准确
|
||||
*
|
||||
* @param name 文件名
|
||||
* @return mineType 无法识别时会返回“application/octet-stream”
|
||||
*/
|
||||
public static String getMineType(String name) {
|
||||
return TIKA.detect(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是图片
|
||||
*
|
||||
* @param mineType 类型
|
||||
* @return 是否是图片
|
||||
*/
|
||||
public static boolean isImage(String mineType) {
|
||||
return StrUtil.startWith(mineType, "image/");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.ai.anthropic.AnthropicChatModel;
|
||||
import org.springframework.ai.anthropic.AnthropicChatOptions;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.ai.chat.messages.SystemMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {@link AnthropicChatModel} 集成测试类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class AnthropicChatModelTest {
|
||||
|
||||
private final AnthropicChatModel chatModel = AnthropicChatModel.builder()
|
||||
.anthropicApi(AnthropicApi.builder()
|
||||
.apiKey("sk-muubv7cXeLw0Etgs743f365cD5Ea44429946Fa7e672d8942")
|
||||
.baseUrl("https://aihubmix.com")
|
||||
.build())
|
||||
.defaultOptions(AnthropicChatOptions.builder()
|
||||
.model(AnthropicApi.ChatModel.CLAUDE_SONNET_4)
|
||||
.temperature(0.7)
|
||||
.maxTokens(4096)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testCall() {
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
messages.add(new UserMessage("1 + 1 = ?"));
|
||||
|
||||
// 调用
|
||||
ChatResponse response = chatModel.call(new Prompt(messages));
|
||||
// 打印结果
|
||||
System.out.println(response);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream() {
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
messages.add(new UserMessage("1 + 1 = ?"));
|
||||
|
||||
// 调用
|
||||
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
|
||||
// 打印结果
|
||||
flux.doOnNext(System.out::println).then().block();
|
||||
}
|
||||
|
||||
// TODO @芋艿:需要等 spring ai 升级:https://github.com/spring-projects/spring-ai/pull/2800
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream_thinking() {
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new UserMessage("thkinking 下,1+1 为什么等于 2 "));
|
||||
AnthropicChatOptions options = AnthropicChatOptions.builder()
|
||||
.model(AnthropicApi.ChatModel.CLAUDE_SONNET_4)
|
||||
.thinking(AnthropicApi.ThinkingType.ENABLED, 3096)
|
||||
.temperature(1D)
|
||||
.build();
|
||||
|
||||
// 调用
|
||||
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages, options));
|
||||
// 打印结果
|
||||
flux.doOnNext(response -> {
|
||||
// System.out.println(response);
|
||||
System.out.println(response.getResult());
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
||||
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.gemini.GeminiChatModel;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.ai.chat.messages.SystemMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import org.springframework.ai.openai.api.OpenAiApi;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {@link GeminiChatModel} 集成测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class GeminiChatModelTests {
|
||||
|
||||
private final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
|
||||
.openAiApi(OpenAiApi.builder()
|
||||
.baseUrl(GeminiChatModel.BASE_URL)
|
||||
.completionsPath(GeminiChatModel.COMPLETE_PATH)
|
||||
.apiKey("AIzaSyAVoBxgoFvvte820vEQMma2LKBnC98bqMQ")
|
||||
.build())
|
||||
.defaultOptions(OpenAiChatOptions.builder()
|
||||
.model(GeminiChatModel.MODEL_DEFAULT) // 模型
|
||||
.temperature(0.7)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
private final GeminiChatModel chatModel = new GeminiChatModel(openAiChatModel);
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testCall() {
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
messages.add(new UserMessage("1 + 1 = ?"));
|
||||
|
||||
// 调用
|
||||
ChatResponse response = chatModel.call(new Prompt(messages));
|
||||
// 打印结果
|
||||
System.out.println(response);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream() {
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
messages.add(new UserMessage("1 + 1 = ?"));
|
||||
|
||||
// 调用
|
||||
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
|
||||
// 打印结果
|
||||
flux.doOnNext(System.out::println).then().block();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.websearch;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchRequest;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchResponse;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.bocha.AiBoChaWebSearchClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* {@link AiBoChaWebSearchClient} 集成测试类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class AiBoChaWebSearchClientTest {
|
||||
|
||||
private final AiBoChaWebSearchClient webSearchClient = new AiBoChaWebSearchClient(
|
||||
"sk-40500e52840f4d24b956d0b1d80d9abe");
|
||||
|
||||
@Test
|
||||
public void testSearch() {
|
||||
AiWebSearchRequest request = new AiWebSearchRequest()
|
||||
.setQuery("阿里巴巴")
|
||||
.setCount(3);
|
||||
AiWebSearchResponse response = webSearchClient.search(request);
|
||||
System.out.println(JsonUtils.toJsonPrettyString(response));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.api.device;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.RpcConstants;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
|
||||
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.annotation.security.PermitAll;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
/**
|
||||
* IoT 设备 API 实现类
|
||||
*
|
||||
* @author haohao
|
||||
*/
|
||||
@RestController
|
||||
@Validated
|
||||
@Primary // 保证优先匹配,因为 yudao-iot-gateway 也有 IotDeviceCommonApi 的实现,并且也可能会被 biz 引入
|
||||
public class IoTDeviceApiImpl implements IotDeviceCommonApi {
|
||||
|
||||
@Resource
|
||||
private IotDeviceService deviceService;
|
||||
@Resource
|
||||
private IotProductService productService;
|
||||
|
||||
@Override
|
||||
@PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/auth")
|
||||
@PermitAll
|
||||
public CommonResult<Boolean> authDevice(@RequestBody IotDeviceAuthReqDTO authReqDTO) {
|
||||
return success(deviceService.authDevice(authReqDTO));
|
||||
}
|
||||
|
||||
@Override
|
||||
@PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/get") // 特殊:方便调用,暂时使用 POST,实际更推荐 GET
|
||||
@PermitAll
|
||||
public CommonResult<IotDeviceRespDTO> getDevice(@RequestBody IotDeviceGetReqDTO getReqDTO) {
|
||||
IotDeviceDO device = getReqDTO.getId() != null ? deviceService.getDeviceFromCache(getReqDTO.getId())
|
||||
: deviceService.getDeviceFromCache(getReqDTO.getProductKey(), getReqDTO.getDeviceName());
|
||||
return success(BeanUtils.toBean(device, IotDeviceRespDTO.class, deviceDTO -> {
|
||||
IotProductDO product = productService.getProductFromCache(deviceDTO.getProductId());
|
||||
if (product != null) {
|
||||
deviceDTO.setCodecType(product.getCodecType());
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.alert;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config.IotAlertConfigPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config.IotAlertConfigRespVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config.IotAlertConfigSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertConfigDO;
|
||||
import cn.iocoder.yudao.module.iot.service.alert.IotAlertConfigService;
|
||||
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
||||
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
|
||||
|
||||
@Tag(name = "管理后台 - IoT 告警配置")
|
||||
@RestController
|
||||
@RequestMapping("/iot/alert-config")
|
||||
@Validated
|
||||
public class IotAlertConfigController {
|
||||
|
||||
@Resource
|
||||
private IotAlertConfigService alertConfigService;
|
||||
|
||||
@Resource
|
||||
private AdminUserApi adminUserApi;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建告警配置")
|
||||
@PreAuthorize("@ss.hasPermission('iot:alert-config:create')")
|
||||
public CommonResult<Long> createAlertConfig(@Valid @RequestBody IotAlertConfigSaveReqVO createReqVO) {
|
||||
return success(alertConfigService.createAlertConfig(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新告警配置")
|
||||
@PreAuthorize("@ss.hasPermission('iot:alert-config:update')")
|
||||
public CommonResult<Boolean> updateAlertConfig(@Valid @RequestBody IotAlertConfigSaveReqVO updateReqVO) {
|
||||
alertConfigService.updateAlertConfig(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除告警配置")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('iot:alert-config:delete')")
|
||||
public CommonResult<Boolean> deleteAlertConfig(@RequestParam("id") Long id) {
|
||||
alertConfigService.deleteAlertConfig(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得告警配置")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('iot:alert-config:query')")
|
||||
public CommonResult<IotAlertConfigRespVO> getAlertConfig(@RequestParam("id") Long id) {
|
||||
IotAlertConfigDO alertConfig = alertConfigService.getAlertConfig(id);
|
||||
return success(BeanUtils.toBean(alertConfig, IotAlertConfigRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得告警配置分页")
|
||||
@PreAuthorize("@ss.hasPermission('iot:alert-config:query')")
|
||||
public CommonResult<PageResult<IotAlertConfigRespVO>> getAlertConfigPage(@Valid IotAlertConfigPageReqVO pageReqVO) {
|
||||
PageResult<IotAlertConfigDO> pageResult = alertConfigService.getAlertConfigPage(pageReqVO);
|
||||
|
||||
// 转换返回
|
||||
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
|
||||
convertSetByFlatMap(pageResult.getList(), config -> config.getReceiveUserIds().stream()));
|
||||
return success(BeanUtils.toBean(pageResult, IotAlertConfigRespVO.class, vo -> {
|
||||
vo.setReceiveUserNames(vo.getReceiveUserIds().stream()
|
||||
.map(userMap::get)
|
||||
.filter(Objects::nonNull)
|
||||
.map(AdminUserRespDTO::getNickname)
|
||||
.collect(Collectors.toList()));
|
||||
}));
|
||||
}
|
||||
|
||||
@GetMapping("/simple-list")
|
||||
@Operation(summary = "获得告警配置简单列表", description = "只包含被开启的告警配置,主要用于前端的下拉选项")
|
||||
@PreAuthorize("@ss.hasPermission('iot:alert-config:query')")
|
||||
public CommonResult<List<IotAlertConfigRespVO>> getAlertConfigSimpleList() {
|
||||
List<IotAlertConfigDO> list = alertConfigService.getAlertConfigListByStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
return success(convertList(list, config -> // 只返回 id、name 字段
|
||||
new IotAlertConfigRespVO().setId(config.getId()).setName(config.getName())));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.alert;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.recrod.IotAlertRecordPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.recrod.IotAlertRecordProcessReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.recrod.IotAlertRecordRespVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertRecordDO;
|
||||
import cn.iocoder.yudao.module.iot.service.alert.IotAlertRecordService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static java.util.Collections.singleton;
|
||||
|
||||
@Tag(name = "管理后台 - IoT 告警记录")
|
||||
@RestController
|
||||
@RequestMapping("/iot/alert-record")
|
||||
@Validated
|
||||
public class IotAlertRecordController {
|
||||
|
||||
@Resource
|
||||
private IotAlertRecordService alertRecordService;
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得告警记录")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('iot:alert-record:query')")
|
||||
public CommonResult<IotAlertRecordRespVO> getAlertRecord(@RequestParam("id") Long id) {
|
||||
IotAlertRecordDO alertRecord = alertRecordService.getAlertRecord(id);
|
||||
return success(BeanUtils.toBean(alertRecord, IotAlertRecordRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得告警记录分页")
|
||||
@PreAuthorize("@ss.hasPermission('iot:alert-record:query')")
|
||||
public CommonResult<PageResult<IotAlertRecordRespVO>> getAlertRecordPage(@Valid IotAlertRecordPageReqVO pageReqVO) {
|
||||
PageResult<IotAlertRecordDO> pageResult = alertRecordService.getAlertRecordPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, IotAlertRecordRespVO.class));
|
||||
}
|
||||
|
||||
@PutMapping("/process")
|
||||
@Operation(summary = "处理告警记录")
|
||||
@PreAuthorize("@ss.hasPermission('iot:alert-record:process')")
|
||||
public CommonResult<Boolean> processAlertRecord(@Valid @RequestBody IotAlertRecordProcessReqVO processReqVO) {
|
||||
alertRecordService.processAlertRecordList(singleton(processReqVO.getId()), processReqVO.getProcessRemark());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 告警配置分页 Request VO")
|
||||
@Data
|
||||
public class IotAlertConfigPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "配置名称", example = "赵六")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "配置状态", example = "1")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 告警配置 Response VO")
|
||||
@Data
|
||||
public class IotAlertConfigRespVO {
|
||||
|
||||
@Schema(description = "配置编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3566")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "配置名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "配置描述", example = "你猜")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "告警级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer level;
|
||||
|
||||
@Schema(description = "配置状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "关联的场景联动规则编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3")
|
||||
private List<Long> sceneRuleIds;
|
||||
|
||||
@Schema(description = "接收的用户编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "100,200")
|
||||
private List<Long> receiveUserIds;
|
||||
|
||||
@Schema(description = "接收的用户名称数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三,李四")
|
||||
private List<String> receiveUserNames;
|
||||
|
||||
@Schema(description = "接收的类型数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3")
|
||||
private List<Integer> receiveTypes;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 告警配置新增/修改 Request VO")
|
||||
@Data
|
||||
public class IotAlertConfigSaveReqVO {
|
||||
|
||||
@Schema(description = "配置编号", example = "3566")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "配置名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
|
||||
@NotEmpty(message = "配置名称不能为空")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "配置描述", example = "你猜")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "告警级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "告警级别不能为空")
|
||||
private Integer level;
|
||||
|
||||
@Schema(description = "配置状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "配置状态不能为空")
|
||||
@InEnum(CommonStatusEnum.class)
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "关联的场景联动规则编号数组")
|
||||
@NotEmpty(message = "关联的场景联动规则编号数组不能为空")
|
||||
private List<Long> sceneRuleIds;
|
||||
|
||||
@Schema(description = "接收的用户编号数组")
|
||||
@NotEmpty(message = "接收的用户编号数组不能为空")
|
||||
private List<Long> receiveUserIds;
|
||||
|
||||
@Schema(description = "接收的类型数组")
|
||||
@NotEmpty(message = "接收的类型数组不能为空")
|
||||
private List<Integer> receiveTypes;
|
||||
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.alert.vo.recrod;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 告警记录分页 Request VO")
|
||||
@Data
|
||||
public class IotAlertRecordPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "告警配置编号", example = "29320")
|
||||
private Long configId;
|
||||
|
||||
@Schema(description = "告警级别", example = "1")
|
||||
private Integer level;
|
||||
|
||||
@Schema(description = "产品编号", example = "2050")
|
||||
private Long productId;
|
||||
|
||||
@Schema(description = "设备编号", example = "21727")
|
||||
private String deviceId;
|
||||
|
||||
@Schema(description = "是否处理", example = "true")
|
||||
private Boolean processStatus;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.alert.vo.recrod;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 告警记录处理 Request VO")
|
||||
@Data
|
||||
public class IotAlertRecordProcessReqVO {
|
||||
|
||||
@Schema(description = "记录编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "记录编号不能为空")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "处理结果(备注)", requiredMode = Schema.RequiredMode.REQUIRED, example = "已处理告警,问题已解决")
|
||||
private String processRemark;
|
||||
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.alert.vo.recrod;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 告警记录 Response VO")
|
||||
@Data
|
||||
public class IotAlertRecordRespVO {
|
||||
|
||||
@Schema(description = "记录编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "19904")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "告警配置编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29320")
|
||||
private Long configId;
|
||||
|
||||
@Schema(description = "告警名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
|
||||
private String configName;
|
||||
|
||||
@Schema(description = "告警级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer configLevel;
|
||||
|
||||
@Schema(description = "产品编号", example = "2050")
|
||||
private Long productId;
|
||||
|
||||
@Schema(description = "设备编号", example = "21727")
|
||||
private Long deviceId;
|
||||
|
||||
@Schema(description = "触发的设备消息")
|
||||
private IotDeviceMessage deviceMessage;
|
||||
|
||||
@Schema(description = "是否处理", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Boolean processStatus;
|
||||
|
||||
@Schema(description = "处理结果(备注)", example = "你说的对")
|
||||
private String processRemark;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
### 请求 /iot/device/message/send 接口(属性上报)=> 成功
|
||||
POST {{baseUrl}}/iot/device/message/send
|
||||
Content-Type: application/json
|
||||
tenant-id: {{adminTenantId}}
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
{
|
||||
"deviceId": 25,
|
||||
"method": "thing.property.post",
|
||||
"params": {
|
||||
"width": 1,
|
||||
"height": "2",
|
||||
"oneThree": "3"
|
||||
}
|
||||
}
|
||||
|
||||
### 请求 /iot/device/downstream 接口(服务调用)=> 成功 TODO 芋艿:未更新为最新
|
||||
POST {{baseUrl}}/iot/device/downstream
|
||||
Content-Type: application/json
|
||||
tenant-id: {{adminTenantId}}
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
{
|
||||
"id": 25,
|
||||
"type": "service",
|
||||
"identifier": "temperature",
|
||||
"data": {
|
||||
"xx": "yy"
|
||||
}
|
||||
}
|
||||
|
||||
### 请求 /iot/device/downstream 接口(属性设置)=> 成功 TODO 芋艿:未更新为最新
|
||||
POST {{baseUrl}}/iot/device/downstream
|
||||
Content-Type: application/json
|
||||
tenant-id: {{adminTenantId}}
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
{
|
||||
"id": 25,
|
||||
"type": "property",
|
||||
"identifier": "set",
|
||||
"data": {
|
||||
"xx": "yy"
|
||||
}
|
||||
}
|
||||
|
||||
### 请求 /iot/device/downstream 接口(属性获取)=> 成功 TODO 芋艿:未更新为最新
|
||||
POST {{baseUrl}}/iot/device/downstream
|
||||
Content-Type: application/json
|
||||
tenant-id: {{adminTenantId}}
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
{
|
||||
"id": 25,
|
||||
"type": "property",
|
||||
"identifier": "get",
|
||||
"data": ["xx", "yy"]
|
||||
}
|
||||
|
||||
### 请求 /iot/device/downstream 接口(配置设置)=> 成功 TODO 芋艿:未更新为最新
|
||||
POST {{baseUrl}}/iot/device/downstream
|
||||
Content-Type: application/json
|
||||
tenant-id: {{adminTenantId}}
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
{
|
||||
"id": 25,
|
||||
"type": "config",
|
||||
"identifier": "set"
|
||||
}
|
||||
|
||||
### 请求 /iot/device/downstream 接口(OTA 升级)=> 成功 TODO 芋艿:未更新为最新
|
||||
POST {{baseUrl}}/iot/device/downstream
|
||||
Content-Type: application/json
|
||||
tenant-id: {{adminTenantId}}
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
{
|
||||
"id": 25,
|
||||
"type": "ota",
|
||||
"identifier": "upgrade",
|
||||
"data": {
|
||||
"firmwareId": 1,
|
||||
"version": "1.0.0",
|
||||
"signMethod": "MD5",
|
||||
"fileSign": "d41d8cd98f00b204e9800998ecf8427e",
|
||||
"fileSize": 1024,
|
||||
"fileUrl": "http://example.com/firmware.bin",
|
||||
"information": "{\"desc\":\"升级到最新版本\"}"
|
||||
}
|
||||
}
|
||||
|
||||
### 查询设备消息对分页 - 基础查询(设备编号25)
|
||||
GET {{baseUrl}}/iot/device/message/pair-page?deviceId=25&pageNo=1&pageSize=10
|
||||
Authorization: Bearer {{token}}
|
||||
tenant-id: {{adminTenantId}}
|
||||
|
||||
### 查询设备消息对分页 - 按标识符过滤(identifier=eat)
|
||||
GET {{baseUrl}}/iot/device/message/pair-page?deviceId=25&identifier=eat&pageNo=1&pageSize=10
|
||||
Authorization: Bearer {{token}}
|
||||
tenant-id: {{adminTenantId}}
|
||||
@@ -1,92 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.device;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceMessagePageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceMessageRespPairVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceMessageRespVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceMessageSendReqVO;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.tdengine.IotDeviceMessageMapper;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
|
||||
import cn.iocoder.yudao.module.iot.service.device.message.IotDeviceMessageService;
|
||||
import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
|
||||
|
||||
@Tag(name = "管理后台 - IoT 设备消息")
|
||||
@RestController
|
||||
@RequestMapping("/iot/device/message")
|
||||
@Validated
|
||||
public class IotDeviceMessageController {
|
||||
|
||||
@Resource
|
||||
private IotDeviceMessageService deviceMessageService;
|
||||
@Resource
|
||||
private IotDeviceService deviceService;
|
||||
@Resource
|
||||
private IotThingModelService thingModelService;
|
||||
@Resource
|
||||
private IotDeviceMessageMapper deviceMessageMapper;
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得设备消息分页")
|
||||
@PreAuthorize("@ss.hasPermission('iot:device:message-query')")
|
||||
public CommonResult<PageResult<IotDeviceMessageRespVO>> getDeviceMessagePage(
|
||||
@Valid IotDeviceMessagePageReqVO pageReqVO) {
|
||||
PageResult<IotDeviceMessageDO> pageResult = deviceMessageService.getDeviceMessagePage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, IotDeviceMessageRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/pair-page")
|
||||
@Operation(summary = "获得设备消息对分页")
|
||||
@PreAuthorize("@ss.hasPermission('iot:device:message-query')")
|
||||
public CommonResult<PageResult<IotDeviceMessageRespPairVO>> getDeviceMessagePairPage(
|
||||
@Valid IotDeviceMessagePageReqVO pageReqVO) {
|
||||
// 1.1 先按照条件,查询 request 的消息(非 reply)
|
||||
pageReqVO.setReply(false);
|
||||
PageResult<IotDeviceMessageDO> requestMessagePageResult = deviceMessageService.getDeviceMessagePage(pageReqVO);
|
||||
if (CollUtil.isEmpty(requestMessagePageResult.getList())) {
|
||||
return success(PageResult.empty());
|
||||
}
|
||||
// 1.2 接着按照 requestIds,批量查询 reply 消息
|
||||
List<String> requestIds = convertList(requestMessagePageResult.getList(), IotDeviceMessageDO::getRequestId);
|
||||
List<IotDeviceMessageDO> replyMessageList = deviceMessageService.getDeviceMessageListByRequestIdsAndReply(
|
||||
pageReqVO.getDeviceId(), requestIds, true);
|
||||
Map<String, IotDeviceMessageDO> replyMessages = convertMap(replyMessageList, IotDeviceMessageDO::getRequestId);
|
||||
|
||||
// 2. 组装结果
|
||||
List<IotDeviceMessageRespPairVO> pairMessages = convertList(requestMessagePageResult.getList(),
|
||||
requestMessage -> {
|
||||
IotDeviceMessageDO replyMessage = replyMessages.get(requestMessage.getRequestId());
|
||||
return new IotDeviceMessageRespPairVO()
|
||||
.setRequest(BeanUtils.toBean(requestMessage, IotDeviceMessageRespVO.class))
|
||||
.setReply(BeanUtils.toBean(replyMessage, IotDeviceMessageRespVO.class));
|
||||
});
|
||||
return success(new PageResult<>(pairMessages, requestMessagePageResult.getTotal()));
|
||||
}
|
||||
|
||||
@PostMapping("/send")
|
||||
@Operation(summary = "发送消息", description = "可用于设备模拟")
|
||||
@PreAuthorize("@ss.hasPermission('iot:device:message-end')")
|
||||
public CommonResult<Boolean> sendDeviceMessage(@Valid @RequestBody IotDeviceMessageSendReqVO sendReqVO) {
|
||||
deviceMessageService.sendDeviceMessage(BeanUtils.toBean(sendReqVO, IotDeviceMessage.class));
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 设备认证信息 Response VO")
|
||||
@Data
|
||||
public class IotDeviceAuthInfoRespVO {
|
||||
|
||||
@Schema(description = "客户端 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "product123.device001")
|
||||
@NotBlank(message = "客户端 ID 不能为空")
|
||||
private String clientId;
|
||||
|
||||
@Schema(description = "用户名", requiredMode = Schema.RequiredMode.REQUIRED, example = "device001&product123")
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
private String username;
|
||||
|
||||
@Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1a2b3c4d5e6f7890abcdef1234567890")
|
||||
@NotBlank(message = "密码不能为空")
|
||||
private String password;
|
||||
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - 通过产品标识和设备名称列表获取设备 Request VO")
|
||||
@Data
|
||||
public class IotDeviceByProductKeyAndNamesReqVO {
|
||||
|
||||
@Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "1de24640dfe")
|
||||
@NotBlank(message = "产品标识不能为空")
|
||||
private String productKey;
|
||||
|
||||
@Schema(description = "设备名称列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "device001,device002")
|
||||
@NotEmpty(message = "设备名称列表不能为空")
|
||||
private List<String> deviceNames;
|
||||
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.message;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 设备消息分页查询 Request VO")
|
||||
@Data
|
||||
public class IotDeviceMessagePageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "设备编号不能为空")
|
||||
private Long deviceId;
|
||||
|
||||
@Schema(description = "消息类型", example = "property")
|
||||
@InEnum(IotDeviceMessageMethodEnum.class)
|
||||
private String method;
|
||||
|
||||
@Schema(description = "是否上行", example = "true")
|
||||
private Boolean upstream;
|
||||
|
||||
@Schema(description = "是否回复", example = "true")
|
||||
private Boolean reply;
|
||||
|
||||
@Schema(description = "标识符", example = "temperature")
|
||||
private String identifier;
|
||||
|
||||
@Schema(description = "时间范围", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
@Size(min = 2, max = 2, message = "请选择时间范围")
|
||||
private LocalDateTime[] times;
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.message;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 设备消息对 Response VO")
|
||||
@Data
|
||||
public class IotDeviceMessageRespPairVO {
|
||||
|
||||
@Schema(description = "请求消息", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private IotDeviceMessageRespVO request;
|
||||
|
||||
@Schema(description = "响应消息")
|
||||
private IotDeviceMessageRespVO reply; // 通过 requestId 配对
|
||||
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.message;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 设备消息 Response VO")
|
||||
@Data
|
||||
public class IotDeviceMessageRespVO {
|
||||
|
||||
@Schema(description = "消息编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private String id;
|
||||
|
||||
@Schema(description = "上报时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime reportTime;
|
||||
|
||||
@Schema(description = "记录时间戳", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime ts;
|
||||
|
||||
@Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "123")
|
||||
private Long deviceId;
|
||||
|
||||
@Schema(description = "服务编号", example = "server_123")
|
||||
private String serverId;
|
||||
|
||||
@Schema(description = "是否上行消息", example = "true", examples = "false")
|
||||
private Boolean upstream;
|
||||
|
||||
@Schema(description = "是否回复消息", example = "false", examples = "true")
|
||||
private Boolean reply;
|
||||
|
||||
@Schema(description = "标识符", example = "temperature")
|
||||
private String identifier;
|
||||
|
||||
// ========== codec(编解码)字段 ==========
|
||||
|
||||
@Schema(description = "请求编号", example = "req_123")
|
||||
private String requestId;
|
||||
|
||||
@Schema(description = "请求方法", requiredMode = Schema.RequiredMode.REQUIRED, example = "thing.property.report")
|
||||
private String method;
|
||||
|
||||
@Schema(description = "请求参数")
|
||||
private Object params;
|
||||
|
||||
@Schema(description = "响应结果")
|
||||
private Object data;
|
||||
|
||||
@Schema(description = "响应错误码", example = "200")
|
||||
private Integer code;
|
||||
|
||||
@Schema(description = "响应提示", example = "操作成功")
|
||||
private String msg;
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.message;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 设备消息发送 Request VO") // 属性上报、事件上报、状态变更等
|
||||
@Data
|
||||
public class IotDeviceMessageSendReqVO {
|
||||
|
||||
@Schema(description = "请求方法", requiredMode = Schema.RequiredMode.REQUIRED, example = "report")
|
||||
@NotEmpty(message = "请求方法不能为空")
|
||||
@InEnum(IotDeviceMessageMethodEnum.class)
|
||||
private String method;
|
||||
|
||||
@Schema(description = "请求参数")
|
||||
private Object params; // 例如说:属性上报的 properties、事件上报的 params
|
||||
|
||||
@Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "177")
|
||||
@NotNull(message = "设备编号不能为空")
|
||||
private Long deviceId;
|
||||
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.property;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType.ThingModelDataSpecs;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 设备属性详细 Response VO") // 额外增加 来自 ThingModelProperty 的变量 属性
|
||||
@Data
|
||||
public class IotDevicePropertyDetailRespVO extends IotDevicePropertyRespVO {
|
||||
|
||||
@Schema(description = "属性名称", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String name;
|
||||
|
||||
@Schema(description = "数据类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "int")
|
||||
private String dataType;
|
||||
|
||||
@Schema(description = "数据定义")
|
||||
private ThingModelDataSpecs dataSpecs;
|
||||
|
||||
@Schema(description = "数据定义列表")
|
||||
private List<ThingModelDataSpecs> dataSpecsList;
|
||||
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.ota;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.IotOtaTaskCreateReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.IotOtaTaskPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.IotOtaTaskRespVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskDO;
|
||||
import cn.iocoder.yudao.module.iot.service.ota.IotOtaTaskService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - IoT OTA 升级任务")
|
||||
@RestController
|
||||
@RequestMapping("/iot/ota/task")
|
||||
@Validated
|
||||
public class IotOtaTaskController {
|
||||
|
||||
@Resource
|
||||
private IotOtaTaskService otaTaskService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建 OTA 升级任务")
|
||||
@PreAuthorize(value = "@ss.hasPermission('iot:ota-task:create')")
|
||||
public CommonResult<Long> createOtaTask(@Valid @RequestBody IotOtaTaskCreateReqVO createReqVO) {
|
||||
return success(otaTaskService.createOtaTask(createReqVO));
|
||||
}
|
||||
|
||||
@PostMapping("/cancel")
|
||||
@Operation(summary = "取消 OTA 升级任务")
|
||||
@Parameter(name = "id", description = "升级任务编号", required = true)
|
||||
@PreAuthorize(value = "@ss.hasPermission('iot:ota-task:cancel')")
|
||||
public CommonResult<Boolean> cancelOtaTask(@RequestParam("id") Long id) {
|
||||
otaTaskService.cancelOtaTask(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得 OTA 升级任务分页")
|
||||
@PreAuthorize(value = "@ss.hasPermission('iot:ota-task:query')")
|
||||
public CommonResult<PageResult<IotOtaTaskRespVO>> getOtaTaskPage(@Valid IotOtaTaskPageReqVO pageReqVO) {
|
||||
PageResult<IotOtaTaskDO> pageResult = otaTaskService.getOtaTaskPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, IotOtaTaskRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得 OTA 升级任务")
|
||||
@Parameter(name = "id", description = "升级任务编号", required = true, example = "1024")
|
||||
@PreAuthorize(value = "@ss.hasPermission('iot:ota-task:query')")
|
||||
public CommonResult<IotOtaTaskRespVO> getOtaTask(@RequestParam("id") Long id) {
|
||||
IotOtaTaskDO upgradeTask = otaTaskService.getOtaTask(id);
|
||||
return success(BeanUtils.toBean(upgradeTask, IotOtaTaskRespVO.class));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.ota;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record.IotOtaTaskRecordPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record.IotOtaTaskRecordRespVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
|
||||
import cn.iocoder.yudao.module.iot.service.ota.IotOtaFirmwareService;
|
||||
import cn.iocoder.yudao.module.iot.service.ota.IotOtaTaskRecordService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.Parameters;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||
|
||||
@Tag(name = "管理后台 - IoT OTA 升级任务记录")
|
||||
@RestController
|
||||
@RequestMapping("/iot/ota/task/record")
|
||||
@Validated
|
||||
public class IotOtaTaskRecordController {
|
||||
|
||||
@Resource
|
||||
private IotOtaTaskRecordService otaTaskRecordService;
|
||||
@Resource
|
||||
private IotDeviceService deviceService;
|
||||
@Resource
|
||||
private IotOtaFirmwareService otaFirmwareService;
|
||||
|
||||
@GetMapping("/get-status-statistics")
|
||||
@Operation(summary = "获得 OTA 升级记录状态统计")
|
||||
@Parameters({
|
||||
@Parameter(name = "firmwareId", description = "固件编号", example = "1024"),
|
||||
@Parameter(name = "taskId", description = "升级任务编号", example = "2048")
|
||||
})
|
||||
@PreAuthorize("@ss.hasPermission('iot:ota-task-record:query')")
|
||||
public CommonResult<Map<Integer, Long>> getOtaTaskRecordStatusStatistics(
|
||||
@RequestParam(value = "firmwareId", required = false) Long firmwareId,
|
||||
@RequestParam(value = "taskId", required = false) Long taskId) {
|
||||
return success(otaTaskRecordService.getOtaTaskRecordStatusStatistics(firmwareId, taskId));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得 OTA 升级记录分页")
|
||||
@PreAuthorize("@ss.hasPermission('iot:ota-task-record:query')")
|
||||
public CommonResult<PageResult<IotOtaTaskRecordRespVO>> getOtaTaskRecordPage(
|
||||
@Valid IotOtaTaskRecordPageReqVO pageReqVO) {
|
||||
PageResult<IotOtaTaskRecordDO> pageResult = otaTaskRecordService.getOtaTaskRecordPage(pageReqVO);
|
||||
if (CollUtil.isEmpty(pageResult.getList())) {
|
||||
return success(PageResult.empty());
|
||||
}
|
||||
|
||||
// 批量查询固件信息
|
||||
Map<Long, IotOtaFirmwareDO> firmwareMap = otaFirmwareService.getOtaFirmwareMap(
|
||||
convertSet(pageResult.getList(), IotOtaTaskRecordDO::getFromFirmwareId));
|
||||
Map<Long, IotDeviceDO> deviceMap = deviceService.getDeviceMap(
|
||||
convertSet(pageResult.getList(), IotOtaTaskRecordDO::getDeviceId));
|
||||
// 转换为响应 VO
|
||||
return success(BeanUtils.toBean(pageResult, IotOtaTaskRecordRespVO.class, (vo) -> {
|
||||
MapUtils.findAndThen(firmwareMap, vo.getFromFirmwareId(), firmware ->
|
||||
vo.setFromFirmwareVersion(firmware.getVersion()));
|
||||
MapUtils.findAndThen(deviceMap, vo.getDeviceId(), device ->
|
||||
vo.setDeviceName(device.getDeviceName()));
|
||||
}));
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得 OTA 升级记录")
|
||||
@PreAuthorize("@ss.hasPermission('iot:ota-task-record:query')")
|
||||
@Parameter(name = "id", description = "升级记录编号", required = true, example = "1024")
|
||||
public CommonResult<IotOtaTaskRecordRespVO> getOtaTaskRecord(@RequestParam("id") Long id) {
|
||||
IotOtaTaskRecordDO upgradeRecord = otaTaskRecordService.getOtaTaskRecord(id);
|
||||
return success(BeanUtils.toBean(upgradeRecord, IotOtaTaskRecordRespVO.class));
|
||||
}
|
||||
|
||||
@PutMapping("/cancel")
|
||||
@Operation(summary = "取消 OTA 升级记录")
|
||||
@PreAuthorize("@ss.hasPermission('iot:ota-task-record:cancel')")
|
||||
@Parameter(name = "id", description = "升级记录编号", required = true, example = "1024")
|
||||
public CommonResult<Boolean> cancelOtaTaskRecord(@RequestParam("id") Long id) {
|
||||
otaTaskRecordService.cancelOtaTaskRecord(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - IoT OTA 升级任务分页 Request VO")
|
||||
@Data
|
||||
public class IotOtaTaskPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "任务名称", example = "升级任务")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "固件编号", example = "1024")
|
||||
private Long firmwareId;
|
||||
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task;
|
||||
|
||||
import com.fhs.core.trans.vo.VO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - IoT OTA 升级任务 Response VO")
|
||||
@Data
|
||||
public class IotOtaTaskRespVO implements VO {
|
||||
|
||||
@Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "任务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "升级任务")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "任务描述", example = "升级任务")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "固件编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long firmwareId;
|
||||
|
||||
@Schema(description = "任务状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "升级范围", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer deviceScope;
|
||||
|
||||
@Schema(description = "设备总共数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Integer deviceTotalCount;
|
||||
|
||||
@Schema(description = "设备成功数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "66")
|
||||
private Integer deviceSuccessCount;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2022-07-08 07:30:00")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskRecordStatusEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - IoT OTA 升级记录分页 Request VO")
|
||||
@Data
|
||||
public class IotOtaTaskRecordPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "升级任务编号", example = "1024")
|
||||
private Long taskId;
|
||||
|
||||
@Schema(description = "升级记录状态", example = "5")
|
||||
@InEnum(IotOtaTaskRecordStatusEnum.class)
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - IoT OTA 升级任务记录 Response VO")
|
||||
@Data
|
||||
public class IotOtaTaskRecordRespVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "固件编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long firmwareId;
|
||||
|
||||
@Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
|
||||
private Long taskId;
|
||||
|
||||
@Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long deviceId;
|
||||
|
||||
@Schema(description = "设备名称", example = "智能开关")
|
||||
private String deviceName;
|
||||
|
||||
@Schema(description = "来源的固件编号", example = "1023")
|
||||
private Long fromFirmwareId;
|
||||
|
||||
@Schema(description = "来源固件版本", example = "1.0.0")
|
||||
private String fromFirmwareVersion;
|
||||
|
||||
@Schema(description = "升级状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "升级进度,百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "50")
|
||||
private Integer progress;
|
||||
|
||||
@Schema(description = "升级进度描述", example = "正在下载固件...")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.rule;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule.IotDataRulePageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule.IotDataRuleRespVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule.IotDataRuleSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.data.IotDataRuleService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - IoT 数据流转规则")
|
||||
@RestController
|
||||
@RequestMapping("/iot/data-rule")
|
||||
@Validated
|
||||
public class IotDataRuleController {
|
||||
|
||||
@Resource
|
||||
private IotDataRuleService dataRuleService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建数据流转规则")
|
||||
@PreAuthorize("@ss.hasPermission('iot:data-rule:create')")
|
||||
public CommonResult<Long> createDataRule(@Valid @RequestBody IotDataRuleSaveReqVO createReqVO) {
|
||||
return success(dataRuleService.createDataRule(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新数据流转规则")
|
||||
@PreAuthorize("@ss.hasPermission('iot:data-rule:update')")
|
||||
public CommonResult<Boolean> updateDataRule(@Valid @RequestBody IotDataRuleSaveReqVO updateReqVO) {
|
||||
dataRuleService.updateDataRule(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除数据流转规则")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('iot:data-rule:delete')")
|
||||
public CommonResult<Boolean> deleteDataRule(@RequestParam("id") Long id) {
|
||||
dataRuleService.deleteDataRule(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得数据流转规则")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('iot:data-rule:query')")
|
||||
public CommonResult<IotDataRuleRespVO> getDataRule(@RequestParam("id") Long id) {
|
||||
IotDataRuleDO dataRule = dataRuleService.getDataRule(id);
|
||||
return success(BeanUtils.toBean(dataRule, IotDataRuleRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得数据流转规则分页")
|
||||
@PreAuthorize("@ss.hasPermission('iot:data-rule:query')")
|
||||
public CommonResult<PageResult<IotDataRuleRespVO>> getDataRulePage(@Valid IotDataRulePageReqVO pageReqVO) {
|
||||
PageResult<IotDataRuleDO> pageResult = dataRuleService.getDataRulePage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, IotDataRuleRespVO.class));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.rule;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.sink.IotDataSinkPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.sink.IotDataSinkRespVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.sink.IotDataSinkSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataSinkDO;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.data.IotDataSinkService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
|
||||
@Tag(name = "管理后台 - IoT 数据流转目的")
|
||||
@RestController
|
||||
@RequestMapping("/iot/data-sink")
|
||||
@Validated
|
||||
public class IotDataSinkController {
|
||||
|
||||
@Resource
|
||||
private IotDataSinkService dataSinkService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建数据目的")
|
||||
@PreAuthorize("@ss.hasPermission('iot:data-sink:create')")
|
||||
public CommonResult<Long> createDataSink(@Valid @RequestBody IotDataSinkSaveReqVO createReqVO) {
|
||||
return success(dataSinkService.createDataSink(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新数据目的")
|
||||
@PreAuthorize("@ss.hasPermission('iot:data-sink:update')")
|
||||
public CommonResult<Boolean> updateDataSink(@Valid @RequestBody IotDataSinkSaveReqVO updateReqVO) {
|
||||
dataSinkService.updateDataSink(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除数据目的")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('iot:data-sink:delete')")
|
||||
public CommonResult<Boolean> deleteDataSink(@RequestParam("id") Long id) {
|
||||
dataSinkService.deleteDataSink(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得数据目的")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('iot:data-sink:query')")
|
||||
public CommonResult<IotDataSinkRespVO> getDataSink(@RequestParam("id") Long id) {
|
||||
IotDataSinkDO sink = dataSinkService.getDataSink(id);
|
||||
return success(BeanUtils.toBean(sink, IotDataSinkRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得数据目的分页")
|
||||
@PreAuthorize("@ss.hasPermission('iot:data-sink:query')")
|
||||
public CommonResult<PageResult<IotDataSinkRespVO>> getDataSinkPage(@Valid IotDataSinkPageReqVO pageReqVO) {
|
||||
PageResult<IotDataSinkDO> pageResult = dataSinkService.getDataSinkPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, IotDataSinkRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/simple-list")
|
||||
@Operation(summary = "获取数据目的的精简信息列表", description = "主要用于前端的下拉选项")
|
||||
public CommonResult<List<IotDataSinkRespVO>> getDataSinkSimpleList() {
|
||||
List<IotDataSinkDO> list = dataSinkService.getDataSinkListByStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
return success(convertList(list, sink -> // 只返回 id、name 字段
|
||||
new IotDataSinkRespVO().setId(sink.getId()).setName(sink.getName())));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.rule;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRulePageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleRespVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleUpdateStatusReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.scene.IotSceneRuleService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
|
||||
@Tag(name = "管理后台 - IoT 场景联动")
|
||||
@RestController
|
||||
@RequestMapping("/iot/scene-rule")
|
||||
@Validated
|
||||
public class IotSceneRuleController {
|
||||
|
||||
@Resource
|
||||
private IotSceneRuleService sceneRuleService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建场景联动")
|
||||
@PreAuthorize("@ss.hasPermission('iot:scene-rule:create')")
|
||||
public CommonResult<Long> createSceneRule(@Valid @RequestBody IotSceneRuleSaveReqVO createReqVO) {
|
||||
return success(sceneRuleService.createSceneRule(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新场景联动")
|
||||
@PreAuthorize("@ss.hasPermission('iot:scene-rule:update')")
|
||||
public CommonResult<Boolean> updateSceneRule(@Valid @RequestBody IotSceneRuleSaveReqVO updateReqVO) {
|
||||
sceneRuleService.updateSceneRule(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PutMapping("/update-status")
|
||||
@Operation(summary = "更新场景联动状态")
|
||||
@PreAuthorize("@ss.hasPermission('iot:scene-rule:update')")
|
||||
public CommonResult<Boolean> updateSceneRuleStatus(@Valid @RequestBody IotSceneRuleUpdateStatusReqVO updateReqVO) {
|
||||
sceneRuleService.updateSceneRuleStatus(updateReqVO.getId(), updateReqVO.getStatus());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除场景联动")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('iot:scene-rule:delete')")
|
||||
public CommonResult<Boolean> deleteSceneRule(@RequestParam("id") Long id) {
|
||||
sceneRuleService.deleteSceneRule(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得场景联动")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('iot:scene-rule:query')")
|
||||
public CommonResult<IotSceneRuleRespVO> getSceneRule(@RequestParam("id") Long id) {
|
||||
IotSceneRuleDO sceneRule = sceneRuleService.getSceneRule(id);
|
||||
return success(BeanUtils.toBean(sceneRule, IotSceneRuleRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得场景联动分页")
|
||||
@PreAuthorize("@ss.hasPermission('iot:scene-rule:query')")
|
||||
public CommonResult<PageResult<IotSceneRuleRespVO>> getSceneRulePage(@Valid IotSceneRulePageReqVO pageReqVO) {
|
||||
PageResult<IotSceneRuleDO> pageResult = sceneRuleService.getSceneRulePage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, IotSceneRuleRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/simple-list")
|
||||
@Operation(summary = "获取场景联动的精简信息列表", description = "主要用于前端的下拉选项")
|
||||
public CommonResult<List<IotSceneRuleRespVO>> getSceneRuleSimpleList() {
|
||||
List<IotSceneRuleDO> list = sceneRuleService.getSceneRuleListByStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
return success(convertList(list, scene -> // 只返回 id、name 字段
|
||||
new IotSceneRuleRespVO().setId(scene.getId()).setName(scene.getName())));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data;
|
||||
@@ -1,26 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 数据流转规则分页 Request VO")
|
||||
@Data
|
||||
public class IotDataRulePageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "数据流转规则名称", example = "芋艿")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "数据流转规则状态", example = "1")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataRuleDO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 数据流转规则 Response VO")
|
||||
@Data
|
||||
public class IotDataRuleRespVO {
|
||||
|
||||
@Schema(description = "数据流转规则编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "8540")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "数据流转规则名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "数据流转规则描述", example = "你猜")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "数据流转规则状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "数据源配置数组", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private List<IotDataRuleDO.SourceConfig> sourceConfigs;
|
||||
|
||||
@Schema(description = "数据目的编号数组", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private List<Long> sinkIds;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataRuleDO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 数据流转规则新增/修改 Request VO")
|
||||
@Data
|
||||
public class IotDataRuleSaveReqVO {
|
||||
|
||||
@Schema(description = "数据流转规则编号", example = "8540")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "数据流转规则名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
|
||||
@NotEmpty(message = "数据流转规则名称不能为空")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "数据流转规则描述", example = "你猜")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "数据流转规则状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "数据流转规则状态不能为空")
|
||||
@InEnum(CommonStatusEnum.class)
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "数据源配置数组", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotEmpty(message = "数据源配置数组不能为空")
|
||||
private List<IotDataRuleDO.SourceConfig> sourceConfigs;
|
||||
|
||||
@Schema(description = "数据目的编号数组", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotEmpty(message = "数据目的编号数组不能为空")
|
||||
private List<Long> sinkIds;
|
||||
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.sink;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotAbstractDataSinkConfig;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 数据流转目的 Response VO")
|
||||
@Data
|
||||
public class IotDataSinkRespVO {
|
||||
|
||||
@Schema(description = "数据目的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "18564")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "数据目的名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "数据目的描述", example = "随便")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "数据目的状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "数据目的类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer type;
|
||||
|
||||
@Schema(description = "数据目的配置")
|
||||
private IotAbstractDataSinkConfig config;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.sink;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotAbstractDataSinkConfig;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotDataSinkTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 数据流转目的新增/修改 Request VO")
|
||||
@Data
|
||||
public class IotDataSinkSaveReqVO {
|
||||
|
||||
@Schema(description = "数据目的编号", example = "18564")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "数据目的名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
|
||||
@NotEmpty(message = "数据目的名称不能为空")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "数据目的描述", example = "随便")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "数据目的状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "数据目的状态不能为空")
|
||||
@InEnum(CommonStatusEnum.class)
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "数据目的类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "数据目的类型不能为空")
|
||||
@InEnum(IotDataSinkTypeEnum.class)
|
||||
private Integer type;
|
||||
|
||||
@Schema(description = "数据目的配置")
|
||||
@NotNull(message = "数据目的配置不能为空")
|
||||
private IotAbstractDataSinkConfig config;
|
||||
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 场景联动分页 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class IotSceneRulePageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "场景名称", example = "赵六")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "场景描述", example = "你猜")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "场景状态", example = "1")
|
||||
@InEnum(CommonStatusEnum.class)
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 场景联动 Response VO")
|
||||
@Data
|
||||
public class IotSceneRuleRespVO {
|
||||
|
||||
@Schema(description = "场景编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15865")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "场景名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "场景描述", example = "你猜")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "场景状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "触发器数组", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private List<IotSceneRuleDO.Trigger> triggers;
|
||||
|
||||
@Schema(description = "执行器数组", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private List<IotSceneRuleDO.Action> actions;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 场景联动新增/修改 Request VO")
|
||||
@Data
|
||||
public class IotSceneRuleSaveReqVO {
|
||||
|
||||
@Schema(description = "场景编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15865")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "场景名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
|
||||
@NotEmpty(message = "场景名称不能为空")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "场景描述", example = "你猜")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "场景状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
|
||||
@NotNull(message = "场景状态不能为空")
|
||||
@InEnum(CommonStatusEnum.class)
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "触发器数组", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotEmpty(message = "触发器数组不能为空")
|
||||
private List<IotSceneRuleDO.Trigger> triggers;
|
||||
|
||||
@Schema(description = "执行器数组", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotEmpty(message = "执行器数组不能为空")
|
||||
private List<IotSceneRuleDO.Action> actions;
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 场景联动更新状态 Request VO")
|
||||
@Data
|
||||
public class IotSceneRuleUpdateStatusReqVO {
|
||||
|
||||
@Schema(description = "场景联动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "场景联动编号不能为空")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
|
||||
@NotNull(message = "状态不能为空")
|
||||
@InEnum(value = CommonStatusEnum.class, message = "修改状态必须是 {value}")
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
### 请求 /iot/statistics/get-device-message-summary-by-date 接口(小时)
|
||||
GET {{baseUrl}}/iot/statistics/get-device-message-summary-by-date?interval=0×[0]=2025-06-13 00:00:00×[1]=2025-06-14 23:59:59
|
||||
Content-Type: application/json
|
||||
tenant-id: {{adminTenantId}}
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
### 请求 /iot/statistics/get-device-message-summary-by-date 接口(天)
|
||||
GET {{baseUrl}}/iot/statistics/get-device-message-summary-by-date?interval=1×[0]=2025-06-13 00:00:00×[1]=2025-06-14 23:59:59
|
||||
Content-Type: application/json
|
||||
tenant-id: {{adminTenantId}}
|
||||
Authorization: Bearer {{token}}
|
||||
@@ -1,27 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.statistics.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.DateIntervalEnum;
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import javax.validation.constraints.Size;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 设备消息数量统计 Response VO")
|
||||
@Data
|
||||
public class IotStatisticsDeviceMessageReqVO {
|
||||
|
||||
@Schema(description = "时间间隔类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@InEnum(value = DateIntervalEnum.class, message = "时间间隔类型,必须是 {value}")
|
||||
private Integer interval;
|
||||
|
||||
@Schema(description = "时间范围", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
@Size(min = 2, max = 2, message = "请选择时间范围")
|
||||
private LocalDateTime[] times;
|
||||
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.statistics.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 设备消息数量统计 Response VO")
|
||||
@Data
|
||||
public class IotStatisticsDeviceMessageSummaryByDateRespVO {
|
||||
|
||||
@Schema(description = "时间轴", requiredMode = Schema.RequiredMode.REQUIRED, example = "202401")
|
||||
private String time;
|
||||
|
||||
@Schema(description = "上行消息数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
|
||||
private Integer upstreamCount;
|
||||
|
||||
@Schema(description = "上行消息数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
|
||||
private Integer downstreamCount;
|
||||
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelEvent;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelProperty;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelService;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 产品物模型 TSL Response VO")
|
||||
@Data
|
||||
public class IotThingModelTSLRespVO {
|
||||
|
||||
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long productId;
|
||||
|
||||
@Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "temperature_sensor")
|
||||
private String productKey;
|
||||
|
||||
@Schema(description = "属性列表", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private List<ThingModelProperty> properties;
|
||||
|
||||
@Schema(description = "服务列表", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private List<ThingModelEvent> events;
|
||||
|
||||
@Schema(description = "事件列表", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private List<ThingModelService> services;
|
||||
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.dataobject.device;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* IoT 设备消息数据 DO
|
||||
*
|
||||
* 目前使用 TDengine 存储
|
||||
*
|
||||
* @author alwayssuper
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class IotDeviceMessageDO {
|
||||
|
||||
/**
|
||||
* 消息编号
|
||||
*/
|
||||
private String id;
|
||||
/**
|
||||
* 上报时间戳
|
||||
*/
|
||||
private Long reportTime;
|
||||
/**
|
||||
* 存储时间戳
|
||||
*/
|
||||
private Long ts;
|
||||
|
||||
/**
|
||||
* 设备编号
|
||||
*
|
||||
* 关联 {@link IotDeviceDO#getId()}
|
||||
*/
|
||||
private Long deviceId;
|
||||
/**
|
||||
* 租户编号
|
||||
*/
|
||||
private Long tenantId;
|
||||
|
||||
/**
|
||||
* 服务编号,该消息由哪个 server 发送
|
||||
*/
|
||||
private String serverId;
|
||||
|
||||
/**
|
||||
* 是否上行消息
|
||||
*
|
||||
* 由 {@link cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils#isUpstreamMessage(IotDeviceMessage)} 计算。
|
||||
* 计算并存储的目的:方便计算多少条上行、多少条下行
|
||||
*/
|
||||
private Boolean upstream;
|
||||
/**
|
||||
* 是否回复消息
|
||||
*
|
||||
* 由 {@link cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils#isReplyMessage(IotDeviceMessage)} 计算。
|
||||
* 计算并存储的目的:方便计算多少条请求、多少条回复
|
||||
*/
|
||||
private Boolean reply;
|
||||
/**
|
||||
* 标识符
|
||||
*
|
||||
* 例如说:{@link IotThingModelDO#getIdentifier()}
|
||||
* 目前,只有事件上报、服务调用才有!!!
|
||||
*/
|
||||
private String identifier;
|
||||
|
||||
// ========== codec(编解码)字段 ==========
|
||||
|
||||
/**
|
||||
* 请求编号
|
||||
*
|
||||
* 由设备生成,对应阿里云 IoT 的 Alink 协议中的 id、华为云 IoTDA 协议的 request_id
|
||||
*/
|
||||
private String requestId;
|
||||
/**
|
||||
* 请求方法
|
||||
*
|
||||
* 枚举 {@link IotDeviceMessageMethodEnum}
|
||||
* 例如说:thing.property.report 属性上报
|
||||
*/
|
||||
private String method;
|
||||
/**
|
||||
* 请求参数
|
||||
*
|
||||
* 例如说:属性上报的 properties、事件上报的 params
|
||||
*/
|
||||
private Object params;
|
||||
/**
|
||||
* 响应结果
|
||||
*/
|
||||
private Object data;
|
||||
/**
|
||||
* 响应错误码
|
||||
*/
|
||||
private Integer code;
|
||||
/**
|
||||
* 响应提示
|
||||
*/
|
||||
private String msg;
|
||||
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.dataobject.ota;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskDeviceScopeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskStatusEnum;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* IoT OTA 升级任务 DO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName(value = "iot_ota_task", autoResultMap = true)
|
||||
@KeySequence("iot_ota_task_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class IotOtaTaskDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 任务编号
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 任务名称
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 任务描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 固件编号
|
||||
* <p>
|
||||
* 关联 {@link IotOtaFirmwareDO#getId()}
|
||||
*/
|
||||
private Long firmwareId;
|
||||
|
||||
/**
|
||||
* 任务状态
|
||||
* <p>
|
||||
* 关联 {@link IotOtaTaskStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 设备升级范围
|
||||
* <p>
|
||||
* 关联 {@link IotOtaTaskDeviceScopeEnum}
|
||||
*/
|
||||
private Integer deviceScope;
|
||||
/**
|
||||
* 设备总数数量
|
||||
*/
|
||||
private Integer deviceTotalCount;
|
||||
/**
|
||||
* 设备成功数量
|
||||
*/
|
||||
private Integer deviceSuccessCount;
|
||||
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.dataobject.ota;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskRecordStatusEnum;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* IoT OTA 升级任务记录 DO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName(value = "iot_ota_task_record", autoResultMap = true)
|
||||
@KeySequence("iot_ota_task_record_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class IotOtaTaskRecordDO extends BaseDO {
|
||||
|
||||
public static final String DESCRIPTION_CANCEL_BY_TASK = "管理员手动取消升级任务(批量)";
|
||||
|
||||
public static final String DESCRIPTION_CANCEL_BY_RECORD = "管理员手动取消升级记录(单个)";
|
||||
|
||||
/**
|
||||
* 升级记录编号
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 固件编号
|
||||
*
|
||||
* 关联 {@link IotOtaFirmwareDO#getId()}
|
||||
*/
|
||||
private Long firmwareId;
|
||||
/**
|
||||
* 任务编号
|
||||
*
|
||||
* 关联 {@link IotOtaTaskDO#getId()}
|
||||
*/
|
||||
private Long taskId;
|
||||
|
||||
/**
|
||||
* 设备编号
|
||||
*
|
||||
* 关联 {@link IotDeviceDO#getId()}
|
||||
*/
|
||||
private Long deviceId;
|
||||
/**
|
||||
* 来源的固件编号
|
||||
*
|
||||
* 关联 {@link IotDeviceDO#getFirmwareId()}
|
||||
*/
|
||||
private Long fromFirmwareId;
|
||||
|
||||
/**
|
||||
* 升级状态
|
||||
*
|
||||
* 关联 {@link IotOtaTaskRecordStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
* 升级进度,百分比
|
||||
*/
|
||||
private Integer progress;
|
||||
/**
|
||||
* 升级进度描述
|
||||
*
|
||||
* 注意,只记录设备最后一次的升级进度描述
|
||||
* 如果想看历史记录,可以查看 {@link IotDeviceMessageDO} 设备日志
|
||||
*/
|
||||
private String description;
|
||||
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.dataobject.rule;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IoT 数据流转规则 DO
|
||||
*
|
||||
* 监听 {@link SourceConfig} 数据源,转发到 {@link IotDataSinkDO} 数据目的
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName(value = "iot_data_rule", autoResultMap = true)
|
||||
@KeySequence("iot_data_rule_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class IotDataRuleDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 数据流转规格编号
|
||||
*/
|
||||
private Long id;
|
||||
/**
|
||||
* 数据流转规格名称
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 数据流转规格描述
|
||||
*/
|
||||
private String description;
|
||||
/**
|
||||
* 数据流转规格状态
|
||||
*
|
||||
* 枚举 {@link CommonStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 数据源配置数组
|
||||
*/
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private List<SourceConfig> sourceConfigs;
|
||||
/**
|
||||
* 数据目的编号数组
|
||||
*
|
||||
* 关联 {@link IotDataSinkDO#getId()}
|
||||
*/
|
||||
@TableField(typeHandler = LongListTypeHandler.class)
|
||||
private List<Long> sinkIds;
|
||||
|
||||
// TODO @芋艿:未来考虑使用 groovy;支持数据处理;
|
||||
|
||||
/**
|
||||
* 数据源配置
|
||||
*/
|
||||
@Data
|
||||
public static class SourceConfig {
|
||||
|
||||
/**
|
||||
* 消息方法
|
||||
*
|
||||
* 枚举 {@link IotDeviceMessageMethodEnum} 中的 upstream 上行部分
|
||||
*/
|
||||
@NotEmpty(message = "消息方法不能为空")
|
||||
private String method;
|
||||
|
||||
/**
|
||||
* 产品编号
|
||||
*
|
||||
* 关联 {@link IotProductDO#getId()}
|
||||
*/
|
||||
private Long productId;
|
||||
/**
|
||||
* 设备编号
|
||||
*
|
||||
* 关联 {@link IotDeviceDO#getId()}
|
||||
* 特殊:如果为 {@link IotDeviceDO#DEVICE_ID_ALL} 时,则是全部设备
|
||||
*/
|
||||
@NotEmpty(message = "设备编号不能为空")
|
||||
private Long deviceId;
|
||||
|
||||
/**
|
||||
* 标识符
|
||||
*
|
||||
* 1. 物模型时,对应:{@link IotThingModelDO#getIdentifier()}
|
||||
*/
|
||||
private String identifier;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,245 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.dataobject.rule;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertConfigDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleActionTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IoT 场景联动规则 DO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName(value = "iot_scene_rule", autoResultMap = true)
|
||||
@KeySequence("iot_scene_rule_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class IotSceneRuleDO extends TenantBaseDO {
|
||||
|
||||
/**
|
||||
* 场景联动编号
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 场景联动名称
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 场景联动描述
|
||||
*/
|
||||
private String description;
|
||||
/**
|
||||
* 场景联动状态
|
||||
*
|
||||
* 枚举 {@link CommonStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 场景定义配置
|
||||
*/
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private List<Trigger> triggers;
|
||||
|
||||
/**
|
||||
* 场景动作配置
|
||||
*/
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private List<Action> actions;
|
||||
|
||||
/**
|
||||
* 场景定义配置
|
||||
*/
|
||||
@Data
|
||||
public static class Trigger {
|
||||
|
||||
// ========== 事件部分 ==========
|
||||
|
||||
/**
|
||||
* 场景事件类型
|
||||
*
|
||||
* 枚举 {@link IotSceneRuleTriggerTypeEnum}
|
||||
* 1. {@link IotSceneRuleTriggerTypeEnum#DEVICE_STATE_UPDATE} 时,operator 非空,并且 value 为在线状态
|
||||
* 2. {@link IotSceneRuleTriggerTypeEnum#DEVICE_PROPERTY_POST}
|
||||
* {@link IotSceneRuleTriggerTypeEnum#DEVICE_EVENT_POST} 时,identifier、operator 非空,并且 value 为属性值
|
||||
* 3. {@link IotSceneRuleTriggerTypeEnum#DEVICE_EVENT_POST}
|
||||
* {@link IotSceneRuleTriggerTypeEnum#DEVICE_SERVICE_INVOKE} 时,identifier 非空,但是 operator、value 为空
|
||||
* 4. {@link IotSceneRuleTriggerTypeEnum#TIMER} 时,conditions 非空,并且设备无关(无需 productId、deviceId 字段)
|
||||
*/
|
||||
private Integer type;
|
||||
|
||||
/**
|
||||
* 产品编号
|
||||
*
|
||||
* 关联 {@link IotProductDO#getId()}
|
||||
*/
|
||||
private Long productId;
|
||||
/**
|
||||
* 设备编号
|
||||
*
|
||||
* 关联 {@link IotDeviceDO#getId()}
|
||||
* 特殊:如果为 {@link IotDeviceDO#DEVICE_ID_ALL} 时,则是全部设备
|
||||
*/
|
||||
private Long deviceId;
|
||||
/**
|
||||
* 物模型标识符
|
||||
*
|
||||
* 对应:{@link IotThingModelDO#getIdentifier()}
|
||||
*/
|
||||
private String identifier;
|
||||
/**
|
||||
* 操作符
|
||||
*
|
||||
* 枚举 {@link IotSceneRuleConditionOperatorEnum}
|
||||
*/
|
||||
private String operator;
|
||||
/**
|
||||
* 参数(属性值、在线状态)
|
||||
* <p>
|
||||
* 如果有多个值,则使用 "," 分隔,类似 "1,2,3"。
|
||||
* 例如说,{@link IotSceneRuleConditionOperatorEnum#IN}、{@link IotSceneRuleConditionOperatorEnum#BETWEEN}
|
||||
*/
|
||||
private String value;
|
||||
|
||||
/**
|
||||
* CRON 表达式
|
||||
*/
|
||||
private String cronExpression;
|
||||
|
||||
// ========== 条件部分 ==========
|
||||
|
||||
/**
|
||||
* 触发条件分组(状态条件分组)的数组
|
||||
* <p>
|
||||
* 第一层 List:分组与分组之间,是“或”的关系
|
||||
* 第二层 List:条件与条件之间,是“且”的关系
|
||||
*/
|
||||
private List<List<TriggerCondition>> conditionGroups;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发条件(状态条件)
|
||||
*/
|
||||
@Data
|
||||
public static class TriggerCondition {
|
||||
|
||||
/**
|
||||
* 触发条件类型
|
||||
*
|
||||
* 枚举 {@link IotSceneRuleConditionTypeEnum}
|
||||
* 1. {@link IotSceneRuleConditionTypeEnum#DEVICE_STATE} 时,operator 非空,并且 value 为在线状态
|
||||
* 2. {@link IotSceneRuleConditionTypeEnum#DEVICE_PROPERTY} 时,identifier、operator 非空,并且 value 为属性值
|
||||
* 3. {@link IotSceneRuleConditionTypeEnum#CURRENT_TIME} 时,operator 非空(使用 DATE_TIME_ 和 TIME_ 部分),并且 value 非空
|
||||
*/
|
||||
private Integer type;
|
||||
|
||||
/**
|
||||
* 产品编号
|
||||
*
|
||||
* 关联 {@link IotProductDO#getId()}
|
||||
*/
|
||||
private Long productId;
|
||||
/**
|
||||
* 设备编号
|
||||
*
|
||||
* 关联 {@link IotDeviceDO#getId()}
|
||||
*/
|
||||
private Long deviceId;
|
||||
/**
|
||||
* 标识符(属性)
|
||||
*
|
||||
* 关联 {@link IotThingModelDO#getIdentifier()}
|
||||
*/
|
||||
private String identifier;
|
||||
/**
|
||||
* 操作符
|
||||
*
|
||||
* 枚举 {@link IotSceneRuleConditionOperatorEnum}
|
||||
*/
|
||||
private String operator;
|
||||
/**
|
||||
* 参数
|
||||
*
|
||||
* 如果有多个值,则使用 "," 分隔,类似 "1,2,3"。
|
||||
* 例如说,{@link IotSceneRuleConditionOperatorEnum#IN}、{@link IotSceneRuleConditionOperatorEnum#BETWEEN}
|
||||
*/
|
||||
private String param;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景动作配置
|
||||
*/
|
||||
@Data
|
||||
public static class Action {
|
||||
|
||||
/**
|
||||
* 执行类型
|
||||
*
|
||||
* 枚举 {@link IotSceneRuleActionTypeEnum}
|
||||
* 1. {@link IotSceneRuleActionTypeEnum#DEVICE_PROPERTY_SET} 时,params 非空
|
||||
* {@link IotSceneRuleActionTypeEnum#DEVICE_SERVICE_INVOKE} 时,params 非空
|
||||
* 2. {@link IotSceneRuleActionTypeEnum#ALERT_TRIGGER} 时,alertConfigId 为空,因为是 {@link IotAlertConfigDO} 里面关联它
|
||||
* 3. {@link IotSceneRuleActionTypeEnum#ALERT_RECOVER} 时,alertConfigId 非空
|
||||
*/
|
||||
private Integer type;
|
||||
|
||||
/**
|
||||
* 产品编号
|
||||
*
|
||||
* 关联 {@link IotProductDO#getId()}
|
||||
*/
|
||||
private Long productId;
|
||||
/**
|
||||
* 设备编号
|
||||
*
|
||||
* 关联 {@link IotDeviceDO#getId()}
|
||||
*/
|
||||
private Long deviceId;
|
||||
|
||||
/**
|
||||
* 标识符(服务)
|
||||
* <p>
|
||||
* 关联 {@link IotThingModelDO#getIdentifier()}
|
||||
*/
|
||||
private String identifier;
|
||||
|
||||
/**
|
||||
* 请求参数
|
||||
*
|
||||
* 一般来说,对应 {@link IotDeviceMessage#getParams()} 请求参数
|
||||
*/
|
||||
private String params;
|
||||
|
||||
/**
|
||||
* 告警配置编号
|
||||
*
|
||||
* 关联 {@link IotAlertConfigDO#getId()}
|
||||
*/
|
||||
private Long alertConfigId;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.dataobject.rule.config;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotDataSinkTypeEnum;
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* IoT IotDataBridgeConfig 抽象类
|
||||
*
|
||||
* 用于表示数据目的配置数据的通用类型,根据具体的 "type" 字段动态映射到对应的子类
|
||||
* 提供多态支持,适用于不同类型的数据结构序列化和反序列化场景。
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Data
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", visible = true)
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = IotDataSinkHttpConfig.class, name = "1"),
|
||||
@JsonSubTypes.Type(value = IotDataSinkMqttConfig.class, name = "10"),
|
||||
@JsonSubTypes.Type(value = IotDataSinkRedisConfig.class, name = "21"),
|
||||
@JsonSubTypes.Type(value = IotDataSinkRocketMQConfig.class, name = "30"),
|
||||
@JsonSubTypes.Type(value = IotDataSinkRabbitMQConfig.class, name = "31"),
|
||||
@JsonSubTypes.Type(value = IotDataSinkKafkaConfig.class, name = "32"),
|
||||
})
|
||||
public abstract class IotAbstractDataSinkConfig {
|
||||
|
||||
/**
|
||||
* 配置类型
|
||||
*
|
||||
* 枚举 {@link IotDataSinkTypeEnum#getType()}
|
||||
*/
|
||||
private String type;
|
||||
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.dataobject.rule.config;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotRedisDataStructureEnum;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* IoT Redis 配置 {@link IotAbstractDataSinkConfig} 实现类
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Data
|
||||
public class IotDataSinkRedisConfig extends IotAbstractDataSinkConfig {
|
||||
|
||||
/**
|
||||
* Redis 服务器地址
|
||||
*/
|
||||
private String host;
|
||||
/**
|
||||
* 端口
|
||||
*/
|
||||
private Integer port;
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
private String password;
|
||||
/**
|
||||
* 数据库索引
|
||||
*/
|
||||
private Integer database;
|
||||
|
||||
/**
|
||||
* Redis 数据结构类型
|
||||
* <p>
|
||||
* 枚举 {@link IotRedisDataStructureEnum}
|
||||
*/
|
||||
@InEnum(IotRedisDataStructureEnum.class)
|
||||
private Integer dataStructure;
|
||||
|
||||
/**
|
||||
* 主题/键名
|
||||
* <p>
|
||||
* 对于不同的数据结构:
|
||||
* - Stream: 流的键名
|
||||
* - Hash: Hash 的键名
|
||||
* - List: 列表的键名
|
||||
* - Set: 集合的键名
|
||||
* - ZSet: 有序集合的键名
|
||||
* - String: 字符串的键名
|
||||
*/
|
||||
private String topic;
|
||||
|
||||
/**
|
||||
* Hash 字段名(仅当 dataStructure 为 HASH 时使用)
|
||||
*/
|
||||
private String hashField;
|
||||
|
||||
/**
|
||||
* ZSet 分数字段(仅当 dataStructure 为 ZSET 时使用)
|
||||
* 指定消息中哪个字段作为分数,如果不指定则使用当前时间戳
|
||||
*/
|
||||
private String scoreField;
|
||||
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
/**
|
||||
* IoT 物模型数据类型为布尔型或枚举型的 DataSpec 定义
|
||||
*
|
||||
* 数据类型,取值为 bool 或 enum
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@JsonIgnoreProperties({"dataType"}) // 忽略子类中的 dataType 字段,从而避免重复
|
||||
public class ThingModelBoolOrEnumDataSpecs extends ThingModelDataSpecs {
|
||||
|
||||
@NotEmpty(message = "枚举项的名称不能为空")
|
||||
@Pattern(regexp = "^[\\u4e00-\\u9fa5a-zA-Z0-9][\\u4e00-\\u9fa5a-zA-Z0-9_-]{0,19}$",
|
||||
message = "枚举项的名称只能包含中文、大小写英文字母、数字、下划线和短划线,必须以中文、英文字母或数字开头,长度不超过 20 个字符")
|
||||
private String name;
|
||||
|
||||
@NotNull(message = "枚举值不能为空")
|
||||
private Integer value;
|
||||
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.mysql.alert;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config.IotAlertConfigPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertConfigDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IoT 告警配置 Mapper
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface IotAlertConfigMapper extends BaseMapperX<IotAlertConfigDO> {
|
||||
|
||||
default PageResult<IotAlertConfigDO> selectPage(IotAlertConfigPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<IotAlertConfigDO>()
|
||||
.likeIfPresent(IotAlertConfigDO::getName, reqVO.getName())
|
||||
.eqIfPresent(IotAlertConfigDO::getStatus, reqVO.getStatus())
|
||||
.betweenIfPresent(IotAlertConfigDO::getCreateTime, reqVO.getCreateTime())
|
||||
.orderByDesc(IotAlertConfigDO::getId));
|
||||
}
|
||||
|
||||
default List<IotAlertConfigDO> selectListByStatus(Integer status) {
|
||||
return selectList(IotAlertConfigDO::getStatus, status);
|
||||
}
|
||||
|
||||
default List<IotAlertConfigDO> selectListBySceneRuleIdAndStatus(Long sceneRuleId, Integer status) {
|
||||
return selectList(new LambdaQueryWrapperX<IotAlertConfigDO>()
|
||||
.eq(IotAlertConfigDO::getStatus, status)
|
||||
.apply(MyBatisUtils.findInSet("scene_rule_id", sceneRuleId)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.mysql.alert;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.recrod.IotAlertRecordPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertRecordDO;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IoT 告警记录 Mapper
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface IotAlertRecordMapper extends BaseMapperX<IotAlertRecordDO> {
|
||||
|
||||
default PageResult<IotAlertRecordDO> selectPage(IotAlertRecordPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<IotAlertRecordDO>()
|
||||
.eqIfPresent(IotAlertRecordDO::getConfigId, reqVO.getConfigId())
|
||||
.eqIfPresent(IotAlertRecordDO::getConfigLevel, reqVO.getLevel())
|
||||
.eqIfPresent(IotAlertRecordDO::getProductId, reqVO.getProductId())
|
||||
.eqIfPresent(IotAlertRecordDO::getDeviceId, reqVO.getDeviceId())
|
||||
.eqIfPresent(IotAlertRecordDO::getProcessStatus, reqVO.getProcessStatus())
|
||||
.betweenIfPresent(IotAlertRecordDO::getCreateTime, reqVO.getCreateTime())
|
||||
.orderByDesc(IotAlertRecordDO::getId));
|
||||
}
|
||||
|
||||
default List<IotAlertRecordDO> selectListBySceneRuleId(Long sceneRuleId, Long deviceId, Boolean processStatus) {
|
||||
return selectList(new LambdaQueryWrapperX<IotAlertRecordDO>()
|
||||
.eq(IotAlertRecordDO::getSceneRuleId, sceneRuleId)
|
||||
.eqIfPresent(IotAlertRecordDO::getDeviceId, deviceId)
|
||||
.eqIfPresent(IotAlertRecordDO::getProcessStatus, processStatus)
|
||||
.orderByDesc(IotAlertRecordDO::getId));
|
||||
}
|
||||
|
||||
default int updateList(Collection<Long> ids, IotAlertRecordDO updateObj) {
|
||||
return update(updateObj, new LambdaUpdateWrapper<IotAlertRecordDO>()
|
||||
.in(IotAlertRecordDO::getId, ids));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.mysql.ota;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.IotOtaTaskPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskDO;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface IotOtaTaskMapper extends BaseMapperX<IotOtaTaskDO> {
|
||||
|
||||
default IotOtaTaskDO selectByFirmwareIdAndName(Long firmwareId, String name) {
|
||||
return selectOne(IotOtaTaskDO::getFirmwareId, firmwareId,
|
||||
IotOtaTaskDO::getName, name);
|
||||
}
|
||||
|
||||
default PageResult<IotOtaTaskDO> selectPage(IotOtaTaskPageReqVO pageReqVO) {
|
||||
return selectPage(pageReqVO, new LambdaQueryWrapperX<IotOtaTaskDO>()
|
||||
.eqIfPresent(IotOtaTaskDO::getFirmwareId, pageReqVO.getFirmwareId())
|
||||
.likeIfPresent(IotOtaTaskDO::getName, pageReqVO.getName())
|
||||
.orderByDesc(IotOtaTaskDO::getId));
|
||||
}
|
||||
|
||||
default int updateByIdAndStatus(Long id, Integer whereStatus, IotOtaTaskDO updateObj) {
|
||||
return update(updateObj, new LambdaUpdateWrapper<IotOtaTaskDO>()
|
||||
.eq(IotOtaTaskDO::getId, id)
|
||||
.eq(IotOtaTaskDO::getStatus, whereStatus));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.mysql.ota;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record.IotOtaTaskRecordPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Mapper
|
||||
public interface IotOtaTaskRecordMapper extends BaseMapperX<IotOtaTaskRecordDO> {
|
||||
|
||||
default List<IotOtaTaskRecordDO> selectListByFirmwareIdAndTaskId(Long firmwareId, Long taskId) {
|
||||
return selectList(new LambdaQueryWrapperX<IotOtaTaskRecordDO>()
|
||||
.eqIfPresent(IotOtaTaskRecordDO::getFirmwareId, firmwareId)
|
||||
.eqIfPresent(IotOtaTaskRecordDO::getTaskId, taskId)
|
||||
.select(IotOtaTaskRecordDO::getDeviceId, IotOtaTaskRecordDO::getStatus));
|
||||
}
|
||||
|
||||
default PageResult<IotOtaTaskRecordDO> selectPage(IotOtaTaskRecordPageReqVO pageReqVO) {
|
||||
return selectPage(pageReqVO, new LambdaQueryWrapperX<IotOtaTaskRecordDO>()
|
||||
.eqIfPresent(IotOtaTaskRecordDO::getTaskId, pageReqVO.getTaskId())
|
||||
.eqIfPresent(IotOtaTaskRecordDO::getStatus, pageReqVO.getStatus()));
|
||||
}
|
||||
|
||||
default List<IotOtaTaskRecordDO> selectListByTaskIdAndStatus(Long taskId, Collection<Integer> statuses) {
|
||||
return selectList(new LambdaQueryWrapperX<IotOtaTaskRecordDO>()
|
||||
.eq(IotOtaTaskRecordDO::getTaskId, taskId)
|
||||
.in(IotOtaTaskRecordDO::getStatus, statuses));
|
||||
}
|
||||
|
||||
default Long selectCountByTaskIdAndStatus(Long taskId, Collection<Integer> statuses) {
|
||||
return selectCount(new LambdaQueryWrapperX<IotOtaTaskRecordDO>()
|
||||
.eq(IotOtaTaskRecordDO::getTaskId, taskId)
|
||||
.in(IotOtaTaskRecordDO::getStatus, statuses));
|
||||
}
|
||||
|
||||
default int updateByIdAndStatus(Long id, Integer status,
|
||||
IotOtaTaskRecordDO updateObj) {
|
||||
return update(updateObj, new LambdaUpdateWrapper<IotOtaTaskRecordDO>()
|
||||
.eq(IotOtaTaskRecordDO::getId, id)
|
||||
.eq(IotOtaTaskRecordDO::getStatus, status));
|
||||
}
|
||||
|
||||
default int updateByIdAndStatus(Long id, Collection<Integer> whereStatuses,
|
||||
IotOtaTaskRecordDO updateObj) {
|
||||
return update(updateObj, new LambdaUpdateWrapper<IotOtaTaskRecordDO>()
|
||||
.eq(IotOtaTaskRecordDO::getId, id)
|
||||
.in(IotOtaTaskRecordDO::getStatus, whereStatuses));
|
||||
}
|
||||
|
||||
default void updateListByIdAndStatus(Collection<Long> ids, Collection<Integer> whereStatuses,
|
||||
IotOtaTaskRecordDO updateObj) {
|
||||
update(updateObj, new LambdaUpdateWrapper<IotOtaTaskRecordDO>()
|
||||
.in(IotOtaTaskRecordDO::getId, ids)
|
||||
.in(IotOtaTaskRecordDO::getStatus, whereStatuses));
|
||||
}
|
||||
|
||||
default List<IotOtaTaskRecordDO> selectListByDeviceIdAndStatus(Set<Long> deviceIds, Set<Integer> statuses) {
|
||||
return selectList(new LambdaQueryWrapperX<IotOtaTaskRecordDO>()
|
||||
.inIfPresent(IotOtaTaskRecordDO::getDeviceId, deviceIds)
|
||||
.inIfPresent(IotOtaTaskRecordDO::getStatus, statuses));
|
||||
}
|
||||
|
||||
default List<IotOtaTaskRecordDO> selectListByDeviceIdAndStatus(Long deviceId, Set<Integer> statuses) {
|
||||
return selectList(new LambdaQueryWrapperX<IotOtaTaskRecordDO>()
|
||||
.eqIfPresent(IotOtaTaskRecordDO::getDeviceId, deviceId)
|
||||
.inIfPresent(IotOtaTaskRecordDO::getStatus, statuses));
|
||||
}
|
||||
|
||||
default List<IotOtaTaskRecordDO> selectListByStatus(Integer status) {
|
||||
return selectList(IotOtaTaskRecordDO::getStatus, status);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.mysql.rule;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule.IotDataRulePageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataRuleDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IoT 数据流转规则 Mapper
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface IotDataRuleMapper extends BaseMapperX<IotDataRuleDO> {
|
||||
|
||||
default PageResult<IotDataRuleDO> selectPage(IotDataRulePageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<IotDataRuleDO>()
|
||||
.likeIfPresent(IotDataRuleDO::getName, reqVO.getName())
|
||||
.eqIfPresent(IotDataRuleDO::getStatus, reqVO.getStatus())
|
||||
.betweenIfPresent(IotDataRuleDO::getCreateTime, reqVO.getCreateTime())
|
||||
.orderByDesc(IotDataRuleDO::getId));
|
||||
}
|
||||
|
||||
default List<IotDataRuleDO> selectListBySinkId(Long sinkId) {
|
||||
return selectList(new LambdaQueryWrapperX<IotDataRuleDO>()
|
||||
.apply(MyBatisUtils.findInSet("sink_ids", sinkId)));
|
||||
}
|
||||
|
||||
default List<IotDataRuleDO> selectListByStatus(Integer status) {
|
||||
return selectList(IotDataRuleDO::getStatus, status);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.mysql.rule;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.sink.IotDataSinkPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataSinkDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IoT 数据流转目的 Mapper
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Mapper
|
||||
public interface IotDataSinkMapper extends BaseMapperX<IotDataSinkDO> {
|
||||
|
||||
default PageResult<IotDataSinkDO> selectPage(IotDataSinkPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<IotDataSinkDO>()
|
||||
.likeIfPresent(IotDataSinkDO::getName, reqVO.getName())
|
||||
.eqIfPresent(IotDataSinkDO::getStatus, reqVO.getStatus())
|
||||
.betweenIfPresent(IotDataSinkDO::getCreateTime, reqVO.getCreateTime())
|
||||
.orderByDesc(IotDataSinkDO::getId));
|
||||
}
|
||||
|
||||
default List<IotDataSinkDO> selectListByStatus(Integer status) {
|
||||
return selectList(IotDataSinkDO::getStatus, status);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.mysql.rule;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRulePageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IoT 场景联动 Mapper
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Mapper
|
||||
public interface IotSceneRuleMapper extends BaseMapperX<IotSceneRuleDO> {
|
||||
|
||||
default PageResult<IotSceneRuleDO> selectPage(IotSceneRulePageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<IotSceneRuleDO>()
|
||||
.likeIfPresent(IotSceneRuleDO::getName, reqVO.getName())
|
||||
.likeIfPresent(IotSceneRuleDO::getDescription, reqVO.getDescription())
|
||||
.eqIfPresent(IotSceneRuleDO::getStatus, reqVO.getStatus())
|
||||
.betweenIfPresent(IotSceneRuleDO::getCreateTime, reqVO.getCreateTime())
|
||||
.orderByDesc(IotSceneRuleDO::getId));
|
||||
}
|
||||
|
||||
default List<IotSceneRuleDO> selectListByStatus(Integer status) {
|
||||
return selectList(IotSceneRuleDO::getStatus, status);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.redis.device;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 设备关联的网关 serverId 的 Redis DAO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Repository
|
||||
public class DeviceServerIdRedisDAO {
|
||||
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
public void update(Long deviceId, String serverId) {
|
||||
stringRedisTemplate.opsForHash().put(RedisKeyConstants.DEVICE_SERVER_ID,
|
||||
String.valueOf(deviceId), serverId);
|
||||
}
|
||||
|
||||
public String get(Long deviceId) {
|
||||
Object value = stringRedisTemplate.opsForHash().get(RedisKeyConstants.DEVICE_SERVER_ID,
|
||||
String.valueOf(deviceId));
|
||||
return value != null ? (String) value : null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.tdengine;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceMessagePageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO;
|
||||
import cn.iocoder.yudao.module.iot.framework.tdengine.core.annotation.TDengineDS;
|
||||
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 设备消息 {@link IotDeviceMessageDO} Mapper 接口
|
||||
*/
|
||||
@Mapper
|
||||
@TDengineDS
|
||||
@InterceptorIgnore(tenantLine = "true") // 避免 SQL 解析,因为 JSqlParser 对 TDengine 的 SQL 解析会报错
|
||||
public interface IotDeviceMessageMapper {
|
||||
|
||||
/**
|
||||
* 创建设备消息超级表
|
||||
*/
|
||||
void createSTable();
|
||||
|
||||
/**
|
||||
* 查询设备消息表是否存在
|
||||
*
|
||||
* @return 存在则返回表名;不存在则返回 null
|
||||
*/
|
||||
String showSTable();
|
||||
|
||||
/**
|
||||
* 插入设备消息数据
|
||||
*
|
||||
* 如果子表不存在,会自动创建子表
|
||||
*
|
||||
* @param message 设备消息数据
|
||||
*/
|
||||
void insert(IotDeviceMessageDO message);
|
||||
|
||||
/**
|
||||
* 获得设备消息分页
|
||||
*
|
||||
* @param reqVO 分页查询条件
|
||||
* @return 设备消息列表
|
||||
*/
|
||||
IPage<IotDeviceMessageDO> selectPage(IPage<IotDeviceMessageDO> page,
|
||||
@Param("reqVO") IotDeviceMessagePageReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 统计设备消息数量
|
||||
*
|
||||
* @param createTime 创建时间,如果为空,则统计所有消息数量
|
||||
* @return 消息数量
|
||||
*/
|
||||
Long selectCountByCreateTime(@Param("createTime") Long createTime);
|
||||
|
||||
/**
|
||||
* 按照 requestIds 批量查询消息
|
||||
*
|
||||
* @param deviceId 设备编号
|
||||
* @param requestIds 请求编号集合
|
||||
* @param reply 是否回复消息
|
||||
* @return 消息列表
|
||||
*/
|
||||
List<IotDeviceMessageDO> selectListByRequestIdsAndReply(@Param("deviceId") Long deviceId,
|
||||
@Param("requestIds") Collection<String> requestIds,
|
||||
@Param("reply") Boolean reply);
|
||||
|
||||
/**
|
||||
* 按照时间范围(小时),统计设备的消息数量
|
||||
*/
|
||||
List<Map<String, Object>> selectDeviceMessageCountGroupByDate(@Param("startTime") Long startTime,
|
||||
@Param("endTime") Long endTime);
|
||||
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.enums;
|
||||
|
||||
/**
|
||||
* IoT 字典类型的枚举类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class DictTypeConstants {
|
||||
|
||||
public static final String NET_TYPE = "iot_net_type";
|
||||
public static final String LOCATION_TYPE = "iot_location_type";
|
||||
public static final String CODEC_TYPE = "iot_codec_type";
|
||||
|
||||
public static final String PRODUCT_STATUS = "iot_product_status";
|
||||
public static final String PRODUCT_DEVICE_TYPE = "iot_product_device_type";
|
||||
|
||||
public static final String DEVICE_STATE = "iot_device_state";
|
||||
|
||||
public static final String ALERT_LEVEL = "iot_alert_level";
|
||||
|
||||
public static final String OTA_TASK_DEVICE_SCOPE = "iot_ota_task_device_scope";
|
||||
public static final String OTA_TASK_STATUS = "iot_ota_task_status";
|
||||
public static final String OTA_TASK_RECORD_STATUS = "iot_ota_task_record_status";
|
||||
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.enums;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||
|
||||
/**
|
||||
* iot 错误码枚举类
|
||||
* <p>
|
||||
* iot 系统,使用 1-050-000-000 段
|
||||
*/
|
||||
public interface ErrorCodeConstants {
|
||||
|
||||
// ========== 产品相关 1-050-001-000 ============
|
||||
ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_050_001_000, "产品不存在");
|
||||
ErrorCode PRODUCT_KEY_EXISTS = new ErrorCode(1_050_001_001, "产品标识已经存在");
|
||||
ErrorCode PRODUCT_STATUS_NOT_DELETE = new ErrorCode(1_050_001_002, "产品状是发布状态,不允许删除");
|
||||
ErrorCode PRODUCT_STATUS_NOT_ALLOW_THING_MODEL = new ErrorCode(1_050_001_003, "产品状是发布状态,不允许操作物模型");
|
||||
ErrorCode PRODUCT_DELETE_FAIL_HAS_DEVICE = new ErrorCode(1_050_001_004, "产品下存在设备,不允许删除");
|
||||
|
||||
// ========== 产品物模型 1-050-002-000 ============
|
||||
ErrorCode THING_MODEL_NOT_EXISTS = new ErrorCode(1_050_002_000, "产品物模型不存在");
|
||||
ErrorCode THING_MODEL_EXISTS_BY_PRODUCT_KEY = new ErrorCode(1_050_002_001, "ProductKey 对应的产品物模型已存在");
|
||||
ErrorCode THING_MODEL_IDENTIFIER_EXISTS = new ErrorCode(1_050_002_002, "存在重复的功能标识符。");
|
||||
ErrorCode THING_MODEL_NAME_EXISTS = new ErrorCode(1_050_002_003, "存在重复的功能名称。");
|
||||
ErrorCode THING_MODEL_IDENTIFIER_INVALID = new ErrorCode(1_050_002_003, "产品物模型标识无效");
|
||||
|
||||
// ========== 设备 1-050-003-000 ============
|
||||
ErrorCode DEVICE_NOT_EXISTS = new ErrorCode(1_050_003_000, "设备不存在");
|
||||
ErrorCode DEVICE_NAME_EXISTS = new ErrorCode(1_050_003_001, "设备名称在同一产品下必须唯一");
|
||||
ErrorCode DEVICE_HAS_CHILDREN = new ErrorCode(1_050_003_002, "有子设备,不允许删除");
|
||||
ErrorCode DEVICE_KEY_EXISTS = new ErrorCode(1_050_003_003, "设备标识已经存在");
|
||||
ErrorCode DEVICE_GATEWAY_NOT_EXISTS = new ErrorCode(1_050_003_004, "网关设备不存在");
|
||||
ErrorCode DEVICE_NOT_GATEWAY = new ErrorCode(1_050_003_005, "设备不是网关设备");
|
||||
ErrorCode DEVICE_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_050_003_006, "导入设备数据不能为空!");
|
||||
ErrorCode DEVICE_DOWNSTREAM_FAILED_SERVER_ID_NULL = new ErrorCode(1_050_003_007, "下行设备消息失败,原因:设备未连接网关");
|
||||
ErrorCode DEVICE_SERIAL_NUMBER_EXISTS = new ErrorCode(1_050_003_008, "设备序列号已存在,序列号必须全局唯一");
|
||||
|
||||
// ========== 产品分类 1-050-004-000 ==========
|
||||
ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_050_004_000, "产品分类不存在");
|
||||
|
||||
// ========== 设备分组 1-050-005-000 ==========
|
||||
ErrorCode DEVICE_GROUP_NOT_EXISTS = new ErrorCode(1_050_005_000, "设备分组不存在");
|
||||
ErrorCode DEVICE_GROUP_DELETE_FAIL_DEVICE_EXISTS = new ErrorCode(1_050_005_001, "设备分组下存在设备,不允许删除");
|
||||
|
||||
// ========== OTA 固件相关 1-050-008-000 ==========
|
||||
|
||||
ErrorCode OTA_FIRMWARE_NOT_EXISTS = new ErrorCode(1_050_008_000, "固件信息不存在");
|
||||
ErrorCode OTA_FIRMWARE_PRODUCT_VERSION_DUPLICATE = new ErrorCode(1_050_008_001, "产品版本号重复");
|
||||
|
||||
// ========== OTA 升级任务相关 1-050-008-100 ==========
|
||||
|
||||
ErrorCode OTA_TASK_NOT_EXISTS = new ErrorCode(1_050_008_100, "升级任务不存在");
|
||||
ErrorCode OTA_TASK_CREATE_FAIL_NAME_DUPLICATE = new ErrorCode(1_050_008_101, "创建 OTA 任务失败,原因:任务名称重复");
|
||||
ErrorCode OTA_TASK_CREATE_FAIL_DEVICE_FIRMWARE_EXISTS = new ErrorCode(1_050_008_102,
|
||||
"创建 OTA 任务失败,原因:设备({})已经是该固件版本");
|
||||
ErrorCode OTA_TASK_CREATE_FAIL_DEVICE_OTA_IN_PROCESS = new ErrorCode(1_050_008_102,
|
||||
"创建 OTA 任务失败,原因:设备({})已经在升级中...");
|
||||
ErrorCode OTA_TASK_CREATE_FAIL_DEVICE_EMPTY = new ErrorCode(1_050_008_103, "创建 OTA 任务失败,原因:没有可升级的设备");
|
||||
ErrorCode OTA_TASK_CANCEL_FAIL_STATUS_END = new ErrorCode(1_050_008_104, "取消 OTA 任务失败,原因:任务状态不是进行中");
|
||||
|
||||
// ========== OTA 升级任务记录相关 1-050-008-200 ==========
|
||||
|
||||
ErrorCode OTA_TASK_RECORD_NOT_EXISTS = new ErrorCode(1_050_008_200, "升级记录不存在");
|
||||
ErrorCode OTA_TASK_RECORD_CANCEL_FAIL_STATUS_ERROR = new ErrorCode(1_050_008_201, "取消 OTA 升级记录失败,原因:记录状态不是进行中");
|
||||
ErrorCode OTA_TASK_RECORD_UPDATE_PROGRESS_FAIL_NO_EXISTS = new ErrorCode(1_050_008_202, "更新 OTA 升级记录进度失败,原因:该设备没有进行中的升级记录");
|
||||
|
||||
// ========== IoT 数据流转规则 1-050-010-000 ==========
|
||||
ErrorCode DATA_RULE_NOT_EXISTS = new ErrorCode(1_050_010_000, "数据流转规则不存在");
|
||||
|
||||
// ========== IoT 数据流转目的 1-050-011-000 ==========
|
||||
ErrorCode DATA_SINK_NOT_EXISTS = new ErrorCode(1_050_011_000, "数据桥梁不存在");
|
||||
ErrorCode DATA_SINK_DELETE_FAIL_USED_BY_RULE = new ErrorCode(1_050_011_001, "数据流转目的正在被数据流转规则使用,无法删除");
|
||||
|
||||
// ========== IoT 场景联动 1-050-012-000 ==========
|
||||
ErrorCode RULE_SCENE_NOT_EXISTS = new ErrorCode(1_050_012_000, "场景联动不存在");
|
||||
|
||||
// ========== IoT 告警配置 1-050-013-000 ==========
|
||||
ErrorCode ALERT_CONFIG_NOT_EXISTS = new ErrorCode(1_050_013_000, "IoT 告警配置不存在");
|
||||
|
||||
// ========== IoT 告警记录 1-050-014-000 ==========
|
||||
ErrorCode ALERT_RECORD_NOT_EXISTS = new ErrorCode(1_050_014_000, "IoT 告警记录不存在");
|
||||
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.enums.ota;
|
||||
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* IoT OTA 升级任务记录的状态枚举
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public enum IotOtaTaskRecordStatusEnum implements ArrayValuable<Integer> {
|
||||
|
||||
PENDING(0), // 待推送
|
||||
PUSHED(10), // 已推送
|
||||
UPGRADING(20), // 升级中
|
||||
SUCCESS(30), // 升级成功
|
||||
FAILURE(40), // 升级失败
|
||||
CANCELED(50),; // 升级取消
|
||||
|
||||
public static final Integer[] ARRAYS = Arrays.stream(values())
|
||||
.map(IotOtaTaskRecordStatusEnum::getStatus).toArray(Integer[]::new);
|
||||
|
||||
public static final Set<Integer> IN_PROCESS_STATUSES = SetUtils.asSet(
|
||||
PENDING.getStatus(),
|
||||
PUSHED.getStatus(),
|
||||
UPGRADING.getStatus());
|
||||
|
||||
public static final List<Integer> PRIORITY_STATUSES = Arrays.asList(
|
||||
SUCCESS.getStatus(),
|
||||
PENDING.getStatus(), PUSHED.getStatus(), UPGRADING.getStatus(),
|
||||
FAILURE.getStatus(), CANCELED.getStatus());
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private final Integer status;
|
||||
|
||||
@Override
|
||||
public Integer[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
public static IotOtaTaskRecordStatusEnum of(Integer status) {
|
||||
return ArrayUtil.firstMatch(o -> o.getStatus().equals(status), values());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.enums.rule;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* IoT 数据目的的类型枚举
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public enum IotDataSinkTypeEnum implements ArrayValuable<Integer> {
|
||||
|
||||
HTTP(1, "HTTP"),
|
||||
TCP(2, "TCP"), // TODO @puhui999:待实现;
|
||||
WEBSOCKET(3, "WebSocket"), // TODO @puhui999:待实现;
|
||||
|
||||
MQTT(10, "MQTT"), // TODO 待实现;
|
||||
|
||||
DATABASE(20, "Database"), // TODO @puhui999:待实现;可以简单点,对应的表名是什么,字段先固定了。
|
||||
REDIS(21, "Redis"),
|
||||
|
||||
ROCKETMQ(30, "RocketMQ"),
|
||||
RABBITMQ(31, "RabbitMQ"),
|
||||
KAFKA(32, "Kafka");
|
||||
|
||||
private final Integer type;
|
||||
|
||||
private final String name;
|
||||
|
||||
public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotDataSinkTypeEnum::getType).toArray(Integer[]::new);
|
||||
|
||||
@Override
|
||||
public Integer[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.enums.rule;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* IoT 规则场景的触发类型枚举
|
||||
*
|
||||
* 设备触发,定时触发
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public enum IotSceneRuleActionTypeEnum implements ArrayValuable<Integer> {
|
||||
|
||||
/**
|
||||
* 设备属性设置
|
||||
*
|
||||
* 对应 {@link IotDeviceMessageMethodEnum#PROPERTY_SET}
|
||||
*/
|
||||
DEVICE_PROPERTY_SET(1),
|
||||
/**
|
||||
* 设备服务调用
|
||||
*
|
||||
* 对应 {@link IotDeviceMessageMethodEnum#SERVICE_INVOKE}
|
||||
*/
|
||||
DEVICE_SERVICE_INVOKE(2),
|
||||
|
||||
/**
|
||||
* 告警触发
|
||||
*/
|
||||
ALERT_TRIGGER(100),
|
||||
/**
|
||||
* 告警恢复
|
||||
*/
|
||||
ALERT_RECOVER(101),
|
||||
|
||||
;
|
||||
|
||||
private final Integer type;
|
||||
|
||||
public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotSceneRuleActionTypeEnum::getType).toArray(Integer[]::new);
|
||||
|
||||
@Override
|
||||
public Integer[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.enums.rule;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* IoT 条件类型枚举
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public enum IotSceneRuleConditionTypeEnum implements ArrayValuable<Integer> {
|
||||
|
||||
DEVICE_STATE(1, "设备状态"),
|
||||
DEVICE_PROPERTY(2, "设备属性"),
|
||||
|
||||
CURRENT_TIME(100, "当前时间"),
|
||||
|
||||
;
|
||||
|
||||
private final Integer type;
|
||||
private final String name;
|
||||
|
||||
public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotSceneRuleConditionTypeEnum::getType).toArray(Integer[]::new);
|
||||
|
||||
@Override
|
||||
public Integer[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
public static IotSceneRuleConditionTypeEnum typeOf(Integer type) {
|
||||
return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.enums.rule;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* IoT 场景流转的触发类型枚举
|
||||
*
|
||||
* 为什么不直接使用 IotDeviceMessageMethodEnum 呢?
|
||||
* 原因是,物模型属性上报,存在批量上报的情况,不只对应一个 method!!!
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public enum IotSceneRuleTriggerTypeEnum implements ArrayValuable<Integer> {
|
||||
|
||||
// TODO @芋艿:后续“对应”部分,要 @下,等包结构梳理完;
|
||||
/**
|
||||
* 设备上下线变更
|
||||
*
|
||||
* 对应 IotDeviceMessageMethodEnum.STATE_UPDATE
|
||||
*/
|
||||
DEVICE_STATE_UPDATE(1),
|
||||
/**
|
||||
* 物模型属性上报
|
||||
*
|
||||
* 对应 IotDeviceMessageMethodEnum.DEVICE_PROPERTY_POST
|
||||
*/
|
||||
DEVICE_PROPERTY_POST(2),
|
||||
/**
|
||||
* 设备事件上报
|
||||
*
|
||||
* 对应 IotDeviceMessageMethodEnum.DEVICE_EVENT_POST
|
||||
*/
|
||||
DEVICE_EVENT_POST(3),
|
||||
/**
|
||||
* 设备服务调用
|
||||
*
|
||||
* 对应 IotDeviceMessageMethodEnum.DEVICE_SERVICE_INVOKE
|
||||
*/
|
||||
DEVICE_SERVICE_INVOKE(4),
|
||||
|
||||
/**
|
||||
* 定时触发
|
||||
*/
|
||||
TIMER(100)
|
||||
|
||||
;
|
||||
|
||||
private final Integer type;
|
||||
|
||||
public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotSceneRuleTriggerTypeEnum::getType).toArray(Integer[]::new);
|
||||
|
||||
@Override
|
||||
public Integer[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
public static IotSceneRuleTriggerTypeEnum typeOf(Integer type) {
|
||||
return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.framework.iot.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* 芋道 IoT 全局配置类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Component
|
||||
@Data
|
||||
public class YudaoIotProperties {
|
||||
|
||||
/**
|
||||
* 设备连接超时时间
|
||||
*/
|
||||
private Duration keepAliveTime = Duration.ofMinutes(10);
|
||||
/**
|
||||
* 设备连接超时时间的因子
|
||||
*
|
||||
* 因为设备可能会有网络抖动,所以需要乘以一个因子,避免误判
|
||||
*/
|
||||
private double keepAliveFactor = 1.5D;
|
||||
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
/**
|
||||
* iot 模块的【全局】拓展封装
|
||||
*/
|
||||
package cn.iocoder.yudao.module.iot.framework.iot;
|
||||
@@ -1,86 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.job.ota;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
|
||||
import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskRecordStatusEnum;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
|
||||
import cn.iocoder.yudao.module.iot.service.ota.IotOtaFirmwareService;
|
||||
import cn.iocoder.yudao.module.iot.service.ota.IotOtaTaskRecordService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* IoT OTA 升级推送 Job:查询待推送的 OTA 升级记录,并推送给设备
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class IotOtaUpgradeJob implements JobHandler {
|
||||
|
||||
@Resource
|
||||
private IotOtaTaskRecordService otaTaskRecordService;
|
||||
@Resource
|
||||
private IotOtaFirmwareService otaFirmwareService;
|
||||
@Resource
|
||||
private IotDeviceService deviceService;
|
||||
|
||||
@Override
|
||||
@TenantJob
|
||||
public String execute(String param) throws Exception {
|
||||
// 1. 查询待推送的 OTA 升级记录
|
||||
List<IotOtaTaskRecordDO> records = otaTaskRecordService.getOtaRecordListByStatus(
|
||||
IotOtaTaskRecordStatusEnum.PENDING.getStatus());
|
||||
if (CollUtil.isEmpty(records)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO 芋艿:可以优化成批量获取 原因是:1. N+1 问题;2. offline 的设备无需查询
|
||||
// 2. 遍历推送记录
|
||||
int successCount = 0;
|
||||
int failureCount = 0;
|
||||
Map<Long, IotOtaFirmwareDO> otaFirmwares = new HashMap<>();
|
||||
for (IotOtaTaskRecordDO record : records) {
|
||||
try {
|
||||
// 2.1 设备如果不在线,直接跳过
|
||||
IotDeviceDO device = deviceService.getDeviceFromCache(record.getDeviceId());
|
||||
// TODO 芋艿:【优化】当前逻辑跳过了离线的设备,但未充分利用 MQTT 的离线消息能力。
|
||||
// 1. MQTT 协议本身支持持久化会话(Clean Session=false)和 QoS > 0 的消息,允许 broker 为离线设备缓存消息。
|
||||
// 2. 对于 OTA 升级这类非实时性强的任务,即使设备当前离线,也应该可以推送升级指令。设备在下次上线时即可收到。
|
||||
// 3. 后续可以考虑:增加一个“允许离线推送”的选项。如果开启,即使设备状态为 OFFLINE,也应尝试推送消息,依赖 MQTT Broker 的能力进行离线缓存。
|
||||
if (device == null || IotDeviceStateEnum.isNotOnline(device.getState())) {
|
||||
continue;
|
||||
}
|
||||
// 2.2 获取 OTA 固件信息
|
||||
IotOtaFirmwareDO fireware = otaFirmwares.get(record.getFirmwareId());
|
||||
if (fireware == null) {
|
||||
fireware = otaFirmwareService.getOtaFirmware(record.getFirmwareId());
|
||||
otaFirmwares.put(record.getFirmwareId(), fireware);
|
||||
}
|
||||
// 2.3 推送 OTA 升级任务
|
||||
boolean result = otaTaskRecordService.pushOtaTaskRecord(record, fireware, device);
|
||||
if (result) {
|
||||
successCount++;
|
||||
} else {
|
||||
failureCount++;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
failureCount++;
|
||||
log.error("[execute][推送 OTA 升级任务({})发生异常]", record.getId(), e);
|
||||
}
|
||||
}
|
||||
return StrUtil.format("升级任务推送成功:{} 条,送失败:{} 条", successCount, failureCount);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.job.rule;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.scene.IotSceneRuleService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.springframework.scheduling.quartz.QuartzJobBean;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* IoT 规则场景 Job,用于执行 {@link IotSceneRuleTriggerTypeEnum#TIMER} 类型的规则场景
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public class IotSceneRuleJob extends QuartzJobBean {
|
||||
|
||||
/**
|
||||
* JobData Key - 规则场景编号
|
||||
*/
|
||||
public static final String JOB_DATA_KEY_RULE_SCENE_ID = "sceneRuleId";
|
||||
|
||||
@Resource
|
||||
private IotSceneRuleService sceneRuleService;
|
||||
|
||||
@Override
|
||||
protected void executeInternal(JobExecutionContext context) {
|
||||
// 获得规则场景编号
|
||||
Long sceneRuleId = context.getMergedJobDataMap().getLong(JOB_DATA_KEY_RULE_SCENE_ID);
|
||||
|
||||
// 执行规则场景
|
||||
sceneRuleService.executeSceneRuleByTimer(sceneRuleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 JobData Map
|
||||
*
|
||||
* @param sceneRuleId 规则场景编号
|
||||
* @return JobData Map
|
||||
*/
|
||||
public static Map<String, Object> buildJobDataMap(Long sceneRuleId) {
|
||||
return MapUtil.of(JOB_DATA_KEY_RULE_SCENE_ID, sceneRuleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 Job 名字
|
||||
*
|
||||
* @param sceneRuleId 规则场景编号
|
||||
* @return Job 名字
|
||||
*/
|
||||
public static String buildJobName(Long sceneRuleId) {
|
||||
return String.format("%s_%d", IotSceneRuleJob.class.getSimpleName(), sceneRuleId);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.mq.consumer.device;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
|
||||
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
|
||||
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
|
||||
import cn.iocoder.yudao.module.iot.service.device.message.IotDeviceMessageService;
|
||||
import cn.iocoder.yudao.module.iot.service.device.property.IotDevicePropertyService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 针对 {@link IotDeviceMessage} 的业务处理器:调用 method 对应的逻辑。例如说:
|
||||
* 1. {@link IotDeviceMessageMethodEnum#PROPERTY_POST} 属性上报时,记录设备属性
|
||||
*
|
||||
* @author alwayssuper
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class IotDeviceMessageSubscriber implements IotMessageSubscriber<IotDeviceMessage> {
|
||||
|
||||
@Resource
|
||||
private IotDeviceService deviceService;
|
||||
@Resource
|
||||
private IotDevicePropertyService devicePropertyService;
|
||||
@Resource
|
||||
private IotDeviceMessageService deviceMessageService;
|
||||
|
||||
@Resource
|
||||
private IotMessageBus messageBus;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
messageBus.register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTopic() {
|
||||
return IotDeviceMessage.MESSAGE_BUS_DEVICE_MESSAGE_TOPIC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGroup() {
|
||||
return "iot_device_message_consumer";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(IotDeviceMessage message) {
|
||||
if (!IotDeviceMessageUtils.isUpstreamMessage(message)) {
|
||||
log.error("[onMessage][message({}) 非上行消息,不进行处理]", message);
|
||||
return;
|
||||
}
|
||||
|
||||
TenantUtils.execute(message.getTenantId(), () -> {
|
||||
// 1.1 更新设备的最后时间
|
||||
IotDeviceDO device = deviceService.validateDeviceExistsFromCache(message.getDeviceId());
|
||||
devicePropertyService.updateDeviceReportTimeAsync(device.getId(), LocalDateTime.now());
|
||||
// 1.2 更新设备的连接 server
|
||||
// TODO 芋艿:HTTP 网关的上行消息,不应该更新 serverId,会覆盖掉 MQTT 等长连接的 serverId,导致下行消息无法发送。
|
||||
devicePropertyService.updateDeviceServerIdAsync(device.getId(), message.getServerId());
|
||||
|
||||
// 2. 未上线的设备,强制上线
|
||||
forceDeviceOnline(message, device);
|
||||
|
||||
// 3. 核心:处理消息
|
||||
deviceMessageService.handleUpstreamDeviceMessage(message, device);
|
||||
});
|
||||
}
|
||||
|
||||
private void forceDeviceOnline(IotDeviceMessage message, IotDeviceDO device) {
|
||||
// 已经在线,无需处理
|
||||
if (ObjectUtil.equal(device.getState(), IotDeviceStateEnum.ONLINE.getState())) {
|
||||
return;
|
||||
}
|
||||
// 如果是 STATE 相关的消息,无需处理,不然就重复处理状态了
|
||||
if (Objects.equals(message.getMethod(), IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 特殊:设备非在线时,主动标记设备为在线
|
||||
// 为什么不直接更新状态呢?因为通过 IotDeviceMessage 可以经过一系列的处理,例如说记录日志、规则引擎等等
|
||||
try {
|
||||
deviceMessageService.sendDeviceMessage(IotDeviceMessage.buildStateUpdateOnline().setDeviceId(device.getId()));
|
||||
} catch (Exception e) {
|
||||
// 注意:即使执行失败,也不影响主流程
|
||||
log.error("[forceDeviceOnline][message({}) device({}) 强制设备上线失败]", message, device, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.mq.consumer.rule;
|
||||
|
||||
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
|
||||
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
|
||||
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.data.IotDataRuleService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 针对 {@link IotDeviceMessage} 的消费者,处理数据流转
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class IotDataRuleMessageHandler implements IotMessageSubscriber<IotDeviceMessage> {
|
||||
|
||||
@Resource
|
||||
private IotDataRuleService dataRuleService;
|
||||
|
||||
@Resource
|
||||
private IotMessageBus messageBus;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
messageBus.register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTopic() {
|
||||
return IotDeviceMessage.MESSAGE_BUS_DEVICE_MESSAGE_TOPIC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGroup() {
|
||||
return "iot_data_rule_consumer";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(IotDeviceMessage message) {
|
||||
TenantUtils.execute(message.getTenantId(), () -> dataRuleService.executeDataRule(message));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.mq.consumer.rule;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
|
||||
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.scene.IotSceneRuleService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
|
||||
// TODO @puhui999:后面重构哈
|
||||
/**
|
||||
* 针对 {@link IotDeviceMessage} 的消费者,处理规则场景
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class IotSceneRuleMessageHandler implements IotMessageSubscriber<IotDeviceMessage> {
|
||||
|
||||
@Resource
|
||||
private IotSceneRuleService sceneRuleService;
|
||||
|
||||
@Resource
|
||||
private IotMessageBus messageBus;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
messageBus.register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTopic() {
|
||||
return IotDeviceMessage.MESSAGE_BUS_DEVICE_MESSAGE_TOPIC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGroup() {
|
||||
return "iot_rule_consumer";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(IotDeviceMessage message) {
|
||||
if (true) {
|
||||
return;
|
||||
}
|
||||
log.info("[onMessage][消息内容({})]", message);
|
||||
sceneRuleService.executeSceneRuleByDevice(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.service.alert;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config.IotAlertConfigPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config.IotAlertConfigSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertConfigDO;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IoT 告警配置 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface IotAlertConfigService {
|
||||
|
||||
/**
|
||||
* 创建告警配置
|
||||
*
|
||||
* @param createReqVO 创建信息
|
||||
* @return 编号
|
||||
*/
|
||||
Long createAlertConfig(@Valid IotAlertConfigSaveReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 更新告警配置
|
||||
*
|
||||
* @param updateReqVO 更新信息
|
||||
*/
|
||||
void updateAlertConfig(@Valid IotAlertConfigSaveReqVO updateReqVO);
|
||||
|
||||
/**
|
||||
* 删除告警配置
|
||||
*
|
||||
* @param id 编号
|
||||
*/
|
||||
void deleteAlertConfig(Long id);
|
||||
|
||||
/**
|
||||
* 获得告警配置
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 告警配置
|
||||
*/
|
||||
IotAlertConfigDO getAlertConfig(Long id);
|
||||
|
||||
/**
|
||||
* 获得告警配置分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @return 告警配置分页
|
||||
*/
|
||||
PageResult<IotAlertConfigDO> getAlertConfigPage(IotAlertConfigPageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 获得告警配置列表
|
||||
*
|
||||
* @param status 状态
|
||||
* @return 告警配置列表
|
||||
*/
|
||||
List<IotAlertConfigDO> getAlertConfigListByStatus(Integer status);
|
||||
|
||||
/**
|
||||
* 获得告警配置列表
|
||||
*
|
||||
* @param sceneRuleId 场景流动规则编号
|
||||
* @return 告警配置列表
|
||||
*/
|
||||
List<IotAlertConfigDO> getAlertConfigListBySceneRuleIdAndStatus(Long sceneRuleId, Integer status);
|
||||
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.service.alert;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config.IotAlertConfigPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config.IotAlertConfigSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertConfigDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.mysql.alert.IotAlertConfigMapper;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.scene.IotSceneRuleService;
|
||||
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.ALERT_CONFIG_NOT_EXISTS;
|
||||
|
||||
/**
|
||||
* IoT 告警配置 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class IotAlertConfigServiceImpl implements IotAlertConfigService {
|
||||
|
||||
@Resource
|
||||
private IotAlertConfigMapper alertConfigMapper;
|
||||
|
||||
@Resource
|
||||
@Lazy // 延迟,避免循环依赖报错
|
||||
private IotSceneRuleService sceneRuleService;
|
||||
|
||||
@Resource
|
||||
private AdminUserApi adminUserApi;
|
||||
|
||||
@Override
|
||||
public Long createAlertConfig(IotAlertConfigSaveReqVO createReqVO) {
|
||||
// 校验关联数据是否存在
|
||||
sceneRuleService.validateSceneRuleList(createReqVO.getSceneRuleIds());
|
||||
adminUserApi.validateUserList(createReqVO.getReceiveUserIds());
|
||||
|
||||
// 插入
|
||||
IotAlertConfigDO alertConfig = BeanUtils.toBean(createReqVO, IotAlertConfigDO.class);
|
||||
alertConfigMapper.insert(alertConfig);
|
||||
return alertConfig.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAlertConfig(IotAlertConfigSaveReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
validateAlertConfigExists(updateReqVO.getId());
|
||||
// 校验关联数据是否存在
|
||||
sceneRuleService.validateSceneRuleList(updateReqVO.getSceneRuleIds());
|
||||
adminUserApi.validateUserList(updateReqVO.getReceiveUserIds());
|
||||
|
||||
// 更新
|
||||
IotAlertConfigDO updateObj = BeanUtils.toBean(updateReqVO, IotAlertConfigDO.class);
|
||||
alertConfigMapper.updateById(updateObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAlertConfig(Long id) {
|
||||
// 校验存在
|
||||
validateAlertConfigExists(id);
|
||||
// 删除
|
||||
alertConfigMapper.deleteById(id);
|
||||
}
|
||||
|
||||
private void validateAlertConfigExists(Long id) {
|
||||
if (alertConfigMapper.selectById(id) == null) {
|
||||
throw exception(ALERT_CONFIG_NOT_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotAlertConfigDO getAlertConfig(Long id) {
|
||||
return alertConfigMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<IotAlertConfigDO> getAlertConfigPage(IotAlertConfigPageReqVO pageReqVO) {
|
||||
return alertConfigMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IotAlertConfigDO> getAlertConfigListByStatus(Integer status) {
|
||||
return alertConfigMapper.selectListByStatus(status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IotAlertConfigDO> getAlertConfigListBySceneRuleIdAndStatus(Long sceneRuleId, Integer status) {
|
||||
return alertConfigMapper.selectListBySceneRuleIdAndStatus(sceneRuleId, status);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.service.alert;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.recrod.IotAlertRecordPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertConfigDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertRecordDO;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IoT 告警记录 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface IotAlertRecordService {
|
||||
|
||||
/**
|
||||
* 获得告警记录
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 告警记录
|
||||
*/
|
||||
IotAlertRecordDO getAlertRecord(Long id);
|
||||
|
||||
/**
|
||||
* 获得告警记录分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @return 告警记录分页
|
||||
*/
|
||||
PageResult<IotAlertRecordDO> getAlertRecordPage(IotAlertRecordPageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 获得指定场景规则的告警记录列表
|
||||
*
|
||||
* @param sceneRuleId 场景规则编号
|
||||
* @param deviceId 设备编号
|
||||
* @param processStatus 处理状态,允许空
|
||||
* @return 告警记录列表
|
||||
*/
|
||||
List<IotAlertRecordDO> getAlertRecordListBySceneRuleId(@NotNull(message = "场景规则编号不能为空") Long sceneRuleId,
|
||||
Long deviceId, Boolean processStatus);
|
||||
|
||||
/**
|
||||
* 处理告警记录
|
||||
*
|
||||
* @param ids 告警记录编号
|
||||
* @param remark 处理结果(备注)
|
||||
*/
|
||||
void processAlertRecordList(Collection<Long> ids, String remark);
|
||||
|
||||
/**
|
||||
* 创建告警记录(包含场景规则编号)
|
||||
*
|
||||
* @param config 告警配置
|
||||
* @param sceneRuleId 场景规则编号
|
||||
* @param deviceMessage 设备消息,可为空
|
||||
* @return 告警记录编号
|
||||
*/
|
||||
Long createAlertRecord(IotAlertConfigDO config, Long sceneRuleId, IotDeviceMessage deviceMessage);
|
||||
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.service.alert;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.recrod.IotAlertRecordPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertConfigDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertRecordDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.mysql.alert.IotAlertRecordMapper;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IoT 告警记录 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class IotAlertRecordServiceImpl implements IotAlertRecordService {
|
||||
|
||||
@Resource
|
||||
private IotAlertRecordMapper alertRecordMapper;
|
||||
|
||||
@Resource
|
||||
private IotDeviceService deviceService;
|
||||
|
||||
@Override
|
||||
public IotAlertRecordDO getAlertRecord(Long id) {
|
||||
return alertRecordMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<IotAlertRecordDO> getAlertRecordPage(IotAlertRecordPageReqVO pageReqVO) {
|
||||
return alertRecordMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IotAlertRecordDO> getAlertRecordListBySceneRuleId(Long sceneRuleId, Long deviceId, Boolean processStatus) {
|
||||
return alertRecordMapper.selectListBySceneRuleId(sceneRuleId, deviceId, processStatus);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processAlertRecordList(Collection<Long> ids, String processRemark) {
|
||||
if (CollUtil.isEmpty(ids)) {
|
||||
return;
|
||||
}
|
||||
// 批量更新告警记录的处理状态
|
||||
alertRecordMapper.updateList(ids, IotAlertRecordDO.builder()
|
||||
.processStatus(true).processRemark(processRemark).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long createAlertRecord(IotAlertConfigDO config, Long sceneRuleId, IotDeviceMessage message) {
|
||||
// 构建告警记录
|
||||
IotAlertRecordDO.IotAlertRecordDOBuilder builder = IotAlertRecordDO.builder()
|
||||
.configId(config.getId()).configName(config.getName()).configLevel(config.getLevel())
|
||||
.sceneRuleId(sceneRuleId).processStatus(false);
|
||||
if (message != null) {
|
||||
builder.deviceMessage(message);
|
||||
// 填充设备信息
|
||||
IotDeviceDO device = deviceService.getDeviceFromCache(message.getDeviceId());
|
||||
if (device != null) {
|
||||
builder.productId(device.getProductId()).deviceId(device.getId());
|
||||
}
|
||||
}
|
||||
|
||||
// 插入记录
|
||||
IotAlertRecordDO record = builder.build();
|
||||
alertRecordMapper.insert(record);
|
||||
return record.getId();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.service.device.message;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceMessagePageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsDeviceMessageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsDeviceMessageSummaryByDateRespVO;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IoT 设备消息 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface IotDeviceMessageService {
|
||||
|
||||
/**
|
||||
* 初始化设备消息的 TDengine 超级表
|
||||
*
|
||||
* 系统启动时,会自动初始化一次
|
||||
*/
|
||||
void defineDeviceMessageStable();
|
||||
|
||||
/**
|
||||
* 发送设备消息
|
||||
*
|
||||
* @param message 消息(“codec(编解码)字段” 部分字段)
|
||||
* @param device 设备
|
||||
* @return 设备消息
|
||||
*/
|
||||
IotDeviceMessage sendDeviceMessage(IotDeviceMessage message, IotDeviceDO device);
|
||||
|
||||
/**
|
||||
* 发送设备消息
|
||||
*
|
||||
* @param message 消息(“codec(编解码)字段” 部分字段)
|
||||
* @return 设备消息
|
||||
*/
|
||||
IotDeviceMessage sendDeviceMessage(IotDeviceMessage message);
|
||||
|
||||
/**
|
||||
* 处理设备上行的消息,包括如下步骤:
|
||||
*
|
||||
* 1. 处理消息
|
||||
* 2. 记录消息
|
||||
* 3. 回复消息
|
||||
*
|
||||
* @param message 消息
|
||||
* @param device 设备
|
||||
*/
|
||||
void handleUpstreamDeviceMessage(IotDeviceMessage message, IotDeviceDO device);
|
||||
|
||||
/**
|
||||
* 获得设备消息分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @return 设备消息分页
|
||||
*/
|
||||
PageResult<IotDeviceMessageDO> getDeviceMessagePage(IotDeviceMessagePageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 获得指定 requestId 的设备消息列表
|
||||
*
|
||||
* @param deviceId 设备编号
|
||||
* @param requestIds requestId 列表
|
||||
* @param reply 是否回复
|
||||
* @return 设备消息列表
|
||||
*/
|
||||
List<IotDeviceMessageDO> getDeviceMessageListByRequestIdsAndReply(
|
||||
@NotNull(message = "设备编号不能为空") Long deviceId,
|
||||
@NotEmpty(message = "请求编号不能为空") List<String> requestIds,
|
||||
Boolean reply);
|
||||
|
||||
/**
|
||||
* 获得设备消息数量
|
||||
*
|
||||
* @param createTime 创建时间,如果为空,则统计所有消息数量
|
||||
* @return 消息数量
|
||||
*/
|
||||
Long getDeviceMessageCount(@Nullable LocalDateTime createTime);
|
||||
|
||||
/**
|
||||
* 获取设备消息的数据统计
|
||||
*
|
||||
* @param reqVO 统计请求
|
||||
* @return 设备消息的数据统计
|
||||
*/
|
||||
List<IotStatisticsDeviceMessageSummaryByDateRespVO> getDeviceMessageSummaryByDate(
|
||||
IotStatisticsDeviceMessageReqVO reqVO);
|
||||
|
||||
}
|
||||
@@ -1,271 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.service.device.message;
|
||||
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceMessagePageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsDeviceMessageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsDeviceMessageSummaryByDateRespVO;
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer;
|
||||
import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.tdengine.IotDeviceMessageMapper;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
|
||||
import cn.iocoder.yudao.module.iot.service.device.property.IotDevicePropertyService;
|
||||
import cn.iocoder.yudao.module.iot.service.ota.IotOtaTaskRecordService;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.google.common.base.Objects;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.DEVICE_DOWNSTREAM_FAILED_SERVER_ID_NULL;
|
||||
|
||||
/**
|
||||
* IoT 设备消息 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
|
||||
|
||||
@Resource
|
||||
private IotDeviceService deviceService;
|
||||
@Resource
|
||||
private IotDevicePropertyService devicePropertyService;
|
||||
@Resource
|
||||
@Lazy // 延迟加载,避免循环依赖
|
||||
private IotOtaTaskRecordService otaTaskRecordService;
|
||||
|
||||
@Resource
|
||||
private IotDeviceMessageMapper deviceMessageMapper;
|
||||
|
||||
@Resource
|
||||
private IotDeviceMessageProducer deviceMessageProducer;
|
||||
|
||||
@Override
|
||||
public void defineDeviceMessageStable() {
|
||||
if (StrUtil.isNotEmpty(deviceMessageMapper.showSTable())) {
|
||||
log.info("[defineDeviceMessageStable][设备消息超级表已存在,创建跳过]");
|
||||
return;
|
||||
}
|
||||
log.info("[defineDeviceMessageStable][设备消息超级表不存在,创建开始...]");
|
||||
deviceMessageMapper.createSTable();
|
||||
log.info("[defineDeviceMessageStable][设备消息超级表不存在,创建成功]");
|
||||
}
|
||||
|
||||
@Async
|
||||
void createDeviceLogAsync(IotDeviceMessage message) {
|
||||
IotDeviceMessageDO messageDO = BeanUtils.toBean(message, IotDeviceMessageDO.class)
|
||||
.setUpstream(IotDeviceMessageUtils.isUpstreamMessage(message))
|
||||
.setReply(IotDeviceMessageUtils.isReplyMessage(message))
|
||||
.setIdentifier(IotDeviceMessageUtils.getIdentifier(message));
|
||||
if (message.getParams() != null) {
|
||||
messageDO.setParams(JsonUtils.toJsonString(messageDO.getParams()));
|
||||
}
|
||||
if (messageDO.getData() != null) {
|
||||
messageDO.setData(JsonUtils.toJsonString(messageDO.getData()));
|
||||
}
|
||||
deviceMessageMapper.insert(messageDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotDeviceMessage sendDeviceMessage(IotDeviceMessage message) {
|
||||
IotDeviceDO device = deviceService.validateDeviceExists(message.getDeviceId());
|
||||
return sendDeviceMessage(message, device);
|
||||
}
|
||||
|
||||
// TODO @芋艿:针对连接网关的设备,是不是 productKey、deviceName 需要调整下;
|
||||
@Override
|
||||
public IotDeviceMessage sendDeviceMessage(IotDeviceMessage message, IotDeviceDO device) {
|
||||
return sendDeviceMessage(message, device, null);
|
||||
}
|
||||
|
||||
private IotDeviceMessage sendDeviceMessage(IotDeviceMessage message, IotDeviceDO device, String serverId) {
|
||||
// 1. 补充信息
|
||||
appendDeviceMessage(message, device);
|
||||
|
||||
// 2.1 情况一:发送上行消息
|
||||
boolean upstream = IotDeviceMessageUtils.isUpstreamMessage(message);
|
||||
if (upstream) {
|
||||
deviceMessageProducer.sendDeviceMessage(message);
|
||||
return message;
|
||||
}
|
||||
|
||||
// 2.2 情况二:发送下行消息
|
||||
// 如果是下行消息,需要校验 serverId 存在
|
||||
// TODO 芋艿:【设计】下行消息需要区分 PUSH 和 PULL 模型
|
||||
// 1. PUSH 模型:适用于 MQTT 等长连接协议。通过 serverId 将消息路由到指定网关,实时推送。
|
||||
// 2. PULL 模型:适用于 HTTP 等短连接协议。设备无固定 serverId,无法主动推送。
|
||||
// 解决方案:
|
||||
// 当 serverId 不存在时,将下行消息存入“待拉取消息表”(例如 iot_device_pull_message)。
|
||||
// 设备端通过定时轮询一个新增的 API(例如 /iot/message/pull)来拉取属于自己的消息。
|
||||
if (StrUtil.isEmpty(serverId)) {
|
||||
serverId = devicePropertyService.getDeviceServerId(device.getId());
|
||||
if (StrUtil.isEmpty(serverId)) {
|
||||
throw exception(DEVICE_DOWNSTREAM_FAILED_SERVER_ID_NULL);
|
||||
}
|
||||
}
|
||||
deviceMessageProducer.sendDeviceMessageToGateway(serverId, message);
|
||||
// 特殊:记录消息日志。原因:上行消息,消费时,已经会记录;下行消息,因为消费在 Gateway 端,所以需要在这里记录
|
||||
getSelf().createDeviceLogAsync(message);
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 补充消息的后端字段
|
||||
*
|
||||
* @param message 消息
|
||||
* @param device 设备信息
|
||||
*/
|
||||
private void appendDeviceMessage(IotDeviceMessage message, IotDeviceDO device) {
|
||||
message.setId(IotDeviceMessageUtils.generateMessageId()).setReportTime(LocalDateTime.now())
|
||||
.setDeviceId(device.getId()).setTenantId(device.getTenantId());
|
||||
// 特殊:如果设备没有指定 requestId,则使用 messageId
|
||||
if (StrUtil.isEmpty(message.getRequestId())) {
|
||||
message.setRequestId(message.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpstreamDeviceMessage(IotDeviceMessage message, IotDeviceDO device) {
|
||||
// 1. 处理消息
|
||||
Object replyData = null;
|
||||
ServiceException serviceException = null;
|
||||
try {
|
||||
replyData = handleUpstreamDeviceMessage0(message, device);
|
||||
} catch (ServiceException ex) {
|
||||
serviceException = ex;
|
||||
log.warn("[handleUpstreamDeviceMessage][message({}) 业务异常]", message, serviceException);
|
||||
} catch (Exception ex) {
|
||||
log.error("[handleUpstreamDeviceMessage][message({}) 发生异常]", message, ex);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
// 2. 记录消息
|
||||
getSelf().createDeviceLogAsync(message);
|
||||
|
||||
// 3. 回复消息。前提:非 _reply 消息,并且非禁用回复的消息
|
||||
if (IotDeviceMessageUtils.isReplyMessage(message)
|
||||
|| IotDeviceMessageMethodEnum.isReplyDisabled(message.getMethod())
|
||||
|| StrUtil.isEmpty(message.getServerId())) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
IotDeviceMessage replyMessage = IotDeviceMessage.replyOf(message.getRequestId(), message.getMethod(), replyData,
|
||||
serviceException != null ? serviceException.getCode() : null,
|
||||
serviceException != null ? serviceException.getMessage() : null);
|
||||
sendDeviceMessage(replyMessage, device, message.getServerId());
|
||||
} catch (Exception ex) {
|
||||
log.error("[handleUpstreamDeviceMessage][message({}) 回复消息失败]", message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO @芋艿:可优化:未来逻辑复杂后,可以独立拆除 Processor 处理器
|
||||
@SuppressWarnings("SameReturnValue")
|
||||
private Object handleUpstreamDeviceMessage0(IotDeviceMessage message, IotDeviceDO device) {
|
||||
// 设备上下线
|
||||
if (Objects.equal(message.getMethod(), IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod())) {
|
||||
String stateStr = IotDeviceMessageUtils.getIdentifier(message);
|
||||
assert stateStr != null;
|
||||
Assert.notEmpty(stateStr, "设备状态不能为空");
|
||||
deviceService.updateDeviceState(device, Integer.valueOf(stateStr));
|
||||
// TODO 芋艿:子设备的关联
|
||||
return null;
|
||||
}
|
||||
|
||||
// 属性上报
|
||||
if (Objects.equal(message.getMethod(), IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod())) {
|
||||
devicePropertyService.saveDeviceProperty(device, message);
|
||||
return null;
|
||||
}
|
||||
|
||||
// OTA 上报升级进度
|
||||
if (Objects.equal(message.getMethod(), IotDeviceMessageMethodEnum.OTA_PROGRESS.getMethod())) {
|
||||
otaTaskRecordService.updateOtaRecordProgress(device, message);
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO @芋艿:这里可以按需,添加别的逻辑;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<IotDeviceMessageDO> getDeviceMessagePage(IotDeviceMessagePageReqVO pageReqVO) {
|
||||
try {
|
||||
IPage<IotDeviceMessageDO> page = deviceMessageMapper.selectPage(
|
||||
new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize()), pageReqVO);
|
||||
return new PageResult<>(page.getRecords(), page.getTotal());
|
||||
} catch (Exception exception) {
|
||||
if (exception.getMessage().contains("Table does not exist")) {
|
||||
return PageResult.empty();
|
||||
}
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IotDeviceMessageDO> getDeviceMessageListByRequestIdsAndReply(Long deviceId,
|
||||
List<String> requestIds,
|
||||
Boolean reply) {
|
||||
return deviceMessageMapper.selectListByRequestIdsAndReply(deviceId, requestIds, reply);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getDeviceMessageCount(LocalDateTime createTime) {
|
||||
return deviceMessageMapper.selectCountByCreateTime(
|
||||
createTime != null ? LocalDateTimeUtil.toEpochMilli(createTime) : null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IotStatisticsDeviceMessageSummaryByDateRespVO> getDeviceMessageSummaryByDate(
|
||||
IotStatisticsDeviceMessageReqVO reqVO) {
|
||||
// 1. 按小时统计,获取分项统计数据
|
||||
List<Map<String, Object>> countList = deviceMessageMapper.selectDeviceMessageCountGroupByDate(
|
||||
LocalDateTimeUtil.toEpochMilli(reqVO.getTimes()[0]),
|
||||
LocalDateTimeUtil.toEpochMilli(reqVO.getTimes()[1]));
|
||||
|
||||
// 2. 按照日期间隔,合并数据
|
||||
List<LocalDateTime[]> timeRanges = LocalDateTimeUtils.getDateRangeList(reqVO.getTimes()[0], reqVO.getTimes()[1],
|
||||
reqVO.getInterval());
|
||||
return convertList(timeRanges, times -> {
|
||||
Integer upstreamCount = countList.stream()
|
||||
.filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], (Timestamp) vo.get("time")))
|
||||
.mapToInt(value -> MapUtil.getInt(value, "upstream_count")).sum();
|
||||
Integer downstreamCount = countList.stream()
|
||||
.filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], (Timestamp) vo.get("time")))
|
||||
.mapToInt(value -> MapUtil.getInt(value, "downstream_count")).sum();
|
||||
return new IotStatisticsDeviceMessageSummaryByDateRespVO()
|
||||
.setTime(LocalDateTimeUtils.formatDateRange(times[0], times[1], reqVO.getInterval()))
|
||||
.setUpstreamCount(upstreamCount).setDownstreamCount(downstreamCount);
|
||||
});
|
||||
}
|
||||
|
||||
private IotDeviceMessageServiceImpl getSelf() {
|
||||
return SpringUtil.getBean(getClass());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.service.device.property;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyHistoryListReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyRespVO;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDevicePropertyDO;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* IoT 设备【属性】数据 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface IotDevicePropertyService {
|
||||
|
||||
// ========== 设备属性相关操作 ==========
|
||||
|
||||
/**
|
||||
* 定义设备属性数据的结构
|
||||
*
|
||||
* @param productId 产品编号
|
||||
*/
|
||||
void defineDevicePropertyData(Long productId);
|
||||
|
||||
/**
|
||||
* 保存设备数据
|
||||
*
|
||||
* @param device 设备
|
||||
* @param message 设备消息
|
||||
*/
|
||||
void saveDeviceProperty(IotDeviceDO device, IotDeviceMessage message);
|
||||
|
||||
/**
|
||||
* 获得设备属性最新数据
|
||||
*
|
||||
* @param deviceId 设备编号
|
||||
* @return 设备属性最新数据
|
||||
*/
|
||||
Map<String, IotDevicePropertyDO> getLatestDeviceProperties(Long deviceId);
|
||||
|
||||
/**
|
||||
* 获得设备属性历史数据
|
||||
*
|
||||
* @param listReqVO 列表请求
|
||||
* @return 设备属性历史数据
|
||||
*/
|
||||
List<IotDevicePropertyRespVO> getHistoryDevicePropertyList(@Valid IotDevicePropertyHistoryListReqVO listReqVO);
|
||||
|
||||
// ========== 设备时间相关操作 ==========
|
||||
|
||||
/**
|
||||
* 获得最后上报时间小于指定时间的设备编号集合
|
||||
*
|
||||
* @param maxReportTime 最大上报时间
|
||||
* @return 设备编号集合
|
||||
*/
|
||||
Set<Long> getDeviceIdListByReportTime(LocalDateTime maxReportTime);
|
||||
|
||||
/**
|
||||
* 更新设备上报时间
|
||||
*
|
||||
* @param id 设备编号
|
||||
* @param reportTime 上报时间
|
||||
*/
|
||||
void updateDeviceReportTimeAsync(Long id, LocalDateTime reportTime);
|
||||
|
||||
/**
|
||||
* 更新设备关联的网关服务 serverId
|
||||
*
|
||||
* @param id 设备编号
|
||||
* @param serverId 网关 serverId
|
||||
*/
|
||||
void updateDeviceServerIdAsync(Long id, String serverId);
|
||||
|
||||
/**
|
||||
* 获得设备关联的网关服务 serverId
|
||||
*
|
||||
* @param id 设备编号
|
||||
* @return 网关 serverId
|
||||
*/
|
||||
String getDeviceServerId(Long id);
|
||||
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.service.ota;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record.IotOtaTaskRecordPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* IoT OTA 升级记录 Service 接口
|
||||
*/
|
||||
public interface IotOtaTaskRecordService {
|
||||
|
||||
/**
|
||||
* 批量创建 OTA 升级记录
|
||||
*
|
||||
* @param devices 设备列表
|
||||
* @param firmwareId 固件编号
|
||||
* @param taskId 任务编号
|
||||
*/
|
||||
void createOtaTaskRecordList(List<IotDeviceDO> devices, Long firmwareId, Long taskId);
|
||||
|
||||
/**
|
||||
* 获取 OTA 升级记录的状态统计
|
||||
*
|
||||
* @param firmwareId 固件编号
|
||||
* @param taskId 任务编号
|
||||
* @return 状态统计 Map,key 为状态码,value 为对应状态的升级记录数量
|
||||
*/
|
||||
Map<Integer, Long> getOtaTaskRecordStatusStatistics(Long firmwareId, Long taskId);
|
||||
|
||||
/**
|
||||
* 获取 OTA 升级记录
|
||||
*
|
||||
* @param id 编号
|
||||
* @return OTA 升级记录
|
||||
*/
|
||||
IotOtaTaskRecordDO getOtaTaskRecord(Long id);
|
||||
|
||||
/**
|
||||
* 获取 OTA 升级记录分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @return OTA 升级记录分页
|
||||
*/
|
||||
PageResult<IotOtaTaskRecordDO> getOtaTaskRecordPage(@Valid IotOtaTaskRecordPageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 根据 OTA 任务编号,取消未结束的升级记录
|
||||
*
|
||||
* @param taskId 升级任务编号
|
||||
*/
|
||||
void cancelTaskRecordListByTaskId(Long taskId);
|
||||
|
||||
/**
|
||||
* 根据设备编号和记录状态,获取 OTA 升级记录列表
|
||||
*
|
||||
* @param deviceIds 设备编号集合
|
||||
* @param statuses 记录状态集合
|
||||
* @return OTA 升级记录列表
|
||||
*/
|
||||
List<IotOtaTaskRecordDO> getOtaTaskRecordListByDeviceIdAndStatus(Set<Long> deviceIds, Set<Integer> statuses);
|
||||
|
||||
/**
|
||||
* 根据记录状态,获取 OTA 升级记录列表
|
||||
*
|
||||
* @param status 升级记录状态
|
||||
* @return 升级记录列表
|
||||
*/
|
||||
List<IotOtaTaskRecordDO> getOtaRecordListByStatus(Integer status);
|
||||
|
||||
/**
|
||||
* 取消 OTA 升级记录
|
||||
*
|
||||
* @param id 记录编号
|
||||
*/
|
||||
void cancelOtaTaskRecord(Long id);
|
||||
|
||||
/**
|
||||
* 推送 OTA 升级任务记录
|
||||
*
|
||||
* @param record 任务记录
|
||||
* @param fireware 固件信息
|
||||
* @param device 设备信息
|
||||
* @return 是否推送成功
|
||||
*/
|
||||
boolean pushOtaTaskRecord(IotOtaTaskRecordDO record, IotOtaFirmwareDO fireware, IotDeviceDO device);
|
||||
|
||||
/**
|
||||
* 更新 OTA 升级记录进度
|
||||
*
|
||||
* @param device 设备信息
|
||||
* @param message 设备消息
|
||||
*/
|
||||
void updateOtaRecordProgress(IotDeviceDO device, IotDeviceMessage message);
|
||||
|
||||
}
|
||||
@@ -1,231 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.service.ota;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record.IotOtaTaskRecordPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.mysql.ota.IotOtaTaskRecordMapper;
|
||||
import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskRecordStatusEnum;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
|
||||
import cn.iocoder.yudao.module.iot.service.device.message.IotDeviceMessageService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.*;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
|
||||
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* OTA 升级任务记录 Service 实现类
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class IotOtaTaskRecordServiceImpl implements IotOtaTaskRecordService {
|
||||
|
||||
@Resource
|
||||
private IotOtaTaskRecordMapper otaTaskRecordMapper;
|
||||
|
||||
@Resource
|
||||
private IotOtaFirmwareService otaFirmwareService;
|
||||
@Resource
|
||||
private IotOtaTaskService otaTaskService;
|
||||
@Resource
|
||||
private IotDeviceMessageService deviceMessageService;
|
||||
@Resource
|
||||
private IotDeviceService deviceService;
|
||||
|
||||
@Override
|
||||
public void createOtaTaskRecordList(List<IotDeviceDO> devices, Long firmwareId, Long taskId) {
|
||||
List<IotOtaTaskRecordDO> records = convertList(devices, device ->
|
||||
IotOtaTaskRecordDO.builder().firmwareId(firmwareId).taskId(taskId)
|
||||
.deviceId(device.getId()).fromFirmwareId(Convert.toLong(device.getFirmwareId()))
|
||||
.status(IotOtaTaskRecordStatusEnum.PENDING.getStatus()).progress(0).build());
|
||||
otaTaskRecordMapper.insertBatch(records);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Integer, Long> getOtaTaskRecordStatusStatistics(Long firmwareId, Long taskId) {
|
||||
// 按照 status 枚举,初始化 countMap 为 0
|
||||
Map<Integer, Long> countMap = convertMap(Arrays.asList(IotOtaTaskRecordStatusEnum.values()),
|
||||
IotOtaTaskRecordStatusEnum::getStatus, iotOtaTaskRecordStatusEnum -> 0L);
|
||||
|
||||
// 查询记录,只返回 id、status 字段
|
||||
List<IotOtaTaskRecordDO> records = otaTaskRecordMapper.selectListByFirmwareIdAndTaskId(firmwareId, taskId);
|
||||
Map<Long, List<Integer>> deviceStatusesMap = convertMultiMap(records,
|
||||
IotOtaTaskRecordDO::getDeviceId, IotOtaTaskRecordDO::getStatus);
|
||||
// 找到第一个匹配的优先级状态,避免重复计算
|
||||
deviceStatusesMap.forEach((deviceId, statuses) -> {
|
||||
for (Integer priorityStatus : IotOtaTaskRecordStatusEnum.PRIORITY_STATUSES) {
|
||||
if (statuses.contains(priorityStatus)) {
|
||||
countMap.put(priorityStatus, countMap.get(priorityStatus) + 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
return countMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotOtaTaskRecordDO getOtaTaskRecord(Long id) {
|
||||
return otaTaskRecordMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<IotOtaTaskRecordDO> getOtaTaskRecordPage(IotOtaTaskRecordPageReqVO pageReqVO) {
|
||||
return otaTaskRecordMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelTaskRecordListByTaskId(Long taskId) {
|
||||
List<IotOtaTaskRecordDO> records = otaTaskRecordMapper.selectListByTaskIdAndStatus(
|
||||
taskId, IotOtaTaskRecordStatusEnum.IN_PROCESS_STATUSES);
|
||||
if (CollUtil.isEmpty(records)) {
|
||||
return;
|
||||
}
|
||||
// 批量更新
|
||||
Collection<Long> ids = convertSet(records, IotOtaTaskRecordDO::getId);
|
||||
otaTaskRecordMapper.updateListByIdAndStatus(ids, IotOtaTaskRecordStatusEnum.IN_PROCESS_STATUSES,
|
||||
IotOtaTaskRecordDO.builder().status(IotOtaTaskRecordStatusEnum.CANCELED.getStatus())
|
||||
.description(IotOtaTaskRecordDO.DESCRIPTION_CANCEL_BY_TASK).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IotOtaTaskRecordDO> getOtaTaskRecordListByDeviceIdAndStatus(Set<Long> deviceIds, Set<Integer> statuses) {
|
||||
return otaTaskRecordMapper.selectListByDeviceIdAndStatus(deviceIds, statuses);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IotOtaTaskRecordDO> getOtaRecordListByStatus(Integer status) {
|
||||
return otaTaskRecordMapper.selectListByStatus(status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelOtaTaskRecord(Long id) {
|
||||
// 1. 校验记录是否存在
|
||||
IotOtaTaskRecordDO record = validateUpgradeRecordExists(id);
|
||||
|
||||
// 2. 更新记录状态为取消
|
||||
int updateCount = otaTaskRecordMapper.updateByIdAndStatus(record.getId(), IotOtaTaskRecordStatusEnum.IN_PROCESS_STATUSES,
|
||||
IotOtaTaskRecordDO.builder().id(id).status(IotOtaTaskRecordStatusEnum.CANCELED.getStatus())
|
||||
.description(IotOtaTaskRecordDO.DESCRIPTION_CANCEL_BY_RECORD).build());
|
||||
if (updateCount == 0) {
|
||||
throw exception(OTA_TASK_RECORD_CANCEL_FAIL_STATUS_ERROR);
|
||||
}
|
||||
|
||||
// 3. 检查并更新任务状态
|
||||
checkAndUpdateOtaTaskStatus(record.getTaskId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean pushOtaTaskRecord(IotOtaTaskRecordDO record, IotOtaFirmwareDO fireware, IotDeviceDO device) {
|
||||
try {
|
||||
// 1. 推送 OTA 任务记录
|
||||
IotDeviceMessage message = IotDeviceMessage.buildOtaUpgrade(
|
||||
fireware.getVersion(), fireware.getFileUrl(), fireware.getFileSize(),
|
||||
fireware.getFileDigestAlgorithm(), fireware.getFileDigestValue());
|
||||
deviceMessageService.sendDeviceMessage(message, device);
|
||||
|
||||
// 2. 更新 OTA 升级记录状态为进行中
|
||||
int updateCount = otaTaskRecordMapper.updateByIdAndStatus(
|
||||
record.getId(), IotOtaTaskRecordStatusEnum.PENDING.getStatus(),
|
||||
IotOtaTaskRecordDO.builder().status(IotOtaTaskRecordStatusEnum.PUSHED.getStatus())
|
||||
.description(StrUtil.format("已推送,设备消息编号({})", message.getId())).build());
|
||||
Assert.isTrue(updateCount == 1, "更新设备记录({})状态失败", record.getId());
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
log.error("[pushOtaTaskRecord][推送 OTA 任务记录({}) 失败]", record.getId(), ex);
|
||||
otaTaskRecordMapper.updateById(IotOtaTaskRecordDO.builder().id(record.getId())
|
||||
.description(StrUtil.format("推送失败,错误信息({})", ex.getMessage())).build());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private IotOtaTaskRecordDO validateUpgradeRecordExists(Long id) {
|
||||
IotOtaTaskRecordDO upgradeRecord = otaTaskRecordMapper.selectById(id);
|
||||
if (upgradeRecord == null) {
|
||||
throw exception(OTA_TASK_RECORD_NOT_EXISTS);
|
||||
}
|
||||
return upgradeRecord;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@SuppressWarnings("unchecked")
|
||||
public void updateOtaRecordProgress(IotDeviceDO device, IotDeviceMessage message) {
|
||||
// 1.1 参数解析
|
||||
Map<String, Object> params = (Map<String, Object>) message.getParams();
|
||||
String version = MapUtil.getStr(params, "version");
|
||||
Assert.notBlank(version, "version 不能为空");
|
||||
Integer status = MapUtil.getInt(params, "status");
|
||||
Assert.notNull(status, "status 不能为空");
|
||||
Assert.notNull(IotOtaTaskRecordStatusEnum.of(status), "status 状态不正确");
|
||||
String description = MapUtil.getStr(params, "description");
|
||||
Integer progress = MapUtil.getInt(params, "progress");
|
||||
Assert.notNull(progress, "progress 不能为空");
|
||||
Assert.isTrue(progress >= 0 && progress <= 100, "progress 必须在 0-100 之间");
|
||||
// 1.2 查询 OTA 升级记录
|
||||
List<IotOtaTaskRecordDO> records = otaTaskRecordMapper.selectListByDeviceIdAndStatus(
|
||||
device.getId(), IotOtaTaskRecordStatusEnum.IN_PROCESS_STATUSES);
|
||||
if (CollUtil.isEmpty(records)) {
|
||||
throw exception(OTA_TASK_RECORD_UPDATE_PROGRESS_FAIL_NO_EXISTS);
|
||||
}
|
||||
if (records.size() > 1) {
|
||||
log.warn("[updateOtaRecordProgress][message({}) 对应升级记录过多({})]", message, records);
|
||||
}
|
||||
IotOtaTaskRecordDO record = CollUtil.getFirst(records);
|
||||
// 1.3 查询 OTA 固件
|
||||
IotOtaFirmwareDO firmware = otaFirmwareService.getOtaFirmwareByProductIdAndVersion(
|
||||
device.getProductId(), version);
|
||||
if (firmware == null) {
|
||||
throw exception(OTA_FIRMWARE_NOT_EXISTS);
|
||||
}
|
||||
|
||||
// 2. 更新 OTA 升级记录状态
|
||||
int updateCount = otaTaskRecordMapper.updateByIdAndStatus(
|
||||
record.getId(), IotOtaTaskRecordStatusEnum.IN_PROCESS_STATUSES,
|
||||
IotOtaTaskRecordDO.builder().status(status).description(description).progress(progress).build());
|
||||
if (updateCount == 0) {
|
||||
throw exception(OTA_TASK_RECORD_UPDATE_PROGRESS_FAIL_NO_EXISTS);
|
||||
}
|
||||
|
||||
// 3. 如果升级成功,则更新设备固件版本
|
||||
if (IotOtaTaskRecordStatusEnum.SUCCESS.getStatus().equals(status)) {
|
||||
deviceService.updateDeviceFirmware(device.getId(), firmware.getId());
|
||||
}
|
||||
|
||||
// 4. 如果状态是“已结束”(非进行中),则更新任务状态
|
||||
if (!IotOtaTaskRecordStatusEnum.IN_PROCESS_STATUSES.contains(status)) {
|
||||
checkAndUpdateOtaTaskStatus(record.getTaskId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查并更新任务状态
|
||||
* 如果任务下没有进行中的记录,则将任务状态更新为已结束
|
||||
*/
|
||||
private void checkAndUpdateOtaTaskStatus(Long taskId) {
|
||||
// 如果还有进行中的记录,直接返回
|
||||
Long inProcessCount = otaTaskRecordMapper.selectCountByTaskIdAndStatus(
|
||||
taskId, IotOtaTaskRecordStatusEnum.IN_PROCESS_STATUSES);
|
||||
if (inProcessCount > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 没有进行中的记录,将任务状态更新为已结束
|
||||
otaTaskService.updateOtaTaskStatusEnd(taskId);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.service.ota;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.IotOtaTaskCreateReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.IotOtaTaskPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskDO;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
/**
|
||||
* IoT OTA 升级任务 Service 接口
|
||||
*
|
||||
* @author Shelly Chan
|
||||
*/
|
||||
public interface IotOtaTaskService {
|
||||
|
||||
/**
|
||||
* 创建 OTA 升级任务
|
||||
*
|
||||
* @param createReqVO 创建请求对象
|
||||
* @return 升级任务编号
|
||||
*/
|
||||
Long createOtaTask(@Valid IotOtaTaskCreateReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 取消 OTA 升级任务
|
||||
*
|
||||
* @param id 升级任务编号
|
||||
*/
|
||||
void cancelOtaTask(Long id);
|
||||
|
||||
/**
|
||||
* 获取 OTA 升级任务
|
||||
*
|
||||
* @param id 升级任务编号
|
||||
* @return 升级任务
|
||||
*/
|
||||
IotOtaTaskDO getOtaTask(Long id);
|
||||
|
||||
/**
|
||||
* 分页查询 OTA 升级任务
|
||||
*
|
||||
* @param pageReqVO 分页查询请求
|
||||
* @return 升级任务分页结果
|
||||
*/
|
||||
PageResult<IotOtaTaskDO> getOtaTaskPage(@Valid IotOtaTaskPageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 更新 OTA 任务状态为已结束
|
||||
*
|
||||
* @param id 任务编号
|
||||
*/
|
||||
void updateOtaTaskStatusEnd(Long id);
|
||||
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.service.ota;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.IotOtaTaskCreateReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.IotOtaTaskPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.mysql.ota.IotOtaTaskMapper;
|
||||
import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskDeviceScopeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskRecordStatusEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskStatusEnum;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* IoT OTA 升级任务 Service 实现类
|
||||
*
|
||||
* @author Shelly Chan
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class IotOtaTaskServiceImpl implements IotOtaTaskService {
|
||||
|
||||
@Resource
|
||||
private IotOtaTaskMapper otaTaskMapper;
|
||||
|
||||
@Resource
|
||||
private IotDeviceService deviceService;
|
||||
@Resource
|
||||
private IotOtaFirmwareService otaFirmwareService;
|
||||
@Resource
|
||||
@Lazy // 延迟,避免循环依赖报错
|
||||
private IotOtaTaskRecordService otaTaskRecordService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long createOtaTask(IotOtaTaskCreateReqVO createReqVO) {
|
||||
// 1.1 校验固件信息是否存在
|
||||
IotOtaFirmwareDO firmware = otaFirmwareService.validateFirmwareExists(createReqVO.getFirmwareId());
|
||||
// 1.2 校验同一固件的升级任务名称不重复
|
||||
if (otaTaskMapper.selectByFirmwareIdAndName(firmware.getId(), createReqVO.getName()) != null) {
|
||||
throw exception(OTA_TASK_CREATE_FAIL_NAME_DUPLICATE);
|
||||
}
|
||||
// 1.3 校验设备范围信息
|
||||
List<IotDeviceDO> devices = validateOtaTaskDeviceScope(createReqVO, firmware.getProductId());
|
||||
|
||||
// 2. 保存升级任务,直接转换
|
||||
IotOtaTaskDO task = BeanUtils.toBean(createReqVO, IotOtaTaskDO.class)
|
||||
.setStatus(IotOtaTaskStatusEnum.IN_PROGRESS.getStatus())
|
||||
.setDeviceTotalCount(devices.size()).setDeviceSuccessCount(0);
|
||||
otaTaskMapper.insert(task);
|
||||
|
||||
// 3. 生成设备升级记录
|
||||
otaTaskRecordService.createOtaTaskRecordList(devices, firmware.getId(), task.getId());
|
||||
return task.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void cancelOtaTask(Long id) {
|
||||
// 1.1 校验升级任务是否存在
|
||||
IotOtaTaskDO upgradeTask = validateUpgradeTaskExists(id);
|
||||
// 1.2 校验升级任务是否可以取消
|
||||
if (ObjUtil.notEqual(upgradeTask.getStatus(), IotOtaTaskStatusEnum.IN_PROGRESS.getStatus())) {
|
||||
throw exception(OTA_TASK_CANCEL_FAIL_STATUS_END);
|
||||
}
|
||||
|
||||
// 2. 更新升级任务状态为已取消
|
||||
otaTaskMapper.updateById(IotOtaTaskDO.builder()
|
||||
.id(id).status(IotOtaTaskStatusEnum.CANCELED.getStatus())
|
||||
.build());
|
||||
|
||||
// 3. 更新升级记录状态为已取消
|
||||
otaTaskRecordService.cancelTaskRecordListByTaskId(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotOtaTaskDO getOtaTask(Long id) {
|
||||
return otaTaskMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<IotOtaTaskDO> getOtaTaskPage(IotOtaTaskPageReqVO pageReqVO) {
|
||||
return otaTaskMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateOtaTaskStatusEnd(Long taskId) {
|
||||
int updateCount = otaTaskMapper.updateByIdAndStatus(taskId, IotOtaTaskStatusEnum.IN_PROGRESS.getStatus(),
|
||||
new IotOtaTaskDO().setStatus(IotOtaTaskStatusEnum.END.getStatus()));
|
||||
if (updateCount == 0) {
|
||||
log.warn("[updateOtaTaskStatusEnd][任务({})不存在或状态不是进行中,无法更新]", taskId);
|
||||
}
|
||||
}
|
||||
|
||||
private List<IotDeviceDO> validateOtaTaskDeviceScope(IotOtaTaskCreateReqVO createReqVO, Long productId) {
|
||||
// 情况一:选择设备
|
||||
if (Objects.equals(createReqVO.getDeviceScope(), IotOtaTaskDeviceScopeEnum.SELECT.getScope())) {
|
||||
// 1.1 校验设备存在
|
||||
List<IotDeviceDO> devices = deviceService.validateDeviceListExists(createReqVO.getDeviceIds());
|
||||
for (IotDeviceDO device : devices) {
|
||||
if (ObjUtil.notEqual(device.getProductId(), productId)) {
|
||||
throw exception(DEVICE_NOT_EXISTS);
|
||||
}
|
||||
}
|
||||
// 1.2 校验设备是否已经是该固件版本
|
||||
devices.forEach(device -> {
|
||||
if (Objects.equals(device.getFirmwareId(), createReqVO.getFirmwareId())) {
|
||||
throw exception(OTA_TASK_CREATE_FAIL_DEVICE_FIRMWARE_EXISTS, device.getDeviceName());
|
||||
}
|
||||
});
|
||||
// 1.3 校验设备是否已经在升级中
|
||||
List<IotOtaTaskRecordDO> records = otaTaskRecordService.getOtaTaskRecordListByDeviceIdAndStatus(
|
||||
convertSet(devices, IotDeviceDO::getId), IotOtaTaskRecordStatusEnum.IN_PROCESS_STATUSES);
|
||||
devices.forEach(device -> {
|
||||
if (CollUtil.contains(records, item -> item.getDeviceId().equals(device.getId()))) {
|
||||
throw exception(OTA_TASK_CREATE_FAIL_DEVICE_OTA_IN_PROCESS, device.getDeviceName());
|
||||
}
|
||||
});
|
||||
return devices;
|
||||
}
|
||||
// 情况二:全部设备
|
||||
if (Objects.equals(createReqVO.getDeviceScope(), IotOtaTaskDeviceScopeEnum.ALL.getScope())) {
|
||||
List<IotDeviceDO> devices = deviceService.getDeviceListByProductId(productId);
|
||||
// 2.1.1 移除已经是该固件版本的设备
|
||||
devices.removeIf(device -> Objects.equals(device.getFirmwareId(), createReqVO.getFirmwareId()));
|
||||
// 2.1.2 移除已经在升级中的设备
|
||||
List<IotOtaTaskRecordDO> records = otaTaskRecordService.getOtaTaskRecordListByDeviceIdAndStatus(
|
||||
convertSet(devices, IotDeviceDO::getId), IotOtaTaskRecordStatusEnum.IN_PROCESS_STATUSES);
|
||||
devices.removeIf(device -> CollUtil.contains(records,
|
||||
item -> item.getDeviceId().equals(device.getId())));
|
||||
// 2.2 校验是否有可升级的设备
|
||||
if (CollUtil.isEmpty(devices)) {
|
||||
throw exception(OTA_TASK_CREATE_FAIL_DEVICE_EMPTY);
|
||||
}
|
||||
return devices;
|
||||
}
|
||||
throw new IllegalArgumentException("不支持的设备范围:" + createReqVO.getDeviceScope());
|
||||
}
|
||||
|
||||
private IotOtaTaskDO validateUpgradeTaskExists(Long id) {
|
||||
IotOtaTaskDO upgradeTask = otaTaskMapper.selectById(id);
|
||||
if (Objects.isNull(upgradeTask)) {
|
||||
throw exception(OTA_TASK_NOT_EXISTS);
|
||||
}
|
||||
return upgradeTask;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.data;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule.IotDataRulePageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule.IotDataRuleSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataRuleDO;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IoT 数据流转规则 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface IotDataRuleService {
|
||||
|
||||
/**
|
||||
* 创建数据流转规则
|
||||
*
|
||||
* @param createReqVO 创建信息
|
||||
* @return 编号
|
||||
*/
|
||||
Long createDataRule(@Valid IotDataRuleSaveReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 更新数据流转规则
|
||||
*
|
||||
* @param updateReqVO 更新信息
|
||||
*/
|
||||
void updateDataRule(@Valid IotDataRuleSaveReqVO updateReqVO);
|
||||
|
||||
/**
|
||||
* 删除数据流转规则
|
||||
*
|
||||
* @param id 编号
|
||||
*/
|
||||
void deleteDataRule(Long id);
|
||||
|
||||
/**
|
||||
* 获得数据流转规则
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 数据流转规则
|
||||
*/
|
||||
IotDataRuleDO getDataRule(Long id);
|
||||
|
||||
/**
|
||||
* 获得数据流转规则分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @return 数据流转规则分页
|
||||
*/
|
||||
PageResult<IotDataRuleDO> getDataRulePage(IotDataRulePageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 根据数据目的编号,获得数据流转规则列表
|
||||
*
|
||||
* @param sinkId 数据目的编号
|
||||
* @return 是否被使用
|
||||
*/
|
||||
List<IotDataRuleDO> getDataRuleListBySinkId(Long sinkId);
|
||||
|
||||
/**
|
||||
* 执行数据流转规则
|
||||
*
|
||||
* @param message 消息
|
||||
*/
|
||||
void executeDataRule(IotDeviceMessage message);
|
||||
|
||||
}
|
||||
@@ -1,259 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.data;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.spring.SpringUtils;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule.IotDataRulePageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule.IotDataRuleSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataSinkDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotDataRuleMapper;
|
||||
import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
|
||||
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.data.action.IotDataRuleAction;
|
||||
import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.*;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.DATA_RULE_NOT_EXISTS;
|
||||
|
||||
/**
|
||||
* IoT 数据流转规则 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class IotDataRuleServiceImpl implements IotDataRuleService {
|
||||
|
||||
@Resource
|
||||
private IotDataRuleMapper dataRuleMapper;
|
||||
|
||||
@Resource
|
||||
private IotProductService productService;
|
||||
@Resource
|
||||
private IotDeviceService deviceService;
|
||||
@Resource
|
||||
private IotThingModelService thingModelService;
|
||||
@Resource
|
||||
private IotDataSinkService dataSinkService;
|
||||
|
||||
@Resource
|
||||
private List<IotDataRuleAction> dataRuleActions;
|
||||
|
||||
@Override
|
||||
@CacheEvict(value = RedisKeyConstants.DATA_RULE_LIST, allEntries = true)
|
||||
public Long createDataRule(IotDataRuleSaveReqVO createReqVO) {
|
||||
// 校验数据源配置和数据目的
|
||||
validateDataRuleConfig(createReqVO);
|
||||
// 新增
|
||||
IotDataRuleDO dataRule = BeanUtils.toBean(createReqVO, IotDataRuleDO.class);
|
||||
dataRuleMapper.insert(dataRule);
|
||||
return dataRule.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict(value = RedisKeyConstants.DATA_RULE_LIST, allEntries = true)
|
||||
public void updateDataRule(IotDataRuleSaveReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
validateDataRuleExists(updateReqVO.getId());
|
||||
// 校验数据源配置和数据目的
|
||||
validateDataRuleConfig(updateReqVO);
|
||||
|
||||
// 更新
|
||||
IotDataRuleDO updateObj = BeanUtils.toBean(updateReqVO, IotDataRuleDO.class);
|
||||
dataRuleMapper.updateById(updateObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict(value = RedisKeyConstants.DATA_RULE_LIST, allEntries = true)
|
||||
public void deleteDataRule(Long id) {
|
||||
// 校验存在
|
||||
validateDataRuleExists(id);
|
||||
// 删除
|
||||
dataRuleMapper.deleteById(id);
|
||||
}
|
||||
|
||||
private void validateDataRuleExists(Long id) {
|
||||
if (dataRuleMapper.selectById(id) == null) {
|
||||
throw exception(DATA_RULE_NOT_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验数据流转规则配置
|
||||
*
|
||||
* @param reqVO 数据流转规则保存请求VO
|
||||
*/
|
||||
private void validateDataRuleConfig(IotDataRuleSaveReqVO reqVO) {
|
||||
// 1. 校验数据源配置
|
||||
validateSourceConfigs(reqVO.getSourceConfigs());
|
||||
// 2. 校验数据目的
|
||||
dataSinkService.validateDataSinksExist(reqVO.getSinkIds());
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验数据源配置
|
||||
*
|
||||
* @param sourceConfigs 数据源配置列表
|
||||
*/
|
||||
private void validateSourceConfigs(List<IotDataRuleDO.SourceConfig> sourceConfigs) {
|
||||
// 1. 校验产品
|
||||
productService.validateProductsExist(
|
||||
convertSet(sourceConfigs, IotDataRuleDO.SourceConfig::getProductId));
|
||||
|
||||
// 2. 校验设备
|
||||
deviceService.validateDeviceListExists(convertSet(sourceConfigs, IotDataRuleDO.SourceConfig::getDeviceId,
|
||||
config -> ObjUtil.notEqual(config.getDeviceId(), IotDeviceDO.DEVICE_ID_ALL)));
|
||||
|
||||
// 3. 校验物模型存在
|
||||
validateThingModelsExist(sourceConfigs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验物模型存在
|
||||
*
|
||||
* @param sourceConfigs 数据源配置列表
|
||||
*/
|
||||
private void validateThingModelsExist(List<IotDataRuleDO.SourceConfig> sourceConfigs) {
|
||||
Map<Long, Set<String>> productIdIdentifiers = new HashMap<>();
|
||||
for (IotDataRuleDO.SourceConfig config : sourceConfigs) {
|
||||
if (StrUtil.isEmpty(config.getIdentifier())) {
|
||||
continue;
|
||||
}
|
||||
productIdIdentifiers.computeIfAbsent(config.getProductId(),
|
||||
productId -> new HashSet<>()).add(config.getIdentifier());
|
||||
}
|
||||
for (Map.Entry<Long, Set<String>> entry : productIdIdentifiers.entrySet()) {
|
||||
thingModelService.validateThingModelListExists(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotDataRuleDO getDataRule(Long id) {
|
||||
return dataRuleMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<IotDataRuleDO> getDataRulePage(IotDataRulePageReqVO pageReqVO) {
|
||||
return dataRuleMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IotDataRuleDO> getDataRuleListBySinkId(Long sinkId) {
|
||||
return dataRuleMapper.selectListBySinkId(sinkId);
|
||||
}
|
||||
|
||||
@Cacheable(value = RedisKeyConstants.DATA_RULE_LIST,
|
||||
key = "#deviceId + '_' + #method + '_' + (#identifier ?: '')")
|
||||
public List<IotDataRuleDO> getDataRuleListByConditionFromCache(Long deviceId, String method, String identifier) {
|
||||
// 1. 查询所有开启的数据流转规则
|
||||
List<IotDataRuleDO> rules = dataRuleMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
// 2. 内存里过滤匹配的规则
|
||||
List<IotDataRuleDO> matchedRules = new ArrayList<>();
|
||||
for (IotDataRuleDO rule : rules) {
|
||||
IotDataRuleDO.SourceConfig found = CollUtil.findOne(rule.getSourceConfigs(),
|
||||
config -> ObjectUtils.equalsAny(config.getDeviceId(), deviceId, IotDeviceDO.DEVICE_ID_ALL)
|
||||
&& Objects.equals(config.getMethod(), method)
|
||||
&& (StrUtil.isEmpty(config.getIdentifier()) || ObjUtil.equal(config.getIdentifier(), identifier)));
|
||||
if (found != null) {
|
||||
matchedRules.add(new IotDataRuleDO().setId(rule.getId()).setSinkIds(rule.getSinkIds()));
|
||||
}
|
||||
}
|
||||
return matchedRules;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeDataRule(IotDeviceMessage message) {
|
||||
try {
|
||||
// 1. 获取匹配的数据流转规则
|
||||
Long deviceId = message.getDeviceId();
|
||||
String method = message.getMethod();
|
||||
String identifier = IotDeviceMessageUtils.getIdentifier(message);
|
||||
List<IotDataRuleDO> rules = getSelf().getDataRuleListByConditionFromCache(deviceId, method, identifier);
|
||||
if (CollUtil.isEmpty(rules)) {
|
||||
log.debug("[executeDataRule][设备({}) 方法({}) 标识符({}) 没有匹配的数据流转规则]",
|
||||
deviceId, method, identifier);
|
||||
return;
|
||||
}
|
||||
log.info("[executeDataRule][设备({}) 方法({}) 标识符({}) 匹配到 {} 条数据流转规则]",
|
||||
deviceId, method, identifier, rules.size());
|
||||
|
||||
// 2. 遍历规则,执行数据流转
|
||||
rules.forEach(rule -> executeDataRule(message, rule));
|
||||
} catch (Exception e) {
|
||||
log.error("[executeDataRule][消息({}) 执行数据流转规则异常]", message, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 为指定规则的所有数据目的执行数据流转
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param rule 数据流转规则
|
||||
*/
|
||||
private void executeDataRule(IotDeviceMessage message, IotDataRuleDO rule) {
|
||||
rule.getSinkIds().forEach(sinkId -> {
|
||||
try {
|
||||
// 获取数据目的配置
|
||||
IotDataSinkDO dataSink = dataSinkService.getDataSinkFromCache(sinkId);
|
||||
if (dataSink == null) {
|
||||
log.error("[executeDataRule][规则({}) 对应的数据目的({}) 不存在]", rule.getId(), sinkId);
|
||||
return;
|
||||
}
|
||||
if (CommonStatusEnum.isDisable(dataSink.getStatus())) {
|
||||
log.info("[executeDataRule][规则({}) 对应的数据目的({}) 状态为禁用]", rule.getId(), sinkId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 执行数据桥接操作
|
||||
executeDataRuleAction(message, dataSink);
|
||||
} catch (Exception e) {
|
||||
log.error("[executeDataRule][规则({}) 数据目的({}) 执行异常]", rule.getId(), sinkId, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行数据流转操作
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param dataSink 数据目的
|
||||
*/
|
||||
private void executeDataRuleAction(IotDeviceMessage message, IotDataSinkDO dataSink) {
|
||||
dataRuleActions.forEach(action -> {
|
||||
if (ObjUtil.notEqual(action.getType(), dataSink.getType())) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
action.execute(message, dataSink);
|
||||
log.info("[executeDataRuleAction][消息({}) 数据目的({}) 执行成功]", message.getId(), dataSink.getId());
|
||||
} catch (Exception e) {
|
||||
log.error("[executeDataRuleAction][消息({}) 数据目的({}) 执行异常]", message.getId(), dataSink.getId(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private IotDataRuleServiceImpl getSelf() {
|
||||
return SpringUtils.getBean(IotDataRuleServiceImpl.class);
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user