【同步】最新精简版本!

This commit is contained in:
YunaiV
2025-08-31 10:50:43 +08:00
parent bbd78a0f66
commit 376b7ef043
206 changed files with 0 additions and 18960 deletions

View File

@@ -1,46 +0,0 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.gemini;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatModel;
import reactor.core.publisher.Flux;
/**
* 谷歌 Gemini {@link ChatModel} 实现类,基于 Google AI Studio 提供的 <a href="https://ai.google.dev/gemini-api/docs/openai">OpenAI 兼容方案</a>
*
* @author 芋道源码
*/
@Slf4j
@RequiredArgsConstructor
public class GeminiChatModel implements ChatModel {
public static final String BASE_URL = "https://generativelanguage.googleapis.com/v1beta/openai/";
public static final String COMPLETE_PATH = "/chat/completions";
public static final String MODEL_DEFAULT = "gemini-2.5-flash";
/**
* 兼容 OpenAI 接口,进行复用
*/
private final OpenAiChatModel openAiChatModel;
@Override
public ChatResponse call(Prompt prompt) {
return openAiChatModel.call(prompt);
}
@Override
public Flux<ChatResponse> stream(Prompt prompt) {
return openAiChatModel.stream(prompt);
}
@Override
public ChatOptions getDefaultOptions() {
return openAiChatModel.getDefaultOptions();
}
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -1,62 +0,0 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.webserch;
import lombok.Data;
import java.util.List;
@Data
public class AiWebSearchResponse {
/**
* 总数(总共匹配的网页数)
*/
private Long total;
/**
* 数据列表
*/
private List<WebPage> lists;
/**
* 网页对象
*/
@Data
public static class WebPage {
/**
* 名称
*
* 例如说:搜狐网
*/
private String name;
/**
* 图标
*/
private String icon;
/**
* 标题
*
* 例如说186页|阿里巴巴2024年环境、社会和治理ESG报告
*/
private String title;
/**
* URL
*
* 例如说https://m.sohu.com/a/815036254_121819701/?pvid=000115_3w_a
*/
@SuppressWarnings("JavadocLinkAsPlainText")
private String url;
/**
* 内容的简短描述
*/
private String snippet;
/**
* 内容的文本摘要
*/
private String summary;
}
}

View File

@@ -1,153 +0,0 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.webserch.bocha;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchClient;
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchRequest;
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchResponse;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
/**
* 博查 {@link AiWebSearchClient} 实现类
*
* @see <a href="https://open.bochaai.com/overview">博查 AI 开放平台</a>
*
* @author 芋道源码
*/
@Slf4j
public class AiBoChaWebSearchClient implements AiWebSearchClient {
public static final String BASE_URL = "https://api.bochaai.com";
private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String BEARER_PREFIX = "Bearer ";
private final WebClient webClient;
private final Predicate<HttpStatusCode> STATUS_PREDICATE = status -> !status.is2xxSuccessful();
private final Function<Object, Function<ClientResponse, Mono<? extends Throwable>>> EXCEPTION_FUNCTION =
reqParam -> response -> response.bodyToMono(String.class).handle((responseBody, sink) -> {
log.error("[AiBoChaWebSearchClient] 调用失败!请求参数:[{}],响应数据: [{}]", reqParam, responseBody);
sink.error(new IllegalStateException("[AiBoChaWebSearchClient] 调用失败!"));
});
public AiBoChaWebSearchClient(String apiKey) {
this.webClient = WebClient.builder()
.baseUrl(BASE_URL)
.defaultHeaders((headers) -> {
headers.setContentType(MediaType.APPLICATION_JSON);
headers.add(AUTHORIZATION_HEADER, BEARER_PREFIX + apiKey);
})
.build();
}
@Override
public AiWebSearchResponse search(AiWebSearchRequest request) {
// 转换请求参数
WebSearchRequest webSearchRequest = new WebSearchRequest(
request.getQuery(),
request.getSummary(),
request.getCount()
);
// 调用博查 API
CommonResult<WebSearchResponse> response = this.webClient.post()
.uri("/v1/web-search")
.bodyValue(webSearchRequest)
.retrieve()
.onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(webSearchRequest))
.bodyToMono(new ParameterizedTypeReference<CommonResult<WebSearchResponse>>() {})
.block();
if (response == null) {
throw new IllegalStateException("[search][搜索结果为空]");
}
if (response.getData() == null) {
throw new IllegalStateException(String.format("[search][搜索失败code = %s, msg = %s]",
response.getCode(), response.getMsg()));
}
WebSearchResponse data = response.getData();
// 转换结果
AiWebSearchResponse result = new AiWebSearchResponse();
if (data.webPages() == null || CollUtil.isEmpty(data.webPages().value())) {
return result.setTotal(0L).setLists(List.of());
}
return result.setTotal(data.webPages().totalEstimatedMatches())
.setLists(convertList(data.webPages().value(), page -> new AiWebSearchResponse.WebPage()
.setName(page.siteName()).setIcon(page.siteIcon())
.setTitle(page.name()).setUrl(page.url())
.setSnippet(page.snippet()).setSummary(page.summary())));
}
/**
* 网页搜索请求参数
*/
@JsonInclude(value = JsonInclude.Include.NON_NULL)
public record WebSearchRequest(
String query,
Boolean summary,
Integer count
) {
public WebSearchRequest {
Assert.notBlank(query, "query 不能为空");
}
}
/**
* 网页搜索响应
*/
@JsonInclude(value = JsonInclude.Include.NON_NULL)
public record WebSearchResponse(
WebSearchWebPages webPages
) {
}
/**
* 网页搜索结果
*/
@JsonInclude(value = JsonInclude.Include.NON_NULL)
public record WebSearchWebPages(
String webSearchUrl,
Long totalEstimatedMatches,
List<WebPageValue> value,
Boolean someResultsRemoved
) {
/**
* 网页结果值
*/
@JsonInclude(value = JsonInclude.Include.NON_NULL)
public record WebPageValue(
String id,
String name,
String url,
String displayUrl,
String snippet,
String summary,
String siteName,
String siteIcon,
String datePublished,
String dateLastCrawled,
String cachedPageUrl,
String language,
Boolean isFamilyFriendly,
Boolean isNavigational
) {
}
}
}

View File

@@ -1,4 +0,0 @@
/**
* 占位
*/
package cn.iocoder.yudao.module.ai.framework.security.core;

View File

@@ -1,4 +0,0 @@
/**
* 参考 <a href="https://docs.spring.io/spring-ai/reference/api/tools.html#_methods_as_tools">Tool Calling —— Methods as Tools</a>
*/
package cn.iocoder.yudao.module.ai.tool.function;

View File

@@ -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
) {
}

View File

@@ -1,80 +0,0 @@
package cn.iocoder.yudao.module.ai.tool.method;
import java.util.List;
import java.util.Optional;
/**
* 来自 Spring AI 官方文档
*
* Service interface for managing Person data.
* Defines the contract for CRUD operations and search/filter functionalities.
*/
public interface PersonService {
/**
* Creates a new Person record.
* Assigns a unique ID to the person and stores it.
*
* @param personData The data for the new person (ID field is ignored). Must not be null.
* @return The created Person record, including the generated ID.
*/
Person createPerson(Person personData);
/**
* Retrieves a Person by their unique ID.
*
* @param id The ID of the person to retrieve.
* @return An Optional containing the found Person, or an empty Optional if not found.
*/
Optional<Person> getPersonById(int id);
/**
* Retrieves all Person records currently stored.
*
* @return An unmodifiable List containing all Persons. Returns an empty list if none exist.
*/
List<Person> getAllPersons();
/**
* Updates an existing Person record identified by ID.
* Replaces the existing data with the provided data, keeping the original ID.
*
* @param id The ID of the person to update.
* @param updatedPersonData The new data for the person (ID field is ignored). Must not be null.
* @return true if the person was found and updated, false otherwise.
*/
boolean updatePerson(int id, Person updatedPersonData);
/**
* Deletes a Person record identified by ID.
*
* @param id The ID of the person to delete.
* @return true if the person was found and deleted, false otherwise.
*/
boolean deletePerson(int id);
/**
* Searches for Persons whose job title contains the given query string (case-insensitive).
*
* @param jobTitleQuery The string to search for within job titles. Can be null or blank.
* @return An unmodifiable List of matching Persons. Returns an empty list if no matches or query is invalid.
*/
List<Person> searchByJobTitle(String jobTitleQuery);
/**
* Filters Persons by their exact sex (case-insensitive).
*
* @param sex The sex to filter by (e.g., "Male", "Female"). Can be null or blank.
* @return An unmodifiable List of matching Persons. Returns an empty list if no matches or filter is invalid.
*/
List<Person> filterBySex(String sex);
/**
* Filters Persons by their exact age.
*
* @param age The age to filter by.
* @return An unmodifiable List of matching Persons. Returns an empty list if no matches.
*/
List<Person> filterByAge(int age);
}

View File

@@ -1,336 +0,0 @@
package cn.iocoder.yudao.module.ai.tool.method;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 来自 Spring AI 官方文档
*
* Implementation of the PersonService interface using an in-memory data store.
* Manages a collection of Person objects loaded from embedded CSV data.
* This class is thread-safe due to the use of ConcurrentHashMap and AtomicInteger.
*/
@Service
@Slf4j
public class PersonServiceImpl implements PersonService {
private final Map<Integer, Person> personStore = new ConcurrentHashMap<>();
private AtomicInteger idGenerator;
/**
* Embedded CSV data for initial population
*/
private static final String CSV_DATA = """
Id,FirstName,LastName,Email,Sex,IpAddress,JobTitle,Age
1,Fons,Tollfree,ftollfree0@senate.gov,Male,55.1 Tollfree Lane,Research Associate,31
2,Emlynne,Tabourier,etabourier1@networksolutions.com,Female,18 Tabourier Way,Associate Professor,38
3,Shae,Johncey,sjohncey2@yellowpages.com,Male,1 Johncey Circle,Structural Analysis Engineer,30
4,Sebastien,Bradly,sbradly3@mapquest.com,Male,2 Bradly Hill,Chief Executive Officer,40
5,Harriott,Kitteringham,hkitteringham4@typepad.com,Female,3 Kitteringham Drive,VP Sales,47
6,Anallise,Parradine,aparradine5@miibeian.gov.cn,Female,4 Parradine Street,Analog Circuit Design manager,44
7,Gorden,Kirkbright,gkirkbright6@reuters.com,Male,5 Kirkbright Plaza,Senior Editor,40
8,Veradis,Ledwitch,vledwitch7@google.com.au,Female,6 Ledwitch Avenue,Computer Systems Analyst IV,44
9,Agnesse,Penhalurick,apenhalurick8@google.it,Female,7 Penhalurick Terrace,Automation Specialist IV,41
10,Bibby,Hutable,bhutable9@craigslist.org,Female,8 Hutable Place,Account Representative I,43
11,Karoly,Lightoller,klightollera@rakuten.co.jp,Female,9 Lightoller Parkway,Senior Developer,46
12,Cristine,Durrad,cdurradb@aol.com,Female,10 Durrad Center,Senior Developer,48
13,Aggy,Napier,anapierc@hostgator.com,Female,11 Napier Court,VP Product Management,44
14,Prisca,Caddens,pcaddensd@vinaora.com,Female,12 Caddens Alley,Business Systems Development Analyst,41
15,Khalil,McKernan,kmckernane@google.fr,Male,13 McKernan Pass,Engineer IV,44
16,Lorry,MacTrusty,lmactrustyf@eventbrite.com,Male,14 MacTrusty Junction,Design Engineer,42
17,Casandra,Worsell,cworsellg@goo.gl,Female,15 Worsell Point,Systems Administrator IV,45
18,Ulrikaumeko,Haveline,uhavelineh@usgs.gov,Female,16 Haveline Trail,Financial Advisor,42
19,Shurlocke,Albany,salbanyi@artisteer.com,Male,17 Albany Plaza,Software Test Engineer III,46
20,Myrilla,Brimilcombe,mbrimilcombej@accuweather.com,Female,18 Brimilcombe Road,Programmer Analyst I,48
21,Carlina,Scimonelli,cscimonellik@va.gov,Female,19 Scimonelli Pass,Help Desk Technician,45
22,Tina,Goullee,tgoulleel@miibeian.gov.cn,Female,20 Goullee Crossing,Accountant IV,43
23,Adriaens,Storek,astorekm@devhub.com,Female,21 Storek Avenue,Recruiting Manager,40
24,Tedra,Giraudot,tgiraudotn@wiley.com,Female,22 Giraudot Terrace,Speech Pathologist,47
25,Josiah,Soares,jsoareso@google.nl,Male,23 Soares Street,Tax Accountant,45
26,Kayle,Gaukrodge,kgaukrodgep@wikispaces.com,Female,24 Gaukrodge Parkway,Accountant II,43
27,Ardys,Chuter,achuterq@ustream.tv,Female,25 Chuter Drive,Engineer IV,41
28,Francyne,Baudinet,fbaudinetr@newyorker.com,Female,26 Baudinet Center,VP Accounting,48
29,Gerick,Bullan,gbullans@seesaa.net,Male,27 Bullan Way,Senior Financial Analyst,43
30,Northrup,Grivori,ngrivorit@unc.edu,Male,28 Grivori Plaza,Systems Administrator I,45
31,Town,Duguid,tduguidu@squarespace.com,Male,29 Duguid Pass,Safety Technician IV,46
32,Pierette,Kopisch,pkopischv@google.com.br,Female,30 Kopisch Lane,Director of Sales,41
33,Jacquenetta,Le Prevost,jleprevostw@netlog.com,Female,31 Le Prevost Trail,Senior Developer,47
34,Garvy,Rusted,grustedx@aboutads.info,Male,32 Rusted Junction,Senior Developer,42
35,Clarice,Aysh,cayshy@merriam-webster.com,Female,33 Aysh Avenue,VP Quality Control,40
36,Tracie,Fedorski,tfedorskiz@bloglines.com,Male,34 Fedorski Terrace,Design Engineer,44
37,Noelyn,Matushenko,nmatushenko10@globo.com,Female,35 Matushenko Place,VP Sales,48
38,Rudiger,Klaesson,rklaesson11@usnews.com,Male,36 Klaesson Road,Database Administrator IV,43
39,Mirella,Syddie,msyddie12@geocities.jp,Female,37 Syddie Circle,Geological Engineer,46
40,Donalt,O'Lunny,dolunny13@elpais.com,Male,38 O'Lunny Center,Analog Circuit Design manager,41
41,Guntar,Deniskevich,gdeniskevich14@google.com.hk,Male,39 Deniskevich Way,Structural Engineer,47
42,Hort,Shufflebotham,hshufflebotham15@about.me,Male,40 Shufflebotham Court,Structural Analysis Engineer,45
43,Dominique,Thickett,dthickett16@slashdot.org,Male,41 Thickett Crossing,Safety Technician I,42
44,Zebulen,Piscopello,zpiscopello17@umich.edu,Male,42 Piscopello Parkway,Web Developer II,40
45,Mellicent,Mac Giany,mmacgiany18@state.tx.us,Female,43 Mac Giany Pass,Assistant Manager,44
46,Merle,Bounds,mbounds19@amazon.co.jp,Female,44 Bounds Alley,Systems Administrator III,41
47,Madelle,Farbrace,mfarbrace1a@xinhuanet.com,Female,45 Farbrace Terrace,Quality Engineer,48
48,Galvin,O'Sheeryne,gosheeryne1b@addtoany.com,Male,46 O'Sheeryne Way,Environmental Specialist,43
49,Guillemette,Bootherstone,gbootherstone1c@nationalgeographic.com,Female,47 Bootherstone Plaza,Professor,46
50,Letti,Aylmore,laylmore1d@vinaora.com,Female,48 Aylmore Circle,Automation Specialist I,40
51,Nonie,Rivalland,nrivalland1e@weather.com,Female,49 Rivalland Avenue,Software Test Engineer IV,45
52,Jacquelynn,Halfacre,jhalfacre1f@surveymonkey.com,Female,50 Halfacre Pass,Geologist II,42
53,Anderea,MacKibbon,amackibbon1g@weibo.com,Female,51 MacKibbon Parkway,Automation Specialist II,47
54,Wash,Klimko,wklimko1h@slashdot.org,Male,52 Klimko Alley,Database Administrator I,40
55,Flori,Kynett,fkynett1i@auda.org.au,Female,53 Kynett Trail,Quality Control Specialist,46
56,Libbey,Penswick,lpenswick1j@google.co.uk,Female,54 Penswick Point,VP Accounting,43
57,Silvanus,Skellorne,sskellorne1k@booking.com,Male,55 Skellorne Drive,Account Executive,48
58,Carmine,Mateos,cmateos1l@plala.or.jp,Male,56 Mateos Terrace,Systems Administrator I,41
59,Sheffie,Blazewicz,sblazewicz1m@google.com.au,Male,57 Blazewicz Center,VP Sales,44
60,Leanor,Worsnop,lworsnop1n@uol.com.br,Female,58 Worsnop Plaza,Systems Administrator III,45
61,Caspar,Pamment,cpamment1o@google.co.jp,Male,59 Pamment Court,Senior Financial Analyst,42
62,Justinian,Pentycost,jpentycost1p@sciencedaily.com,Male,60 Pentycost Way,Senior Quality Engineer,47
63,Gerianne,Jarnell,gjarnell1q@bing.com,Female,61 Jarnell Avenue,Help Desk Operator,40
64,Boycie,Zanetto,bzanetto1r@about.com,Male,62 Zanetto Place,Quality Engineer,46
65,Camilla,Mac Giany,cmacgiany1s@state.gov,Female,63 Mac Giany Parkway,Senior Cost Accountant,43
66,Hadlee,Piscopiello,hpiscopiello1t@artisteer.com,Male,64 Piscopiello Street,Account Representative III,48
67,Bobbie,Penvarden,bpenvarden1u@google.cn,Male,65 Penvarden Lane,Help Desk Operator,41
68,Ali,Gowlett,agowlett1v@parallels.com,Male,66 Gowlett Pass,VP Marketing,44
69,Olivette,Acome,oacome1w@qq.com,Female,67 Acome Hill,VP Product Management,45
70,Jehanna,Brotherheed,jbrotherheed1x@google.nl,Female,68 Brotherheed Junction,Database Administrator III,42
71,Morgan,Berthomieu,mberthomieu1y@artisteer.com,Male,69 Berthomieu Alley,Systems Administrator II,47
72,Linzy,Shilladay,lshilladay1z@icq.com,Female,70 Shilladay Trail,Research Assistant IV,40
73,Faydra,Brimner,fbrimner20@mozilla.org,Female,71 Brimner Road,Senior Editor,46
74,Gwenore,Oxlee,goxlee21@devhub.com,Female,72 Oxlee Terrace,Systems Administrator II,43
75,Evangelin,Beinke,ebeinke22@mozilla.com,Female,73 Beinke Circle,Accountant I,48
76,Missy,Cockling,mcockling23@si.edu,Female,74 Cockling Way,Software Engineer I,41
77,Suzanne,Klimschak,sklimschak24@etsy.com,Female,75 Klimschak Plaza,Tax Accountant,44
78,Candide,Goricke,cgoricke25@weebly.com,Female,76 Goricke Pass,Sales Associate,45
79,Gerome,Pinsent,gpinsent26@google.com.au,Male,77 Pinsent Junction,Software Consultant,42
80,Lezley,Mac Giany,lmacgiany27@scribd.com,Male,78 Mac Giany Alley,Operator,47
81,Tobiah,Durn,tdurn28@state.tx.us,Male,79 Durn Court,VP Sales,40
82,Sherlocke,Cockshoot,scockshoot29@yelp.com,Male,80 Cockshoot Street,Senior Financial Analyst,46
83,Myrle,Speenden,mspeenden2a@utexas.edu,Female,81 Speenden Center,Senior Developer,43
84,Isidore,Gorries,igorries2b@flavors.me,Male,82 Gorries Parkway,Sales Representative,48
85,Isac,Kitchingman,ikitchingman2c@businessinsider.com,Male,83 Kitchingman Drive,VP Accounting,41
86,Benedetta,Purrier,bpurrier2d@admin.ch,Female,84 Purrier Trail,VP Accounting,44
87,Tera,Fitchell,tfitchell2e@fotki.com,Female,85 Fitchell Place,Software Engineer IV,45
88,Abbe,Pamment,apamment2f@about.com,Male,86 Pamment Avenue,VP Sales,42
89,Jandy,Gommowe,jgommowe2g@angelfire.com,Female,87 Gommowe Road,Financial Analyst,47
90,Karena,Fussey,kfussey2h@google.com.au,Female,88 Fussey Point,Assistant Professor,40
91,Gaspar,Pammenter,gpammenter2i@google.com.br,Male,89 Pammenter Hill,Help Desk Operator,46
92,Stanwood,Mac Giany,smacgiany2j@prlog.org,Male,90 Mac Giany Terrace,Research Associate,43
93,Byrom,Beedell,bbeedell2k@google.co.jp,Male,91 Beedell Way,VP Sales,48
94,Annabella,Rowbottom,arowbottom2l@google.com.au,Female,92 Rowbottom Plaza,Help Desk Operator,41
95,Rodolphe,Debell,rdebell2m@imageshack.us,Male,93 Debell Pass,Design Engineer,44
96,Tyne,Gommey,tgommey2n@joomla.org,Female,94 Gommey Junction,VP Marketing,45
97,Christoper,Pincked,cpincked2o@icq.com,Male,95 Pincked Alley,Human Resources Manager,42
98,Kore,Le Prevost,kleprevost2p@tripadvisor.com,Female,96 Le Prevost Street,VP Quality Control,47
99,Ceciley,Petrolli,cpetrolli2q@oaic.gov.au,Female,97 Petrolli Court,Senior Developer,40
100,Elspeth,Mac Giany,emacgiany2r@icio.us,Female,98 Mac Giany Parkway,Internal Auditor,46
""";
/**
* Initializes the service after dependency injection by loading data from the CSV string.
* Uses @PostConstruct to ensure this runs after the bean is created.
*/
@PostConstruct
private void initializeData() {
log.info("Initializing PersonService data store...");
int maxId = loadDataFromCsv();
idGenerator = new AtomicInteger(maxId);
log.info("PersonService initialized with {} records. Next ID: {}", personStore.size(), idGenerator.get() + 1);
}
/**
* Parses the embedded CSV data and populates the in-memory store.
* Calculates the maximum ID found in the data to initialize the ID generator.
*
* @return The maximum ID found in the loaded CSV data.
*/
private int loadDataFromCsv() {
final AtomicInteger currentMaxId = new AtomicInteger(0);
// Clear existing data before loading (important for tests or re-initialization scenarios)
personStore.clear();
try (Stream<String> lines = CSV_DATA.lines().skip(1)) { // Skip header row
lines.forEach(line -> {
try {
// Split carefully, handling potential commas within quoted fields if necessary (simple split here)
String[] fields = line.split(",", 8); // Limit split to handle potential commas in job title
if (fields.length == 8) {
int id = Integer.parseInt(fields[0].trim());
String firstName = fields[1].trim();
String lastName = fields[2].trim();
String email = fields[3].trim();
String sex = fields[4].trim();
String ipAddress = fields[5].trim();
String jobTitle = fields[6].trim();
int age = Integer.parseInt(fields[7].trim());
Person person = new Person(id, firstName, lastName, email, sex, ipAddress, jobTitle, age);
personStore.put(id, person);
currentMaxId.updateAndGet(max -> Math.max(max, id));
} else {
log.warn("Skipping malformed CSV line (expected 8 fields, found {}): {}", fields.length, line);
}
} catch (NumberFormatException e) {
log.warn("Skipping line due to parsing error (ID or Age): {} - Error: {}", line, e.getMessage());
} catch (Exception e) {
log.error("Skipping line due to unexpected error: {} - Error: {}", line, e.getMessage(), e);
}
});
} catch (Exception e) {
log.error("Fatal error reading embedded CSV data: {}", e.getMessage(), e);
// In a real application, might throw a specific initialization exception
}
return currentMaxId.get();
}
@Override
@Tool(
name = "ps_create_person",
description = "Create a new person record in the in-memory store."
)
public Person createPerson(Person personData) {
if (personData == null) {
throw new IllegalArgumentException("Person data cannot be null");
}
int newId = idGenerator.incrementAndGet();
// Create a new Person record using data from the input, but with the generated ID
Person newPerson = new Person(
newId,
personData.firstName(),
personData.lastName(),
personData.email(),
personData.sex(),
personData.ipAddress(),
personData.jobTitle(),
personData.age()
);
personStore.put(newId, newPerson);
log.debug("Created person: {}", newPerson);
return newPerson;
}
@Override
@Tool(
name = "ps_get_person_by_id",
description = "Retrieve a person record by ID from the in-memory store."
)
public Optional<Person> getPersonById(int id) {
Person person = personStore.get(id);
log.debug("Retrieved person by ID {}: {}", id, person);
return Optional.ofNullable(person);
}
@Override
@Tool(
name = "ps_get_all_persons",
description = "Retrieve all person records from the in-memory store."
)
public List<Person> getAllPersons() {
// Return an unmodifiable view of the values
List<Person> allPersons = personStore.values().stream().toList();
log.debug("Retrieved all persons (count: {})", allPersons.size());
return allPersons;
}
@Override
@Tool(
name = "ps_update_person",
description = "Update an existing person record by ID in the in-memory store."
)
public boolean updatePerson(int id, Person updatedPersonData) {
if (updatedPersonData == null) {
throw new IllegalArgumentException("Updated person data cannot be null");
}
// Use computeIfPresent for atomic update if the key exists
Person result = personStore.computeIfPresent(id, (key, existingPerson) ->
// Create a new Person record with the original ID but updated data
new Person(
id, // Keep original ID
updatedPersonData.firstName(),
updatedPersonData.lastName(),
updatedPersonData.email(),
updatedPersonData.sex(),
updatedPersonData.ipAddress(),
updatedPersonData.jobTitle(),
updatedPersonData.age()
)
);
boolean updated = result != null;
log.debug("Update attempt for ID {}: {}", id, updated ? "Successful" : "Failed (Not Found)");
if(updated) log.trace("Updated person data for ID {}: {}", id, result);
return updated;
}
@Override
@Tool(
name = "ps_delete_person",
description = "Delete a person record by ID from the in-memory store."
)
public boolean deletePerson(int id) {
boolean removed = personStore.remove(id) != null;
log.debug("Delete attempt for ID {}: {}", id, removed ? "Successful" : "Failed (Not Found)");
return removed;
}
@Override
@Tool(
name = "ps_search_by_job_title",
description = "Search for persons by job title in the in-memory store."
)
public List<Person> searchByJobTitle(String jobTitleQuery) {
if (jobTitleQuery == null || jobTitleQuery.isBlank()) {
log.debug("Search by job title skipped due to blank query.");
return Collections.emptyList();
}
String lowerCaseQuery = jobTitleQuery.toLowerCase();
List<Person> results = personStore.values().stream()
.filter(person -> person.jobTitle() != null && person.jobTitle().toLowerCase().contains(lowerCaseQuery))
.collect(Collectors.toList());
log.debug("Search by job title '{}' found {} results.", jobTitleQuery, results.size());
return Collections.unmodifiableList(results);
}
@Override
@Tool(
name = "ps_filter_by_sex",
description = "Filters Persons by sex (case-insensitive)."
)
public List<Person> filterBySex(String sex) {
if (sex == null || sex.isBlank()) {
log.debug("Filter by sex skipped due to blank filter.");
return Collections.emptyList();
}
List<Person> results = personStore.values().stream()
.filter(person -> person.sex() != null && person.sex().equalsIgnoreCase(sex))
.collect(Collectors.toList());
log.debug("Filter by sex '{}' found {} results.", sex, results.size());
return Collections.unmodifiableList(results);
}
@Override
@Tool(
name = "ps_filter_by_age",
description = "Filters Persons by age."
)
public List<Person> filterByAge(int age) {
if (age < 0) {
log.debug("Filter by age skipped due to negative age: {}", age);
return Collections.emptyList(); // Or throw IllegalArgumentException based on requirements
}
List<Person> results = personStore.values().stream()
.filter(person -> person.age() == age)
.collect(Collectors.toList());
log.debug("Filter by age {} found {} results.", age, results.size());
return Collections.unmodifiableList(results);
}
}

