diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/gemini/GeminiChatModel.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/gemini/GeminiChatModel.java deleted file mode 100644 index 378a0af1fb..0000000000 --- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/gemini/GeminiChatModel.java +++ /dev/null @@ -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 提供的 OpenAI 兼容方案 - * - * @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 stream(Prompt prompt) { - return openAiChatModel.stream(prompt); - } - - @Override - public ChatOptions getDefaultOptions() { - return openAiChatModel.getDefaultOptions(); - } - -} diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/webserch/AiWebSearchClient.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/webserch/AiWebSearchClient.java deleted file mode 100644 index 9fbff556c1..0000000000 --- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/webserch/AiWebSearchClient.java +++ /dev/null @@ -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); - -} diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/webserch/AiWebSearchRequest.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/webserch/AiWebSearchRequest.java deleted file mode 100644 index 9bd2cfef32..0000000000 --- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/webserch/AiWebSearchRequest.java +++ /dev/null @@ -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; - -} diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/webserch/AiWebSearchResponse.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/webserch/AiWebSearchResponse.java deleted file mode 100644 index 8755b32ed0..0000000000 --- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/webserch/AiWebSearchResponse.java +++ /dev/null @@ -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 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; - - } - -} \ No newline at end of file diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/webserch/bocha/AiBoChaWebSearchClient.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/webserch/bocha/AiBoChaWebSearchClient.java deleted file mode 100644 index 7395fe645a..0000000000 --- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/webserch/bocha/AiBoChaWebSearchClient.java +++ /dev/null @@ -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 博查 AI 开放平台 - * - * @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 STATUS_PREDICATE = status -> !status.is2xxSuccessful(); - - private final Function>> 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 response = this.webClient.post() - .uri("/v1/web-search") - .bodyValue(webSearchRequest) - .retrieve() - .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(webSearchRequest)) - .bodyToMono(new ParameterizedTypeReference>() {}) - .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 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 - ) { - } - - } - -} diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/security/core/package-info.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/security/core/package-info.java deleted file mode 100644 index 87969449d8..0000000000 --- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/security/core/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * 占位 - */ -package cn.iocoder.yudao.module.ai.framework.security.core; diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/tool/function/package-info.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/tool/function/package-info.java deleted file mode 100644 index 0b59656352..0000000000 --- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/tool/function/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * 参考 Tool Calling —— Methods as Tools - */ -package cn.iocoder.yudao.module.ai.tool.function; \ No newline at end of file diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/tool/method/Person.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/tool/method/Person.java deleted file mode 100644 index 66bab5a7fc..0000000000 --- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/tool/method/Person.java +++ /dev/null @@ -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 -) { -} \ No newline at end of file diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/tool/method/PersonService.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/tool/method/PersonService.java deleted file mode 100644 index 52c8954945..0000000000 --- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/tool/method/PersonService.java +++ /dev/null @@ -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 getPersonById(int id); - - /** - * Retrieves all Person records currently stored. - * - * @return An unmodifiable List containing all Persons. Returns an empty list if none exist. - */ - List 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 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 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 filterByAge(int age); - -} \ No newline at end of file diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/tool/method/PersonServiceImpl.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/tool/method/PersonServiceImpl.java deleted file mode 100644 index 3b8c31b420..0000000000 --- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/tool/method/PersonServiceImpl.java +++ /dev/null @@ -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 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 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 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 getAllPersons() { - // Return an unmodifiable view of the values - List 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 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 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 filterBySex(String sex) { - if (sex == null || sex.isBlank()) { - log.debug("Filter by sex skipped due to blank filter."); - return Collections.emptyList(); - } - List 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 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 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); - } - -} \ No newline at end of file diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/tool/method/package-info.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/tool/method/package-info.java deleted file mode 100644 index 44b53e1974..0000000000 --- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/tool/method/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * 参考 Tool Calling —— Methods as Tools - */ -package cn.iocoder.yudao.module.ai.tool.method; \ No newline at end of file diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/util/FileTypeUtils.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/util/FileTypeUtils.java deleted file mode 100644 index 9c3b202c4f..0000000000 --- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/util/FileTypeUtils.java +++ /dev/null @@ -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/"); - } - -} diff --git a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/AnthropicChatModelTest.java b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/AnthropicChatModelTest.java deleted file mode 100644 index 454fad47b6..0000000000 --- a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/AnthropicChatModelTest.java +++ /dev/null @@ -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 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 messages = new ArrayList<>(); - messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); - messages.add(new UserMessage("1 + 1 = ?")); - - // 调用 - Flux 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 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 flux = chatModel.stream(new Prompt(messages, options)); - // 打印结果 - flux.doOnNext(response -> { -// System.out.println(response); - System.out.println(response.getResult()); - }).then().block(); - } - -} diff --git a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/GeminiChatModelTests.java b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/GeminiChatModelTests.java deleted file mode 100644 index 964a5f3c36..0000000000 --- a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/GeminiChatModelTests.java +++ /dev/null @@ -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 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 messages = new ArrayList<>(); - messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); - messages.add(new UserMessage("1 + 1 = ?")); - - // 调用 - Flux flux = chatModel.stream(new Prompt(messages)); - // 打印结果 - flux.doOnNext(System.out::println).then().block(); - } - -} diff --git a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/websearch/AiBoChaWebSearchClientTest.java b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/websearch/AiBoChaWebSearchClientTest.java deleted file mode 100644 index 0a02ab589d..0000000000 --- a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/websearch/AiBoChaWebSearchClientTest.java +++ /dev/null @@ -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)); - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceApiImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceApiImpl.java deleted file mode 100644 index add569fb97..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceApiImpl.java +++ /dev/null @@ -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 authDevice(@RequestBody IotDeviceAuthReqDTO authReqDTO) { - return success(deviceService.authDevice(authReqDTO)); - } - - @Override - @PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/get") // 特殊:方便调用,暂时使用 POST,实际更推荐 GET - @PermitAll - public CommonResult 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()); - } - })); - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/IotAlertConfigController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/IotAlertConfigController.java deleted file mode 100644 index b7f3cc1608..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/IotAlertConfigController.java +++ /dev/null @@ -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 createAlertConfig(@Valid @RequestBody IotAlertConfigSaveReqVO createReqVO) { - return success(alertConfigService.createAlertConfig(createReqVO)); - } - - @PutMapping("/update") - @Operation(summary = "更新告警配置") - @PreAuthorize("@ss.hasPermission('iot:alert-config:update')") - public CommonResult 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 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 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> getAlertConfigPage(@Valid IotAlertConfigPageReqVO pageReqVO) { - PageResult pageResult = alertConfigService.getAlertConfigPage(pageReqVO); - - // 转换返回 - Map 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> getAlertConfigSimpleList() { - List list = alertConfigService.getAlertConfigListByStatus(CommonStatusEnum.ENABLE.getStatus()); - return success(convertList(list, config -> // 只返回 id、name 字段 - new IotAlertConfigRespVO().setId(config.getId()).setName(config.getName()))); - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/IotAlertRecordController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/IotAlertRecordController.java deleted file mode 100644 index 7a586fd555..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/IotAlertRecordController.java +++ /dev/null @@ -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 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> getAlertRecordPage(@Valid IotAlertRecordPageReqVO pageReqVO) { - PageResult pageResult = alertRecordService.getAlertRecordPage(pageReqVO); - return success(BeanUtils.toBean(pageResult, IotAlertRecordRespVO.class)); - } - - @PutMapping("/process") - @Operation(summary = "处理告警记录") - @PreAuthorize("@ss.hasPermission('iot:alert-record:process')") - public CommonResult processAlertRecord(@Valid @RequestBody IotAlertRecordProcessReqVO processReqVO) { - alertRecordService.processAlertRecordList(singleton(processReqVO.getId()), processReqVO.getProcessRemark()); - return success(true); - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/config/IotAlertConfigPageReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/config/IotAlertConfigPageReqVO.java deleted file mode 100644 index 0f9a1e9ce1..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/config/IotAlertConfigPageReqVO.java +++ /dev/null @@ -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; - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/config/IotAlertConfigRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/config/IotAlertConfigRespVO.java deleted file mode 100644 index e68a7b7851..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/config/IotAlertConfigRespVO.java +++ /dev/null @@ -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 sceneRuleIds; - - @Schema(description = "接收的用户编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "100,200") - private List receiveUserIds; - - @Schema(description = "接收的用户名称数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三,李四") - private List receiveUserNames; - - @Schema(description = "接收的类型数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3") - private List receiveTypes; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/config/IotAlertConfigSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/config/IotAlertConfigSaveReqVO.java deleted file mode 100644 index 37b99da806..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/config/IotAlertConfigSaveReqVO.java +++ /dev/null @@ -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 sceneRuleIds; - - @Schema(description = "接收的用户编号数组") - @NotEmpty(message = "接收的用户编号数组不能为空") - private List receiveUserIds; - - @Schema(description = "接收的类型数组") - @NotEmpty(message = "接收的类型数组不能为空") - private List receiveTypes; - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/recrod/IotAlertRecordPageReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/recrod/IotAlertRecordPageReqVO.java deleted file mode 100644 index 109f240917..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/recrod/IotAlertRecordPageReqVO.java +++ /dev/null @@ -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; - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/recrod/IotAlertRecordProcessReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/recrod/IotAlertRecordProcessReqVO.java deleted file mode 100644 index e967150a04..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/recrod/IotAlertRecordProcessReqVO.java +++ /dev/null @@ -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; - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/recrod/IotAlertRecordRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/recrod/IotAlertRecordRespVO.java deleted file mode 100644 index 97ccf6cca4..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/recrod/IotAlertRecordRespVO.java +++ /dev/null @@ -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; - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceMessageController.http b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceMessageController.http deleted file mode 100644 index 93c86e146b..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceMessageController.http +++ /dev/null @@ -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}} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceMessageController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceMessageController.java deleted file mode 100644 index 1ed433f409..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceMessageController.java +++ /dev/null @@ -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> getDeviceMessagePage( - @Valid IotDeviceMessagePageReqVO pageReqVO) { - PageResult 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> getDeviceMessagePairPage( - @Valid IotDeviceMessagePageReqVO pageReqVO) { - // 1.1 先按照条件,查询 request 的消息(非 reply) - pageReqVO.setReply(false); - PageResult requestMessagePageResult = deviceMessageService.getDeviceMessagePage(pageReqVO); - if (CollUtil.isEmpty(requestMessagePageResult.getList())) { - return success(PageResult.empty()); - } - // 1.2 接着按照 requestIds,批量查询 reply 消息 - List requestIds = convertList(requestMessagePageResult.getList(), IotDeviceMessageDO::getRequestId); - List replyMessageList = deviceMessageService.getDeviceMessageListByRequestIdsAndReply( - pageReqVO.getDeviceId(), requestIds, true); - Map replyMessages = convertMap(replyMessageList, IotDeviceMessageDO::getRequestId); - - // 2. 组装结果 - List 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 sendDeviceMessage(@Valid @RequestBody IotDeviceMessageSendReqVO sendReqVO) { - deviceMessageService.sendDeviceMessage(BeanUtils.toBean(sendReqVO, IotDeviceMessage.class)); - return success(true); - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceAuthInfoRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceAuthInfoRespVO.java deleted file mode 100644 index 18009bd1fb..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceAuthInfoRespVO.java +++ /dev/null @@ -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; - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceByProductKeyAndNamesReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceByProductKeyAndNamesReqVO.java deleted file mode 100644 index 5fc66f321d..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceByProductKeyAndNamesReqVO.java +++ /dev/null @@ -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 deviceNames; - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/message/IotDeviceMessagePageReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/message/IotDeviceMessagePageReqVO.java deleted file mode 100644 index 07da966060..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/message/IotDeviceMessagePageReqVO.java +++ /dev/null @@ -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; - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/message/IotDeviceMessageRespPairVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/message/IotDeviceMessageRespPairVO.java deleted file mode 100644 index 119dd02777..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/message/IotDeviceMessageRespPairVO.java +++ /dev/null @@ -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 配对 - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/message/IotDeviceMessageRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/message/IotDeviceMessageRespVO.java deleted file mode 100644 index e53f5acb60..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/message/IotDeviceMessageRespVO.java +++ /dev/null @@ -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; - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/message/IotDeviceMessageSendReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/message/IotDeviceMessageSendReqVO.java deleted file mode 100644 index da88eb8d1d..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/message/IotDeviceMessageSendReqVO.java +++ /dev/null @@ -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; - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/property/IotDevicePropertyDetailRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/property/IotDevicePropertyDetailRespVO.java deleted file mode 100644 index 57712691f8..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/property/IotDevicePropertyDetailRespVO.java +++ /dev/null @@ -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 dataSpecsList; - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaTaskController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaTaskController.java deleted file mode 100644 index 6e5b4d1039..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaTaskController.java +++ /dev/null @@ -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 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 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> getOtaTaskPage(@Valid IotOtaTaskPageReqVO pageReqVO) { - PageResult 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 getOtaTask(@RequestParam("id") Long id) { - IotOtaTaskDO upgradeTask = otaTaskService.getOtaTask(id); - return success(BeanUtils.toBean(upgradeTask, IotOtaTaskRespVO.class)); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaTaskRecordController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaTaskRecordController.java deleted file mode 100644 index da4dc61985..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaTaskRecordController.java +++ /dev/null @@ -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> 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> getOtaTaskRecordPage( - @Valid IotOtaTaskRecordPageReqVO pageReqVO) { - PageResult pageResult = otaTaskRecordService.getOtaTaskRecordPage(pageReqVO); - if (CollUtil.isEmpty(pageResult.getList())) { - return success(PageResult.empty()); - } - - // 批量查询固件信息 - Map firmwareMap = otaFirmwareService.getOtaFirmwareMap( - convertSet(pageResult.getList(), IotOtaTaskRecordDO::getFromFirmwareId)); - Map 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 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 cancelOtaTaskRecord(@RequestParam("id") Long id) { - otaTaskRecordService.cancelOtaTaskRecord(id); - return success(true); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/task/IotOtaTaskPageReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/task/IotOtaTaskPageReqVO.java deleted file mode 100644 index 4638f1a401..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/task/IotOtaTaskPageReqVO.java +++ /dev/null @@ -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; - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/task/IotOtaTaskRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/task/IotOtaTaskRespVO.java deleted file mode 100644 index 247f7c658f..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/task/IotOtaTaskRespVO.java +++ /dev/null @@ -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; - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/task/record/IotOtaTaskRecordPageReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/task/record/IotOtaTaskRecordPageReqVO.java deleted file mode 100644 index 00c6fe7f32..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/task/record/IotOtaTaskRecordPageReqVO.java +++ /dev/null @@ -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; - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/task/record/IotOtaTaskRecordRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/task/record/IotOtaTaskRecordRespVO.java deleted file mode 100644 index f7ab1edf58..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/task/record/IotOtaTaskRecordRespVO.java +++ /dev/null @@ -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; - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/IotDataRuleController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/IotDataRuleController.java deleted file mode 100644 index 2f7aa7e40b..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/IotDataRuleController.java +++ /dev/null @@ -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 createDataRule(@Valid @RequestBody IotDataRuleSaveReqVO createReqVO) { - return success(dataRuleService.createDataRule(createReqVO)); - } - - @PutMapping("/update") - @Operation(summary = "更新数据流转规则") - @PreAuthorize("@ss.hasPermission('iot:data-rule:update')") - public CommonResult 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 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 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> getDataRulePage(@Valid IotDataRulePageReqVO pageReqVO) { - PageResult pageResult = dataRuleService.getDataRulePage(pageReqVO); - return success(BeanUtils.toBean(pageResult, IotDataRuleRespVO.class)); - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/IotDataSinkController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/IotDataSinkController.java deleted file mode 100644 index b985f2b0ba..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/IotDataSinkController.java +++ /dev/null @@ -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 createDataSink(@Valid @RequestBody IotDataSinkSaveReqVO createReqVO) { - return success(dataSinkService.createDataSink(createReqVO)); - } - - @PutMapping("/update") - @Operation(summary = "更新数据目的") - @PreAuthorize("@ss.hasPermission('iot:data-sink:update')") - public CommonResult 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 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 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> getDataSinkPage(@Valid IotDataSinkPageReqVO pageReqVO) { - PageResult pageResult = dataSinkService.getDataSinkPage(pageReqVO); - return success(BeanUtils.toBean(pageResult, IotDataSinkRespVO.class)); - } - - @GetMapping("/simple-list") - @Operation(summary = "获取数据目的的精简信息列表", description = "主要用于前端的下拉选项") - public CommonResult> getDataSinkSimpleList() { - List list = dataSinkService.getDataSinkListByStatus(CommonStatusEnum.ENABLE.getStatus()); - return success(convertList(list, sink -> // 只返回 id、name 字段 - new IotDataSinkRespVO().setId(sink.getId()).setName(sink.getName()))); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/IotSceneRuleController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/IotSceneRuleController.java deleted file mode 100644 index a5a8a8aa75..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/IotSceneRuleController.java +++ /dev/null @@ -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 createSceneRule(@Valid @RequestBody IotSceneRuleSaveReqVO createReqVO) { - return success(sceneRuleService.createSceneRule(createReqVO)); - } - - @PutMapping("/update") - @Operation(summary = "更新场景联动") - @PreAuthorize("@ss.hasPermission('iot:scene-rule:update')") - public CommonResult 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 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 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 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> getSceneRulePage(@Valid IotSceneRulePageReqVO pageReqVO) { - PageResult pageResult = sceneRuleService.getSceneRulePage(pageReqVO); - return success(BeanUtils.toBean(pageResult, IotSceneRuleRespVO.class)); - } - - @GetMapping("/simple-list") - @Operation(summary = "获取场景联动的精简信息列表", description = "主要用于前端的下拉选项") - public CommonResult> getSceneRuleSimpleList() { - List list = sceneRuleService.getSceneRuleListByStatus(CommonStatusEnum.ENABLE.getStatus()); - return success(convertList(list, scene -> // 只返回 id、name 字段 - new IotSceneRuleRespVO().setId(scene.getId()).setName(scene.getName()))); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/package-info.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/package-info.java deleted file mode 100644 index 6be90cf325..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/package-info.java +++ /dev/null @@ -1 +0,0 @@ -package cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data; \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/rule/IotDataRulePageReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/rule/IotDataRulePageReqVO.java deleted file mode 100644 index 8e21c7992c..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/rule/IotDataRulePageReqVO.java +++ /dev/null @@ -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; - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/rule/IotDataRuleRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/rule/IotDataRuleRespVO.java deleted file mode 100644 index 3427370f7c..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/rule/IotDataRuleRespVO.java +++ /dev/null @@ -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 sourceConfigs; - - @Schema(description = "数据目的编号数组", requiredMode = Schema.RequiredMode.REQUIRED) - private List sinkIds; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/rule/IotDataRuleSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/rule/IotDataRuleSaveReqVO.java deleted file mode 100644 index 12b7056827..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/rule/IotDataRuleSaveReqVO.java +++ /dev/null @@ -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 sourceConfigs; - - @Schema(description = "数据目的编号数组", requiredMode = Schema.RequiredMode.REQUIRED) - @NotEmpty(message = "数据目的编号数组不能为空") - private List sinkIds; - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/sink/IotDataSinkRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/sink/IotDataSinkRespVO.java deleted file mode 100644 index 0ced03c225..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/sink/IotDataSinkRespVO.java +++ /dev/null @@ -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; - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/sink/IotDataSinkSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/sink/IotDataSinkSaveReqVO.java deleted file mode 100644 index c0d5bc544d..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/sink/IotDataSinkSaveReqVO.java +++ /dev/null @@ -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; - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRulePageReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRulePageReqVO.java deleted file mode 100644 index 8345004b67..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRulePageReqVO.java +++ /dev/null @@ -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; - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleRespVO.java deleted file mode 100644 index 835ef62933..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleRespVO.java +++ /dev/null @@ -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 triggers; - - @Schema(description = "执行器数组", requiredMode = Schema.RequiredMode.REQUIRED) - private List actions; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleSaveReqVO.java deleted file mode 100644 index 8b208d6565..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleSaveReqVO.java +++ /dev/null @@ -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 triggers; - - @Schema(description = "执行器数组", requiredMode = Schema.RequiredMode.REQUIRED) - @NotEmpty(message = "执行器数组不能为空") - private List actions; - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleUpdateStatusReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleUpdateStatusReqVO.java deleted file mode 100644 index 9ba4f81691..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleUpdateStatusReqVO.java +++ /dev/null @@ -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; - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/statistics/IotStatisticsController.http b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/statistics/IotStatisticsController.http deleted file mode 100644 index b8cb6b544f..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/statistics/IotStatisticsController.http +++ /dev/null @@ -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}} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/statistics/vo/IotStatisticsDeviceMessageReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/statistics/vo/IotStatisticsDeviceMessageReqVO.java deleted file mode 100644 index 92c9477c1b..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/statistics/vo/IotStatisticsDeviceMessageReqVO.java +++ /dev/null @@ -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; - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/statistics/vo/IotStatisticsDeviceMessageSummaryByDateRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/statistics/vo/IotStatisticsDeviceMessageSummaryByDateRespVO.java deleted file mode 100644 index 9c605dd341..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/statistics/vo/IotStatisticsDeviceMessageSummaryByDateRespVO.java +++ /dev/null @@ -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; - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/vo/IotThingModelTSLRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/vo/IotThingModelTSLRespVO.java deleted file mode 100644 index d3809d8819..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/vo/IotThingModelTSLRespVO.java +++ /dev/null @@ -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 properties; - - @Schema(description = "服务列表", requiredMode = Schema.RequiredMode.REQUIRED) - private List events; - - @Schema(description = "事件列表", requiredMode = Schema.RequiredMode.REQUIRED) - private List services; - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceMessageDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceMessageDO.java deleted file mode 100644 index 9f1f6a6a0c..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceMessageDO.java +++ /dev/null @@ -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; - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/ota/IotOtaTaskDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/ota/IotOtaTaskDO.java deleted file mode 100644 index 4c9124b89f..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/ota/IotOtaTaskDO.java +++ /dev/null @@ -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; - - /** - * 固件编号 - *

- * 关联 {@link IotOtaFirmwareDO#getId()} - */ - private Long firmwareId; - - /** - * 任务状态 - *

- * 关联 {@link IotOtaTaskStatusEnum} - */ - private Integer status; - - /** - * 设备升级范围 - *

- * 关联 {@link IotOtaTaskDeviceScopeEnum} - */ - private Integer deviceScope; - /** - * 设备总数数量 - */ - private Integer deviceTotalCount; - /** - * 设备成功数量 - */ - private Integer deviceSuccessCount; - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/ota/IotOtaTaskRecordDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/ota/IotOtaTaskRecordDO.java deleted file mode 100644 index d99a1bb60a..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/ota/IotOtaTaskRecordDO.java +++ /dev/null @@ -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; - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/IotDataRuleDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/IotDataRuleDO.java deleted file mode 100644 index 6d679237e5..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/IotDataRuleDO.java +++ /dev/null @@ -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 sourceConfigs; - /** - * 数据目的编号数组 - * - * 关联 {@link IotDataSinkDO#getId()} - */ - @TableField(typeHandler = LongListTypeHandler.class) - private List 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; - - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/IotSceneRuleDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/IotSceneRuleDO.java deleted file mode 100644 index 94aa1eb5a3..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/IotSceneRuleDO.java +++ /dev/null @@ -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 triggers; - - /** - * 场景动作配置 - */ - @TableField(typeHandler = JacksonTypeHandler.class) - private List 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; - /** - * 参数(属性值、在线状态) - *

- * 如果有多个值,则使用 "," 分隔,类似 "1,2,3"。 - * 例如说,{@link IotSceneRuleConditionOperatorEnum#IN}、{@link IotSceneRuleConditionOperatorEnum#BETWEEN} - */ - private String value; - - /** - * CRON 表达式 - */ - private String cronExpression; - - // ========== 条件部分 ========== - - /** - * 触发条件分组(状态条件分组)的数组 - *

- * 第一层 List:分组与分组之间,是“或”的关系 - * 第二层 List:条件与条件之间,是“且”的关系 - */ - private List> 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; - - /** - * 标识符(服务) - *

- * 关联 {@link IotThingModelDO#getIdentifier()} - */ - private String identifier; - - /** - * 请求参数 - * - * 一般来说,对应 {@link IotDeviceMessage#getParams()} 请求参数 - */ - private String params; - - /** - * 告警配置编号 - * - * 关联 {@link IotAlertConfigDO#getId()} - */ - private Long alertConfigId; - - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotAbstractDataSinkConfig.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotAbstractDataSinkConfig.java deleted file mode 100644 index 68a8fd699b..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotAbstractDataSinkConfig.java +++ /dev/null @@ -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; - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkRedisConfig.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkRedisConfig.java deleted file mode 100644 index 07460ac368..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkRedisConfig.java +++ /dev/null @@ -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 数据结构类型 - *

- * 枚举 {@link IotRedisDataStructureEnum} - */ - @InEnum(IotRedisDataStructureEnum.class) - private Integer dataStructure; - - /** - * 主题/键名 - *

- * 对于不同的数据结构: - * - Stream: 流的键名 - * - Hash: Hash 的键名 - * - List: 列表的键名 - * - Set: 集合的键名 - * - ZSet: 有序集合的键名 - * - String: 字符串的键名 - */ - private String topic; - - /** - * Hash 字段名(仅当 dataStructure 为 HASH 时使用) - */ - private String hashField; - - /** - * ZSet 分数字段(仅当 dataStructure 为 ZSET 时使用) - * 指定消息中哪个字段作为分数,如果不指定则使用当前时间戳 - */ - private String scoreField; - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/model/dataType/ThingModelBoolOrEnumDataSpecs.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/model/dataType/ThingModelBoolOrEnumDataSpecs.java deleted file mode 100644 index fa8b1ffab3..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/model/dataType/ThingModelBoolOrEnumDataSpecs.java +++ /dev/null @@ -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; - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alert/IotAlertConfigMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alert/IotAlertConfigMapper.java deleted file mode 100644 index c5d7154ff6..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alert/IotAlertConfigMapper.java +++ /dev/null @@ -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 { - - default PageResult selectPage(IotAlertConfigPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .likeIfPresent(IotAlertConfigDO::getName, reqVO.getName()) - .eqIfPresent(IotAlertConfigDO::getStatus, reqVO.getStatus()) - .betweenIfPresent(IotAlertConfigDO::getCreateTime, reqVO.getCreateTime()) - .orderByDesc(IotAlertConfigDO::getId)); - } - - default List selectListByStatus(Integer status) { - return selectList(IotAlertConfigDO::getStatus, status); - } - - default List selectListBySceneRuleIdAndStatus(Long sceneRuleId, Integer status) { - return selectList(new LambdaQueryWrapperX() - .eq(IotAlertConfigDO::getStatus, status) - .apply(MyBatisUtils.findInSet("scene_rule_id", sceneRuleId))); - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alert/IotAlertRecordMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alert/IotAlertRecordMapper.java deleted file mode 100644 index f23fe60f74..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alert/IotAlertRecordMapper.java +++ /dev/null @@ -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 { - - default PageResult selectPage(IotAlertRecordPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .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 selectListBySceneRuleId(Long sceneRuleId, Long deviceId, Boolean processStatus) { - return selectList(new LambdaQueryWrapperX() - .eq(IotAlertRecordDO::getSceneRuleId, sceneRuleId) - .eqIfPresent(IotAlertRecordDO::getDeviceId, deviceId) - .eqIfPresent(IotAlertRecordDO::getProcessStatus, processStatus) - .orderByDesc(IotAlertRecordDO::getId)); - } - - default int updateList(Collection ids, IotAlertRecordDO updateObj) { - return update(updateObj, new LambdaUpdateWrapper() - .in(IotAlertRecordDO::getId, ids)); - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/ota/IotOtaTaskMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/ota/IotOtaTaskMapper.java deleted file mode 100644 index cf73231234..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/ota/IotOtaTaskMapper.java +++ /dev/null @@ -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 { - - default IotOtaTaskDO selectByFirmwareIdAndName(Long firmwareId, String name) { - return selectOne(IotOtaTaskDO::getFirmwareId, firmwareId, - IotOtaTaskDO::getName, name); - } - - default PageResult selectPage(IotOtaTaskPageReqVO pageReqVO) { - return selectPage(pageReqVO, new LambdaQueryWrapperX() - .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() - .eq(IotOtaTaskDO::getId, id) - .eq(IotOtaTaskDO::getStatus, whereStatus)); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/ota/IotOtaTaskRecordMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/ota/IotOtaTaskRecordMapper.java deleted file mode 100644 index 017adc9192..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/ota/IotOtaTaskRecordMapper.java +++ /dev/null @@ -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 { - - default List selectListByFirmwareIdAndTaskId(Long firmwareId, Long taskId) { - return selectList(new LambdaQueryWrapperX() - .eqIfPresent(IotOtaTaskRecordDO::getFirmwareId, firmwareId) - .eqIfPresent(IotOtaTaskRecordDO::getTaskId, taskId) - .select(IotOtaTaskRecordDO::getDeviceId, IotOtaTaskRecordDO::getStatus)); - } - - default PageResult selectPage(IotOtaTaskRecordPageReqVO pageReqVO) { - return selectPage(pageReqVO, new LambdaQueryWrapperX() - .eqIfPresent(IotOtaTaskRecordDO::getTaskId, pageReqVO.getTaskId()) - .eqIfPresent(IotOtaTaskRecordDO::getStatus, pageReqVO.getStatus())); - } - - default List selectListByTaskIdAndStatus(Long taskId, Collection statuses) { - return selectList(new LambdaQueryWrapperX() - .eq(IotOtaTaskRecordDO::getTaskId, taskId) - .in(IotOtaTaskRecordDO::getStatus, statuses)); - } - - default Long selectCountByTaskIdAndStatus(Long taskId, Collection statuses) { - return selectCount(new LambdaQueryWrapperX() - .eq(IotOtaTaskRecordDO::getTaskId, taskId) - .in(IotOtaTaskRecordDO::getStatus, statuses)); - } - - default int updateByIdAndStatus(Long id, Integer status, - IotOtaTaskRecordDO updateObj) { - return update(updateObj, new LambdaUpdateWrapper() - .eq(IotOtaTaskRecordDO::getId, id) - .eq(IotOtaTaskRecordDO::getStatus, status)); - } - - default int updateByIdAndStatus(Long id, Collection whereStatuses, - IotOtaTaskRecordDO updateObj) { - return update(updateObj, new LambdaUpdateWrapper() - .eq(IotOtaTaskRecordDO::getId, id) - .in(IotOtaTaskRecordDO::getStatus, whereStatuses)); - } - - default void updateListByIdAndStatus(Collection ids, Collection whereStatuses, - IotOtaTaskRecordDO updateObj) { - update(updateObj, new LambdaUpdateWrapper() - .in(IotOtaTaskRecordDO::getId, ids) - .in(IotOtaTaskRecordDO::getStatus, whereStatuses)); - } - - default List selectListByDeviceIdAndStatus(Set deviceIds, Set statuses) { - return selectList(new LambdaQueryWrapperX() - .inIfPresent(IotOtaTaskRecordDO::getDeviceId, deviceIds) - .inIfPresent(IotOtaTaskRecordDO::getStatus, statuses)); - } - - default List selectListByDeviceIdAndStatus(Long deviceId, Set statuses) { - return selectList(new LambdaQueryWrapperX() - .eqIfPresent(IotOtaTaskRecordDO::getDeviceId, deviceId) - .inIfPresent(IotOtaTaskRecordDO::getStatus, statuses)); - } - - default List selectListByStatus(Integer status) { - return selectList(IotOtaTaskRecordDO::getStatus, status); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotDataRuleMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotDataRuleMapper.java deleted file mode 100644 index 7c0c17d3bc..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotDataRuleMapper.java +++ /dev/null @@ -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 { - - default PageResult selectPage(IotDataRulePageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .likeIfPresent(IotDataRuleDO::getName, reqVO.getName()) - .eqIfPresent(IotDataRuleDO::getStatus, reqVO.getStatus()) - .betweenIfPresent(IotDataRuleDO::getCreateTime, reqVO.getCreateTime()) - .orderByDesc(IotDataRuleDO::getId)); - } - - default List selectListBySinkId(Long sinkId) { - return selectList(new LambdaQueryWrapperX() - .apply(MyBatisUtils.findInSet("sink_ids", sinkId))); - } - - default List selectListByStatus(Integer status) { - return selectList(IotDataRuleDO::getStatus, status); - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotDataSinkMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotDataSinkMapper.java deleted file mode 100644 index e65001db86..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotDataSinkMapper.java +++ /dev/null @@ -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 { - - default PageResult selectPage(IotDataSinkPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .likeIfPresent(IotDataSinkDO::getName, reqVO.getName()) - .eqIfPresent(IotDataSinkDO::getStatus, reqVO.getStatus()) - .betweenIfPresent(IotDataSinkDO::getCreateTime, reqVO.getCreateTime()) - .orderByDesc(IotDataSinkDO::getId)); - } - - default List selectListByStatus(Integer status) { - return selectList(IotDataSinkDO::getStatus, status); - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotSceneRuleMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotSceneRuleMapper.java deleted file mode 100644 index 4fd6490d15..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotSceneRuleMapper.java +++ /dev/null @@ -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 { - - default PageResult selectPage(IotSceneRulePageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .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 selectListByStatus(Integer status) { - return selectList(IotSceneRuleDO::getStatus, status); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/device/DeviceServerIdRedisDAO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/device/DeviceServerIdRedisDAO.java deleted file mode 100644 index 864ba80788..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/device/DeviceServerIdRedisDAO.java +++ /dev/null @@ -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; - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDeviceMessageMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDeviceMessageMapper.java deleted file mode 100644 index b09895fd36..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDeviceMessageMapper.java +++ /dev/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 selectPage(IPage page, - @Param("reqVO") IotDeviceMessagePageReqVO reqVO); - - /** - * 统计设备消息数量 - * - * @param createTime 创建时间,如果为空,则统计所有消息数量 - * @return 消息数量 - */ - Long selectCountByCreateTime(@Param("createTime") Long createTime); - - /** - * 按照 requestIds 批量查询消息 - * - * @param deviceId 设备编号 - * @param requestIds 请求编号集合 - * @param reply 是否回复消息 - * @return 消息列表 - */ - List selectListByRequestIdsAndReply(@Param("deviceId") Long deviceId, - @Param("requestIds") Collection requestIds, - @Param("reply") Boolean reply); - - /** - * 按照时间范围(小时),统计设备的消息数量 - */ - List> selectDeviceMessageCountGroupByDate(@Param("startTime") Long startTime, - @Param("endTime") Long endTime); - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/DictTypeConstants.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/DictTypeConstants.java deleted file mode 100644 index 4f07ddfc1c..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/DictTypeConstants.java +++ /dev/null @@ -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"; - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java deleted file mode 100644 index d1cf60e206..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java +++ /dev/null @@ -1,82 +0,0 @@ -package cn.iocoder.yudao.module.iot.enums; - -import cn.iocoder.yudao.framework.common.exception.ErrorCode; - -/** - * iot 错误码枚举类 - *

- * 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 告警记录不存在"); - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ota/IotOtaTaskRecordStatusEnum.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ota/IotOtaTaskRecordStatusEnum.java deleted file mode 100644 index 0f95eb79cc..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ota/IotOtaTaskRecordStatusEnum.java +++ /dev/null @@ -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 { - - 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 IN_PROCESS_STATUSES = SetUtils.asSet( - PENDING.getStatus(), - PUSHED.getStatus(), - UPGRADING.getStatus()); - - public static final List 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()); - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotDataSinkTypeEnum.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotDataSinkTypeEnum.java deleted file mode 100644 index 45a557db61..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotDataSinkTypeEnum.java +++ /dev/null @@ -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 { - - 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; - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleActionTypeEnum.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleActionTypeEnum.java deleted file mode 100644 index 7e9e4de631..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleActionTypeEnum.java +++ /dev/null @@ -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 { - - /** - * 设备属性设置 - * - * 对应 {@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; - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleConditionTypeEnum.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleConditionTypeEnum.java deleted file mode 100644 index 81d7e6e1f5..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleConditionTypeEnum.java +++ /dev/null @@ -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 { - - 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()); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleTriggerTypeEnum.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleTriggerTypeEnum.java deleted file mode 100644 index bfc84c9f60..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleTriggerTypeEnum.java +++ /dev/null @@ -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 { - - // 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()); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/iot/config/YudaoIotProperties.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/iot/config/YudaoIotProperties.java deleted file mode 100644 index 07473c0293..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/iot/config/YudaoIotProperties.java +++ /dev/null @@ -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; - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/iot/package-info.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/iot/package-info.java deleted file mode 100644 index 0930a1409c..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/iot/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * iot 模块的【全局】拓展封装 - */ -package cn.iocoder.yudao.module.iot.framework.iot; \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/ota/IotOtaUpgradeJob.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/ota/IotOtaUpgradeJob.java deleted file mode 100644 index 05d898be35..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/ota/IotOtaUpgradeJob.java +++ /dev/null @@ -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 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 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); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/rule/IotSceneRuleJob.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/rule/IotSceneRuleJob.java deleted file mode 100644 index 871623ef48..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/rule/IotSceneRuleJob.java +++ /dev/null @@ -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 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); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/device/IotDeviceMessageSubscriber.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/device/IotDeviceMessageSubscriber.java deleted file mode 100644 index 409c12d5f9..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/device/IotDeviceMessageSubscriber.java +++ /dev/null @@ -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 { - - @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); - } - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotDataRuleMessageHandler.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotDataRuleMessageHandler.java deleted file mode 100644 index 5fa281e788..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotDataRuleMessageHandler.java +++ /dev/null @@ -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 { - - @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)); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotSceneRuleMessageHandler.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotSceneRuleMessageHandler.java deleted file mode 100644 index 278cd775e9..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotSceneRuleMessageHandler.java +++ /dev/null @@ -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 { - - @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); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/alert/IotAlertConfigService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/alert/IotAlertConfigService.java deleted file mode 100644 index 859aedd977..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/alert/IotAlertConfigService.java +++ /dev/null @@ -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 getAlertConfigPage(IotAlertConfigPageReqVO pageReqVO); - - /** - * 获得告警配置列表 - * - * @param status 状态 - * @return 告警配置列表 - */ - List getAlertConfigListByStatus(Integer status); - - /** - * 获得告警配置列表 - * - * @param sceneRuleId 场景流动规则编号 - * @return 告警配置列表 - */ - List getAlertConfigListBySceneRuleIdAndStatus(Long sceneRuleId, Integer status); - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/alert/IotAlertConfigServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/alert/IotAlertConfigServiceImpl.java deleted file mode 100644 index ea59321d6d..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/alert/IotAlertConfigServiceImpl.java +++ /dev/null @@ -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 getAlertConfigPage(IotAlertConfigPageReqVO pageReqVO) { - return alertConfigMapper.selectPage(pageReqVO); - } - - @Override - public List getAlertConfigListByStatus(Integer status) { - return alertConfigMapper.selectListByStatus(status); - } - - @Override - public List getAlertConfigListBySceneRuleIdAndStatus(Long sceneRuleId, Integer status) { - return alertConfigMapper.selectListBySceneRuleIdAndStatus(sceneRuleId, status); - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/alert/IotAlertRecordService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/alert/IotAlertRecordService.java deleted file mode 100644 index 3144a31e67..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/alert/IotAlertRecordService.java +++ /dev/null @@ -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 getAlertRecordPage(IotAlertRecordPageReqVO pageReqVO); - - /** - * 获得指定场景规则的告警记录列表 - * - * @param sceneRuleId 场景规则编号 - * @param deviceId 设备编号 - * @param processStatus 处理状态,允许空 - * @return 告警记录列表 - */ - List getAlertRecordListBySceneRuleId(@NotNull(message = "场景规则编号不能为空") Long sceneRuleId, - Long deviceId, Boolean processStatus); - - /** - * 处理告警记录 - * - * @param ids 告警记录编号 - * @param remark 处理结果(备注) - */ - void processAlertRecordList(Collection ids, String remark); - - /** - * 创建告警记录(包含场景规则编号) - * - * @param config 告警配置 - * @param sceneRuleId 场景规则编号 - * @param deviceMessage 设备消息,可为空 - * @return 告警记录编号 - */ - Long createAlertRecord(IotAlertConfigDO config, Long sceneRuleId, IotDeviceMessage deviceMessage); - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/alert/IotAlertRecordServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/alert/IotAlertRecordServiceImpl.java deleted file mode 100644 index d77ec44a10..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/alert/IotAlertRecordServiceImpl.java +++ /dev/null @@ -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 getAlertRecordPage(IotAlertRecordPageReqVO pageReqVO) { - return alertRecordMapper.selectPage(pageReqVO); - } - - @Override - public List getAlertRecordListBySceneRuleId(Long sceneRuleId, Long deviceId, Boolean processStatus) { - return alertRecordMapper.selectListBySceneRuleId(sceneRuleId, deviceId, processStatus); - } - - @Override - public void processAlertRecordList(Collection 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(); - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/message/IotDeviceMessageService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/message/IotDeviceMessageService.java deleted file mode 100644 index 55c2b10b26..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/message/IotDeviceMessageService.java +++ /dev/null @@ -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 getDeviceMessagePage(IotDeviceMessagePageReqVO pageReqVO); - - /** - * 获得指定 requestId 的设备消息列表 - * - * @param deviceId 设备编号 - * @param requestIds requestId 列表 - * @param reply 是否回复 - * @return 设备消息列表 - */ - List getDeviceMessageListByRequestIdsAndReply( - @NotNull(message = "设备编号不能为空") Long deviceId, - @NotEmpty(message = "请求编号不能为空") List requestIds, - Boolean reply); - - /** - * 获得设备消息数量 - * - * @param createTime 创建时间,如果为空,则统计所有消息数量 - * @return 消息数量 - */ - Long getDeviceMessageCount(@Nullable LocalDateTime createTime); - - /** - * 获取设备消息的数据统计 - * - * @param reqVO 统计请求 - * @return 设备消息的数据统计 - */ - List getDeviceMessageSummaryByDate( - IotStatisticsDeviceMessageReqVO reqVO); - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/message/IotDeviceMessageServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/message/IotDeviceMessageServiceImpl.java deleted file mode 100644 index 6cd3f772b5..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/message/IotDeviceMessageServiceImpl.java +++ /dev/null @@ -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 getDeviceMessagePage(IotDeviceMessagePageReqVO pageReqVO) { - try { - IPage 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 getDeviceMessageListByRequestIdsAndReply(Long deviceId, - List 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 getDeviceMessageSummaryByDate( - IotStatisticsDeviceMessageReqVO reqVO) { - // 1. 按小时统计,获取分项统计数据 - List> countList = deviceMessageMapper.selectDeviceMessageCountGroupByDate( - LocalDateTimeUtil.toEpochMilli(reqVO.getTimes()[0]), - LocalDateTimeUtil.toEpochMilli(reqVO.getTimes()[1])); - - // 2. 按照日期间隔,合并数据 - List 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()); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/property/IotDevicePropertyService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/property/IotDevicePropertyService.java deleted file mode 100644 index 80754cccc8..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/property/IotDevicePropertyService.java +++ /dev/null @@ -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 getLatestDeviceProperties(Long deviceId); - - /** - * 获得设备属性历史数据 - * - * @param listReqVO 列表请求 - * @return 设备属性历史数据 - */ - List getHistoryDevicePropertyList(@Valid IotDevicePropertyHistoryListReqVO listReqVO); - - // ========== 设备时间相关操作 ========== - - /** - * 获得最后上报时间小于指定时间的设备编号集合 - * - * @param maxReportTime 最大上报时间 - * @return 设备编号集合 - */ - Set 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); - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskRecordService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskRecordService.java deleted file mode 100644 index df2c93ea1a..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskRecordService.java +++ /dev/null @@ -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 devices, Long firmwareId, Long taskId); - - /** - * 获取 OTA 升级记录的状态统计 - * - * @param firmwareId 固件编号 - * @param taskId 任务编号 - * @return 状态统计 Map,key 为状态码,value 为对应状态的升级记录数量 - */ - Map getOtaTaskRecordStatusStatistics(Long firmwareId, Long taskId); - - /** - * 获取 OTA 升级记录 - * - * @param id 编号 - * @return OTA 升级记录 - */ - IotOtaTaskRecordDO getOtaTaskRecord(Long id); - - /** - * 获取 OTA 升级记录分页 - * - * @param pageReqVO 分页查询 - * @return OTA 升级记录分页 - */ - PageResult getOtaTaskRecordPage(@Valid IotOtaTaskRecordPageReqVO pageReqVO); - - /** - * 根据 OTA 任务编号,取消未结束的升级记录 - * - * @param taskId 升级任务编号 - */ - void cancelTaskRecordListByTaskId(Long taskId); - - /** - * 根据设备编号和记录状态,获取 OTA 升级记录列表 - * - * @param deviceIds 设备编号集合 - * @param statuses 记录状态集合 - * @return OTA 升级记录列表 - */ - List getOtaTaskRecordListByDeviceIdAndStatus(Set deviceIds, Set statuses); - - /** - * 根据记录状态,获取 OTA 升级记录列表 - * - * @param status 升级记录状态 - * @return 升级记录列表 - */ - List 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); - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskRecordServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskRecordServiceImpl.java deleted file mode 100644 index 94b8bd8f57..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskRecordServiceImpl.java +++ /dev/null @@ -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 devices, Long firmwareId, Long taskId) { - List 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 getOtaTaskRecordStatusStatistics(Long firmwareId, Long taskId) { - // 按照 status 枚举,初始化 countMap 为 0 - Map countMap = convertMap(Arrays.asList(IotOtaTaskRecordStatusEnum.values()), - IotOtaTaskRecordStatusEnum::getStatus, iotOtaTaskRecordStatusEnum -> 0L); - - // 查询记录,只返回 id、status 字段 - List records = otaTaskRecordMapper.selectListByFirmwareIdAndTaskId(firmwareId, taskId); - Map> 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 getOtaTaskRecordPage(IotOtaTaskRecordPageReqVO pageReqVO) { - return otaTaskRecordMapper.selectPage(pageReqVO); - } - - @Override - public void cancelTaskRecordListByTaskId(Long taskId) { - List records = otaTaskRecordMapper.selectListByTaskIdAndStatus( - taskId, IotOtaTaskRecordStatusEnum.IN_PROCESS_STATUSES); - if (CollUtil.isEmpty(records)) { - return; - } - // 批量更新 - Collection 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 getOtaTaskRecordListByDeviceIdAndStatus(Set deviceIds, Set statuses) { - return otaTaskRecordMapper.selectListByDeviceIdAndStatus(deviceIds, statuses); - } - - @Override - public List 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 params = (Map) 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 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); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskService.java deleted file mode 100644 index 86ef505c13..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskService.java +++ /dev/null @@ -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 getOtaTaskPage(@Valid IotOtaTaskPageReqVO pageReqVO); - - /** - * 更新 OTA 任务状态为已结束 - * - * @param id 任务编号 - */ - void updateOtaTaskStatusEnd(Long id); - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskServiceImpl.java deleted file mode 100644 index 2072b3ec70..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskServiceImpl.java +++ /dev/null @@ -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 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 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 validateOtaTaskDeviceScope(IotOtaTaskCreateReqVO createReqVO, Long productId) { - // 情况一:选择设备 - if (Objects.equals(createReqVO.getDeviceScope(), IotOtaTaskDeviceScopeEnum.SELECT.getScope())) { - // 1.1 校验设备存在 - List 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 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 devices = deviceService.getDeviceListByProductId(productId); - // 2.1.1 移除已经是该固件版本的设备 - devices.removeIf(device -> Objects.equals(device.getFirmwareId(), createReqVO.getFirmwareId())); - // 2.1.2 移除已经在升级中的设备 - List 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; - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/IotDataRuleService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/IotDataRuleService.java deleted file mode 100644 index 6fb5f79b3d..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/IotDataRuleService.java +++ /dev/null @@ -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 getDataRulePage(IotDataRulePageReqVO pageReqVO); - - /** - * 根据数据目的编号,获得数据流转规则列表 - * - * @param sinkId 数据目的编号 - * @return 是否被使用 - */ - List getDataRuleListBySinkId(Long sinkId); - - /** - * 执行数据流转规则 - * - * @param message 消息 - */ - void executeDataRule(IotDeviceMessage message); - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/IotDataRuleServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/IotDataRuleServiceImpl.java deleted file mode 100644 index 3301315731..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/IotDataRuleServiceImpl.java +++ /dev/null @@ -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 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 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 sourceConfigs) { - Map> 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> entry : productIdIdentifiers.entrySet()) { - thingModelService.validateThingModelListExists(entry.getKey(), entry.getValue()); - } - } - - @Override - public IotDataRuleDO getDataRule(Long id) { - return dataRuleMapper.selectById(id); - } - - @Override - public PageResult getDataRulePage(IotDataRulePageReqVO pageReqVO) { - return dataRuleMapper.selectPage(pageReqVO); - } - - @Override - public List getDataRuleListBySinkId(Long sinkId) { - return dataRuleMapper.selectListBySinkId(sinkId); - } - - @Cacheable(value = RedisKeyConstants.DATA_RULE_LIST, - key = "#deviceId + '_' + #method + '_' + (#identifier ?: '')") - public List getDataRuleListByConditionFromCache(Long deviceId, String method, String identifier) { - // 1. 查询所有开启的数据流转规则 - List rules = dataRuleMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus()); - // 2. 内存里过滤匹配的规则 - List 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 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); - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/IotDataSinkService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/IotDataSinkService.java deleted file mode 100644 index 8cd5b5d319..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/IotDataSinkService.java +++ /dev/null @@ -1,80 +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.sink.IotDataSinkPageReqVO; -import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.sink.IotDataSinkSaveReqVO; -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataSinkDO; - -import javax.validation.Valid; -import java.util.Collection; -import java.util.List; - -/** - * IoT 数据流转目的 Service 接口 - * - * @author HUIHUI - */ -public interface IotDataSinkService { - - /** - * 创建数据流转目的 - * - * @param createReqVO 创建信息 - * @return 编号 - */ - Long createDataSink(@Valid IotDataSinkSaveReqVO createReqVO); - - /** - * 更新数据流转目的 - * - * @param updateReqVO 更新信息 - */ - void updateDataSink(@Valid IotDataSinkSaveReqVO updateReqVO); - - /** - * 删除数据流转目的 - * - * @param id 编号 - */ - void deleteDataSink(Long id); - - /** - * 获得数据流转目的 - * - * @param id 编号 - * @return 数据流转目的 - */ - IotDataSinkDO getDataSink(Long id); - - /** - * 从缓存中获得数据流转目的 - * - * @param id 编号 - * @return 数据流转目的 - */ - IotDataSinkDO getDataSinkFromCache(Long id); - - /** - * 获得数据流转目的分页 - * - * @param pageReqVO 分页查询 - * @return 数据流转目的分页 - */ - PageResult getDataSinkPage(IotDataSinkPageReqVO pageReqVO); - - /** - * 获取数据流转目的列表 - * - * @param status 状态,如果为空,则不进行筛选 - * @return 数据流转目的列表 - */ - List getDataSinkListByStatus(Integer status); - - /** - * 批量校验数据目的存在 - * - * @param ids 数据目的编号集合 - */ - void validateDataSinksExist(Collection ids); - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/IotDataSinkServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/IotDataSinkServiceImpl.java deleted file mode 100644 index 53453c9896..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/IotDataSinkServiceImpl.java +++ /dev/null @@ -1,106 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.data; - -import cn.hutool.core.collection.CollUtil; -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.IotDataSinkSaveReqVO; -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataSinkDO; -import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotDataSinkMapper; -import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; - -import javax.annotation.Resource; -import java.util.Collection; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.DATA_SINK_DELETE_FAIL_USED_BY_RULE; -import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.DATA_SINK_NOT_EXISTS; - -/** - * IoT 数据流转目的 Service 实现类 - * - * @author HUIHUI - */ -@Service -@Validated -public class IotDataSinkServiceImpl implements IotDataSinkService { - - @Resource - private IotDataSinkMapper dataSinkMapper; - - @Resource - @Lazy // 延迟,避免循环依赖报错 - private IotDataRuleService dataRuleService; - - @Override - public Long createDataSink(IotDataSinkSaveReqVO createReqVO) { - IotDataSinkDO dataBridge = BeanUtils.toBean(createReqVO, IotDataSinkDO.class); - dataSinkMapper.insert(dataBridge); - return dataBridge.getId(); - } - - @Override - public void updateDataSink(IotDataSinkSaveReqVO updateReqVO) { - // 校验存在 - validateDataBridgeExists(updateReqVO.getId()); - // 更新 - IotDataSinkDO updateObj = BeanUtils.toBean(updateReqVO, IotDataSinkDO.class); - dataSinkMapper.updateById(updateObj); - } - - @Override - public void deleteDataSink(Long id) { - // 校验存在 - validateDataBridgeExists(id); - // 校验是否被数据流转规则使用 - if (CollUtil.isNotEmpty(dataRuleService.getDataRuleListBySinkId(id))) { - throw exception(DATA_SINK_DELETE_FAIL_USED_BY_RULE); - } - // 删除 - dataSinkMapper.deleteById(id); - } - - private void validateDataBridgeExists(Long id) { - if (dataSinkMapper.selectById(id) == null) { - throw exception(DATA_SINK_NOT_EXISTS); - } - } - - @Override - public IotDataSinkDO getDataSink(Long id) { - return dataSinkMapper.selectById(id); - } - - @Override - @Cacheable(value = RedisKeyConstants.DATA_SINK, key = "#id") - public IotDataSinkDO getDataSinkFromCache(Long id) { - return dataSinkMapper.selectById(id); - } - - @Override - public PageResult getDataSinkPage(IotDataSinkPageReqVO pageReqVO) { - return dataSinkMapper.selectPage(pageReqVO); - } - - @Override - public List getDataSinkListByStatus(Integer status) { - return dataSinkMapper.selectListByStatus(status); - } - - @Override - public void validateDataSinksExist(Collection ids) { - if (CollUtil.isEmpty(ids)) { - return; - } - List sinks = dataSinkMapper.selectByIds(ids); - if (sinks.size() != ids.size()) { - throw exception(DATA_SINK_NOT_EXISTS); - } - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotDataRuleAction.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotDataRuleAction.java deleted file mode 100644 index 8e6458ba86..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotDataRuleAction.java +++ /dev/null @@ -1,28 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.data.action; - -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataSinkDO; - -/** - * IoT 数据流转目的的执行器 action 接口 - * - * @author HUIHUI - */ -public interface IotDataRuleAction { - - /** - * 获取数据流转目的类型 - * - * @return 数据流转目的类型 - */ - Integer getType(); - - /** - * 执行数据流转目的操作 - * - * @param message 设备消息 - * @param dataSink 数据流转目的 - */ - void execute(IotDeviceMessage message, IotDataSinkDO dataSink); - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotRabbitMQDataRuleAction.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotRabbitMQDataRuleAction.java deleted file mode 100644 index 075871a376..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotRabbitMQDataRuleAction.java +++ /dev/null @@ -1,77 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.data.action; - -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotDataSinkRabbitMQConfig; -import cn.iocoder.yudao.module.iot.enums.rule.IotDataSinkTypeEnum; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.stereotype.Component; - -/** - * RabbitMQ 的 {@link IotDataRuleAction} 实现类 - * - * @author HUIHUI - */ -@ConditionalOnClass(name = "com.rabbitmq.client.Channel") -@Component -@Slf4j -public class IotRabbitMQDataRuleAction - extends IotDataRuleCacheableAction { - - @Override - public Integer getType() { - return IotDataSinkTypeEnum.RABBITMQ.getType(); - } - - @Override - public void execute(IotDeviceMessage message, IotDataSinkRabbitMQConfig config) throws Exception { - try { - // 1.1 获取或创建 Channel - Channel channel = getProducer(config); - // 1.2 声明交换机、队列和绑定关系 - channel.exchangeDeclare(config.getExchange(), "direct", true); - channel.queueDeclare(config.getQueue(), true, false, false, null); - channel.queueBind(config.getQueue(), config.getExchange(), config.getRoutingKey()); - - // 2. 发送消息 - channel.basicPublish(config.getExchange(), config.getRoutingKey(), null, - JsonUtils.toJsonByte(message)); - log.info("[execute][message({}) config({}) 发送成功]", message, config); - } catch (Exception e) { - log.error("[execute][message({}) config({}) 发送失败]", message, config, e); - throw e; - } - } - - @Override - @SuppressWarnings("resource") - protected Channel initProducer(IotDataSinkRabbitMQConfig config) throws Exception { - // 1. 创建连接工厂 - ConnectionFactory factory = new ConnectionFactory(); - factory.setHost(config.getHost()); - factory.setPort(config.getPort()); - factory.setVirtualHost(config.getVirtualHost()); - factory.setUsername(config.getUsername()); - factory.setPassword(config.getPassword()); - // 2. 创建连接 - Connection connection = factory.newConnection(); - // 3. 创建信道 - return connection.createChannel(); - } - - @Override - protected void closeProducer(Channel channel) throws Exception { - if (channel.isOpen()) { - channel.close(); - } - Connection connection = channel.getConnection(); - if (connection.isOpen()) { - connection.close(); - } - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotRedisRuleAction.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotRedisRuleAction.java deleted file mode 100644 index 904240da8b..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotRedisRuleAction.java +++ /dev/null @@ -1,181 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.data.action; - -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotDataSinkRedisConfig; -import cn.iocoder.yudao.module.iot.enums.rule.IotDataSinkTypeEnum; -import cn.iocoder.yudao.module.iot.enums.rule.IotRedisDataStructureEnum; -import lombok.extern.slf4j.Slf4j; -import org.redisson.Redisson; -import org.redisson.api.RedissonClient; -import org.redisson.config.Config; -import org.redisson.config.SingleServerConfig; -import org.redisson.spring.data.connection.RedissonConnectionFactory; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.connection.stream.ObjectRecord; -import org.springframework.data.redis.connection.stream.StreamRecords; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.serializer.RedisSerializer; -import org.springframework.stereotype.Component; - -import java.util.Map; - -/** - * Redis 的 {@link IotDataRuleAction} 实现类 - * 支持多种 Redis 数据结构:Stream、Hash、List、Set、ZSet、String - * - * @author HUIHUI - */ -@Component -@Slf4j -public class IotRedisRuleAction extends - IotDataRuleCacheableAction> { - - @Override - public Integer getType() { - return IotDataSinkTypeEnum.REDIS.getType(); - } - - @Override - public void execute(IotDeviceMessage message, IotDataSinkRedisConfig config) throws Exception { - // 1. 获取 RedisTemplate - RedisTemplate redisTemplate = getProducer(config); - - // 2. 根据数据结构类型执行不同的操作 - String messageJson = JsonUtils.toJsonString(message); - IotRedisDataStructureEnum dataStructure = getDataStructureByType(config.getDataStructure()); - switch (dataStructure) { - case STREAM: - executeStream(redisTemplate, config, messageJson); - break; - case HASH: - executeHash(redisTemplate, config, message, messageJson); - break; - case LIST: - executeList(redisTemplate, config, messageJson); - break; - case SET: - executeSet(redisTemplate, config, messageJson); - break; - case ZSET: - executeZSet(redisTemplate, config, message, messageJson); - break; - case STRING: - executeString(redisTemplate, config, messageJson); - break; - default: - throw new IllegalArgumentException("不支持的 Redis 数据结构类型: " + dataStructure); - } - - log.info("[execute][消息发送成功] dataStructure: {}, config: {}", dataStructure.getName(), config); - } - - /** - * 执行 Stream 操作 - */ - private void executeStream(RedisTemplate redisTemplate, IotDataSinkRedisConfig config, String messageJson) { - ObjectRecord record = StreamRecords.newRecord() - .ofObject(messageJson).withStreamKey(config.getTopic()); - redisTemplate.opsForStream().add(record); - } - - /** - * 执行 Hash 操作 - */ - private void executeHash(RedisTemplate redisTemplate, IotDataSinkRedisConfig config, - IotDeviceMessage message, String messageJson) { - String hashField = StrUtil.isNotBlank(config.getHashField()) ? - config.getHashField() : String.valueOf(message.getDeviceId()); - redisTemplate.opsForHash().put(config.getTopic(), hashField, messageJson); - } - - /** - * 执行 List 操作 - */ - private void executeList(RedisTemplate redisTemplate, IotDataSinkRedisConfig config, String messageJson) { - redisTemplate.opsForList().rightPush(config.getTopic(), messageJson); - } - - /** - * 执行 Set 操作 - */ - private void executeSet(RedisTemplate redisTemplate, IotDataSinkRedisConfig config, String messageJson) { - redisTemplate.opsForSet().add(config.getTopic(), messageJson); - } - - /** - * 执行 ZSet 操作 - */ - private void executeZSet(RedisTemplate redisTemplate, IotDataSinkRedisConfig config, - IotDeviceMessage message, String messageJson) { - double score; - if (StrUtil.isNotBlank(config.getScoreField())) { - // 尝试从消息中获取分数字段 - try { - Map messageMap = JsonUtils.parseObject(messageJson, Map.class); - Object scoreValue = messageMap.get(config.getScoreField()); - score = scoreValue instanceof Number ? ((Number) scoreValue).doubleValue() : System.currentTimeMillis(); - } catch (Exception e) { - score = System.currentTimeMillis(); - } - } else { - // 使用当前时间戳作为分数 - score = System.currentTimeMillis(); - } - redisTemplate.opsForZSet().add(config.getTopic(), messageJson, score); - } - - /** - * 执行 String 操作 - */ - private void executeString(RedisTemplate redisTemplate, IotDataSinkRedisConfig config, String messageJson) { - redisTemplate.opsForValue().set(config.getTopic(), messageJson); - } - - @Override - protected RedisTemplate initProducer(IotDataSinkRedisConfig config) { - // 1.1 创建 Redisson 配置 - Config redissonConfig = new Config(); - SingleServerConfig serverConfig = redissonConfig.useSingleServer() - .setAddress("redis://" + config.getHost() + ":" + config.getPort()) - .setDatabase(config.getDatabase()); - // 1.2 设置密码(如果有) - if (StrUtil.isNotBlank(config.getPassword())) { - serverConfig.setPassword(config.getPassword()); - } - - // 2.1 创建 RedisTemplate 并配置 - RedissonClient redisson = Redisson.create(redissonConfig); - RedisTemplate template = new RedisTemplate<>(); - template.setConnectionFactory(new RedissonConnectionFactory(redisson)); - // 2.2 设置序列化器 - template.setKeySerializer(RedisSerializer.string()); - template.setHashKeySerializer(RedisSerializer.string()); - template.setValueSerializer(RedisSerializer.json()); - template.setHashValueSerializer(RedisSerializer.json()); - template.afterPropertiesSet(); - return template; - } - - @Override - protected void closeProducer(RedisTemplate producer) throws Exception { - RedisConnectionFactory factory = producer.getConnectionFactory(); - if (factory != null) { - ((RedissonConnectionFactory) factory).destroy(); - } - } - - /** - * 根据类型值获取数据结构枚举 - */ - private IotRedisDataStructureEnum getDataStructureByType(Integer type) { - for (IotRedisDataStructureEnum dataStructure : IotRedisDataStructureEnum.values()) { - if (dataStructure.getType().equals(type)) { - return dataStructure; - } - } - throw new IllegalArgumentException("不支持的 Redis 数据结构类型: " + type); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotRocketMQDataRuleAction.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotRocketMQDataRuleAction.java deleted file mode 100644 index d73205c6df..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotRocketMQDataRuleAction.java +++ /dev/null @@ -1,61 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.data.action; - -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotDataSinkRocketMQConfig; -import cn.iocoder.yudao.module.iot.enums.rule.IotDataSinkTypeEnum; -import lombok.extern.slf4j.Slf4j; -import org.apache.rocketmq.client.producer.DefaultMQProducer; -import org.apache.rocketmq.client.producer.SendResult; -import org.apache.rocketmq.client.producer.SendStatus; -import org.apache.rocketmq.common.message.Message; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.stereotype.Component; - -/** - * RocketMQ 的 {@link IotDataRuleAction} 实现类 - * - * @author HUIHUI - */ -@ConditionalOnClass(name = "org.apache.rocketmq.client.producer.DefaultMQProducer") -@Component -@Slf4j -public class IotRocketMQDataRuleAction extends - IotDataRuleCacheableAction { - - @Override - public Integer getType() { - return IotDataSinkTypeEnum.ROCKETMQ.getType(); - } - - @Override - public void execute(IotDeviceMessage message, IotDataSinkRocketMQConfig config) throws Exception { - // 1. 获取或创建 Producer - DefaultMQProducer producer = getProducer(config); - - // 2.1 创建消息对象,指定 Topic、Tag 和消息体 - Message msg = new Message(config.getTopic(), config.getTags(), JsonUtils.toJsonByte(message)); - // 2.2 发送同步消息并处理结果 - SendResult sendResult = producer.send(msg); - // 2.3 处理发送结果 - if (SendStatus.SEND_OK.equals(sendResult.getSendStatus())) { - log.info("[execute][message({}) config({}) 发送成功,结果({})]", message, config, sendResult); - } else { - log.error("[execute][message({}) config({}) 发送失败,结果({})]", message, config, sendResult); - } - } - - @Override - protected DefaultMQProducer initProducer(IotDataSinkRocketMQConfig config) throws Exception { - DefaultMQProducer producer = new DefaultMQProducer(config.getGroup()); - producer.setNamesrvAddr(config.getNameServer()); - producer.start(); - return producer; - } - - @Override - protected void closeProducer(DefaultMQProducer producer) { - producer.shutdown(); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleService.java deleted file mode 100644 index deb31eebcc..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleService.java +++ /dev/null @@ -1,110 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRulePageReqVO; -import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleSaveReqVO; -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; - -import javax.validation.Valid; -import java.util.Collection; -import java.util.List; - -/** - * IoT 规则场景规则 Service 接口 - * - * @author 芋道源码 - */ -public interface IotSceneRuleService { - - /** - * 创建场景联动 - * - * @param createReqVO 创建信息 - * @return 编号 - */ - Long createSceneRule(@Valid IotSceneRuleSaveReqVO createReqVO); - - /** - * 更新场景联动 - * - * @param updateReqVO 更新信息 - */ - void updateSceneRule(@Valid IotSceneRuleSaveReqVO updateReqVO); - - /** - * 更新场景联动状态 - * - * @param id 场景联动编号 - * @param status 状态 - */ - void updateSceneRuleStatus(Long id, Integer status); - - /** - * 删除场景联动 - * - * @param id 编号 - */ - void deleteSceneRule(Long id); - - /** - * 获得场景联动 - * - * @param id 编号 - * @return 场景联动 - */ - IotSceneRuleDO getSceneRule(Long id); - - /** - * 获得场景联动分页 - * - * @param pageReqVO 分页查询 - * @return 场景联动分页 - */ - PageResult getSceneRulePage(IotSceneRulePageReqVO pageReqVO); - - /** - * 校验规则场景联动规则编号们是否存在。如下情况,视为无效: - * 1. 规则场景联动规则编号不存在 - * - * @param ids 场景联动规则编号数组 - */ - void validateSceneRuleList(Collection ids); - - /** - * 获得指定状态的场景联动列表 - * - * @param status 状态 - * @return 场景联动列表 - */ - List getSceneRuleListByStatus(Integer status); - - /** - * 【缓存】获得指定设备的场景列表 - * - * @param productId 产品 ID - * @param deviceId 设备 ID - * @return 场景列表 - */ - List getSceneRuleListByProductIdAndDeviceIdFromCache(Long productId, Long deviceId); - - /** - * 基于 {@link IotSceneRuleTriggerTypeEnum} 场景,执行规则场景 - * 1. {@link IotSceneRuleTriggerTypeEnum#DEVICE_STATE_UPDATE} - * 2. {@link IotSceneRuleTriggerTypeEnum#DEVICE_PROPERTY_POST} - * {@link IotSceneRuleTriggerTypeEnum#DEVICE_EVENT_POST} - * 3. {@link IotSceneRuleTriggerTypeEnum#DEVICE_EVENT_POST} - * {@link IotSceneRuleTriggerTypeEnum#DEVICE_SERVICE_INVOKE} - * @param message 消息 - */ - void executeSceneRuleByDevice(IotDeviceMessage message); - - /** - * 基于 {@link IotSceneRuleTriggerTypeEnum#TIMER} 场景,执行规则场景 - * - * @param id 场景联动规则编号 - */ - void executeSceneRuleByTimer(Long id); - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceImpl.java deleted file mode 100644 index c08f11afba..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceImpl.java +++ /dev/null @@ -1,380 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.collection.ListUtil; -import cn.hutool.core.util.ObjUtil; -import cn.hutool.extra.spring.SpringUtil; -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.tenant.core.aop.TenantIgnore; -import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; -import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRulePageReqVO; -import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleSaveReqVO; -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.product.IotProductDO; -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotSceneRuleMapper; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; -import cn.iocoder.yudao.module.iot.framework.job.core.IotSchedulerManager; -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.scene.action.IotSceneRuleAction; -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherManager; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; - -import javax.annotation.Resource; -import java.util.Collection; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; -import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.RULE_SCENE_NOT_EXISTS; - -/** - * IoT 规则场景 Service 实现类 - * - * @author 芋道源码 - */ -@Service -@Validated -@Slf4j -public class IotSceneRuleServiceImpl implements IotSceneRuleService { - - @Resource - private IotSceneRuleMapper sceneRuleMapper; - - // TODO @puhui999:定时任务,基于它调度; - @Resource(name = "iotSchedulerManager") - private IotSchedulerManager schedulerManager; - @Resource - private IotProductService productService; - @Resource - private IotDeviceService deviceService; - - @Resource - private IotSceneRuleMatcherManager sceneRuleMatcherManager; - @Resource - private List sceneRuleActions; - - @Override - public Long createSceneRule(IotSceneRuleSaveReqVO createReqVO) { - IotSceneRuleDO sceneRule = BeanUtils.toBean(createReqVO, IotSceneRuleDO.class); - sceneRuleMapper.insert(sceneRule); - return sceneRule.getId(); - } - - @Override - public void updateSceneRule(IotSceneRuleSaveReqVO updateReqVO) { - // 校验存在 - validateSceneRuleExists(updateReqVO.getId()); - // 更新 - IotSceneRuleDO updateObj = BeanUtils.toBean(updateReqVO, IotSceneRuleDO.class); - sceneRuleMapper.updateById(updateObj); - } - - @Override - public void updateSceneRuleStatus(Long id, Integer status) { - // 校验存在 - validateSceneRuleExists(id); - // 更新状态 - IotSceneRuleDO updateObj = new IotSceneRuleDO().setId(id).setStatus(status); - sceneRuleMapper.updateById(updateObj); - } - - @Override - public void deleteSceneRule(Long id) { - // 校验存在 - validateSceneRuleExists(id); - // 删除 - sceneRuleMapper.deleteById(id); - } - - private void validateSceneRuleExists(Long id) { - if (sceneRuleMapper.selectById(id) == null) { - throw exception(RULE_SCENE_NOT_EXISTS); - } - } - - @Override - public IotSceneRuleDO getSceneRule(Long id) { - return sceneRuleMapper.selectById(id); - } - - @Override - public PageResult getSceneRulePage(IotSceneRulePageReqVO pageReqVO) { - return sceneRuleMapper.selectPage(pageReqVO); - } - - @Override - public void validateSceneRuleList(Collection ids) { - if (CollUtil.isEmpty(ids)) { - return; - } - // 批量查询存在的规则场景 - List existingScenes = sceneRuleMapper.selectByIds(ids); - if (existingScenes.size() != ids.size()) { - throw exception(RULE_SCENE_NOT_EXISTS); - } - } - - @Override - public List getSceneRuleListByStatus(Integer status) { - return sceneRuleMapper.selectListByStatus(status); - } - - // TODO 芋艿,缓存待实现 @puhui999 - @Override - @TenantIgnore // 忽略租户隔离:因为 IotSceneRuleMessageHandler 调用时,一般未传递租户,所以需要忽略 - public List getSceneRuleListByProductIdAndDeviceIdFromCache(Long productId, Long deviceId) { - List list = sceneRuleMapper.selectList(); - // 只返回启用状态的规则场景 - List enabledList = filterList(list, - sceneRule -> CommonStatusEnum.isEnable(sceneRule.getStatus())); - - // 根据 productKey 和 deviceName 进行匹配 - return filterList(enabledList, sceneRule -> { - if (CollUtil.isEmpty(sceneRule.getTriggers())) { - return false; - } - - for (IotSceneRuleDO.Trigger trigger : sceneRule.getTriggers()) { - // 检查触发器是否匹配指定的产品和设备 - try { - // 1. 检查产品是否匹配 - if (trigger.getProductId() == null) { - return false; - } - if (trigger.getDeviceId() == null) { - return false; - } - // 检查是否是全部设备的特殊标识 - if (IotDeviceDO.DEVICE_ID_ALL.equals(trigger.getDeviceId())) { - return true; // 匹配所有设备 - } - // 检查具体设备 ID 是否匹配 - return ObjUtil.equal(productId, trigger.getProductId()) && ObjUtil.equal(deviceId, trigger.getDeviceId()); - } catch (Exception e) { - log.warn("[isMatchProductAndDevice][产品({}) 设备({}) 匹配触发器异常]", productId, deviceId, e); - return false; - } - } - return false; - }); - } - - @Override - public void executeSceneRuleByDevice(IotDeviceMessage message) { - // TODO @芋艿:这里的 tenantId,通过设备获取;@puhui999: - TenantUtils.execute(message.getTenantId(), () -> { - // 1. 获得设备匹配的规则场景 - List sceneRules = getMatchedSceneRuleListByMessage(message); - if (CollUtil.isEmpty(sceneRules)) { - return; - } - - // 2. 执行规则场景 - executeSceneRuleAction(message, sceneRules); - }); - } - - @Override - public void executeSceneRuleByTimer(Long id) { - // 1.1 获得规则场景 - IotSceneRuleDO scene = TenantUtils.executeIgnore(() -> sceneRuleMapper.selectById(id)); - if (scene == null) { - log.error("[executeSceneRuleByTimer][规则场景({}) 不存在]", id); - return; - } - if (CommonStatusEnum.isDisable(scene.getStatus())) { - log.info("[executeSceneRuleByTimer][规则场景({}) 已被禁用]", id); - return; - } - // 1.2 判断是否有定时触发器,避免脏数据 - IotSceneRuleDO.Trigger config = CollUtil.findOne(scene.getTriggers(), - trigger -> ObjUtil.equals(trigger.getType(), IotSceneRuleTriggerTypeEnum.TIMER.getType())); - if (config == null) { - log.error("[executeSceneRuleByTimer][规则场景({}) 不存在定时触发器]", scene); - return; - } - - // 2. 执行规则场景 - TenantUtils.execute(scene.getTenantId(), - () -> executeSceneRuleAction(null, ListUtil.toList(scene))); - } - - /** - * 基于消息,获得匹配的规则场景列表 - * - * @param message 设备消息 - * @return 规则场景列表 - */ - private List getMatchedSceneRuleListByMessage(IotDeviceMessage message) { - // 1. 匹配设备 - // TODO @芋艿:可能需要 getSelf(); 缓存 @puhui999; - // 1.1 通过 deviceId 获取设备信息 - IotDeviceDO device = deviceService.getDeviceFromCache(message.getDeviceId()); - if (device == null) { - log.warn("[getMatchedSceneRuleListByMessage][设备({}) 不存在]", message.getDeviceId()); - return ListUtil.of(); - } - - // 1.2 通过 productId 获取产品信息 - IotProductDO product = productService.getProductFromCache(device.getProductId()); - if (product == null) { - log.warn("[getMatchedSceneRuleListByMessage][产品({}) 不存在]", device.getProductId()); - return ListUtil.of(); - } - - // 1.3 获取匹配的规则场景 - List sceneRules = getSceneRuleListByProductIdAndDeviceIdFromCache( - product.getId(), device.getId()); - if (CollUtil.isEmpty(sceneRules)) { - return sceneRules; - } - - // 2. 使用重构后的触发器匹配逻辑 - return filterList(sceneRules, sceneRule -> matchSceneRuleTriggers(message, sceneRule)); - } - - /** - * 匹配场景规则的所有触发器 - * - * @param message 设备消息 - * @param sceneRule 场景规则 - * @return 是否匹配 - */ - private boolean matchSceneRuleTriggers(IotDeviceMessage message, IotSceneRuleDO sceneRule) { - if (CollUtil.isEmpty(sceneRule.getTriggers())) { - log.debug("[matchSceneRuleTriggers][规则场景({}) 没有配置触发器]", sceneRule.getId()); - return false; - } - - for (IotSceneRuleDO.Trigger trigger : sceneRule.getTriggers()) { - if (matchSingleTrigger(message, trigger, sceneRule)) { - log.info("[matchSceneRuleTriggers][消息({}) 匹配到规则场景编号({}) 的触发器({})]", - message.getRequestId(), sceneRule.getId(), trigger.getType()); - return true; - } - } - return false; - } - - /** - * 匹配单个触发器 - * - * @param message 设备消息 - * @param trigger 触发器 - * @param sceneRule 场景规则(用于日志) - * @return 是否匹配 - */ - private boolean matchSingleTrigger(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, IotSceneRuleDO sceneRule) { - try { - // 2. 检查触发器的条件分组 - return sceneRuleMatcherManager.isMatched(message, trigger) && isTriggerConditionGroupsMatched(message, trigger, sceneRule); - } catch (Exception e) { - log.error("[matchSingleTrigger][触发器匹配异常] sceneRuleId: {}, triggerType: {}, message: {}", - sceneRule.getId(), trigger.getType(), message, e); - return false; - } - } - - /** - * 检查触发器的条件分组是否匹配 - * - * @param message 设备消息 - * @param trigger 触发器 - * @param sceneRule 场景规则(用于日志) - * @return 是否匹配 - */ - private boolean isTriggerConditionGroupsMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, IotSceneRuleDO sceneRule) { - // 如果没有条件分组,则认为匹配成功(只依赖基础触发器匹配) - if (CollUtil.isEmpty(trigger.getConditionGroups())) { - return true; - } - - // 检查条件分组:分组与分组之间是"或"的关系,条件与条件之间是"且"的关系 - for (List conditionGroup : trigger.getConditionGroups()) { - if (CollUtil.isEmpty(conditionGroup)) { - continue; - } - - // 检查当前分组中的所有条件是否都匹配(且关系) - boolean allConditionsMatched = true; - for (IotSceneRuleDO.TriggerCondition condition : conditionGroup) { - if (!isTriggerConditionMatched(message, condition, sceneRule, trigger)) { - allConditionsMatched = false; - break; - } - } - - // 如果当前分组的所有条件都匹配,则整个触发器匹配成功 - if (allConditionsMatched) { - return true; - } - } - - // 所有分组都不匹配 - return false; - } - - /** - * 基于消息,判断触发器的子条件是否匹配 - * - * @param message 设备消息 - * @param condition 触发条件 - * @param sceneRule 规则场景(用于日志,无其它作用) - * @param trigger 触发器(用于日志,无其它作用) - * @return 是否匹配 - */ - private boolean isTriggerConditionMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition, - IotSceneRuleDO sceneRule, IotSceneRuleDO.Trigger trigger) { - try { - return sceneRuleMatcherManager.isConditionMatched(message, condition); - } catch (Exception e) { - log.error("[isTriggerConditionMatched][规则场景编号({}) 的触发器({}) 条件匹配异常]", - sceneRule.getId(), trigger, e); - return false; - } - } - - /** - * 执行规则场景的动作 - * - * @param message 设备消息 - * @param sceneRules 规则场景列表 - */ - private void executeSceneRuleAction(IotDeviceMessage message, List sceneRules) { - // 1. 遍历规则场景 - sceneRules.forEach(sceneRule -> { - // 2. 遍历规则场景的动作 - sceneRule.getActions().forEach(actionConfig -> { - // 3.1 获取对应的动作 Action 数组 - List actions = filterList(sceneRuleActions, - action -> action.getType().getType().equals(actionConfig.getType())); - if (CollUtil.isEmpty(actions)) { - return; - } - // 3.2 执行动作 - actions.forEach(action -> { - try { - action.execute(message, sceneRule, actionConfig); - log.info("[executeSceneRuleAction][消息({}) 规则场景编号({}) 的执行动作({}) 成功]", - message, sceneRule.getId(), actionConfig); - } catch (Exception e) { - log.error("[executeSceneRuleAction][消息({}) 规则场景编号({}) 的执行动作({}) 执行异常]", - message, sceneRule.getId(), actionConfig, e); - } - }); - }); - }); - } - - private IotSceneRuleServiceImpl getSelf() { - return SpringUtil.getBean(IotSceneRuleServiceImpl.class); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotAlertRecoverSceneRuleAction.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotAlertRecoverSceneRuleAction.java deleted file mode 100644 index e2b2321be2..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotAlertRecoverSceneRuleAction.java +++ /dev/null @@ -1,49 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.action; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertRecordDO; -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleActionTypeEnum; -import cn.iocoder.yudao.module.iot.service.alert.IotAlertRecordService; -import org.springframework.stereotype.Component; - -import javax.annotation.Resource; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; - -// TODO @puhui999、@芋艿:未测试;需要场景联动开发完 -/** - * IoT 告警恢复的 {@link IotSceneRuleAction} 实现类 - * - * @author 芋道源码 - */ -@Component -public class IotAlertRecoverSceneRuleAction implements IotSceneRuleAction { - - private static final String PROCESS_REMARK = "告警自动回复,基于【{}】场景联动规则"; - - @Resource - private IotAlertRecordService alertRecordService; - - @Override - public void execute(IotDeviceMessage message, - IotSceneRuleDO rule, IotSceneRuleDO.Action actionConfig) throws Exception { - Long deviceId = message != null ? message.getDeviceId() : null; - List alertRecords = alertRecordService.getAlertRecordListBySceneRuleId( - rule.getId(), deviceId, false); - if (CollUtil.isEmpty(alertRecords)) { - return; - } - alertRecordService.processAlertRecordList(convertList(alertRecords, IotAlertRecordDO::getId), - StrUtil.format(PROCESS_REMARK, rule.getName())); - } - - @Override - public IotSceneRuleActionTypeEnum getType() { - return IotSceneRuleActionTypeEnum.ALERT_RECOVER; - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotAlertTriggerSceneRuleAction.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotAlertTriggerSceneRuleAction.java deleted file mode 100644 index d84ecaaf81..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotAlertTriggerSceneRuleAction.java +++ /dev/null @@ -1,69 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.action; - -import cn.hutool.core.collection.CollUtil; -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -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.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleActionTypeEnum; -import cn.iocoder.yudao.module.iot.service.alert.IotAlertConfigService; -import cn.iocoder.yudao.module.iot.service.alert.IotAlertRecordService; -import cn.iocoder.yudao.module.system.api.mail.MailSendApi; -import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi; -import cn.iocoder.yudao.module.system.api.sms.SmsSendApi; -import org.springframework.stereotype.Component; - -import javax.annotation.Nullable; -import javax.annotation.Resource; -import java.util.List; - -// TODO @puhui999、@芋艿:未测试;需要场景联动开发完 -/** - * IoT 告警触发的 {@link IotSceneRuleAction} 实现类 - * - * @author 芋道源码 - */ -@Component -public class IotAlertTriggerSceneRuleAction implements IotSceneRuleAction { - - @Resource - private IotAlertConfigService alertConfigService; - @Resource - private IotAlertRecordService alertRecordService; - - @Resource - private SmsSendApi smsSendApi; - @Resource - private MailSendApi mailSendApi; - @Resource - private NotifyMessageSendApi notifyMessageSendApi; - - @Override - public void execute(@Nullable IotDeviceMessage message, - IotSceneRuleDO rule, IotSceneRuleDO.Action actionConfig) throws Exception { - List alertConfigs = alertConfigService.getAlertConfigListBySceneRuleIdAndStatus( - rule.getId(), CommonStatusEnum.ENABLE.getStatus()); - if (CollUtil.isEmpty(alertConfigs)) { - return; - } - alertConfigs.forEach(alertConfig -> { - // 记录告警记录,传递场景规则ID - alertRecordService.createAlertRecord(alertConfig, rule.getId(), message); - // 发送告警消息 - sendAlertMessage(alertConfig, message); - }); - } - - private void sendAlertMessage(IotAlertConfigDO config, IotDeviceMessage deviceMessage) { - // TODO @芋艿:等场景联动开发完,再实现 - // TODO @芋艿:短信 - // TODO @芋艿:邮箱 - // TODO @芋艿:站内信 - } - - @Override - public IotSceneRuleActionTypeEnum getType() { - return IotSceneRuleActionTypeEnum.ALERT_TRIGGER; - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotDeviceControlSceneRuleAction.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotDeviceControlSceneRuleAction.java deleted file mode 100644 index f622436e25..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotDeviceControlSceneRuleAction.java +++ /dev/null @@ -1,56 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.action; - -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleActionTypeEnum; -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.Component; - -import javax.annotation.Resource; - -/** - * IoT 设备控制的 {@link IotSceneRuleAction} 实现类 - * - * @author 芋道源码 - */ -@Component -@Slf4j -public class IotDeviceControlSceneRuleAction implements IotSceneRuleAction { - - @Resource - private IotDeviceService deviceService; - @Resource - private IotDeviceMessageService deviceMessageService; - - // TODO @puhui999:这里 - @Override - public void execute(IotDeviceMessage message, - IotSceneRuleDO rule, IotSceneRuleDO.Action actionConfig) { - //IotSceneRuleDO.ActionDeviceControl control = actionConfig.getDeviceControl(); - //Assert.notNull(control, "设备控制配置不能为空"); - //// 遍历每个设备,下发消息 - //control.getDeviceNames().forEach(deviceName -> { - // IotDeviceDO device = deviceService.getDeviceFromCache(control.getProductKey(), deviceName); - // if (device == null) { - // log.error("[execute][message({}) actionConfig({}) 对应的设备不存在]", message, actionConfig); - // return; - // } - // try { - // // TODO @芋艿:@puhui999:这块可能要改,从 type => method - // IotDeviceMessage downstreamMessage = deviceMessageService.sendDeviceMessage(IotDeviceMessage.requestOf( - // control.getType() + control.getIdentifier(), control.getData()).setDeviceId(device.getId())); - // log.info("[execute][message({}) actionConfig({}) 下发消息({})成功]", message, actionConfig, downstreamMessage); - // } catch (Exception e) { - // log.error("[execute][message({}) actionConfig({}) 下发消息失败]", message, actionConfig, e); - // } - //}); - } - - @Override - public IotSceneRuleActionTypeEnum getType() { - return IotSceneRuleActionTypeEnum.DEVICE_PROPERTY_SET; - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotSceneRuleAction.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotSceneRuleAction.java deleted file mode 100644 index c88a37f8ce..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotSceneRuleAction.java +++ /dev/null @@ -1,36 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.action; - -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleActionTypeEnum; - -import javax.annotation.Nullable; - -/** - * IoT 场景联动的执行器接口 - * - * @author 芋道源码 - */ -public interface IotSceneRuleAction { - - /** - * 执行场景联动 - * - * @param message 消息,允许空 - * 1. 空的情况:定时触发 - * 2. 非空的情况:设备触发 - * @param rule 规则 - * @param actionConfig 执行配置(实际对应规则里的哪条执行配置) - */ - void execute(@Nullable IotDeviceMessage message, - IotSceneRuleDO rule, - IotSceneRuleDO.Action actionConfig) throws Exception; - - /** - * 获得类型 - * - * @return 类型 - */ - IotSceneRuleActionTypeEnum getType(); - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcher.java deleted file mode 100644 index 84795d9fe5..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcher.java +++ /dev/null @@ -1,40 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; - -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition.IotSceneRuleConditionMatcher; -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger.IotSceneRuleTriggerMatcher; - -/** - * IoT 场景规则匹配器基础接口 - *

- * 定义所有匹配器的通用行为,包括优先级、名称和启用状态 - *

- * - {@link IotSceneRuleTriggerMatcher} 触发器匹配器 - * - {@link IotSceneRuleConditionMatcher} 条件匹配器 - * - * @author HUIHUI - */ -public interface IotSceneRuleMatcher { - - /** - * 获取匹配优先级(数值越小优先级越高) - *

- * 用于在多个匹配器支持同一类型时确定优先级 - * - * @return 优先级数值 - */ - default int getPriority() { - return 100; - } - - /** - * 是否启用该匹配器 - *

- * 可用于动态开关某些匹配器 - * - * @return 是否启用 - */ - default boolean isEnabled() { - return true; - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherHelper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherHelper.java deleted file mode 100644 index 7175e37a7e..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherHelper.java +++ /dev/null @@ -1,238 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; - -import cn.hutool.core.text.CharPool; -import cn.hutool.core.util.NumberUtil; -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.common.util.number.NumberUtils; -import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; -import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils; -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; -import lombok.extern.slf4j.Slf4j; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; - -/** - * IoT 场景规则匹配器工具类 - *

- * 提供通用的条件评估逻辑和工具方法,供触发器和条件匹配器使用 - *

- * 该类包含了匹配器实现中常用的工具方法,如条件评估、参数校验、日志记录等 - * - * @author HUIHUI - */ -@Slf4j -public final class IotSceneRuleMatcherHelper { - - /** - * 私有构造函数,防止实例化 - */ - private IotSceneRuleMatcherHelper() { - } - - /** - * 评估条件是否匹配 - * - * @param sourceValue 源值(来自消息) - * @param operator 操作符 - * @param paramValue 参数值(来自条件配置) - * @return 是否匹配 - */ - public static boolean evaluateCondition(Object sourceValue, String operator, String paramValue) { - try { - // 1. 校验操作符是否合法 - IotSceneRuleConditionOperatorEnum operatorEnum = IotSceneRuleConditionOperatorEnum.operatorOf(operator); - if (operatorEnum == null) { - log.warn("[evaluateCondition][operator({}) 操作符无效]", operator); - return false; - } - - // 2. 构建 Spring 表达式变量 - return evaluateConditionWithOperatorEnum(sourceValue, operatorEnum, paramValue); - } catch (Exception e) { - log.error("[evaluateCondition][sourceValue({}) operator({}) paramValue({}) 条件评估异常]", - sourceValue, operator, paramValue, e); - return false; - } - } - - /** - * 使用操作符枚举评估条件是否匹配 - * - * @param sourceValue 源值(来自消息) - * @param operatorEnum 操作符枚举 - * @param paramValue 参数值(来自条件配置) - * @return 是否匹配 - */ - @SuppressWarnings("DataFlowIssue") - public static boolean evaluateConditionWithOperatorEnum(Object sourceValue, IotSceneRuleConditionOperatorEnum operatorEnum, String paramValue) { - try { - // 1. 构建 Spring 表达式变量 - Map springExpressionVariables = buildSpringExpressionVariables(sourceValue, operatorEnum, paramValue); - - // 2. 计算 Spring 表达式 - return (Boolean) SpringExpressionUtils.parseExpression(operatorEnum.getSpringExpression(), springExpressionVariables); - } catch (Exception e) { - log.error("[evaluateConditionWithOperatorEnum][sourceValue({}) operatorEnum({}) paramValue({}) 条件评估异常]", - sourceValue, operatorEnum, paramValue, e); - return false; - } - } - - /** - * 构建 Spring 表达式变量 - */ - private static Map buildSpringExpressionVariables(Object sourceValue, IotSceneRuleConditionOperatorEnum operatorEnum, String paramValue) { - Map springExpressionVariables = new HashMap<>(); - - // 设置源值 - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, sourceValue); - - // 处理参数值 - if (StrUtil.isNotBlank(paramValue)) { - List parameterValues = StrUtil.splitTrim(paramValue, CharPool.COMMA); - - // 设置原始参数值 - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE, paramValue); - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST, parameterValues); - - // 特殊处理:解决数字比较问题 - // Spring 表达式基于 compareTo 方法,对数字的比较存在问题,需要转换为数字类型 - if (isNumericComparisonOperator(operatorEnum) && isNumericComparison(sourceValue, parameterValues)) { - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, - NumberUtil.parseDouble(String.valueOf(sourceValue))); - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE, - NumberUtil.parseDouble(paramValue)); - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST, - convertList(parameterValues, NumberUtil::parseDouble)); - } - } - - return springExpressionVariables; - } - - /** - * 判断是否为数字比较操作符 - */ - private static boolean isNumericComparisonOperator(IotSceneRuleConditionOperatorEnum operatorEnum) { - return ObjectUtils.equalsAny(operatorEnum, - IotSceneRuleConditionOperatorEnum.BETWEEN, - IotSceneRuleConditionOperatorEnum.NOT_BETWEEN, - IotSceneRuleConditionOperatorEnum.GREATER_THAN, - IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS, - IotSceneRuleConditionOperatorEnum.LESS_THAN, - IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS); - } - - /** - * 判断是否为数字比较场景 - */ - private static boolean isNumericComparison(Object sourceValue, List parameterValues) { - return NumberUtil.isNumber(String.valueOf(sourceValue)) && NumberUtils.isAllNumber(parameterValues); - } - - // ========== 【触发器】相关工具方法 ========== - - /** - * 检查基础触发器参数是否有效 - * - * @param trigger 触发器配置 - * @return 是否有效 - */ - public static boolean isBasicTriggerValid(IotSceneRuleDO.Trigger trigger) { - return trigger != null && trigger.getType() != null; - } - - /** - * 检查触发器操作符和值是否有效 - * - * @param trigger 触发器配置 - * @return 是否有效 - */ - public static boolean isTriggerOperatorAndValueValid(IotSceneRuleDO.Trigger trigger) { - return StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue()); - } - - /** - * 记录触发器匹配成功日志 - * - * @param message 设备消息 - * @param trigger 触发器配置 - */ - public static void logTriggerMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { - log.debug("[isMatched][message({}) trigger({}) 匹配触发器成功]", message.getRequestId(), trigger.getType()); - } - - /** - * 记录触发器匹配失败日志 - * - * @param message 设备消息 - * @param trigger 触发器配置 - * @param reason 失败原因 - */ - public static void logTriggerMatchFailure(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, String reason) { - log.debug("[isMatched][message({}) trigger({}) reason({}) 匹配触发器失败]", message.getRequestId(), trigger.getType(), reason); - } - - // ========== 【条件】相关工具方法 ========== - - /** - * 检查基础条件参数是否有效 - * - * @param condition 触发条件 - * @return 是否有效 - */ - public static boolean isBasicConditionValid(IotSceneRuleDO.TriggerCondition condition) { - return condition != null && condition.getType() != null; - } - - /** - * 检查条件操作符和参数是否有效 - * - * @param condition 触发条件 - * @return 是否有效 - */ - public static boolean isConditionOperatorAndParamValid(IotSceneRuleDO.TriggerCondition condition) { - return StrUtil.isNotBlank(condition.getOperator()) && StrUtil.isNotBlank(condition.getParam()); - } - - /** - * 记录条件匹配成功日志 - * - * @param message 设备消息 - * @param condition 触发条件 - */ - public static void logConditionMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { - log.debug("[isMatched][message({}) condition({}) 匹配条件成功]", message.getRequestId(), condition.getType()); - } - - /** - * 记录条件匹配失败日志 - * - * @param message 设备消息 - * @param condition 触发条件 - * @param reason 失败原因 - */ - public static void logConditionMatchFailure(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition, String reason) { - log.debug("[isMatched][message({}) condition({}) reason({}) 匹配条件失败]", message.getRequestId(), condition.getType(), reason); - } - - // ========== 【通用】工具方法 ========== - - /** - * 检查标识符是否匹配 - * - * @param expectedIdentifier 期望的标识符 - * @param actualIdentifier 实际的标识符 - * @return 是否匹配 - */ - public static boolean isIdentifierMatched(String expectedIdentifier, String actualIdentifier) { - return StrUtil.isNotBlank(expectedIdentifier) && expectedIdentifier.equals(actualIdentifier); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherManager.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherManager.java deleted file mode 100644 index 22278ae9f6..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherManager.java +++ /dev/null @@ -1,160 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; - -import cn.hutool.core.collection.CollUtil; -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition.IotSceneRuleConditionMatcher; -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger.IotSceneRuleTriggerMatcher; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -import java.util.*; -import java.util.function.Function; -import java.util.stream.Collectors; - -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; - -/** - * IoT 场景规则匹配器统一管理器 - *

- * 负责管理所有匹配器(触发器匹配器和条件匹配器),并提供统一的匹配入口 - * - * @author HUIHUI - */ -@Component -@Slf4j -public class IotSceneRuleMatcherManager { - - /** - * 触发器匹配器映射表 - */ - private final Map triggerMatchers; - - /** - * 条件匹配器映射表 - */ - private final Map conditionMatchers; - - public IotSceneRuleMatcherManager(List matchers) { - if (CollUtil.isEmpty(matchers)) { - log.warn("[IotSceneRuleMatcherManager][没有找到任何匹配器]"); - this.triggerMatchers = new HashMap<>(); - this.conditionMatchers = new HashMap<>(); - return; - } - - // 按优先级排序并过滤启用的匹配器 - List allMatchers = matchers.stream() - .filter(IotSceneRuleMatcher::isEnabled) - .sorted(Comparator.comparing(IotSceneRuleMatcher::getPriority)) - .collect(Collectors.toList()); - - // 分离触发器匹配器和条件匹配器 - List triggerMatchers = allMatchers.stream() - .filter(matcher -> matcher instanceof IotSceneRuleTriggerMatcher) - .map(matcher -> (IotSceneRuleTriggerMatcher) matcher) - .collect(Collectors.toList()); - List conditionMatchers = allMatchers.stream() - .filter(matcher -> matcher instanceof IotSceneRuleConditionMatcher) - .map(matcher -> (IotSceneRuleConditionMatcher) matcher) - .collect(Collectors.toList()); - - // 构建触发器匹配器映射表 - this.triggerMatchers = convertMap(triggerMatchers, IotSceneRuleTriggerMatcher::getSupportedTriggerType, - Function.identity(), - (existing, replacement) -> { - log.warn("[IotSceneRuleMatcherManager][触发器类型({})存在多个匹配器,使用优先级更高的: {}]", - existing.getSupportedTriggerType(), - existing.getPriority() <= replacement.getPriority() ? - existing.getSupportedTriggerType() : replacement.getSupportedTriggerType()); - return existing.getPriority() <= replacement.getPriority() ? existing : replacement; - }, LinkedHashMap::new); - // 构建条件匹配器映射表 - this.conditionMatchers = convertMap(conditionMatchers, IotSceneRuleConditionMatcher::getSupportedConditionType, - Function.identity(), - (existing, replacement) -> { - log.warn("[IotSceneRuleMatcherManager][条件类型({})存在多个匹配器,使用优先级更高的: {}]", - existing.getSupportedConditionType(), - existing.getPriority() <= replacement.getPriority() ? - existing.getSupportedConditionType() : replacement.getSupportedConditionType()); - return existing.getPriority() <= replacement.getPriority() ? existing : replacement; - }, - LinkedHashMap::new); - - // 日志输出初始化信息 - log.info("[IotSceneRuleMatcherManager][初始化完成,共加载({})个匹配器,其中触发器匹配器({})个,条件匹配器({})个]", - allMatchers.size(), this.triggerMatchers.size(), this.conditionMatchers.size()); - this.triggerMatchers.forEach((type, matcher) -> - log.info("[IotSceneRuleMatcherManager][触发器匹配器类型: ({}), 优先级: ({})] ", type, matcher.getPriority())); - this.conditionMatchers.forEach((type, matcher) -> - log.info("[IotSceneRuleMatcherManager][条件匹配器类型: ({}), 优先级: ({})]", type, matcher.getPriority())); - } - - /** - * 检查触发器是否匹配消息(主条件匹配) - * - * @param message 设备消息 - * @param trigger 触发器配置 - * @return 是否匹配 - */ - public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { - if (message == null || trigger == null || trigger.getType() == null) { - log.debug("[isMatched][message({}) trigger({}) 参数无效]", message, trigger); - return false; - } - IotSceneRuleTriggerTypeEnum triggerType = IotSceneRuleTriggerTypeEnum.typeOf(trigger.getType()); - if (triggerType == null) { - log.warn("[isMatched][triggerType({}) 未知的触发器类型]", trigger.getType()); - return false; - } - IotSceneRuleTriggerMatcher matcher = triggerMatchers.get(triggerType); - if (matcher == null) { - log.warn("[isMatched][triggerType({}) 没有对应的匹配器]", triggerType); - return false; - } - - try { - return matcher.matches(message, trigger); - } catch (Exception e) { - log.error("[isMatched][触发器匹配异常] message: {}, trigger: {}", message, trigger, e); - return false; - } - } - - /** - * 检查子条件是否匹配消息 - * - * @param message 设备消息 - * @param condition 触发条件 - * @return 是否匹配 - */ - public boolean isConditionMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { - if (message == null || condition == null || condition.getType() == null) { - log.debug("[isConditionMatched][message({}) condition({}) 参数无效]", message, condition); - return false; - } - - // 根据条件类型查找对应的匹配器 - IotSceneRuleConditionTypeEnum conditionType = IotSceneRuleConditionTypeEnum.typeOf(condition.getType()); - if (conditionType == null) { - log.warn("[isConditionMatched][conditionType({}) 未知的条件类型]", condition.getType()); - return false; - } - IotSceneRuleConditionMatcher matcher = conditionMatchers.get(conditionType); - if (matcher == null) { - log.warn("[isConditionMatched][conditionType({}) 没有对应的匹配器]", conditionType); - return false; - } - - // 执行匹配逻辑 - try { - return matcher.matches(message, condition); - } catch (Exception e) { - log.error("[isConditionMatched][message({}) condition({}) 条件匹配异常]", message, condition, e); - return false; - } - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcher.java deleted file mode 100644 index 81c8fba597..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcher.java +++ /dev/null @@ -1,229 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; - -import cn.hutool.core.lang.Assert; -import cn.hutool.core.text.CharPool; -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.format.DateTimeFormatter; -import java.util.List; - -/** - * 当前时间条件匹配器 - *

- * 处理时间相关的子条件匹配逻辑 - * - * @author HUIHUI - */ -@Component -@Slf4j -public class CurrentTimeConditionMatcher implements IotSceneRuleConditionMatcher { - - /** - * 时间格式化器 - HH:mm:ss - */ - private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss"); - - /** - * 时间格式化器 - HH:mm - */ - private static final DateTimeFormatter TIME_FORMATTER_SHORT = DateTimeFormatter.ofPattern("HH:mm"); - - @Override - public IotSceneRuleConditionTypeEnum getSupportedConditionType() { - return IotSceneRuleConditionTypeEnum.CURRENT_TIME; - } - - @Override - public boolean matches(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { - // 1.1 基础参数校验 - if (!IotSceneRuleMatcherHelper.isBasicConditionValid(condition)) { - IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "条件基础参数无效"); - return false; - } - - // 1.2 检查操作符和参数是否有效 - if (!IotSceneRuleMatcherHelper.isConditionOperatorAndParamValid(condition)) { - IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "操作符或参数无效"); - return false; - } - - // 1.3 验证操作符是否为支持的时间操作符 - String operator = condition.getOperator(); - IotSceneRuleConditionOperatorEnum operatorEnum = IotSceneRuleConditionOperatorEnum.operatorOf(operator); - if (operatorEnum == null) { - IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "无效的操作符: " + operator); - return false; - } - - if (!isTimeOperator(operatorEnum)) { - IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "不支持的时间操作符: " + operator); - return false; - } - - // 2.1 执行时间匹配 - boolean matched = executeTimeMatching(operatorEnum, condition.getParam()); - - // 2.2 记录匹配结果 - if (matched) { - IotSceneRuleMatcherHelper.logConditionMatchSuccess(message, condition); - } else { - IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "时间条件不匹配"); - } - - return matched; - } - - /** - * 执行时间匹配逻辑 - * 直接实现时间条件匹配,不使用 Spring EL 表达式 - */ - private boolean executeTimeMatching(IotSceneRuleConditionOperatorEnum operatorEnum, String param) { - try { - LocalDateTime now = LocalDateTime.now(); - - if (isDateTimeOperator(operatorEnum)) { - // 日期时间匹配(时间戳) - long currentTimestamp = now.toEpochSecond(java.time.ZoneOffset.of("+8")); - return matchDateTime(currentTimestamp, operatorEnum, param); - } else { - // 当日时间匹配(HH:mm:ss) - return matchTime(now.toLocalTime(), operatorEnum, param); - } - } catch (Exception e) { - log.error("[executeTimeMatching][operatorEnum({}) param({}) 时间匹配异常]", operatorEnum, param, e); - return false; - } - } - - /** - * 判断是否为日期时间操作符 - */ - private boolean isDateTimeOperator(IotSceneRuleConditionOperatorEnum operatorEnum) { - return operatorEnum == IotSceneRuleConditionOperatorEnum.DATE_TIME_GREATER_THAN || - operatorEnum == IotSceneRuleConditionOperatorEnum.DATE_TIME_LESS_THAN || - operatorEnum == IotSceneRuleConditionOperatorEnum.DATE_TIME_BETWEEN; - } - - /** - * 判断是否为时间操作符 - */ - private boolean isTimeOperator(IotSceneRuleConditionOperatorEnum operatorEnum) { - return operatorEnum == IotSceneRuleConditionOperatorEnum.TIME_GREATER_THAN || - operatorEnum == IotSceneRuleConditionOperatorEnum.TIME_LESS_THAN || - operatorEnum == IotSceneRuleConditionOperatorEnum.TIME_BETWEEN || - isDateTimeOperator(operatorEnum); - } - - /** - * 匹配日期时间(时间戳) - * 直接实现时间戳比较逻辑 - */ - private boolean matchDateTime(long currentTimestamp, IotSceneRuleConditionOperatorEnum operatorEnum, String param) { - try { - long targetTimestamp = Long.parseLong(param); - switch (operatorEnum) { - case DATE_TIME_GREATER_THAN: - return currentTimestamp > targetTimestamp; - case DATE_TIME_LESS_THAN: - return currentTimestamp < targetTimestamp; - case DATE_TIME_BETWEEN: - return matchDateTimeBetween(currentTimestamp, param); - default: - log.warn("[matchDateTime][operatorEnum({}) 不支持的日期时间操作符]", operatorEnum); - return false; - } - } catch (Exception e) { - log.error("[matchDateTime][operatorEnum({}) param({}) 日期时间匹配异常]", operatorEnum, param, e); - return false; - } - } - - /** - * 匹配日期时间区间 - */ - private boolean matchDateTimeBetween(long currentTimestamp, String param) { - List timestampRange = StrUtil.splitTrim(param, CharPool.COMMA); - if (timestampRange.size() != 2) { - log.warn("[matchDateTimeBetween][param({}) 时间戳区间参数格式错误]", param); - return false; - } - long startTimestamp = Long.parseLong(timestampRange.get(0).trim()); - long endTimestamp = Long.parseLong(timestampRange.get(1).trim()); - return currentTimestamp >= startTimestamp && currentTimestamp <= endTimestamp; - } - - /** - * 匹配当日时间(HH:mm:ss) - * 直接实现时间比较逻辑 - */ - private boolean matchTime(LocalTime currentTime, IotSceneRuleConditionOperatorEnum operatorEnum, String param) { - try { - LocalTime targetTime = parseTime(param); - switch (operatorEnum) { - case TIME_GREATER_THAN: - return currentTime.isAfter(targetTime); - case TIME_LESS_THAN: - return currentTime.isBefore(targetTime); - case TIME_BETWEEN: - return matchTimeBetween(currentTime, param); - default: - log.warn("[matchTime][operatorEnum({}) 不支持的时间操作符]", operatorEnum); - return false; - } - } catch (Exception e) { - log.error("[matchTime][][operatorEnum({}) param({}) 时间解析异常]", operatorEnum, param, e); - return false; - } - } - - /** - * 匹配时间区间 - */ - private boolean matchTimeBetween(LocalTime currentTime, String param) { - List timeRange = StrUtil.splitTrim(param, CharPool.COMMA); - if (timeRange.size() != 2) { - log.warn("[matchTimeBetween][param({}) 时间区间参数格式错误]", param); - return false; - } - LocalTime startTime = parseTime(timeRange.get(0).trim()); - LocalTime endTime = parseTime(timeRange.get(1).trim()); - return !currentTime.isBefore(startTime) && !currentTime.isAfter(endTime); - } - - /** - * 解析时间字符串 - * 支持 HH:mm 和 HH:mm:ss 两种格式 - */ - private LocalTime parseTime(String timeStr) { - Assert.isFalse(StrUtil.isBlank(timeStr), "时间字符串不能为空"); - - try { - // 尝试不同的时间格式 - if (timeStr.length() == 5) { // HH:mm - return LocalTime.parse(timeStr, TIME_FORMATTER_SHORT); - } else if (timeStr.length() == 8) { // HH:mm:ss - return LocalTime.parse(timeStr, TIME_FORMATTER); - } else { - throw new IllegalArgumentException("时间格式长度不正确,期望 HH:mm 或 HH:mm:ss 格式"); - } - } catch (Exception e) { - log.error("[parseTime][timeStr({}) 时间格式解析失败]", timeStr, e); - throw new IllegalArgumentException("时间格式无效: " + timeStr, e); - } - } - - @Override - public int getPriority() { - return 40; // 较低优先级 - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java deleted file mode 100644 index 4a8a8ab6f5..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java +++ /dev/null @@ -1,68 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; - -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.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; -import org.springframework.stereotype.Component; - -/** - * 设备属性条件匹配器 - *

- * 处理设备属性相关的子条件匹配逻辑 - * - * @author HUIHUI - */ -@Component -public class DevicePropertyConditionMatcher implements IotSceneRuleConditionMatcher { - - @Override - public IotSceneRuleConditionTypeEnum getSupportedConditionType() { - return IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY; - } - - @Override - public boolean matches(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { - // 1.1 基础参数校验 - if (!IotSceneRuleMatcherHelper.isBasicConditionValid(condition)) { - IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "条件基础参数无效"); - return false; - } - - // 1.2 检查标识符是否匹配 - String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); - if (!IotSceneRuleMatcherHelper.isIdentifierMatched(condition.getIdentifier(), messageIdentifier)) { - IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "标识符不匹配,期望: " + condition.getIdentifier() + ", 实际: " + messageIdentifier); - return false; - } - - // 1.3 检查操作符和参数是否有效 - if (!IotSceneRuleMatcherHelper.isConditionOperatorAndParamValid(condition)) { - IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "操作符或参数无效"); - return false; - } - - // 2.1. 获取属性值 - Object propertyValue = message.getParams(); - if (propertyValue == null) { - IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "消息中属性值为空"); - return false; - } - - // 2.2 使用条件评估器进行匹配 - boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(propertyValue, condition.getOperator(), condition.getParam()); - if (matched) { - IotSceneRuleMatcherHelper.logConditionMatchSuccess(message, condition); - } else { - IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "设备属性条件不匹配"); - } - return matched; - } - - @Override - public int getPriority() { - return 25; // 中等优先级 - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java deleted file mode 100644 index d5bb97a53e..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java +++ /dev/null @@ -1,60 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; - -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; -import org.springframework.stereotype.Component; - -/** - * 设备状态条件匹配器 - *

- * 处理设备状态相关的子条件匹配逻辑 - * - * @author HUIHUI - */ -@Component -public class DeviceStateConditionMatcher implements IotSceneRuleConditionMatcher { - - @Override - public IotSceneRuleConditionTypeEnum getSupportedConditionType() { - return IotSceneRuleConditionTypeEnum.DEVICE_STATE; - } - - @Override - public boolean matches(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { - // 1.1 基础参数校验 - if (!IotSceneRuleMatcherHelper.isBasicConditionValid(condition)) { - IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "条件基础参数无效"); - return false; - } - - // 1.2 检查操作符和参数是否有效 - if (!IotSceneRuleMatcherHelper.isConditionOperatorAndParamValid(condition)) { - IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "操作符或参数无效"); - return false; - } - - // 2.1 获取设备状态值 - Object stateValue = message.getParams(); - if (stateValue == null) { - IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "消息中设备状态值为空"); - return false; - } - - // 2.2 使用条件评估器进行匹配 - boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(stateValue, condition.getOperator(), condition.getParam()); - if (matched) { - IotSceneRuleMatcherHelper.logConditionMatchSuccess(message, condition); - } else { - IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "设备状态条件不匹配"); - } - return matched; - } - - @Override - public int getPriority() { - return 30; // 中等优先级 - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotSceneRuleConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotSceneRuleConditionMatcher.java deleted file mode 100644 index 875e8b1563..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotSceneRuleConditionMatcher.java +++ /dev/null @@ -1,38 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; - -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcher; - -/** - * IoT 场景规则条件匹配器接口 - *

- * 专门处理子条件的匹配逻辑,如设备状态、属性值、时间条件等 - *

- * 条件匹配器负责判断设备消息是否满足场景规则的附加条件, - * 在触发器匹配成功后进行进一步的条件筛选 - * - * @author HUIHUI - */ -public interface IotSceneRuleConditionMatcher extends IotSceneRuleMatcher { - - /** - * 获取支持的条件类型 - * - * @return 条件类型枚举 - */ - IotSceneRuleConditionTypeEnum getSupportedConditionType(); - - /** - * 检查条件是否匹配消息 - *

- * 判断设备消息是否满足指定的触发条件 - * - * @param message 设备消息 - * @param condition 触发条件 - * @return 是否匹配 - */ - boolean matches(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition); - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcher.java deleted file mode 100644 index 1ab1bb9d26..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcher.java +++ /dev/null @@ -1,75 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; - -import cn.hutool.core.util.StrUtil; -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.util.IotDeviceMessageUtils; -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; -import org.springframework.stereotype.Component; - -/** - * 设备事件上报触发器匹配器 - *

- * 处理设备事件上报的触发器匹配逻辑 - * - * @author HUIHUI - */ -@Component -public class DeviceEventPostTriggerMatcher implements IotSceneRuleTriggerMatcher { - - @Override - public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { - return IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST; - } - - @Override - public boolean matches(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { - // 1.1 基础参数校验 - if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); - return false; - } - - // 1.2 检查消息方法是否匹配 - if (!IotDeviceMessageMethodEnum.EVENT_POST.getMethod().equals(message.getMethod())) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + - IotDeviceMessageMethodEnum.EVENT_POST.getMethod() + ", 实际: " + message.getMethod()); - return false; - } - - // 1.3 检查标识符是否匹配 - String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); - if (!IotSceneRuleMatcherHelper.isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + - trigger.getIdentifier() + ", 实际: " + messageIdentifier); - return false; - } - - // 2. 对于事件触发器,通常不需要检查操作符和值,只要事件发生即匹配 - // 但如果配置了操作符和值,则需要进行条件匹配 - if (StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue())) { - Object eventData = message.getData(); - if (eventData == null) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中事件数据为空"); - return false; - } - - boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(eventData, trigger.getOperator(), trigger.getValue()); - if (!matched) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "事件数据条件不匹配"); - return false; - } - } - - IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger); - return true; - } - - @Override - public int getPriority() { - return 30; // 中等优先级 - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java deleted file mode 100644 index 6eccdab427..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java +++ /dev/null @@ -1,77 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; - -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.util.IotDeviceMessageUtils; -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; -import org.springframework.stereotype.Component; - -/** - * 设备属性上报触发器匹配器 - *

- * 处理设备属性数据上报的触发器匹配逻辑 - * - * @author HUIHUI - */ -@Component -public class DevicePropertyPostTriggerMatcher implements IotSceneRuleTriggerMatcher { - - @Override - public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { - return IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST; - } - - @Override - public boolean matches(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { - // 1.1 基础参数校验 - if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); - return false; - } - - // 1.2 检查消息方法是否匹配 - if (!IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod().equals(message.getMethod())) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + - IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod() + ", 实际: " + message.getMethod()); - return false; - } - - // 1.3 检查标识符是否匹配 - String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); - if (!IotSceneRuleMatcherHelper.isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + - trigger.getIdentifier() + ", 实际: " + messageIdentifier); - return false; - } - - // 1.4 检查操作符和值是否有效 - if (!IotSceneRuleMatcherHelper.isTriggerOperatorAndValueValid(trigger)) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "操作符或值无效"); - return false; - } - - // 2.1 获取属性值 - Object propertyValue = message.getParams(); - if (propertyValue == null) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中属性值为空"); - return false; - } - - // 2.2 使用条件评估器进行匹配 - boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(propertyValue, trigger.getOperator(), trigger.getValue()); - if (matched) { - IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger); - } else { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "属性值条件不匹配"); - } - return matched; - } - - @Override - public int getPriority() { - return 20; // 中等优先级 - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcher.java deleted file mode 100644 index e0caba2d37..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcher.java +++ /dev/null @@ -1,59 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; - -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.util.IotDeviceMessageUtils; -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; -import org.springframework.stereotype.Component; - -/** - * 设备服务调用触发器匹配器 - *

- * 处理设备服务调用的触发器匹配逻辑 - * - * @author HUIHUI - */ -@Component -public class DeviceServiceInvokeTriggerMatcher implements IotSceneRuleTriggerMatcher { - - @Override - public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { - return IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE; - } - - @Override - public boolean matches(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { - // 1.1 基础参数校验 - if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); - return false; - } - - // 1.2 检查消息方法是否匹配 - if (!IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod().equals(message.getMethod())) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod() + ", 实际: " + message.getMethod()); - return false; - } - - // 1.3 检查标识符是否匹配 - String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); - if (!IotSceneRuleMatcherHelper.isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); - return false; - } - - // 2. 对于服务调用触发器,通常只需要匹配服务标识符即可 - // 不需要检查操作符和值,因为服务调用本身就是触发条件 - // TODO @puhui999: 服务调用时校验输入参数是否匹配条件 - IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger); - return true; - } - - @Override - public int getPriority() { - return 40; // 较低优先级 - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java deleted file mode 100644 index edd3c4e907..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java +++ /dev/null @@ -1,69 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; - -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.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; -import org.springframework.stereotype.Component; - -/** - * 设备状态更新触发器匹配器 - *

- * 处理设备上下线状态变更的触发器匹配逻辑 - * - * @author HUIHUI - */ -@Component -public class DeviceStateUpdateTriggerMatcher implements IotSceneRuleTriggerMatcher { - - @Override - public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { - return IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE; - } - - @Override - public boolean matches(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { - // 1.1 基础参数校验 - if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); - return false; - } - - // 1.2 检查消息方法是否匹配 - if (!IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod().equals(message.getMethod())) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + - IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod() + ", 实际: " + message.getMethod()); - return false; - } - - // 1.3 检查操作符和值是否有效 - if (!IotSceneRuleMatcherHelper.isTriggerOperatorAndValueValid(trigger)) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "操作符或值无效"); - return false; - } - - // 2.1 获取设备状态值 - Object stateValue = message.getParams(); - if (stateValue == null) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中设备状态值为空"); - return false; - } - - // 2.2 使用条件评估器进行匹配 - // TODO @puhui999: 状态匹配重新实现 - boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(stateValue, trigger.getOperator(), trigger.getValue()); - if (matched) { - IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger); - } else { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "状态值条件不匹配"); - } - return matched; - } - - @Override - public int getPriority() { - return 10; // 高优先级 - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotSceneRuleTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotSceneRuleTriggerMatcher.java deleted file mode 100644 index 89de00a686..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotSceneRuleTriggerMatcher.java +++ /dev/null @@ -1,38 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; - -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcher; - -/** - * IoT 场景规则触发器匹配器接口 - *

- * 专门处理主触发条件的匹配逻辑,如设备消息类型、定时器等 - *

- * 触发器匹配器负责判断设备消息是否满足场景规则的主触发条件, - * 是场景规则执行的第一道门槛 - * - * @author HUIHUI - */ -public interface IotSceneRuleTriggerMatcher extends IotSceneRuleMatcher { - - /** - * 获取支持的触发器类型 - * - * @return 触发器类型枚举 - */ - IotSceneRuleTriggerTypeEnum getSupportedTriggerType(); - - /** - * 检查触发器是否匹配消息 - *

- * 判断设备消息是否满足指定的触发器条件 - * - * @param message 设备消息 - * @param trigger 触发器配置 - * @return 是否匹配 - */ - boolean matches(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger); - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcher.java deleted file mode 100644 index 794f8d6ae6..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcher.java +++ /dev/null @@ -1,57 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; - -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; -import org.quartz.CronExpression; -import org.springframework.stereotype.Component; - -/** - * 定时触发器匹配器 - *

- * 处理定时触发的触发器匹配逻辑 - * 注意:定时触发器不依赖设备消息,主要用于定时任务场景 - * - * @author HUIHUI - */ -@Component -public class TimerTriggerMatcher implements IotSceneRuleTriggerMatcher { - - @Override - public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { - return IotSceneRuleTriggerTypeEnum.TIMER; - } - - @Override - public boolean matches(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { - // 1.1 基础参数校验 - if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); - return false; - } - - // 1.2 检查 CRON 表达式是否存在 - if (StrUtil.isBlank(trigger.getCronExpression())) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "定时触发器缺少 CRON 表达式"); - return false; - } - - // 1.3 定时触发器通常不依赖具体的设备消息 - // 它是通过定时任务调度器触发的,这里主要是验证配置的有效性 - if (!CronExpression.isValidExpression(trigger.getCronExpression())) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "CRON 表达式格式无效: " + trigger.getCronExpression()); - return false; - } - - IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger); - return true; - } - - @Override - public int getPriority() { - return 50; // 最低优先级,因为定时触发器不依赖消息 - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDeviceMessageMapper.xml b/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDeviceMessageMapper.xml deleted file mode 100644 index deef23b5c7..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDeviceMessageMapper.xml +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - CREATE STABLE IF NOT EXISTS device_message ( - ts TIMESTAMP, - id NCHAR(50), - report_time TIMESTAMP, - tenant_id BIGINT, - server_id NCHAR(50), - upstream BOOL, - reply BOOL, - identifier NCHAR(100), - request_id NCHAR(50), - method NCHAR(100), - params NCHAR(2048), - data NCHAR(2048), - code INT, - msg NCHAR(256) - ) TAGS ( - device_id BIGINT - ) - - - - - - INSERT INTO device_message_${deviceId} ( - ts, id, report_time, tenant_id, server_id, - upstream, reply, identifier, request_id, method, - params, data, code, msg - ) - USING device_message - TAGS (#{deviceId}) - VALUES ( - NOW, #{id}, #{reportTime}, #{tenantId}, #{serverId}, - #{upstream}, #{reply}, #{identifier}, #{requestId}, #{method}, - #{params}, #{data}, #{code}, #{msg} - ) - - - - - - - - - - - \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceSimpleTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceSimpleTest.java deleted file mode 100644 index 056794b797..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceSimpleTest.java +++ /dev/null @@ -1,211 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene; - -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleSaveReqVO; -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotSceneRuleMapper; -import cn.iocoder.yudao.module.iot.framework.job.core.IotSchedulerManager; -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.scene.action.IotSceneRuleAction; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; - -import java.util.Collections; -import java.util.List; - -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - -/** - * {@link IotSceneRuleServiceImpl} 的简化单元测试类 - * 使用 Mockito 进行纯单元测试,不依赖 Spring 容器 - * - * @author 芋道源码 - */ -public class IotSceneRuleServiceSimpleTest extends BaseMockitoUnitTest { - - @InjectMocks - private IotSceneRuleServiceImpl sceneRuleService; - - @Mock - private IotSceneRuleMapper sceneRuleMapper; - - @Mock - private List sceneRuleActions; - - @Mock - private IotSchedulerManager schedulerManager; - - @Mock - private IotProductService productService; - - @Mock - private IotDeviceService deviceService; - - @Test - public void testCreateScene_Rule_success() { - // 准备参数 - IotSceneRuleSaveReqVO createReqVO = randomPojo(IotSceneRuleSaveReqVO.class, o -> { - o.setId(null); - o.setStatus(CommonStatusEnum.ENABLE.getStatus()); - o.setTriggers(Collections.singletonList(randomPojo(IotSceneRuleDO.Trigger.class))); - o.setActions(Collections.singletonList(randomPojo(IotSceneRuleDO.Action.class))); - }); - - // Mock 行为 - Long expectedId = randomLongId(); - when(sceneRuleMapper.insert(any(IotSceneRuleDO.class))).thenAnswer(invocation -> { - IotSceneRuleDO sceneRule = invocation.getArgument(0); - sceneRule.setId(expectedId); - return 1; - }); - - // 调用 - Long sceneRuleId = sceneRuleService.createSceneRule(createReqVO); - - // 断言 - assertEquals(expectedId, sceneRuleId); - verify(sceneRuleMapper, times(1)).insert(any(IotSceneRuleDO.class)); - } - - @Test - public void testUpdateScene_Rule_success() { - // 准备参数 - Long id = randomLongId(); - IotSceneRuleSaveReqVO updateReqVO = randomPojo(IotSceneRuleSaveReqVO.class, o -> { - o.setId(id); - o.setStatus(CommonStatusEnum.ENABLE.getStatus()); - o.setTriggers(Collections.singletonList(randomPojo(IotSceneRuleDO.Trigger.class))); - o.setActions(Collections.singletonList(randomPojo(IotSceneRuleDO.Action.class))); - }); - - // Mock 行为 - IotSceneRuleDO existingSceneRule = randomPojo(IotSceneRuleDO.class, o -> o.setId(id)); - when(sceneRuleMapper.selectById(id)).thenReturn(existingSceneRule); - when(sceneRuleMapper.updateById(any(IotSceneRuleDO.class))).thenReturn(1); - - // 调用 - assertDoesNotThrow(() -> sceneRuleService.updateSceneRule(updateReqVO)); - - // 验证 - verify(sceneRuleMapper, times(1)).selectById(id); - verify(sceneRuleMapper, times(1)).updateById(any(IotSceneRuleDO.class)); - } - - @Test - public void testDeleteSceneRule_success() { - // 准备参数 - Long id = randomLongId(); - - // Mock 行为 - IotSceneRuleDO existingSceneRule = randomPojo(IotSceneRuleDO.class, o -> o.setId(id)); - when(sceneRuleMapper.selectById(id)).thenReturn(existingSceneRule); - when(sceneRuleMapper.deleteById(id)).thenReturn(1); - - // 调用 - assertDoesNotThrow(() -> sceneRuleService.deleteSceneRule(id)); - - // 验证 - verify(sceneRuleMapper, times(1)).selectById(id); - verify(sceneRuleMapper, times(1)).deleteById(id); - } - - @Test - public void testGetSceneRule() { - // 准备参数 - Long id = randomLongId(); - IotSceneRuleDO expectedSceneRule = randomPojo(IotSceneRuleDO.class, o -> o.setId(id)); - - // Mock 行为 - when(sceneRuleMapper.selectById(id)).thenReturn(expectedSceneRule); - - // 调用 - IotSceneRuleDO result = sceneRuleService.getSceneRule(id); - - // 断言 - assertEquals(expectedSceneRule, result); - verify(sceneRuleMapper, times(1)).selectById(id); - } - - @Test - public void testUpdateSceneRuleStatus_success() { - // 准备参数 - Long id = randomLongId(); - Integer status = CommonStatusEnum.DISABLE.getStatus(); - - // Mock 行为 - IotSceneRuleDO existingSceneRule = randomPojo(IotSceneRuleDO.class, o -> { - o.setId(id); - o.setStatus(CommonStatusEnum.ENABLE.getStatus()); - }); - when(sceneRuleMapper.selectById(id)).thenReturn(existingSceneRule); - when(sceneRuleMapper.updateById(any(IotSceneRuleDO.class))).thenReturn(1); - - // 调用 - assertDoesNotThrow(() -> sceneRuleService.updateSceneRuleStatus(id, status)); - - // 验证 - verify(sceneRuleMapper, times(1)).selectById(id); - verify(sceneRuleMapper, times(1)).updateById(any(IotSceneRuleDO.class)); - } - - @Test - public void testExecuteSceneRuleByTimer_success() { - // 准备参数 - Long id = randomLongId(); - - // Mock 行为 - IotSceneRuleDO sceneRule = randomPojo(IotSceneRuleDO.class, o -> { - o.setId(id); - o.setStatus(CommonStatusEnum.ENABLE.getStatus()); - }); - when(sceneRuleMapper.selectById(id)).thenReturn(sceneRule); - - // 调用 - assertDoesNotThrow(() -> sceneRuleService.executeSceneRuleByTimer(id)); - - // 验证 - verify(sceneRuleMapper, times(1)).selectById(id); - } - - @Test - public void testExecuteSceneRuleByTimer_notExists() { - // 准备参数 - Long id = randomLongId(); - - // Mock 行为 - when(sceneRuleMapper.selectById(id)).thenReturn(null); - - // 调用 - 不存在的场景规则应该不会抛异常,只是记录日志 - assertDoesNotThrow(() -> sceneRuleService.executeSceneRuleByTimer(id)); - - // 验证 - verify(sceneRuleMapper, times(1)).selectById(id); - } - - @Test - public void testExecuteSceneRuleByTimer_disabled() { - // 准备参数 - Long id = randomLongId(); - - // Mock 行为 - IotSceneRuleDO sceneRule = randomPojo(IotSceneRuleDO.class, o -> { - o.setId(id); - o.setStatus(CommonStatusEnum.DISABLE.getStatus()); - }); - when(sceneRuleMapper.selectById(id)).thenReturn(sceneRule); - - // 调用 - 禁用的场景规则应该不会执行,只是记录日志 - assertDoesNotThrow(() -> sceneRuleService.executeSceneRuleByTimer(id)); - - // 验证 - verify(sceneRuleMapper, times(1)).selectById(id); - } -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcherTest.java deleted file mode 100644 index 4b4bdfd029..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcherTest.java +++ /dev/null @@ -1,338 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; - -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; - -import java.time.LocalDateTime; -import java.time.ZoneOffset; - -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; -import static org.junit.jupiter.api.Assertions.*; - -/** - * {@link CurrentTimeConditionMatcher} 的单元测试 - * - * @author HUIHUI - */ -public class CurrentTimeConditionMatcherTest extends BaseMockitoUnitTest { - - @InjectMocks - private CurrentTimeConditionMatcher matcher; - - @Test - public void testGetSupportedConditionType() { - // 调用 - IotSceneRuleConditionTypeEnum result = matcher.getSupportedConditionType(); - - // 断言 - assertEquals(IotSceneRuleConditionTypeEnum.CURRENT_TIME, result); - } - - @Test - public void testGetPriority() { - // 调用 - int result = matcher.getPriority(); - - // 断言 - assertEquals(40, result); - } - - @Test - public void testIsEnabled() { - // 调用 - boolean result = matcher.isEnabled(); - - // 断言 - assertTrue(result); - } - - // ========== 时间戳条件测试 ========== - - @Test - public void testMatches_DateTimeGreaterThan_success() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(); - long pastTimestamp = LocalDateTime.now().minusHours(1).toEpochSecond(ZoneOffset.of("+8")); - IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition( - IotSceneRuleConditionOperatorEnum.DATE_TIME_GREATER_THAN.getOperator(), - String.valueOf(pastTimestamp) - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_DateTimeGreaterThan_fail() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(); - long futureTimestamp = LocalDateTime.now().plusHours(1).toEpochSecond(ZoneOffset.of("+8")); - IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition( - IotSceneRuleConditionOperatorEnum.DATE_TIME_GREATER_THAN.getOperator(), - String.valueOf(futureTimestamp) - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_DateTimeLessThan_success() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(); - long futureTimestamp = LocalDateTime.now().plusHours(1).toEpochSecond(ZoneOffset.of("+8")); - IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition( - IotSceneRuleConditionOperatorEnum.DATE_TIME_LESS_THAN.getOperator(), - String.valueOf(futureTimestamp) - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_DateTimeBetween_success() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(); - long startTimestamp = LocalDateTime.now().minusHours(1).toEpochSecond(ZoneOffset.of("+8")); - long endTimestamp = LocalDateTime.now().plusHours(1).toEpochSecond(ZoneOffset.of("+8")); - IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition( - IotSceneRuleConditionOperatorEnum.DATE_TIME_BETWEEN.getOperator(), - startTimestamp + "," + endTimestamp - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_DateTimeBetween_fail() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(); - long startTimestamp = LocalDateTime.now().plusHours(1).toEpochSecond(ZoneOffset.of("+8")); - long endTimestamp = LocalDateTime.now().plusHours(2).toEpochSecond(ZoneOffset.of("+8")); - IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition( - IotSceneRuleConditionOperatorEnum.DATE_TIME_BETWEEN.getOperator(), - startTimestamp + "," + endTimestamp - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertFalse(result); - } - - // ========== 当日时间条件测试 ========== - - @Test - public void testMatches_TimeGreaterThan_earlyMorning() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(); - IotSceneRuleDO.TriggerCondition condition = createTimeCondition( - IotSceneRuleConditionOperatorEnum.TIME_GREATER_THAN.getOperator(), - "06:00:00" // 早上6点 - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - // 结果取决于当前时间,如果当前时间大于6点则为true - assertNotNull(result); - } - - @Test - public void testMatches_TimeLessThan_lateNight() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(); - IotSceneRuleDO.TriggerCondition condition = createTimeCondition( - IotSceneRuleConditionOperatorEnum.TIME_LESS_THAN.getOperator(), - "23:59:59" // 晚上11点59分59秒 - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - // 大部分情况下应该为true,除非在午夜前1秒运行测试 - assertNotNull(result); - } - - @Test - public void testMatches_TimeBetween_allDay() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(); - IotSceneRuleDO.TriggerCondition condition = createTimeCondition( - IotSceneRuleConditionOperatorEnum.TIME_BETWEEN.getOperator(), - "00:00:00,23:59:59" // 全天 - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertTrue(result); // 全天范围应该总是匹配 - } - - @Test - public void testMatches_TimeBetween_workingHours() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(); - IotSceneRuleDO.TriggerCondition condition = createTimeCondition( - IotSceneRuleConditionOperatorEnum.TIME_BETWEEN.getOperator(), - "09:00:00,17:00:00" // 工作时间 - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - // 结果取决于当前时间是否在工作时间内 - assertNotNull(result); - } - - // ========== 异常情况测试 ========== - - @Test - public void testMatches_nullCondition() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(); - - // 调用 - boolean result = matcher.matches(message, null); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_nullConditionType() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(); - IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); - condition.setType(null); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_invalidOperator() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(); - IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); - condition.setType(IotSceneRuleConditionTypeEnum.CURRENT_TIME.getType()); - condition.setOperator(randomString()); // 随机无效操作符 - condition.setParam("12:00:00"); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_invalidTimeFormat() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(); - IotSceneRuleDO.TriggerCondition condition = createTimeCondition( - IotSceneRuleConditionOperatorEnum.TIME_GREATER_THAN.getOperator(), - randomString() // 随机无效时间格式 - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_invalidTimestampFormat() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(); - IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition( - IotSceneRuleConditionOperatorEnum.DATE_TIME_GREATER_THAN.getOperator(), - randomString() // 随机无效时间戳格式 - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_invalidBetweenFormat() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(); - IotSceneRuleDO.TriggerCondition condition = createTimeCondition( - IotSceneRuleConditionOperatorEnum.TIME_BETWEEN.getOperator(), - "09:00:00" // 缺少结束时间 - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertFalse(result); - } - - // ========== 辅助方法 ========== - - /** - * 创建设备消息 - */ - private IotDeviceMessage createDeviceMessage() { - IotDeviceMessage message = new IotDeviceMessage(); - message.setDeviceId(randomLongId()); - return message; - } - - /** - * 创建日期时间条件 - */ - private IotSceneRuleDO.TriggerCondition createDateTimeCondition(String operator, String param) { - IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); - condition.setType(IotSceneRuleConditionTypeEnum.CURRENT_TIME.getType()); - condition.setOperator(operator); - condition.setParam(param); - return condition; - } - - /** - * 创建当日时间条件 - */ - private IotSceneRuleDO.TriggerCondition createTimeCondition(String operator, String param) { - IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); - condition.setType(IotSceneRuleConditionTypeEnum.CURRENT_TIME.getType()); - condition.setOperator(operator); - condition.setParam(param); - return condition; - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java deleted file mode 100644 index c4edf34361..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java +++ /dev/null @@ -1,424 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; - -import cn.hutool.core.map.MapUtil; -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; - -import java.util.HashMap; -import java.util.Map; - -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; -import static org.junit.jupiter.api.Assertions.*; - -/** - * {@link DevicePropertyConditionMatcher} 的单元测试 - * - * @author HUIHUI - */ -public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { - - @InjectMocks - private DevicePropertyConditionMatcher matcher; - - @Test - public void testGetSupportedConditionType() { - // 调用 - IotSceneRuleConditionTypeEnum result = matcher.getSupportedConditionType(); - - // 断言 - assertEquals(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY, result); - } - - @Test - public void testGetPriority() { - // 调用 - int result = matcher.getPriority(); - - // 断言 - assertEquals(20, result); - } - - @Test - public void testIsEnabled() { - // 调用 - boolean result = matcher.isEnabled(); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_temperatureEquals_success() { - // 准备参数 - String propertyName = "temperature"; - Double propertyValue = 25.5; - Map properties = MapUtil.of(propertyName, propertyValue); - IotDeviceMessage message = createDeviceMessage(properties); - IotSceneRuleDO.TriggerCondition condition = createValidCondition( - propertyName, - IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), - String.valueOf(propertyValue) - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_humidityGreaterThan_success() { - // 准备参数 - String propertyName = "humidity"; - Integer propertyValue = 75; - Integer compareValue = 70; - Map properties = MapUtil.of(propertyName, propertyValue); - IotDeviceMessage message = createDeviceMessage(properties); - IotSceneRuleDO.TriggerCondition condition = createValidCondition( - propertyName, - IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), - String.valueOf(compareValue) - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_pressureLessThan_success() { - // 准备参数 - String propertyName = "pressure"; - Double propertyValue = 1010.5; - Integer compareValue = 1020; - Map properties = MapUtil.of(propertyName, propertyValue); - IotDeviceMessage message = createDeviceMessage(properties); - IotSceneRuleDO.TriggerCondition condition = createValidCondition( - propertyName, - IotSceneRuleConditionOperatorEnum.LESS_THAN.getOperator(), - String.valueOf(compareValue) - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_statusNotEquals_success() { - // 准备参数 - String propertyName = "status"; - String propertyValue = "active"; - String compareValue = "inactive"; - Map properties = MapUtil.of(propertyName, propertyValue); - IotDeviceMessage message = createDeviceMessage(properties); - IotSceneRuleDO.TriggerCondition condition = createValidCondition( - propertyName, - IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(), - compareValue - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_propertyMismatch_fail() { - // 准备参数 - String propertyName = "temperature"; - Double propertyValue = 15.0; - Integer compareValue = 20; - Map properties = MapUtil.of(propertyName, propertyValue); - IotDeviceMessage message = createDeviceMessage(properties); - IotSceneRuleDO.TriggerCondition condition = createValidCondition( - propertyName, - IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), - String.valueOf(compareValue) - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_propertyNotFound_fail() { - // 准备参数 - Map properties = MapUtil.of("temperature", 25.5); - IotDeviceMessage message = createDeviceMessage(properties); - IotSceneRuleDO.TriggerCondition condition = createValidCondition( - randomString(), // 随机不存在的属性名 - IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), - "50" - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_nullCondition_fail() { - // 准备参数 - Map properties = MapUtil.of("temperature", 25.5); - IotDeviceMessage message = createDeviceMessage(properties); - - // 调用 - boolean result = matcher.matches(message, null); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_nullConditionType_fail() { - // 准备参数 - Map properties = MapUtil.of("temperature", 25.5); - IotDeviceMessage message = createDeviceMessage(properties); - IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); - condition.setType(null); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_missingIdentifier_fail() { - // 准备参数 - Map properties = MapUtil.of("temperature", 25.5); - IotDeviceMessage message = createDeviceMessage(properties); - IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); - condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType()); - condition.setIdentifier(null); // 缺少标识符 - condition.setOperator(IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator()); - condition.setParam("20"); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_missingOperator_fail() { - // 准备参数 - Map properties = MapUtil.of("temperature", 25.5); - IotDeviceMessage message = createDeviceMessage(properties); - IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); - condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType()); - condition.setIdentifier("temperature"); - condition.setOperator(null); // 缺少操作符 - condition.setParam("20"); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_missingParam_fail() { - // 准备参数 - Map properties = MapUtil.of("temperature", 25.5); - IotDeviceMessage message = createDeviceMessage(properties); - IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); - condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType()); - condition.setIdentifier("temperature"); - condition.setOperator(IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator()); - condition.setParam(null); // 缺少参数 - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_nullMessage_fail() { - // 准备参数 - IotSceneRuleDO.TriggerCondition condition = createValidCondition( - "temperature", - IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), - "20" - ); - - // 调用 - boolean result = matcher.matches(null, condition); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_nullDeviceProperties_fail() { - // 准备参数 - IotDeviceMessage message = new IotDeviceMessage(); - message.setParams(null); - IotSceneRuleDO.TriggerCondition condition = createValidCondition( - "temperature", - IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), - "20" - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_voltageGreaterThanOrEquals_success() { - // 准备参数 - String propertyName = "voltage"; - Double propertyValue = 12.0; - Map properties = MapUtil.of(propertyName, propertyValue); - IotDeviceMessage message = createDeviceMessage(properties); - IotSceneRuleDO.TriggerCondition condition = createValidCondition( - propertyName, - IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS.getOperator(), - String.valueOf(propertyValue) - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_currentLessThanOrEquals_success() { - // 准备参数 - String propertyName = "current"; - Double propertyValue = 2.5; - Double compareValue = 3.0; - Map properties = MapUtil.of(propertyName, propertyValue); - IotDeviceMessage message = createDeviceMessage(properties); - IotSceneRuleDO.TriggerCondition condition = createValidCondition( - propertyName, - IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS.getOperator(), - String.valueOf(compareValue) - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_stringProperty_success() { - // 准备参数 - String propertyName = "mode"; - String propertyValue = "auto"; - Map properties = MapUtil.of(propertyName, propertyValue); - IotDeviceMessage message = createDeviceMessage(properties); - IotSceneRuleDO.TriggerCondition condition = createValidCondition( - propertyName, - IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), - propertyValue - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_booleanProperty_success() { - // 准备参数 - String propertyName = "enabled"; - Boolean propertyValue = true; - Map properties = MapUtil.of(propertyName, propertyValue); - IotDeviceMessage message = createDeviceMessage(properties); - IotSceneRuleDO.TriggerCondition condition = createValidCondition( - propertyName, - IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), - String.valueOf(propertyValue) - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_multipleProperties_success() { - // 准备参数 - Map properties = MapUtil.builder(new HashMap()) - .put("temperature", 25.5) - .put("humidity", 60) - .put("status", "active") - .put("enabled", true) - .build(); - IotDeviceMessage message = createDeviceMessage(properties); - String targetProperty = "humidity"; - Integer targetValue = 60; - IotSceneRuleDO.TriggerCondition condition = createValidCondition( - targetProperty, - IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), - String.valueOf(targetValue) - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertTrue(result); - } - - // ========== 辅助方法 ========== - - /** - * 创建设备消息 - */ - private IotDeviceMessage createDeviceMessage(Map properties) { - IotDeviceMessage message = new IotDeviceMessage(); - message.setDeviceId(randomLongId()); - message.setParams(properties); - return message; - } - - /** - * 创建有效的条件 - */ - private IotSceneRuleDO.TriggerCondition createValidCondition(String identifier, String operator, String param) { - IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); - condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType()); - condition.setIdentifier(identifier); - condition.setOperator(operator); - condition.setParam(param); - return condition; - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcherTest.java deleted file mode 100644 index 25ea571528..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcherTest.java +++ /dev/null @@ -1,356 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; - -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; - -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; -import static org.junit.jupiter.api.Assertions.*; - -/** - * {@link DeviceStateConditionMatcher} 的单元测试 - * - * @author HUIHUI - */ -public class DeviceStateConditionMatcherTest extends BaseMockitoUnitTest { - - @InjectMocks - private DeviceStateConditionMatcher matcher; - - @Test - public void testGetSupportedConditionType() { - // 调用 - IotSceneRuleConditionTypeEnum result = matcher.getSupportedConditionType(); - - // 断言 - assertEquals(IotSceneRuleConditionTypeEnum.DEVICE_STATE, result); - } - - @Test - public void testGetPriority() { - // 调用 - int result = matcher.getPriority(); - - // 断言 - assertEquals(30, result); - } - - @Test - public void testIsEnabled() { - // 调用 - boolean result = matcher.isEnabled(); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_onlineState_success() { - // 准备参数 - IotDeviceStateEnum deviceState = IotDeviceStateEnum.ONLINE; - IotDeviceMessage message = createDeviceMessage(deviceState.getState()); - IotSceneRuleDO.TriggerCondition condition = createValidCondition( - IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), - deviceState.getState().toString() - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_offlineState_success() { - // 准备参数 - IotDeviceStateEnum deviceState = IotDeviceStateEnum.OFFLINE; - IotDeviceMessage message = createDeviceMessage(deviceState.getState()); - IotSceneRuleDO.TriggerCondition condition = createValidCondition( - IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), - deviceState.getState().toString() - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_inactiveState_success() { - // 准备参数 - IotDeviceStateEnum deviceState = IotDeviceStateEnum.INACTIVE; - IotDeviceMessage message = createDeviceMessage(deviceState.getState()); - IotSceneRuleDO.TriggerCondition condition = createValidCondition( - IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), - deviceState.getState().toString() - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_stateMismatch_fail() { - // 准备参数 - IotDeviceStateEnum actualState = IotDeviceStateEnum.ONLINE; - IotDeviceStateEnum expectedState = IotDeviceStateEnum.OFFLINE; - IotDeviceMessage message = createDeviceMessage(actualState.getState()); - IotSceneRuleDO.TriggerCondition condition = createValidCondition( - IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), - expectedState.getState().toString() - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_notEqualsOperator_success() { - // 准备参数 - IotDeviceStateEnum actualState = IotDeviceStateEnum.ONLINE; - IotDeviceStateEnum compareState = IotDeviceStateEnum.OFFLINE; - IotDeviceMessage message = createDeviceMessage(actualState.getState()); - IotSceneRuleDO.TriggerCondition condition = createValidCondition( - IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(), - compareState.getState().toString() - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_greaterThanOperator_success() { - // 准备参数 - IotDeviceStateEnum actualState = IotDeviceStateEnum.OFFLINE; // 状态值为 2 - IotDeviceStateEnum compareState = IotDeviceStateEnum.ONLINE; // 状态值为 1 - IotDeviceMessage message = createDeviceMessage(actualState.getState()); - IotSceneRuleDO.TriggerCondition condition = createValidCondition( - IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), - compareState.getState().toString() - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_lessThanOperator_success() { - // 准备参数 - IotDeviceStateEnum actualState = IotDeviceStateEnum.INACTIVE; // 状态值为 0 - IotDeviceStateEnum compareState = IotDeviceStateEnum.ONLINE; // 状态值为 1 - IotDeviceMessage message = createDeviceMessage(actualState.getState()); - IotSceneRuleDO.TriggerCondition condition = createValidCondition( - IotSceneRuleConditionOperatorEnum.LESS_THAN.getOperator(), - compareState.getState().toString() - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_nullCondition_fail() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); - - // 调用 - boolean result = matcher.matches(message, null); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_nullConditionType_fail() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); - IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); - condition.setType(null); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_missingOperator_fail() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); - IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); - condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_STATE.getType()); - condition.setOperator(null); - condition.setParam(IotDeviceStateEnum.ONLINE.getState().toString()); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_missingParam_fail() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); - IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); - condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_STATE.getType()); - condition.setOperator(IotSceneRuleConditionOperatorEnum.EQUALS.getOperator()); - condition.setParam(null); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_nullMessage_fail() { - // 准备参数 - IotSceneRuleDO.TriggerCondition condition = createValidCondition( - IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), - IotDeviceStateEnum.ONLINE.getState().toString() - ); - - // 调用 - boolean result = matcher.matches(null, condition); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_nullDeviceState_fail() { - // 准备参数 - IotDeviceMessage message = new IotDeviceMessage(); - message.setParams(null); - IotSceneRuleDO.TriggerCondition condition = createValidCondition( - IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), - IotDeviceStateEnum.ONLINE.getState().toString() - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_greaterThanOrEqualsOperator_success() { - // 准备参数 - IotDeviceStateEnum deviceState = IotDeviceStateEnum.ONLINE; // 状态值为 1 - IotDeviceMessage message = createDeviceMessage(deviceState.getState()); - IotSceneRuleDO.TriggerCondition condition = createValidCondition( - IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS.getOperator(), - deviceState.getState().toString() // 比较值也为 1 - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_lessThanOrEqualsOperator_success() { - // 准备参数 - IotDeviceStateEnum actualState = IotDeviceStateEnum.ONLINE; // 状态值为 1 - IotDeviceStateEnum compareState = IotDeviceStateEnum.OFFLINE; // 状态值为 2 - IotDeviceMessage message = createDeviceMessage(actualState.getState()); - IotSceneRuleDO.TriggerCondition condition = createValidCondition( - IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS.getOperator(), - compareState.getState().toString() - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_invalidOperator_fail() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); - IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); - condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_STATE.getType()); - condition.setOperator(randomString()); // 随机无效操作符 - condition.setParam(IotDeviceStateEnum.ONLINE.getState().toString()); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_invalidParamFormat_fail() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); - IotSceneRuleDO.TriggerCondition condition = createValidCondition( - IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), - randomString() // 随机无效状态值 - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertFalse(result); - } - - // ========== 辅助方法 ========== - - /** - * 创建设备消息 - */ - private IotDeviceMessage createDeviceMessage(Integer deviceState) { - IotDeviceMessage message = new IotDeviceMessage(); - message.setDeviceId(randomLongId()); - message.setParams(deviceState); - return message; - } - - /** - * 创建有效的条件 - */ - private IotSceneRuleDO.TriggerCondition createValidCondition(String operator, String param) { - IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); - condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_STATE.getType()); - condition.setOperator(operator); - condition.setParam(param); - return condition; - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcherTest.java deleted file mode 100644 index 1ed8f1c48f..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcherTest.java +++ /dev/null @@ -1,376 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; - -import cn.hutool.core.map.MapUtil; -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -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.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; - -import java.util.HashMap; -import java.util.Map; - -import static cn.hutool.core.util.RandomUtil.randomInt; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; -import static org.junit.jupiter.api.Assertions.*; - -/** - * {@link DeviceEventPostTriggerMatcher} 的单元测试 - * - * @author HUIHUI - */ -public class DeviceEventPostTriggerMatcherTest extends BaseMockitoUnitTest { - - @InjectMocks - private DeviceEventPostTriggerMatcher matcher; - - @Test - public void testGetSupportedTriggerType_success() { - // 准备参数 - // 无需准备参数 - - // 调用 - IotSceneRuleTriggerTypeEnum result = matcher.getSupportedTriggerType(); - - // 断言 - assertEquals(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST, result); - } - - @Test - public void testGetPriority_success() { - // 准备参数 - // 无需准备参数 - - // 调用 - int result = matcher.getPriority(); - - // 断言 - assertEquals(30, result); - } - - @Test - public void testIsEnabled_success() { - // 准备参数 - // 无需准备参数 - - // 调用 - boolean result = matcher.isEnabled(); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_alarmEventSuccess() { - // 准备参数 - String eventIdentifier = randomString(); - Map eventParams = MapUtil.builder(new HashMap()) - .put("identifier", eventIdentifier) - .put("value", MapUtil.builder(new HashMap()) - .put("level", randomString()) - .put("message", randomString()) - .build()) - .build(); - IotDeviceMessage message = createEventPostMessage(eventParams); - IotSceneRuleDO.Trigger trigger = createValidTrigger(eventIdentifier); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_errorEventSuccess() { - // 准备参数 - String eventIdentifier = randomString(); - Map eventParams = MapUtil.builder(new HashMap()) - .put("identifier", eventIdentifier) - .put("value", MapUtil.builder(new HashMap()) - .put("code", randomInt()) - .put("description", randomString()) - .build()) - .build(); - IotDeviceMessage message = createEventPostMessage(eventParams); - IotSceneRuleDO.Trigger trigger = createValidTrigger(eventIdentifier); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_infoEventSuccess() { - // 准备参数 - String eventIdentifier = randomString(); - Map eventParams = MapUtil.builder(new HashMap()) - .put("identifier", eventIdentifier) - .put("value", MapUtil.builder(new HashMap()) - .put("status", randomString()) - .put("timestamp", System.currentTimeMillis()) - .build()) - .build(); - IotDeviceMessage message = createEventPostMessage(eventParams); - IotSceneRuleDO.Trigger trigger = createValidTrigger(eventIdentifier); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_eventIdentifierMismatch() { - // 准备参数 - String messageIdentifier = randomString(); - String triggerIdentifier = randomString(); - Map eventParams = MapUtil.builder(new HashMap()) - .put("identifier", messageIdentifier) - .put("value", MapUtil.builder(new HashMap()) - .put("level", randomString()) - .build()) - .build(); - IotDeviceMessage message = createEventPostMessage(eventParams); - IotSceneRuleDO.Trigger trigger = createValidTrigger(triggerIdentifier); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_wrongMessageMethod() { - // 准备参数 - String eventIdentifier = randomString(); - Map eventParams = MapUtil.builder(new HashMap()) - .put("identifier", eventIdentifier) - .put("value", MapUtil.builder(new HashMap()) - .put("level", randomString()) - .build()) - .build(); - IotDeviceMessage message = new IotDeviceMessage(); - message.setDeviceId(randomLongId()); - message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); // 错误的方法 - message.setParams(eventParams); - IotSceneRuleDO.Trigger trigger = createValidTrigger(eventIdentifier); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_nullTriggerIdentifier() { - // 准备参数 - String eventIdentifier = randomString(); - Map eventParams = MapUtil.builder(new HashMap()) - .put("identifier", eventIdentifier) - .put("value", MapUtil.builder(new HashMap()) - .put("level", randomString()) - .build()) - .build(); - IotDeviceMessage message = createEventPostMessage(eventParams); - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); - trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST.getType()); - trigger.setIdentifier(null); // 缺少标识符 - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_nullMessageParams() { - // 准备参数 - String eventIdentifier = randomString(); - IotDeviceMessage message = new IotDeviceMessage(); - message.setDeviceId(randomLongId()); - message.setMethod(IotDeviceMessageMethodEnum.EVENT_POST.getMethod()); - message.setParams(null); - IotSceneRuleDO.Trigger trigger = createValidTrigger(eventIdentifier); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_invalidMessageParams() { - // 准备参数 - String eventIdentifier = randomString(); - IotDeviceMessage message = new IotDeviceMessage(); - message.setDeviceId(randomLongId()); - message.setMethod(IotDeviceMessageMethodEnum.EVENT_POST.getMethod()); - message.setParams(randomString()); // 不是 Map 类型 - IotSceneRuleDO.Trigger trigger = createValidTrigger(eventIdentifier); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_missingEventIdentifierInParams() { - // 准备参数 - String eventIdentifier = randomString(); - Map eventParams = MapUtil.builder(new HashMap()) - .put("value", MapUtil.builder(new HashMap()) - .put("level", randomString()) - .build()) // 缺少 identifier 字段 - .build(); - IotDeviceMessage message = createEventPostMessage(eventParams); - IotSceneRuleDO.Trigger trigger = createValidTrigger(eventIdentifier); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_nullTrigger() { - // 准备参数 - String eventIdentifier = randomString(); - Map eventParams = MapUtil.builder(new HashMap()) - .put("identifier", eventIdentifier) - .put("value", MapUtil.builder(new HashMap()) - .put("level", randomString()) - .build()) - .build(); - IotDeviceMessage message = createEventPostMessage(eventParams); - - // 调用 - boolean result = matcher.matches(message, null); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_nullTriggerType() { - // 准备参数 - String eventIdentifier = randomString(); - Map eventParams = MapUtil.builder(new HashMap()) - .put("identifier", eventIdentifier) - .put("value", MapUtil.builder(new HashMap()) - .put("level", randomString()) - .build()) - .build(); - IotDeviceMessage message = createEventPostMessage(eventParams); - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); - trigger.setType(null); - trigger.setIdentifier(eventIdentifier); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_complexEventValueSuccess() { - // 准备参数 - String eventIdentifier = randomString(); - Map eventParams = MapUtil.builder(new HashMap()) - .put("identifier", eventIdentifier) - .put("value", MapUtil.builder(new HashMap()) - .put("type", randomString()) - .put("duration", randomInt()) - .put("components", new String[]{randomString(), randomString()}) - .put("priority", randomString()) - .build()) - .build(); - IotDeviceMessage message = createEventPostMessage(eventParams); - IotSceneRuleDO.Trigger trigger = createValidTrigger(eventIdentifier); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_emptyEventValueSuccess() { - // 准备参数 - String eventIdentifier = randomString(); - Map eventParams = MapUtil.builder(new HashMap()) - .put("identifier", eventIdentifier) - .put("value", MapUtil.ofEntries()) // 空的事件值 - .build(); - IotDeviceMessage message = createEventPostMessage(eventParams); - IotSceneRuleDO.Trigger trigger = createValidTrigger(eventIdentifier); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_caseSensitiveIdentifierMismatch() { - // 准备参数 - String eventIdentifier = randomString().toUpperCase(); // 大写 - String triggerIdentifier = eventIdentifier.toLowerCase(); // 小写 - Map eventParams = MapUtil.builder(new HashMap()) - .put("identifier", eventIdentifier) - .put("value", MapUtil.builder(new HashMap()) - .put("level", randomString()) - .build()) - .build(); - IotDeviceMessage message = createEventPostMessage(eventParams); - IotSceneRuleDO.Trigger trigger = createValidTrigger(triggerIdentifier); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - // 根据实际实现,这里可能需要调整期望结果 - // 如果实现是大小写敏感的,则应该为 false - assertFalse(result); - } - - // ========== 辅助方法 ========== - - /** - * 创建事件上报消息 - */ - private IotDeviceMessage createEventPostMessage(Map eventParams) { - IotDeviceMessage message = new IotDeviceMessage(); - message.setDeviceId(randomLongId()); - message.setMethod(IotDeviceMessageMethodEnum.EVENT_POST.getMethod()); - message.setParams(eventParams); - return message; - } - - /** - * 创建有效的触发器 - */ - private IotSceneRuleDO.Trigger createValidTrigger(String identifier) { - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); - trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST.getType()); - trigger.setIdentifier(identifier); - return trigger; - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcherTest.java deleted file mode 100644 index 2bed7fa631..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcherTest.java +++ /dev/null @@ -1,340 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; - -import cn.hutool.core.map.MapUtil; -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -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.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; - -import java.util.HashMap; -import java.util.Map; - -import static cn.hutool.core.util.RandomUtil.randomDouble; -import static cn.hutool.core.util.RandomUtil.randomInt; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; -import static org.junit.jupiter.api.Assertions.*; - -/** - * {@link DevicePropertyPostTriggerMatcher} 的单元测试 - * - * @author HUIHUI - */ -public class DevicePropertyPostTriggerMatcherTest extends BaseMockitoUnitTest { - - @InjectMocks - private DevicePropertyPostTriggerMatcher matcher; - - @Test - public void testGetSupportedTriggerType_success() { - // 准备参数 - // 无需准备参数 - - // 调用 - IotSceneRuleTriggerTypeEnum result = matcher.getSupportedTriggerType(); - - // 断言 - assertEquals(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST, result); - } - - @Test - public void testGetPriority_success() { - // 准备参数 - // 无需准备参数 - - // 调用 - int result = matcher.getPriority(); - - // 断言 - assertEquals(20, result); - } - - @Test - public void testIsEnabled_success() { - // 准备参数 - // 无需准备参数 - - // 调用 - boolean result = matcher.isEnabled(); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_numericPropertyGreaterThanSuccess() { - // 准备参数 - String propertyName = randomString(); - Double propertyValue = 25.5; - Integer compareValue = 20; - Map properties = MapUtil.builder(new HashMap()) - .put(propertyName, propertyValue) - .build(); - IotDeviceMessage message = createPropertyPostMessage(properties); - IotSceneRuleDO.Trigger trigger = createValidTrigger( - propertyName, - IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), - String.valueOf(compareValue) - ); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_integerPropertyEqualsSuccess() { - // 准备参数 - String propertyName = randomString(); - Integer propertyValue = randomInt(); - Map properties = MapUtil.builder(new HashMap()) - .put(propertyName, propertyValue) - .build(); - IotDeviceMessage message = createPropertyPostMessage(properties); - IotSceneRuleDO.Trigger trigger = createValidTrigger( - propertyName, - IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), - String.valueOf(propertyValue) - ); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_propertyValueNotMeetCondition() { - // 准备参数 - String propertyName = randomString(); - Double propertyValue = 15.0; - Integer compareValue = 20; - Map properties = MapUtil.builder(new HashMap()) - .put(propertyName, propertyValue) - .build(); - IotDeviceMessage message = createPropertyPostMessage(properties); - IotSceneRuleDO.Trigger trigger = createValidTrigger( - propertyName, - IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), - String.valueOf(compareValue) - ); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_propertyNotFound() { - // 准备参数 - String existingProperty = randomString(); - String missingProperty = randomString(); - Map properties = MapUtil.builder(new HashMap()) - .put(existingProperty, randomDouble()) - .build(); - IotDeviceMessage message = createPropertyPostMessage(properties); - IotSceneRuleDO.Trigger trigger = createValidTrigger( - missingProperty, // 不存在的属性 - IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), - String.valueOf(randomInt()) - ); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_wrongMessageMethod() { - // 准备参数 - String propertyName = randomString(); - Map properties = MapUtil.builder(new HashMap()) - .put(propertyName, randomDouble()) - .build(); - IotDeviceMessage message = new IotDeviceMessage(); - message.setDeviceId(randomLongId()); - message.setMethod(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod()); - message.setParams(properties); - IotSceneRuleDO.Trigger trigger = createValidTrigger( - propertyName, - IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), - String.valueOf(randomInt()) - ); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_nullTriggerIdentifier() { - // 准备参数 - String propertyName = randomString(); - Map properties = MapUtil.builder(new HashMap()) - .put(propertyName, randomDouble()) - .build(); - IotDeviceMessage message = createPropertyPostMessage(properties); - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); - trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST.getType()); - trigger.setIdentifier(null); // 缺少标识符 - trigger.setOperator(IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator()); - trigger.setValue(String.valueOf(randomInt())); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_nullMessageParams() { - // 准备参数 - String propertyName = randomString(); - IotDeviceMessage message = new IotDeviceMessage(); - message.setDeviceId(randomLongId()); - message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); - message.setParams(null); - IotSceneRuleDO.Trigger trigger = createValidTrigger( - propertyName, - IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), - String.valueOf(randomInt()) - ); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_invalidMessageParams() { - // 准备参数 - String propertyName = randomString(); - IotDeviceMessage message = new IotDeviceMessage(); - message.setDeviceId(randomLongId()); - message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); - message.setParams(randomString()); // 不是 Map 类型 - IotSceneRuleDO.Trigger trigger = createValidTrigger( - propertyName, - IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), - String.valueOf(randomInt()) - ); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_lessThanOperatorSuccess() { - // 准备参数 - String propertyName = randomString(); - Double propertyValue = 15.0; - Integer compareValue = 20; - Map properties = MapUtil.builder(new HashMap()) - .put(propertyName, propertyValue) - .build(); - IotDeviceMessage message = createPropertyPostMessage(properties); - IotSceneRuleDO.Trigger trigger = createValidTrigger( - propertyName, - IotSceneRuleConditionOperatorEnum.LESS_THAN.getOperator(), - String.valueOf(compareValue) - ); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_notEqualsOperatorSuccess() { - // 准备参数 - String propertyName = randomString(); - String propertyValue = randomString(); - String compareValue = randomString(); - Map properties = MapUtil.builder(new HashMap()) - .put(propertyName, propertyValue) - .build(); - IotDeviceMessage message = createPropertyPostMessage(properties); - IotSceneRuleDO.Trigger trigger = createValidTrigger( - propertyName, - IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(), - compareValue - ); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_multiplePropertiesTargetPropertySuccess() { - // 准备参数 - String targetProperty = randomString(); - Integer targetValue = randomInt(); - Map properties = MapUtil.builder(new HashMap()) - .put(randomString(), randomDouble()) - .put(targetProperty, targetValue) - .put(randomString(), randomString()) - .build(); - IotDeviceMessage message = createPropertyPostMessage(properties); - IotSceneRuleDO.Trigger trigger = createValidTrigger( - targetProperty, - IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), - String.valueOf(targetValue) - ); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertTrue(result); - } - - // ========== 辅助方法 ========== - - /** - * 创建属性上报消息 - */ - private IotDeviceMessage createPropertyPostMessage(Map properties) { - IotDeviceMessage message = new IotDeviceMessage(); - message.setDeviceId(randomLongId()); - message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); - message.setParams(properties); - return message; - } - - /** - * 创建有效的触发器 - */ - private IotSceneRuleDO.Trigger createValidTrigger(String identifier, String operator, String value) { - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); - trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST.getType()); - trigger.setIdentifier(identifier); - trigger.setOperator(operator); - trigger.setValue(value); - return trigger; - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcherTest.java deleted file mode 100644 index a9348456f4..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcherTest.java +++ /dev/null @@ -1,398 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; - -import cn.hutool.core.map.MapUtil; -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -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.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; - -import java.util.HashMap; -import java.util.Map; - -import static cn.hutool.core.util.RandomUtil.*; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; -import static org.junit.jupiter.api.Assertions.*; - -/** - * {@link DeviceServiceInvokeTriggerMatcher} 的单元测试 - * - * @author HUIHUI - */ -public class DeviceServiceInvokeTriggerMatcherTest extends BaseMockitoUnitTest { - - @InjectMocks - private DeviceServiceInvokeTriggerMatcher matcher; - - @Test - public void testGetSupportedTriggerType_success() { - // 准备参数 - // 无需准备参数 - - // 调用 - IotSceneRuleTriggerTypeEnum result = matcher.getSupportedTriggerType(); - - // 断言 - assertEquals(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE, result); - } - - @Test - public void testGetPriority_success() { - // 准备参数 - // 无需准备参数 - - // 调用 - int result = matcher.getPriority(); - - // 断言 - assertEquals(40, result); - } - - @Test - public void testIsEnabled_success() { - // 准备参数 - // 无需准备参数 - - // 调用 - boolean result = matcher.isEnabled(); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_serviceInvokeSuccess() { - // 准备参数 - String serviceIdentifier = randomString(); - Map serviceParams = MapUtil.builder(new HashMap()) - .put("identifier", serviceIdentifier) - .put("inputData", MapUtil.builder(new HashMap()) - .put("mode", randomString()) - .build()) - .build(); - IotDeviceMessage message = createServiceInvokeMessage(serviceParams); - IotSceneRuleDO.Trigger trigger = createValidTrigger(serviceIdentifier); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_configServiceSuccess() { - // 准备参数 - String serviceIdentifier = randomString(); - Map serviceParams = MapUtil.builder(new HashMap()) - .put("identifier", serviceIdentifier) - .put("inputData", MapUtil.builder(new HashMap()) - .put("interval", randomInt()) - .put("enabled", randomBoolean()) - .put("threshold", randomDouble()) - .build()) - .build(); - IotDeviceMessage message = createServiceInvokeMessage(serviceParams); - IotSceneRuleDO.Trigger trigger = createValidTrigger(serviceIdentifier); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_updateServiceSuccess() { - // 准备参数 - String serviceIdentifier = randomString(); - Map serviceParams = MapUtil.builder(new HashMap()) - .put("identifier", serviceIdentifier) - .put("inputData", MapUtil.builder(new HashMap()) - .put("version", randomString()) - .put("url", randomString()) - .build()) - .build(); - IotDeviceMessage message = createServiceInvokeMessage(serviceParams); - IotSceneRuleDO.Trigger trigger = createValidTrigger(serviceIdentifier); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_serviceIdentifierMismatch() { - // 准备参数 - String messageIdentifier = randomString(); - String triggerIdentifier = randomString(); - Map serviceParams = MapUtil.builder(new HashMap()) - .put("identifier", messageIdentifier) - .put("inputData", MapUtil.builder(new HashMap()) - .put("mode", randomString()) - .build()) - .build(); - IotDeviceMessage message = createServiceInvokeMessage(serviceParams); - IotSceneRuleDO.Trigger trigger = createValidTrigger(triggerIdentifier); // 不匹配的服务标识符 - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_wrongMessageMethod() { - // 准备参数 - String serviceIdentifier = randomString(); - Map serviceParams = MapUtil.builder(new HashMap()) - .put("identifier", serviceIdentifier) - .put("inputData", MapUtil.builder(new HashMap()) - .put("mode", randomString()) - .build()) - .build(); - IotDeviceMessage message = new IotDeviceMessage(); - message.setDeviceId(randomLongId()); - message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); // 错误的方法 - message.setParams(serviceParams); - IotSceneRuleDO.Trigger trigger = createValidTrigger(serviceIdentifier); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_nullTriggerIdentifier() { - // 准备参数 - String serviceIdentifier = randomString(); - Map serviceParams = MapUtil.builder(new HashMap()) - .put("identifier", serviceIdentifier) - .put("inputData", MapUtil.builder(new HashMap()) - .put("mode", randomString()) - .build()) - .build(); - IotDeviceMessage message = createServiceInvokeMessage(serviceParams); - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); - trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType()); - trigger.setIdentifier(null); // 缺少标识符 - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_nullMessageParams() { - // 准备参数 - String serviceIdentifier = randomString(); - IotDeviceMessage message = new IotDeviceMessage(); - message.setDeviceId(randomLongId()); - message.setMethod(IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod()); - message.setParams(null); - IotSceneRuleDO.Trigger trigger = createValidTrigger(serviceIdentifier); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_invalidMessageParams() { - // 准备参数 - String serviceIdentifier = randomString(); - IotDeviceMessage message = new IotDeviceMessage(); - message.setDeviceId(randomLongId()); - message.setMethod(IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod()); - message.setParams(randomString()); // 不是 Map 类型 - IotSceneRuleDO.Trigger trigger = createValidTrigger(serviceIdentifier); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_missingServiceIdentifierInParams() { - // 准备参数 - String serviceIdentifier = randomString(); - Map serviceParams = MapUtil.builder(new HashMap()) - .put("inputData", MapUtil.builder(new HashMap()) - .put("mode", randomString()) - .build()) // 缺少 identifier 字段 - .build(); - IotDeviceMessage message = createServiceInvokeMessage(serviceParams); - IotSceneRuleDO.Trigger trigger = createValidTrigger(serviceIdentifier); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_nullTrigger() { - // 准备参数 - String serviceIdentifier = randomString(); - Map serviceParams = MapUtil.builder(new HashMap()) - .put("identifier", serviceIdentifier) - .put("inputData", MapUtil.builder(new HashMap()) - .put("mode", randomString()) - .build()) - .build(); - IotDeviceMessage message = createServiceInvokeMessage(serviceParams); - - // 调用 - boolean result = matcher.matches(message, null); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_nullTriggerType() { - // 准备参数 - String serviceIdentifier = randomString(); - Map serviceParams = MapUtil.builder(new HashMap()) - .put("identifier", serviceIdentifier) - .put("inputData", MapUtil.builder(new HashMap()) - .put("mode", randomString()) - .build()) - .build(); - IotDeviceMessage message = createServiceInvokeMessage(serviceParams); - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); - trigger.setType(null); - trigger.setIdentifier(serviceIdentifier); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_emptyInputDataSuccess() { - // 准备参数 - String serviceIdentifier = randomString(); - Map serviceParams = MapUtil.builder(new HashMap()) - .put("identifier", serviceIdentifier) - .put("inputData", MapUtil.ofEntries()) // 空的输入数据 - .build(); - IotDeviceMessage message = createServiceInvokeMessage(serviceParams); - IotSceneRuleDO.Trigger trigger = createValidTrigger(serviceIdentifier); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_noInputDataSuccess() { - // 准备参数 - String serviceIdentifier = randomString(); - Map serviceParams = MapUtil.builder(new HashMap()) - .put("identifier", serviceIdentifier) - // 没有 inputData 字段 - .build(); - IotDeviceMessage message = createServiceInvokeMessage(serviceParams); - IotSceneRuleDO.Trigger trigger = createValidTrigger(serviceIdentifier); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_complexInputDataSuccess() { - // 准备参数 - String serviceIdentifier = randomString(); - Map serviceParams = MapUtil.builder(new HashMap()) - .put("identifier", serviceIdentifier) - .put("inputData", MapUtil.builder(new HashMap()) - .put("sensors", new String[]{randomString(), randomString(), randomString()}) - .put("precision", randomDouble()) - .put("duration", randomInt()) - .put("autoSave", randomBoolean()) - .put("config", MapUtil.builder(new HashMap()) - .put("mode", randomString()) - .put("level", randomString()) - .build()) - .build()) - .build(); - IotDeviceMessage message = createServiceInvokeMessage(serviceParams); - IotSceneRuleDO.Trigger trigger = createValidTrigger(serviceIdentifier); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_caseSensitiveIdentifierMismatch() { - // 准备参数 - String serviceIdentifier = randomString().toUpperCase(); // 大写 - String triggerIdentifier = serviceIdentifier.toLowerCase(); // 小写 - Map serviceParams = MapUtil.builder(new HashMap()) - .put("identifier", serviceIdentifier) - .put("inputData", MapUtil.builder(new HashMap()) - .put("mode", randomString()) - .build()) - .build(); - IotDeviceMessage message = createServiceInvokeMessage(serviceParams); - IotSceneRuleDO.Trigger trigger = createValidTrigger(triggerIdentifier); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - // 根据实际实现,这里可能需要调整期望结果 - // 如果实现是大小写敏感的,则应该为 false - assertFalse(result); - } - - // ========== 辅助方法 ========== - - /** - * 创建服务调用消息 - */ - private IotDeviceMessage createServiceInvokeMessage(Map serviceParams) { - IotDeviceMessage message = new IotDeviceMessage(); - message.setDeviceId(randomLongId()); - message.setMethod(IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod()); - message.setParams(serviceParams); - return message; - } - - /** - * 创建有效的触发器 - */ - private IotSceneRuleDO.Trigger createValidTrigger(String identifier) { - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); - trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType()); - trigger.setIdentifier(identifier); - return trigger; - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcherTest.java deleted file mode 100644 index b1e095ea3b..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcherTest.java +++ /dev/null @@ -1,262 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; - -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -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.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; - -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; -import static org.junit.jupiter.api.Assertions.*; - -/** - * {@link DeviceStateUpdateTriggerMatcher} 的单元测试 - * - * @author HUIHUI - */ -public class DeviceStateUpdateTriggerMatcherTest extends BaseMockitoUnitTest { - - @InjectMocks - private DeviceStateUpdateTriggerMatcher matcher; - - @Test - public void testGetSupportedTriggerType_success() { - // 准备参数 - // 无需准备参数 - - // 调用 - IotSceneRuleTriggerTypeEnum result = matcher.getSupportedTriggerType(); - - // 断言 - assertEquals(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE, result); - } - - @Test - public void testGetPriority_success() { - // 准备参数 - // 无需准备参数 - - // 调用 - int result = matcher.getPriority(); - - // 断言 - assertEquals(10, result); - } - - @Test - public void testIsEnabled_success() { - // 准备参数 - // 无需准备参数 - - // 调用 - boolean result = matcher.isEnabled(); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_onlineStateSuccess() { - // 准备参数 - IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); - IotSceneRuleDO.Trigger trigger = createValidTrigger( - IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), - IotDeviceStateEnum.ONLINE.getState().toString() - ); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_offlineStateSuccess() { - // 准备参数 - IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.OFFLINE.getState()); - IotSceneRuleDO.Trigger trigger = createValidTrigger( - IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), - IotDeviceStateEnum.OFFLINE.getState().toString() - ); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_stateMismatch() { - // 准备参数 - IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); - IotSceneRuleDO.Trigger trigger = createValidTrigger( - IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), - IotDeviceStateEnum.OFFLINE.getState().toString() - ); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_nullTrigger() { - // 准备参数 - IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); - - // 调用 - boolean result = matcher.matches(message, null); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_nullTriggerType() { - // 准备参数 - IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); - trigger.setType(null); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_wrongMessageMethod() { - // 准备参数 - IotDeviceMessage message = new IotDeviceMessage(); - message.setDeviceId(randomLongId()); - message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); - message.setParams(IotDeviceStateEnum.ONLINE.getState()); - IotSceneRuleDO.Trigger trigger = createValidTrigger( - IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), - IotDeviceStateEnum.ONLINE.getState().toString() - ); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_nullTriggerOperator() { - // 准备参数 - IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); - trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE.getType()); - trigger.setOperator(null); - trigger.setValue(IotDeviceStateEnum.ONLINE.getState().toString()); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_nullTriggerValue() { - // 准备参数 - IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); - trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE.getType()); - trigger.setOperator(IotSceneRuleConditionOperatorEnum.EQUALS.getOperator()); - trigger.setValue(null); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_nullMessageParams() { - // 准备参数 - IotDeviceMessage message = new IotDeviceMessage(); - message.setDeviceId(randomLongId()); - message.setMethod(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod()); - message.setParams(null); - IotSceneRuleDO.Trigger trigger = createValidTrigger( - IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), - IotDeviceStateEnum.ONLINE.getState().toString() - ); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_greaterThanOperatorSuccess() { - // 准备参数 - IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); - IotSceneRuleDO.Trigger trigger = createValidTrigger( - IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), - IotDeviceStateEnum.INACTIVE.getState().toString() - ); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_notEqualsOperatorSuccess() { - // 准备参数 - IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); - IotSceneRuleDO.Trigger trigger = createValidTrigger( - IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(), - IotDeviceStateEnum.OFFLINE.getState().toString() - ); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertTrue(result); - } - - // ========== 辅助方法 ========== - - /** - * 创建设备状态更新消息 - */ - private IotDeviceMessage createStateUpdateMessage(Integer state) { - IotDeviceMessage message = new IotDeviceMessage(); - message.setDeviceId(randomLongId()); - message.setMethod(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod()); - message.setParams(state); - return message; - } - - /** - * 创建有效的触发器 - */ - private IotSceneRuleDO.Trigger createValidTrigger(String operator, String value) { - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); - trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE.getType()); - trigger.setOperator(operator); - trigger.setValue(value); - return trigger; - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcherTest.java deleted file mode 100644 index 52ed5ec3de..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcherTest.java +++ /dev/null @@ -1,276 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; - -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; - -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; -import static org.junit.jupiter.api.Assertions.*; - -/** - * {@link TimerTriggerMatcher} 的单元测试 - * - * @author HUIHUI - */ -public class TimerTriggerMatcherTest extends BaseMockitoUnitTest { - - @InjectMocks - private TimerTriggerMatcher matcher; - - @Test - public void testGetSupportedTriggerType_success() { - // 准备参数 - // 无需准备参数 - - // 调用 - IotSceneRuleTriggerTypeEnum result = matcher.getSupportedTriggerType(); - - // 断言 - assertEquals(IotSceneRuleTriggerTypeEnum.TIMER, result); - } - - @Test - public void testGetPriority_success() { - // 准备参数 - // 无需准备参数 - - // 调用 - int result = matcher.getPriority(); - - // 断言 - assertEquals(50, result); - } - - @Test - public void testIsEnabled_success() { - // 准备参数 - // 无需准备参数 - - // 调用 - boolean result = matcher.isEnabled(); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_validCronExpressionSuccess() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(); - String cronExpression = "0 0 12 * * ?"; // 每天中午12点 - IotSceneRuleDO.Trigger trigger = createValidTrigger(cronExpression); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_everyMinuteCronSuccess() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(); - String cronExpression = "0 * * * * ?"; // 每分钟 - IotSceneRuleDO.Trigger trigger = createValidTrigger(cronExpression); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_weekdaysCronSuccess() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(); - String cronExpression = "0 0 9 ? * MON-FRI"; // 工作日上午9点 - IotSceneRuleDO.Trigger trigger = createValidTrigger(cronExpression); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_invalidCronExpression() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(); - String cronExpression = randomString(); // 随机无效的 cron 表达式 - IotSceneRuleDO.Trigger trigger = createValidTrigger(cronExpression); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_emptyCronExpression() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(); - String cronExpression = ""; // 空的 cron 表达式 - IotSceneRuleDO.Trigger trigger = createValidTrigger(cronExpression); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_nullCronExpression() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(); - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); - trigger.setType(IotSceneRuleTriggerTypeEnum.TIMER.getType()); - trigger.setCronExpression(null); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_nullTrigger() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(); - - // 调用 - boolean result = matcher.matches(message, null); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_nullTriggerType() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(); - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); - trigger.setType(null); - trigger.setCronExpression("0 0 12 * * ?"); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_complexCronExpressionSuccess() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(); - String cronExpression = "0 15 10 ? * 6#3"; // 每月第三个星期五上午10:15 - IotSceneRuleDO.Trigger trigger = createValidTrigger(cronExpression); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_incorrectCronFormat() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(); - String cronExpression = "0 0 12 * *"; // 缺少字段的 cron 表达式 - IotSceneRuleDO.Trigger trigger = createValidTrigger(cronExpression); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_specificDateCronSuccess() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(); - String cronExpression = "0 0 0 1 1 ? 2025"; // 2025年1月1日午夜 - IotSceneRuleDO.Trigger trigger = createValidTrigger(cronExpression); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_everySecondCronSuccess() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(); - String cronExpression = "* * * * * ?"; // 每秒执行 - IotSceneRuleDO.Trigger trigger = createValidTrigger(cronExpression); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertTrue(result); - } - - @Test - public void testMatches_invalidCharactersCron() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(); - String cronExpression = "0 0 12 * * @ #"; // 包含无效字符的 cron 表达式 - IotSceneRuleDO.Trigger trigger = createValidTrigger(cronExpression); - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertFalse(result); - } - - @Test - public void testMatches_rangeCronSuccess() { - // 准备参数 - IotDeviceMessage message = createDeviceMessage(); - IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 9-17 * * MON-FRI"); // 工作日9-17点 - - // 调用 - boolean result = matcher.matches(message, trigger); - - // 断言 - assertTrue(result); - } - - // ========== 辅助方法 ========== - - /** - * 创建设备消息 - */ - private IotDeviceMessage createDeviceMessage() { - IotDeviceMessage message = new IotDeviceMessage(); - message.setDeviceId(randomLongId()); - return message; - } - - /** - * 创建有效的定时触发器 - */ - private IotSceneRuleDO.Trigger createValidTrigger(String cronExpression) { - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); - trigger.setType(IotSceneRuleTriggerTypeEnum.TIMER.getType()); - trigger.setCronExpression(cronExpression); - return trigger; - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/resources/application-unit-test.yaml b/yudao-module-iot/yudao-module-iot-biz/src/test/resources/application-unit-test.yaml deleted file mode 100644 index 3966a274d4..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/resources/application-unit-test.yaml +++ /dev/null @@ -1,52 +0,0 @@ -spring: - main: - lazy-initialization: true # 开启懒加载,加快速度 - banner-mode: off # 单元测试,禁用 Banner - # 数据源配置项 - datasource: - name: ruoyi-vue-pro - url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写 - driver-class-name: org.h2.Driver - username: sa - password: - druid: - async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度 - initial-size: 1 # 单元测试,配置为 1,提升启动速度 - sql: - init: - schema-locations: classpath:/sql/create_tables.sql - -mybatis-plus: - lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试 - type-aliases-package: ${yudao.info.base-package}.module.*.dal.dataobject - -# 日志配置 -logging: - level: - cn.iocoder.yudao.module.iot.service.rule.scene.matcher: DEBUG - cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherManager: INFO - cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition: DEBUG - cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger: DEBUG - root: WARN - ---- #################### 定时任务相关配置 #################### - ---- #################### 配置中心相关配置 #################### - ---- #################### 服务保障相关配置 #################### - -# Lock4j 配置项(单元测试,禁用 Lock4j) - ---- #################### 监控相关配置 #################### - ---- #################### 芋道相关配置 #################### - -# 芋道配置项,设置当前项目所有自定义的配置 -yudao: - info: - base-package: cn.iocoder.yudao - tenant: # 多租户相关配置项 - enable: true - xss: - enable: false - demo: false # 关闭演示模式 diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/resources/logback.xml b/yudao-module-iot/yudao-module-iot-biz/src/test/resources/logback.xml deleted file mode 100644 index b68931dc1c..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/resources/logback.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - UTF-8 - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/resources/sql/clean.sql b/yudao-module-iot/yudao-module-iot-biz/src/test/resources/sql/clean.sql deleted file mode 100644 index ae1c5e5156..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/resources/sql/clean.sql +++ /dev/null @@ -1,10 +0,0 @@ -DELETE FROM "iot_scene_rule"; -DELETE FROM "iot_product"; -DELETE FROM "iot_device"; -DELETE FROM "iot_thing_model"; -DELETE FROM "iot_device_data"; -DELETE FROM "iot_alert_config"; -DELETE FROM "iot_alert_record"; -DELETE FROM "iot_ota_firmware"; -DELETE FROM "iot_ota_task"; -DELETE FROM "iot_ota_record"; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/resources/sql/create_tables.sql b/yudao-module-iot/yudao-module-iot-biz/src/test/resources/sql/create_tables.sql deleted file mode 100644 index 306c66b5e5..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/resources/sql/create_tables.sql +++ /dev/null @@ -1,182 +0,0 @@ -CREATE TABLE IF NOT EXISTS "iot_scene_rule" ( - "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, - "name" varchar(255) NOT NULL DEFAULT '', - "description" varchar(500) DEFAULT NULL, - "status" tinyint NOT NULL DEFAULT '0', - "triggers" text, - "actions" text, - "creator" varchar(64) DEFAULT '', - "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updater" varchar(64) DEFAULT '', - "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - "deleted" bit NOT NULL DEFAULT FALSE, - "tenant_id" bigint NOT NULL DEFAULT '0', - PRIMARY KEY ("id") -) COMMENT 'IoT 场景联动规则表'; - -CREATE TABLE IF NOT EXISTS "iot_product" ( - "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, - "name" varchar(255) NOT NULL DEFAULT '', - "product_key" varchar(100) NOT NULL DEFAULT '', - "protocol_type" tinyint NOT NULL DEFAULT '0', - "category_id" bigint DEFAULT NULL, - "description" varchar(500) DEFAULT NULL, - "data_format" tinyint NOT NULL DEFAULT '0', - "device_type" tinyint NOT NULL DEFAULT '0', - "net_type" tinyint NOT NULL DEFAULT '0', - "validate_type" tinyint NOT NULL DEFAULT '0', - "status" tinyint NOT NULL DEFAULT '0', - "creator" varchar(64) DEFAULT '', - "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updater" varchar(64) DEFAULT '', - "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - "deleted" bit NOT NULL DEFAULT FALSE, - "tenant_id" bigint NOT NULL DEFAULT '0', - PRIMARY KEY ("id") -) COMMENT 'IoT 产品表'; - -CREATE TABLE IF NOT EXISTS "iot_device" ( - "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, - "device_name" varchar(255) NOT NULL DEFAULT '', - "product_id" bigint NOT NULL, - "device_key" varchar(100) NOT NULL DEFAULT '', - "device_secret" varchar(100) NOT NULL DEFAULT '', - "nickname" varchar(255) DEFAULT NULL, - "status" tinyint NOT NULL DEFAULT '0', - "status_last_update_time" timestamp DEFAULT NULL, - "last_online_time" timestamp DEFAULT NULL, - "last_offline_time" timestamp DEFAULT NULL, - "active_time" timestamp DEFAULT NULL, - "ip" varchar(50) DEFAULT NULL, - "firmware_version" varchar(50) DEFAULT NULL, - "device_type" tinyint NOT NULL DEFAULT '0', - "gateway_id" bigint DEFAULT NULL, - "sub_device_count" int NOT NULL DEFAULT '0', - "creator" varchar(64) DEFAULT '', - "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updater" varchar(64) DEFAULT '', - "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - "deleted" bit NOT NULL DEFAULT FALSE, - "tenant_id" bigint NOT NULL DEFAULT '0', - PRIMARY KEY ("id") -) COMMENT 'IoT 设备表'; - -CREATE TABLE IF NOT EXISTS "iot_thing_model" ( - "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, - "product_id" bigint NOT NULL, - "identifier" varchar(100) NOT NULL DEFAULT '', - "name" varchar(255) NOT NULL DEFAULT '', - "description" varchar(500) DEFAULT NULL, - "type" tinyint NOT NULL DEFAULT '1', - "property" text, - "creator" varchar(64) DEFAULT '', - "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updater" varchar(64) DEFAULT '', - "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - "deleted" bit NOT NULL DEFAULT FALSE, - "tenant_id" bigint NOT NULL DEFAULT '0', - PRIMARY KEY ("id") -) COMMENT 'IoT 物模型表'; - -CREATE TABLE IF NOT EXISTS "iot_device_data" ( - "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, - "device_id" bigint NOT NULL, - "product_id" bigint NOT NULL, - "identifier" varchar(100) NOT NULL DEFAULT '', - "type" tinyint NOT NULL DEFAULT '1', - "data" text, - "ts" bigint NOT NULL DEFAULT '0', - "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY ("id") -) COMMENT 'IoT 设备数据表'; - -CREATE TABLE IF NOT EXISTS "iot_alert_config" ( - "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, - "name" varchar(255) NOT NULL DEFAULT '', - "product_id" bigint NOT NULL, - "device_id" bigint DEFAULT NULL, - "rule_id" bigint DEFAULT NULL, - "status" tinyint NOT NULL DEFAULT '0', - "creator" varchar(64) DEFAULT '', - "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updater" varchar(64) DEFAULT '', - "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - "deleted" bit NOT NULL DEFAULT FALSE, - "tenant_id" bigint NOT NULL DEFAULT '0', - PRIMARY KEY ("id") -) COMMENT 'IoT 告警配置表'; - -CREATE TABLE IF NOT EXISTS "iot_alert_record" ( - "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, - "alert_config_id" bigint NOT NULL, - "alert_name" varchar(255) NOT NULL DEFAULT '', - "product_id" bigint NOT NULL, - "device_id" bigint DEFAULT NULL, - "rule_id" bigint DEFAULT NULL, - "alert_data" text, - "alert_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - "deal_status" tinyint NOT NULL DEFAULT '0', - "deal_time" timestamp DEFAULT NULL, - "deal_user_id" bigint DEFAULT NULL, - "deal_remark" varchar(500) DEFAULT NULL, - "creator" varchar(64) DEFAULT '', - "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updater" varchar(64) DEFAULT '', - "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - "deleted" bit NOT NULL DEFAULT FALSE, - "tenant_id" bigint NOT NULL DEFAULT '0', - PRIMARY KEY ("id") -) COMMENT 'IoT 告警记录表'; - -CREATE TABLE IF NOT EXISTS "iot_ota_firmware" ( - "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, - "name" varchar(255) NOT NULL DEFAULT '', - "product_id" bigint NOT NULL, - "version" varchar(50) NOT NULL DEFAULT '', - "description" varchar(500) DEFAULT NULL, - "file_url" varchar(500) DEFAULT NULL, - "file_size" bigint NOT NULL DEFAULT '0', - "status" tinyint NOT NULL DEFAULT '0', - "creator" varchar(64) DEFAULT '', - "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updater" varchar(64) DEFAULT '', - "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - "deleted" bit NOT NULL DEFAULT FALSE, - "tenant_id" bigint NOT NULL DEFAULT '0', - PRIMARY KEY ("id") -) COMMENT 'IoT OTA 固件表'; - -CREATE TABLE IF NOT EXISTS "iot_ota_task" ( - "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, - "name" varchar(255) NOT NULL DEFAULT '', - "firmware_id" bigint NOT NULL, - "product_id" bigint NOT NULL, - "upgrade_type" tinyint NOT NULL DEFAULT '0', - "status" tinyint NOT NULL DEFAULT '0', - "creator" varchar(64) DEFAULT '', - "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updater" varchar(64) DEFAULT '', - "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - "deleted" bit NOT NULL DEFAULT FALSE, - "tenant_id" bigint NOT NULL DEFAULT '0', - PRIMARY KEY ("id") -) COMMENT 'IoT OTA 升级任务表'; - -CREATE TABLE IF NOT EXISTS "iot_ota_record" ( - "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, - "task_id" bigint NOT NULL, - "firmware_id" bigint NOT NULL, - "device_id" bigint NOT NULL, - "status" tinyint NOT NULL DEFAULT '0', - "progress" int NOT NULL DEFAULT '0', - "error_msg" varchar(500) DEFAULT NULL, - "start_time" timestamp DEFAULT NULL, - "end_time" timestamp DEFAULT NULL, - "creator" varchar(64) DEFAULT '', - "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updater" varchar(64) DEFAULT '', - "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - "deleted" bit NOT NULL DEFAULT FALSE, - "tenant_id" bigint NOT NULL DEFAULT '0', - PRIMARY KEY ("id") -) COMMENT 'IoT OTA 升级记录表'; diff --git a/yudao-module-iot/yudao-module-iot-core/pom.xml b/yudao-module-iot/yudao-module-iot-core/pom.xml deleted file mode 100644 index 30ebc2de0c..0000000000 --- a/yudao-module-iot/yudao-module-iot-core/pom.xml +++ /dev/null @@ -1,72 +0,0 @@ - - - - yudao-module-iot - cn.iocoder.boot - ${revision} - - 4.0.0 - - yudao-module-iot-core - jar - - ${project.artifactId} - - iot 模块下,提供 iot-biz 和 iot-gateway 模块的核心功能。例如说: - 1. 消息总线:跨 iot-biz 和 iot-gateway 的设备消息。可选择使用 spring event、redis stream、rocketmq、kafka、rabbitmq 等。 - 2. 查询设备信息的通用 API - - - - - cn.iocoder.boot - yudao-common - - - - - org.springframework.boot - spring-boot-starter - - - - - cn.iocoder.boot - yudao-spring-boot-starter-mq - - - - org.springframework.data - spring-data-redis - true - - - - org.apache.rocketmq - rocketmq-spring-boot-starter - true - - - - org.springframework.amqp - spring-rabbit - true - - - - org.springframework.kafka - spring-kafka - true - - - - - org.springframework.boot - spring-boot-starter-test - test - - - - \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/IotDeviceCommonApi.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/IotDeviceCommonApi.java deleted file mode 100644 index 29d540e73e..0000000000 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/IotDeviceCommonApi.java +++ /dev/null @@ -1,31 +0,0 @@ -package cn.iocoder.yudao.module.iot.core.biz; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -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; - -/** - * IoT 设备通用 API - * - * @author haohao - */ -public interface IotDeviceCommonApi { - - /** - * 设备认证 - * - * @param authReqDTO 认证请求 - * @return 认证结果 - */ - CommonResult authDevice(IotDeviceAuthReqDTO authReqDTO); - - /** - * 获取设备信息 - * - * @param infoReqDTO 设备信息请求 - * @return 设备信息 - */ - CommonResult getDevice(IotDeviceGetReqDTO infoReqDTO); - -} diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotDeviceGetReqDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotDeviceGetReqDTO.java deleted file mode 100644 index 981509dd6a..0000000000 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotDeviceGetReqDTO.java +++ /dev/null @@ -1,27 +0,0 @@ -package cn.iocoder.yudao.module.iot.core.biz.dto; - -import lombok.Data; - -/** - * IoT 设备信息查询 Request DTO - * - * @author 芋道源码 - */ -@Data -public class IotDeviceGetReqDTO { - - /** - * 设备编号 - */ - private Long id; - - /** - * 产品标识 - */ - private String productKey; - /** - * 设备名称 - */ - private String deviceName; - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotDeviceRespDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotDeviceRespDTO.java deleted file mode 100644 index add1167801..0000000000 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotDeviceRespDTO.java +++ /dev/null @@ -1,41 +0,0 @@ -package cn.iocoder.yudao.module.iot.core.biz.dto; - -import lombok.Data; - -/** - * IoT 设备信息 Response DTO - * - * @author 芋道源码 - */ -@Data -public class IotDeviceRespDTO { - - /** - * 设备编号 - */ - private Long id; - /** - * 产品标识 - */ - private String productKey; - /** - * 设备名称 - */ - private String deviceName; - /** - * 租户编号 - */ - private Long tenantId; - - // ========== 产品相关字段 ========== - - /** - * 产品编号 - */ - private Long productId; - /** - * 编解码器类型 - */ - private String codecType; - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceMessageMethodEnum.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceMessageMethodEnum.java deleted file mode 100644 index 047fe5ffcd..0000000000 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceMessageMethodEnum.java +++ /dev/null @@ -1,86 +0,0 @@ -package cn.iocoder.yudao.module.iot.core.enums; - -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.AllArgsConstructor; -import lombok.Getter; - -import java.util.Arrays; -import java.util.Set; - -/** - * IoT 设备消息的方法枚举 - * - * @author haohao - */ -@Getter -@AllArgsConstructor -public enum IotDeviceMessageMethodEnum implements ArrayValuable { - - // ========== 设备状态 ========== - - STATE_UPDATE("thing.state.update", "设备状态更新", true), - - // TODO 芋艿:要不要加个 ping 消息; - - // ========== 设备属性 ========== - // 可参考:https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services - - PROPERTY_POST("thing.property.post", "属性上报", true), - PROPERTY_SET("thing.property.set", "属性设置", false), - - // ========== 设备事件 ========== - // 可参考:https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services - - EVENT_POST("thing.event.post", "事件上报", true), - - // ========== 设备服务调用 ========== - // 可参考:https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services - - SERVICE_INVOKE("thing.service.invoke", "服务调用", false), - - // ========== 设备配置 ========== - // 可参考:https://help.aliyun.com/zh/iot/user-guide/remote-configuration-1 - - CONFIG_PUSH("thing.config.push", "配置推送", true), - - // ========== OTA 固件 ========== - // 可参考:https://help.aliyun.com/zh/iot/user-guide/perform-ota-updates - - OTA_UPGRADE("thing.ota.upgrade", "OTA 固定信息推送", false), - OTA_PROGRESS("thing.ota.progress", "OTA 升级进度上报", true), - ; - - public static final String[] ARRAYS = Arrays.stream(values()).map(IotDeviceMessageMethodEnum::getMethod) - .toArray(String[]::new); - - /** - * 不进行 reply 回复的方法集合 - */ - public static final Set REPLY_DISABLED = SetUtils.asSet( - STATE_UPDATE.getMethod(), - OTA_PROGRESS.getMethod() // 参考阿里云,OTA 升级进度上报,不进行回复 - ); - - private final String method; - - private final String name; - - private final Boolean upstream; - - @Override - public String[] array() { - return ARRAYS; - } - - public static IotDeviceMessageMethodEnum of(String method) { - return ArrayUtil.firstMatch(item -> item.getMethod().equals(method), - IotDeviceMessageMethodEnum.values()); - } - - public static boolean isReplyDisabled(String method) { - return REPLY_DISABLED.contains(method); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceMessageTypeEnum.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceMessageTypeEnum.java deleted file mode 100644 index e2fe8be204..0000000000 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceMessageTypeEnum.java +++ /dev/null @@ -1,37 +0,0 @@ -package cn.iocoder.yudao.module.iot.core.enums; - -import cn.iocoder.yudao.framework.common.core.ArrayValuable; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -import java.util.Arrays; - -/** - * IoT 设备消息类型枚举 - */ -@Getter -@RequiredArgsConstructor -public enum IotDeviceMessageTypeEnum implements ArrayValuable { - - STATE("state"), // 设备状态 -// PROPERTY("property"), // 设备属性:可参考 https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services 设备属性、事件、服务 - EVENT("event"), // 设备事件:可参考 https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services 设备属性、事件、服务 - SERVICE("service"), // 设备服务:可参考 https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services 设备属性、事件、服务 - CONFIG("config"), // 设备配置:可参考 https://help.aliyun.com/zh/iot/user-guide/remote-configuration-1 远程配置 - OTA("ota"), // 设备 OTA:可参考 https://help.aliyun.com/zh/iot/user-guide/ota-update OTA 升级 - REGISTER("register"), // 设备注册:可参考 https://help.aliyun.com/zh/iot/user-guide/register-devices 设备身份注册 - TOPOLOGY("topology"),; // 设备拓扑:可参考 https://help.aliyun.com/zh/iot/user-guide/manage-topological-relationships 设备拓扑 - - public static final String[] ARRAYS = Arrays.stream(values()).map(IotDeviceMessageTypeEnum::getType).toArray(String[]::new); - - /** - * 属性 - */ - private final String type; - - @Override - public String[] array() { - return ARRAYS; - } - -} diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/config/IotMessageBusAutoConfiguration.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/config/IotMessageBusAutoConfiguration.java deleted file mode 100644 index acf3ad0434..0000000000 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/config/IotMessageBusAutoConfiguration.java +++ /dev/null @@ -1,129 +0,0 @@ -package cn.iocoder.yudao.module.iot.core.messagebus.config; - -import cn.iocoder.yudao.framework.mq.redis.core.RedisMQTemplate; -import cn.iocoder.yudao.framework.mq.redis.core.job.RedisPendingMessageResendJob; -import cn.iocoder.yudao.framework.mq.redis.core.job.RedisStreamMessageCleanupJob; -import cn.iocoder.yudao.framework.mq.redis.core.stream.AbstractRedisStreamMessage; -import cn.iocoder.yudao.framework.mq.redis.core.stream.AbstractRedisStreamMessageListener; -import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; -import cn.iocoder.yudao.module.iot.core.messagebus.core.local.IotLocalMessageBus; -import cn.iocoder.yudao.module.iot.core.messagebus.core.redis.IotRedisMessageBus; -import cn.iocoder.yudao.module.iot.core.messagebus.core.rocketmq.IotRocketMQMessageBus; -import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer; -import lombok.extern.slf4j.Slf4j; -import org.apache.rocketmq.spring.autoconfigure.RocketMQProperties; -import org.apache.rocketmq.spring.core.RocketMQTemplate; -import org.redisson.api.RedissonClient; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.StringRedisTemplate; - -import java.util.List; - -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; - -/** - * IoT 消息总线自动配置 - * - * @author 芋道源码 - */ -@AutoConfiguration -@EnableConfigurationProperties(IotMessageBusProperties.class) -@Slf4j -public class IotMessageBusAutoConfiguration { - - @Bean - public IotDeviceMessageProducer deviceMessageProducer(IotMessageBus messageBus) { - return new IotDeviceMessageProducer(messageBus); - } - - // ==================== Local 实现 ==================== - - @Configuration - @ConditionalOnProperty(prefix = "yudao.iot.message-bus", name = "type", havingValue = "local", matchIfMissing = true) - public static class IotLocalMessageBusConfiguration { - - @Bean - public IotLocalMessageBus iotLocalMessageBus(ApplicationContext applicationContext) { - log.info("[iotLocalMessageBus][创建 IoT Local 消息总线]"); - return new IotLocalMessageBus(applicationContext); - } - - } - - // ==================== RocketMQ 实现 ==================== - - @Configuration - @ConditionalOnProperty(prefix = "yudao.iot.message-bus", name = "type", havingValue = "rocketmq") - @ConditionalOnClass(RocketMQTemplate.class) - public static class IotRocketMQMessageBusConfiguration { - - @Bean - public IotRocketMQMessageBus iotRocketMQMessageBus(RocketMQProperties rocketMQProperties, - RocketMQTemplate rocketMQTemplate) { - log.info("[iotRocketMQMessageBus][创建 IoT RocketMQ 消息总线]"); - return new IotRocketMQMessageBus(rocketMQProperties, rocketMQTemplate); - } - - } - - // ==================== Redis 实现 ==================== - - /** - * 特殊:由于 YudaoRedisMQConsumerAutoConfiguration 关于 Redis stream 的消费是动态注册,所以这里只能拷贝相关的逻辑!!! - * - * @see cn.iocoder.yudao.framework.mq.redis.config.YudaoRedisMQConsumerAutoConfiguration - */ - @Configuration - @ConditionalOnProperty(prefix = "yudao.iot.message-bus", name = "type", havingValue = "redis") - @ConditionalOnClass(RedisTemplate.class) - public static class IotRedisMessageBusConfiguration { - - @Bean - public IotRedisMessageBus iotRedisMessageBus(StringRedisTemplate redisTemplate) { - log.info("[iotRedisMessageBus][创建 IoT Redis 消息总线]"); - return new IotRedisMessageBus(redisTemplate); - } - - /** - * 创建 Redis Stream 重新消费的任务 - */ - @Bean - public RedisPendingMessageResendJob iotRedisPendingMessageResendJob(IotRedisMessageBus messageBus, - RedisMQTemplate redisTemplate, - RedissonClient redissonClient) { - List> listeners = getListeners(messageBus); - return new RedisPendingMessageResendJob(listeners, redisTemplate, redissonClient); - } - - /** - * 创建 Redis Stream 消息清理任务 - */ - @Bean - public RedisStreamMessageCleanupJob iotRedisStreamMessageCleanupJob(IotRedisMessageBus messageBus, - RedisMQTemplate redisTemplate, - RedissonClient redissonClient) { - List> listeners = getListeners(messageBus); - return new RedisStreamMessageCleanupJob(listeners, redisTemplate, redissonClient); - } - - private List> getListeners(IotRedisMessageBus messageBus) { - return convertList(messageBus.getSubscribers(), subscriber -> - new AbstractRedisStreamMessageListener(subscriber.getTopic(), subscriber.getGroup()) { - - @Override - public void onMessage(AbstractRedisStreamMessage message) { - throw new UnsupportedOperationException("不应该调用!!!"); - } - }); - } - - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/config/IotMessageBusProperties.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/config/IotMessageBusProperties.java deleted file mode 100644 index 377b1dd9b7..0000000000 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/config/IotMessageBusProperties.java +++ /dev/null @@ -1,27 +0,0 @@ -package cn.iocoder.yudao.module.iot.core.messagebus.config; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.validation.annotation.Validated; - -import javax.validation.constraints.NotNull; - -/** - * IoT 消息总线配置属性 - * - * @author 芋道源码 - */ -@ConfigurationProperties("yudao.iot.message-bus") -@Data -@Validated -public class IotMessageBusProperties { - - /** - * 消息总线类型 - * - * 可选值:local、redis、rocketmq、rabbitmq - */ - @NotNull(message = "IoT 消息总线类型不能为空") - private String type = "local"; - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/IotMessageBus.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/IotMessageBus.java deleted file mode 100644 index c621467610..0000000000 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/IotMessageBus.java +++ /dev/null @@ -1,27 +0,0 @@ -package cn.iocoder.yudao.module.iot.core.messagebus.core; - -/** - * IoT 消息总线接口 - * - * 用于在 IoT 系统中发布和订阅消息,支持多种消息中间件实现 - * - * @author 芋道源码 - */ -public interface IotMessageBus { - - /** - * 发布消息到消息总线 - * - * @param topic 主题 - * @param message 消息内容 - */ - void post(String topic, Object message); - - /** - * 注册消息订阅者 - * - * @param subscriber 订阅者 - */ - void register(IotMessageSubscriber subscriber); - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/IotMessageSubscriber.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/IotMessageSubscriber.java deleted file mode 100644 index 23a055325c..0000000000 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/IotMessageSubscriber.java +++ /dev/null @@ -1,29 +0,0 @@ -package cn.iocoder.yudao.module.iot.core.messagebus.core; - -/** - * IoT 消息总线订阅者接口 - * - * 用于处理从消息总线接收到的消息 - * - * @author 芋道源码 - */ -public interface IotMessageSubscriber { - - /** - * @return 主题 - */ - String getTopic(); - - /** - * @return 分组 - */ - String getGroup(); - - /** - * 处理接收到的消息 - * - * @param message 消息内容 - */ - void onMessage(T message); - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/local/IotLocalMessage.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/local/IotLocalMessage.java deleted file mode 100644 index 5a9841a754..0000000000 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/local/IotLocalMessage.java +++ /dev/null @@ -1,14 +0,0 @@ -package cn.iocoder.yudao.module.iot.core.messagebus.core.local; - -import lombok.AllArgsConstructor; -import lombok.Data; - -@Data -@AllArgsConstructor -public class IotLocalMessage { - - private String topic; - - private Object message; - -} diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/local/IotLocalMessageBus.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/local/IotLocalMessageBus.java deleted file mode 100644 index 1fc608bc50..0000000000 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/local/IotLocalMessageBus.java +++ /dev/null @@ -1,67 +0,0 @@ -package cn.iocoder.yudao.module.iot.core.messagebus.core.local; - -import cn.hutool.core.collection.CollUtil; -import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; -import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.context.ApplicationContext; -import org.springframework.context.event.EventListener; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * 本地的 {@link IotMessageBus} 实现类 - * - * 注意:仅适用于单机场景!!! - * - * @author 芋道源码 - */ -@RequiredArgsConstructor -@Slf4j -public class IotLocalMessageBus implements IotMessageBus { - - private final ApplicationContext applicationContext; - - /** - * 订阅者映射表 - * Key: topic - */ - private final Map>> subscribers = new HashMap<>(); - - @Override - public void post(String topic, Object message) { - applicationContext.publishEvent(new IotLocalMessage(topic, message)); - } - - @Override - public void register(IotMessageSubscriber subscriber) { - String topic = subscriber.getTopic(); - List> topicSubscribers = subscribers.computeIfAbsent(topic, k -> new ArrayList<>()); - topicSubscribers.add(subscriber); - log.info("[register][topic({}/{}) 注册消费者({})成功]", - topic, subscriber.getGroup(), subscriber.getClass().getName()); - } - - @EventListener - @SuppressWarnings({"unchecked", "rawtypes"}) - public void onMessage(IotLocalMessage message) { - String topic = message.getTopic(); - List> topicSubscribers = subscribers.get(topic); - if (CollUtil.isEmpty(topicSubscribers)) { - return; - } - for (IotMessageSubscriber subscriber : topicSubscribers) { - try { - subscriber.onMessage(message.getMessage()); - } catch (Exception ex) { - log.error("[onMessage][topic({}/{}) message({}) 消费者({}) 处理异常]", - subscriber.getTopic(), subscriber.getGroup(), message.getMessage(), subscriber.getClass().getName(), ex); - } - } - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/redis/IotRedisMessageBus.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/redis/IotRedisMessageBus.java deleted file mode 100644 index d75301de4f..0000000000 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/redis/IotRedisMessageBus.java +++ /dev/null @@ -1,99 +0,0 @@ -package cn.iocoder.yudao.module.iot.core.messagebus.core.redis; - -import cn.hutool.core.util.TypeUtil; -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; -import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.redis.connection.stream.*; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.stream.StreamMessageListenerContainer; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.List; - -import static cn.iocoder.yudao.framework.mq.redis.config.YudaoRedisMQConsumerAutoConfiguration.buildConsumerName; -import static cn.iocoder.yudao.framework.mq.redis.config.YudaoRedisMQConsumerAutoConfiguration.checkRedisVersion; - -/** - * Redis 的 {@link IotMessageBus} 实现类 - * - * @author 芋道源码 - */ -@Slf4j -public class IotRedisMessageBus implements IotMessageBus { - - private final RedisTemplate redisTemplate; - - private final StreamMessageListenerContainer> redisStreamMessageListenerContainer; - - @Getter - private final List> subscribers = new ArrayList<>(); - - public IotRedisMessageBus(RedisTemplate redisTemplate) { - this.redisTemplate = redisTemplate; - checkRedisVersion(redisTemplate); - // 创建 options 配置 - StreamMessageListenerContainer.StreamMessageListenerContainerOptions> containerOptions = - StreamMessageListenerContainer.StreamMessageListenerContainerOptions.builder() - .batchSize(10) // 一次性最多拉取多少条消息 - .targetType(String.class) // 目标类型。统一使用 String,通过自己封装的 AbstractStreamMessageListener 去反序列化 - .build(); - // 创建 container 对象 - this.redisStreamMessageListenerContainer = - StreamMessageListenerContainer.create(redisTemplate.getRequiredConnectionFactory(), containerOptions); - } - - @PostConstruct - public void init() { - this.redisStreamMessageListenerContainer.start(); - } - - @PreDestroy - public void destroy() { - this.redisStreamMessageListenerContainer.stop(); - } - - @Override - public void post(String topic, Object message) { - redisTemplate.opsForStream().add(StreamRecords.newRecord() - .ofObject(JsonUtils.toJsonString(message)) // 设置内容 - .withStreamKey(topic)); // 设置 stream key - } - - @Override - public void register(IotMessageSubscriber subscriber) { - Type type = TypeUtil.getTypeArgument(subscriber.getClass(), 0); - if (type == null) { - throw new IllegalStateException(String.format("类型(%s) 需要设置消息类型", getClass().getName())); - } - - // 创建 listener 对应的消费者分组 - try { - redisTemplate.opsForStream().createGroup(subscriber.getTopic(), subscriber.getGroup()); - } catch (Exception ignore) { - } - // 创建 Consumer 对象 - String consumerName = buildConsumerName(); - Consumer consumer = Consumer.from(subscriber.getGroup(), consumerName); - // 设置 Consumer 消费进度,以最小消费进度为准 - StreamOffset streamOffset = StreamOffset.create(subscriber.getTopic(), ReadOffset.lastConsumed()); - // 设置 Consumer 监听 - StreamMessageListenerContainer.StreamReadRequestBuilder builder = StreamMessageListenerContainer.StreamReadRequest - .builder(streamOffset).consumer(consumer) - .autoAcknowledge(false) // 不自动 ack - .cancelOnError(throwable -> false); // 默认配置,发生异常就取消消费,显然不符合预期;因此,我们设置为 false - redisStreamMessageListenerContainer.register(builder.build(), message -> { - // 消费消息 - subscriber.onMessage(JsonUtils.parseObject(message.getValue(), type)); - // ack 消息消费完成 - redisTemplate.opsForStream().acknowledge(subscriber.getGroup(), message); - }); - this.subscribers.add(subscriber); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/rocketmq/IotRocketMQMessageBus.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/rocketmq/IotRocketMQMessageBus.java deleted file mode 100644 index 46aa05154b..0000000000 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/rocketmq/IotRocketMQMessageBus.java +++ /dev/null @@ -1,98 +0,0 @@ -package cn.iocoder.yudao.module.iot.core.messagebus.core.rocketmq; - -import cn.hutool.core.util.TypeUtil; -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; -import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber; -import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; -import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; -import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; -import org.apache.rocketmq.client.producer.SendResult; -import org.apache.rocketmq.common.message.MessageExt; -import org.apache.rocketmq.spring.autoconfigure.RocketMQProperties; -import org.apache.rocketmq.spring.core.RocketMQTemplate; - -import javax.annotation.PreDestroy; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.List; - -/** - * 基于 RocketMQ 的 {@link IotMessageBus} 实现类 - * - * @author 芋道源码 - */ -@RequiredArgsConstructor -@Slf4j -public class IotRocketMQMessageBus implements IotMessageBus { - - private final RocketMQProperties rocketMQProperties; - - private final RocketMQTemplate rocketMQTemplate; - - /** - * 主题对应的消费者映射 - */ - private final List topicConsumers = new ArrayList<>(); - - /** - * 销毁时关闭所有消费者 - */ - @PreDestroy - public void destroy() { - for (DefaultMQPushConsumer consumer : topicConsumers) { - try { - consumer.shutdown(); - log.info("[destroy][关闭 group({}) 的消费者成功]", consumer.getConsumerGroup()); - } catch (Exception e) { - log.error("[destroy]关闭 group({}) 的消费者异常]", consumer.getConsumerGroup(), e); - } - } - } - - @Override - public void post(String topic, Object message) { - // TODO @芋艿:需要 orderly! - SendResult result = rocketMQTemplate.syncSend(topic, JsonUtils.toJsonString(message)); - log.info("[post][topic({}) 发送消息({}) result({})]", topic, message, result); - } - - @Override - @SneakyThrows - public void register(IotMessageSubscriber subscriber) { - Type type = TypeUtil.getTypeArgument(subscriber.getClass(), 0); - if (type == null) { - throw new IllegalStateException(String.format("类型(%s) 需要设置消息类型", getClass().getName())); - } - - // 1.1 创建 DefaultMQPushConsumer - DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(); - consumer.setNamesrvAddr(rocketMQProperties.getNameServer()); - consumer.setConsumerGroup(subscriber.getGroup()); - // 1.2 订阅主题 - consumer.subscribe(subscriber.getTopic(), "*"); - // 1.3 设置消息监听器 - consumer.setMessageListener((MessageListenerConcurrently) (messages, context) -> { - for (MessageExt messageExt : messages) { - try { - byte[] body = messageExt.getBody(); - subscriber.onMessage(JsonUtils.parseObject(body, type)); - } catch (Exception ex) { - log.error("[onMessage][topic({}/{}) message({}) 消费者({}) 处理异常]", - subscriber.getTopic(), subscriber.getGroup(), messageExt, subscriber.getClass().getName(), ex); - return ConsumeConcurrentlyStatus.RECONSUME_LATER; - } - } - return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; - }); - // 1.4 启动消费者 - consumer.start(); - - // 2. 保存消费者引用 - topicConsumers.add(consumer); - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/mq/message/IotDeviceMessage.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/mq/message/IotDeviceMessage.java deleted file mode 100644 index 6821c0d160..0000000000 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/mq/message/IotDeviceMessage.java +++ /dev/null @@ -1,151 +0,0 @@ -package cn.iocoder.yudao.module.iot.core.mq.message; - -import cn.hutool.core.map.MapUtil; -import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; -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.util.IotDeviceMessageUtils; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.time.LocalDateTime; - -/** - * IoT 设备消息 - */ -@Data -@NoArgsConstructor -@AllArgsConstructor -@Builder -public class IotDeviceMessage { - - /** - * 【消息总线】应用的设备消息 Topic,由 iot-gateway 发给 iot-biz 进行消费 - */ - public static final String MESSAGE_BUS_DEVICE_MESSAGE_TOPIC = "iot_device_message"; - - /** - * 【消息总线】设备消息 Topic,由 iot-biz 发送给 iot-gateway 的某个 "server"(protocol) 进行消费 - * - * 其中,%s 就是该"server"(protocol) 的标识 - */ - public static final String MESSAGE_BUS_GATEWAY_DEVICE_MESSAGE_TOPIC = MESSAGE_BUS_DEVICE_MESSAGE_TOPIC + "_%s"; - - /** - * 消息编号 - * - * 由后端生成,通过 {@link IotDeviceMessageUtils#generateMessageId()} - */ - private String id; - /** - * 上报时间 - * - * 由后端生成,当前时间 - */ - private LocalDateTime reportTime; - - /** - * 设备编号 - */ - private Long deviceId; - /** - * 租户编号 - */ - private Long tenantId; - - /** - * 服务编号,该消息由哪个 server 发送 - */ - private String serverId; - - // ========== 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; - - // ========== 基础方法:只传递"codec(编解码)字段" ========== - - public static IotDeviceMessage requestOf(String method) { - return requestOf(null, method, null); - } - - public static IotDeviceMessage requestOf(String method, Object params) { - return requestOf(null, method, params); - } - - public static IotDeviceMessage requestOf(String requestId, String method, Object params) { - return of(requestId, method, params, null, null, null); - } - - public static IotDeviceMessage replyOf(String requestId, String method, - Object data, Integer code, String msg) { - if (code == null) { - code = GlobalErrorCodeConstants.SUCCESS.getCode(); - msg = GlobalErrorCodeConstants.SUCCESS.getMsg(); - } - return of(requestId, method, null, data, code, msg); - } - - public static IotDeviceMessage of(String requestId, String method, - Object params, Object data, Integer code, String msg) { - // 通用参数 - IotDeviceMessage message = new IotDeviceMessage() - .setId(IotDeviceMessageUtils.generateMessageId()).setReportTime(LocalDateTime.now()); - // 当前参数 - message.setRequestId(requestId).setMethod(method).setParams(params) - .setData(data).setCode(code).setMsg(msg); - return message; - } - - // ========== 核心方法:在 of 基础方法之上,添加对应 method ========== - - public static IotDeviceMessage buildStateUpdateOnline() { - return requestOf(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod(), - MapUtil.of("state", IotDeviceStateEnum.ONLINE.getState())); - } - - public static IotDeviceMessage buildStateOffline() { - return requestOf(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod(), - MapUtil.of("state", IotDeviceStateEnum.OFFLINE.getState())); - } - - public static IotDeviceMessage buildOtaUpgrade(String version, String fileUrl, Long fileSize, - String fileDigestAlgorithm, String fileDigestValue) { - return requestOf(IotDeviceMessageMethodEnum.OTA_UPGRADE.getMethod(), MapUtil.builder() - .put("version", version).put("fileUrl", fileUrl).put("fileSize", fileSize) - .put("fileDigestAlgorithm", fileDigestAlgorithm).put("fileDigestValue", fileDigestValue) - .build()); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/mq/producer/IotDeviceMessageProducer.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/mq/producer/IotDeviceMessageProducer.java deleted file mode 100644 index e152417230..0000000000 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/mq/producer/IotDeviceMessageProducer.java +++ /dev/null @@ -1,37 +0,0 @@ -package cn.iocoder.yudao.module.iot.core.mq.producer; - -import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; -import lombok.RequiredArgsConstructor; - -/** - * IoT 设备消息生产者 - * - * @author 芋道源码 - */ -@RequiredArgsConstructor -public class IotDeviceMessageProducer { - - private final IotMessageBus messageBus; - - /** - * 发送设备消息 - * - * @param message 设备消息 - */ - public void sendDeviceMessage(IotDeviceMessage message) { - messageBus.post(IotDeviceMessage.MESSAGE_BUS_DEVICE_MESSAGE_TOPIC, message); - } - - /** - * 发送网关设备消息 - * - * @param serverId 网关的 serverId 标识 - * @param message 设备消息 - */ - public void sendDeviceMessageToGateway(String serverId, IotDeviceMessage message) { - messageBus.post(IotDeviceMessageUtils.buildMessageBusGatewayDeviceMessageTopic(serverId), message); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceAuthUtils.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceAuthUtils.java deleted file mode 100644 index 2bc4880070..0000000000 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceAuthUtils.java +++ /dev/null @@ -1,85 +0,0 @@ -package cn.iocoder.yudao.module.iot.core.util; - -import cn.hutool.crypto.digest.DigestUtil; -import cn.hutool.crypto.digest.HmacAlgorithm; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - * IoT 设备【认证】的工具类,参考阿里云 - * - * @see 如何计算 MQTT 签名参数 - */ -public class IotDeviceAuthUtils { - - /** - * 认证信息 - */ - @Data - @NoArgsConstructor - @AllArgsConstructor - public static class AuthInfo { - - /** - * 客户端 ID - */ - private String clientId; - - /** - * 用户名 - */ - private String username; - - /** - * 密码 - */ - private String password; - - } - - /** - * 设备信息 - */ - @Data - public static class DeviceInfo { - - private String productKey; - - private String deviceName; - - } - - public static AuthInfo getAuthInfo(String productKey, String deviceName, String deviceSecret) { - String clientId = buildClientId(productKey, deviceName); - String username = buildUsername(productKey, deviceName); - String content = "clientId" + clientId + - "deviceName" + deviceName + - "deviceSecret" + deviceSecret + - "productKey" + productKey; - String password = buildPassword(deviceSecret, content); - return new AuthInfo(clientId, username, password); - } - - private static String buildClientId(String productKey, String deviceName) { - return String.format("%s.%s", productKey, deviceName); - } - - private static String buildUsername(String productKey, String deviceName) { - return String.format("%s&%s", deviceName, productKey); - } - - private static String buildPassword(String deviceSecret, String content) { - return DigestUtil.hmac(HmacAlgorithm.HmacSHA256, deviceSecret.getBytes()) - .digestHex(content); - } - - public static DeviceInfo parseUsername(String username) { - String[] usernameParts = username.split("&"); - if (usernameParts.length != 2) { - return null; - } - return new DeviceInfo().setProductKey(usernameParts[1]).setDeviceName(usernameParts[0]); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceMessageUtils.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceMessageUtils.java deleted file mode 100644 index 5b7778ea0c..0000000000 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceMessageUtils.java +++ /dev/null @@ -1,90 +0,0 @@ -package cn.iocoder.yudao.module.iot.core.util; - -import cn.hutool.core.lang.Assert; -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.system.SystemUtil; -import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; - -import java.util.Map; - -/** - * IoT 设备【消息】的工具类 - * - * @author 芋道源码 - */ -public class IotDeviceMessageUtils { - - // ========== Message 相关 ========== - - public static String generateMessageId() { - return IdUtil.fastSimpleUUID(); - } - - /** - * 是否是上行消息:由设备发送 - * - * @param message 消息 - * @return 是否 - */ - @SuppressWarnings("SimplifiableConditionalExpression") - public static boolean isUpstreamMessage(IotDeviceMessage message) { - IotDeviceMessageMethodEnum methodEnum = IotDeviceMessageMethodEnum.of(message.getMethod()); - Assert.notNull(methodEnum, "无法识别的消息方法:" + message.getMethod()); - // 注意:回复消息时,需要取反 - return !isReplyMessage(message) ? methodEnum.getUpstream() : !methodEnum.getUpstream(); - } - - /** - * 是否是回复消息,通过 {@link IotDeviceMessage#getCode()} 非空进行识别 - * - * @param message 消息 - * @return 是否 - */ - public static boolean isReplyMessage(IotDeviceMessage message) { - return message.getCode() != null; - } - - /** - * 提取消息中的标识符 - * - * @param message 消息 - * @return 标识符 - */ - @SuppressWarnings("unchecked") - public static String getIdentifier(IotDeviceMessage message) { - if (message.getParams() == null) { - return null; - } - if (StrUtil.equalsAny(message.getMethod(), IotDeviceMessageMethodEnum.EVENT_POST.getMethod(), - IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod())) { - Map params = (Map) message.getParams(); - return MapUtil.getStr(params, "identifier"); - } else if (StrUtil.equalsAny(message.getMethod(), IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod())) { - Map params = (Map) message.getParams(); - return MapUtil.getStr(params, "state"); - } - return null; - } - - // ========== Topic 相关 ========== - - public static String buildMessageBusGatewayDeviceMessageTopic(String serverId) { - return String.format(IotDeviceMessage.MESSAGE_BUS_GATEWAY_DEVICE_MESSAGE_TOPIC, serverId); - } - - /** - * 生成服务器编号 - * - * @param serverPort 服务器端口 - * @return 服务器编号 - */ - public static String generateServerId(Integer serverPort) { - String serverId = String.format("%s.%d", SystemUtil.getHostInfo().getAddress(), serverPort); - // 避免一些场景无法使用 . 符号,例如说 RocketMQ Topic - return serverId.replaceAll("\\.", "_"); - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/yudao-module-iot/yudao-module-iot-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index 4c183f8227..0000000000 --- a/yudao-module-iot/yudao-module-iot-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1 +0,0 @@ -cn.iocoder.yudao.module.iot.core.messagebus.config.IotMessageBusAutoConfiguration \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-core/src/test/java/cn/iocoder/yudao/module/iot/core/messagebus/core/TestMessage.java b/yudao-module-iot/yudao-module-iot-core/src/test/java/cn/iocoder/yudao/module/iot/core/messagebus/core/TestMessage.java deleted file mode 100644 index e06c9ec04b..0000000000 --- a/yudao-module-iot/yudao-module-iot-core/src/test/java/cn/iocoder/yudao/module/iot/core/messagebus/core/TestMessage.java +++ /dev/null @@ -1,12 +0,0 @@ -package cn.iocoder.yudao.module.iot.core.messagebus.core; - -import lombok.Data; - -@Data -public class TestMessage { - - private String nickname; - - private Integer age; - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-core/src/test/java/cn/iocoder/yudao/module/iot/core/messagebus/core/local/LocalIotMessageBusIntegrationTest.java b/yudao-module-iot/yudao-module-iot-core/src/test/java/cn/iocoder/yudao/module/iot/core/messagebus/core/local/LocalIotMessageBusIntegrationTest.java deleted file mode 100644 index 72eb3a3281..0000000000 --- a/yudao-module-iot/yudao-module-iot-core/src/test/java/cn/iocoder/yudao/module/iot/core/messagebus/core/local/LocalIotMessageBusIntegrationTest.java +++ /dev/null @@ -1,178 +0,0 @@ -package cn.iocoder.yudao.module.iot.core.messagebus.core.local; - -import cn.iocoder.yudao.module.iot.core.messagebus.config.IotMessageBusAutoConfiguration; -import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; -import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber; -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Import; -import org.springframework.test.context.TestPropertySource; - -import javax.annotation.Resource; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * {@link IotLocalMessageBus} 集成测试 - * - * @author 芋道源码 - */ -@SpringBootTest(classes = LocalIotMessageBusIntegrationTest.class) -@Import(IotMessageBusAutoConfiguration.class) -@TestPropertySource(properties = { - "yudao.iot.message-bus.type=local" -}) -@Slf4j -public class LocalIotMessageBusIntegrationTest { - - @Resource - private IotMessageBus messageBus; - - /** - * 1 topic 2 subscriber - */ - @Test - public void testSendMessageWithTwoSubscribers() throws InterruptedException { - // 准备 - String topic = "test-topic"; - String testMessage = "Hello IoT Message Bus!"; - // 用于等待消息处理完成 - CountDownLatch latch = new CountDownLatch(2); - // 用于记录接收到的消息 - AtomicInteger subscriber1Count = new AtomicInteger(0); - AtomicInteger subscriber2Count = new AtomicInteger(0); - - // 创建第一个订阅者 - IotMessageSubscriber subscriber1 = new IotMessageSubscriber() { - - @Override - public String getTopic() { - return topic; - } - - @Override - public String getGroup() { - return "group1"; - } - - @Override - public void onMessage(String message) { - log.info("[订阅者1] 收到消息 - Topic: {}, Message: {}", getTopic(), message); - subscriber1Count.incrementAndGet(); - assertEquals(testMessage, message); - latch.countDown(); - } - - }; - // 创建第二个订阅者 - IotMessageSubscriber subscriber2 = new IotMessageSubscriber() { - - @Override - public String getTopic() { - return topic; - } - - @Override - public String getGroup() { - return "group2"; - } - - @Override - public void onMessage(String message) { - log.info("[订阅者2] 收到消息 - Topic: {}, Message: {}", getTopic(), message); - subscriber2Count.incrementAndGet(); - assertEquals(testMessage, message); - latch.countDown(); - } - - }; - // 注册订阅者 - messageBus.register(subscriber1); - messageBus.register(subscriber2); - - // 发送消息 - log.info("[测试] 发送消息 - Topic: {}, Message: {}", topic, testMessage); - messageBus.post(topic, testMessage); - // 等待消息处理完成(最多等待 10 秒) - boolean completed = latch.await(10, TimeUnit.SECONDS); - - // 验证结果 - assertTrue(completed, "消息处理超时"); - assertEquals(1, subscriber1Count.get(), "订阅者 1 应该收到 1 条消息"); - assertEquals(1, subscriber2Count.get(), "订阅者 2 应该收到 1 条消息"); - log.info("[测试] 测试完成 - 订阅者 1 收到{}条消息,订阅者 2 收到{}条消息", subscriber1Count.get(), subscriber2Count.get()); - } - - /** - * 2 topic 2 subscriber - */ - @Test - public void testMultipleTopics() throws InterruptedException { - // 准备 - String topic1 = "device-status"; - String topic2 = "device-data"; - String message1 = "设备在线"; - String message2 = "温度:25°C"; - CountDownLatch latch = new CountDownLatch(2); - - // 创建订阅者 1 - 只订阅设备状态 - IotMessageSubscriber statusSubscriber = new IotMessageSubscriber() { - - @Override - public String getTopic() { - return topic1; - } - - @Override - public String getGroup() { - return "status-group"; - } - - @Override - public void onMessage(String message) { - log.info("[状态订阅者] 收到消息 - Topic: {}, Message: {}", getTopic(), message); - assertEquals(message1, message); - latch.countDown(); - } - - }; - // 创建订阅者 2 - 只订阅设备数据 - IotMessageSubscriber dataSubscriber = new IotMessageSubscriber() { - - @Override - public String getTopic() { - return topic2; - } - - @Override - public String getGroup() { - return "data-group"; - } - - @Override - public void onMessage(String message) { - log.info("[数据订阅者] 收到消息 - Topic: {}, Message: {}", getTopic(), message); - assertEquals(message2, message); - latch.countDown(); - } - - }; - // 注册订阅者到不同主题 - messageBus.register(statusSubscriber); - messageBus.register(dataSubscriber); - - // 发送消息到不同主题 - messageBus.post(topic1, message1); - messageBus.post(topic2, message2); - // 等待消息处理完成 - boolean completed = latch.await(10, TimeUnit.SECONDS); - assertTrue(completed, "消息处理超时"); - log.info("[测试] 多主题测试完成"); - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-core/src/test/java/cn/iocoder/yudao/module/iot/core/messagebus/core/rocketmq/RocketMQIotMessageBusTest.java b/yudao-module-iot/yudao-module-iot-core/src/test/java/cn/iocoder/yudao/module/iot/core/messagebus/core/rocketmq/RocketMQIotMessageBusTest.java deleted file mode 100644 index e080c27c23..0000000000 --- a/yudao-module-iot/yudao-module-iot-core/src/test/java/cn/iocoder/yudao/module/iot/core/messagebus/core/rocketmq/RocketMQIotMessageBusTest.java +++ /dev/null @@ -1,269 +0,0 @@ -package cn.iocoder.yudao.module.iot.core.messagebus.core.rocketmq; - -import cn.hutool.core.util.IdUtil; -import cn.iocoder.yudao.module.iot.core.messagebus.config.IotMessageBusAutoConfiguration; -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.messagebus.core.TestMessage; -import lombok.extern.slf4j.Slf4j; -import org.apache.rocketmq.spring.autoconfigure.RocketMQAutoConfiguration; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Import; -import org.springframework.test.context.TestPropertySource; - -import javax.annotation.Resource; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * {@link IotRocketMQMessageBus} 集成测试 - * - * @author 芋道源码 - */ -@SpringBootTest(classes = RocketMQIotMessageBusTest.class) -@Import({RocketMQAutoConfiguration.class, IotMessageBusAutoConfiguration.class}) -@TestPropertySource(properties = { - "yudao.iot.message-bus.type=rocketmq", - "rocketmq.name-server=127.0.0.1:9876", - "rocketmq.producer.group=test-rocketmq-group", - "rocketmq.producer.send-message-timeout=10000" -}) -@Slf4j -public class RocketMQIotMessageBusTest { - - @Resource - private IotMessageBus messageBus; - - /** - * 1 topic 1 subscriber(string) - */ - @Test - public void testSendMessageWithOneSubscriber() throws InterruptedException { - // 准备 - String topic = "test-topic-" + IdUtil.simpleUUID(); -// String topic = "test-topic-pojo"; - String testMessage = "Hello IoT Message Bus!"; - // 用于等待消息处理完成 - CountDownLatch latch = new CountDownLatch(1); - // 用于记录接收到的消息 - AtomicInteger subscriberCount = new AtomicInteger(0); - AtomicReference subscriberMessageRef = new AtomicReference<>(); - - // 发送消息(需要提前发,保证 RocketMQ 路由的创建) - log.info("[测试] 发送消息 - Topic: {}, Message: {}", topic, testMessage); - messageBus.post(topic, testMessage); - - // 创建订阅者 - IotMessageSubscriber subscriber1 = new IotMessageSubscriber() { - - @Override - public String getTopic() { - return topic; - } - - @Override - public String getGroup() { - return "test-topic-" + IdUtil.simpleUUID() + "-consumer"; -// return "test-topic-consumer-01"; - } - - @Override - public void onMessage(String message) { - log.info("[订阅者] 收到消息 - Topic: {}, Message: {}", getTopic(), message); - subscriberCount.incrementAndGet(); - subscriberMessageRef.set(message); - assertEquals(testMessage, message); - latch.countDown(); - } - - }; - // 注册订阅者 - messageBus.register(subscriber1); - - // 等待消息处理完成(最多等待 5 秒) - boolean completed = latch.await(10, TimeUnit.SECONDS); - - // 验证结果 - assertTrue(completed, "消息处理超时"); - assertEquals(1, subscriberCount.get(), "订阅者应该收到 1 条消息"); - log.info("[测试] 测试完成 - 订阅者收到{}条消息", subscriberCount.get()); - assertEquals(testMessage, subscriberMessageRef.get(), "接收到的消息内容不匹配"); - } - - /** - * 1 topic 2 subscriber(pojo) - */ - @Test - public void testSendMessageWithTwoSubscribers() throws InterruptedException { - // 准备 - String topic = "test-topic-" + IdUtil.simpleUUID(); -// String topic = "test-topic-pojo"; - TestMessage testMessage = new TestMessage().setNickname("yunai").setAge(18); - // 用于等待消息处理完成 - CountDownLatch latch = new CountDownLatch(2); - // 用于记录接收到的消息 - AtomicInteger subscriber1Count = new AtomicInteger(0); - AtomicReference subscriber1MessageRef = new AtomicReference<>(); - AtomicInteger subscriber2Count = new AtomicInteger(0); - AtomicReference subscriber2MessageRef = new AtomicReference<>(); - - // 发送消息(需要提前发,保证 RocketMQ 路由的创建) - log.info("[测试] 发送消息 - Topic: {}, Message: {}", topic, testMessage); - messageBus.post(topic, testMessage); - - // 创建第一个订阅者 - IotMessageSubscriber subscriber1 = new IotMessageSubscriber() { - - @Override - public String getTopic() { - return topic; - } - - @Override - public String getGroup() { - return "test-topic-" + IdUtil.simpleUUID() + "-consumer"; -// return "test-topic-consumer-01"; - } - - @Override - public void onMessage(TestMessage message) { - log.info("[订阅者1] 收到消息 - Topic: {}, Message: {}", getTopic(), message); - subscriber1Count.incrementAndGet(); - subscriber1MessageRef.set(message); - assertEquals(testMessage, message); - latch.countDown(); - } - - }; - // 创建第二个订阅者 - IotMessageSubscriber subscriber2 = new IotMessageSubscriber() { - - @Override - public String getTopic() { - return topic; - } - - @Override - public String getGroup() { - return "test-topic-" + IdUtil.simpleUUID() + "-consumer"; -// return "test-topic-consumer-02"; - } - - @Override - public void onMessage(TestMessage message) { - log.info("[订阅者2] 收到消息 - Topic: {}, Message: {}", getTopic(), message); - subscriber2Count.incrementAndGet(); - subscriber2MessageRef.set(message); - assertEquals(testMessage, message); - latch.countDown(); - } - - }; - // 注册订阅者 - messageBus.register(subscriber1); - messageBus.register(subscriber2); - - // 等待消息处理完成(最多等待 5 秒) - boolean completed = latch.await(10, TimeUnit.SECONDS); - - // 验证结果 - assertTrue(completed, "消息处理超时"); - assertEquals(1, subscriber1Count.get(), "订阅者 1 应该收到 1 条消息"); - assertEquals(1, subscriber2Count.get(), "订阅者 2 应该收到 1 条消息"); - log.info("[测试] 测试完成 - 订阅者 1 收到{}条消息,订阅者2收到{}条消息", subscriber1Count.get(), subscriber2Count.get()); - assertEquals(testMessage, subscriber1MessageRef.get(), "接收到的消息内容不匹配"); - assertEquals(testMessage, subscriber2MessageRef.get(), "接收到的消息内容不匹配"); - } - - /** - * 2 topic 2 subscriber - */ - @Test - public void testMultipleTopics() throws InterruptedException { - // 准备 - String topic1 = "device-status-" + IdUtil.simpleUUID(); - String topic2 = "device-data-" + IdUtil.simpleUUID(); - String message1 = "设备在线"; - String message2 = "温度:25°C"; - CountDownLatch latch = new CountDownLatch(2); - AtomicInteger subscriber1Count = new AtomicInteger(0); - AtomicReference subscriber1MessageRef = new AtomicReference<>(); - AtomicInteger subscriber2Count = new AtomicInteger(0); - AtomicReference subscriber2MessageRef = new AtomicReference<>(); - - - // 发送消息到不同主题(需要提前发,保证 RocketMQ 路由的创建) - log.info("[测试] 发送消息 - Topic1: {}, Message1: {}", topic1, message1); - messageBus.post(topic1, message1); - log.info("[测试] 发送消息 - Topic2: {}, Message2: {}", topic2, message2); - messageBus.post(topic2, message2); - - // 创建订阅者 1 - 只订阅设备状态 - IotMessageSubscriber statusSubscriber = new IotMessageSubscriber() { - - @Override - public String getTopic() { - return topic1; - } - - @Override - public String getGroup() { - return "status-group-" + IdUtil.simpleUUID(); - } - - @Override - public void onMessage(String message) { - log.info("[状态订阅者] 收到消息 - Topic: {}, Message: {}", getTopic(), message); - subscriber1Count.incrementAndGet(); - subscriber1MessageRef.set(message); - assertEquals(message1, message); - latch.countDown(); - } - - }; - // 创建订阅者 2 - 只订阅设备数据 - IotMessageSubscriber dataSubscriber = new IotMessageSubscriber() { - - @Override - public String getTopic() { - return topic2; - } - - @Override - public String getGroup() { - return "data-group-" + IdUtil.simpleUUID(); - } - - @Override - public void onMessage(String message) { - log.info("[数据订阅者] 收到消息 - Topic: {}, Message: {}", getTopic(), message); - subscriber2Count.incrementAndGet(); - subscriber2MessageRef.set(message); - assertEquals(message2, message); - latch.countDown(); - } - - }; - // 注册订阅者到不同主题 - messageBus.register(statusSubscriber); - messageBus.register(dataSubscriber); - - // 等待消息处理完成 - boolean completed = latch.await(10, TimeUnit.SECONDS); - - // 验证结果 - assertTrue(completed, "消息处理超时"); - assertEquals(1, subscriber1Count.get(), "状态订阅者应该收到 1 条消息"); - assertEquals(message1, subscriber1MessageRef.get(), "状态订阅者接收到的消息内容不匹配"); - assertEquals(1, subscriber2Count.get(), "数据订阅者应该收到 1 条消息"); - assertEquals(message2, subscriber2MessageRef.get(), "数据订阅者接收到的消息内容不匹配"); - log.info("[测试] 多主题测试完成 - 状态订阅者收到{}条消息,数据订阅者收到{}条消息", subscriber1Count.get(), subscriber2Count.get()); - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-gateway/pom.xml b/yudao-module-iot/yudao-module-iot-gateway/pom.xml deleted file mode 100644 index 3c2b1fc642..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/pom.xml +++ /dev/null @@ -1,78 +0,0 @@ - - - yudao-module-iot - cn.iocoder.boot - ${revision} - - 4.0.0 - jar - yudao-module-iot-gateway - - ${project.artifactId} - - iot 模块下,设备网关: - ① 功能一:接收来自设备的消息,并进行解码(decode)后,发送到消息网关,提供给 iot-biz 进行处理 - ② 功能二:接收来自消息网关的消息(由 iot-biz 发送),并进行编码(encode)后,发送给设备 - - - - - cn.iocoder.boot - yudao-module-iot-core - ${revision} - - - - org.springframework - spring-web - - - - - org.apache.rocketmq - rocketmq-spring-boot-starter - - - - - - - io.vertx - vertx-web - - - - - io.vertx - vertx-mqtt - - - - - cn.iocoder.boot - yudao-spring-boot-starter-test - test - - - - - - ${project.artifactId} - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - - - - - - diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/IotGatewayServerApplication.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/IotGatewayServerApplication.java deleted file mode 100644 index e9c4578850..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/IotGatewayServerApplication.java +++ /dev/null @@ -1,13 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class IotGatewayServerApplication { - - public static void main(String[] args) { - SpringApplication.run(IotGatewayServerApplication.class, args); - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/IotDeviceMessageCodec.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/IotDeviceMessageCodec.java deleted file mode 100644 index 94dd309dd1..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/IotDeviceMessageCodec.java +++ /dev/null @@ -1,33 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.codec; - -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; - -/** - * {@link cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage} 的编解码器 - * - * @author 芋道源码 - */ -public interface IotDeviceMessageCodec { - - /** - * 编码消息 - * - * @param message 消息 - * @return 编码后的消息内容 - */ - byte[] encode(IotDeviceMessage message); - - /** - * 解码消息 - * - * @param bytes 消息内容 - * @return 解码后的消息内容 - */ - IotDeviceMessage decode(byte[] bytes); - - /** - * @return 数据格式(编码器类型) - */ - String type(); - -} diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/alink/IotAlinkDeviceMessageCodec.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/alink/IotAlinkDeviceMessageCodec.java deleted file mode 100644 index 9086480d3f..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/alink/IotAlinkDeviceMessageCodec.java +++ /dev/null @@ -1,89 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.codec.alink; - -import cn.hutool.core.lang.Assert; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.springframework.stereotype.Component; - -/** - * 阿里云 Alink {@link IotDeviceMessage} 的编解码器 - * - * @author 芋道源码 - */ -@Component -public class IotAlinkDeviceMessageCodec implements IotDeviceMessageCodec { - - private static final String TYPE = "Alink"; - - @Data - @NoArgsConstructor - @AllArgsConstructor - private static class AlinkMessage { - - public static final String VERSION_1 = "1.0"; - - /** - * 消息 ID,且每个消息 ID 在当前设备具有唯一性 - */ - private String id; - - /** - * 版本号 - */ - private String version; - - /** - * 请求方法 - */ - private String method; - - /** - * 请求参数 - */ - private Object params; - - /** - * 响应结果 - */ - private Object data; - /** - * 响应错误码 - */ - private Integer code; - /** - * 响应提示 - * - * 特殊:这里阿里云是 message,为了保持和项目的 {@link CommonResult#getMsg()} 一致。 - */ - private String msg; - - } - - @Override - public String type() { - return TYPE; - } - - @Override - public byte[] encode(IotDeviceMessage message) { - AlinkMessage alinkMessage = new AlinkMessage(message.getRequestId(), AlinkMessage.VERSION_1, - message.getMethod(), message.getParams(), message.getData(), message.getCode(), message.getMsg()); - return JsonUtils.toJsonByte(alinkMessage); - } - - @Override - @SuppressWarnings("DataFlowIssue") - public IotDeviceMessage decode(byte[] bytes) { - AlinkMessage alinkMessage = JsonUtils.parseObject(bytes, AlinkMessage.class); - Assert.notNull(alinkMessage, "消息不能为空"); - Assert.equals(alinkMessage.getVersion(), AlinkMessage.VERSION_1, "消息版本号必须是 1.0"); - return IotDeviceMessage.of(alinkMessage.getId(), alinkMessage.getMethod(), alinkMessage.getParams(), - alinkMessage.getData(), alinkMessage.getCode(), alinkMessage.getMsg()); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/package-info.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/package-info.java deleted file mode 100644 index e1dae7707a..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * 提供设备接入的各种数据(请求、响应)的编解码 - */ -package cn.iocoder.yudao.module.iot.gateway.codec; \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/simple/package-info.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/simple/package-info.java deleted file mode 100644 index 5bd676ad1a..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/simple/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * TODO @芋艿:实现一个 alink 的 xml 版本 - */ -package cn.iocoder.yudao.module.iot.gateway.codec.simple; \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/tcp/IotTcpBinaryDeviceMessageCodec.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/tcp/IotTcpBinaryDeviceMessageCodec.java deleted file mode 100644 index 4f42a8c2f6..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/tcp/IotTcpBinaryDeviceMessageCodec.java +++ /dev/null @@ -1,286 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.codec.tcp; - -import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -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.gateway.codec.IotDeviceMessageCodec; -import io.vertx.core.buffer.Buffer; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -import java.nio.charset.StandardCharsets; - -/** - * TCP 二进制格式 {@link IotDeviceMessage} 编解码器 - *

- * 二进制协议格式(所有数值使用大端序): - * - *

- * +--------+--------+--------+---------------------------+--------+--------+
- * | 魔术字 | 版本号 | 消息类型|         消息长度(4 字节)          |
- * +--------+--------+--------+---------------------------+--------+--------+
- * |           消息 ID 长度(2 字节)        |      消息 ID (变长字符串)         |
- * +--------+--------+--------+--------+--------+--------+--------+--------+
- * |           方法名长度(2 字节)        |      方法名(变长字符串)         |
- * +--------+--------+--------+--------+--------+--------+--------+--------+
- * |                        消息体数据(变长)                              |
- * +--------+--------+--------+--------+--------+--------+--------+--------+
- * 
- *

- * 消息体格式: - * - 请求消息:params 数据(JSON) - * - 响应消息:code (4字节) + msg 长度(2字节) + msg 字符串 + data 数据(JSON) - *

- * 注意:deviceId 不包含在协议中,由服务器根据连接上下文自动设置 - * - * @author 芋道源码 - */ -@Slf4j -@Component -public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { - - public static final String TYPE = "TCP_BINARY"; - - /** - * 协议魔术字,用于协议识别 - */ - private static final byte MAGIC_NUMBER = (byte) 0x7E; - - /** - * 协议版本号 - */ - private static final byte PROTOCOL_VERSION = (byte) 0x01; - - /** - * 请求消息类型 - */ - private static final byte REQUEST = (byte) 0x01; - - /** - * 响应消息类型 - */ - private static final byte RESPONSE = (byte) 0x02; - - /** - * 协议头部固定长度(魔术字 + 版本号 + 消息类型 + 消息长度) - */ - private static final int HEADER_FIXED_LENGTH = 7; - - /** - * 最小消息长度(头部 + 消息ID长度 + 方法名长度) - */ - private static final int MIN_MESSAGE_LENGTH = HEADER_FIXED_LENGTH + 4; - - @Override - public String type() { - return TYPE; - } - - @Override - public byte[] encode(IotDeviceMessage message) { - Assert.notNull(message, "消息不能为空"); - Assert.notBlank(message.getMethod(), "消息方法不能为空"); - try { - // 1. 确定消息类型 - byte messageType = determineMessageType(message); - // 2. 构建消息体 - byte[] bodyData = buildMessageBody(message, messageType); - // 3. 构建完整消息 - return buildCompleteMessage(message, messageType, bodyData); - } catch (Exception e) { - log.error("[encode][TCP 二进制消息编码失败,消息: {}]", message, e); - throw new RuntimeException("TCP 二进制消息编码失败: " + e.getMessage(), e); - } - } - - @Override - public IotDeviceMessage decode(byte[] bytes) { - Assert.notNull(bytes, "待解码数据不能为空"); - Assert.isTrue(bytes.length >= MIN_MESSAGE_LENGTH, "数据包长度不足"); - try { - Buffer buffer = Buffer.buffer(bytes); - // 解析协议头部和消息内容 - int index = 0; - // 1. 验证魔术字 - byte magic = buffer.getByte(index++); - Assert.isTrue(magic == MAGIC_NUMBER, "无效的协议魔术字: " + magic); - - // 2. 验证版本号 - byte version = buffer.getByte(index++); - Assert.isTrue(version == PROTOCOL_VERSION, "不支持的协议版本: " + version); - - // 3. 读取消息类型 - byte messageType = buffer.getByte(index++); - // 直接验证消息类型,无需抽取方法 - Assert.isTrue(messageType == REQUEST || messageType == RESPONSE, - "无效的消息类型: " + messageType); - - // 4. 读取消息长度 - int messageLength = buffer.getInt(index); - index += 4; - Assert.isTrue(messageLength == buffer.length(), - "消息长度不匹配,期望: " + messageLength + ", 实际: " + buffer.length()); - - // 5. 读取消息 ID - short messageIdLength = buffer.getShort(index); - index += 2; - String messageId = buffer.getString(index, index + messageIdLength, StandardCharsets.UTF_8.name()); - index += messageIdLength; - - // 6. 读取方法名 - short methodLength = buffer.getShort(index); - index += 2; - String method = buffer.getString(index, index + methodLength, StandardCharsets.UTF_8.name()); - index += methodLength; - - // 7. 解析消息体 - return parseMessageBody(buffer, index, messageType, messageId, method); - } catch (Exception e) { - log.error("[decode][TCP 二进制消息解码失败,数据长度: {}]", bytes.length, e); - throw new RuntimeException("TCP 二进制消息解码失败: " + e.getMessage(), e); - } - } - - /** - * 确定消息类型 - * 优化后的判断逻辑:有响应字段就是响应消息,否则就是请求消息 - */ - private byte determineMessageType(IotDeviceMessage message) { - // 判断是否为响应消息:有响应码或响应消息时为响应 - if (message.getCode() != null) { - return RESPONSE; - } - // 默认为请求消息 - return REQUEST; - } - - /** - * 构建消息体 - */ - private byte[] buildMessageBody(IotDeviceMessage message, byte messageType) { - Buffer bodyBuffer = Buffer.buffer(); - if (messageType == RESPONSE) { - // code - bodyBuffer.appendInt(message.getCode() != null ? message.getCode() : 0); - // msg - String msg = message.getMsg() != null ? message.getMsg() : ""; - byte[] msgBytes = StrUtil.utf8Bytes(msg); - bodyBuffer.appendShort((short) msgBytes.length); - bodyBuffer.appendBytes(msgBytes); - // data - if (message.getData() != null) { - bodyBuffer.appendBytes(JsonUtils.toJsonByte(message.getData())); - } - } else { - // 请求消息只处理 params 参数 - // TODO @haohao:如果为空,是不是得写个长度 0 哈? - if (message.getParams() != null) { - bodyBuffer.appendBytes(JsonUtils.toJsonByte(message.getParams())); - } - } - return bodyBuffer.getBytes(); - } - - /** - * 构建完整消息 - */ - private byte[] buildCompleteMessage(IotDeviceMessage message, byte messageType, byte[] bodyData) { - Buffer buffer = Buffer.buffer(); - // 1. 写入协议头部 - buffer.appendByte(MAGIC_NUMBER); - buffer.appendByte(PROTOCOL_VERSION); - buffer.appendByte(messageType); - // 2. 预留消息长度位置(在 5. 更新消息长度) - int lengthPosition = buffer.length(); - buffer.appendInt(0); - // 3. 写入消息 ID - String messageId = StrUtil.isNotBlank(message.getRequestId()) ? message.getRequestId() - : IotDeviceMessageUtils.generateMessageId(); - byte[] messageIdBytes = StrUtil.utf8Bytes(messageId); - buffer.appendShort((short) messageIdBytes.length); - buffer.appendBytes(messageIdBytes); - // 4. 写入方法名 - byte[] methodBytes = StrUtil.utf8Bytes(message.getMethod()); - buffer.appendShort((short) methodBytes.length); - buffer.appendBytes(methodBytes); - // 5. 写入消息体 - buffer.appendBytes(bodyData); - // 6. 更新消息长度 - buffer.setInt(lengthPosition, buffer.length()); - return buffer.getBytes(); - } - - /** - * 解析消息体 - */ - private IotDeviceMessage parseMessageBody(Buffer buffer, int startIndex, byte messageType, - String messageId, String method) { - if (startIndex >= buffer.length()) { - // 空消息体 - return IotDeviceMessage.of(messageId, method, null, null, null, null); - } - - if (messageType == RESPONSE) { - // 响应消息:解析 code + msg + data - return parseResponseMessage(buffer, startIndex, messageId, method); - } else { - // 请求消息:解析 payload - Object payload = parseJsonData(buffer, startIndex, buffer.length()); - return IotDeviceMessage.of(messageId, method, payload, null, null, null); - } - } - - /** - * 解析响应消息 - */ - private IotDeviceMessage parseResponseMessage(Buffer buffer, int startIndex, String messageId, String method) { - int index = startIndex; - - // 1. 读取响应码 - Integer code = buffer.getInt(index); - index += 4; - - // 2. 读取响应消息 - short msgLength = buffer.getShort(index); - index += 2; - String msg = msgLength > 0 ? buffer.getString(index, index + msgLength, StandardCharsets.UTF_8.name()) : null; - index += msgLength; - - // 3. 读取响应数据 - Object data = null; - if (index < buffer.length()) { - data = parseJsonData(buffer, index, buffer.length()); - } - - return IotDeviceMessage.of(messageId, method, null, data, code, msg); - } - - /** - * 解析 JSON 数据 - */ - private Object parseJsonData(Buffer buffer, int startIndex, int endIndex) { - if (startIndex >= endIndex) { - return null; - } - try { - String jsonStr = buffer.getString(startIndex, endIndex, StandardCharsets.UTF_8.name()); - return JsonUtils.parseObject(jsonStr, Object.class); - } catch (Exception e) { - log.warn("[parseJsonData][JSON 解析失败,返回原始字符串]", e); - return buffer.getString(startIndex, endIndex, StandardCharsets.UTF_8.name()); - } - } - - /** - * 快速检测是否为二进制格式 - * - * @param data 数据 - * @return 是否为二进制格式 - */ - public static boolean isBinaryFormatQuick(byte[] data) { - return data != null && data.length >= 1 && data[0] == MAGIC_NUMBER; - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/tcp/IotTcpJsonDeviceMessageCodec.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/tcp/IotTcpJsonDeviceMessageCodec.java deleted file mode 100644 index 10ffbdf5c6..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/tcp/IotTcpJsonDeviceMessageCodec.java +++ /dev/null @@ -1,110 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.codec.tcp; - -import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.springframework.stereotype.Component; - -/** - * TCP JSON 格式 {@link IotDeviceMessage} 编解码器 - * - * 采用纯 JSON 格式传输,格式如下: - * { - * "id": "消息 ID", - * "method": "消息方法", - * "params": {...}, // 请求参数 - * "data": {...}, // 响应结果 - * "code": 200, // 响应错误码 - * "msg": "success", // 响应提示 - * "timestamp": 时间戳 - * } - * - * @author 芋道源码 - */ -@Component -public class IotTcpJsonDeviceMessageCodec implements IotDeviceMessageCodec { - - public static final String TYPE = "TCP_JSON"; - - @Data - @NoArgsConstructor - @AllArgsConstructor - private static class TcpJsonMessage { - - /** - * 消息 ID,且每个消息 ID 在当前设备具有唯一性 - */ - private String id; - - /** - * 请求方法 - */ - private String method; - - /** - * 请求参数 - */ - private Object params; - - /** - * 响应结果 - */ - private Object data; - - /** - * 响应错误码 - */ - private Integer code; - - /** - * 响应提示 - */ - private String msg; - - /** - * 时间戳 - */ - private Long timestamp; - - } - - @Override - public String type() { - return TYPE; - } - - @Override - public byte[] encode(IotDeviceMessage message) { - TcpJsonMessage tcpJsonMessage = new TcpJsonMessage( - message.getRequestId(), - message.getMethod(), - message.getParams(), - message.getData(), - message.getCode(), - message.getMsg(), - System.currentTimeMillis()); - return JsonUtils.toJsonByte(tcpJsonMessage); - } - - @Override - @SuppressWarnings("DataFlowIssue") - public IotDeviceMessage decode(byte[] bytes) { - String jsonStr = StrUtil.utf8Str(bytes).trim(); - TcpJsonMessage tcpJsonMessage = JsonUtils.parseObject(jsonStr, TcpJsonMessage.class); - Assert.notNull(tcpJsonMessage, "消息不能为空"); - Assert.notBlank(tcpJsonMessage.getMethod(), "消息方法不能为空"); - return IotDeviceMessage.of( - tcpJsonMessage.getId(), - tcpJsonMessage.getMethod(), - tcpJsonMessage.getParams(), - tcpJsonMessage.getData(), - tcpJsonMessage.getCode(), - tcpJsonMessage.getMsg()); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayConfiguration.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayConfiguration.java deleted file mode 100644 index 4b9c3af32c..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayConfiguration.java +++ /dev/null @@ -1,154 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.config; - -import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; -import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxAuthEventProtocol; -import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxDownstreamSubscriber; -import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxUpstreamProtocol; -import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpDownstreamSubscriber; -import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpUpstreamProtocol; -import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttDownstreamSubscriber; -import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol; -import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager; -import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttDownstreamHandler; -import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpDownstreamSubscriber; -import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpUpstreamProtocol; -import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager.IotTcpConnectionManager; -import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService; -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; -import io.vertx.core.Vertx; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -@EnableConfigurationProperties(IotGatewayProperties.class) -@Slf4j -public class IotGatewayConfiguration { - - /** - * IoT 网关 HTTP 协议配置类 - */ - @Configuration - @ConditionalOnProperty(prefix = "yudao.iot.gateway.protocol.http", name = "enabled", havingValue = "true") - @Slf4j - public static class HttpProtocolConfiguration { - - @Bean - public IotHttpUpstreamProtocol iotHttpUpstreamProtocol(IotGatewayProperties gatewayProperties) { - return new IotHttpUpstreamProtocol(gatewayProperties.getProtocol().getHttp()); - } - - @Bean - public IotHttpDownstreamSubscriber iotHttpDownstreamSubscriber(IotHttpUpstreamProtocol httpUpstreamProtocol, - IotMessageBus messageBus) { - return new IotHttpDownstreamSubscriber(httpUpstreamProtocol, messageBus); - } - } - - /** - * IoT 网关 EMQX 协议配置类 - */ - @Configuration - @ConditionalOnProperty(prefix = "yudao.iot.gateway.protocol.emqx", name = "enabled", havingValue = "true") - @Slf4j - public static class EmqxProtocolConfiguration { - - @Bean(destroyMethod = "close") - public Vertx emqxVertx() { - return Vertx.vertx(); - } - - @Bean - public IotEmqxAuthEventProtocol iotEmqxAuthEventProtocol(IotGatewayProperties gatewayProperties, - Vertx emqxVertx) { - return new IotEmqxAuthEventProtocol(gatewayProperties.getProtocol().getEmqx(), emqxVertx); - } - - @Bean - public IotEmqxUpstreamProtocol iotEmqxUpstreamProtocol(IotGatewayProperties gatewayProperties, - Vertx emqxVertx) { - return new IotEmqxUpstreamProtocol(gatewayProperties.getProtocol().getEmqx(), emqxVertx); - } - - @Bean - public IotEmqxDownstreamSubscriber iotEmqxDownstreamSubscriber(IotEmqxUpstreamProtocol mqttUpstreamProtocol, - IotMessageBus messageBus) { - return new IotEmqxDownstreamSubscriber(mqttUpstreamProtocol, messageBus); - } - } - - /** - * IoT 网关 TCP 协议配置类 - */ - @Configuration - @ConditionalOnProperty(prefix = "yudao.iot.gateway.protocol.tcp", name = "enabled", havingValue = "true") - @Slf4j - public static class TcpProtocolConfiguration { - - @Bean(destroyMethod = "close") - public Vertx tcpVertx() { - return Vertx.vertx(); - } - - @Bean - public IotTcpUpstreamProtocol iotTcpUpstreamProtocol(IotGatewayProperties gatewayProperties, - IotDeviceService deviceService, - IotDeviceMessageService messageService, - IotTcpConnectionManager connectionManager, - Vertx tcpVertx) { - return new IotTcpUpstreamProtocol(gatewayProperties.getProtocol().getTcp(), - deviceService, messageService, connectionManager, tcpVertx); - } - - @Bean - public IotTcpDownstreamSubscriber iotTcpDownstreamSubscriber(IotTcpUpstreamProtocol protocolHandler, - IotDeviceMessageService messageService, - IotDeviceService deviceService, - IotTcpConnectionManager connectionManager, - IotMessageBus messageBus) { - return new IotTcpDownstreamSubscriber(protocolHandler, messageService, deviceService, connectionManager, - messageBus); - } - - } - - /** - * IoT 网关 MQTT 协议配置类 - */ - @Configuration - @ConditionalOnProperty(prefix = "yudao.iot.gateway.protocol.mqtt", name = "enabled", havingValue = "true") - @Slf4j - public static class MqttProtocolConfiguration { - - @Bean(destroyMethod = "close") - public Vertx mqttVertx() { - return Vertx.vertx(); - } - - @Bean - public IotMqttUpstreamProtocol iotMqttUpstreamProtocol(IotGatewayProperties gatewayProperties, - IotDeviceMessageService messageService, - IotMqttConnectionManager connectionManager, - Vertx mqttVertx) { - return new IotMqttUpstreamProtocol(gatewayProperties.getProtocol().getMqtt(), messageService, - connectionManager, mqttVertx); - } - - @Bean - public IotMqttDownstreamHandler iotMqttDownstreamHandler(IotDeviceMessageService messageService, - IotMqttConnectionManager connectionManager) { - return new IotMqttDownstreamHandler(messageService, connectionManager); - } - - @Bean - public IotMqttDownstreamSubscriber iotMqttDownstreamSubscriber(IotMqttUpstreamProtocol mqttUpstreamProtocol, - IotMqttDownstreamHandler downstreamHandler, - IotMessageBus messageBus) { - return new IotMqttDownstreamSubscriber(mqttUpstreamProtocol, downstreamHandler, messageBus); - } - - } - -} diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayProperties.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayProperties.java deleted file mode 100644 index 1a2bf82a1e..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayProperties.java +++ /dev/null @@ -1,405 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.config; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.validation.annotation.Validated; - -import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; -import java.time.Duration; -import java.util.List; - -@ConfigurationProperties(prefix = "yudao.iot.gateway") -@Validated -@Data -public class IotGatewayProperties { - - /** - * 设备 RPC 服务配置 - */ - private RpcProperties rpc; - /** - * Token 配置 - */ - private TokenProperties token; - - /** - * 协议配置 - */ - private ProtocolProperties protocol; - - @Data - public static class RpcProperties { - - /** - * 主程序 API 地址 - */ - @NotEmpty(message = "主程序 API 地址不能为空") - private String url; - /** - * 连接超时时间 - */ - @NotNull(message = "连接超时时间不能为空") - private Duration connectTimeout; - /** - * 读取超时时间 - */ - @NotNull(message = "读取超时时间不能为空") - private Duration readTimeout; - - } - - @Data - public static class TokenProperties { - - /** - * 密钥 - */ - @NotEmpty(message = "密钥不能为空") - private String secret; - /** - * 令牌有效期 - */ - @NotNull(message = "令牌有效期不能为空") - private Duration expiration; - - } - - @Data - public static class ProtocolProperties { - - /** - * HTTP 组件配置 - */ - private HttpProperties http; - - /** - * EMQX 组件配置 - */ - private EmqxProperties emqx; - - /** - * TCP 组件配置 - */ - private TcpProperties tcp; - - /** - * MQTT 组件配置 - */ - private MqttProperties mqtt; - - } - - @Data - public static class HttpProperties { - - /** - * 是否开启 - */ - @NotNull(message = "是否开启不能为空") - private Boolean enabled; - /** - * 服务端口 - */ - private Integer serverPort; - - /** - * 是否开启 SSL - */ - @NotNull(message = "是否开启 SSL 不能为空") - private Boolean sslEnabled = false; - - /** - * SSL 证书路径 - */ - private String sslKeyPath; - /** - * SSL 证书路径 - */ - private String sslCertPath; - - } - - @Data - public static class EmqxProperties { - - /** - * 是否开启 - */ - @NotNull(message = "是否开启不能为空") - private Boolean enabled; - - /** - * HTTP 服务端口(默认:8090) - */ - private Integer httpPort = 8090; - - /** - * MQTT 服务器地址 - */ - @NotEmpty(message = "MQTT 服务器地址不能为空") - private String mqttHost; - - /** - * MQTT 服务器端口(默认:1883) - */ - @NotNull(message = "MQTT 服务器端口不能为空") - private Integer mqttPort = 1883; - - /** - * MQTT 用户名 - */ - @NotEmpty(message = "MQTT 用户名不能为空") - private String mqttUsername; - - /** - * MQTT 密码 - */ - @NotEmpty(message = "MQTT 密码不能为空") - private String mqttPassword; - - /** - * MQTT 客户端的 SSL 开关 - */ - @NotNull(message = "MQTT 是否开启 SSL 不能为空") - private Boolean mqttSsl = false; - - /** - * MQTT 客户端 ID(如果为空,系统将自动生成) - */ - @NotEmpty(message = "MQTT 客户端 ID 不能为空") - private String mqttClientId; - - /** - * MQTT 订阅的主题 - */ - @NotEmpty(message = "MQTT 主题不能为空") - private List<@NotEmpty(message = "MQTT 主题不能为空") String> mqttTopics; - - /** - * 默认 QoS 级别 - *

- * 0 - 最多一次 - * 1 - 至少一次 - * 2 - 刚好一次 - */ - private Integer mqttQos = 1; - - /** - * 连接超时时间(秒) - */ - private Integer connectTimeoutSeconds = 10; - - /** - * 重连延迟时间(毫秒) - */ - private Long reconnectDelayMs = 5000L; - - /** - * 是否启用 Clean Session (清理会话) - * true: 每次连接都是新会话,Broker 不保留离线消息和订阅关系。 - * 对于网关这类“永远在线”且会主动重新订阅的应用,建议为 true。 - */ - private Boolean cleanSession = true; - - /** - * 心跳间隔(秒) - * 用于保持连接活性,及时发现网络中断。 - */ - private Integer keepAliveIntervalSeconds = 60; - - /** - * 最大未确认消息队列大小 - * 限制已发送但未收到 Broker 确认的 QoS 1/2 消息数量,用于流量控制。 - */ - private Integer maxInflightQueue = 10000; - - /** - * 是否信任所有 SSL 证书 - * 警告:此配置会绕过证书验证,仅建议在开发和测试环境中使用! - * 在生产环境中,应设置为 false,并配置正确的信任库。 - */ - private Boolean trustAll = false; - - /** - * 遗嘱消息配置 (用于网关异常下线时通知其他系统) - */ - private final Will will = new Will(); - - /** - * 高级 SSL/TLS 配置 (用于生产环境) - */ - private final Ssl sslOptions = new Ssl(); - - /** - * 遗嘱消息 (Last Will and Testament) - */ - @Data - public static class Will { - - /** - * 是否启用遗嘱消息 - */ - private boolean enabled = false; - /** - * 遗嘱消息主题 - */ - private String topic; - /** - * 遗嘱消息内容 - */ - private String payload; - /** - * 遗嘱消息 QoS 等级 - */ - private Integer qos = 1; - /** - * 遗嘱消息是否作为保留消息发布 - */ - private boolean retain = true; - - } - - /** - * 高级 SSL/TLS 配置 - */ - @Data - public static class Ssl { - - /** - * 密钥库(KeyStore)路径,例如:classpath:certs/client.jks - * 包含客户端自己的证书和私钥,用于向服务端证明身份(双向认证)。 - */ - private String keyStorePath; - /** - * 密钥库密码 - */ - private String keyStorePassword; - /** - * 信任库(TrustStore)路径,例如:classpath:certs/trust.jks - * 包含服务端信任的 CA 证书,用于验证服务端的身份,防止中间人攻击。 - */ - private String trustStorePath; - /** - * 信任库密码 - */ - private String trustStorePassword; - - } - - } - - @Data - public static class TcpProperties { - - /** - * 是否开启 - */ - @NotNull(message = "是否开启不能为空") - private Boolean enabled; - - /** - * 服务器端口 - */ - private Integer port = 8091; - - /** - * 心跳超时时间(毫秒) - */ - private Long keepAliveTimeoutMs = 30000L; - - /** - * 最大连接数 - */ - private Integer maxConnections = 1000; - - /** - * 是否启用SSL - */ - private Boolean sslEnabled = false; - - /** - * SSL证书路径 - */ - private String sslCertPath; - - /** - * SSL私钥路径 - */ - private String sslKeyPath; - - } - - @Data - public static class MqttProperties { - - /** - * 是否开启 - */ - @NotNull(message = "是否开启不能为空") - private Boolean enabled; - - /** - * 服务器端口 - */ - private Integer port = 1883; - - /** - * 最大消息大小(字节) - */ - private Integer maxMessageSize = 8192; - - /** - * 连接超时时间(秒) - */ - private Integer connectTimeoutSeconds = 60; - /** - * 保持连接超时时间(秒) - */ - private Integer keepAliveTimeoutSeconds = 300; - - /** - * 是否启用 SSL - */ - private Boolean sslEnabled = false; - /** - * SSL 配置 - */ - private SslOptions sslOptions = new SslOptions(); - - /** - * SSL 配置选项 - */ - @Data - public static class SslOptions { - - /** - * 密钥证书选项 - */ - private io.vertx.core.net.KeyCertOptions keyCertOptions; - /** - * 信任选项 - */ - private io.vertx.core.net.TrustOptions trustOptions; - /** - * SSL 证书路径 - */ - private String certPath; - /** - * SSL 私钥路径 - */ - private String keyPath; - /** - * 信任存储路径 - */ - private String trustStorePath; - /** - * 信任存储密码 - */ - private String trustStorePassword; - - } - - } - -} diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/enums/ErrorCodeConstants.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/enums/ErrorCodeConstants.java deleted file mode 100644 index 90afda224e..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/enums/ErrorCodeConstants.java +++ /dev/null @@ -1,19 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.enums; - -import cn.iocoder.yudao.framework.common.exception.ErrorCode; - -/** - * iot gateway 错误码枚举类 - *

- * iot 系统,使用 1-051-000-000 段 - */ -public interface ErrorCodeConstants { - - // ========== 设备认证 1-050-001-000 ============ - ErrorCode DEVICE_AUTH_FAIL = new ErrorCode(1_051_001_000, "设备鉴权失败"); // 对应阿里云 20000 - ErrorCode DEVICE_TOKEN_EXPIRED = new ErrorCode(1_051_001_002, "token 失效。需重新调用 auth 进行鉴权,获取token"); // 对应阿里云 20001 - - // ========== 设备信息 1-050-002-000 ============ - ErrorCode DEVICE_NOT_EXISTS = new ErrorCode(1_051_002_001, "设备({}/{}) 不存在"); - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/IotEmqxAuthEventProtocol.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/IotEmqxAuthEventProtocol.java deleted file mode 100644 index fb928eaa4c..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/IotEmqxAuthEventProtocol.java +++ /dev/null @@ -1,105 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.protocol.emqx; - -import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; -import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties; -import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.router.IotEmqxAuthEventHandler; -import cn.iocoder.yudao.module.iot.gateway.util.IotMqttTopicUtils; -import io.vertx.core.Vertx; -import io.vertx.core.http.HttpServer; -import io.vertx.ext.web.Router; -import io.vertx.ext.web.handler.BodyHandler; -import lombok.extern.slf4j.Slf4j; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; - -/** - * IoT 网关 EMQX 认证事件协议服务 - *

- * 为 EMQX 提供 HTTP 接口服务,包括: - * 1. 设备认证接口 - 对应 EMQX HTTP 认证插件 - * 2. 设备事件处理接口 - 对应 EMQX Webhook 事件通知 - * - * @author 芋道源码 - */ -@Slf4j -public class IotEmqxAuthEventProtocol { - - private final IotGatewayProperties.EmqxProperties emqxProperties; - - private final String serverId; - - private final Vertx vertx; - - private HttpServer httpServer; - - public IotEmqxAuthEventProtocol(IotGatewayProperties.EmqxProperties emqxProperties, - Vertx vertx) { - this.emqxProperties = emqxProperties; - this.vertx = vertx; - this.serverId = IotDeviceMessageUtils.generateServerId(emqxProperties.getMqttPort()); - } - - @PostConstruct - public void start() { - try { - startHttpServer(); - log.info("[start][IoT 网关 EMQX 认证事件协议服务启动成功, 端口: {}]", emqxProperties.getHttpPort()); - } catch (Exception e) { - log.error("[start][IoT 网关 EMQX 认证事件协议服务启动失败]", e); - throw e; - } - } - - @PreDestroy - public void stop() { - stopHttpServer(); - log.info("[stop][IoT 网关 EMQX 认证事件协议服务已停止]"); - } - - /** - * 启动 HTTP 服务器 - */ - private void startHttpServer() { - int port = emqxProperties.getHttpPort(); - - // 1. 创建路由 - Router router = Router.router(vertx); - router.route().handler(BodyHandler.create()); - - // 2. 创建处理器,传入 serverId - IotEmqxAuthEventHandler handler = new IotEmqxAuthEventHandler(serverId); - router.post(IotMqttTopicUtils.MQTT_AUTH_PATH).handler(handler::handleAuth); - router.post(IotMqttTopicUtils.MQTT_EVENT_PATH).handler(handler::handleEvent); - // TODO @haohao:/mqtt/acl 需要处理么? - // TODO @芋艿:已在 EMQX 处理,如果是“设备直连”模式需要处理 - - // 3. 启动 HTTP 服务器 - try { - httpServer = vertx.createHttpServer() - .requestHandler(router) - .listen(port) - .result(); - } catch (Exception e) { - log.error("[startHttpServer][HTTP 服务器启动失败, 端口: {}]", port, e); - throw e; - } - } - - /** - * 停止 HTTP 服务器 - */ - private void stopHttpServer() { - if (httpServer == null) { - return; - } - - try { - httpServer.close().result(); - log.info("[stopHttpServer][HTTP 服务器已停止]"); - } catch (Exception e) { - log.error("[stopHttpServer][HTTP 服务器停止失败]", e); - } - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/IotEmqxDownstreamSubscriber.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/IotEmqxDownstreamSubscriber.java deleted file mode 100644 index 9496b8a7f0..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/IotEmqxDownstreamSubscriber.java +++ /dev/null @@ -1,69 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.protocol.emqx; - -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.gateway.protocol.emqx.router.IotEmqxDownstreamHandler; -import lombok.extern.slf4j.Slf4j; - -import javax.annotation.PostConstruct; - -/** - * IoT 网关 EMQX 订阅者:接收下行给设备的消息 - * - * @author 芋道源码 - */ -@Slf4j -public class IotEmqxDownstreamSubscriber implements IotMessageSubscriber { - - private final IotEmqxDownstreamHandler downstreamHandler; - - private final IotMessageBus messageBus; - - private final IotEmqxUpstreamProtocol protocol; - - public IotEmqxDownstreamSubscriber(IotEmqxUpstreamProtocol protocol, IotMessageBus messageBus) { - this.protocol = protocol; - this.messageBus = messageBus; - this.downstreamHandler = new IotEmqxDownstreamHandler(protocol); - } - - @PostConstruct - public void init() { - messageBus.register(this); - } - - @Override - public String getTopic() { - return IotDeviceMessageUtils.buildMessageBusGatewayDeviceMessageTopic(protocol.getServerId()); - } - - @Override - public String getGroup() { - // 保证点对点消费,需要保证独立的 Group,所以使用 Topic 作为 Group - return getTopic(); - } - - @Override - public void onMessage(IotDeviceMessage message) { - log.debug("[onMessage][接收到下行消息, messageId: {}, method: {}, deviceId: {}]", - message.getId(), message.getMethod(), message.getDeviceId()); - try { - // 1. 校验 - String method = message.getMethod(); - if (method == null) { - log.warn("[onMessage][消息方法为空, messageId: {}, deviceId: {}]", - message.getId(), message.getDeviceId()); - return; - } - - // 2. 处理下行消息 - downstreamHandler.handle(message); - } catch (Exception e) { - log.error("[onMessage][处理下行消息失败, messageId: {}, method: {}, deviceId: {}]", - message.getId(), message.getMethod(), message.getDeviceId(), e); - } - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/IotEmqxUpstreamProtocol.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/IotEmqxUpstreamProtocol.java deleted file mode 100644 index 0528f378b3..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/IotEmqxUpstreamProtocol.java +++ /dev/null @@ -1,365 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.protocol.emqx; - -import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; -import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties; -import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.router.IotEmqxUpstreamHandler; -import io.netty.handler.codec.mqtt.MqttQoS; -import io.vertx.core.Vertx; -import io.vertx.core.buffer.Buffer; -import io.vertx.core.net.JksOptions; -import io.vertx.mqtt.MqttClient; -import io.vertx.mqtt.MqttClientOptions; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * IoT 网关 EMQX 协议:接收设备上行消息 - * - * @author 芋道源码 - */ -@Slf4j -public class IotEmqxUpstreamProtocol { - - private final IotGatewayProperties.EmqxProperties emqxProperties; - - private volatile boolean isRunning = false; - - private final Vertx vertx; - - @Getter - private final String serverId; - - private MqttClient mqttClient; - - private IotEmqxUpstreamHandler upstreamHandler; - - public IotEmqxUpstreamProtocol(IotGatewayProperties.EmqxProperties emqxProperties, - Vertx vertx) { - this.emqxProperties = emqxProperties; - this.serverId = IotDeviceMessageUtils.generateServerId(emqxProperties.getMqttPort()); - this.vertx = vertx; - } - - @PostConstruct - public void start() { - if (isRunning) { - return; - } - - try { - // 1. 启动 MQTT 客户端 - startMqttClient(); - - // 2. 标记服务为运行状态 - isRunning = true; - log.info("[start][IoT 网关 EMQX 协议启动成功]"); - } catch (Exception e) { - log.error("[start][IoT 网关 EMQX 协议服务启动失败,应用将关闭]", e); - stop(); - - // 异步关闭应用 - Thread shutdownThread = new Thread(() -> { - try { - // 确保日志输出完成,使用更优雅的方式 - log.error("[start][由于 MQTT 连接失败,正在关闭应用]"); - // 等待日志输出完成 - Thread.sleep(1000); - } catch (InterruptedException ie) { - Thread.currentThread().interrupt(); - log.warn("[start][应用关闭被中断]"); - } - System.exit(1); - }); - shutdownThread.setDaemon(true); - shutdownThread.setName("emergency-shutdown"); - shutdownThread.start(); - - throw e; - } - } - - @PreDestroy - public void stop() { - if (!isRunning) { - return; - } - - // 1. 停止 MQTT 客户端 - stopMqttClient(); - - // 2. 标记服务为停止状态 - isRunning = false; - log.info("[stop][IoT 网关 MQTT 协议服务已停止]"); - } - - /** - * 启动 MQTT 客户端 - */ - private void startMqttClient() { - try { - // 1. 初始化消息处理器 - this.upstreamHandler = new IotEmqxUpstreamHandler(this); - - // 2. 创建 MQTT 客户端 - createMqttClient(); - - // 3. 同步连接 MQTT Broker - connectMqttSync(); - } catch (Exception e) { - log.error("[startMqttClient][MQTT 客户端启动失败]", e); - throw new RuntimeException("MQTT 客户端启动失败: " + e.getMessage(), e); - } - } - - /** - * 同步连接 MQTT Broker - */ - private void connectMqttSync() { - String host = emqxProperties.getMqttHost(); - int port = emqxProperties.getMqttPort(); - // 1. 连接 MQTT Broker - CountDownLatch latch = new CountDownLatch(1); - AtomicBoolean success = new AtomicBoolean(false); - mqttClient.connect(port, host, connectResult -> { - if (connectResult.succeeded()) { - log.info("[connectMqttSync][MQTT 客户端连接成功, host: {}, port: {}]", host, port); - setupMqttHandlers(); - subscribeToTopics(); - success.set(true); - } else { - log.error("[connectMqttSync][连接 MQTT Broker 失败, host: {}, port: {}]", - host, port, connectResult.cause()); - } - latch.countDown(); - }); - - // 2. 等待连接结果 - try { - // 应用层超时控制:防止启动过程无限阻塞,与MQTT客户端的网络超时是不同层次的控制 - boolean awaitResult = latch.await(10, java.util.concurrent.TimeUnit.SECONDS); - if (!awaitResult) { - log.error("[connectMqttSync][等待连接结果超时]"); - throw new RuntimeException("连接 MQTT Broker 超时"); - } - if (!success.get()) { - throw new RuntimeException(String.format("首次连接 MQTT Broker 失败,地址: %s, 端口: %d", host, port)); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - log.error("[connectMqttSync][等待连接结果被中断]", e); - throw new RuntimeException("连接 MQTT Broker 被中断", e); - } - } - - /** - * 异步连接 MQTT Broker - */ - private void connectMqttAsync() { - String host = emqxProperties.getMqttHost(); - int port = emqxProperties.getMqttPort(); - mqttClient.connect(port, host, connectResult -> { - if (connectResult.succeeded()) { - log.info("[connectMqttAsync][MQTT 客户端重连成功]"); - setupMqttHandlers(); - subscribeToTopics(); - } else { - log.error("[connectMqttAsync][连接 MQTT Broker 失败, host: {}, port: {}]", - host, port, connectResult.cause()); - log.warn("[connectMqttAsync][重连失败,将再次尝试]"); - reconnectWithDelay(); - } - }); - } - - /** - * 延迟重连 - */ - private void reconnectWithDelay() { - if (!isRunning) { - return; - } - if (mqttClient != null && mqttClient.isConnected()) { - return; - } - - long delay = emqxProperties.getReconnectDelayMs(); - log.info("[reconnectWithDelay][将在 {} 毫秒后尝试重连 MQTT Broker]", delay); - vertx.setTimer(delay, timerId -> { - if (!isRunning) { - return; - } - if (mqttClient != null && mqttClient.isConnected()) { - return; - } - - log.info("[reconnectWithDelay][开始重连 MQTT Broker]"); - try { - createMqttClient(); - connectMqttAsync(); - } catch (Exception e) { - log.error("[reconnectWithDelay][重连过程中发生异常]", e); - vertx.setTimer(delay, t -> reconnectWithDelay()); - } - }); - } - - /** - * 停止 MQTT 客户端 - */ - private void stopMqttClient() { - if (mqttClient == null) { - return; - } - try { - if (mqttClient.isConnected()) { - // 1. 取消订阅所有主题 - List topicList = emqxProperties.getMqttTopics(); - for (String topic : topicList) { - try { - mqttClient.unsubscribe(topic); - } catch (Exception e) { - log.warn("[stopMqttClient][取消订阅主题({})异常]", topic, e); - } - } - - // 2. 断开 MQTT 客户端连接 - try { - CountDownLatch disconnectLatch = new CountDownLatch(1); - mqttClient.disconnect(ar -> disconnectLatch.countDown()); - if (!disconnectLatch.await(5, java.util.concurrent.TimeUnit.SECONDS)) { - log.warn("[stopMqttClient][断开 MQTT 连接超时]"); - } - } catch (Exception e) { - log.warn("[stopMqttClient][关闭 MQTT 客户端异常]", e); - } - } - } catch (Exception e) { - log.warn("[stopMqttClient][停止 MQTT 客户端过程中发生异常]", e); - } finally { - mqttClient = null; - } - } - - /** - * 创建 MQTT 客户端 - */ - private void createMqttClient() { - // 1.1 创建基础配置 - MqttClientOptions options = (MqttClientOptions) new MqttClientOptions() - .setClientId(emqxProperties.getMqttClientId()) - .setUsername(emqxProperties.getMqttUsername()) - .setPassword(emqxProperties.getMqttPassword()) - .setSsl(emqxProperties.getMqttSsl()) - .setCleanSession(emqxProperties.getCleanSession()) - .setKeepAliveInterval(emqxProperties.getKeepAliveIntervalSeconds()) - .setMaxInflightQueue(emqxProperties.getMaxInflightQueue()) - .setConnectTimeout(emqxProperties.getConnectTimeoutSeconds() * 1000) // Vert.x 需要毫秒 - .setTrustAll(emqxProperties.getTrustAll()); - // 1.2 配置遗嘱消息 - IotGatewayProperties.EmqxProperties.Will will = emqxProperties.getWill(); - if (will.isEnabled()) { - Assert.notBlank(will.getTopic(), "遗嘱消息主题(will.topic)不能为空"); - Assert.notNull(will.getPayload(), "遗嘱消息内容(will.payload)不能为空"); - options.setWillFlag(true) - .setWillTopic(will.getTopic()) - .setWillMessageBytes(Buffer.buffer(will.getPayload())) - .setWillQoS(will.getQos()) - .setWillRetain(will.isRetain()); - } - // 1.3 配置高级 SSL/TLS (仅在启用 SSL 且不信任所有证书时生效) - if (Boolean.TRUE.equals(emqxProperties.getMqttSsl()) && !Boolean.TRUE.equals(emqxProperties.getTrustAll())) { - IotGatewayProperties.EmqxProperties.Ssl sslOptions = emqxProperties.getSslOptions(); - if (StrUtil.isNotBlank(sslOptions.getTrustStorePath())) { - options.setTrustStoreOptions(new JksOptions() - .setPath(sslOptions.getTrustStorePath()) - .setPassword(sslOptions.getTrustStorePassword())); - } - if (StrUtil.isNotBlank(sslOptions.getKeyStorePath())) { - options.setKeyStoreOptions(new JksOptions() - .setPath(sslOptions.getKeyStorePath()) - .setPassword(sslOptions.getKeyStorePassword())); - } - } - // 1.4 安全警告日志 - if (Boolean.TRUE.equals(emqxProperties.getTrustAll())) { - log.warn("[createMqttClient][安全警告:当前配置信任所有 SSL 证书(trustAll=true),这在生产环境中存在严重安全风险!]"); - } - - // 2. 创建客户端实例 - this.mqttClient = MqttClient.create(vertx, options); - } - - /** - * 设置 MQTT 处理器 - */ - private void setupMqttHandlers() { - // 1. 设置断开重连监听器 - mqttClient.closeHandler(closeEvent -> { - if (!isRunning) { - return; - } - log.warn("[closeHandler][MQTT 连接已断开, 准备重连]"); - reconnectWithDelay(); - }); - - // 2. 设置异常处理器 - mqttClient.exceptionHandler(exception -> - log.error("[exceptionHandler][MQTT 客户端异常]", exception)); - - // 3. 设置消息处理器 - mqttClient.publishHandler(upstreamHandler::handle); - } - - /** - * 订阅设备上行消息主题 - */ - private void subscribeToTopics() { - // 1. 校验 MQTT 客户端是否连接 - List topicList = emqxProperties.getMqttTopics(); - if (mqttClient == null || !mqttClient.isConnected()) { - log.warn("[subscribeToTopics][MQTT 客户端未连接, 跳过订阅]"); - return; - } - - // 2. 批量订阅所有主题 - Map topics = new HashMap<>(); - int qos = emqxProperties.getMqttQos(); - for (String topic : topicList) { - topics.put(topic, qos); - } - mqttClient.subscribe(topics, subscribeResult -> { - if (subscribeResult.succeeded()) { - log.info("[subscribeToTopics][订阅主题成功, 共 {} 个主题]", topicList.size()); - } else { - log.error("[subscribeToTopics][订阅主题失败, 共 {} 个主题, 原因: {}]", - topicList.size(), subscribeResult.cause().getMessage(), subscribeResult.cause()); - } - }); - } - - /** - * 发布消息到 MQTT Broker - * - * @param topic 主题 - * @param payload 消息内容 - */ - public void publishMessage(String topic, byte[] payload) { - if (mqttClient == null || !mqttClient.isConnected()) { - log.warn("[publishMessage][MQTT 客户端未连接, 无法发布消息]"); - return; - } - MqttQoS qos = MqttQoS.valueOf(emqxProperties.getMqttQos()); - mqttClient.publish(topic, Buffer.buffer(payload), qos, false, false); - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/router/IotEmqxAuthEventHandler.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/router/IotEmqxAuthEventHandler.java deleted file mode 100644 index d6957bd52f..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/router/IotEmqxAuthEventHandler.java +++ /dev/null @@ -1,248 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.protocol.emqx.router; - -import cn.hutool.core.util.BooleanUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.spring.SpringUtil; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -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.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; -import io.vertx.core.json.JsonObject; -import io.vertx.ext.web.RoutingContext; -import lombok.extern.slf4j.Slf4j; - -/** - * IoT 网关 EMQX 认证事件处理器 - *

- * 为 EMQX 提供 HTTP 接口服务,包括: - * 1. 设备认证接口 - 对应 EMQX HTTP 认证插件 - * 2. 设备事件处理接口 - 对应 EMQX Webhook 事件通知 - * - * @author 芋道源码 - */ -@Slf4j -public class IotEmqxAuthEventHandler { - - /** - * HTTP 成功状态码(EMQX 要求固定使用 200) - */ - private static final int SUCCESS_STATUS_CODE = 200; - - /** - * 认证允许结果 - */ - private static final String RESULT_ALLOW = "allow"; - /** - * 认证拒绝结果 - */ - private static final String RESULT_DENY = "deny"; - /** - * 认证忽略结果 - */ - private static final String RESULT_IGNORE = "ignore"; - - /** - * EMQX 事件类型常量 - */ - private static final String EVENT_CLIENT_CONNECTED = "client.connected"; - private static final String EVENT_CLIENT_DISCONNECTED = "client.disconnected"; - - private final String serverId; - - private final IotDeviceMessageService deviceMessageService; - - private final IotDeviceCommonApi deviceApi; - - public IotEmqxAuthEventHandler(String serverId) { - this.serverId = serverId; - this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class); - this.deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class); - } - - /** - * EMQX 认证接口 - */ - public void handleAuth(RoutingContext context) { - try { - // 1. 参数校验 - JsonObject body = parseRequestBody(context); - if (body == null) { - return; - } - String clientId = body.getString("clientid"); - String username = body.getString("username"); - String password = body.getString("password"); - log.debug("[handleAuth][设备认证请求: clientId={}, username={}]", clientId, username); - if (StrUtil.hasEmpty(clientId, username, password)) { - log.info("[handleAuth][认证参数不完整: clientId={}, username={}]", clientId, username); - sendAuthResponse(context, RESULT_DENY); - return; - } - - // 2. 执行认证 - boolean authResult = handleDeviceAuth(clientId, username, password); - log.info("[handleAuth][设备认证结果: {} -> {}]", username, authResult); - if (authResult) { - sendAuthResponse(context, RESULT_ALLOW); - } else { - sendAuthResponse(context, RESULT_DENY); - } - } catch (Exception e) { - log.error("[handleAuth][设备认证异常]", e); - sendAuthResponse(context, RESULT_IGNORE); - } - } - - /** - * EMQX 统一事件处理接口:根据 EMQX 官方 Webhook 设计,统一处理所有客户端事件 - * 支持的事件类型:client.connected、client.disconnected 等 - */ - public void handleEvent(RoutingContext context) { - JsonObject body = null; - try { - // 1. 解析请求体 - body = parseRequestBody(context); - if (body == null) { - return; - } - String event = body.getString("event"); - String username = body.getString("username"); - log.debug("[handleEvent][收到事件: {} - {}]", event, username); - - // 2. 根据事件类型进行分发处理 - switch (event) { - case EVENT_CLIENT_CONNECTED: - handleClientConnected(body); - break; - case EVENT_CLIENT_DISCONNECTED: - handleClientDisconnected(body); - break; - default: - break; - } - - // EMQX Webhook 只需要 200 状态码,无需响应体 - context.response().setStatusCode(SUCCESS_STATUS_CODE).end(); - } catch (Exception e) { - log.error("[handleEvent][事件处理失败][body={}]", body != null ? body.encode() : "null", e); - // 即使处理失败,也返回 200 避免EMQX重试 - context.response().setStatusCode(SUCCESS_STATUS_CODE).end(); - } - } - - /** - * 处理客户端连接事件 - */ - private void handleClientConnected(JsonObject body) { - String username = body.getString("username"); - log.info("[handleClientConnected][设备上线: {}]", username); - handleDeviceStateChange(username, true); - } - - /** - * 处理客户端断开连接事件 - */ - private void handleClientDisconnected(JsonObject body) { - String username = body.getString("username"); - String reason = body.getString("reason"); - log.info("[handleClientDisconnected][设备下线: {} ({})]", username, reason); - handleDeviceStateChange(username, false); - } - - /** - * 解析请求体 - * - * @param context 路由上下文 - * @return 请求体JSON对象,解析失败时返回null - */ - private JsonObject parseRequestBody(RoutingContext context) { - try { - JsonObject body = context.body().asJsonObject(); - if (body == null) { - log.info("[parseRequestBody][请求体为空]"); - sendAuthResponse(context, RESULT_IGNORE); - return null; - } - return body; - } catch (Exception e) { - log.error("[parseRequestBody][body({}) 解析请求体失败]", context.body().asString(), e); - sendAuthResponse(context, RESULT_IGNORE); - return null; - } - } - - /** - * 执行设备认证 - * - * @param clientId 客户端ID - * @param username 用户名 - * @param password 密码 - * @return 认证是否成功 - */ - private boolean handleDeviceAuth(String clientId, String username, String password) { - try { - CommonResult result = deviceApi.authDevice(new IotDeviceAuthReqDTO() - .setClientId(clientId).setUsername(username).setPassword(password)); - result.checkError(); - return BooleanUtil.isTrue(result.getData()); - } catch (Exception e) { - log.error("[handleDeviceAuth][设备({}) 认证接口调用失败]", username, e); - throw e; - } - } - - /** - * 处理设备状态变化 - * - * @param username 用户名 - * @param online 是否在线 true 在线 false 离线 - */ - private void handleDeviceStateChange(String username, boolean online) { - // 1. 解析设备信息 - IotDeviceAuthUtils.DeviceInfo deviceInfo = IotDeviceAuthUtils.parseUsername(username); - if (deviceInfo == null) { - log.debug("[handleDeviceStateChange][跳过非设备({})连接]", username); - return; - } - - try { - // 2. 构建设备状态消息 - IotDeviceMessage message = online ? IotDeviceMessage.buildStateUpdateOnline() - : IotDeviceMessage.buildStateOffline(); - - // 3. 发送设备状态消息 - deviceMessageService.sendDeviceMessage(message, - deviceInfo.getProductKey(), deviceInfo.getDeviceName(), serverId); - } catch (Exception e) { - log.error("[handleDeviceStateChange][发送设备状态消息失败: {}]", username, e); - } - } - - /** - * 发送 EMQX 认证响应 - * 根据 EMQX 官方文档要求,必须返回 JSON 格式响应 - * - * @param context 路由上下文 - * @param result 认证结果:allow、deny、ignore - */ - private void sendAuthResponse(RoutingContext context, String result) { - // 构建符合 EMQX 官方规范的响应 - JsonObject response = new JsonObject() - .put("result", result) - .put("is_superuser", false); - - // 可以根据业务需求添加客户端属性 - // response.put("client_attrs", new JsonObject().put("role", "device")); - - // 可以添加认证过期时间(可选) - // response.put("expire_at", System.currentTimeMillis() / 1000 + 3600); - - context.response() - .setStatusCode(SUCCESS_STATUS_CODE) - .putHeader("Content-Type", "application/json; charset=utf-8") - .end(response.encode()); - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/router/IotEmqxDownstreamHandler.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/router/IotEmqxDownstreamHandler.java deleted file mode 100644 index 06632b3e8f..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/router/IotEmqxDownstreamHandler.java +++ /dev/null @@ -1,77 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.protocol.emqx.router; - -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.spring.SpringUtil; -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; -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.gateway.protocol.emqx.IotEmqxUpstreamProtocol; -import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService; -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; -import cn.iocoder.yudao.module.iot.gateway.util.IotMqttTopicUtils; -import lombok.extern.slf4j.Slf4j; - -/** - * IoT 网关 EMQX 下行消息处理器 - *

- * 从消息总线接收到下行消息,然后发布到 MQTT Broker,从而被设备所接收 - * - * @author 芋道源码 - */ -@Slf4j -public class IotEmqxDownstreamHandler { - - private final IotEmqxUpstreamProtocol protocol; - - private final IotDeviceService deviceService; - - private final IotDeviceMessageService deviceMessageService; - - public IotEmqxDownstreamHandler(IotEmqxUpstreamProtocol protocol) { - this.protocol = protocol; - this.deviceService = SpringUtil.getBean(IotDeviceService.class); - this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class); - } - - /** - * 处理下行消息 - * - * @param message 设备消息 - */ - public void handle(IotDeviceMessage message) { - // 1. 获取设备信息 - IotDeviceRespDTO deviceInfo = deviceService.getDeviceFromCache(message.getDeviceId()); - if (deviceInfo == null) { - log.error("[handle][设备信息({})不存在]", message.getDeviceId()); - return; - } - - // 2.1 根据方法构建主题 - String topic = buildTopicByMethod(message, deviceInfo.getProductKey(), deviceInfo.getDeviceName()); - if (StrUtil.isBlank(topic)) { - log.warn("[handle][未知的消息方法: {}]", message.getMethod()); - return; - } - // 2.2 构建载荷 - byte[] payload = deviceMessageService.encodeDeviceMessage(message, deviceInfo.getProductKey(), - deviceInfo.getDeviceName()); - // 2.3 发布消息 - protocol.publishMessage(topic, payload); - } - - /** - * 根据消息方法和回复状态构建主题 - * - * @param message 设备消息 - * @param productKey 产品标识 - * @param deviceName 设备名称 - * @return 构建的主题,如果方法不支持返回 null - */ - private String buildTopicByMethod(IotDeviceMessage message, String productKey, String deviceName) { - // 1. 判断是否为回复消息 - boolean isReply = IotDeviceMessageUtils.isReplyMessage(message); - // 2. 根据消息方法类型构建对应的主题 - return IotMqttTopicUtils.buildTopicByMethod(message.getMethod(), productKey, deviceName, isReply); - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/router/IotEmqxUpstreamHandler.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/router/IotEmqxUpstreamHandler.java deleted file mode 100644 index 81d8cbb13a..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/router/IotEmqxUpstreamHandler.java +++ /dev/null @@ -1,60 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.protocol.emqx.router; - -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.spring.SpringUtil; -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxUpstreamProtocol; -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; -import io.vertx.mqtt.messages.MqttPublishMessage; -import lombok.extern.slf4j.Slf4j; - -/** - * IoT 网关 EMQX 上行消息处理器 - * - * @author 芋道源码 - */ -@Slf4j -public class IotEmqxUpstreamHandler { - - private final IotDeviceMessageService deviceMessageService; - - private final String serverId; - - public IotEmqxUpstreamHandler(IotEmqxUpstreamProtocol protocol) { - this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class); - this.serverId = protocol.getServerId(); - } - - /** - * 处理 MQTT 发布消息 - */ - public void handle(MqttPublishMessage mqttMessage) { - log.info("[handle][收到 MQTT 消息, topic: {}, payload: {}]", mqttMessage.topicName(), mqttMessage.payload()); - String topic = mqttMessage.topicName(); - byte[] payload = mqttMessage.payload().getBytes(); - try { - // 1. 解析主题,一次性获取所有信息 - String[] topicParts = topic.split("/"); - if (topicParts.length < 4 || StrUtil.hasBlank(topicParts[2], topicParts[3])) { - log.warn("[handle][topic({}) 格式不正确,无法解析有效的 productKey 和 deviceName]", topic); - return; - } - - String productKey = topicParts[2]; - String deviceName = topicParts[3]; - - // 3. 解码消息 - IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(payload, productKey, deviceName); - if (message == null) { - log.warn("[handle][topic({}) payload({}) 消息解码失败]", topic, new String(payload)); - return; - } - - // 4. 发送消息到队列 - deviceMessageService.sendDeviceMessage(message, productKey, deviceName, serverId); - } catch (Exception e) { - log.error("[handle][topic({}) payload({}) 处理异常]", topic, new String(payload), e); - } - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/IotHttpDownstreamSubscriber.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/IotHttpDownstreamSubscriber.java deleted file mode 100644 index 157d241689..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/IotHttpDownstreamSubscriber.java +++ /dev/null @@ -1,46 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.protocol.http; - -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 lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -import javax.annotation.PostConstruct; - -/** - * IoT 网关 HTTP 订阅者:接收下行给设备的消息 - * - * @author 芋道源码 - */ -@RequiredArgsConstructor -@Slf4j -public class IotHttpDownstreamSubscriber implements IotMessageSubscriber { - - private final IotHttpUpstreamProtocol protocol; - - private final IotMessageBus messageBus; - - @PostConstruct - public void init() { - messageBus.register(this); - } - - @Override - public String getTopic() { - return IotDeviceMessageUtils.buildMessageBusGatewayDeviceMessageTopic(protocol.getServerId()); - } - - @Override - public String getGroup() { - // 保证点对点消费,需要保证独立的 Group,所以使用 Topic 作为 Group - return getTopic(); - } - - @Override - public void onMessage(IotDeviceMessage message) { - log.info("[onMessage][IoT 网关 HTTP 协议不支持下行消息,忽略消息:{}]", message); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/IotHttpUpstreamProtocol.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/IotHttpUpstreamProtocol.java deleted file mode 100644 index 172fb210e6..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/IotHttpUpstreamProtocol.java +++ /dev/null @@ -1,87 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.protocol.http; - -import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; -import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties; -import cn.iocoder.yudao.module.iot.gateway.protocol.http.router.IotHttpAuthHandler; -import cn.iocoder.yudao.module.iot.gateway.protocol.http.router.IotHttpUpstreamHandler; -import io.vertx.core.AbstractVerticle; -import io.vertx.core.Vertx; -import io.vertx.core.http.HttpServer; -import io.vertx.core.http.HttpServerOptions; -import io.vertx.core.net.PemKeyCertOptions; -import io.vertx.ext.web.Router; -import io.vertx.ext.web.handler.BodyHandler; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; - -/** - * IoT 网关 HTTP 协议:接收设备上行消息 - * - * @author 芋道源码 - */ -@Slf4j -public class IotHttpUpstreamProtocol extends AbstractVerticle { - - private final IotGatewayProperties.HttpProperties httpProperties; - - private HttpServer httpServer; - - @Getter - private final String serverId; - - public IotHttpUpstreamProtocol(IotGatewayProperties.HttpProperties httpProperties) { - this.httpProperties = httpProperties; - this.serverId = IotDeviceMessageUtils.generateServerId(httpProperties.getServerPort()); - } - - @Override - @PostConstruct - public void start() { - // 创建路由 - Vertx vertx = Vertx.vertx(); - Router router = Router.router(vertx); - router.route().handler(BodyHandler.create()); - - // 创建处理器,添加路由处理器 - IotHttpAuthHandler authHandler = new IotHttpAuthHandler(this); - router.post(IotHttpAuthHandler.PATH).handler(authHandler); - IotHttpUpstreamHandler upstreamHandler = new IotHttpUpstreamHandler(this); - router.post(IotHttpUpstreamHandler.PATH).handler(upstreamHandler); - - // 启动 HTTP 服务器 - HttpServerOptions options = new HttpServerOptions() - .setPort(httpProperties.getServerPort()); - if (Boolean.TRUE.equals(httpProperties.getSslEnabled())) { - PemKeyCertOptions pemKeyCertOptions = new PemKeyCertOptions().setKeyPath(httpProperties.getSslKeyPath()) - .setCertPath(httpProperties.getSslCertPath()); - options = options.setSsl(true).setKeyCertOptions(pemKeyCertOptions); - } - try { - httpServer = vertx.createHttpServer(options) - .requestHandler(router) - .listen() - .result(); - log.info("[start][IoT 网关 HTTP 协议启动成功,端口:{}]", httpProperties.getServerPort()); - } catch (Exception e) { - log.error("[start][IoT 网关 HTTP 协议启动失败]", e); - throw e; - } - } - - @Override - @PreDestroy - public void stop() { - if (httpServer != null) { - try { - httpServer.close().result(); - log.info("[stop][IoT 网关 HTTP 协议已停止]"); - } catch (Exception e) { - log.error("[stop][IoT 网关 HTTP 协议停止失败]", e); - } - } - } - -} diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/router/IotHttpAbstractHandler.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/router/IotHttpAbstractHandler.java deleted file mode 100644 index f5461c2c51..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/router/IotHttpAbstractHandler.java +++ /dev/null @@ -1,93 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.protocol.http.router; - -import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.ObjUtil; -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.CommonResult; -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; -import cn.iocoder.yudao.module.iot.gateway.service.auth.IotDeviceTokenService; -import io.vertx.core.Handler; -import io.vertx.core.http.HttpHeaders; -import io.vertx.ext.web.RoutingContext; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.MediaType; - -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.FORBIDDEN; -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR; -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException; - -/** - * IoT 网关 HTTP 协议的处理器抽象基类:提供通用的前置处理(认证)、全局的异常捕获等 - * - * @author 芋道源码 - */ -@RequiredArgsConstructor -@Slf4j -public abstract class IotHttpAbstractHandler implements Handler { - - private final IotDeviceTokenService deviceTokenService = SpringUtil.getBean(IotDeviceTokenService.class); - - @Override - public final void handle(RoutingContext context) { - try { - // 1. 前置处理 - beforeHandle(context); - - // 2. 执行逻辑 - CommonResult result = handle0(context); - writeResponse(context, result); - } catch (ServiceException e) { - writeResponse(context, CommonResult.error(e.getCode(), e.getMessage())); - } catch (Exception e) { - log.error("[handle][path({}) 处理异常]", context.request().path(), e); - writeResponse(context, CommonResult.error(INTERNAL_SERVER_ERROR)); - } - } - - protected abstract CommonResult handle0(RoutingContext context); - - private void beforeHandle(RoutingContext context) { - // 如果不需要认证,则不走前置处理 - String path = context.request().path(); - if (ObjUtil.equal(path, IotHttpAuthHandler.PATH)) { - return; - } - - // 解析参数 - String token = context.request().getHeader(HttpHeaders.AUTHORIZATION); - if (StrUtil.isEmpty(token)) { - throw invalidParamException("token 不能为空"); - } - String productKey = context.pathParam("productKey"); - if (StrUtil.isEmpty(productKey)) { - throw invalidParamException("productKey 不能为空"); - } - String deviceName = context.pathParam("deviceName"); - if (StrUtil.isEmpty(deviceName)) { - throw invalidParamException("deviceName 不能为空"); - } - - // 校验 token - IotDeviceAuthUtils.DeviceInfo deviceInfo = deviceTokenService.verifyToken(token); - Assert.notNull(deviceInfo, "设备信息不能为空"); - // 校验设备信息是否匹配 - if (ObjUtil.notEqual(productKey, deviceInfo.getProductKey()) - || ObjUtil.notEqual(deviceName, deviceInfo.getDeviceName())) { - throw exception(FORBIDDEN); - } - } - - @SuppressWarnings("deprecation") - public static void writeResponse(RoutingContext context, Object data) { - context.response() - .setStatusCode(200) - .putHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE) - .end(JsonUtils.toJsonString(data)); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/router/IotHttpAuthHandler.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/router/IotHttpAuthHandler.java deleted file mode 100644 index e6a52cdf0f..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/router/IotHttpAuthHandler.java +++ /dev/null @@ -1,89 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.protocol.http.router; - -import cn.hutool.core.lang.Assert; -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.BooleanUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.spring.SpringUtil; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -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.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; -import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpUpstreamProtocol; -import cn.iocoder.yudao.module.iot.gateway.service.auth.IotDeviceTokenService; -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; -import io.vertx.core.json.JsonObject; -import io.vertx.ext.web.RoutingContext; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException; -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.module.iot.gateway.enums.ErrorCodeConstants.DEVICE_AUTH_FAIL; - -/** - * IoT 网关 HTTP 协议的【认证】处理器 - * - * 参考 https://help.aliyun.com/zh/iot/user-guide/establish-connections-over-https - * - * @author 芋道源码 - */ -public class IotHttpAuthHandler extends IotHttpAbstractHandler { - - public static final String PATH = "/auth"; - - private final IotHttpUpstreamProtocol protocol; - - private final IotDeviceTokenService deviceTokenService; - - private final IotDeviceCommonApi deviceApi; - - private final IotDeviceMessageService deviceMessageService; - - public IotHttpAuthHandler(IotHttpUpstreamProtocol protocol) { - this.protocol = protocol; - this.deviceTokenService = SpringUtil.getBean(IotDeviceTokenService.class); - this.deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class); - this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class); - } - - @Override - public CommonResult handle0(RoutingContext context) { - // 1. 解析参数 - JsonObject body = context.body().asJsonObject(); - String clientId = body.getString("clientId"); - if (StrUtil.isEmpty(clientId)) { - throw invalidParamException("clientId 不能为空"); - } - String username = body.getString("username"); - if (StrUtil.isEmpty(username)) { - throw invalidParamException("username 不能为空"); - } - String password = body.getString("password"); - if (StrUtil.isEmpty(password)) { - throw invalidParamException("password 不能为空"); - } - - // 2.1 执行认证 - CommonResult result = deviceApi.authDevice(new IotDeviceAuthReqDTO() - .setClientId(clientId).setUsername(username).setPassword(password)); - result.checkError(); - if (!BooleanUtil.isTrue(result.getData())) { - throw exception(DEVICE_AUTH_FAIL); - } - // 2.2 生成 Token - IotDeviceAuthUtils.DeviceInfo deviceInfo = deviceTokenService.parseUsername(username); - Assert.notNull(deviceInfo, "设备信息不能为空"); - String token = deviceTokenService.createToken(deviceInfo.getProductKey(), deviceInfo.getDeviceName()); - Assert.notBlank(token, "生成 token 不能为空位"); - - // 3. 执行上线 - IotDeviceMessage message = IotDeviceMessage.buildStateUpdateOnline(); - deviceMessageService.sendDeviceMessage(message, - deviceInfo.getProductKey(), deviceInfo.getDeviceName(), protocol.getServerId()); - - // 构建响应数据 - return success(MapUtil.of("token", token)); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/router/IotHttpUpstreamHandler.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/router/IotHttpUpstreamHandler.java deleted file mode 100644 index d7d4d52ff2..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/router/IotHttpUpstreamHandler.java +++ /dev/null @@ -1,55 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.protocol.http.router; - -import cn.hutool.core.lang.Assert; -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.text.StrPool; -import cn.hutool.extra.spring.SpringUtil; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpUpstreamProtocol; -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; -import io.vertx.ext.web.RoutingContext; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -/** - * IoT 网关 HTTP 协议的【上行】处理器 - * - * @author 芋道源码 - */ -@RequiredArgsConstructor -@Slf4j -public class IotHttpUpstreamHandler extends IotHttpAbstractHandler { - - public static final String PATH = "/topic/sys/:productKey/:deviceName/*"; - - private final IotHttpUpstreamProtocol protocol; - - private final IotDeviceMessageService deviceMessageService; - - public IotHttpUpstreamHandler(IotHttpUpstreamProtocol protocol) { - this.protocol = protocol; - this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class); - } - - @Override - protected CommonResult handle0(RoutingContext context) { - // 1. 解析通用参数 - String productKey = context.pathParam("productKey"); - String deviceName = context.pathParam("deviceName"); - String method = context.pathParam("*").replaceAll(StrPool.SLASH, StrPool.DOT); - - // 2.1 解析消息 - byte[] bytes = context.body().buffer().getBytes(); - IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(bytes, - productKey, deviceName); - Assert.equals(method, message.getMethod(), "method 不匹配"); - // 2.2 发送消息 - deviceMessageService.sendDeviceMessage(message, - productKey, deviceName, protocol.getServerId()); - - // 3. 返回结果 - return CommonResult.success(MapUtil.of("messageId", message.getId())); - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttDownstreamSubscriber.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttDownstreamSubscriber.java deleted file mode 100644 index 674074ef2c..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttDownstreamSubscriber.java +++ /dev/null @@ -1,80 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt; - -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.gateway.protocol.mqtt.router.IotMqttDownstreamHandler; -import lombok.extern.slf4j.Slf4j; - -import javax.annotation.PostConstruct; - -/** - * IoT 网关 MQTT 协议:下行消息订阅器 - *

- * 负责接收来自消息总线的下行消息,并委托给下行处理器进行业务处理 - * - * @author 芋道源码 - */ -@Slf4j -public class IotMqttDownstreamSubscriber implements IotMessageSubscriber { - - private final IotMqttUpstreamProtocol upstreamProtocol; - - private final IotMqttDownstreamHandler downstreamHandler; - - private final IotMessageBus messageBus; - - public IotMqttDownstreamSubscriber(IotMqttUpstreamProtocol upstreamProtocol, - IotMqttDownstreamHandler downstreamHandler, - IotMessageBus messageBus) { - this.upstreamProtocol = upstreamProtocol; - this.downstreamHandler = downstreamHandler; - this.messageBus = messageBus; - } - - @PostConstruct - public void subscribe() { - messageBus.register(this); - log.info("[subscribe][MQTT 协议下行消息订阅成功,主题:{}]", getTopic()); - } - - @Override - public String getTopic() { - return IotDeviceMessageUtils.buildMessageBusGatewayDeviceMessageTopic(upstreamProtocol.getServerId()); - } - - @Override - public String getGroup() { - // 保证点对点消费,需要保证独立的 Group,所以使用 Topic 作为 Group - return getTopic(); - } - - @Override - public void onMessage(IotDeviceMessage message) { - log.debug("[onMessage][接收到下行消息, messageId: {}, method: {}, deviceId: {}]", - message.getId(), message.getMethod(), message.getDeviceId()); - try { - // 1. 校验 - String method = message.getMethod(); - if (method == null) { - log.warn("[onMessage][消息方法为空, messageId: {}, deviceId: {}]", - message.getId(), message.getDeviceId()); - return; - } - - // 2. 委托给下行处理器处理业务逻辑 - boolean success = downstreamHandler.handleDownstreamMessage(message); - if (success) { - log.debug("[onMessage][下行消息处理成功, messageId: {}, method: {}, deviceId: {}]", - message.getId(), message.getMethod(), message.getDeviceId()); - } else { - log.warn("[onMessage][下行消息处理失败, messageId: {}, method: {}, deviceId: {}]", - message.getId(), message.getMethod(), message.getDeviceId()); - } - } catch (Exception e) { - log.error("[onMessage][处理下行消息失败, messageId: {}, method: {}, deviceId: {}]", - message.getId(), message.getMethod(), message.getDeviceId(), e); - } - } -} diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttUpstreamProtocol.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttUpstreamProtocol.java deleted file mode 100644 index ee46e194df..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttUpstreamProtocol.java +++ /dev/null @@ -1,93 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt; - -import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; -import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties; -import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager; -import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttUpstreamHandler; -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; -import io.vertx.core.Vertx; -import io.vertx.mqtt.MqttServer; -import io.vertx.mqtt.MqttServerOptions; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; - -/** - * IoT 网关 MQTT 协议:接收设备上行消息 - * - * @author 芋道源码 - */ -@Slf4j -public class IotMqttUpstreamProtocol { - - private final IotGatewayProperties.MqttProperties mqttProperties; - - private final IotDeviceMessageService messageService; - - private final IotMqttConnectionManager connectionManager; - - private final Vertx vertx; - - @Getter - private final String serverId; - - private MqttServer mqttServer; - - public IotMqttUpstreamProtocol(IotGatewayProperties.MqttProperties mqttProperties, - IotDeviceMessageService messageService, - IotMqttConnectionManager connectionManager, - Vertx vertx) { - this.mqttProperties = mqttProperties; - this.messageService = messageService; - this.connectionManager = connectionManager; - this.vertx = vertx; - this.serverId = IotDeviceMessageUtils.generateServerId(mqttProperties.getPort()); - } - - // TODO @haohao:这里的编写,是不是和 tcp 对应的,风格保持一致哈; - @PostConstruct - public void start() { - // 创建服务器选项 - MqttServerOptions options = new MqttServerOptions() - .setPort(mqttProperties.getPort()) - .setMaxMessageSize(mqttProperties.getMaxMessageSize()) - .setTimeoutOnConnect(mqttProperties.getConnectTimeoutSeconds()); - - // 配置 SSL(如果启用) - if (Boolean.TRUE.equals(mqttProperties.getSslEnabled())) { - options.setSsl(true) - .setKeyCertOptions(mqttProperties.getSslOptions().getKeyCertOptions()) - .setTrustOptions(mqttProperties.getSslOptions().getTrustOptions()); - } - - // 创建服务器并设置连接处理器 - mqttServer = MqttServer.create(vertx, options); - mqttServer.endpointHandler(endpoint -> { - IotMqttUpstreamHandler handler = new IotMqttUpstreamHandler(this, messageService, connectionManager); - handler.handle(endpoint); - }); - - // 启动服务器 - try { - mqttServer.listen().result(); - log.info("[start][IoT 网关 MQTT 协议启动成功,端口:{}]", mqttProperties.getPort()); - } catch (Exception e) { - log.error("[start][IoT 网关 MQTT 协议启动失败]", e); - throw e; - } - } - - @PreDestroy - public void stop() { - if (mqttServer != null) { - try { - mqttServer.close().result(); - log.info("[stop][IoT 网关 MQTT 协议已停止]"); - } catch (Exception e) { - log.error("[stop][IoT 网关 MQTT 协议停止失败]", e); - } - } - } -} diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/manager/IotMqttConnectionManager.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/manager/IotMqttConnectionManager.java deleted file mode 100644 index 3fd1a3a041..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/manager/IotMqttConnectionManager.java +++ /dev/null @@ -1,223 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager; - -import cn.hutool.core.util.StrUtil; -import io.netty.handler.codec.mqtt.MqttQoS; -import io.vertx.mqtt.MqttEndpoint; -import lombok.Data; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * IoT 网关 MQTT 连接管理器 - *

- * 统一管理 MQTT 连接的认证状态、设备会话和消息发送功能: - * 1. 管理 MQTT 连接的认证状态 - * 2. 管理设备会话和在线状态 - * 3. 管理消息发送到设备 - * - * @author 芋道源码 - */ -@Slf4j -@Component -public class IotMqttConnectionManager { - - /** - * 未知地址常量(当获取端点地址失败时使用) - */ - private static final String UNKNOWN_ADDRESS = "unknown"; - - /** - * 连接信息映射:MqttEndpoint -> 连接信息 - */ - private final Map connectionMap = new ConcurrentHashMap<>(); - - /** - * 设备 ID -> MqttEndpoint 的映射 - */ - private final Map deviceEndpointMap = new ConcurrentHashMap<>(); - - /** - * 安全获取 endpoint 地址 - *

- * 优先从缓存获取地址,缓存为空时再尝试实时获取 - * - * @param endpoint MQTT 连接端点 - * @return 地址字符串,获取失败时返回 "unknown" - */ - public String getEndpointAddress(MqttEndpoint endpoint) { - String realTimeAddress = UNKNOWN_ADDRESS; - if (endpoint == null) { - return realTimeAddress; - } - - // 1. 优先从缓存获取(避免连接关闭时的异常) - ConnectionInfo connectionInfo = connectionMap.get(endpoint); - if (connectionInfo != null && StrUtil.isNotBlank(connectionInfo.getRemoteAddress())) { - return connectionInfo.getRemoteAddress(); - } - - // 2. 缓存为空时尝试实时获取 - try { - realTimeAddress = endpoint.remoteAddress().toString(); - } catch (Exception ignored) { - // 连接已关闭,忽略异常 - } - - return realTimeAddress; - } - - /** - * 注册设备连接(包含认证信息) - * - * @param endpoint MQTT 连接端点 - * @param deviceId 设备 ID - * @param connectionInfo 连接信息 - */ - public void registerConnection(MqttEndpoint endpoint, Long deviceId, ConnectionInfo connectionInfo) { - // 如果设备已有其他连接,先清理旧连接 - MqttEndpoint oldEndpoint = deviceEndpointMap.get(deviceId); - if (oldEndpoint != null && oldEndpoint != endpoint) { - log.info("[registerConnection][设备已有其他连接,断开旧连接,设备 ID: {},旧连接: {}]", - deviceId, getEndpointAddress(oldEndpoint)); - oldEndpoint.close(); - // 清理旧连接的映射 - connectionMap.remove(oldEndpoint); - } - - connectionMap.put(endpoint, connectionInfo); - deviceEndpointMap.put(deviceId, endpoint); - - log.info("[registerConnection][注册设备连接,设备 ID: {},连接: {},product key: {},device name: {}]", - deviceId, getEndpointAddress(endpoint), connectionInfo.getProductKey(), connectionInfo.getDeviceName()); - } - - /** - * 注销设备连接 - * - * @param endpoint MQTT 连接端点 - */ - public void unregisterConnection(MqttEndpoint endpoint) { - ConnectionInfo connectionInfo = connectionMap.remove(endpoint); - if (connectionInfo != null) { - Long deviceId = connectionInfo.getDeviceId(); - deviceEndpointMap.remove(deviceId); - - log.info("[unregisterConnection][注销设备连接,设备 ID: {},连接: {}]", deviceId, - getEndpointAddress(endpoint)); - } - } - - /** - * 获取连接信息 - */ - public ConnectionInfo getConnectionInfo(MqttEndpoint endpoint) { - return connectionMap.get(endpoint); - } - - /** - * 根据设备 ID 获取连接信息 - * - * @param deviceId 设备 ID - * @return 连接信息 - */ - public IotMqttConnectionManager.ConnectionInfo getConnectionInfoByDeviceId(Long deviceId) { - // 通过设备 ID 获取连接端点 - var endpoint = getDeviceEndpoint(deviceId); - if (endpoint == null) { - return null; - } - - // 获取连接信息 - return getConnectionInfo(endpoint); - } - - /** - * 检查设备是否在线 - */ - public boolean isDeviceOnline(Long deviceId) { - return deviceEndpointMap.containsKey(deviceId); - } - - /** - * 检查设备是否离线 - */ - public boolean isDeviceOffline(Long deviceId) { - return !isDeviceOnline(deviceId); - } - - /** - * 发送消息到设备 - * - * @param deviceId 设备 ID - * @param topic 主题 - * @param payload 消息内容 - * @param qos 服务质量 - * @param retain 是否保留消息 - * @return 是否发送成功 - */ - public boolean sendToDevice(Long deviceId, String topic, byte[] payload, int qos, boolean retain) { - MqttEndpoint endpoint = deviceEndpointMap.get(deviceId); - if (endpoint == null) { - log.warn("[sendToDevice][设备离线,无法发送消息,设备 ID: {},主题: {}]", deviceId, topic); - return false; - } - - try { - endpoint.publish(topic, io.vertx.core.buffer.Buffer.buffer(payload), MqttQoS.valueOf(qos), false, retain); - log.debug("[sendToDevice][发送消息成功,设备 ID: {},主题: {},QoS: {}]", deviceId, topic, qos); - return true; - } catch (Exception e) { - log.error("[sendToDevice][发送消息失败,设备 ID: {},主题: {},错误: {}]", deviceId, topic, e.getMessage()); - return false; - } - } - - /** - * 获取设备连接端点 - */ - public MqttEndpoint getDeviceEndpoint(Long deviceId) { - return deviceEndpointMap.get(deviceId); - } - - /** - * 连接信息 - */ - @Data - public static class ConnectionInfo { - - /** - * 设备 ID - */ - private Long deviceId; - - /** - * 产品 Key - */ - private String productKey; - - /** - * 设备名称 - */ - private String deviceName; - - /** - * 客户端 ID - */ - private String clientId; - - /** - * 是否已认证 - */ - private boolean authenticated; - - /** - * 连接地址 - */ - private String remoteAddress; - - } - -} diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/package-info.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/package-info.java deleted file mode 100644 index fabe79466e..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * MQTT 协议实现包 - *

- * 提供基于 Vert.x MQTT Server 的 IoT 设备连接和消息处理功能 - */ -package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt; diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttDownstreamHandler.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttDownstreamHandler.java deleted file mode 100644 index c848833f66..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttDownstreamHandler.java +++ /dev/null @@ -1,132 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router; - -import cn.hutool.core.util.StrUtil; -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.gateway.protocol.mqtt.manager.IotMqttConnectionManager; -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; -import cn.iocoder.yudao.module.iot.gateway.util.IotMqttTopicUtils; -import io.netty.handler.codec.mqtt.MqttQoS; -import lombok.extern.slf4j.Slf4j; - -/** - * IoT 网关 MQTT 协议:下行消息处理器 - *

- * 专门处理下行消息的业务逻辑,包括: - * 1. 消息编码 - * 2. 主题构建 - * 3. 消息发送 - * - * @author 芋道源码 - */ -@Slf4j -public class IotMqttDownstreamHandler { - - private final IotDeviceMessageService deviceMessageService; - - private final IotMqttConnectionManager connectionManager; - - public IotMqttDownstreamHandler(IotDeviceMessageService deviceMessageService, - IotMqttConnectionManager connectionManager) { - this.deviceMessageService = deviceMessageService; - this.connectionManager = connectionManager; - } - - /** - * 处理下行消息 - * - * @param message 设备消息 - * @return 是否处理成功 - */ - public boolean handleDownstreamMessage(IotDeviceMessage message) { - try { - // 1. 基础校验 - if (message == null || message.getDeviceId() == null) { - log.warn("[handleDownstreamMessage][消息或设备 ID 为空,忽略处理]"); - return false; - } - - // 2. 检查设备是否在线 - if (connectionManager.isDeviceOffline(message.getDeviceId())) { - log.warn("[handleDownstreamMessage][设备离线,无法发送消息,设备 ID:{}]", message.getDeviceId()); - return false; - } - - // 3. 获取连接信息 - IotMqttConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfoByDeviceId(message.getDeviceId()); - if (connectionInfo == null) { - log.warn("[handleDownstreamMessage][连接信息不存在,设备 ID:{}]", message.getDeviceId()); - return false; - } - - // 4. 编码消息 - byte[] payload = deviceMessageService.encodeDeviceMessage(message, connectionInfo.getProductKey(), - connectionInfo.getDeviceName()); - if (payload == null || payload.length == 0) { - log.warn("[handleDownstreamMessage][消息编码失败,设备 ID:{}]", message.getDeviceId()); - return false; - } - - // 5. 发送消息到设备 - return sendMessageToDevice(message, connectionInfo, payload); - } catch (Exception e) { - if (message != null) { - log.error("[handleDownstreamMessage][处理下行消息异常,设备 ID:{},错误:{}]", - message.getDeviceId(), e.getMessage(), e); - } - return false; - } - } - - /** - * 发送消息到设备 - * - * @param message 设备消息 - * @param connectionInfo 连接信息 - * @param payload 消息负载 - * @return 是否发送成功 - */ - private boolean sendMessageToDevice(IotDeviceMessage message, - IotMqttConnectionManager.ConnectionInfo connectionInfo, - byte[] payload) { - // 1. 构建主题 - String topic = buildDownstreamTopic(message, connectionInfo); - if (StrUtil.isBlank(topic)) { - log.warn("[sendMessageToDevice][主题构建失败,设备 ID:{},方法:{}]", - message.getDeviceId(), message.getMethod()); - return false; - } - - // 2. 发送消息 - boolean success = connectionManager.sendToDevice(message.getDeviceId(), topic, payload, MqttQoS.AT_LEAST_ONCE.value(), false); - if (success) { - log.debug("[sendMessageToDevice][消息发送成功,设备 ID:{},主题:{},方法:{}]", - message.getDeviceId(), topic, message.getMethod()); - } else { - log.warn("[sendMessageToDevice][消息发送失败,设备 ID:{},主题:{},方法:{}]", - message.getDeviceId(), topic, message.getMethod()); - } - return success; - } - - /** - * 构建下行消息主题 - * - * @param message 设备消息 - * @param connectionInfo 连接信息 - * @return 主题 - */ - private String buildDownstreamTopic(IotDeviceMessage message, - IotMqttConnectionManager.ConnectionInfo connectionInfo) { - String method = message.getMethod(); - if (StrUtil.isBlank(method)) { - return null; - } - - // 使用工具类构建主题,支持回复消息处理 - boolean isReply = IotDeviceMessageUtils.isReplyMessage(message); - return IotMqttTopicUtils.buildTopicByMethod(method, connectionInfo.getProductKey(), - connectionInfo.getDeviceName(), isReply); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttUpstreamHandler.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttUpstreamHandler.java deleted file mode 100644 index c19053f144..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttUpstreamHandler.java +++ /dev/null @@ -1,305 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router; - -import cn.hutool.core.util.BooleanUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.spring.SpringUtil; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -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.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; -import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol; -import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager; -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; -import io.netty.handler.codec.mqtt.MqttConnectReturnCode; -import io.netty.handler.codec.mqtt.MqttQoS; -import io.vertx.mqtt.MqttEndpoint; -import io.vertx.mqtt.MqttTopicSubscription; -import lombok.extern.slf4j.Slf4j; - -import java.util.List; - -/** - * MQTT 上行消息处理器 - * - * @author 芋道源码 - */ -@Slf4j -public class IotMqttUpstreamHandler { - - private final IotDeviceMessageService deviceMessageService; - - private final IotMqttConnectionManager connectionManager; - - private final IotDeviceCommonApi deviceApi; - - private final String serverId; - - public IotMqttUpstreamHandler(IotMqttUpstreamProtocol protocol, - IotDeviceMessageService deviceMessageService, - IotMqttConnectionManager connectionManager) { - this.deviceMessageService = deviceMessageService; - this.deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class); - this.connectionManager = connectionManager; - this.serverId = protocol.getServerId(); - } - - /** - * 处理 MQTT 连接 - * - * @param endpoint MQTT 连接端点 - */ - public void handle(MqttEndpoint endpoint) { - String clientId = endpoint.clientIdentifier(); - String username = endpoint.auth() != null ? endpoint.auth().getUsername() : null; - String password = endpoint.auth() != null ? endpoint.auth().getPassword() : null; - - log.debug("[handle][设备连接请求,客户端 ID: {},用户名: {},地址: {}]", - clientId, username, connectionManager.getEndpointAddress(endpoint)); - - // 1. 先进行认证 - if (!authenticateDevice(clientId, username, password, endpoint)) { - log.warn("[handle][设备认证失败,拒绝连接,客户端 ID: {},用户名: {}]", clientId, username); - endpoint.reject(MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD); - return; - } - - log.info("[handle][设备认证成功,建立连接,客户端 ID: {},用户名: {}]", clientId, username); - - // 2. 设置异常和关闭处理器 - endpoint.exceptionHandler(ex -> { - log.warn("[handle][连接异常,客户端 ID: {},地址: {}]", clientId, connectionManager.getEndpointAddress(endpoint)); - cleanupConnection(endpoint); - }); - endpoint.closeHandler(v -> { - cleanupConnection(endpoint); - }); - - // 3. 设置消息处理器 - endpoint.publishHandler(message -> { - try { - processMessage(clientId, message.topicName(), message.payload().getBytes()); - - // 根据 QoS 级别发送相应的确认消息 - if (message.qosLevel() == MqttQoS.AT_LEAST_ONCE) { - // QoS 1: 发送 PUBACK 确认 - endpoint.publishAcknowledge(message.messageId()); - } else if (message.qosLevel() == MqttQoS.EXACTLY_ONCE) { - // QoS 2: 发送 PUBREC 确认 - endpoint.publishReceived(message.messageId()); - } - // QoS 0 无需确认 - - } catch (Exception e) { - log.error("[handle][消息解码失败,断开连接,客户端 ID: {},地址: {},错误: {}]", - clientId, connectionManager.getEndpointAddress(endpoint), e.getMessage()); - cleanupConnection(endpoint); - endpoint.close(); - } - }); - - // 4. 设置订阅处理器 - endpoint.subscribeHandler(subscribe -> { - // 提取主题名称列表用于日志显示 - List topicNames = subscribe.topicSubscriptions().stream() - .map(MqttTopicSubscription::topicName) - .collect(java.util.stream.Collectors.toList()); - log.debug("[handle][设备订阅,客户端 ID: {},主题: {}]", clientId, topicNames); - - // 提取 QoS 列表 - List grantedQoSLevels = subscribe.topicSubscriptions().stream() - .map(MqttTopicSubscription::qualityOfService) - .collect(java.util.stream.Collectors.toList()); - endpoint.subscribeAcknowledge(subscribe.messageId(), grantedQoSLevels); - }); - - // 5. 设置取消订阅处理器 - endpoint.unsubscribeHandler(unsubscribe -> { - log.debug("[handle][设备取消订阅,客户端 ID: {},主题: {}]", clientId, unsubscribe.topics()); - endpoint.unsubscribeAcknowledge(unsubscribe.messageId()); - }); - - // 6. 设置 QoS 2消息的 PUBREL 处理器 - endpoint.publishReleaseHandler(endpoint::publishComplete); - - // 7. 设置断开连接处理器 - endpoint.disconnectHandler(v -> { - log.debug("[handle][设备断开连接,客户端 ID: {}]", clientId); - cleanupConnection(endpoint); - }); - - // 8. 接受连接 - endpoint.accept(false); - } - - /** - * 处理消息 - * - * @param clientId 客户端 ID - * @param topic 主题 - * @param payload 消息内容 - */ - private void processMessage(String clientId, String topic, byte[] payload) { - // 1. 基础检查 - if (payload == null || payload.length == 0) { - return; - } - - // 2. 解析主题,获取 productKey 和 deviceName - String[] topicParts = topic.split("/"); - if (topicParts.length < 4 || StrUtil.hasBlank(topicParts[2], topicParts[3])) { - log.warn("[processMessage][topic({}) 格式不正确,无法解析有效的 productKey 和 deviceName]", topic); - return; - } - - String productKey = topicParts[2]; - String deviceName = topicParts[3]; - - // 3. 解码消息(使用从 topic 解析的 productKey 和 deviceName) - try { - IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(payload, productKey, deviceName); - if (message == null) { - log.warn("[processMessage][消息解码失败,客户端 ID: {},主题: {}]", clientId, topic); - return; - } - - log.info("[processMessage][收到设备消息,设备: {}.{}, 方法: {}]", - productKey, deviceName, message.getMethod()); - - // 4. 处理业务消息(认证已在连接时完成) - handleBusinessRequest(message, productKey, deviceName); - } catch (Exception e) { - log.error("[processMessage][消息处理异常,客户端 ID: {},主题: {},错误: {}]", - clientId, topic, e.getMessage(), e); - } - } - - /** - * 在 MQTT 连接时进行设备认证 - * - * @param clientId 客户端 ID - * @param username 用户名 - * @param password 密码 - * @param endpoint MQTT 连接端点 - * @return 认证是否成功 - */ - private boolean authenticateDevice(String clientId, String username, String password, MqttEndpoint endpoint) { - try { - // 1. 参数校验 - if (StrUtil.hasEmpty(clientId, username, password)) { - log.warn("[authenticateDevice][认证参数不完整,客户端 ID: {},用户名: {}]", clientId, username); - return false; - } - - // 2. 构建认证参数 - IotDeviceAuthReqDTO authParams = new IotDeviceAuthReqDTO() - .setClientId(clientId) - .setUsername(username) - .setPassword(password); - - // 3. 调用设备认证 API - CommonResult authResult = deviceApi.authDevice(authParams); - if (!authResult.isSuccess() || !BooleanUtil.isTrue(authResult.getData())) { - log.warn("[authenticateDevice][设备认证失败,客户端 ID: {},用户名: {},错误: {}]", - clientId, username, authResult.getMsg()); - return false; - } - - // 4. 获取设备信息 - IotDeviceAuthUtils.DeviceInfo deviceInfo = IotDeviceAuthUtils.parseUsername(username); - if (deviceInfo == null) { - log.warn("[authenticateDevice][用户名格式不正确,客户端 ID: {},用户名: {}]", clientId, username); - return false; - } - - IotDeviceGetReqDTO getReqDTO = new IotDeviceGetReqDTO() - .setProductKey(deviceInfo.getProductKey()) - .setDeviceName(deviceInfo.getDeviceName()); - - CommonResult deviceResult = deviceApi.getDevice(getReqDTO); - if (!deviceResult.isSuccess() || deviceResult.getData() == null) { - log.warn("[authenticateDevice][获取设备信息失败,客户端 ID: {},用户名: {},错误: {}]", - clientId, username, deviceResult.getMsg()); - return false; - } - - // 5. 注册连接 - IotDeviceRespDTO device = deviceResult.getData(); - registerConnection(endpoint, device, clientId); - - // 6. 发送设备上线消息 - sendOnlineMessage(device); - - return true; - } catch (Exception e) { - log.error("[authenticateDevice][设备认证异常,客户端 ID: {},用户名: {}]", clientId, username, e); - return false; - } - } - - /** - * 处理业务请求 - */ - private void handleBusinessRequest(IotDeviceMessage message, String productKey, String deviceName) { - // 发送消息到消息总线 - message.setServerId(serverId); - deviceMessageService.sendDeviceMessage(message, productKey, deviceName, serverId); - } - - /** - * 注册连接 - */ - private void registerConnection(MqttEndpoint endpoint, IotDeviceRespDTO device, - String clientId) { - - IotMqttConnectionManager.ConnectionInfo connectionInfo = new IotMqttConnectionManager.ConnectionInfo() - .setDeviceId(device.getId()) - .setProductKey(device.getProductKey()) - .setDeviceName(device.getDeviceName()) - .setClientId(clientId) - .setAuthenticated(true) - .setRemoteAddress(connectionManager.getEndpointAddress(endpoint)); - - connectionManager.registerConnection(endpoint, device.getId(), connectionInfo); - } - - /** - * 发送设备上线消息 - */ - private void sendOnlineMessage(IotDeviceRespDTO device) { - try { - IotDeviceMessage onlineMessage = IotDeviceMessage.buildStateUpdateOnline(); - deviceMessageService.sendDeviceMessage(onlineMessage, device.getProductKey(), - device.getDeviceName(), serverId); - log.info("[sendOnlineMessage][设备上线,设备 ID: {},设备名称: {}]", device.getId(), device.getDeviceName()); - } catch (Exception e) { - log.error("[sendOnlineMessage][发送设备上线消息失败,设备 ID: {},错误: {}]", device.getId(), e.getMessage()); - } - } - - /** - * 清理连接 - */ - private void cleanupConnection(MqttEndpoint endpoint) { - try { - IotMqttConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfo(endpoint); - if (connectionInfo != null) { - // 发送设备离线消息 - IotDeviceMessage offlineMessage = IotDeviceMessage.buildStateOffline(); - deviceMessageService.sendDeviceMessage(offlineMessage, connectionInfo.getProductKey(), - connectionInfo.getDeviceName(), serverId); - log.info("[cleanupConnection][设备离线,设备 ID: {},设备名称: {}]", - connectionInfo.getDeviceId(), connectionInfo.getDeviceName()); - } - - // 注销连接 - connectionManager.unregisterConnection(endpoint); - } catch (Exception e) { - log.error("[cleanupConnection][清理连接失败,客户端 ID: {},错误: {}]", - endpoint.clientIdentifier(), e.getMessage()); - } - } - -} diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/package-info.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/package-info.java deleted file mode 100644 index 6eb414ee9f..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * 提供设备接入的各种协议的实现 - */ -package cn.iocoder.yudao.module.iot.gateway.protocol; \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotTcpDownstreamSubscriber.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotTcpDownstreamSubscriber.java deleted file mode 100644 index 914e04dcfc..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotTcpDownstreamSubscriber.java +++ /dev/null @@ -1,68 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.protocol.tcp; - -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.gateway.protocol.tcp.manager.IotTcpConnectionManager; -import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router.IotTcpDownstreamHandler; -import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService; -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -import javax.annotation.PostConstruct; - -/** - * IoT 网关 TCP 下游订阅者:接收下行给设备的消息 - * - * @author 芋道源码 - */ -@Slf4j -@RequiredArgsConstructor -public class IotTcpDownstreamSubscriber implements IotMessageSubscriber { - - private final IotTcpUpstreamProtocol protocol; - - private final IotDeviceMessageService messageService; - - private final IotDeviceService deviceService; - - private final IotTcpConnectionManager connectionManager; - - private final IotMessageBus messageBus; - - private IotTcpDownstreamHandler downstreamHandler; - - @PostConstruct - public void init() { - // 初始化下游处理器 - this.downstreamHandler = new IotTcpDownstreamHandler(messageService, deviceService, connectionManager); - - messageBus.register(this); - log.info("[init][TCP 下游订阅者初始化完成,服务器 ID: {},Topic: {}]", - protocol.getServerId(), getTopic()); - } - - @Override - public String getTopic() { - return IotDeviceMessageUtils.buildMessageBusGatewayDeviceMessageTopic(protocol.getServerId()); - } - - @Override - public String getGroup() { - // 保证点对点消费,需要保证独立的 Group,所以使用 Topic 作为 Group - return getTopic(); - } - - @Override - public void onMessage(IotDeviceMessage message) { - try { - downstreamHandler.handle(message); - } catch (Exception e) { - log.error("[onMessage][处理下行消息失败,设备 ID: {},方法: {},消息 ID: {}]", - message.getDeviceId(), message.getMethod(), message.getId(), e); - } - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotTcpUpstreamProtocol.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotTcpUpstreamProtocol.java deleted file mode 100644 index 8cfafc3de0..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotTcpUpstreamProtocol.java +++ /dev/null @@ -1,100 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.protocol.tcp; - -import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; -import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties; -import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager.IotTcpConnectionManager; -import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router.IotTcpUpstreamHandler; -import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService; -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; -import io.vertx.core.Vertx; -import io.vertx.core.net.NetServer; -import io.vertx.core.net.NetServerOptions; -import io.vertx.core.net.PemKeyCertOptions; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; - -/** - * IoT 网关 TCP 协议:接收设备上行消息 - * - * @author 芋道源码 - */ -@Slf4j -public class IotTcpUpstreamProtocol { - - private final IotGatewayProperties.TcpProperties tcpProperties; - - private final IotDeviceService deviceService; - - private final IotDeviceMessageService messageService; - - private final IotTcpConnectionManager connectionManager; - - private final Vertx vertx; - - @Getter - private final String serverId; - - private NetServer tcpServer; - - public IotTcpUpstreamProtocol(IotGatewayProperties.TcpProperties tcpProperties, - IotDeviceService deviceService, - IotDeviceMessageService messageService, - IotTcpConnectionManager connectionManager, - Vertx vertx) { - this.tcpProperties = tcpProperties; - this.deviceService = deviceService; - this.messageService = messageService; - this.connectionManager = connectionManager; - this.vertx = vertx; - this.serverId = IotDeviceMessageUtils.generateServerId(tcpProperties.getPort()); - } - - @PostConstruct - public void start() { - // 创建服务器选项 - NetServerOptions options = new NetServerOptions() - .setPort(tcpProperties.getPort()) - .setTcpKeepAlive(true) - .setTcpNoDelay(true) - .setReuseAddress(true); - // 配置 SSL(如果启用) - if (Boolean.TRUE.equals(tcpProperties.getSslEnabled())) { - PemKeyCertOptions pemKeyCertOptions = new PemKeyCertOptions() - .setKeyPath(tcpProperties.getSslKeyPath()) - .setCertPath(tcpProperties.getSslCertPath()); - options.setSsl(true).setKeyCertOptions(pemKeyCertOptions); - } - - // 创建服务器并设置连接处理器 - tcpServer = vertx.createNetServer(options); - tcpServer.connectHandler(socket -> { - IotTcpUpstreamHandler handler = new IotTcpUpstreamHandler(this, messageService, deviceService, - connectionManager); - handler.handle(socket); - }); - - // 启动服务器 - try { - tcpServer.listen().result(); - log.info("[start][IoT 网关 TCP 协议启动成功,端口:{}]", tcpProperties.getPort()); - } catch (Exception e) { - log.error("[start][IoT 网关 TCP 协议启动失败]", e); - throw e; - } - } - - @PreDestroy - public void stop() { - if (tcpServer != null) { - try { - tcpServer.close().result(); - log.info("[stop][IoT 网关 TCP 协议已停止]"); - } catch (Exception e) { - log.error("[stop][IoT 网关 TCP 协议停止失败]", e); - } - } - } -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/manager/IotTcpConnectionManager.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/manager/IotTcpConnectionManager.java deleted file mode 100644 index c0d209814e..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/manager/IotTcpConnectionManager.java +++ /dev/null @@ -1,168 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager; - -import io.vertx.core.net.NetSocket; -import lombok.Data; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * IoT 网关 TCP 连接管理器 - *

- * 统一管理 TCP 连接的认证状态、设备会话和消息发送功能: - * 1. 管理 TCP 连接的认证状态 - * 2. 管理设备会话和在线状态 - * 3. 管理消息发送到设备 - * - * @author 芋道源码 - */ -@Slf4j -@Component -public class IotTcpConnectionManager { - - /** - * 连接信息映射:NetSocket -> 连接信息 - */ - private final Map connectionMap = new ConcurrentHashMap<>(); - - /** - * 设备 ID -> NetSocket 的映射 - */ - private final Map deviceSocketMap = new ConcurrentHashMap<>(); - - /** - * 注册设备连接(包含认证信息) - * - * @param socket TCP 连接 - * @param deviceId 设备 ID - * @param connectionInfo 连接信息 - */ - public void registerConnection(NetSocket socket, Long deviceId, ConnectionInfo connectionInfo) { - // 如果设备已有其他连接,先清理旧连接 - NetSocket oldSocket = deviceSocketMap.get(deviceId); - if (oldSocket != null && oldSocket != socket) { - log.info("[registerConnection][设备已有其他连接,断开旧连接,设备 ID: {},旧连接: {}]", - deviceId, oldSocket.remoteAddress()); - oldSocket.close(); - // 清理旧连接的映射 - connectionMap.remove(oldSocket); - } - - connectionMap.put(socket, connectionInfo); - deviceSocketMap.put(deviceId, socket); - - log.info("[registerConnection][注册设备连接,设备 ID: {},连接: {},product key: {},device name: {}]", - deviceId, socket.remoteAddress(), connectionInfo.getProductKey(), connectionInfo.getDeviceName()); - } - - /** - * 注销设备连接 - * - * @param socket TCP 连接 - */ - public void unregisterConnection(NetSocket socket) { - ConnectionInfo connectionInfo = connectionMap.remove(socket); - if (connectionInfo != null) { - Long deviceId = connectionInfo.getDeviceId(); - deviceSocketMap.remove(deviceId); - log.info("[unregisterConnection][注销设备连接,设备 ID: {},连接: {}]", - deviceId, socket.remoteAddress()); - } - } - - /** - * 检查连接是否已认证 - */ - public boolean isAuthenticated(NetSocket socket) { - ConnectionInfo info = connectionMap.get(socket); - return info != null && info.isAuthenticated(); - } - - /** - * 检查连接是否未认证 - */ - public boolean isNotAuthenticated(NetSocket socket) { - return !isAuthenticated(socket); - } - - /** - * 获取连接信息 - */ - public ConnectionInfo getConnectionInfo(NetSocket socket) { - return connectionMap.get(socket); - } - - /** - * 检查设备是否在线 - */ - public boolean isDeviceOnline(Long deviceId) { - return deviceSocketMap.containsKey(deviceId); - } - - /** - * 检查设备是否离线 - */ - public boolean isDeviceOffline(Long deviceId) { - return !isDeviceOnline(deviceId); - } - - /** - * 发送消息到设备 - */ - public boolean sendToDevice(Long deviceId, byte[] data) { - NetSocket socket = deviceSocketMap.get(deviceId); - if (socket == null) { - log.warn("[sendToDevice][设备未连接,设备 ID: {}]", deviceId); - return false; - } - - try { - socket.write(io.vertx.core.buffer.Buffer.buffer(data)); - log.debug("[sendToDevice][发送消息成功,设备 ID: {},数据长度: {} 字节]", deviceId, data.length); - return true; - } catch (Exception e) { - log.error("[sendToDevice][发送消息失败,设备 ID: {}]", deviceId, e); - // 发送失败时清理连接 - unregisterConnection(socket); - return false; - } - } - - /** - * 连接信息(包含认证信息) - */ - @Data - public static class ConnectionInfo { - - /** - * 设备 ID - */ - private Long deviceId; - /** - * 产品 Key - */ - private String productKey; - /** - * 设备名称 - */ - private String deviceName; - - /** - * 客户端 ID - */ - private String clientId; - /** - * 消息编解码类型(认证后确定) - */ - private String codecType; - // TODO @haohao:有没可能不要 authenticated 字段,通过 deviceId 或者其他的?进一步简化,想的是哈。 - /** - * 是否已认证 - */ - private boolean authenticated; - - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/router/IotTcpDownstreamHandler.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/router/IotTcpDownstreamHandler.java deleted file mode 100644 index 3ee31d82e4..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/router/IotTcpDownstreamHandler.java +++ /dev/null @@ -1,63 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router; - -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager.IotTcpConnectionManager; -import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService; -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -/** - * IoT 网关 TCP 下行消息处理器 - * - * @author 芋道源码 - */ -@Slf4j -@RequiredArgsConstructor -public class IotTcpDownstreamHandler { - - private final IotDeviceMessageService deviceMessageService; - - private final IotDeviceService deviceService; - - private final IotTcpConnectionManager connectionManager; - - /** - * 处理下行消息 - */ - public void handle(IotDeviceMessage message) { - try { - log.info("[handle][处理下行消息,设备 ID: {},方法: {},消息 ID: {}]", - message.getDeviceId(), message.getMethod(), message.getId()); - - // 1.1 获取设备信息 - IotDeviceRespDTO deviceInfo = deviceService.getDeviceFromCache(message.getDeviceId()); - if (deviceInfo == null) { - log.error("[handle][设备不存在,设备 ID: {}]", message.getDeviceId()); - return; - } - // 1.2 检查设备是否在线 - if (connectionManager.isDeviceOffline(message.getDeviceId())) { - log.warn("[handle][设备不在线,设备 ID: {}]", message.getDeviceId()); - return; - } - - // 2. 根据产品 Key 和设备名称编码消息并发送到设备 - byte[] bytes = deviceMessageService.encodeDeviceMessage(message, deviceInfo.getProductKey(), - deviceInfo.getDeviceName()); - boolean success = connectionManager.sendToDevice(message.getDeviceId(), bytes); - if (success) { - log.info("[handle][下行消息发送成功,设备 ID: {},方法: {},消息 ID: {},数据长度: {} 字节]", - message.getDeviceId(), message.getMethod(), message.getId(), bytes.length); - } else { - log.error("[handle][下行消息发送失败,设备 ID: {},方法: {},消息 ID: {}]", - message.getDeviceId(), message.getMethod(), message.getId()); - } - } catch (Exception e) { - log.error("[handle][处理下行消息失败,设备 ID: {},方法: {},消息内容: {}]", - message.getDeviceId(), message.getMethod(), message, e); - } - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/router/IotTcpUpstreamHandler.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/router/IotTcpUpstreamHandler.java deleted file mode 100644 index 0aff8f72f2..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/router/IotTcpUpstreamHandler.java +++ /dev/null @@ -1,408 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router; - -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.BooleanUtil; -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.spring.SpringUtil; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -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.IotDeviceRespDTO; -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; -import cn.iocoder.yudao.module.iot.gateway.codec.tcp.IotTcpBinaryDeviceMessageCodec; -import cn.iocoder.yudao.module.iot.gateway.codec.tcp.IotTcpJsonDeviceMessageCodec; -import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpUpstreamProtocol; -import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager.IotTcpConnectionManager; -import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService; -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; -import io.vertx.core.Handler; -import io.vertx.core.buffer.Buffer; -import io.vertx.core.net.NetSocket; -import lombok.extern.slf4j.Slf4j; - -/** - * TCP 上行消息处理器 - * - * @author 芋道源码 - */ -@Slf4j -public class IotTcpUpstreamHandler implements Handler { - - private static final String CODEC_TYPE_JSON = IotTcpJsonDeviceMessageCodec.TYPE; - private static final String CODEC_TYPE_BINARY = IotTcpBinaryDeviceMessageCodec.TYPE; - - private static final String AUTH_METHOD = "auth"; - - private final IotDeviceMessageService deviceMessageService; - - private final IotDeviceService deviceService; - - private final IotTcpConnectionManager connectionManager; - - private final IotDeviceCommonApi deviceApi; - - private final String serverId; - - public IotTcpUpstreamHandler(IotTcpUpstreamProtocol protocol, - IotDeviceMessageService deviceMessageService, - IotDeviceService deviceService, - IotTcpConnectionManager connectionManager) { - this.deviceMessageService = deviceMessageService; - this.deviceService = deviceService; - this.connectionManager = connectionManager; - this.deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class); - this.serverId = protocol.getServerId(); - } - - @Override - public void handle(NetSocket socket) { - String clientId = IdUtil.simpleUUID(); - log.debug("[handle][设备连接,客户端 ID: {},地址: {}]", clientId, socket.remoteAddress()); - - // 设置异常和关闭处理器 - socket.exceptionHandler(ex -> { - log.warn("[handle][连接异常,客户端 ID: {},地址: {}]", clientId, socket.remoteAddress()); - cleanupConnection(socket); - }); - socket.closeHandler(v -> { - log.debug("[handle][连接关闭,客户端 ID: {},地址: {}]", clientId, socket.remoteAddress()); - cleanupConnection(socket); - }); - - // 设置消息处理器 - socket.handler(buffer -> { - try { - processMessage(clientId, buffer, socket); - } catch (Exception e) { - log.error("[handle][消息解码失败,断开连接,客户端 ID: {},地址: {},错误: {}]", - clientId, socket.remoteAddress(), e.getMessage()); - cleanupConnection(socket); - socket.close(); - } - }); - } - - /** - * 处理消息 - * - * @param clientId 客户端 ID - * @param buffer 消息 - * @param socket 网络连接 - * @throws Exception 消息解码失败时抛出异常 - */ - private void processMessage(String clientId, Buffer buffer, NetSocket socket) throws Exception { - // 1. 基础检查 - if (buffer == null || buffer.length() == 0) { - return; - } - - // 2. 获取消息格式类型 - String codecType = getMessageCodecType(buffer, socket); - - // 3. 解码消息 - IotDeviceMessage message; - try { - message = deviceMessageService.decodeDeviceMessage(buffer.getBytes(), codecType); - if (message == null) { - throw new Exception("解码后消息为空"); - } - } catch (Exception e) { - // 消息格式错误时抛出异常,由上层处理连接断开 - throw new Exception("消息解码失败: " + e.getMessage(), e); - } - - // 4. 根据消息类型路由处理 - try { - if (AUTH_METHOD.equals(message.getMethod())) { - // 认证请求 - handleAuthenticationRequest(clientId, message, codecType, socket); - } else { - // 业务消息 - handleBusinessRequest(clientId, message, codecType, socket); - } - } catch (Exception e) { - log.error("[processMessage][处理消息失败,客户端 ID: {},消息方法: {}]", - clientId, message.getMethod(), e); - // 发送错误响应,避免客户端一直等待 - try { - sendErrorResponse(socket, message.getRequestId(), "消息处理失败", codecType); - } catch (Exception responseEx) { - log.error("[processMessage][发送错误响应失败,客户端 ID: {}]", clientId, responseEx); - } - } - } - - /** - * 处理认证请求 - * - * @param clientId 客户端 ID - * @param message 消息信息 - * @param codecType 消息编解码类型 - * @param socket 网络连接 - */ - private void handleAuthenticationRequest(String clientId, IotDeviceMessage message, String codecType, - NetSocket socket) { - try { - // 1.1 解析认证参数 - IotDeviceAuthReqDTO authParams = parseAuthParams(message.getParams()); - if (authParams == null) { - log.warn("[handleAuthenticationRequest][认证参数解析失败,客户端 ID: {}]", clientId); - sendErrorResponse(socket, message.getRequestId(), "认证参数不完整", codecType); - return; - } - // 1.2 执行认证 - if (!validateDeviceAuth(authParams)) { - log.warn("[handleAuthenticationRequest][认证失败,客户端 ID: {},username: {}]", - clientId, authParams.getUsername()); - sendErrorResponse(socket, message.getRequestId(), "认证失败", codecType); - return; - } - - // 2.1 解析设备信息 - IotDeviceAuthUtils.DeviceInfo deviceInfo = IotDeviceAuthUtils.parseUsername(authParams.getUsername()); - if (deviceInfo == null) { - sendErrorResponse(socket, message.getRequestId(), "解析设备信息失败", codecType); - return; - } - // 2.2 获取设备信息 - IotDeviceRespDTO device = deviceService.getDeviceFromCache(deviceInfo.getProductKey(), - deviceInfo.getDeviceName()); - if (device == null) { - sendErrorResponse(socket, message.getRequestId(), "设备不存在", codecType); - return; - } - - // 3.1 注册连接 - registerConnection(socket, device, clientId, codecType); - // 3.2 发送上线消息 - sendOnlineMessage(device); - // 3.3 发送成功响应 - sendSuccessResponse(socket, message.getRequestId(), "认证成功", codecType); - log.info("[handleAuthenticationRequest][认证成功,设备 ID: {},设备名: {}]", - device.getId(), device.getDeviceName()); - } catch (Exception e) { - log.error("[handleAuthenticationRequest][认证处理异常,客户端 ID: {}]", clientId, e); - sendErrorResponse(socket, message.getRequestId(), "认证处理异常", codecType); - } - } - - /** - * 处理业务请求 - * - * @param clientId 客户端 ID - * @param message 消息信息 - * @param codecType 消息编解码类型 - * @param socket 网络连接 - */ - private void handleBusinessRequest(String clientId, IotDeviceMessage message, String codecType, NetSocket socket) { - try { - // 1. 检查认证状态 - if (connectionManager.isNotAuthenticated(socket)) { - log.warn("[handleBusinessRequest][设备未认证,客户端 ID: {}]", clientId); - sendErrorResponse(socket, message.getRequestId(), "请先进行认证", codecType); - return; - } - - // 2. 获取认证信息并处理业务消息 - IotTcpConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfo(socket); - - // 3. 发送消息到消息总线 - deviceMessageService.sendDeviceMessage(message, connectionInfo.getProductKey(), - connectionInfo.getDeviceName(), serverId); - log.info("[handleBusinessRequest][发送消息到消息总线,客户端 ID: {},消息: {}", - clientId, message.toString()); - } catch (Exception e) { - log.error("[handleBusinessRequest][业务请求处理异常,客户端 ID: {}]", clientId, e); - } - } - - /** - * 获取消息编解码类型 - * - * @param buffer 消息 - * @param socket 网络连接 - * @return 消息编解码类型 - */ - private String getMessageCodecType(Buffer buffer, NetSocket socket) { - // 1. 如果已认证,优先使用缓存的编解码类型 - IotTcpConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfo(socket); - if (connectionInfo != null && connectionInfo.isAuthenticated() && - StrUtil.isNotBlank(connectionInfo.getCodecType())) { - return connectionInfo.getCodecType(); - } - - // 2. 未认证时检测消息格式类型 - return IotTcpBinaryDeviceMessageCodec.isBinaryFormatQuick(buffer.getBytes()) ? CODEC_TYPE_BINARY - : CODEC_TYPE_JSON; - } - - /** - * 注册连接信息 - * - * @param socket 网络连接 - * @param device 设备 - * @param clientId 客户端 ID - * @param codecType 消息编解码类型 - */ - private void registerConnection(NetSocket socket, IotDeviceRespDTO device, - String clientId, String codecType) { - IotTcpConnectionManager.ConnectionInfo connectionInfo = new IotTcpConnectionManager.ConnectionInfo() - .setDeviceId(device.getId()) - .setProductKey(device.getProductKey()) - .setDeviceName(device.getDeviceName()) - .setClientId(clientId) - .setCodecType(codecType) - .setAuthenticated(true); - // 注册连接 - connectionManager.registerConnection(socket, device.getId(), connectionInfo); - } - - /** - * 发送设备上线消息 - * - * @param device 设备信息 - */ - private void sendOnlineMessage(IotDeviceRespDTO device) { - try { - IotDeviceMessage onlineMessage = IotDeviceMessage.buildStateUpdateOnline(); - deviceMessageService.sendDeviceMessage(onlineMessage, device.getProductKey(), - device.getDeviceName(), serverId); - } catch (Exception e) { - log.error("[sendOnlineMessage][发送上线消息失败,设备: {}]", device.getDeviceName(), e); - } - } - - /** - * 清理连接 - * - * @param socket 网络连接 - */ - private void cleanupConnection(NetSocket socket) { - try { - // 1. 发送离线消息(如果已认证) - IotTcpConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfo(socket); - if (connectionInfo != null) { - IotDeviceMessage offlineMessage = IotDeviceMessage.buildStateOffline(); - deviceMessageService.sendDeviceMessage(offlineMessage, connectionInfo.getProductKey(), - connectionInfo.getDeviceName(), serverId); - } - - // 2. 注销连接 - connectionManager.unregisterConnection(socket); - } catch (Exception e) { - log.error("[cleanupConnection][清理连接失败]", e); - } - } - - /** - * 发送响应消息 - * - * @param socket 网络连接 - * @param success 是否成功 - * @param message 消息 - * @param requestId 请求 ID - * @param codecType 消息编解码类型 - */ - private void sendResponse(NetSocket socket, boolean success, String message, String requestId, String codecType) { - try { - Object responseData = MapUtil.builder() - .put("success", success) - .put("message", message) - .build(); - - int code = success ? 0 : 401; - IotDeviceMessage responseMessage = IotDeviceMessage.replyOf(requestId, AUTH_METHOD, responseData, - code, message); - - byte[] encodedData = deviceMessageService.encodeDeviceMessage(responseMessage, codecType); - socket.write(Buffer.buffer(encodedData)); - - } catch (Exception e) { - log.error("[sendResponse][发送响应失败,requestId: {}]", requestId, e); - } - } - - /** - * 验证设备认证信息 - * - * @param authParams 认证参数 - * @return 是否认证成功 - */ - private boolean validateDeviceAuth(IotDeviceAuthReqDTO authParams) { - try { - CommonResult result = deviceApi.authDevice(new IotDeviceAuthReqDTO() - .setClientId(authParams.getClientId()).setUsername(authParams.getUsername()) - .setPassword(authParams.getPassword())); - result.checkError(); - return BooleanUtil.isTrue(result.getData()); - } catch (Exception e) { - log.error("[validateDeviceAuth][设备认证异常,username: {}]", authParams.getUsername(), e); - return false; - } - } - - /** - * 发送错误响应 - * - * @param socket 网络连接 - * @param requestId 请求 ID - * @param errorMessage 错误消息 - * @param codecType 消息编解码类型 - */ - private void sendErrorResponse(NetSocket socket, String requestId, String errorMessage, String codecType) { - sendResponse(socket, false, errorMessage, requestId, codecType); - } - - /** - * 发送成功响应 - * - * @param socket 网络连接 - * @param requestId 请求 ID - * @param message 消息 - * @param codecType 消息编解码类型 - */ - @SuppressWarnings("SameParameterValue") - private void sendSuccessResponse(NetSocket socket, String requestId, String message, String codecType) { - sendResponse(socket, true, message, requestId, codecType); - } - - /** - * 解析认证参数 - * - * @param params 参数对象(通常为 Map 类型) - * @return 认证参数 DTO,解析失败时返回 null - */ - @SuppressWarnings("unchecked") - private IotDeviceAuthReqDTO parseAuthParams(Object params) { - if (params == null) { - return null; - } - - try { - // 参数默认为 Map 类型,直接转换 - if (params instanceof java.util.Map) { - java.util.Map paramMap = (java.util.Map) params; - return new IotDeviceAuthReqDTO() - .setClientId(MapUtil.getStr(paramMap, "clientId")) - .setUsername(MapUtil.getStr(paramMap, "username")) - .setPassword(MapUtil.getStr(paramMap, "password")); - } - - // 如果已经是目标类型,直接返回 - if (params instanceof IotDeviceAuthReqDTO) { - return (IotDeviceAuthReqDTO) params; - } - - // 其他情况尝试 JSON 转换 - String jsonStr = JsonUtils.toJsonString(params); - return JsonUtils.parseObject(jsonStr, IotDeviceAuthReqDTO.class); - } catch (Exception e) { - log.error("[parseAuthParams][解析认证参数({})失败]", params, e); - return null; - } - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/auth/IotDeviceTokenService.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/auth/IotDeviceTokenService.java deleted file mode 100644 index 9aab67236b..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/auth/IotDeviceTokenService.java +++ /dev/null @@ -1,37 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.service.auth; - -import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; - -/** - * IoT 设备 Token Service 接口 - * - * @author 芋道源码 - */ -public interface IotDeviceTokenService { - - /** - * 创建设备 Token - * - * @param productKey 产品 Key - * @param deviceName 设备名称 - * @return 设备 Token - */ - String createToken(String productKey, String deviceName); - - /** - * 验证设备 Token - * - * @param token 设备 Token - * @return 设备信息 - */ - IotDeviceAuthUtils.DeviceInfo verifyToken(String token); - - /** - * 解析用户名 - * - * @param username 用户名 - * @return 设备信息 - */ - IotDeviceAuthUtils.DeviceInfo parseUsername(String username); - -} diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/auth/IotDeviceTokenServiceImpl.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/auth/IotDeviceTokenServiceImpl.java deleted file mode 100644 index fda5a51714..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/auth/IotDeviceTokenServiceImpl.java +++ /dev/null @@ -1,79 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.service.auth; - -import cn.hutool.core.lang.Assert; -import cn.hutool.json.JSONObject; -import cn.hutool.jwt.JWT; -import cn.hutool.jwt.JWTUtil; -import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; -import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; -import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -import javax.annotation.Resource; -import java.time.LocalDateTime; -import java.util.HashMap; -import java.util.Map; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.iot.gateway.enums.ErrorCodeConstants.DEVICE_TOKEN_EXPIRED; - -/** - * IoT 设备 Token Service 实现类:调用远程的 device http 接口,进行设备 Token 生成、解析等逻辑 - * - * 注意:目前仅 HTTP 协议使用 - * - * @author 芋道源码 - */ -@Service -@Slf4j -public class IotDeviceTokenServiceImpl implements IotDeviceTokenService { - - @Resource - private IotGatewayProperties gatewayProperties; - - @Override - public String createToken(String productKey, String deviceName) { - Assert.notBlank(productKey, "productKey 不能为空"); - Assert.notBlank(deviceName, "deviceName 不能为空"); - // 构建 JWT payload - Map payload = new HashMap<>(); - payload.put("productKey", productKey); - payload.put("deviceName", deviceName); - LocalDateTime expireTime = LocalDateTimeUtils.addTime(gatewayProperties.getToken().getExpiration()); - payload.put("exp", LocalDateTimeUtils.toEpochSecond(expireTime)); // 过期时间(exp 是 JWT 规范推荐) - - // 生成 JWT Token - return JWTUtil.createToken(payload, gatewayProperties.getToken().getSecret().getBytes()); - } - - @Override - public IotDeviceAuthUtils.DeviceInfo verifyToken(String token) { - Assert.notBlank(token, "token 不能为空"); - // 校验 JWT Token - boolean verify = JWTUtil.verify(token, gatewayProperties.getToken().getSecret().getBytes()); - if (!verify) { - throw exception(DEVICE_TOKEN_EXPIRED); - } - - // 解析 Token - JWT jwt = JWTUtil.parseToken(token); - JSONObject payload = jwt.getPayloads(); - // 检查过期时间 - Long exp = payload.getLong("exp"); - if (exp == null || exp < System.currentTimeMillis() / 1000) { - throw exception(DEVICE_TOKEN_EXPIRED); - } - String productKey = payload.getStr("productKey"); - String deviceName = payload.getStr("deviceName"); - Assert.notBlank(productKey, "productKey 不能为空"); - Assert.notBlank(deviceName, "deviceName 不能为空"); - return new IotDeviceAuthUtils.DeviceInfo().setProductKey(productKey).setDeviceName(deviceName); - } - - @Override - public IotDeviceAuthUtils.DeviceInfo parseUsername(String username) { - return IotDeviceAuthUtils.parseUsername(username); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/IotDeviceService.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/IotDeviceService.java deleted file mode 100644 index c0d4943dab..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/IotDeviceService.java +++ /dev/null @@ -1,29 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.service.device; - -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; - -/** - * IoT 设备信息 Service 接口 - * - * @author 芋道源码 - */ -public interface IotDeviceService { - - /** - * 根据 productKey 和 deviceName 获取设备信息 - * - * @param productKey 产品标识 - * @param deviceName 设备名称 - * @return 设备信息 - */ - IotDeviceRespDTO getDeviceFromCache(String productKey, String deviceName); - - /** - * 根据 id 获取设备信息 - * - * @param id 设备编号 - * @return 设备信息 - */ - IotDeviceRespDTO getDeviceFromCache(Long id); - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/IotDeviceServiceImpl.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/IotDeviceServiceImpl.java deleted file mode 100644 index 0ad51f4035..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/IotDeviceServiceImpl.java +++ /dev/null @@ -1,81 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.service.device; - -import cn.hutool.core.lang.Assert; -import cn.iocoder.yudao.framework.common.core.KeyValue; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO; -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -import javax.annotation.Resource; -import java.time.Duration; - -import static cn.iocoder.yudao.framework.common.util.cache.CacheUtils.buildAsyncReloadingCache; - -/** - * IoT 设备信息 Service 实现类 - * - * @author 芋道源码 - */ -@Service -@Slf4j -public class IotDeviceServiceImpl implements IotDeviceService { - - private static final Duration CACHE_EXPIRE = Duration.ofMinutes(1); - - /** - * 通过 id 查询设备的缓存 - */ - private final LoadingCache deviceCaches = buildAsyncReloadingCache( - CACHE_EXPIRE, - new CacheLoader<>() { - - @Override - public IotDeviceRespDTO load(Long id) { - CommonResult result = deviceApi.getDevice(new IotDeviceGetReqDTO().setId(id)); - IotDeviceRespDTO device = result.getCheckedData(); - Assert.notNull(device, "设备({}) 不能为空", id); - // 相互缓存 - deviceCaches2.put(new KeyValue<>(device.getProductKey(), device.getDeviceName()), device); - return device; - } - - }); - - /** - * 通过 productKey + deviceName 查询设备的缓存 - */ - private final LoadingCache, IotDeviceRespDTO> deviceCaches2 = buildAsyncReloadingCache( - CACHE_EXPIRE, - new CacheLoader<>() { - - @Override - public IotDeviceRespDTO load(KeyValue kv) { - CommonResult result = deviceApi.getDevice(new IotDeviceGetReqDTO() - .setProductKey(kv.getKey()).setDeviceName(kv.getValue())); - IotDeviceRespDTO device = result.getCheckedData(); - Assert.notNull(device, "设备({}/{}) 不能为空", kv.getKey(), kv.getValue()); - // 相互缓存 - deviceCaches.put(device.getId(), device); - return device; - } - }); - - @Resource - private IotDeviceCommonApi deviceApi; - - @Override - public IotDeviceRespDTO getDeviceFromCache(String productKey, String deviceName) { - return deviceCaches2.getUnchecked(new KeyValue<>(productKey, deviceName)); - } - - @Override - public IotDeviceRespDTO getDeviceFromCache(Long id) { - return deviceCaches.getUnchecked(id); - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/message/IotDeviceMessageService.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/message/IotDeviceMessageService.java deleted file mode 100644 index c86fc0983d..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/message/IotDeviceMessageService.java +++ /dev/null @@ -1,64 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.service.device.message; - -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; - -/** - * IoT 设备消息 Service 接口 - * - * @author 芋道源码 - */ -public interface IotDeviceMessageService { - - /** - * 编码消息 - * - * @param message 消息 - * @param productKey 产品 Key - * @param deviceName 设备名称 - * @return 编码后的消息内容 - */ - byte[] encodeDeviceMessage(IotDeviceMessage message, - String productKey, String deviceName); - - /** - * 编码消息 - * - * @param message 消息 - * @param codecType 编解码器类型 - * @return 编码后的消息内容 - */ - byte[] encodeDeviceMessage(IotDeviceMessage message, - String codecType); - - /** - * 解码消息 - * - * @param bytes 消息内容 - * @param productKey 产品 Key - * @param deviceName 设备名称 - * @return 解码后的消息内容 - */ - IotDeviceMessage decodeDeviceMessage(byte[] bytes, - String productKey, String deviceName); - - /** - * 解码消息 - * - * @param bytes 消息内容 - * @param codecType 编解码器类型 - * @return 解码后的消息内容 - */ - IotDeviceMessage decodeDeviceMessage(byte[] bytes, String codecType); - - /** - * 发送消息 - * - * @param message 消息 - * @param productKey 产品 Key - * @param deviceName 设备名称 - * @param serverId 设备连接的 serverId - */ - void sendDeviceMessage(IotDeviceMessage message, - String productKey, String deviceName, String serverId); - -} diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/message/IotDeviceMessageServiceImpl.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/message/IotDeviceMessageServiceImpl.java deleted file mode 100644 index 8a93ed95c3..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/message/IotDeviceMessageServiceImpl.java +++ /dev/null @@ -1,138 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.service.device.message; - -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; -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.gateway.codec.IotDeviceMessageCodec; -import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -import javax.annotation.Resource; -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.module.iot.gateway.enums.ErrorCodeConstants.DEVICE_NOT_EXISTS; - -/** - * IoT 设备消息 Service 实现类 - * - * @author 芋道源码 - */ -@Service -@Slf4j -public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { - - /** - * 编解码器 - */ - private final Map codes; - - @Resource - private IotDeviceService deviceService; - - @Resource - private IotDeviceMessageProducer deviceMessageProducer; - - public IotDeviceMessageServiceImpl(List codes) { - this.codes = CollectionUtils.convertMap(codes, IotDeviceMessageCodec::type); - } - - @Override - public byte[] encodeDeviceMessage(IotDeviceMessage message, - String productKey, String deviceName) { - // 1.1 获取设备信息 - IotDeviceRespDTO device = deviceService.getDeviceFromCache(productKey, deviceName); - if (device == null) { - throw exception(DEVICE_NOT_EXISTS, productKey, deviceName); - } - // 1.2 获取编解码器 - IotDeviceMessageCodec codec = codes.get(device.getCodecType()); - if (codec == null) { - throw new IllegalArgumentException(StrUtil.format("编解码器({}) 不存在", device.getCodecType())); - } - - // 2. 编码消息 - return codec.encode(message); - } - - @Override - public byte[] encodeDeviceMessage(IotDeviceMessage message, - String codecType) { - // 1. 获取编解码器 - IotDeviceMessageCodec codec = codes.get(codecType); - if (codec == null) { - throw new IllegalArgumentException(StrUtil.format("编解码器({}) 不存在", codecType)); - } - - // 2. 编码消息 - return codec.encode(message); - } - - @Override - public IotDeviceMessage decodeDeviceMessage(byte[] bytes, - String productKey, String deviceName) { - // 1.1 获取设备信息 - IotDeviceRespDTO device = deviceService.getDeviceFromCache(productKey, deviceName); - if (device == null) { - throw exception(DEVICE_NOT_EXISTS, productKey, deviceName); - } - // 1.2 获取编解码器 - IotDeviceMessageCodec codec = codes.get(device.getCodecType()); - if (codec == null) { - throw new IllegalArgumentException(StrUtil.format("编解码器({}) 不存在", device.getCodecType())); - } - - // 2. 解码消息 - return codec.decode(bytes); - } - - @Override - public IotDeviceMessage decodeDeviceMessage(byte[] bytes, String codecType) { - // 1. 获取编解码器 - IotDeviceMessageCodec codec = codes.get(codecType); - if (codec == null) { - throw new IllegalArgumentException(StrUtil.format("编解码器({}) 不存在", codecType)); - } - - // 2. 解码消息 - return codec.decode(bytes); - } - - @Override - public void sendDeviceMessage(IotDeviceMessage message, - String productKey, String deviceName, String serverId) { - // 1. 获取设备信息 - IotDeviceRespDTO device = deviceService.getDeviceFromCache(productKey, deviceName); - if (device == null) { - throw exception(DEVICE_NOT_EXISTS, productKey, deviceName); - } - - // 2. 发送消息 - appendDeviceMessage(message, device, serverId); - deviceMessageProducer.sendDeviceMessage(message); - } - - /** - * 补充消息的后端字段 - * - * @param message 消息 - * @param device 设备信息 - * @param serverId 设备连接的 serverId - */ - private void appendDeviceMessage(IotDeviceMessage message, - IotDeviceRespDTO device, String serverId) { - message.setId(IotDeviceMessageUtils.generateMessageId()).setReportTime(LocalDateTime.now()) - .setDeviceId(device.getId()).setTenantId(device.getTenantId()).setServerId(serverId); - // 特殊:如果设备没有指定 requestId,则使用 messageId - if (StrUtil.isEmpty(message.getRequestId())) { - message.setRequestId(message.getId()); - } - } - -} diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/remote/IotDeviceApiImpl.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/remote/IotDeviceApiImpl.java deleted file mode 100644 index 58e42f93e4..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/remote/IotDeviceApiImpl.java +++ /dev/null @@ -1,75 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.service.device.remote; - -import cn.hutool.core.lang.Assert; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -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.gateway.config.IotGatewayProperties; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.web.client.RestTemplateBuilder; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Service; -import org.springframework.web.client.RestTemplate; - -import javax.annotation.PostConstruct; -import javax.annotation.Resource; - -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR; - -/** - * Iot 设备信息 Service 实现类:调用远程的 device http 接口,进行设备认证、设备获取等 - * - * @author 芋道源码 - */ -@Service -@Slf4j -public class IotDeviceApiImpl implements IotDeviceCommonApi { - - @Resource - private IotGatewayProperties gatewayProperties; - - private RestTemplate restTemplate; - - @PostConstruct - public void init() { - IotGatewayProperties.RpcProperties rpc = gatewayProperties.getRpc(); - restTemplate = new RestTemplateBuilder() - .rootUri(rpc.getUrl() + "/rpc-api/iot/device") - .readTimeout(rpc.getReadTimeout()) - .connectTimeout(rpc.getConnectTimeout()) - .build(); - } - - @Override - public CommonResult authDevice(IotDeviceAuthReqDTO authReqDTO) { - return doPost("/auth", authReqDTO, new ParameterizedTypeReference<>() { }); - } - - @Override - public CommonResult getDevice(IotDeviceGetReqDTO getReqDTO) { - return doPost("/get", getReqDTO, new ParameterizedTypeReference<>() { }); - } - - private CommonResult doPost(String url, T body, - ParameterizedTypeReference> responseType) { - try { - // 请求 - HttpEntity requestEntity = new HttpEntity<>(body); - ResponseEntity> response = restTemplate.exchange( - url, HttpMethod.POST, requestEntity, responseType); - // 响应 - CommonResult result = response.getBody(); - Assert.notNull(result, "请求结果不能为空"); - return result; - } catch (Exception e) { - log.error("[doPost][url({}) body({}) 发生异常]", url, body, e); - return CommonResult.error(INTERNAL_SERVER_ERROR); - } - } - -} diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/util/IotMqttTopicUtils.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/util/IotMqttTopicUtils.java deleted file mode 100644 index 7f72937efb..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/util/IotMqttTopicUtils.java +++ /dev/null @@ -1,66 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.util; - -import cn.hutool.core.util.StrUtil; - -/** - * IoT 网关 MQTT 主题工具类 - *

- * 用于统一管理 MQTT 协议中的主题常量,基于 Alink 协议规范 - * - * @author 芋道源码 - */ -public final class IotMqttTopicUtils { - - // ========== 静态常量 ========== - - /** - * 系统主题前缀 - */ - private static final String SYS_TOPIC_PREFIX = "/sys/"; - - /** - * 回复主题后缀 - */ - private static final String REPLY_TOPIC_SUFFIX = "_reply"; - - // ========== MQTT HTTP 接口路径常量 ========== - - /** - * MQTT 认证接口路径 - * 对应 EMQX HTTP 认证插件的认证请求接口 - */ - public static final String MQTT_AUTH_PATH = "/mqtt/auth"; - - /** - * MQTT 统一事件处理接口路径 - * 对应 EMQX Webhook 的统一事件处理接口,支持所有客户端事件 - * 包括:client.connected、client.disconnected、message.publish 等 - */ - public static final String MQTT_EVENT_PATH = "/mqtt/event"; - - // ========== 工具方法 ========== - - /** - * 根据消息方法构建对应的主题 - * - * @param method 消息方法,例如 thing.property.post - * @param productKey 产品 Key - * @param deviceName 设备名称 - * @param isReply 是否为回复消息 - * @return 完整的主题路径 - */ - public static String buildTopicByMethod(String method, String productKey, String deviceName, boolean isReply) { - if (StrUtil.isBlank(method)) { - return null; - } - // 1. 将点分隔符转换为斜杠 - String topicSuffix = method.replace('.', '/'); - // 2. 对于回复消息,添加 _reply 后缀 - if (isReply) { - topicSuffix += REPLY_TOPIC_SUFFIX; - } - // 3. 构建完整主题 - return SYS_TOPIC_PREFIX + productKey + "/" + deviceName + "/" + topicSuffix; - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/resources/application.yaml b/yudao-module-iot/yudao-module-iot-gateway/src/main/resources/application.yaml deleted file mode 100644 index 322748d46d..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/resources/application.yaml +++ /dev/null @@ -1,130 +0,0 @@ -spring: - application: - name: iot-gateway-server - profiles: - active: local # 默认激活本地开发环境 - - # Redis 配置 - data: - redis: - host: 127.0.0.1 # Redis 服务器地址 - port: 6379 # Redis 服务器端口 - database: 0 # Redis 数据库索引 - # password: # Redis 密码,如果有的话 - timeout: 30000ms # 连接超时时间 - ---- #################### 消息队列相关 #################### - -# rocketmq 配置项,对应 RocketMQProperties 配置类 -rocketmq: - name-server: 127.0.0.1:9876 # RocketMQ Namesrv - # Producer 配置项 - producer: - group: ${spring.application.name}_PRODUCER # 生产者分组 - ---- #################### IoT 网关相关配置 #################### - -yudao: - iot: - # 消息总线配置 - message-bus: - type: redis # 消息总线的类型 - - # 网关配置 - gateway: - # 设备 RPC 配置 - rpc: - url: http://127.0.0.1:48080 # 主程序 API 地址 - connect-timeout: 30s - read-timeout: 30s - # 设备 Token 配置 - token: - secret: yudaoIotGatewayTokenSecret123456789 # Token 密钥,至少32位 - expiration: 7d - - # 协议配置 - protocol: - # ==================================== - # 针对引入的 HTTP 组件的配置 - # ==================================== - http: - enabled: true - server-port: 8092 - # ==================================== - # 针对引入的 EMQX 组件的配置 - # ==================================== - emqx: - enabled: false - http-port: 8090 # MQTT HTTP 服务端口 - mqtt-host: 127.0.0.1 # MQTT Broker 地址 - mqtt-port: 1883 # MQTT Broker 端口 - mqtt-username: admin # MQTT 用户名 - mqtt-password: public # MQTT 密码 - mqtt-client-id: iot-gateway-mqtt # MQTT 客户端 ID - mqtt-ssl: false # 是否开启 SSL - mqtt-topics: - - "/sys/#" # 系统主题 - clean-session: true # 是否启用 Clean Session (默认: true) - keep-alive-interval-seconds: 60 # 心跳间隔,单位秒 (默认: 60) - max-inflight-queue: 10000 # 最大飞行消息队列,单位:条 - connect-timeout-seconds: 10 # 连接超时,单位:秒 - # 是否信任所有 SSL 证书 (默认: false)。警告:生产环境必须为 false! - # 仅在开发环境或内网测试时,如果使用了自签名证书,可以临时设置为 true - trust-all: true # 在 dev 环境可以设为 true - # 遗嘱消息配置 (用于网关异常下线时通知其他系统) - will: - enabled: true # 生产环境强烈建议开启 - topic: "gateway/status/${yudao.iot.gateway.emqx.mqtt-client-id}" # 遗嘱消息主题 - payload: "offline" # 遗嘱消息负载 - qos: 1 # 遗嘱消息 QoS - retain: true # 遗嘱消息是否保留 - # 高级 SSL/TLS 配置 (当 trust-all: false 且 mqtt-ssl: true 时生效) - ssl-options: - key-store-path: "classpath:certs/client.jks" # 客户端证书库路径 - key-store-password: "your-keystore-password" # 客户端证书库密码 - trust-store-path: "classpath:certs/trust.jks" # 信任的 CA 证书库路径 - trust-store-password: "your-truststore-password" # 信任的 CA 证书库密码 - # ==================================== - # 针对引入的 TCP 组件的配置 - # ==================================== - tcp: - enabled: false - port: 8091 - keep-alive-timeout-ms: 30000 - max-connections: 1000 - ssl-enabled: false - ssl-cert-path: "classpath:certs/client.jks" - ssl-key-path: "classpath:certs/client.jks" - # ==================================== - # 针对引入的 MQTT 组件的配置 - # ==================================== - mqtt: - enabled: true - port: 1883 - max-message-size: 8192 - connect-timeout-seconds: 60 - keep-alive-timeout-seconds: 300 - ssl-enabled: false - ---- #################### 日志相关配置 #################### - -# 基础日志配置 -logging: - file: - name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径 - level: - # 应用基础日志级别 - cn.iocoder.yudao.module.iot.gateway: INFO - org.springframework.boot: INFO - # RocketMQ 日志 - org.apache.rocketmq: WARN - # MQTT 客户端日志 - # io.vertx.mqtt: DEBUG - # 开发环境详细日志 - cn.iocoder.yudao.module.iot.gateway.protocol.emqx: DEBUG - cn.iocoder.yudao.module.iot.gateway.protocol.http: DEBUG - cn.iocoder.yudao.module.iot.gateway.protocol.mqtt: DEBUG - # 根日志级别 - root: INFO - -debug: false diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/test/resources/tcp-binary-packet-examples.md b/yudao-module-iot/yudao-module-iot-gateway/src/test/resources/tcp-binary-packet-examples.md deleted file mode 100644 index d6b2b3fdb5..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/test/resources/tcp-binary-packet-examples.md +++ /dev/null @@ -1,193 +0,0 @@ -# TCP 二进制协议数据包格式说明 - -## 1. 协议概述 - -TCP 二进制协议是一种高效的自定义协议格式,采用紧凑的二进制格式传输数据,适用于对带宽和性能要求较高的 IoT 场景。 - -### 1.1 协议特点 - -- **高效传输**:完全二进制格式,减少数据传输量 -- **版本控制**:内置协议版本号,支持协议升级 -- **类型安全**:明确的消息类型标识 -- **简洁设计**:去除冗余字段,协议更加精简 -- **兼容性**:与现有 `IotDeviceMessage` 接口完全兼容 - -## 2. 协议格式 - -### 2.1 整体结构 - -``` -+--------+--------+--------+---------------------------+--------+--------+ -| 魔术字 | 版本号 | 消息类型| 消息长度(4字节) | -+--------+--------+--------+---------------------------+--------+--------+ -| 消息 ID 长度(2字节) | 消息 ID (变长字符串) | -+--------+--------+--------+--------+--------+--------+--------+--------+ -| 方法名长度(2字节) | 方法名(变长字符串) | -+--------+--------+--------+--------+--------+--------+--------+--------+ -| 消息体数据(变长) | -+--------+--------+--------+--------+--------+--------+--------+--------+ -``` - -### 2.2 字段详细说明 - -| 字段 | 长度 | 类型 | 说明 | -|------|------|------|------| -| 魔术字 | 1字节 | byte | `0x7E` - 协议识别标识,用于数据同步 | -| 版本号 | 1字节 | byte | `0x01` - 协议版本号,支持版本控制 | -| 消息类型 | 1字节 | byte | `0x01`=请求, `0x02`=响应 | -| 消息长度 | 4字节 | int | 整个消息的总长度(包含头部) | -| 消息 ID 长度 | 2字节 | short | 消息 ID 字符串的字节长度 | -| 消息 ID | 变长 | string | 消息唯一标识符(UTF-8编码) | -| 方法名长度 | 2字节 | short | 方法名字符串的字节长度 | -| 方法名 | 变长 | string | 消息方法名(UTF-8编码) | -| 消息体 | 变长 | binary | 根据消息类型的不同数据格式 | - -**⚠️ 重要说明**:deviceId 不包含在协议中,由服务器根据连接上下文自动设置 - -### 2.3 协议常量定义 - -```java -// 协议标识 -private static final byte MAGIC_NUMBER = (byte) 0x7E; -private static final byte PROTOCOL_VERSION = (byte) 0x01; - -// 消息类型 -private static final byte REQUEST = (byte) 0x01; // 请求消息 -private static final byte RESPONSE = (byte) 0x02; // 响应消息 - -// 协议长度 -private static final int HEADER_FIXED_LENGTH = 7; // 固定头部长度 -private static final int MIN_MESSAGE_LENGTH = 11; // 最小消息长度 -``` - -## 3. 消息类型和格式 - -### 3.1 请求消息 (REQUEST - 0x01) - -请求消息用于设备向服务器发送数据或请求。 - -#### 3.1.1 消息体格式 -``` -消息体 = params 数据(JSON格式) -``` - -#### 3.1.2 示例:设备认证请求 - -**消息内容:** -- 消息 ID: `auth_1704067200000_123` -- 方法名: `auth` -- 参数: `{"clientId":"device_001","username":"productKey_deviceName","password":"device_password"}` - -**二进制数据包结构:** -``` -7E // 魔术字 (0x7E) -01 // 版本号 (0x01) -01 // 消息类型 (REQUEST) -00 00 00 89 // 消息长度 (137字节) -00 19 // 消息 ID 长度 (25字节) -61 75 74 68 5F 31 37 30 34 30 // 消息 ID: "auth_1704067200000_123" -36 37 32 30 30 30 30 30 5F 31 -32 33 -00 04 // 方法名长度 (4字节) -61 75 74 68 // 方法名: "auth" -7B 22 63 6C 69 65 6E 74 49 64 // JSON参数数据 -22 3A 22 64 65 76 69 63 65 5F // {"clientId":"device_001", -30 30 31 22 2C 22 75 73 65 72 // "username":"productKey_deviceName", -6E 61 6D 65 22 3A 22 70 72 6F // "password":"device_password"} -64 75 63 74 4B 65 79 5F 64 65 -76 69 63 65 4E 61 6D 65 22 2C -22 70 61 73 73 77 6F 72 64 22 -3A 22 64 65 76 69 63 65 5F 70 -61 73 73 77 6F 72 64 22 7D -``` - -#### 3.1.3 示例:属性数据上报 - -**消息内容:** -- 消息 ID: `property_1704067200000_456` -- 方法名: `thing.property.post` -- 参数: `{"temperature":25.5,"humidity":60.2,"pressure":1013.25}` - -### 3.2 响应消息 (RESPONSE - 0x02) - -响应消息用于服务器向设备回复请求结果。 - -#### 3.2.1 消息体格式 -``` -消息体 = 响应码(4字节) + 响应消息长度(2字节) + 响应消息(UTF-8) + 响应数据(JSON) -``` - -#### 3.2.2 字段说明 - -| 字段 | 长度 | 类型 | 说明 | -|------|------|------|------| -| 响应码 | 4字节 | int | HTTP状态码风格,0=成功,其他=错误 | -| 响应消息长度 | 2字节 | short | 响应消息字符串的字节长度 | -| 响应消息 | 变长 | string | 响应提示信息(UTF-8编码) | -| 响应数据 | 变长 | binary | JSON格式的响应数据(可选) | - -#### 3.2.3 示例:认证成功响应 - -**消息内容:** -- 消息 ID: `auth_response_1704067200000_123` -- 方法名: `auth` -- 响应码: `0` -- 响应消息: `认证成功` -- 响应数据: `{"success":true,"message":"认证成功"}` - -**二进制数据包结构:** -``` -7E // 魔术字 (0x7E) -01 // 版本号 (0x01) -02 // 消息类型 (RESPONSE) -00 00 00 A4 // 消息长度 (164字节) -00 22 // 消息 ID 长度 (34字节) -61 75 74 68 5F 72 65 73 70 6F // 消息 ID: "auth_response_1704067200000_123" -6E 73 65 5F 31 37 30 34 30 36 -37 32 30 30 30 30 30 5F 31 32 -33 -00 04 // 方法名长度 (4字节) -61 75 74 68 // 方法名: "auth" -00 00 00 00 // 响应码 (0 = 成功) -00 0C // 响应消息长度 (12字节) -E8 AE A4 E8 AF 81 E6 88 90 E5 // 响应消息: "认证成功" (UTF-8) -8A 9F -7B 22 73 75 63 63 65 73 73 22 // JSON响应数据 -3A 74 72 75 65 2C 22 6D 65 73 // {"success":true,"message":"认证成功"} -73 61 67 65 22 3A 22 E8 AE A4 -E8 AF 81 E6 88 90 E5 8A 9F 22 -7D -``` - -## 4. 编解码器标识 - -```java -public static final String TYPE = "TCP_BINARY"; -``` - -## 5. 协议优势 - -- **数据紧凑**:二进制格式,相比 JSON 减少 30-50% 的数据量 -- **解析高效**:直接二进制操作,减少字符串转换开销 -- **类型安全**:明确的消息类型和字段定义 -- **设计简洁**:去除冗余字段,协议更加精简高效 -- **版本控制**:内置版本号支持协议升级 - -## 6. 与 JSON 协议对比 - -| 特性 | 二进制协议 | JSON协议 | -|------|-------------|--------| -| 数据大小 | 小(节省30-50%) | 大 | -| 解析性能 | 高 | 中等 | -| 网络开销 | 低 | 高 | -| 可读性 | 差 | 优秀 | -| 调试难度 | 高 | 低 | -| 扩展性 | 良好 | 优秀 | - -**推荐场景**: -- ✅ **高频数据传输**:传感器数据实时上报 -- ✅ **带宽受限环境**:移动网络、卫星通信 -- ✅ **性能要求高**:需要低延迟、高吞吐的场景 -- ✅ **设备资源有限**:嵌入式设备、低功耗设备 -- ❌ **开发调试阶段**:调试困难,建议使用 JSON 协议 -- ❌ **快速原型开发**:开发效率低 diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/test/resources/tcp-json-packet-examples.md b/yudao-module-iot/yudao-module-iot-gateway/src/test/resources/tcp-json-packet-examples.md deleted file mode 100644 index 09ef50cfe5..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/test/resources/tcp-json-packet-examples.md +++ /dev/null @@ -1,191 +0,0 @@ -# TCP JSON 格式协议说明 - -## 1. 协议概述 - -TCP JSON 格式协议采用纯 JSON 格式进行数据传输,具有以下特点: - -- **标准化**:使用标准 JSON 格式,易于解析和处理 -- **可读性**:人类可读,便于调试和维护 -- **扩展性**:可以轻松添加新字段,向后兼容 -- **跨平台**:JSON 格式支持所有主流编程语言 -- **安全优化**:移除冗余的 deviceId 字段,提高安全性 - -## 2. 消息格式 - -### 2.1 基础消息结构 - -```json -{ - "id": "消息唯一标识", - "method": "消息方法", - "params": { - // 请求参数 - }, - "data": { - // 响应数据 - }, - "code": 响应码, - "msg": "响应消息", - "timestamp": 时间戳 -} -``` - -**⚠️ 重要说明**: -- **不包含 deviceId 字段**:由服务器通过 TCP 连接上下文自动确定设备 ID -- **避免伪造攻击**:防止设备伪造其他设备的 ID 发送消息 - -### 2.2 字段详细说明 - -| 字段名 | 类型 | 必填 | 用途 | 说明 | -|--------|------|------|------|------| -| id | String | 是 | 所有消息 | 消息唯一标识 | -| method | String | 是 | 所有消息 | 消息方法,如 `auth`、`thing.property.post` | -| params | Object | 否 | 请求消息 | 请求参数,具体内容根据method而定 | -| data | Object | 否 | 响应消息 | 响应数据,服务器返回的结果数据 | -| code | Integer | 否 | 响应消息 | 响应码,0=成功,其他=错误 | -| msg | String | 否 | 响应消息 | 响应提示信息 | -| timestamp | Long | 是 | 所有消息 | 时间戳(毫秒),编码时自动生成 | - -### 2.3 消息分类 - -#### 2.3.1 请求消息(上行) -- **特征**:包含 `params` 字段,不包含 `code`、`msg` 字段 -- **方向**:设备 → 服务器 -- **用途**:设备认证、数据上报、状态更新等 - -#### 2.3.2 响应消息(下行) -- **特征**:包含 `code`、`msg` 字段,可能包含 `data` 字段 -- **方向**:服务器 → 设备 -- **用途**:认证结果、指令响应、错误提示等 - -## 3. 消息示例 - -### 3.1 设备认证 (auth) - -#### 认证请求格式 -**消息方向**:设备 → 服务器 - -```json -{ - "id": "auth_1704067200000_123", - "method": "auth", - "params": { - "clientId": "device_001", - "username": "productKey_deviceName", - "password": "设备密码" - }, - "timestamp": 1704067200000 -} -``` - -**认证参数说明:** - -| 字段名 | 类型 | 必填 | 说明 | -|--------|------|------|------| -| clientId | String | 是 | 客户端唯一标识,用于连接管理 | -| username | String | 是 | 设备用户名,格式为 `productKey_deviceName` | -| password | String | 是 | 设备密码,在设备管理平台配置 | - -#### 认证响应格式 -**消息方向**:服务器 → 设备 - -**认证成功响应:** -```json -{ - "id": "response_auth_1704067200000_123", - "method": "auth", - "data": { - "success": true, - "message": "认证成功" - }, - "code": 0, - "msg": "认证成功", - "timestamp": 1704067200001 -} -``` - -**认证失败响应:** -```json -{ - "id": "response_auth_1704067200000_123", - "method": "auth", - "data": { - "success": false, - "message": "认证失败:用户名或密码错误" - }, - "code": 401, - "msg": "认证失败", - "timestamp": 1704067200001 -} -``` - -### 3.2 属性数据上报 (thing.property.post) - -**消息方向**:设备 → 服务器 - -**示例:温度传感器数据上报** -```json -{ - "id": "property_1704067200000_456", - "method": "thing.property.post", - "params": { - "temperature": 25.5, - "humidity": 60.2, - "pressure": 1013.25, - "battery": 85, - "signal_strength": -65 - }, - "timestamp": 1704067200000 -} -``` - -### 3.3 设备状态更新 (thing.state.update) - -**消息方向**:设备 → 服务器 - -**示例:心跳请求** -```json -{ - "id": "heartbeat_1704067200000_321", - "method": "thing.state.update", - "params": { - "state": "online", - "uptime": 86400, - "memory_usage": 65.2, - "cpu_usage": 12.8 - }, - "timestamp": 1704067200000 -} -``` - -## 4. 编解码器标识 - -```java -public static final String TYPE = "TCP_JSON"; -``` - -## 5. 协议优势 - -- **开发效率高**:JSON 格式,开发和调试简单 -- **跨语言支持**:所有主流语言都支持 JSON -- **可读性优秀**:可以直接查看消息内容 -- **扩展性强**:可以轻松添加新字段 -- **安全性高**:移除 deviceId 字段,防止伪造攻击 - -## 6. 与二进制协议对比 - -| 特性 | JSON协议 | 二进制协议 | -|------|----------|------------| -| 开发难度 | 低 | 高 | -| 调试难度 | 低 | 高 | -| 可读性 | 优秀 | 差 | -| 数据大小 | 中等 | 小(节省30-50%) | -| 解析性能 | 中等 | 高 | -| 学习成本 | 低 | 高 | - -**推荐场景**: -- ✅ **开发调试阶段**:调试友好,开发效率高 -- ✅ **快速原型开发**:实现简单,快速迭代 -- ✅ **多语言集成**:广泛的语言支持 -- ❌ **高频数据传输**:建议使用二进制协议 -- ❌ **带宽受限环境**:建议使用二进制协议 \ No newline at end of file