View File

@@ -1,4 +0,0 @@
/**
* 参考 <a href="https://docs.spring.io/spring-ai/reference/api/tools.html#_methods_as_tools">Tool Calling —— Methods as Tools</a>
*/
package cn.iocoder.yudao.module.ai.tool.method;

View File

@@ -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/");
}
}

View File

@@ -1,87 +0,0 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.anthropic.AnthropicChatModel;
import org.springframework.ai.anthropic.AnthropicChatOptions;
import org.springframework.ai.anthropic.api.AnthropicApi;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
import java.util.List;
/**
* {@link AnthropicChatModel} 集成测试类
*
* @author 芋道源码
*/
public class AnthropicChatModelTest {
private final AnthropicChatModel chatModel = AnthropicChatModel.builder()
.anthropicApi(AnthropicApi.builder()
.apiKey("sk-muubv7cXeLw0Etgs743f365cD5Ea44429946Fa7e672d8942")
.baseUrl("https://aihubmix.com")
.build())
.defaultOptions(AnthropicChatOptions.builder()
.model(AnthropicApi.ChatModel.CLAUDE_SONNET_4)
.temperature(0.7)
.maxTokens(4096)
.build())
.build();
@Test
@Disabled
public void testCall() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
ChatResponse response = chatModel.call(new Prompt(messages));
// 打印结果
System.out.println(response);
}
@Test
@Disabled
public void testStream() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
// 打印结果
flux.doOnNext(System.out::println).then().block();
}
// TODO @芋艿:需要等 spring ai 升级https://github.com/spring-projects/spring-ai/pull/2800
@Test
@Disabled
public void testStream_thinking() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new UserMessage("thkinking 下1+1 为什么等于 2 "));
AnthropicChatOptions options = AnthropicChatOptions.builder()
.model(AnthropicApi.ChatModel.CLAUDE_SONNET_4)
.thinking(AnthropicApi.ThinkingType.ENABLED, 3096)
.temperature(1D)
.build();
// 调用
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages, options));
// 打印结果
flux.doOnNext(response -> {
// System.out.println(response);
System.out.println(response.getResult());
}).then().block();
}
}

View File

@@ -1,68 +0,0 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.gemini.GeminiChatModel;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
import java.util.List;
/**
* {@link GeminiChatModel} 集成测试
*
* @author 芋道源码
*/
public class GeminiChatModelTests {
private final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder()
.baseUrl(GeminiChatModel.BASE_URL)
.completionsPath(GeminiChatModel.COMPLETE_PATH)
.apiKey("AIzaSyAVoBxgoFvvte820vEQMma2LKBnC98bqMQ")
.build())
.defaultOptions(OpenAiChatOptions.builder()
.model(GeminiChatModel.MODEL_DEFAULT) // 模型
.temperature(0.7)
.build())
.build();
private final GeminiChatModel chatModel = new GeminiChatModel(openAiChatModel);
@Test
@Disabled
public void testCall() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
ChatResponse response = chatModel.call(new Prompt(messages));
// 打印结果
System.out.println(response);
}
@Test
@Disabled
public void testStream() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
// 打印结果
flux.doOnNext(System.out::println).then().block();
}
}

View File

@@ -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));
}
}

View File

@@ -1,61 +0,0 @@
package cn.iocoder.yudao.module.iot.api.device;
import cn.iocoder.yudao.framework.common.enums.RpcConstants;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
import org.springframework.context.annotation.Primary;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.annotation.security.PermitAll;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
/**
* IoT 设备 API 实现类
*
* @author haohao
*/
@RestController
@Validated
@Primary // 保证优先匹配,因为 yudao-iot-gateway 也有 IotDeviceCommonApi 的实现,并且也可能会被 biz 引入
public class IoTDeviceApiImpl implements IotDeviceCommonApi {
@Resource
private IotDeviceService deviceService;
@Resource
private IotProductService productService;
@Override
@PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/auth")
@PermitAll
public CommonResult<Boolean> authDevice(@RequestBody IotDeviceAuthReqDTO authReqDTO) {
return success(deviceService.authDevice(authReqDTO));
}
@Override
@PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/get") // 特殊:方便调用,暂时使用 POST实际更推荐 GET
@PermitAll
public CommonResult<IotDeviceRespDTO> getDevice(@RequestBody IotDeviceGetReqDTO getReqDTO) {
IotDeviceDO device = getReqDTO.getId() != null ? deviceService.getDeviceFromCache(getReqDTO.getId())
: deviceService.getDeviceFromCache(getReqDTO.getProductKey(), getReqDTO.getDeviceName());
return success(BeanUtils.toBean(device, IotDeviceRespDTO.class, deviceDTO -> {
IotProductDO product = productService.getProductFromCache(deviceDTO.getProductId());
if (product != null) {
deviceDTO.setCodecType(product.getCodecType());
}
}));
}
}

View File

@@ -1,104 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.alert;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config.IotAlertConfigPageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config.IotAlertConfigRespVO;
import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config.IotAlertConfigSaveReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertConfigDO;
import cn.iocoder.yudao.module.iot.service.alert.IotAlertConfigService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
@Tag(name = "管理后台 - IoT 告警配置")
@RestController
@RequestMapping("/iot/alert-config")
@Validated
public class IotAlertConfigController {
@Resource
private IotAlertConfigService alertConfigService;
@Resource
private AdminUserApi adminUserApi;
@PostMapping("/create")
@Operation(summary = "创建告警配置")
@PreAuthorize("@ss.hasPermission('iot:alert-config:create')")
public CommonResult<Long> createAlertConfig(@Valid @RequestBody IotAlertConfigSaveReqVO createReqVO) {
return success(alertConfigService.createAlertConfig(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新告警配置")
@PreAuthorize("@ss.hasPermission('iot:alert-config:update')")
public CommonResult<Boolean> updateAlertConfig(@Valid @RequestBody IotAlertConfigSaveReqVO updateReqVO) {
alertConfigService.updateAlertConfig(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除告警配置")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('iot:alert-config:delete')")
public CommonResult<Boolean> deleteAlertConfig(@RequestParam("id") Long id) {
alertConfigService.deleteAlertConfig(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得告警配置")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('iot:alert-config:query')")
public CommonResult<IotAlertConfigRespVO> getAlertConfig(@RequestParam("id") Long id) {
IotAlertConfigDO alertConfig = alertConfigService.getAlertConfig(id);
return success(BeanUtils.toBean(alertConfig, IotAlertConfigRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得告警配置分页")
@PreAuthorize("@ss.hasPermission('iot:alert-config:query')")
public CommonResult<PageResult<IotAlertConfigRespVO>> getAlertConfigPage(@Valid IotAlertConfigPageReqVO pageReqVO) {
PageResult<IotAlertConfigDO> pageResult = alertConfigService.getAlertConfigPage(pageReqVO);
// 转换返回
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
convertSetByFlatMap(pageResult.getList(), config -> config.getReceiveUserIds().stream()));
return success(BeanUtils.toBean(pageResult, IotAlertConfigRespVO.class, vo -> {
vo.setReceiveUserNames(vo.getReceiveUserIds().stream()
.map(userMap::get)
.filter(Objects::nonNull)
.map(AdminUserRespDTO::getNickname)
.collect(Collectors.toList()));
}));
}
@GetMapping("/simple-list")
@Operation(summary = "获得告警配置简单列表", description = "只包含被开启的告警配置,主要用于前端的下拉选项")
@PreAuthorize("@ss.hasPermission('iot:alert-config:query')")
public CommonResult<List<IotAlertConfigRespVO>> getAlertConfigSimpleList() {
List<IotAlertConfigDO> list = alertConfigService.getAlertConfigListByStatus(CommonStatusEnum.ENABLE.getStatus());
return success(convertList(list, config -> // 只返回 id、name 字段
new IotAlertConfigRespVO().setId(config.getId()).setName(config.getName())));
}
}

View File

@@ -1,58 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.alert;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.recrod.IotAlertRecordPageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.recrod.IotAlertRecordProcessReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.recrod.IotAlertRecordRespVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertRecordDO;
import cn.iocoder.yudao.module.iot.service.alert.IotAlertRecordService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static java.util.Collections.singleton;
@Tag(name = "管理后台 - IoT 告警记录")
@RestController
@RequestMapping("/iot/alert-record")
@Validated
public class IotAlertRecordController {
@Resource
private IotAlertRecordService alertRecordService;
@GetMapping("/get")
@Operation(summary = "获得告警记录")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('iot:alert-record:query')")
public CommonResult<IotAlertRecordRespVO> getAlertRecord(@RequestParam("id") Long id) {
IotAlertRecordDO alertRecord = alertRecordService.getAlertRecord(id);
return success(BeanUtils.toBean(alertRecord, IotAlertRecordRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得告警记录分页")
@PreAuthorize("@ss.hasPermission('iot:alert-record:query')")
public CommonResult<PageResult<IotAlertRecordRespVO>> getAlertRecordPage(@Valid IotAlertRecordPageReqVO pageReqVO) {
PageResult<IotAlertRecordDO> pageResult = alertRecordService.getAlertRecordPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, IotAlertRecordRespVO.class));
}
@PutMapping("/process")
@Operation(summary = "处理告警记录")
@PreAuthorize("@ss.hasPermission('iot:alert-record:process')")
public CommonResult<Boolean> processAlertRecord(@Valid @RequestBody IotAlertRecordProcessReqVO processReqVO) {
alertRecordService.processAlertRecordList(singleton(processReqVO.getId()), processReqVO.getProcessRemark());
return success(true);
}
}

View File

@@ -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;
}

View File

@@ -1,43 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - IoT 告警配置 Response VO")
@Data
public class IotAlertConfigRespVO {
@Schema(description = "配置编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3566")
private Long id;
@Schema(description = "配置名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
private String name;
@Schema(description = "配置描述", example = "你猜")
private String description;
@Schema(description = "告警级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer level;
@Schema(description = "配置状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer status;
@Schema(description = "关联的场景联动规则编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3")
private List<Long> sceneRuleIds;
@Schema(description = "接收的用户编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "100,200")
private List<Long> receiveUserIds;
@Schema(description = "接收的用户名称数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三,李四")
private List<String> receiveUserNames;
@Schema(description = "接收的类型数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3")
private List<Integer> receiveTypes;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@@ -1,47 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
@Schema(description = "管理后台 - IoT 告警配置新增/修改 Request VO")
@Data
public class IotAlertConfigSaveReqVO {
@Schema(description = "配置编号", example = "3566")
private Long id;
@Schema(description = "配置名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
@NotEmpty(message = "配置名称不能为空")
private String name;
@Schema(description = "配置描述", example = "你猜")
private String description;
@Schema(description = "告警级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "告警级别不能为空")
private Integer level;
@Schema(description = "配置状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "配置状态不能为空")
@InEnum(CommonStatusEnum.class)
private Integer status;
@Schema(description = "关联的场景联动规则编号数组")
@NotEmpty(message = "关联的场景联动规则编号数组不能为空")
private List<Long> sceneRuleIds;
@Schema(description = "接收的用户编号数组")
@NotEmpty(message = "接收的用户编号数组不能为空")
private List<Long> receiveUserIds;
@Schema(description = "接收的类型数组")
@NotEmpty(message = "接收的类型数组不能为空")
private List<Integer> receiveTypes;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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}}

View File

@@ -1,92 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.device;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceMessagePageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceMessageRespPairVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceMessageRespVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceMessageSendReqVO;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO;
import cn.iocoder.yudao.module.iot.dal.tdengine.IotDeviceMessageMapper;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import cn.iocoder.yudao.module.iot.service.device.message.IotDeviceMessageService;
import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
@Tag(name = "管理后台 - IoT 设备消息")
@RestController
@RequestMapping("/iot/device/message")
@Validated
public class IotDeviceMessageController {
@Resource
private IotDeviceMessageService deviceMessageService;
@Resource
private IotDeviceService deviceService;
@Resource
private IotThingModelService thingModelService;
@Resource
private IotDeviceMessageMapper deviceMessageMapper;
@GetMapping("/page")
@Operation(summary = "获得设备消息分页")
@PreAuthorize("@ss.hasPermission('iot:device:message-query')")
public CommonResult<PageResult<IotDeviceMessageRespVO>> getDeviceMessagePage(
@Valid IotDeviceMessagePageReqVO pageReqVO) {
PageResult<IotDeviceMessageDO> pageResult = deviceMessageService.getDeviceMessagePage(pageReqVO);
return success(BeanUtils.toBean(pageResult, IotDeviceMessageRespVO.class));
}
@GetMapping("/pair-page")
@Operation(summary = "获得设备消息对分页")
@PreAuthorize("@ss.hasPermission('iot:device:message-query')")
public CommonResult<PageResult<IotDeviceMessageRespPairVO>> getDeviceMessagePairPage(
@Valid IotDeviceMessagePageReqVO pageReqVO) {
// 1.1 先按照条件,查询 request 的消息(非 reply
pageReqVO.setReply(false);
PageResult<IotDeviceMessageDO> requestMessagePageResult = deviceMessageService.getDeviceMessagePage(pageReqVO);
if (CollUtil.isEmpty(requestMessagePageResult.getList())) {
return success(PageResult.empty());
}
// 1.2 接着按照 requestIds批量查询 reply 消息
List<String> requestIds = convertList(requestMessagePageResult.getList(), IotDeviceMessageDO::getRequestId);
List<IotDeviceMessageDO> replyMessageList = deviceMessageService.getDeviceMessageListByRequestIdsAndReply(
pageReqVO.getDeviceId(), requestIds, true);
Map<String, IotDeviceMessageDO> replyMessages = convertMap(replyMessageList, IotDeviceMessageDO::getRequestId);
// 2. 组装结果
List<IotDeviceMessageRespPairVO> pairMessages = convertList(requestMessagePageResult.getList(),
requestMessage -> {
IotDeviceMessageDO replyMessage = replyMessages.get(requestMessage.getRequestId());
return new IotDeviceMessageRespPairVO()
.setRequest(BeanUtils.toBean(requestMessage, IotDeviceMessageRespVO.class))
.setReply(BeanUtils.toBean(replyMessage, IotDeviceMessageRespVO.class));
});
return success(new PageResult<>(pairMessages, requestMessagePageResult.getTotal()));
}
@PostMapping("/send")
@Operation(summary = "发送消息", description = "可用于设备模拟")
@PreAuthorize("@ss.hasPermission('iot:device:message-end')")
public CommonResult<Boolean> sendDeviceMessage(@Valid @RequestBody IotDeviceMessageSendReqVO sendReqVO) {
deviceMessageService.sendDeviceMessage(BeanUtils.toBean(sendReqVO, IotDeviceMessage.class));
return success(true);
}
}

View File

@@ -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;
}

View File

@@ -1,22 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import java.util.List;
@Schema(description = "管理后台 - 通过产品标识和设备名称列表获取设备 Request VO")
@Data
public class IotDeviceByProductKeyAndNamesReqVO {
@Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "1de24640dfe")
@NotBlank(message = "产品标识不能为空")
private String productKey;
@Schema(description = "设备名称列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "device001,device002")
@NotEmpty(message = "设备名称列表不能为空")
private List<String> deviceNames;
}

View File

@@ -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;
}

View File

@@ -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 配对
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -1,25 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.property;
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType.ThingModelDataSpecs;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - IoT 设备属性详细 Response VO") // 额外增加 来自 ThingModelProperty 的变量 属性
@Data
public class IotDevicePropertyDetailRespVO extends IotDevicePropertyRespVO {
@Schema(description = "属性名称", requiredMode = Schema.RequiredMode.REQUIRED)
private String name;
@Schema(description = "数据类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "int")
private String dataType;
@Schema(description = "数据定义")
private ThingModelDataSpecs dataSpecs;
@Schema(description = "数据定义列表")
private List<ThingModelDataSpecs> dataSpecsList;
}

View File

@@ -1,65 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.ota;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.IotOtaTaskCreateReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.IotOtaTaskPageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.IotOtaTaskRespVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskDO;
import cn.iocoder.yudao.module.iot.service.ota.IotOtaTaskService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - IoT OTA 升级任务")
@RestController
@RequestMapping("/iot/ota/task")
@Validated
public class IotOtaTaskController {
@Resource
private IotOtaTaskService otaTaskService;
@PostMapping("/create")
@Operation(summary = "创建 OTA 升级任务")
@PreAuthorize(value = "@ss.hasPermission('iot:ota-task:create')")
public CommonResult<Long> createOtaTask(@Valid @RequestBody IotOtaTaskCreateReqVO createReqVO) {
return success(otaTaskService.createOtaTask(createReqVO));
}
@PostMapping("/cancel")
@Operation(summary = "取消 OTA 升级任务")
@Parameter(name = "id", description = "升级任务编号", required = true)
@PreAuthorize(value = "@ss.hasPermission('iot:ota-task:cancel')")
public CommonResult<Boolean> cancelOtaTask(@RequestParam("id") Long id) {
otaTaskService.cancelOtaTask(id);
return success(true);
}
@GetMapping("/page")
@Operation(summary = "获得 OTA 升级任务分页")
@PreAuthorize(value = "@ss.hasPermission('iot:ota-task:query')")
public CommonResult<PageResult<IotOtaTaskRespVO>> getOtaTaskPage(@Valid IotOtaTaskPageReqVO pageReqVO) {
PageResult<IotOtaTaskDO> pageResult = otaTaskService.getOtaTaskPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, IotOtaTaskRespVO.class));
}
@GetMapping("/get")
@Operation(summary = "获得 OTA 升级任务")
@Parameter(name = "id", description = "升级任务编号", required = true, example = "1024")
@PreAuthorize(value = "@ss.hasPermission('iot:ota-task:query')")
public CommonResult<IotOtaTaskRespVO> getOtaTask(@RequestParam("id") Long id) {
IotOtaTaskDO upgradeTask = otaTaskService.getOtaTask(id);
return success(BeanUtils.toBean(upgradeTask, IotOtaTaskRespVO.class));
}
}

View File

@@ -1,99 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.ota;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record.IotOtaTaskRecordPageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record.IotOtaTaskRecordRespVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import cn.iocoder.yudao.module.iot.service.ota.IotOtaFirmwareService;
import cn.iocoder.yudao.module.iot.service.ota.IotOtaTaskRecordService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
@Tag(name = "管理后台 - IoT OTA 升级任务记录")
@RestController
@RequestMapping("/iot/ota/task/record")
@Validated
public class IotOtaTaskRecordController {
@Resource
private IotOtaTaskRecordService otaTaskRecordService;
@Resource
private IotDeviceService deviceService;
@Resource
private IotOtaFirmwareService otaFirmwareService;
@GetMapping("/get-status-statistics")
@Operation(summary = "获得 OTA 升级记录状态统计")
@Parameters({
@Parameter(name = "firmwareId", description = "固件编号", example = "1024"),
@Parameter(name = "taskId", description = "升级任务编号", example = "2048")
})
@PreAuthorize("@ss.hasPermission('iot:ota-task-record:query')")
public CommonResult<Map<Integer, Long>> getOtaTaskRecordStatusStatistics(
@RequestParam(value = "firmwareId", required = false) Long firmwareId,
@RequestParam(value = "taskId", required = false) Long taskId) {
return success(otaTaskRecordService.getOtaTaskRecordStatusStatistics(firmwareId, taskId));
}
@GetMapping("/page")
@Operation(summary = "获得 OTA 升级记录分页")
@PreAuthorize("@ss.hasPermission('iot:ota-task-record:query')")
public CommonResult<PageResult<IotOtaTaskRecordRespVO>> getOtaTaskRecordPage(
@Valid IotOtaTaskRecordPageReqVO pageReqVO) {
PageResult<IotOtaTaskRecordDO> pageResult = otaTaskRecordService.getOtaTaskRecordPage(pageReqVO);
if (CollUtil.isEmpty(pageResult.getList())) {
return success(PageResult.empty());
}
// 批量查询固件信息
Map<Long, IotOtaFirmwareDO> firmwareMap = otaFirmwareService.getOtaFirmwareMap(
convertSet(pageResult.getList(), IotOtaTaskRecordDO::getFromFirmwareId));
Map<Long, IotDeviceDO> deviceMap = deviceService.getDeviceMap(
convertSet(pageResult.getList(), IotOtaTaskRecordDO::getDeviceId));
// 转换为响应 VO
return success(BeanUtils.toBean(pageResult, IotOtaTaskRecordRespVO.class, (vo) -> {
MapUtils.findAndThen(firmwareMap, vo.getFromFirmwareId(), firmware ->
vo.setFromFirmwareVersion(firmware.getVersion()));
MapUtils.findAndThen(deviceMap, vo.getDeviceId(), device ->
vo.setDeviceName(device.getDeviceName()));
}));
}
@GetMapping("/get")
@Operation(summary = "获得 OTA 升级记录")
@PreAuthorize("@ss.hasPermission('iot:ota-task-record:query')")
@Parameter(name = "id", description = "升级记录编号", required = true, example = "1024")
public CommonResult<IotOtaTaskRecordRespVO> getOtaTaskRecord(@RequestParam("id") Long id) {
IotOtaTaskRecordDO upgradeRecord = otaTaskRecordService.getOtaTaskRecord(id);
return success(BeanUtils.toBean(upgradeRecord, IotOtaTaskRecordRespVO.class));
}
@PutMapping("/cancel")
@Operation(summary = "取消 OTA 升级记录")
@PreAuthorize("@ss.hasPermission('iot:ota-task-record:cancel')")
@Parameter(name = "id", description = "升级记录编号", required = true, example = "1024")
public CommonResult<Boolean> cancelOtaTaskRecord(@RequestParam("id") Long id) {
otaTaskRecordService.cancelOtaTaskRecord(id);
return success(true);
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -1,73 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.rule;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule.IotDataRulePageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule.IotDataRuleRespVO;
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule.IotDataRuleSaveReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataRuleDO;
import cn.iocoder.yudao.module.iot.service.rule.data.IotDataRuleService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - IoT 数据流转规则")
@RestController
@RequestMapping("/iot/data-rule")
@Validated
public class IotDataRuleController {
@Resource
private IotDataRuleService dataRuleService;
@PostMapping("/create")
@Operation(summary = "创建数据流转规则")
@PreAuthorize("@ss.hasPermission('iot:data-rule:create')")
public CommonResult<Long> createDataRule(@Valid @RequestBody IotDataRuleSaveReqVO createReqVO) {
return success(dataRuleService.createDataRule(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新数据流转规则")
@PreAuthorize("@ss.hasPermission('iot:data-rule:update')")
public CommonResult<Boolean> updateDataRule(@Valid @RequestBody IotDataRuleSaveReqVO updateReqVO) {
dataRuleService.updateDataRule(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除数据流转规则")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('iot:data-rule:delete')")
public CommonResult<Boolean> deleteDataRule(@RequestParam("id") Long id) {
dataRuleService.deleteDataRule(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得数据流转规则")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('iot:data-rule:query')")
public CommonResult<IotDataRuleRespVO> getDataRule(@RequestParam("id") Long id) {
IotDataRuleDO dataRule = dataRuleService.getDataRule(id);
return success(BeanUtils.toBean(dataRule, IotDataRuleRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得数据流转规则分页")
@PreAuthorize("@ss.hasPermission('iot:data-rule:query')")
public CommonResult<PageResult<IotDataRuleRespVO>> getDataRulePage(@Valid IotDataRulePageReqVO pageReqVO) {
PageResult<IotDataRuleDO> pageResult = dataRuleService.getDataRulePage(pageReqVO);
return success(BeanUtils.toBean(pageResult, IotDataRuleRespVO.class));
}
}

View File

@@ -1,84 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.rule;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.sink.IotDataSinkPageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.sink.IotDataSinkRespVO;
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.sink.IotDataSinkSaveReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataSinkDO;
import cn.iocoder.yudao.module.iot.service.rule.data.IotDataSinkService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@Tag(name = "管理后台 - IoT 数据流转目的")
@RestController
@RequestMapping("/iot/data-sink")
@Validated
public class IotDataSinkController {
@Resource
private IotDataSinkService dataSinkService;
@PostMapping("/create")
@Operation(summary = "创建数据目的")
@PreAuthorize("@ss.hasPermission('iot:data-sink:create')")
public CommonResult<Long> createDataSink(@Valid @RequestBody IotDataSinkSaveReqVO createReqVO) {
return success(dataSinkService.createDataSink(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新数据目的")
@PreAuthorize("@ss.hasPermission('iot:data-sink:update')")
public CommonResult<Boolean> updateDataSink(@Valid @RequestBody IotDataSinkSaveReqVO updateReqVO) {
dataSinkService.updateDataSink(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除数据目的")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('iot:data-sink:delete')")
public CommonResult<Boolean> deleteDataSink(@RequestParam("id") Long id) {
dataSinkService.deleteDataSink(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得数据目的")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('iot:data-sink:query')")
public CommonResult<IotDataSinkRespVO> getDataSink(@RequestParam("id") Long id) {
IotDataSinkDO sink = dataSinkService.getDataSink(id);
return success(BeanUtils.toBean(sink, IotDataSinkRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得数据目的分页")
@PreAuthorize("@ss.hasPermission('iot:data-sink:query')")
public CommonResult<PageResult<IotDataSinkRespVO>> getDataSinkPage(@Valid IotDataSinkPageReqVO pageReqVO) {
PageResult<IotDataSinkDO> pageResult = dataSinkService.getDataSinkPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, IotDataSinkRespVO.class));
}
@GetMapping("/simple-list")
@Operation(summary = "获取数据目的的精简信息列表", description = "主要用于前端的下拉选项")
public CommonResult<List<IotDataSinkRespVO>> getDataSinkSimpleList() {
List<IotDataSinkDO> list = dataSinkService.getDataSinkListByStatus(CommonStatusEnum.ENABLE.getStatus());
return success(convertList(list, sink -> // 只返回 id、name 字段
new IotDataSinkRespVO().setId(sink.getId()).setName(sink.getName())));
}
}

View File

@@ -1,93 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.rule;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRulePageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleRespVO;
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleSaveReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleUpdateStatusReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
import cn.iocoder.yudao.module.iot.service.rule.scene.IotSceneRuleService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@Tag(name = "管理后台 - IoT 场景联动")
@RestController
@RequestMapping("/iot/scene-rule")
@Validated
public class IotSceneRuleController {
@Resource
private IotSceneRuleService sceneRuleService;
@PostMapping("/create")
@Operation(summary = "创建场景联动")
@PreAuthorize("@ss.hasPermission('iot:scene-rule:create')")
public CommonResult<Long> createSceneRule(@Valid @RequestBody IotSceneRuleSaveReqVO createReqVO) {
return success(sceneRuleService.createSceneRule(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新场景联动")
@PreAuthorize("@ss.hasPermission('iot:scene-rule:update')")
public CommonResult<Boolean> updateSceneRule(@Valid @RequestBody IotSceneRuleSaveReqVO updateReqVO) {
sceneRuleService.updateSceneRule(updateReqVO);
return success(true);
}
@PutMapping("/update-status")
@Operation(summary = "更新场景联动状态")
@PreAuthorize("@ss.hasPermission('iot:scene-rule:update')")
public CommonResult<Boolean> updateSceneRuleStatus(@Valid @RequestBody IotSceneRuleUpdateStatusReqVO updateReqVO) {
sceneRuleService.updateSceneRuleStatus(updateReqVO.getId(), updateReqVO.getStatus());
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除场景联动")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('iot:scene-rule:delete')")
public CommonResult<Boolean> deleteSceneRule(@RequestParam("id") Long id) {
sceneRuleService.deleteSceneRule(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得场景联动")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('iot:scene-rule:query')")
public CommonResult<IotSceneRuleRespVO> getSceneRule(@RequestParam("id") Long id) {
IotSceneRuleDO sceneRule = sceneRuleService.getSceneRule(id);
return success(BeanUtils.toBean(sceneRule, IotSceneRuleRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得场景联动分页")
@PreAuthorize("@ss.hasPermission('iot:scene-rule:query')")
public CommonResult<PageResult<IotSceneRuleRespVO>> getSceneRulePage(@Valid IotSceneRulePageReqVO pageReqVO) {
PageResult<IotSceneRuleDO> pageResult = sceneRuleService.getSceneRulePage(pageReqVO);
return success(BeanUtils.toBean(pageResult, IotSceneRuleRespVO.class));
}
@GetMapping("/simple-list")
@Operation(summary = "获取场景联动的精简信息列表", description = "主要用于前端的下拉选项")
public CommonResult<List<IotSceneRuleRespVO>> getSceneRuleSimpleList() {
List<IotSceneRuleDO> list = sceneRuleService.getSceneRuleListByStatus(CommonStatusEnum.ENABLE.getStatus());
return success(convertList(list, scene -> // 只返回 id、name 字段
new IotSceneRuleRespVO().setId(scene.getId()).setName(scene.getName())));
}
}

View File

@@ -1 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data;

View File

@@ -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;
}

View File

@@ -1,35 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataRuleDO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - IoT 数据流转规则 Response VO")
@Data
public class IotDataRuleRespVO {
@Schema(description = "数据流转规则编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "8540")
private Long id;
@Schema(description = "数据流转规则名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
private String name;
@Schema(description = "数据流转规则描述", example = "你猜")
private String description;
@Schema(description = "数据流转规则状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer status;
@Schema(description = "数据源配置数组", requiredMode = Schema.RequiredMode.REQUIRED)
private List<IotDataRuleDO.SourceConfig> sourceConfigs;
@Schema(description = "数据目的编号数组", requiredMode = Schema.RequiredMode.REQUIRED)
private List<Long> sinkIds;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@@ -1,40 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataRuleDO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
@Schema(description = "管理后台 - IoT 数据流转规则新增/修改 Request VO")
@Data
public class IotDataRuleSaveReqVO {
@Schema(description = "数据流转规则编号", example = "8540")
private Long id;
@Schema(description = "数据流转规则名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
@NotEmpty(message = "数据流转规则名称不能为空")
private String name;
@Schema(description = "数据流转规则描述", example = "你猜")
private String description;
@Schema(description = "数据流转规则状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "数据流转规则状态不能为空")
@InEnum(CommonStatusEnum.class)
private Integer status;
@Schema(description = "数据源配置数组", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "数据源配置数组不能为空")
private List<IotDataRuleDO.SourceConfig> sourceConfigs;
@Schema(description = "数据目的编号数组", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "数据目的编号数组不能为空")
private List<Long> sinkIds;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -1,35 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - IoT 场景联动 Response VO")
@Data
public class IotSceneRuleRespVO {
@Schema(description = "场景编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15865")
private Long id;
@Schema(description = "场景名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
private String name;
@Schema(description = "场景描述", example = "你猜")
private String description;
@Schema(description = "场景状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer status;
@Schema(description = "触发器数组", requiredMode = Schema.RequiredMode.REQUIRED)
private List<IotSceneRuleDO.Trigger> triggers;
@Schema(description = "执行器数组", requiredMode = Schema.RequiredMode.REQUIRED)
private List<IotSceneRuleDO.Action> actions;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@@ -1,40 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
@Schema(description = "管理后台 - IoT 场景联动新增/修改 Request VO")
@Data
public class IotSceneRuleSaveReqVO {
@Schema(description = "场景编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15865")
private Long id;
@Schema(description = "场景名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
@NotEmpty(message = "场景名称不能为空")
private String name;
@Schema(description = "场景描述", example = "你猜")
private String description;
@Schema(description = "场景状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@NotNull(message = "场景状态不能为空")
@InEnum(CommonStatusEnum.class)
private Integer status;
@Schema(description = "触发器数组", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "触发器数组不能为空")
private List<IotSceneRuleDO.Trigger> triggers;
@Schema(description = "执行器数组", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "执行器数组不能为空")
private List<IotSceneRuleDO.Action> actions;
}

View File

@@ -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;
}

View File

@@ -1,11 +0,0 @@
### 请求 /iot/statistics/get-device-message-summary-by-date 接口(小时)
GET {{baseUrl}}/iot/statistics/get-device-message-summary-by-date?interval=0&times[0]=2025-06-13 00:00:00&times[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&times[0]=2025-06-13 00:00:00&times[1]=2025-06-14 23:59:59
Content-Type: application/json
tenant-id: {{adminTenantId}}
Authorization: Bearer {{token}}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -1,30 +0,0 @@
package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo;
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelEvent;
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelProperty;
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelService;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - IoT 产品物模型 TSL Response VO")
@Data
public class IotThingModelTSLRespVO {
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long productId;
@Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "temperature_sensor")
private String productKey;
@Schema(description = "属性列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<ThingModelProperty> properties;
@Schema(description = "服务列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<ThingModelEvent> events;
@Schema(description = "事件列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<ThingModelService> services;
}

View File

@@ -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;
}

View File

@@ -1,70 +0,0 @@
package cn.iocoder.yudao.module.iot.dal.dataobject.ota;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskDeviceScopeEnum;
import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskStatusEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* IoT OTA 升级任务 DO
*
* @author 芋道源码
*/
@TableName(value = "iot_ota_task", autoResultMap = true)
@KeySequence("iot_ota_task_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class IotOtaTaskDO extends BaseDO {
/**
* 任务编号
*/
@TableId
private Long id;
/**
* 任务名称
*/
private String name;
/**
* 任务描述
*/
private String description;
/**
* 固件编号
* <p>
* 关联 {@link IotOtaFirmwareDO#getId()}
*/
private Long firmwareId;
/**
* 任务状态
* <p>
* 关联 {@link IotOtaTaskStatusEnum}
*/
private Integer status;
/**
* 设备升级范围
* <p>
* 关联 {@link IotOtaTaskDeviceScopeEnum}
*/
private Integer deviceScope;
/**
* 设备总数数量
*/
private Integer deviceTotalCount;
/**
* 设备成功数量
*/
private Integer deviceSuccessCount;
}

View File

@@ -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;
}

View File

@@ -1,109 +0,0 @@
package cn.iocoder.yudao.module.iot.dal.dataobject.rule;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotEmpty;
import java.util.List;
/**
* IoT 数据流转规则 DO
*
* 监听 {@link SourceConfig} 数据源,转发到 {@link IotDataSinkDO} 数据目的
*
* @author 芋道源码
*/
@TableName(value = "iot_data_rule", autoResultMap = true)
@KeySequence("iot_data_rule_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class IotDataRuleDO extends BaseDO {
/**
* 数据流转规格编号
*/
private Long id;
/**
* 数据流转规格名称
*/
private String name;
/**
* 数据流转规格描述
*/
private String description;
/**
* 数据流转规格状态
*
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;
/**
* 数据源配置数组
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private List<SourceConfig> sourceConfigs;
/**
* 数据目的编号数组
*
* 关联 {@link IotDataSinkDO#getId()}
*/
@TableField(typeHandler = LongListTypeHandler.class)
private List<Long> sinkIds;
// TODO @芋艿:未来考虑使用 groovy支持数据处理
/**
* 数据源配置
*/
@Data
public static class SourceConfig {
/**
* 消息方法
*
* 枚举 {@link IotDeviceMessageMethodEnum} 中的 upstream 上行部分
*/
@NotEmpty(message = "消息方法不能为空")
private String method;
/**
* 产品编号
*
* 关联 {@link IotProductDO#getId()}
*/
private Long productId;
/**
* 设备编号
*
* 关联 {@link IotDeviceDO#getId()}
* 特殊:如果为 {@link IotDeviceDO#DEVICE_ID_ALL} 时,则是全部设备
*/
@NotEmpty(message = "设备编号不能为空")
private Long deviceId;
/**
* 标识符
*
* 1. 物模型时,对应:{@link IotThingModelDO#getIdentifier()}
*/
private String identifier;
}
}

View File

@@ -1,245 +0,0 @@
package cn.iocoder.yudao.module.iot.dal.dataobject.rule;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertConfigDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleActionTypeEnum;
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum;
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* IoT 场景联动规则 DO
*
* @author 芋道源码
*/
@TableName(value = "iot_scene_rule", autoResultMap = true)
@KeySequence("iot_scene_rule_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class IotSceneRuleDO extends TenantBaseDO {
/**
* 场景联动编号
*/
@TableId
private Long id;
/**
* 场景联动名称
*/
private String name;
/**
* 场景联动描述
*/
private String description;
/**
* 场景联动状态
*
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;
/**
* 场景定义配置
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private List<Trigger> triggers;
/**
* 场景动作配置
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private List<Action> actions;
/**
* 场景定义配置
*/
@Data
public static class Trigger {
// ========== 事件部分 ==========
/**
* 场景事件类型
*
* 枚举 {@link IotSceneRuleTriggerTypeEnum}
* 1. {@link IotSceneRuleTriggerTypeEnum#DEVICE_STATE_UPDATE} 时operator 非空,并且 value 为在线状态
* 2. {@link IotSceneRuleTriggerTypeEnum#DEVICE_PROPERTY_POST}
* {@link IotSceneRuleTriggerTypeEnum#DEVICE_EVENT_POST} 时identifier、operator 非空,并且 value 为属性值
* 3. {@link IotSceneRuleTriggerTypeEnum#DEVICE_EVENT_POST}
* {@link IotSceneRuleTriggerTypeEnum#DEVICE_SERVICE_INVOKE} 时identifier 非空,但是 operator、value 为空
* 4. {@link IotSceneRuleTriggerTypeEnum#TIMER} 时conditions 非空,并且设备无关(无需 productId、deviceId 字段)
*/
private Integer type;
/**
* 产品编号
*
* 关联 {@link IotProductDO#getId()}
*/
private Long productId;
/**
* 设备编号
*
* 关联 {@link IotDeviceDO#getId()}
* 特殊:如果为 {@link IotDeviceDO#DEVICE_ID_ALL} 时,则是全部设备
*/
private Long deviceId;
/**
* 物模型标识符
*
* 对应:{@link IotThingModelDO#getIdentifier()}
*/
private String identifier;
/**
* 操作符
*
* 枚举 {@link IotSceneRuleConditionOperatorEnum}
*/
private String operator;
/**
* 参数(属性值、在线状态)
* <p>
* 如果有多个值,则使用 "," 分隔,类似 "1,2,3"。
* 例如说,{@link IotSceneRuleConditionOperatorEnum#IN}、{@link IotSceneRuleConditionOperatorEnum#BETWEEN}
*/
private String value;
/**
* CRON 表达式
*/
private String cronExpression;
// ========== 条件部分 ==========
/**
* 触发条件分组(状态条件分组)的数组
* <p>
* 第一层 List分组与分组之间是“或”的关系
* 第二层 List条件与条件之间是“且”的关系
*/
private List<List<TriggerCondition>> conditionGroups;
}
/**
* 触发条件(状态条件)
*/
@Data
public static class TriggerCondition {
/**
* 触发条件类型
*
* 枚举 {@link IotSceneRuleConditionTypeEnum}
* 1. {@link IotSceneRuleConditionTypeEnum#DEVICE_STATE} 时operator 非空,并且 value 为在线状态
* 2. {@link IotSceneRuleConditionTypeEnum#DEVICE_PROPERTY} 时identifier、operator 非空,并且 value 为属性值
* 3. {@link IotSceneRuleConditionTypeEnum#CURRENT_TIME} 时operator 非空(使用 DATE_TIME_ 和 TIME_ 部分),并且 value 非空
*/
private Integer type;
/**
* 产品编号
*
* 关联 {@link IotProductDO#getId()}
*/
private Long productId;
/**
* 设备编号
*
* 关联 {@link IotDeviceDO#getId()}
*/
private Long deviceId;
/**
* 标识符(属性)
*
* 关联 {@link IotThingModelDO#getIdentifier()}
*/
private String identifier;
/**
* 操作符
*
* 枚举 {@link IotSceneRuleConditionOperatorEnum}
*/
private String operator;
/**
* 参数
*
* 如果有多个值,则使用 "," 分隔,类似 "1,2,3"。
* 例如说,{@link IotSceneRuleConditionOperatorEnum#IN}、{@link IotSceneRuleConditionOperatorEnum#BETWEEN}
*/
private String param;
}
/**
* 场景动作配置
*/
@Data
public static class Action {
/**
* 执行类型
*
* 枚举 {@link IotSceneRuleActionTypeEnum}
* 1. {@link IotSceneRuleActionTypeEnum#DEVICE_PROPERTY_SET} 时params 非空
* {@link IotSceneRuleActionTypeEnum#DEVICE_SERVICE_INVOKE} 时params 非空
* 2. {@link IotSceneRuleActionTypeEnum#ALERT_TRIGGER} 时alertConfigId 为空,因为是 {@link IotAlertConfigDO} 里面关联它
* 3. {@link IotSceneRuleActionTypeEnum#ALERT_RECOVER} 时alertConfigId 非空
*/
private Integer type;
/**
* 产品编号
*
* 关联 {@link IotProductDO#getId()}
*/
private Long productId;
/**
* 设备编号
*
* 关联 {@link IotDeviceDO#getId()}
*/
private Long deviceId;
/**
* 标识符(服务)
* <p>
* 关联 {@link IotThingModelDO#getIdentifier()}
*/
private String identifier;
/**
* 请求参数
*
* 一般来说,对应 {@link IotDeviceMessage#getParams()} 请求参数
*/
private String params;
/**
* 告警配置编号
*
* 关联 {@link IotAlertConfigDO#getId()}
*/
private Long alertConfigId;
}
}

View File

@@ -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;
}

View File

@@ -1,64 +0,0 @@
package cn.iocoder.yudao.module.iot.dal.dataobject.rule.config;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.iot.enums.rule.IotRedisDataStructureEnum;
import lombok.Data;
/**
* IoT Redis 配置 {@link IotAbstractDataSinkConfig} 实现类
*
* @author HUIHUI
*/
@Data
public class IotDataSinkRedisConfig extends IotAbstractDataSinkConfig {
/**
* Redis 服务器地址
*/
private String host;
/**
* 端口
*/
private Integer port;
/**
* 密码
*/
private String password;
/**
* 数据库索引
*/
private Integer database;
/**
* Redis 数据结构类型
* <p>
* 枚举 {@link IotRedisDataStructureEnum}
*/
@InEnum(IotRedisDataStructureEnum.class)
private Integer dataStructure;
/**
* 主题/键名
* <p>
* 对于不同的数据结构:
* - Stream: 流的键名
* - Hash: Hash 的键名
* - List: 列表的键名
* - Set: 集合的键名
* - ZSet: 有序集合的键名
* - String: 字符串的键名
*/
private String topic;
/**
* Hash 字段名(仅当 dataStructure 为 HASH 时使用)
*/
private String hashField;
/**
* ZSet 分数字段(仅当 dataStructure 为 ZSET 时使用)
* 指定消息中哪个字段作为分数,如果不指定则使用当前时间戳
*/
private String scoreField;
}

View File

@@ -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;
}

View File

@@ -1,39 +0,0 @@
package cn.iocoder.yudao.module.iot.dal.mysql.alert;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config.IotAlertConfigPageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertConfigDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* IoT 告警配置 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface IotAlertConfigMapper extends BaseMapperX<IotAlertConfigDO> {
default PageResult<IotAlertConfigDO> selectPage(IotAlertConfigPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<IotAlertConfigDO>()
.likeIfPresent(IotAlertConfigDO::getName, reqVO.getName())
.eqIfPresent(IotAlertConfigDO::getStatus, reqVO.getStatus())
.betweenIfPresent(IotAlertConfigDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(IotAlertConfigDO::getId));
}
default List<IotAlertConfigDO> selectListByStatus(Integer status) {
return selectList(IotAlertConfigDO::getStatus, status);
}
default List<IotAlertConfigDO> selectListBySceneRuleIdAndStatus(Long sceneRuleId, Integer status) {
return selectList(new LambdaQueryWrapperX<IotAlertConfigDO>()
.eq(IotAlertConfigDO::getStatus, status)
.apply(MyBatisUtils.findInSet("scene_rule_id", sceneRuleId)));
}
}

View File

@@ -1,46 +0,0 @@
package cn.iocoder.yudao.module.iot.dal.mysql.alert;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.recrod.IotAlertRecordPageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertRecordDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
/**
* IoT 告警记录 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface IotAlertRecordMapper extends BaseMapperX<IotAlertRecordDO> {
default PageResult<IotAlertRecordDO> selectPage(IotAlertRecordPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<IotAlertRecordDO>()
.eqIfPresent(IotAlertRecordDO::getConfigId, reqVO.getConfigId())
.eqIfPresent(IotAlertRecordDO::getConfigLevel, reqVO.getLevel())
.eqIfPresent(IotAlertRecordDO::getProductId, reqVO.getProductId())
.eqIfPresent(IotAlertRecordDO::getDeviceId, reqVO.getDeviceId())
.eqIfPresent(IotAlertRecordDO::getProcessStatus, reqVO.getProcessStatus())
.betweenIfPresent(IotAlertRecordDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(IotAlertRecordDO::getId));
}
default List<IotAlertRecordDO> selectListBySceneRuleId(Long sceneRuleId, Long deviceId, Boolean processStatus) {
return selectList(new LambdaQueryWrapperX<IotAlertRecordDO>()
.eq(IotAlertRecordDO::getSceneRuleId, sceneRuleId)
.eqIfPresent(IotAlertRecordDO::getDeviceId, deviceId)
.eqIfPresent(IotAlertRecordDO::getProcessStatus, processStatus)
.orderByDesc(IotAlertRecordDO::getId));
}
default int updateList(Collection<Long> ids, IotAlertRecordDO updateObj) {
return update(updateObj, new LambdaUpdateWrapper<IotAlertRecordDO>()
.in(IotAlertRecordDO::getId, ids));
}
}

View File

@@ -1,32 +0,0 @@
package cn.iocoder.yudao.module.iot.dal.mysql.ota;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.IotOtaTaskPageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface IotOtaTaskMapper extends BaseMapperX<IotOtaTaskDO> {
default IotOtaTaskDO selectByFirmwareIdAndName(Long firmwareId, String name) {
return selectOne(IotOtaTaskDO::getFirmwareId, firmwareId,
IotOtaTaskDO::getName, name);
}
default PageResult<IotOtaTaskDO> selectPage(IotOtaTaskPageReqVO pageReqVO) {
return selectPage(pageReqVO, new LambdaQueryWrapperX<IotOtaTaskDO>()
.eqIfPresent(IotOtaTaskDO::getFirmwareId, pageReqVO.getFirmwareId())
.likeIfPresent(IotOtaTaskDO::getName, pageReqVO.getName())
.orderByDesc(IotOtaTaskDO::getId));
}
default int updateByIdAndStatus(Long id, Integer whereStatus, IotOtaTaskDO updateObj) {
return update(updateObj, new LambdaUpdateWrapper<IotOtaTaskDO>()
.eq(IotOtaTaskDO::getId, id)
.eq(IotOtaTaskDO::getStatus, whereStatus));
}
}

View File

@@ -1,80 +0,0 @@
package cn.iocoder.yudao.module.iot.dal.mysql.ota;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record.IotOtaTaskRecordPageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
import java.util.Set;
@Mapper
public interface IotOtaTaskRecordMapper extends BaseMapperX<IotOtaTaskRecordDO> {
default List<IotOtaTaskRecordDO> selectListByFirmwareIdAndTaskId(Long firmwareId, Long taskId) {
return selectList(new LambdaQueryWrapperX<IotOtaTaskRecordDO>()
.eqIfPresent(IotOtaTaskRecordDO::getFirmwareId, firmwareId)
.eqIfPresent(IotOtaTaskRecordDO::getTaskId, taskId)
.select(IotOtaTaskRecordDO::getDeviceId, IotOtaTaskRecordDO::getStatus));
}
default PageResult<IotOtaTaskRecordDO> selectPage(IotOtaTaskRecordPageReqVO pageReqVO) {
return selectPage(pageReqVO, new LambdaQueryWrapperX<IotOtaTaskRecordDO>()
.eqIfPresent(IotOtaTaskRecordDO::getTaskId, pageReqVO.getTaskId())
.eqIfPresent(IotOtaTaskRecordDO::getStatus, pageReqVO.getStatus()));
}
default List<IotOtaTaskRecordDO> selectListByTaskIdAndStatus(Long taskId, Collection<Integer> statuses) {
return selectList(new LambdaQueryWrapperX<IotOtaTaskRecordDO>()
.eq(IotOtaTaskRecordDO::getTaskId, taskId)
.in(IotOtaTaskRecordDO::getStatus, statuses));
}
default Long selectCountByTaskIdAndStatus(Long taskId, Collection<Integer> statuses) {
return selectCount(new LambdaQueryWrapperX<IotOtaTaskRecordDO>()
.eq(IotOtaTaskRecordDO::getTaskId, taskId)
.in(IotOtaTaskRecordDO::getStatus, statuses));
}
default int updateByIdAndStatus(Long id, Integer status,
IotOtaTaskRecordDO updateObj) {
return update(updateObj, new LambdaUpdateWrapper<IotOtaTaskRecordDO>()
.eq(IotOtaTaskRecordDO::getId, id)
.eq(IotOtaTaskRecordDO::getStatus, status));
}
default int updateByIdAndStatus(Long id, Collection<Integer> whereStatuses,
IotOtaTaskRecordDO updateObj) {
return update(updateObj, new LambdaUpdateWrapper<IotOtaTaskRecordDO>()
.eq(IotOtaTaskRecordDO::getId, id)
.in(IotOtaTaskRecordDO::getStatus, whereStatuses));
}
default void updateListByIdAndStatus(Collection<Long> ids, Collection<Integer> whereStatuses,
IotOtaTaskRecordDO updateObj) {
update(updateObj, new LambdaUpdateWrapper<IotOtaTaskRecordDO>()
.in(IotOtaTaskRecordDO::getId, ids)
.in(IotOtaTaskRecordDO::getStatus, whereStatuses));
}
default List<IotOtaTaskRecordDO> selectListByDeviceIdAndStatus(Set<Long> deviceIds, Set<Integer> statuses) {
return selectList(new LambdaQueryWrapperX<IotOtaTaskRecordDO>()
.inIfPresent(IotOtaTaskRecordDO::getDeviceId, deviceIds)
.inIfPresent(IotOtaTaskRecordDO::getStatus, statuses));
}
default List<IotOtaTaskRecordDO> selectListByDeviceIdAndStatus(Long deviceId, Set<Integer> statuses) {
return selectList(new LambdaQueryWrapperX<IotOtaTaskRecordDO>()
.eqIfPresent(IotOtaTaskRecordDO::getDeviceId, deviceId)
.inIfPresent(IotOtaTaskRecordDO::getStatus, statuses));
}
default List<IotOtaTaskRecordDO> selectListByStatus(Integer status) {
return selectList(IotOtaTaskRecordDO::getStatus, status);
}
}

View File

@@ -1,38 +0,0 @@
package cn.iocoder.yudao.module.iot.dal.mysql.rule;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule.IotDataRulePageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataRuleDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* IoT 数据流转规则 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface IotDataRuleMapper extends BaseMapperX<IotDataRuleDO> {
default PageResult<IotDataRuleDO> selectPage(IotDataRulePageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<IotDataRuleDO>()
.likeIfPresent(IotDataRuleDO::getName, reqVO.getName())
.eqIfPresent(IotDataRuleDO::getStatus, reqVO.getStatus())
.betweenIfPresent(IotDataRuleDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(IotDataRuleDO::getId));
}
default List<IotDataRuleDO> selectListBySinkId(Long sinkId) {
return selectList(new LambdaQueryWrapperX<IotDataRuleDO>()
.apply(MyBatisUtils.findInSet("sink_ids", sinkId)));
}
default List<IotDataRuleDO> selectListByStatus(Integer status) {
return selectList(IotDataRuleDO::getStatus, status);
}
}

View File

@@ -1,32 +0,0 @@
package cn.iocoder.yudao.module.iot.dal.mysql.rule;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.sink.IotDataSinkPageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataSinkDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* IoT 数据流转目的 Mapper
*
* @author HUIHUI
*/
@Mapper
public interface IotDataSinkMapper extends BaseMapperX<IotDataSinkDO> {
default PageResult<IotDataSinkDO> selectPage(IotDataSinkPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<IotDataSinkDO>()
.likeIfPresent(IotDataSinkDO::getName, reqVO.getName())
.eqIfPresent(IotDataSinkDO::getStatus, reqVO.getStatus())
.betweenIfPresent(IotDataSinkDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(IotDataSinkDO::getId));
}
default List<IotDataSinkDO> selectListByStatus(Integer status) {
return selectList(IotDataSinkDO::getStatus, status);
}
}

View File

@@ -1,33 +0,0 @@
package cn.iocoder.yudao.module.iot.dal.mysql.rule;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRulePageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* IoT 场景联动 Mapper
*
* @author HUIHUI
*/
@Mapper
public interface IotSceneRuleMapper extends BaseMapperX<IotSceneRuleDO> {
default PageResult<IotSceneRuleDO> selectPage(IotSceneRulePageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<IotSceneRuleDO>()
.likeIfPresent(IotSceneRuleDO::getName, reqVO.getName())
.likeIfPresent(IotSceneRuleDO::getDescription, reqVO.getDescription())
.eqIfPresent(IotSceneRuleDO::getStatus, reqVO.getStatus())
.betweenIfPresent(IotSceneRuleDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(IotSceneRuleDO::getId));
}
default List<IotSceneRuleDO> selectListByStatus(Integer status) {
return selectList(IotSceneRuleDO::getStatus, status);
}
}

View File

@@ -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;
}
}

View File

@@ -1,79 +0,0 @@
package cn.iocoder.yudao.module.iot.dal.tdengine;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceMessagePageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO;
import cn.iocoder.yudao.module.iot.framework.tdengine.core.annotation.TDengineDS;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* 设备消息 {@link IotDeviceMessageDO} Mapper 接口
*/
@Mapper
@TDengineDS
@InterceptorIgnore(tenantLine = "true") // 避免 SQL 解析,因为 JSqlParser 对 TDengine 的 SQL 解析会报错
public interface IotDeviceMessageMapper {
/**
* 创建设备消息超级表
*/
void createSTable();
/**
* 查询设备消息表是否存在
*
* @return 存在则返回表名;不存在则返回 null
*/
String showSTable();
/**
* 插入设备消息数据
*
* 如果子表不存在,会自动创建子表
*
* @param message 设备消息数据
*/
void insert(IotDeviceMessageDO message);
/**
* 获得设备消息分页
*
* @param reqVO 分页查询条件
* @return 设备消息列表
*/
IPage<IotDeviceMessageDO> selectPage(IPage<IotDeviceMessageDO> page,
@Param("reqVO") IotDeviceMessagePageReqVO reqVO);
/**
* 统计设备消息数量
*
* @param createTime 创建时间,如果为空,则统计所有消息数量
* @return 消息数量
*/
Long selectCountByCreateTime(@Param("createTime") Long createTime);
/**
* 按照 requestIds 批量查询消息
*
* @param deviceId 设备编号
* @param requestIds 请求编号集合
* @param reply 是否回复消息
* @return 消息列表
*/
List<IotDeviceMessageDO> selectListByRequestIdsAndReply(@Param("deviceId") Long deviceId,
@Param("requestIds") Collection<String> requestIds,
@Param("reply") Boolean reply);
/**
* 按照时间范围(小时),统计设备的消息数量
*/
List<Map<String, Object>> selectDeviceMessageCountGroupByDate(@Param("startTime") Long startTime,
@Param("endTime") Long endTime);
}

View File

@@ -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";
}

View File

@@ -1,82 +0,0 @@
package cn.iocoder.yudao.module.iot.enums;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
/**
* iot 错误码枚举类
* <p>
* iot 系统,使用 1-050-000-000 段
*/
public interface ErrorCodeConstants {
// ========== 产品相关 1-050-001-000 ============
ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_050_001_000, "产品不存在");
ErrorCode PRODUCT_KEY_EXISTS = new ErrorCode(1_050_001_001, "产品标识已经存在");
ErrorCode PRODUCT_STATUS_NOT_DELETE = new ErrorCode(1_050_001_002, "产品状是发布状态,不允许删除");
ErrorCode PRODUCT_STATUS_NOT_ALLOW_THING_MODEL = new ErrorCode(1_050_001_003, "产品状是发布状态,不允许操作物模型");
ErrorCode PRODUCT_DELETE_FAIL_HAS_DEVICE = new ErrorCode(1_050_001_004, "产品下存在设备,不允许删除");
// ========== 产品物模型 1-050-002-000 ============
ErrorCode THING_MODEL_NOT_EXISTS = new ErrorCode(1_050_002_000, "产品物模型不存在");
ErrorCode THING_MODEL_EXISTS_BY_PRODUCT_KEY = new ErrorCode(1_050_002_001, "ProductKey 对应的产品物模型已存在");
ErrorCode THING_MODEL_IDENTIFIER_EXISTS = new ErrorCode(1_050_002_002, "存在重复的功能标识符。");
ErrorCode THING_MODEL_NAME_EXISTS = new ErrorCode(1_050_002_003, "存在重复的功能名称。");
ErrorCode THING_MODEL_IDENTIFIER_INVALID = new ErrorCode(1_050_002_003, "产品物模型标识无效");
// ========== 设备 1-050-003-000 ============
ErrorCode DEVICE_NOT_EXISTS = new ErrorCode(1_050_003_000, "设备不存在");
ErrorCode DEVICE_NAME_EXISTS = new ErrorCode(1_050_003_001, "设备名称在同一产品下必须唯一");
ErrorCode DEVICE_HAS_CHILDREN = new ErrorCode(1_050_003_002, "有子设备,不允许删除");
ErrorCode DEVICE_KEY_EXISTS = new ErrorCode(1_050_003_003, "设备标识已经存在");
ErrorCode DEVICE_GATEWAY_NOT_EXISTS = new ErrorCode(1_050_003_004, "网关设备不存在");
ErrorCode DEVICE_NOT_GATEWAY = new ErrorCode(1_050_003_005, "设备不是网关设备");
ErrorCode DEVICE_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_050_003_006, "导入设备数据不能为空!");
ErrorCode DEVICE_DOWNSTREAM_FAILED_SERVER_ID_NULL = new ErrorCode(1_050_003_007, "下行设备消息失败,原因:设备未连接网关");
ErrorCode DEVICE_SERIAL_NUMBER_EXISTS = new ErrorCode(1_050_003_008, "设备序列号已存在,序列号必须全局唯一");
// ========== 产品分类 1-050-004-000 ==========
ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_050_004_000, "产品分类不存在");
// ========== 设备分组 1-050-005-000 ==========
ErrorCode DEVICE_GROUP_NOT_EXISTS = new ErrorCode(1_050_005_000, "设备分组不存在");
ErrorCode DEVICE_GROUP_DELETE_FAIL_DEVICE_EXISTS = new ErrorCode(1_050_005_001, "设备分组下存在设备,不允许删除");
// ========== OTA 固件相关 1-050-008-000 ==========
ErrorCode OTA_FIRMWARE_NOT_EXISTS = new ErrorCode(1_050_008_000, "固件信息不存在");
ErrorCode OTA_FIRMWARE_PRODUCT_VERSION_DUPLICATE = new ErrorCode(1_050_008_001, "产品版本号重复");
// ========== OTA 升级任务相关 1-050-008-100 ==========
ErrorCode OTA_TASK_NOT_EXISTS = new ErrorCode(1_050_008_100, "升级任务不存在");
ErrorCode OTA_TASK_CREATE_FAIL_NAME_DUPLICATE = new ErrorCode(1_050_008_101, "创建 OTA 任务失败,原因:任务名称重复");
ErrorCode OTA_TASK_CREATE_FAIL_DEVICE_FIRMWARE_EXISTS = new ErrorCode(1_050_008_102,
"创建 OTA 任务失败,原因:设备({})已经是该固件版本");
ErrorCode OTA_TASK_CREATE_FAIL_DEVICE_OTA_IN_PROCESS = new ErrorCode(1_050_008_102,
"创建 OTA 任务失败,原因:设备({})已经在升级中...");
ErrorCode OTA_TASK_CREATE_FAIL_DEVICE_EMPTY = new ErrorCode(1_050_008_103, "创建 OTA 任务失败,原因:没有可升级的设备");
ErrorCode OTA_TASK_CANCEL_FAIL_STATUS_END = new ErrorCode(1_050_008_104, "取消 OTA 任务失败,原因:任务状态不是进行中");
// ========== OTA 升级任务记录相关 1-050-008-200 ==========
ErrorCode OTA_TASK_RECORD_NOT_EXISTS = new ErrorCode(1_050_008_200, "升级记录不存在");
ErrorCode OTA_TASK_RECORD_CANCEL_FAIL_STATUS_ERROR = new ErrorCode(1_050_008_201, "取消 OTA 升级记录失败,原因:记录状态不是进行中");
ErrorCode OTA_TASK_RECORD_UPDATE_PROGRESS_FAIL_NO_EXISTS = new ErrorCode(1_050_008_202, "更新 OTA 升级记录进度失败,原因:该设备没有进行中的升级记录");
// ========== IoT 数据流转规则 1-050-010-000 ==========
ErrorCode DATA_RULE_NOT_EXISTS = new ErrorCode(1_050_010_000, "数据流转规则不存在");
// ========== IoT 数据流转目的 1-050-011-000 ==========
ErrorCode DATA_SINK_NOT_EXISTS = new ErrorCode(1_050_011_000, "数据桥梁不存在");
ErrorCode DATA_SINK_DELETE_FAIL_USED_BY_RULE = new ErrorCode(1_050_011_001, "数据流转目的正在被数据流转规则使用,无法删除");
// ========== IoT 场景联动 1-050-012-000 ==========
ErrorCode RULE_SCENE_NOT_EXISTS = new ErrorCode(1_050_012_000, "场景联动不存在");
// ========== IoT 告警配置 1-050-013-000 ==========
ErrorCode ALERT_CONFIG_NOT_EXISTS = new ErrorCode(1_050_013_000, "IoT 告警配置不存在");
// ========== IoT 告警记录 1-050-014-000 ==========
ErrorCode ALERT_RECORD_NOT_EXISTS = new ErrorCode(1_050_014_000, "IoT 告警记录不存在");
}

View File

@@ -1,57 +0,0 @@
package cn.iocoder.yudao.module.iot.enums.ota;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
/**
* IoT OTA 升级任务记录的状态枚举
*
* @author 芋道源码
*/
@RequiredArgsConstructor
@Getter
public enum IotOtaTaskRecordStatusEnum implements ArrayValuable<Integer> {
PENDING(0), // 待推送
PUSHED(10), // 已推送
UPGRADING(20), // 升级中
SUCCESS(30), // 升级成功
FAILURE(40), // 升级失败
CANCELED(50),; // 升级取消
public static final Integer[] ARRAYS = Arrays.stream(values())
.map(IotOtaTaskRecordStatusEnum::getStatus).toArray(Integer[]::new);
public static final Set<Integer> IN_PROCESS_STATUSES = SetUtils.asSet(
PENDING.getStatus(),
PUSHED.getStatus(),
UPGRADING.getStatus());
public static final List<Integer> PRIORITY_STATUSES = Arrays.asList(
SUCCESS.getStatus(),
PENDING.getStatus(), PUSHED.getStatus(), UPGRADING.getStatus(),
FAILURE.getStatus(), CANCELED.getStatus());
/**
* 状态
*/
private final Integer status;
@Override
public Integer[] array() {
return ARRAYS;
}
public static IotOtaTaskRecordStatusEnum of(Integer status) {
return ArrayUtil.firstMatch(o -> o.getStatus().equals(status), values());
}
}

View File

@@ -1,42 +0,0 @@
package cn.iocoder.yudao.module.iot.enums.rule;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
/**
* IoT 数据目的的类型枚举
*
* @author 芋道源码
*/
@RequiredArgsConstructor
@Getter
public enum IotDataSinkTypeEnum implements ArrayValuable<Integer> {
HTTP(1, "HTTP"),
TCP(2, "TCP"), // TODO @puhui999待实现
WEBSOCKET(3, "WebSocket"), // TODO @puhui999待实现
MQTT(10, "MQTT"), // TODO 待实现;
DATABASE(20, "Database"), // TODO @puhui999待实现可以简单点对应的表名是什么字段先固定了。
REDIS(21, "Redis"),
ROCKETMQ(30, "RocketMQ"),
RABBITMQ(31, "RabbitMQ"),
KAFKA(32, "Kafka");
private final Integer type;
private final String name;
public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotDataSinkTypeEnum::getType).toArray(Integer[]::new);
@Override
public Integer[] array() {
return ARRAYS;
}
}

View File

@@ -1,52 +0,0 @@
package cn.iocoder.yudao.module.iot.enums.rule;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
/**
* IoT 规则场景的触发类型枚举
*
* 设备触发,定时触发
*/
@RequiredArgsConstructor
@Getter
public enum IotSceneRuleActionTypeEnum implements ArrayValuable<Integer> {
/**
* 设备属性设置
*
* 对应 {@link IotDeviceMessageMethodEnum#PROPERTY_SET}
*/
DEVICE_PROPERTY_SET(1),
/**
* 设备服务调用
*
* 对应 {@link IotDeviceMessageMethodEnum#SERVICE_INVOKE}
*/
DEVICE_SERVICE_INVOKE(2),
/**
* 告警触发
*/
ALERT_TRIGGER(100),
/**
* 告警恢复
*/
ALERT_RECOVER(101),
;
private final Integer type;
public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotSceneRuleActionTypeEnum::getType).toArray(Integer[]::new);
@Override
public Integer[] array() {
return ARRAYS;
}
}

View File

@@ -1,40 +0,0 @@
package cn.iocoder.yudao.module.iot.enums.rule;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
/**
* IoT 条件类型枚举
*
* @author 芋道源码
*/
@RequiredArgsConstructor
@Getter
public enum IotSceneRuleConditionTypeEnum implements ArrayValuable<Integer> {
DEVICE_STATE(1, "设备状态"),
DEVICE_PROPERTY(2, "设备属性"),
CURRENT_TIME(100, "当前时间"),
;
private final Integer type;
private final String name;
public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotSceneRuleConditionTypeEnum::getType).toArray(Integer[]::new);
@Override
public Integer[] array() {
return ARRAYS;
}
public static IotSceneRuleConditionTypeEnum typeOf(Integer type) {
return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
}
}

View File

@@ -1,68 +0,0 @@
package cn.iocoder.yudao.module.iot.enums.rule;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
/**
* IoT 场景流转的触发类型枚举
*
* 为什么不直接使用 IotDeviceMessageMethodEnum 呢?
* 原因是,物模型属性上报,存在批量上报的情况,不只对应一个 method
*
* @author 芋道源码
*/
@RequiredArgsConstructor
@Getter
public enum IotSceneRuleTriggerTypeEnum implements ArrayValuable<Integer> {
// TODO @芋艿:后续“对应”部分,要 @下,等包结构梳理完;
/**
* 设备上下线变更
*
* 对应 IotDeviceMessageMethodEnum.STATE_UPDATE
*/
DEVICE_STATE_UPDATE(1),
/**
* 物模型属性上报
*
* 对应 IotDeviceMessageMethodEnum.DEVICE_PROPERTY_POST
*/
DEVICE_PROPERTY_POST(2),
/**
* 设备事件上报
*
* 对应 IotDeviceMessageMethodEnum.DEVICE_EVENT_POST
*/
DEVICE_EVENT_POST(3),
/**
* 设备服务调用
*
* 对应 IotDeviceMessageMethodEnum.DEVICE_SERVICE_INVOKE
*/
DEVICE_SERVICE_INVOKE(4),
/**
* 定时触发
*/
TIMER(100)
;
private final Integer type;
public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotSceneRuleTriggerTypeEnum::getType).toArray(Integer[]::new);
@Override
public Integer[] array() {
return ARRAYS;
}
public static IotSceneRuleTriggerTypeEnum typeOf(Integer type) {
return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
}
}

View File

@@ -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;
}

View File

@@ -1,4 +0,0 @@
/**
* iot 模块的【全局】拓展封装
*/
package cn.iocoder.yudao.module.iot.framework.iot;

View File

@@ -1,86 +0,0 @@
package cn.iocoder.yudao.module.iot.job.ota;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO;
import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskRecordStatusEnum;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import cn.iocoder.yudao.module.iot.service.ota.IotOtaFirmwareService;
import cn.iocoder.yudao.module.iot.service.ota.IotOtaTaskRecordService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* IoT OTA 升级推送 Job查询待推送的 OTA 升级记录,并推送给设备
*
* @author 芋道源码
*/
@Component
@Slf4j
public class IotOtaUpgradeJob implements JobHandler {
@Resource
private IotOtaTaskRecordService otaTaskRecordService;
@Resource
private IotOtaFirmwareService otaFirmwareService;
@Resource
private IotDeviceService deviceService;
@Override
@TenantJob
public String execute(String param) throws Exception {
// 1. 查询待推送的 OTA 升级记录
List<IotOtaTaskRecordDO> records = otaTaskRecordService.getOtaRecordListByStatus(
IotOtaTaskRecordStatusEnum.PENDING.getStatus());
if (CollUtil.isEmpty(records)) {
return null;
}
// TODO 芋艿:可以优化成批量获取 原因是1. N+1 问题2. offline 的设备无需查询
// 2. 遍历推送记录
int successCount = 0;
int failureCount = 0;
Map<Long, IotOtaFirmwareDO> otaFirmwares = new HashMap<>();
for (IotOtaTaskRecordDO record : records) {
try {
// 2.1 设备如果不在线,直接跳过
IotDeviceDO device = deviceService.getDeviceFromCache(record.getDeviceId());
// TODO 芋艿:【优化】当前逻辑跳过了离线的设备,但未充分利用 MQTT 的离线消息能力。
// 1. MQTT 协议本身支持持久化会话Clean Session=false和 QoS > 0 的消息,允许 broker 为离线设备缓存消息。
// 2. 对于 OTA 升级这类非实时性强的任务,即使设备当前离线,也应该可以推送升级指令。设备在下次上线时即可收到。
// 3. 后续可以考虑:增加一个“允许离线推送”的选项。如果开启,即使设备状态为 OFFLINE也应尝试推送消息依赖 MQTT Broker 的能力进行离线缓存。
if (device == null || IotDeviceStateEnum.isNotOnline(device.getState())) {
continue;
}
// 2.2 获取 OTA 固件信息
IotOtaFirmwareDO fireware = otaFirmwares.get(record.getFirmwareId());
if (fireware == null) {
fireware = otaFirmwareService.getOtaFirmware(record.getFirmwareId());
otaFirmwares.put(record.getFirmwareId(), fireware);
}
// 2.3 推送 OTA 升级任务
boolean result = otaTaskRecordService.pushOtaTaskRecord(record, fireware, device);
if (result) {
successCount++;
} else {
failureCount++;
}
} catch (Exception e) {
failureCount++;
log.error("[execute][推送 OTA 升级任务({})发生异常]", record.getId(), e);
}
}
return StrUtil.format("升级任务推送成功:{} 条,送失败:{} 条", successCount, failureCount);
}
}

View File

@@ -1,58 +0,0 @@
package cn.iocoder.yudao.module.iot.job.rule;
import cn.hutool.core.map.MapUtil;
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
import cn.iocoder.yudao.module.iot.service.rule.scene.IotSceneRuleService;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobExecutionContext;
import org.springframework.scheduling.quartz.QuartzJobBean;
import javax.annotation.Resource;
import java.util.Map;
/**
* IoT 规则场景 Job用于执行 {@link IotSceneRuleTriggerTypeEnum#TIMER} 类型的规则场景
*
* @author 芋道源码
*/
@Slf4j
public class IotSceneRuleJob extends QuartzJobBean {
/**
* JobData Key - 规则场景编号
*/
public static final String JOB_DATA_KEY_RULE_SCENE_ID = "sceneRuleId";
@Resource
private IotSceneRuleService sceneRuleService;
@Override
protected void executeInternal(JobExecutionContext context) {
// 获得规则场景编号
Long sceneRuleId = context.getMergedJobDataMap().getLong(JOB_DATA_KEY_RULE_SCENE_ID);
// 执行规则场景
sceneRuleService.executeSceneRuleByTimer(sceneRuleId);
}
/**
* 创建 JobData Map
*
* @param sceneRuleId 规则场景编号
* @return JobData Map
*/
public static Map<String, Object> buildJobDataMap(Long sceneRuleId) {
return MapUtil.of(JOB_DATA_KEY_RULE_SCENE_ID, sceneRuleId);
}
/**
* 创建 Job 名字
*
* @param sceneRuleId 规则场景编号
* @return Job 名字
*/
public static String buildJobName(Long sceneRuleId) {
return String.format("%s_%d", IotSceneRuleJob.class.getSimpleName(), sceneRuleId);
}
}

View File

@@ -1,101 +0,0 @@
package cn.iocoder.yudao.module.iot.mq.consumer.device;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import cn.iocoder.yudao.module.iot.service.device.message.IotDeviceMessageService;
import cn.iocoder.yudao.module.iot.service.device.property.IotDevicePropertyService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Objects;
/**
* 针对 {@link IotDeviceMessage} 的业务处理器:调用 method 对应的逻辑。例如说:
* 1. {@link IotDeviceMessageMethodEnum#PROPERTY_POST} 属性上报时,记录设备属性
*
* @author alwayssuper
*/
@Component
@Slf4j
public class IotDeviceMessageSubscriber implements IotMessageSubscriber<IotDeviceMessage> {
@Resource
private IotDeviceService deviceService;
@Resource
private IotDevicePropertyService devicePropertyService;
@Resource
private IotDeviceMessageService deviceMessageService;
@Resource
private IotMessageBus messageBus;
@PostConstruct
public void init() {
messageBus.register(this);
}
@Override
public String getTopic() {
return IotDeviceMessage.MESSAGE_BUS_DEVICE_MESSAGE_TOPIC;
}
@Override
public String getGroup() {
return "iot_device_message_consumer";
}
@Override
public void onMessage(IotDeviceMessage message) {
if (!IotDeviceMessageUtils.isUpstreamMessage(message)) {
log.error("[onMessage][message({}) 非上行消息,不进行处理]", message);
return;
}
TenantUtils.execute(message.getTenantId(), () -> {
// 1.1 更新设备的最后时间
IotDeviceDO device = deviceService.validateDeviceExistsFromCache(message.getDeviceId());
devicePropertyService.updateDeviceReportTimeAsync(device.getId(), LocalDateTime.now());
// 1.2 更新设备的连接 server
// TODO 芋艿HTTP 网关的上行消息,不应该更新 serverId会覆盖掉 MQTT 等长连接的 serverId导致下行消息无法发送。
devicePropertyService.updateDeviceServerIdAsync(device.getId(), message.getServerId());
// 2. 未上线的设备,强制上线
forceDeviceOnline(message, device);
// 3. 核心:处理消息
deviceMessageService.handleUpstreamDeviceMessage(message, device);
});
}
private void forceDeviceOnline(IotDeviceMessage message, IotDeviceDO device) {
// 已经在线,无需处理
if (ObjectUtil.equal(device.getState(), IotDeviceStateEnum.ONLINE.getState())) {
return;
}
// 如果是 STATE 相关的消息,无需处理,不然就重复处理状态了
if (Objects.equals(message.getMethod(), IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod())) {
return;
}
// 特殊:设备非在线时,主动标记设备为在线
// 为什么不直接更新状态呢?因为通过 IotDeviceMessage 可以经过一系列的处理,例如说记录日志、规则引擎等等
try {
deviceMessageService.sendDeviceMessage(IotDeviceMessage.buildStateUpdateOnline().setDeviceId(device.getId()));
} catch (Exception e) {
// 注意:即使执行失败,也不影响主流程
log.error("[forceDeviceOnline][message({}) device({}) 强制设备上线失败]", message, device, e);
}
}
}

View File

@@ -1,49 +0,0 @@
package cn.iocoder.yudao.module.iot.mq.consumer.rule;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.service.rule.data.IotDataRuleService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
/**
* 针对 {@link IotDeviceMessage} 的消费者,处理数据流转
*
* @author 芋道源码
*/
@Component
@Slf4j
public class IotDataRuleMessageHandler implements IotMessageSubscriber<IotDeviceMessage> {
@Resource
private IotDataRuleService dataRuleService;
@Resource
private IotMessageBus messageBus;
@PostConstruct
public void init() {
messageBus.register(this);
}
@Override
public String getTopic() {
return IotDeviceMessage.MESSAGE_BUS_DEVICE_MESSAGE_TOPIC;
}
@Override
public String getGroup() {
return "iot_data_rule_consumer";
}
@Override
public void onMessage(IotDeviceMessage message) {
TenantUtils.execute(message.getTenantId(), () -> dataRuleService.executeDataRule(message));
}
}

View File

@@ -1,53 +0,0 @@
package cn.iocoder.yudao.module.iot.mq.consumer.rule;
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.service.rule.scene.IotSceneRuleService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
// TODO @puhui999后面重构哈
/**
* 针对 {@link IotDeviceMessage} 的消费者,处理规则场景
*
* @author 芋道源码
*/
@Component
@Slf4j
public class IotSceneRuleMessageHandler implements IotMessageSubscriber<IotDeviceMessage> {
@Resource
private IotSceneRuleService sceneRuleService;
@Resource
private IotMessageBus messageBus;
@PostConstruct
public void init() {
messageBus.register(this);
}
@Override
public String getTopic() {
return IotDeviceMessage.MESSAGE_BUS_DEVICE_MESSAGE_TOPIC;
}
@Override
public String getGroup() {
return "iot_rule_consumer";
}
@Override
public void onMessage(IotDeviceMessage message) {
if (true) {
return;
}
log.info("[onMessage][消息内容({})]", message);
sceneRuleService.executeSceneRuleByDevice(message);
}
}

View File

@@ -1,72 +0,0 @@
package cn.iocoder.yudao.module.iot.service.alert;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config.IotAlertConfigPageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config.IotAlertConfigSaveReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertConfigDO;
import javax.validation.Valid;
import java.util.List;
/**
* IoT 告警配置 Service 接口
*
* @author 芋道源码
*/
public interface IotAlertConfigService {
/**
* 创建告警配置
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createAlertConfig(@Valid IotAlertConfigSaveReqVO createReqVO);
/**
* 更新告警配置
*
* @param updateReqVO 更新信息
*/
void updateAlertConfig(@Valid IotAlertConfigSaveReqVO updateReqVO);
/**
* 删除告警配置
*
* @param id 编号
*/
void deleteAlertConfig(Long id);
/**
* 获得告警配置
*
* @param id 编号
* @return 告警配置
*/
IotAlertConfigDO getAlertConfig(Long id);
/**
* 获得告警配置分页
*
* @param pageReqVO 分页查询
* @return 告警配置分页
*/
PageResult<IotAlertConfigDO> getAlertConfigPage(IotAlertConfigPageReqVO pageReqVO);
/**
* 获得告警配置列表
*
* @param status 状态
* @return 告警配置列表
*/
List<IotAlertConfigDO> getAlertConfigListByStatus(Integer status);
/**
* 获得告警配置列表
*
* @param sceneRuleId 场景流动规则编号
* @return 告警配置列表
*/
List<IotAlertConfigDO> getAlertConfigListBySceneRuleIdAndStatus(Long sceneRuleId, Integer status);
}

View File

@@ -1,99 +0,0 @@
package cn.iocoder.yudao.module.iot.service.alert;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config.IotAlertConfigPageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config.IotAlertConfigSaveReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertConfigDO;
import cn.iocoder.yudao.module.iot.dal.mysql.alert.IotAlertConfigMapper;
import cn.iocoder.yudao.module.iot.service.rule.scene.IotSceneRuleService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.ALERT_CONFIG_NOT_EXISTS;
/**
* IoT 告警配置 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class IotAlertConfigServiceImpl implements IotAlertConfigService {
@Resource
private IotAlertConfigMapper alertConfigMapper;
@Resource
@Lazy // 延迟,避免循环依赖报错
private IotSceneRuleService sceneRuleService;
@Resource
private AdminUserApi adminUserApi;
@Override
public Long createAlertConfig(IotAlertConfigSaveReqVO createReqVO) {
// 校验关联数据是否存在
sceneRuleService.validateSceneRuleList(createReqVO.getSceneRuleIds());
adminUserApi.validateUserList(createReqVO.getReceiveUserIds());
// 插入
IotAlertConfigDO alertConfig = BeanUtils.toBean(createReqVO, IotAlertConfigDO.class);
alertConfigMapper.insert(alertConfig);
return alertConfig.getId();
}
@Override
public void updateAlertConfig(IotAlertConfigSaveReqVO updateReqVO) {
// 校验存在
validateAlertConfigExists(updateReqVO.getId());
// 校验关联数据是否存在
sceneRuleService.validateSceneRuleList(updateReqVO.getSceneRuleIds());
adminUserApi.validateUserList(updateReqVO.getReceiveUserIds());
// 更新
IotAlertConfigDO updateObj = BeanUtils.toBean(updateReqVO, IotAlertConfigDO.class);
alertConfigMapper.updateById(updateObj);
}
@Override
public void deleteAlertConfig(Long id) {
// 校验存在
validateAlertConfigExists(id);
// 删除
alertConfigMapper.deleteById(id);
}
private void validateAlertConfigExists(Long id) {
if (alertConfigMapper.selectById(id) == null) {
throw exception(ALERT_CONFIG_NOT_EXISTS);
}
}
@Override
public IotAlertConfigDO getAlertConfig(Long id) {
return alertConfigMapper.selectById(id);
}
@Override
public PageResult<IotAlertConfigDO> getAlertConfigPage(IotAlertConfigPageReqVO pageReqVO) {
return alertConfigMapper.selectPage(pageReqVO);
}
@Override
public List<IotAlertConfigDO> getAlertConfigListByStatus(Integer status) {
return alertConfigMapper.selectListByStatus(status);
}
@Override
public List<IotAlertConfigDO> getAlertConfigListBySceneRuleIdAndStatus(Long sceneRuleId, Integer status) {
return alertConfigMapper.selectListBySceneRuleIdAndStatus(sceneRuleId, status);
}
}

View File

@@ -1,65 +0,0 @@
package cn.iocoder.yudao.module.iot.service.alert;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.recrod.IotAlertRecordPageReqVO;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertConfigDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertRecordDO;
import javax.validation.constraints.NotNull;
import java.util.Collection;
import java.util.List;
/**
* IoT 告警记录 Service 接口
*
* @author 芋道源码
*/
public interface IotAlertRecordService {
/**
* 获得告警记录
*
* @param id 编号
* @return 告警记录
*/
IotAlertRecordDO getAlertRecord(Long id);
/**
* 获得告警记录分页
*
* @param pageReqVO 分页查询
* @return 告警记录分页
*/
PageResult<IotAlertRecordDO> getAlertRecordPage(IotAlertRecordPageReqVO pageReqVO);
/**
* 获得指定场景规则的告警记录列表
*
* @param sceneRuleId 场景规则编号
* @param deviceId 设备编号
* @param processStatus 处理状态,允许空
* @return 告警记录列表
*/
List<IotAlertRecordDO> getAlertRecordListBySceneRuleId(@NotNull(message = "场景规则编号不能为空") Long sceneRuleId,
Long deviceId, Boolean processStatus);
/**
* 处理告警记录
*
* @param ids 告警记录编号
* @param remark 处理结果(备注)
*/
void processAlertRecordList(Collection<Long> ids, String remark);
/**
* 创建告警记录(包含场景规则编号)
*
* @param config 告警配置
* @param sceneRuleId 场景规则编号
* @param deviceMessage 设备消息,可为空
* @return 告警记录编号
*/
Long createAlertRecord(IotAlertConfigDO config, Long sceneRuleId, IotDeviceMessage deviceMessage);
}

View File

@@ -1,80 +0,0 @@
package cn.iocoder.yudao.module.iot.service.alert;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.recrod.IotAlertRecordPageReqVO;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertConfigDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertRecordDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.mysql.alert.IotAlertRecordMapper;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;
/**
* IoT 告警记录 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class IotAlertRecordServiceImpl implements IotAlertRecordService {
@Resource
private IotAlertRecordMapper alertRecordMapper;
@Resource
private IotDeviceService deviceService;
@Override
public IotAlertRecordDO getAlertRecord(Long id) {
return alertRecordMapper.selectById(id);
}
@Override
public PageResult<IotAlertRecordDO> getAlertRecordPage(IotAlertRecordPageReqVO pageReqVO) {
return alertRecordMapper.selectPage(pageReqVO);
}
@Override
public List<IotAlertRecordDO> getAlertRecordListBySceneRuleId(Long sceneRuleId, Long deviceId, Boolean processStatus) {
return alertRecordMapper.selectListBySceneRuleId(sceneRuleId, deviceId, processStatus);
}
@Override
public void processAlertRecordList(Collection<Long> ids, String processRemark) {
if (CollUtil.isEmpty(ids)) {
return;
}
// 批量更新告警记录的处理状态
alertRecordMapper.updateList(ids, IotAlertRecordDO.builder()
.processStatus(true).processRemark(processRemark).build());
}
@Override
public Long createAlertRecord(IotAlertConfigDO config, Long sceneRuleId, IotDeviceMessage message) {
// 构建告警记录
IotAlertRecordDO.IotAlertRecordDOBuilder builder = IotAlertRecordDO.builder()
.configId(config.getId()).configName(config.getName()).configLevel(config.getLevel())
.sceneRuleId(sceneRuleId).processStatus(false);
if (message != null) {
builder.deviceMessage(message);
// 填充设备信息
IotDeviceDO device = deviceService.getDeviceFromCache(message.getDeviceId());
if (device != null) {
builder.productId(device.getProductId()).deviceId(device.getId());
}
}
// 插入记录
IotAlertRecordDO record = builder.build();
alertRecordMapper.insert(record);
return record.getId();
}
}

View File

@@ -1,98 +0,0 @@
package cn.iocoder.yudao.module.iot.service.device.message;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceMessagePageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsDeviceMessageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsDeviceMessageSummaryByDateRespVO;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO;
import javax.annotation.Nullable;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import java.util.List;
/**
* IoT 设备消息 Service 接口
*
* @author 芋道源码
*/
public interface IotDeviceMessageService {
/**
* 初始化设备消息的 TDengine 超级表
*
* 系统启动时,会自动初始化一次
*/
void defineDeviceMessageStable();
/**
* 发送设备消息
*
* @param message 消息“codec编解码字段” 部分字段)
* @param device 设备
* @return 设备消息
*/
IotDeviceMessage sendDeviceMessage(IotDeviceMessage message, IotDeviceDO device);
/**
* 发送设备消息
*
* @param message 消息“codec编解码字段” 部分字段)
* @return 设备消息
*/
IotDeviceMessage sendDeviceMessage(IotDeviceMessage message);
/**
* 处理设备上行的消息,包括如下步骤:
*
* 1. 处理消息
* 2. 记录消息
* 3. 回复消息
*
* @param message 消息
* @param device 设备
*/
void handleUpstreamDeviceMessage(IotDeviceMessage message, IotDeviceDO device);
/**
* 获得设备消息分页
*
* @param pageReqVO 分页查询
* @return 设备消息分页
*/
PageResult<IotDeviceMessageDO> getDeviceMessagePage(IotDeviceMessagePageReqVO pageReqVO);
/**
* 获得指定 requestId 的设备消息列表
*
* @param deviceId 设备编号
* @param requestIds requestId 列表
* @param reply 是否回复
* @return 设备消息列表
*/
List<IotDeviceMessageDO> getDeviceMessageListByRequestIdsAndReply(
@NotNull(message = "设备编号不能为空") Long deviceId,
@NotEmpty(message = "请求编号不能为空") List<String> requestIds,
Boolean reply);
/**
* 获得设备消息数量
*
* @param createTime 创建时间,如果为空,则统计所有消息数量
* @return 消息数量
*/
Long getDeviceMessageCount(@Nullable LocalDateTime createTime);
/**
* 获取设备消息的数据统计
*
* @param reqVO 统计请求
* @return 设备消息的数据统计
*/
List<IotStatisticsDeviceMessageSummaryByDateRespVO> getDeviceMessageSummaryByDate(
IotStatisticsDeviceMessageReqVO reqVO);
}

View File

@@ -1,271 +0,0 @@
package cn.iocoder.yudao.module.iot.service.device.message;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceMessagePageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsDeviceMessageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsDeviceMessageSummaryByDateRespVO;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer;
import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO;
import cn.iocoder.yudao.module.iot.dal.tdengine.IotDeviceMessageMapper;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import cn.iocoder.yudao.module.iot.service.device.property.IotDevicePropertyService;
import cn.iocoder.yudao.module.iot.service.ota.IotOtaTaskRecordService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.base.Objects;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.DEVICE_DOWNSTREAM_FAILED_SERVER_ID_NULL;
/**
* IoT 设备消息 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
@Slf4j
public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
@Resource
private IotDeviceService deviceService;
@Resource
private IotDevicePropertyService devicePropertyService;
@Resource
@Lazy // 延迟加载,避免循环依赖
private IotOtaTaskRecordService otaTaskRecordService;
@Resource
private IotDeviceMessageMapper deviceMessageMapper;
@Resource
private IotDeviceMessageProducer deviceMessageProducer;
@Override
public void defineDeviceMessageStable() {
if (StrUtil.isNotEmpty(deviceMessageMapper.showSTable())) {
log.info("[defineDeviceMessageStable][设备消息超级表已存在,创建跳过]");
return;
}
log.info("[defineDeviceMessageStable][设备消息超级表不存在,创建开始...]");
deviceMessageMapper.createSTable();
log.info("[defineDeviceMessageStable][设备消息超级表不存在,创建成功]");
}
@Async
void createDeviceLogAsync(IotDeviceMessage message) {
IotDeviceMessageDO messageDO = BeanUtils.toBean(message, IotDeviceMessageDO.class)
.setUpstream(IotDeviceMessageUtils.isUpstreamMessage(message))
.setReply(IotDeviceMessageUtils.isReplyMessage(message))
.setIdentifier(IotDeviceMessageUtils.getIdentifier(message));
if (message.getParams() != null) {
messageDO.setParams(JsonUtils.toJsonString(messageDO.getParams()));
}
if (messageDO.getData() != null) {
messageDO.setData(JsonUtils.toJsonString(messageDO.getData()));
}
deviceMessageMapper.insert(messageDO);
}
@Override
public IotDeviceMessage sendDeviceMessage(IotDeviceMessage message) {
IotDeviceDO device = deviceService.validateDeviceExists(message.getDeviceId());
return sendDeviceMessage(message, device);
}
// TODO @芋艿:针对连接网关的设备,是不是 productKey、deviceName 需要调整下;
@Override
public IotDeviceMessage sendDeviceMessage(IotDeviceMessage message, IotDeviceDO device) {
return sendDeviceMessage(message, device, null);
}
private IotDeviceMessage sendDeviceMessage(IotDeviceMessage message, IotDeviceDO device, String serverId) {
// 1. 补充信息
appendDeviceMessage(message, device);
// 2.1 情况一:发送上行消息
boolean upstream = IotDeviceMessageUtils.isUpstreamMessage(message);
if (upstream) {
deviceMessageProducer.sendDeviceMessage(message);
return message;
}
// 2.2 情况二:发送下行消息
// 如果是下行消息,需要校验 serverId 存在
// TODO 芋艿:【设计】下行消息需要区分 PUSH 和 PULL 模型
// 1. PUSH 模型:适用于 MQTT 等长连接协议。通过 serverId 将消息路由到指定网关,实时推送。
// 2. PULL 模型:适用于 HTTP 等短连接协议。设备无固定 serverId无法主动推送。
// 解决方案:
// 当 serverId 不存在时,将下行消息存入“待拉取消息表”(例如 iot_device_pull_message
// 设备端通过定时轮询一个新增的 API例如 /iot/message/pull来拉取属于自己的消息。
if (StrUtil.isEmpty(serverId)) {
serverId = devicePropertyService.getDeviceServerId(device.getId());
if (StrUtil.isEmpty(serverId)) {
throw exception(DEVICE_DOWNSTREAM_FAILED_SERVER_ID_NULL);
}
}
deviceMessageProducer.sendDeviceMessageToGateway(serverId, message);
// 特殊:记录消息日志。原因:上行消息,消费时,已经会记录;下行消息,因为消费在 Gateway 端,所以需要在这里记录
getSelf().createDeviceLogAsync(message);
return message;
}
/**
* 补充消息的后端字段
*
* @param message 消息
* @param device 设备信息
*/
private void appendDeviceMessage(IotDeviceMessage message, IotDeviceDO device) {
message.setId(IotDeviceMessageUtils.generateMessageId()).setReportTime(LocalDateTime.now())
.setDeviceId(device.getId()).setTenantId(device.getTenantId());
// 特殊:如果设备没有指定 requestId则使用 messageId
if (StrUtil.isEmpty(message.getRequestId())) {
message.setRequestId(message.getId());
}
}
@Override
public void handleUpstreamDeviceMessage(IotDeviceMessage message, IotDeviceDO device) {
// 1. 处理消息
Object replyData = null;
ServiceException serviceException = null;
try {
replyData = handleUpstreamDeviceMessage0(message, device);
} catch (ServiceException ex) {
serviceException = ex;
log.warn("[handleUpstreamDeviceMessage][message({}) 业务异常]", message, serviceException);
} catch (Exception ex) {
log.error("[handleUpstreamDeviceMessage][message({}) 发生异常]", message, ex);
throw ex;
}
// 2. 记录消息
getSelf().createDeviceLogAsync(message);
// 3. 回复消息。前提:非 _reply 消息,并且非禁用回复的消息
if (IotDeviceMessageUtils.isReplyMessage(message)
|| IotDeviceMessageMethodEnum.isReplyDisabled(message.getMethod())
|| StrUtil.isEmpty(message.getServerId())) {
return;
}
try {
IotDeviceMessage replyMessage = IotDeviceMessage.replyOf(message.getRequestId(), message.getMethod(), replyData,
serviceException != null ? serviceException.getCode() : null,
serviceException != null ? serviceException.getMessage() : null);
sendDeviceMessage(replyMessage, device, message.getServerId());
} catch (Exception ex) {
log.error("[handleUpstreamDeviceMessage][message({}) 回复消息失败]", message, ex);
}
}
// TODO @芋艿:可优化:未来逻辑复杂后,可以独立拆除 Processor 处理器
@SuppressWarnings("SameReturnValue")
private Object handleUpstreamDeviceMessage0(IotDeviceMessage message, IotDeviceDO device) {
// 设备上下线
if (Objects.equal(message.getMethod(), IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod())) {
String stateStr = IotDeviceMessageUtils.getIdentifier(message);
assert stateStr != null;
Assert.notEmpty(stateStr, "设备状态不能为空");
deviceService.updateDeviceState(device, Integer.valueOf(stateStr));
// TODO 芋艿:子设备的关联
return null;
}
// 属性上报
if (Objects.equal(message.getMethod(), IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod())) {
devicePropertyService.saveDeviceProperty(device, message);
return null;
}
// OTA 上报升级进度
if (Objects.equal(message.getMethod(), IotDeviceMessageMethodEnum.OTA_PROGRESS.getMethod())) {
otaTaskRecordService.updateOtaRecordProgress(device, message);
return null;
}
// TODO @芋艿:这里可以按需,添加别的逻辑;
return null;
}
@Override
public PageResult<IotDeviceMessageDO> getDeviceMessagePage(IotDeviceMessagePageReqVO pageReqVO) {
try {
IPage<IotDeviceMessageDO> page = deviceMessageMapper.selectPage(
new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize()), pageReqVO);
return new PageResult<>(page.getRecords(), page.getTotal());
} catch (Exception exception) {
if (exception.getMessage().contains("Table does not exist")) {
return PageResult.empty();
}
throw exception;
}
}
@Override
public List<IotDeviceMessageDO> getDeviceMessageListByRequestIdsAndReply(Long deviceId,
List<String> requestIds,
Boolean reply) {
return deviceMessageMapper.selectListByRequestIdsAndReply(deviceId, requestIds, reply);
}
@Override
public Long getDeviceMessageCount(LocalDateTime createTime) {
return deviceMessageMapper.selectCountByCreateTime(
createTime != null ? LocalDateTimeUtil.toEpochMilli(createTime) : null);
}
@Override
public List<IotStatisticsDeviceMessageSummaryByDateRespVO> getDeviceMessageSummaryByDate(
IotStatisticsDeviceMessageReqVO reqVO) {
// 1. 按小时统计,获取分项统计数据
List<Map<String, Object>> countList = deviceMessageMapper.selectDeviceMessageCountGroupByDate(
LocalDateTimeUtil.toEpochMilli(reqVO.getTimes()[0]),
LocalDateTimeUtil.toEpochMilli(reqVO.getTimes()[1]));
// 2. 按照日期间隔,合并数据
List<LocalDateTime[]> timeRanges = LocalDateTimeUtils.getDateRangeList(reqVO.getTimes()[0], reqVO.getTimes()[1],
reqVO.getInterval());
return convertList(timeRanges, times -> {
Integer upstreamCount = countList.stream()
.filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], (Timestamp) vo.get("time")))
.mapToInt(value -> MapUtil.getInt(value, "upstream_count")).sum();
Integer downstreamCount = countList.stream()
.filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], (Timestamp) vo.get("time")))
.mapToInt(value -> MapUtil.getInt(value, "downstream_count")).sum();
return new IotStatisticsDeviceMessageSummaryByDateRespVO()
.setTime(LocalDateTimeUtils.formatDateRange(times[0], times[1], reqVO.getInterval()))
.setUpstreamCount(upstreamCount).setDownstreamCount(downstreamCount);
});
}
private IotDeviceMessageServiceImpl getSelf() {
return SpringUtil.getBean(getClass());
}
}

View File

@@ -1,89 +0,0 @@
package cn.iocoder.yudao.module.iot.service.device.property;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyHistoryListReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyRespVO;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDevicePropertyDO;
import javax.validation.Valid;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* IoT 设备【属性】数据 Service 接口
*
* @author 芋道源码
*/
public interface IotDevicePropertyService {
// ========== 设备属性相关操作 ==========
/**
* 定义设备属性数据的结构
*
* @param productId 产品编号
*/
void defineDevicePropertyData(Long productId);
/**
* 保存设备数据
*
* @param device 设备
* @param message 设备消息
*/
void saveDeviceProperty(IotDeviceDO device, IotDeviceMessage message);
/**
* 获得设备属性最新数据
*
* @param deviceId 设备编号
* @return 设备属性最新数据
*/
Map<String, IotDevicePropertyDO> getLatestDeviceProperties(Long deviceId);
/**
* 获得设备属性历史数据
*
* @param listReqVO 列表请求
* @return 设备属性历史数据
*/
List<IotDevicePropertyRespVO> getHistoryDevicePropertyList(@Valid IotDevicePropertyHistoryListReqVO listReqVO);
// ========== 设备时间相关操作 ==========
/**
* 获得最后上报时间小于指定时间的设备编号集合
*
* @param maxReportTime 最大上报时间
* @return 设备编号集合
*/
Set<Long> getDeviceIdListByReportTime(LocalDateTime maxReportTime);
/**
* 更新设备上报时间
*
* @param id 设备编号
* @param reportTime 上报时间
*/
void updateDeviceReportTimeAsync(Long id, LocalDateTime reportTime);
/**
* 更新设备关联的网关服务 serverId
*
* @param id 设备编号
* @param serverId 网关 serverId
*/
void updateDeviceServerIdAsync(Long id, String serverId);
/**
* 获得设备关联的网关服务 serverId
*
* @param id 设备编号
* @return 网关 serverId
*/
String getDeviceServerId(Long id);
}

View File

@@ -1,103 +0,0 @@
package cn.iocoder.yudao.module.iot.service.ota;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record.IotOtaTaskRecordPageReqVO;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* IoT OTA 升级记录 Service 接口
*/
public interface IotOtaTaskRecordService {
/**
* 批量创建 OTA 升级记录
*
* @param devices 设备列表
* @param firmwareId 固件编号
* @param taskId 任务编号
*/
void createOtaTaskRecordList(List<IotDeviceDO> devices, Long firmwareId, Long taskId);
/**
* 获取 OTA 升级记录的状态统计
*
* @param firmwareId 固件编号
* @param taskId 任务编号
* @return 状态统计 Mapkey 为状态码value 为对应状态的升级记录数量
*/
Map<Integer, Long> getOtaTaskRecordStatusStatistics(Long firmwareId, Long taskId);
/**
* 获取 OTA 升级记录
*
* @param id 编号
* @return OTA 升级记录
*/
IotOtaTaskRecordDO getOtaTaskRecord(Long id);
/**
* 获取 OTA 升级记录分页
*
* @param pageReqVO 分页查询
* @return OTA 升级记录分页
*/
PageResult<IotOtaTaskRecordDO> getOtaTaskRecordPage(@Valid IotOtaTaskRecordPageReqVO pageReqVO);
/**
* 根据 OTA 任务编号,取消未结束的升级记录
*
* @param taskId 升级任务编号
*/
void cancelTaskRecordListByTaskId(Long taskId);
/**
* 根据设备编号和记录状态,获取 OTA 升级记录列表
*
* @param deviceIds 设备编号集合
* @param statuses 记录状态集合
* @return OTA 升级记录列表
*/
List<IotOtaTaskRecordDO> getOtaTaskRecordListByDeviceIdAndStatus(Set<Long> deviceIds, Set<Integer> statuses);
/**
* 根据记录状态,获取 OTA 升级记录列表
*
* @param status 升级记录状态
* @return 升级记录列表
*/
List<IotOtaTaskRecordDO> getOtaRecordListByStatus(Integer status);
/**
* 取消 OTA 升级记录
*
* @param id 记录编号
*/
void cancelOtaTaskRecord(Long id);
/**
* 推送 OTA 升级任务记录
*
* @param record 任务记录
* @param fireware 固件信息
* @param device 设备信息
* @return 是否推送成功
*/
boolean pushOtaTaskRecord(IotOtaTaskRecordDO record, IotOtaFirmwareDO fireware, IotDeviceDO device);
/**
* 更新 OTA 升级记录进度
*
* @param device 设备信息
* @param message 设备消息
*/
void updateOtaRecordProgress(IotDeviceDO device, IotDeviceMessage message);
}

View File

@@ -1,231 +0,0 @@
package cn.iocoder.yudao.module.iot.service.ota;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record.IotOtaTaskRecordPageReqVO;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO;
import cn.iocoder.yudao.module.iot.dal.mysql.ota.IotOtaTaskRecordMapper;
import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskRecordStatusEnum;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import cn.iocoder.yudao.module.iot.service.device.message.IotDeviceMessageService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
/**
* OTA 升级任务记录 Service 实现类
*/
@Service
@Validated
@Slf4j
public class IotOtaTaskRecordServiceImpl implements IotOtaTaskRecordService {
@Resource
private IotOtaTaskRecordMapper otaTaskRecordMapper;
@Resource
private IotOtaFirmwareService otaFirmwareService;
@Resource
private IotOtaTaskService otaTaskService;
@Resource
private IotDeviceMessageService deviceMessageService;
@Resource
private IotDeviceService deviceService;
@Override
public void createOtaTaskRecordList(List<IotDeviceDO> devices, Long firmwareId, Long taskId) {
List<IotOtaTaskRecordDO> records = convertList(devices, device ->
IotOtaTaskRecordDO.builder().firmwareId(firmwareId).taskId(taskId)
.deviceId(device.getId()).fromFirmwareId(Convert.toLong(device.getFirmwareId()))
.status(IotOtaTaskRecordStatusEnum.PENDING.getStatus()).progress(0).build());
otaTaskRecordMapper.insertBatch(records);
}
@Override
public Map<Integer, Long> getOtaTaskRecordStatusStatistics(Long firmwareId, Long taskId) {
// 按照 status 枚举,初始化 countMap 为 0
Map<Integer, Long> countMap = convertMap(Arrays.asList(IotOtaTaskRecordStatusEnum.values()),
IotOtaTaskRecordStatusEnum::getStatus, iotOtaTaskRecordStatusEnum -> 0L);
// 查询记录,只返回 id、status 字段
List<IotOtaTaskRecordDO> records = otaTaskRecordMapper.selectListByFirmwareIdAndTaskId(firmwareId, taskId);
Map<Long, List<Integer>> deviceStatusesMap = convertMultiMap(records,
IotOtaTaskRecordDO::getDeviceId, IotOtaTaskRecordDO::getStatus);
// 找到第一个匹配的优先级状态,避免重复计算
deviceStatusesMap.forEach((deviceId, statuses) -> {
for (Integer priorityStatus : IotOtaTaskRecordStatusEnum.PRIORITY_STATUSES) {
if (statuses.contains(priorityStatus)) {
countMap.put(priorityStatus, countMap.get(priorityStatus) + 1);
return;
}
}
});
return countMap;
}
@Override
public IotOtaTaskRecordDO getOtaTaskRecord(Long id) {
return otaTaskRecordMapper.selectById(id);
}
@Override
public PageResult<IotOtaTaskRecordDO> getOtaTaskRecordPage(IotOtaTaskRecordPageReqVO pageReqVO) {
return otaTaskRecordMapper.selectPage(pageReqVO);
}
@Override
public void cancelTaskRecordListByTaskId(Long taskId) {
List<IotOtaTaskRecordDO> records = otaTaskRecordMapper.selectListByTaskIdAndStatus(
taskId, IotOtaTaskRecordStatusEnum.IN_PROCESS_STATUSES);
if (CollUtil.isEmpty(records)) {
return;
}
// 批量更新
Collection<Long> ids = convertSet(records, IotOtaTaskRecordDO::getId);
otaTaskRecordMapper.updateListByIdAndStatus(ids, IotOtaTaskRecordStatusEnum.IN_PROCESS_STATUSES,
IotOtaTaskRecordDO.builder().status(IotOtaTaskRecordStatusEnum.CANCELED.getStatus())
.description(IotOtaTaskRecordDO.DESCRIPTION_CANCEL_BY_TASK).build());
}
@Override
public List<IotOtaTaskRecordDO> getOtaTaskRecordListByDeviceIdAndStatus(Set<Long> deviceIds, Set<Integer> statuses) {
return otaTaskRecordMapper.selectListByDeviceIdAndStatus(deviceIds, statuses);
}
@Override
public List<IotOtaTaskRecordDO> getOtaRecordListByStatus(Integer status) {
return otaTaskRecordMapper.selectListByStatus(status);
}
@Override
public void cancelOtaTaskRecord(Long id) {
// 1. 校验记录是否存在
IotOtaTaskRecordDO record = validateUpgradeRecordExists(id);
// 2. 更新记录状态为取消
int updateCount = otaTaskRecordMapper.updateByIdAndStatus(record.getId(), IotOtaTaskRecordStatusEnum.IN_PROCESS_STATUSES,
IotOtaTaskRecordDO.builder().id(id).status(IotOtaTaskRecordStatusEnum.CANCELED.getStatus())
.description(IotOtaTaskRecordDO.DESCRIPTION_CANCEL_BY_RECORD).build());
if (updateCount == 0) {
throw exception(OTA_TASK_RECORD_CANCEL_FAIL_STATUS_ERROR);
}
// 3. 检查并更新任务状态
checkAndUpdateOtaTaskStatus(record.getTaskId());
}
@Override
public boolean pushOtaTaskRecord(IotOtaTaskRecordDO record, IotOtaFirmwareDO fireware, IotDeviceDO device) {
try {
// 1. 推送 OTA 任务记录
IotDeviceMessage message = IotDeviceMessage.buildOtaUpgrade(
fireware.getVersion(), fireware.getFileUrl(), fireware.getFileSize(),
fireware.getFileDigestAlgorithm(), fireware.getFileDigestValue());
deviceMessageService.sendDeviceMessage(message, device);
// 2. 更新 OTA 升级记录状态为进行中
int updateCount = otaTaskRecordMapper.updateByIdAndStatus(
record.getId(), IotOtaTaskRecordStatusEnum.PENDING.getStatus(),
IotOtaTaskRecordDO.builder().status(IotOtaTaskRecordStatusEnum.PUSHED.getStatus())
.description(StrUtil.format("已推送,设备消息编号({})", message.getId())).build());
Assert.isTrue(updateCount == 1, "更新设备记录({})状态失败", record.getId());
return true;
} catch (Exception ex) {
log.error("[pushOtaTaskRecord][推送 OTA 任务记录({}) 失败]", record.getId(), ex);
otaTaskRecordMapper.updateById(IotOtaTaskRecordDO.builder().id(record.getId())
.description(StrUtil.format("推送失败,错误信息({})", ex.getMessage())).build());
return false;
}
}
private IotOtaTaskRecordDO validateUpgradeRecordExists(Long id) {
IotOtaTaskRecordDO upgradeRecord = otaTaskRecordMapper.selectById(id);
if (upgradeRecord == null) {
throw exception(OTA_TASK_RECORD_NOT_EXISTS);
}
return upgradeRecord;
}
@Override
@Transactional(rollbackFor = Exception.class)
@SuppressWarnings("unchecked")
public void updateOtaRecordProgress(IotDeviceDO device, IotDeviceMessage message) {
// 1.1 参数解析
Map<String, Object> params = (Map<String, Object>) message.getParams();
String version = MapUtil.getStr(params, "version");
Assert.notBlank(version, "version 不能为空");
Integer status = MapUtil.getInt(params, "status");
Assert.notNull(status, "status 不能为空");
Assert.notNull(IotOtaTaskRecordStatusEnum.of(status), "status 状态不正确");
String description = MapUtil.getStr(params, "description");
Integer progress = MapUtil.getInt(params, "progress");
Assert.notNull(progress, "progress 不能为空");
Assert.isTrue(progress >= 0 && progress <= 100, "progress 必须在 0-100 之间");
// 1.2 查询 OTA 升级记录
List<IotOtaTaskRecordDO> records = otaTaskRecordMapper.selectListByDeviceIdAndStatus(
device.getId(), IotOtaTaskRecordStatusEnum.IN_PROCESS_STATUSES);
if (CollUtil.isEmpty(records)) {
throw exception(OTA_TASK_RECORD_UPDATE_PROGRESS_FAIL_NO_EXISTS);
}
if (records.size() > 1) {
log.warn("[updateOtaRecordProgress][message({}) 对应升级记录过多({})]", message, records);
}
IotOtaTaskRecordDO record = CollUtil.getFirst(records);
// 1.3 查询 OTA 固件
IotOtaFirmwareDO firmware = otaFirmwareService.getOtaFirmwareByProductIdAndVersion(
device.getProductId(), version);
if (firmware == null) {
throw exception(OTA_FIRMWARE_NOT_EXISTS);
}
// 2. 更新 OTA 升级记录状态
int updateCount = otaTaskRecordMapper.updateByIdAndStatus(
record.getId(), IotOtaTaskRecordStatusEnum.IN_PROCESS_STATUSES,
IotOtaTaskRecordDO.builder().status(status).description(description).progress(progress).build());
if (updateCount == 0) {
throw exception(OTA_TASK_RECORD_UPDATE_PROGRESS_FAIL_NO_EXISTS);
}
// 3. 如果升级成功,则更新设备固件版本
if (IotOtaTaskRecordStatusEnum.SUCCESS.getStatus().equals(status)) {
deviceService.updateDeviceFirmware(device.getId(), firmware.getId());
}
// 4. 如果状态是“已结束”(非进行中),则更新任务状态
if (!IotOtaTaskRecordStatusEnum.IN_PROCESS_STATUSES.contains(status)) {
checkAndUpdateOtaTaskStatus(record.getTaskId());
}
}
/**
* 检查并更新任务状态
* 如果任务下没有进行中的记录,则将任务状态更新为已结束
*/
private void checkAndUpdateOtaTaskStatus(Long taskId) {
// 如果还有进行中的记录,直接返回
Long inProcessCount = otaTaskRecordMapper.selectCountByTaskIdAndStatus(
taskId, IotOtaTaskRecordStatusEnum.IN_PROCESS_STATUSES);
if (inProcessCount > 0) {
return;
}
// 没有进行中的记录,将任务状态更新为已结束
otaTaskService.updateOtaTaskStatusEnd(taskId);
}
}

View File

@@ -1,55 +0,0 @@
package cn.iocoder.yudao.module.iot.service.ota;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.IotOtaTaskCreateReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.IotOtaTaskPageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskDO;
import javax.validation.Valid;
/**
* IoT OTA 升级任务 Service 接口
*
* @author Shelly Chan
*/
public interface IotOtaTaskService {
/**
* 创建 OTA 升级任务
*
* @param createReqVO 创建请求对象
* @return 升级任务编号
*/
Long createOtaTask(@Valid IotOtaTaskCreateReqVO createReqVO);
/**
* 取消 OTA 升级任务
*
* @param id 升级任务编号
*/
void cancelOtaTask(Long id);
/**
* 获取 OTA 升级任务
*
* @param id 升级任务编号
* @return 升级任务
*/
IotOtaTaskDO getOtaTask(Long id);
/**
* 分页查询 OTA 升级任务
*
* @param pageReqVO 分页查询请求
* @return 升级任务分页结果
*/
PageResult<IotOtaTaskDO> getOtaTaskPage(@Valid IotOtaTaskPageReqVO pageReqVO);
/**
* 更新 OTA 任务状态为已结束
*
* @param id 任务编号
*/
void updateOtaTaskStatusEnd(Long id);
}

View File

@@ -1,167 +0,0 @@
package cn.iocoder.yudao.module.iot.service.ota;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.IotOtaTaskCreateReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.IotOtaTaskPageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO;
import cn.iocoder.yudao.module.iot.dal.mysql.ota.IotOtaTaskMapper;
import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskDeviceScopeEnum;
import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskRecordStatusEnum;
import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskStatusEnum;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
/**
* IoT OTA 升级任务 Service 实现类
*
* @author Shelly Chan
*/
@Service
@Validated
@Slf4j
public class IotOtaTaskServiceImpl implements IotOtaTaskService {
@Resource
private IotOtaTaskMapper otaTaskMapper;
@Resource
private IotDeviceService deviceService;
@Resource
private IotOtaFirmwareService otaFirmwareService;
@Resource
@Lazy // 延迟,避免循环依赖报错
private IotOtaTaskRecordService otaTaskRecordService;
@Override
@Transactional(rollbackFor = Exception.class)
public Long createOtaTask(IotOtaTaskCreateReqVO createReqVO) {
// 1.1 校验固件信息是否存在
IotOtaFirmwareDO firmware = otaFirmwareService.validateFirmwareExists(createReqVO.getFirmwareId());
// 1.2 校验同一固件的升级任务名称不重复
if (otaTaskMapper.selectByFirmwareIdAndName(firmware.getId(), createReqVO.getName()) != null) {
throw exception(OTA_TASK_CREATE_FAIL_NAME_DUPLICATE);
}
// 1.3 校验设备范围信息
List<IotDeviceDO> devices = validateOtaTaskDeviceScope(createReqVO, firmware.getProductId());
// 2. 保存升级任务,直接转换
IotOtaTaskDO task = BeanUtils.toBean(createReqVO, IotOtaTaskDO.class)
.setStatus(IotOtaTaskStatusEnum.IN_PROGRESS.getStatus())
.setDeviceTotalCount(devices.size()).setDeviceSuccessCount(0);
otaTaskMapper.insert(task);
// 3. 生成设备升级记录
otaTaskRecordService.createOtaTaskRecordList(devices, firmware.getId(), task.getId());
return task.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void cancelOtaTask(Long id) {
// 1.1 校验升级任务是否存在
IotOtaTaskDO upgradeTask = validateUpgradeTaskExists(id);
// 1.2 校验升级任务是否可以取消
if (ObjUtil.notEqual(upgradeTask.getStatus(), IotOtaTaskStatusEnum.IN_PROGRESS.getStatus())) {
throw exception(OTA_TASK_CANCEL_FAIL_STATUS_END);
}
// 2. 更新升级任务状态为已取消
otaTaskMapper.updateById(IotOtaTaskDO.builder()
.id(id).status(IotOtaTaskStatusEnum.CANCELED.getStatus())
.build());
// 3. 更新升级记录状态为已取消
otaTaskRecordService.cancelTaskRecordListByTaskId(id);
}
@Override
public IotOtaTaskDO getOtaTask(Long id) {
return otaTaskMapper.selectById(id);
}
@Override
public PageResult<IotOtaTaskDO> getOtaTaskPage(IotOtaTaskPageReqVO pageReqVO) {
return otaTaskMapper.selectPage(pageReqVO);
}
@Override
public void updateOtaTaskStatusEnd(Long taskId) {
int updateCount = otaTaskMapper.updateByIdAndStatus(taskId, IotOtaTaskStatusEnum.IN_PROGRESS.getStatus(),
new IotOtaTaskDO().setStatus(IotOtaTaskStatusEnum.END.getStatus()));
if (updateCount == 0) {
log.warn("[updateOtaTaskStatusEnd][任务({})不存在或状态不是进行中,无法更新]", taskId);
}
}
private List<IotDeviceDO> validateOtaTaskDeviceScope(IotOtaTaskCreateReqVO createReqVO, Long productId) {
// 情况一:选择设备
if (Objects.equals(createReqVO.getDeviceScope(), IotOtaTaskDeviceScopeEnum.SELECT.getScope())) {
// 1.1 校验设备存在
List<IotDeviceDO> devices = deviceService.validateDeviceListExists(createReqVO.getDeviceIds());
for (IotDeviceDO device : devices) {
if (ObjUtil.notEqual(device.getProductId(), productId)) {
throw exception(DEVICE_NOT_EXISTS);
}
}
// 1.2 校验设备是否已经是该固件版本
devices.forEach(device -> {
if (Objects.equals(device.getFirmwareId(), createReqVO.getFirmwareId())) {
throw exception(OTA_TASK_CREATE_FAIL_DEVICE_FIRMWARE_EXISTS, device.getDeviceName());
}
});
// 1.3 校验设备是否已经在升级中
List<IotOtaTaskRecordDO> records = otaTaskRecordService.getOtaTaskRecordListByDeviceIdAndStatus(
convertSet(devices, IotDeviceDO::getId), IotOtaTaskRecordStatusEnum.IN_PROCESS_STATUSES);
devices.forEach(device -> {
if (CollUtil.contains(records, item -> item.getDeviceId().equals(device.getId()))) {
throw exception(OTA_TASK_CREATE_FAIL_DEVICE_OTA_IN_PROCESS, device.getDeviceName());
}
});
return devices;
}
// 情况二:全部设备
if (Objects.equals(createReqVO.getDeviceScope(), IotOtaTaskDeviceScopeEnum.ALL.getScope())) {
List<IotDeviceDO> devices = deviceService.getDeviceListByProductId(productId);
// 2.1.1 移除已经是该固件版本的设备
devices.removeIf(device -> Objects.equals(device.getFirmwareId(), createReqVO.getFirmwareId()));
// 2.1.2 移除已经在升级中的设备
List<IotOtaTaskRecordDO> records = otaTaskRecordService.getOtaTaskRecordListByDeviceIdAndStatus(
convertSet(devices, IotDeviceDO::getId), IotOtaTaskRecordStatusEnum.IN_PROCESS_STATUSES);
devices.removeIf(device -> CollUtil.contains(records,
item -> item.getDeviceId().equals(device.getId())));
// 2.2 校验是否有可升级的设备
if (CollUtil.isEmpty(devices)) {
throw exception(OTA_TASK_CREATE_FAIL_DEVICE_EMPTY);
}
return devices;
}
throw new IllegalArgumentException("不支持的设备范围:" + createReqVO.getDeviceScope());
}
private IotOtaTaskDO validateUpgradeTaskExists(Long id) {
IotOtaTaskDO upgradeTask = otaTaskMapper.selectById(id);
if (Objects.isNull(upgradeTask)) {
throw exception(OTA_TASK_NOT_EXISTS);
}
return upgradeTask;
}
}

View File

@@ -1,72 +0,0 @@
package cn.iocoder.yudao.module.iot.service.rule.data;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule.IotDataRulePageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule.IotDataRuleSaveReqVO;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataRuleDO;
import javax.validation.Valid;
import java.util.List;
/**
* IoT 数据流转规则 Service 接口
*
* @author 芋道源码
*/
public interface IotDataRuleService {
/**
* 创建数据流转规则
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createDataRule(@Valid IotDataRuleSaveReqVO createReqVO);
/**
* 更新数据流转规则
*
* @param updateReqVO 更新信息
*/
void updateDataRule(@Valid IotDataRuleSaveReqVO updateReqVO);
/**
* 删除数据流转规则
*
* @param id 编号
*/
void deleteDataRule(Long id);
/**
* 获得数据流转规则
*
* @param id 编号
* @return 数据流转规则
*/
IotDataRuleDO getDataRule(Long id);
/**
* 获得数据流转规则分页
*
* @param pageReqVO 分页查询
* @return 数据流转规则分页
*/
PageResult<IotDataRuleDO> getDataRulePage(IotDataRulePageReqVO pageReqVO);
/**
* 根据数据目的编号,获得数据流转规则列表
*
* @param sinkId 数据目的编号
* @return 是否被使用
*/
List<IotDataRuleDO> getDataRuleListBySinkId(Long sinkId);
/**
* 执行数据流转规则
*
* @param message 消息
*/
void executeDataRule(IotDeviceMessage message);
}

View File

@@ -1,259 +0,0 @@
package cn.iocoder.yudao.module.iot.service.rule.data;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.common.util.spring.SpringUtils;
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule.IotDataRulePageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule.IotDataRuleSaveReqVO;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataRuleDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataSinkDO;
import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotDataRuleMapper;
import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
import cn.iocoder.yudao.module.iot.service.rule.data.action.IotDataRuleAction;
import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.DATA_RULE_NOT_EXISTS;
/**
* IoT 数据流转规则 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
@Slf4j
public class IotDataRuleServiceImpl implements IotDataRuleService {
@Resource
private IotDataRuleMapper dataRuleMapper;
@Resource
private IotProductService productService;
@Resource
private IotDeviceService deviceService;
@Resource
private IotThingModelService thingModelService;
@Resource
private IotDataSinkService dataSinkService;
@Resource
private List<IotDataRuleAction> dataRuleActions;
@Override
@CacheEvict(value = RedisKeyConstants.DATA_RULE_LIST, allEntries = true)
public Long createDataRule(IotDataRuleSaveReqVO createReqVO) {
// 校验数据源配置和数据目的
validateDataRuleConfig(createReqVO);
// 新增
IotDataRuleDO dataRule = BeanUtils.toBean(createReqVO, IotDataRuleDO.class);
dataRuleMapper.insert(dataRule);
return dataRule.getId();
}
@Override
@CacheEvict(value = RedisKeyConstants.DATA_RULE_LIST, allEntries = true)
public void updateDataRule(IotDataRuleSaveReqVO updateReqVO) {
// 校验存在
validateDataRuleExists(updateReqVO.getId());
// 校验数据源配置和数据目的
validateDataRuleConfig(updateReqVO);
// 更新
IotDataRuleDO updateObj = BeanUtils.toBean(updateReqVO, IotDataRuleDO.class);
dataRuleMapper.updateById(updateObj);
}
@Override
@CacheEvict(value = RedisKeyConstants.DATA_RULE_LIST, allEntries = true)
public void deleteDataRule(Long id) {
// 校验存在
validateDataRuleExists(id);
// 删除
dataRuleMapper.deleteById(id);
}
private void validateDataRuleExists(Long id) {
if (dataRuleMapper.selectById(id) == null) {
throw exception(DATA_RULE_NOT_EXISTS);
}
}
/**
* 校验数据流转规则配置
*
* @param reqVO 数据流转规则保存请求VO
*/
private void validateDataRuleConfig(IotDataRuleSaveReqVO reqVO) {
// 1. 校验数据源配置
validateSourceConfigs(reqVO.getSourceConfigs());
// 2. 校验数据目的
dataSinkService.validateDataSinksExist(reqVO.getSinkIds());
}
/**
* 校验数据源配置
*
* @param sourceConfigs 数据源配置列表
*/
private void validateSourceConfigs(List<IotDataRuleDO.SourceConfig> sourceConfigs) {
// 1. 校验产品
productService.validateProductsExist(
convertSet(sourceConfigs, IotDataRuleDO.SourceConfig::getProductId));
// 2. 校验设备
deviceService.validateDeviceListExists(convertSet(sourceConfigs, IotDataRuleDO.SourceConfig::getDeviceId,
config -> ObjUtil.notEqual(config.getDeviceId(), IotDeviceDO.DEVICE_ID_ALL)));
// 3. 校验物模型存在
validateThingModelsExist(sourceConfigs);
}
/**
* 校验物模型存在
*
* @param sourceConfigs 数据源配置列表
*/
private void validateThingModelsExist(List<IotDataRuleDO.SourceConfig> sourceConfigs) {
Map<Long, Set<String>> productIdIdentifiers = new HashMap<>();
for (IotDataRuleDO.SourceConfig config : sourceConfigs) {
if (StrUtil.isEmpty(config.getIdentifier())) {
continue;
}
productIdIdentifiers.computeIfAbsent(config.getProductId(),
productId -> new HashSet<>()).add(config.getIdentifier());
}
for (Map.Entry<Long, Set<String>> entry : productIdIdentifiers.entrySet()) {
thingModelService.validateThingModelListExists(entry.getKey(), entry.getValue());
}
}
@Override
public IotDataRuleDO getDataRule(Long id) {
return dataRuleMapper.selectById(id);
}
@Override
public PageResult<IotDataRuleDO> getDataRulePage(IotDataRulePageReqVO pageReqVO) {
return dataRuleMapper.selectPage(pageReqVO);
}
@Override
public List<IotDataRuleDO> getDataRuleListBySinkId(Long sinkId) {
return dataRuleMapper.selectListBySinkId(sinkId);
}
@Cacheable(value = RedisKeyConstants.DATA_RULE_LIST,
key = "#deviceId + '_' + #method + '_' + (#identifier ?: '')")
public List<IotDataRuleDO> getDataRuleListByConditionFromCache(Long deviceId, String method, String identifier) {
// 1. 查询所有开启的数据流转规则
List<IotDataRuleDO> rules = dataRuleMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus());
// 2. 内存里过滤匹配的规则
List<IotDataRuleDO> matchedRules = new ArrayList<>();
for (IotDataRuleDO rule : rules) {
IotDataRuleDO.SourceConfig found = CollUtil.findOne(rule.getSourceConfigs(),
config -> ObjectUtils.equalsAny(config.getDeviceId(), deviceId, IotDeviceDO.DEVICE_ID_ALL)
&& Objects.equals(config.getMethod(), method)
&& (StrUtil.isEmpty(config.getIdentifier()) || ObjUtil.equal(config.getIdentifier(), identifier)));
if (found != null) {
matchedRules.add(new IotDataRuleDO().setId(rule.getId()).setSinkIds(rule.getSinkIds()));
}
}
return matchedRules;
}
@Override
public void executeDataRule(IotDeviceMessage message) {
try {
// 1. 获取匹配的数据流转规则
Long deviceId = message.getDeviceId();
String method = message.getMethod();
String identifier = IotDeviceMessageUtils.getIdentifier(message);
List<IotDataRuleDO> rules = getSelf().getDataRuleListByConditionFromCache(deviceId, method, identifier);
if (CollUtil.isEmpty(rules)) {
log.debug("[executeDataRule][设备({}) 方法({}) 标识符({}) 没有匹配的数据流转规则]",
deviceId, method, identifier);
return;
}
log.info("[executeDataRule][设备({}) 方法({}) 标识符({}) 匹配到 {} 条数据流转规则]",
deviceId, method, identifier, rules.size());
// 2. 遍历规则,执行数据流转
rules.forEach(rule -> executeDataRule(message, rule));
} catch (Exception e) {
log.error("[executeDataRule][消息({}) 执行数据流转规则异常]", message, e);
}
}
/**
* 为指定规则的所有数据目的执行数据流转
*
* @param message 设备消息
* @param rule 数据流转规则
*/
private void executeDataRule(IotDeviceMessage message, IotDataRuleDO rule) {
rule.getSinkIds().forEach(sinkId -> {
try {
// 获取数据目的配置
IotDataSinkDO dataSink = dataSinkService.getDataSinkFromCache(sinkId);
if (dataSink == null) {
log.error("[executeDataRule][规则({}) 对应的数据目的({}) 不存在]", rule.getId(), sinkId);
return;
}
if (CommonStatusEnum.isDisable(dataSink.getStatus())) {
log.info("[executeDataRule][规则({}) 对应的数据目的({}) 状态为禁用]", rule.getId(), sinkId);
return;
}
// 执行数据桥接操作
executeDataRuleAction(message, dataSink);
} catch (Exception e) {
log.error("[executeDataRule][规则({}) 数据目的({}) 执行异常]", rule.getId(), sinkId, e);
}
});
}
/**
* 执行数据流转操作
*
* @param message 设备消息
* @param dataSink 数据目的
*/
private void executeDataRuleAction(IotDeviceMessage message, IotDataSinkDO dataSink) {
dataRuleActions.forEach(action -> {
if (ObjUtil.notEqual(action.getType(), dataSink.getType())) {
return;
}
try {
action.execute(message, dataSink);
log.info("[executeDataRuleAction][消息({}) 数据目的({}) 执行成功]", message.getId(), dataSink.getId());
} catch (Exception e) {
log.error("[executeDataRuleAction][消息({}) 数据目的({}) 执行异常]", message.getId(), dataSink.getId(), e);
}
});
}
private IotDataRuleServiceImpl getSelf() {
return SpringUtils.getBean(IotDataRuleServiceImpl.class);
}
}

Some files were not shown because too many files have changed in this diff Show More